@namewta/speculo 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/template/canonical/canonical-specdev-goal-plan.md +2 -1
- package/template/canonical/canonical-specdev-grill-with-docs.md +2 -1
- package/template/canonical/canonical-specdev-spec.md +2 -1
- package/template/canonical/canonical-specdev-tickets.md +2 -1
- package/template/commands/handoff.md +1 -1
- package/template/commands/status.md +1 -1
- package/template/workflows/specdev/I-init-setup/tracking-template.md +1 -1
- package/template/workflows/specdev/INDEX.md +1 -1
- package/template/workflows/specdev/README.md +3 -3
- package/template/workflows/specdev/T-triage/T-triage.md +44 -10
- package/template/workflows/specdev/T-triage/capture-protocol.md +89 -0
- package/template/workflows/specdev/T-triage/capture-template.md +42 -0
- package/template/workflows/specdev/T-triage/intake-protocol.md +2 -0
- package/template/workflows/specdev/T-triage/issue-record-template.md +27 -0
- package/template/workflows/specdev/T-triage/references/classification-map.md +10 -9
- package/template/workflows/specdev/T-triage/references/public-projection.md +27 -4
- package/template/workflows/specdev/T-triage/tools/capture-status.mjs +125 -0
- package/template/workflows/specdev/common/README.md +1 -0
- package/template/workflows/specdev/common/rules/artifact-contract.md +2 -1
- package/template/workflows/specdev/common/rules/workflow-routing.md +2 -1
- package/template/workflows/specdev/common/rules/workflow-state-and-lifecycle.md +1 -0
- package/template/workflows/specdev/common/schemas/capture.schema.json +15 -0
- package/template/workflows/specdev/common/tools/README.md +7 -0
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +132 -4
package/README.md
CHANGED
|
@@ -73,13 +73,13 @@ After initialization, the target project gains the following AI agent-callable a
|
|
|
73
73
|
| Workflow | Work Entries | Description |
|
|
74
74
|
|---|---:|---|
|
|
75
75
|
| **learning** | 9 | Evidence-aware learning for projects, products, subjects, languages, and skills: complete 30–40 minute plain-language lessons, Socratic inquiry lessons, Goal-Plan compilation for later external /goal execution (teach a mine-unit of ≤15 lessons, then fan out miners), single-file homework review, optional retention review, and provenance-preserving topic synthesis |
|
|
76
|
-
| **specdev** | 14 | Local-first specification-driven development: archive, code review, diagnosis, grilling, implementation, setup, learning, goal planning, prototyping, architecture review, specs, tickets, triage (intake / reconcile / publish), and wayfinding |
|
|
76
|
+
| **specdev** | 14 | Local-first specification-driven development: archive, code review, diagnosis, grilling, implementation, setup, learning, goal planning, prototyping, architecture review, specs, tickets, triage (intake / reconcile / publish / capture), and wayfinding |
|
|
77
77
|
| **ops** | 3 | Host inventory and project deployment: initialize, host manage, and APP/shared-service deploy with dual documentation |
|
|
78
78
|
| **person** | 2 | Persona-methodology and rigorous deliberation workflows (Mao Zedong Cognitive OS; Bidirectional Steelman Deliberation) |
|
|
79
79
|
|
|
80
80
|
Every workflow ships an `INDEX.md` as its auto-generated work catalog. Work entries follow `<Letter>-<work_name>/<Letter>-<work_name>.md` naming with progressive-disclosure sub-files, and resolve runtime paths via `<Path>{roots.xxx}/...</Path>` pointers in `workspace.json`.
|
|
81
81
|
|
|
82
|
-
SpecDev T-triage stays the only remote boundary. Use **intake** to freeze a source, **reconcile** to close that original source Issue after local completion,
|
|
82
|
+
SpecDev T-triage stays the only remote boundary. Use **intake** to freeze a source, **reconcile** to close that original source Issue after local completion, **publish** to project each completed Ticket as a classified GitHub Issue (local-origin work included), and **capture** to park a not-yet-Change note as a still-open GitHub Issue. GitHub is a projection, counter, and inbox — not the development source of truth. Run both reconcile and publish when an intake Change should also count its tickets. Capture does not create a Change.
|
|
83
83
|
|
|
84
84
|
## Documentation
|
|
85
85
|
|
package/package.json
CHANGED
|
@@ -452,6 +452,7 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
452
452
|
| 来源快照 | `specdev/changes/{change}/source.md` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
|
|
453
453
|
| 分诊 | `specdev/changes/{change}/triage.md` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
|
|
454
454
|
| 发布账本 | `specdev/changes/{change}/publish.md` | 票级 GitHub 投影的编号、标签、marker、state 和发布计数 | Ticket 契约、Evidence 原文或源 Issue 关闭 |
|
|
455
|
+
| 捕获账本 | `specdev/capture.md` | 尚未成 Change 的记事项、GitHub inbox 编号、标签、marker 和 inbox 计数;缺失合法 | Change、Ticket、Evidence 或已完成票的发布投影 |
|
|
455
456
|
| 诊断 | `specdev/changes/{change}/diagnosis.md` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
|
|
456
457
|
| 设计日志 | `specdev/changes/{change}/LOG.md` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
|
|
457
458
|
| 设计树 | `specdev/changes/{change}/design-tree.json` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
|
|
@@ -494,7 +495,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
|
|
|
494
495
|
|
|
495
496
|
当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
|
|
496
497
|
|
|
497
|
-
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。
|
|
498
|
+
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。GitHub 上由 capture 记下的 inbox Issue 也不是开发权威;inbox 计数以 `specdev/capture.md` 为准,缺失该文件视为空 inbox。
|
|
498
499
|
|
|
499
500
|
代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 下方 `<deviation-control>` 标签 退回相应工件修订。
|
|
500
501
|
|
|
@@ -253,6 +253,7 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
253
253
|
| 来源快照 | `specdev/changes/{change}/source.md` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
|
|
254
254
|
| 分诊 | `specdev/changes/{change}/triage.md` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
|
|
255
255
|
| 发布账本 | `specdev/changes/{change}/publish.md` | 票级 GitHub 投影的编号、标签、marker、state 和发布计数 | Ticket 契约、Evidence 原文或源 Issue 关闭 |
|
|
256
|
+
| 捕获账本 | `specdev/capture.md` | 尚未成 Change 的记事项、GitHub inbox 编号、标签、marker 和 inbox 计数;缺失合法 | Change、Ticket、Evidence 或已完成票的发布投影 |
|
|
256
257
|
| 诊断 | `specdev/changes/{change}/diagnosis.md` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
|
|
257
258
|
| 设计日志 | `specdev/changes/{change}/LOG.md` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
|
|
258
259
|
| 设计树 | `specdev/changes/{change}/design-tree.json` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
|
|
@@ -295,7 +296,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
|
|
|
295
296
|
|
|
296
297
|
当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
|
|
297
298
|
|
|
298
|
-
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。
|
|
299
|
+
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。GitHub 上由 capture 记下的 inbox Issue 也不是开发权威;inbox 计数以 `specdev/capture.md` 为准,缺失该文件视为空 inbox。
|
|
299
300
|
|
|
300
301
|
代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 下方 `<deviation-control>` 标签 退回相应工件修订。
|
|
301
302
|
|
|
@@ -365,6 +365,7 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
365
365
|
| 来源快照 | `specdev/changes/{change}/source.md` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
|
|
366
366
|
| 分诊 | `specdev/changes/{change}/triage.md` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
|
|
367
367
|
| 发布账本 | `specdev/changes/{change}/publish.md` | 票级 GitHub 投影的编号、标签、marker、state 和发布计数 | Ticket 契约、Evidence 原文或源 Issue 关闭 |
|
|
368
|
+
| 捕获账本 | `specdev/capture.md` | 尚未成 Change 的记事项、GitHub inbox 编号、标签、marker 和 inbox 计数;缺失合法 | Change、Ticket、Evidence 或已完成票的发布投影 |
|
|
368
369
|
| 诊断 | `specdev/changes/{change}/diagnosis.md` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
|
|
369
370
|
| 设计日志 | `specdev/changes/{change}/LOG.md` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
|
|
370
371
|
| 设计树 | `specdev/changes/{change}/design-tree.json` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
|
|
@@ -407,7 +408,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
|
|
|
407
408
|
|
|
408
409
|
当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
|
|
409
410
|
|
|
410
|
-
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。
|
|
411
|
+
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。GitHub 上由 capture 记下的 inbox Issue 也不是开发权威;inbox 计数以 `specdev/capture.md` 为准,缺失该文件视为空 inbox。
|
|
411
412
|
|
|
412
413
|
代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 下方 `<deviation-control>` 标签 退回相应工件修订。
|
|
413
414
|
|
|
@@ -508,6 +508,7 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
508
508
|
| 来源快照 | `specdev/changes/{change}/source.md` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
|
|
509
509
|
| 分诊 | `specdev/changes/{change}/triage.md` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
|
|
510
510
|
| 发布账本 | `specdev/changes/{change}/publish.md` | 票级 GitHub 投影的编号、标签、marker、state 和发布计数 | Ticket 契约、Evidence 原文或源 Issue 关闭 |
|
|
511
|
+
| 捕获账本 | `specdev/capture.md` | 尚未成 Change 的记事项、GitHub inbox 编号、标签、marker 和 inbox 计数;缺失合法 | Change、Ticket、Evidence 或已完成票的发布投影 |
|
|
511
512
|
| 诊断 | `specdev/changes/{change}/diagnosis.md` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
|
|
512
513
|
| 设计日志 | `specdev/changes/{change}/LOG.md` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
|
|
513
514
|
| 设计树 | `specdev/changes/{change}/design-tree.json` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
|
|
@@ -550,7 +551,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
|
|
|
550
551
|
|
|
551
552
|
当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
|
|
552
553
|
|
|
553
|
-
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。
|
|
554
|
+
`specdev/changes/{change}/source.md` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `specdev/changes/{change}/publish.md` 为准。GitHub 上由 capture 记下的 inbox Issue 也不是开发权威;inbox 计数以 `specdev/capture.md` 为准,缺失该文件视为空 inbox。
|
|
554
555
|
|
|
555
556
|
代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 下方 `<deviation-control>` 标签 退回相应工件修订。
|
|
556
557
|
|
|
@@ -35,7 +35,7 @@ disable-model-invocation: true
|
|
|
35
35
|
|
|
36
36
|
如果用户传入了参数,将其视为对下一个会话重点内容的描述,并据此定制文档。
|
|
37
37
|
|
|
38
|
-
交接范围包含 SpecDev change 时,引用该 change 的 `source.md`、`triage.md`、`publish.md`(若请求过)、`.status.json` 和当前 owning 工件,不复制正文。若 `external_action` 为 `pending-close` 或 `close-failed`,必须记录准确远程 locator、已完成步骤、授权状态和恢复入口 `T-triage`;不得把待关闭误报为本地未完成。若 `publish_action` 为 `pending` 或 `publish-failed`,必须记录账本路径、失败行和下一步,恢复入口仍是 T-triage publish。
|
|
38
|
+
交接范围包含 SpecDev change 时,引用该 change 的 `source.md`、`triage.md`、`publish.md`(若请求过)、`.status.json` 和当前 owning 工件,不复制正文。若 `external_action` 为 `pending-close` 或 `close-failed`,必须记录准确远程 locator、已完成步骤、授权状态和恢复入口 `T-triage`;不得把待关闭误报为本地未完成。若 `publish_action` 为 `pending` 或 `publish-failed`,必须记录账本路径、失败行和下一步,恢复入口仍是 T-triage publish。交接范围包含尚未成 Change 的 inbox 记事项时,引用 `<Path>{roots.state}/specdev/capture.md</Path>`(若存在)的失败行与恢复入口 T-triage capture,不创建 change。
|
|
39
39
|
|
|
40
40
|
## 路径引用规范
|
|
41
41
|
|
|
@@ -11,7 +11,7 @@ keywords: [status, 状态, active, blocked]
|
|
|
11
11
|
1. 读取 `<Path>{roots.state}/workspace.json</Path>`,解析 `<Path>{roots.config}</Path>`(不存在时以默认值静默降级),获取全部已安装 workflow/state 根。
|
|
12
12
|
2. 扫描 `<Path>{roots.workflows}/{workflow}/INDEX.md</Path>`,得到已安装 workflow ids。
|
|
13
13
|
3. 对每个 id 读取 `<Path>{roots.state}/{workflow}/status.json</Path>`,按该 workflow 自己的 schema 解释,不把 SpecDev 字段套到其他 workflow。Learning schema v2 的 active/archived entry 携带 stable `change_id`、kind、parent/root、current locator、current_work、Homework 和 retention projection;递归 Change 位于 `changes/**` 或 `archive/**`,位置历史读取 `<Path>{roots.state}/learning/locations.json</Path>`。SpecDev 继续使用 schema v5 根级 `changes/<change>`/`archive/YYYY-MM/<change>`;Ops schema v3 使用 hosts/projects/deployments/allocations/bindings/releases 资源表,运行记录在 hosts/{host_id}/runs/{run_id} 或 releases/{run_id};保留的非空 v2 只作为旧证据读取,不转换旧批准。不得按 change 名跨 workflow 合并。
|
|
14
|
-
4. 报告 active 数量、各 Change 的 `current_work`、去重后的 `works_run`、生命周期、parent/root、最近更新时间和停滞 Change(`.status.json` 超过 14 天未更新)。SpecDev 额外报告调查 claims、triage `external_action` / `publish_action`,以及 active+archive
|
|
14
|
+
4. 报告 active 数量、各 Change 的 `current_work`、去重后的 `works_run`、生命周期、parent/root、最近更新时间和停滞 Change(`.status.json` 超过 14 天未更新)。SpecDev 额外报告调查 claims、triage `external_action` / `publish_action`,以及 active+archive `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` 汇总的 `published_issues`(含 `origin:local` / `origin:intake`)、`publish_skipped`、`publish_failed`;并报告 workspace `<Path>{roots.state}/specdev/capture.md</Path>` 的 `inbox_open` / `inbox_intaken` / `inbox_waived`(缺失视为空 inbox,不计入 `published_issues`)。数字以本地账本为准,不扫 GitHub 当权威。可用 `<Path>{roots.workflows}/specdev/T-triage/tools/publish-status.mjs</Path>` 与 `<Path>{roots.workflows}/specdev/T-triage/tools/capture-status.mjs</Path>`。Learning 额外报告 domain/topic、Lesson/Homework、immediate/retention 和 synthesis 状态;Ops 按主机和 APP/公共服务分别展示 deployment_id、observed_version、路径、最后部署时间、共享绑定、plan/approval/run 状态与双边 docs-receipt。docs_pending/unknown 必须列异常;普通状态报告不读取或输出 private 明文账本,不把 OPS 资源套入 active/archived Change 统计。
|
|
15
15
|
5. 报告 archived 数量和完整 scope/project/change locator;Learning 同时读取 `context/REVIEW.md` 汇总到期 Review、topic evidence status 和需要刷新内容。预期归档目录或 `.status.json` 缺失、active/archived 重叠、parent cycle、位置登记不一致、未知 schema、断开链接和 malformed 目录均列为异常,不自动修复。
|
|
16
16
|
6. 报告没有 workflow 资产的孤立状态根,以及缺少状态根的已安装 workflow;不自动修复。
|
|
17
17
|
7. 用户要求持久化时写入 `<Path>{roots.state}/commands/status/{date}-workspace-{topic}[-NN].md</Path>`,并在报告中列出本次扫描的 workflow 选择。
|
|
@@ -10,4 +10,4 @@ SpecDev 只使用本地 Markdown/JSON 工件跟踪开发。远程 Issue、URL
|
|
|
10
10
|
- 工件状态应在同一次操作中同步,避免入口状态、Ticket 状态与 Map 状态漂移。
|
|
11
11
|
- 完成条件:全部必需 Ticket 为 `done` 或有批准的 `cancelled`,证据齐全,无未批准 deviation,change 级验证通过。
|
|
12
12
|
- 归档后的 `<Path>{roots.state}/specdev/archive/YYYY-MM/{change}/</Path>` 默认只读;后续纠正通过新 change 和 supersedes 链完成。
|
|
13
|
-
- 可关闭的远程来源在本地完成后由 Triage reconcile;`closed`、显式 `waived` 或 `not-applicable` 后才归档。完成后若要把 Ticket 记到 GitHub,由 T-triage publish 写入 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`;`pending`/`publish-failed`
|
|
13
|
+
- 可关闭的远程来源在本地完成后由 Triage reconcile;`closed`、显式 `waived` 或 `not-applicable` 后才归档。完成后若要把 Ticket 记到 GitHub,由 T-triage publish 写入 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`;`pending`/`publish-failed` 不可归档。尚未成 Change 的记事项由 T-triage capture 写入 `<Path>{roots.state}/specdev/capture.md</Path>`(缺失合法),Issue 保持 open,不创建 change。发布计数与 inbox 计数都以对应本地账本为准,标签词表见 T-triage classification-map。远程 Issue 仍不是开发权威。
|
|
@@ -3,7 +3,7 @@ id: specdev
|
|
|
3
3
|
type: workflow
|
|
4
4
|
workflow: specdev
|
|
5
5
|
name: SpecDev Workflow
|
|
6
|
-
description: 以本地工件为唯一开发权威,从来源冻结、诊断、设计、原型、规格、Ticket、编排和审查推进到证据驱动实现、远程 reconcile
|
|
6
|
+
description: 以本地工件为唯一开发权威,从来源冻结、诊断、设计、原型、规格、Ticket、编排和审查推进到证据驱动实现、远程 reconcile 或票级发布投影、记事项捕获与知识归档。
|
|
7
7
|
keywords: [specdev, local-first, 规格驱动开发, decision-complete, prototype, code-review, TDD, 证据]
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 工件链与权威
|
|
10
10
|
|
|
11
|
-
大需求 → W 的 Initiative 候选图 → 每个 change 自己的 G/Grill → S/Spec → T 的计划型 Ticket 与 tickets-map → P 的 Goal → I/实现与 Evidence → Goal 集成验收 → 按需学习/远程 reconcile 或 publish
|
|
11
|
+
大需求 → W 的 Initiative 候选图 → 每个 change 自己的 G/Grill → S/Spec → T 的计划型 Ticket 与 tickets-map → P 的 Goal → I/实现与 Evidence → Goal 集成验收 → 按需学习/远程 reconcile 或 publish/归档。尚未成 Change 的记事项可先 T-triage capture 到 GitHub inbox,再逐条 intake。
|
|
12
12
|
|
|
13
13
|
已明确的小请求直接进入适用阶段;无需为了路由而创建 Triage。跨 change 实现由 P 统一管理。
|
|
14
14
|
|
|
@@ -47,7 +47,7 @@ CLI 初始化和刷新保持原 namespace、三方配置合并、schema migrator
|
|
|
47
47
|
|
|
48
48
|
## 场景路由
|
|
49
49
|
|
|
50
|
-
不清晰的大需求选 W,单 change 的高影响决定选 G,Ready Spec 拆票选 T-tickets,一个或多个 Ready change 的 Goal 选 P-goal-plan
|
|
50
|
+
不清晰的大需求选 W,单 change 的高影响决定选 G,Ready Spec 拆票选 T-tickets,一个或多个 Ready change 的 Goal 选 P-goal-plan;来源冻结/远程回写/完成后记账/尚未成 Change 的记事项才选 T-triage。精确条件与全部出口见 `<Path>{roots.workflows}/specdev/common/rules/workflow-routing.md</Path>`。
|
|
51
51
|
|
|
52
52
|
## Work 条目
|
|
53
53
|
|
|
@@ -66,7 +66,7 @@ CLI 初始化和刷新保持原 namespace、三方配置合并、schema migrator
|
|
|
66
66
|
- **R-review-architecture** — 架构审查:从用户指定范围或 Git 热点扫描代码库中的结构性坏味道、代码 judo 机会和维护性风险,以中文 Markdown 记录高置信候选,并对用户选择的一个方案运行设计树访谈。
|
|
67
67
|
- **S-spec** — 编写 Spec:综合已知事实、设计决定、诊断与代码现状,产出以外部行为和验收合同为权威的 Ready Spec。
|
|
68
68
|
- **T-tickets** — 编写计划型 Tickets:将已澄清的 Spec 或等价获批计划拆为可验收的实施 Ticket,并绑定真实项目 Skill;不用于探索未知需求或执行代码。
|
|
69
|
-
- **T-triage** — 请求分诊:需要冻结外部来源、审计摄入、对 completed change 回写来源 Issue
|
|
69
|
+
- **T-triage** — 请求分诊:需要冻结外部来源、审计摄入、对 completed change 回写来源 Issue、把已完成 Ticket 投影为带分类标签的 GitHub Issue,或把尚未成 Change 的记事项写成仍 open 的 GitHub Issue 时使用;已清晰的本地需求不必为了路由而经本入口。
|
|
70
70
|
- **W-wayfinder** — 探索大需求与 Change 边界:大需求的 change 边界或实施路线尚不可见时建立探索地图,并分别澄清各 change;已有清晰 Spec 时不触发。
|
|
71
71
|
|
|
72
72
|
<!-- AUTO-INDEX-END -->
|
|
@@ -3,15 +3,15 @@ id: specdev/triage
|
|
|
3
3
|
type: workflow-entry
|
|
4
4
|
workflow: specdev
|
|
5
5
|
name: 请求分诊
|
|
6
|
-
description: 需要冻结外部来源、审计摄入、对 completed change 回写来源 Issue
|
|
7
|
-
keywords: [triage, 摄入, import, issue, reconcile, publish, close, 风险, 路由]
|
|
6
|
+
description: 需要冻结外部来源、审计摄入、对 completed change 回写来源 Issue、把已完成 Ticket 投影为带分类标签的 GitHub Issue,或把尚未成 Change 的记事项写成仍 open 的 GitHub Issue 时使用;已清晰的本地需求不必为了路由而经本入口。
|
|
7
|
+
keywords: [triage, 摄入, import, issue, reconcile, publish, capture, close, 风险, 路由]
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# 请求分诊
|
|
11
11
|
|
|
12
12
|
> 激活本 Work 后,先读取 `<Path>{roots.workflows}/specdev/README.md</Path>`,再执行本入口。
|
|
13
13
|
|
|
14
|
-
Triage 是 SpecDev
|
|
14
|
+
Triage 是 SpecDev 唯一的远程摄入、关闭、发布投影与记事项捕获边界。开发期间,change 级 `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>`、`<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`、`<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`、Spec、Ticket、Map、Goal Plan、Evidence 和状态文件是开发唯一权威;workspace 级 `<Path>{roots.state}/specdev/capture.md</Path>` 是尚未成 Change 的 inbox 计数权威(缺失合法)。远程系统只保存原始请求、经确认后的完成通知、票级发布记录,以及仍 open 的记事项。GitHub 不是 tracker,也不是开发权威。
|
|
15
15
|
|
|
16
16
|
## 读取范围
|
|
17
17
|
|
|
@@ -19,22 +19,23 @@ Triage 是 SpecDev 唯一的远程摄入、关闭与发布投影边界。开发
|
|
|
19
19
|
2. 再读取 `<Path>{roots.workflows}/specdev/common/rules/activation-and-memory.md</Path>`,按当前分支、状态和关键词定位最小相关工件。
|
|
20
20
|
3. 只在本 Work 明确要求恢复、冲突、执行安全或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
|
|
21
21
|
|
|
22
|
-
普通本地请求直接进入适用 Work,不为了完成路由额外创建来源工件。用户明确要求来源审计时仍执行完整 intake;缺陷根因诊断仍交 D,不删除风险分诊与远程回写能力。完成后要记账才选 publish。
|
|
22
|
+
普通本地请求直接进入适用 Work,不为了完成路由额外创建来源工件。用户明确要求来源审计时仍执行完整 intake;缺陷根因诊断仍交 D,不删除风险分诊与远程回写能力。完成后要记账才选 publish。尚未成 Change、只想先在 GitHub 留一条仍 open 的记录时才选 capture。
|
|
23
23
|
|
|
24
24
|
## 模式
|
|
25
25
|
|
|
26
26
|
- **intake**:冻结输入、创建或恢复 change、分类并返回下一 Work。
|
|
27
27
|
- **reconcile**:本地 change 已完成后,确认远程完成摘要并关闭支持的 GitHub 来源 Issue;不重新分诊或修改开发契约。
|
|
28
28
|
- **publish**:本地 change 已完成后,把计划内 Ticket 投影为带分类标签的 GitHub Issue,用脱敏 Evidence 关闭它们,并留下 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` 账本。标签必须包含 `specdev:published`。
|
|
29
|
+
- **capture**:尚未有 Change 时,把记事项写成仍 open 的 GitHub Issue,并留下 `<Path>{roots.state}/specdev/capture.md</Path>`。标签必须包含 `specdev:captured`。不创建 change,不写 change 级 triage 工件,`mode=capture` 不得出现在 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`。
|
|
29
30
|
|
|
30
31
|
## 共同启动
|
|
31
32
|
|
|
32
33
|
1. 解析 roots,按 `<Path>{roots.workflows}/specdev/README.md</Path>` 读取全局状态和 change 状态。
|
|
33
|
-
2. Intake 可以创建 change;reconcile 与 publish 必须选择一个已存在的 change。publish 的对象必须已 completed。
|
|
34
|
-
3.
|
|
35
|
-
4. 重读已有 `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`,不覆盖已冻结的来源。publish 在本地源缺失这些文件时可以按协议补建轻量快照,仍不得虚构 GitHub locator。
|
|
34
|
+
2. Intake 可以创建 change;reconcile 与 publish 必须选择一个已存在的 change。publish 的对象必须已 completed。capture 既不创建也不选择 change,不写 `current_work`,不加载 `<Path>{roots.workflows}/specdev/common/rules/change-completion.md</Path>`。
|
|
35
|
+
3. 仅 intake、reconcile 与 publish:若该 change 的 `current_work` 为 null,设置为 `specdev/triage`;指向其他 Work 时先恢复或完成显式 handoff。capture 跳过本步。
|
|
36
|
+
4. 重读已有 `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`,不覆盖已冻结的来源。publish 在本地源缺失这些文件时可以按协议补建轻量快照,仍不得虚构 GitHub locator。capture 不读、不写这些文件。
|
|
36
37
|
|
|
37
|
-
`external_action` 只描述源 Issue。`publish_action`
|
|
38
|
+
`external_action` 只描述源 Issue。`publish_action` 只描述票级发布投影。capture 行 state 只描述 inbox 记录。三者互不覆盖。未授权时远程写入为零。
|
|
38
39
|
|
|
39
40
|
## Intake
|
|
40
41
|
|
|
@@ -88,9 +89,28 @@ node <Path>{roots.workflows}/specdev/T-triage/tools/publish-status.mjs</Path> \
|
|
|
88
89
|
--state-root <Path>{roots.state}/specdev</Path>
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
## Capture
|
|
93
|
+
|
|
94
|
+
尚未成 Change、用户要求先把记事项写到 GitHub 时,加载:
|
|
95
|
+
|
|
96
|
+
- `<Path>{roots.workflows}/specdev/T-triage/capture-protocol.md</Path>`;
|
|
97
|
+
- `<Path>{roots.workflows}/specdev/T-triage/references/classification-map.md</Path>`;
|
|
98
|
+
- `<Path>{roots.workflows}/specdev/T-triage/references/public-projection.md</Path>`。
|
|
99
|
+
|
|
100
|
+
按协议走硬门、确认表、dry-run,再调用 github-npm-ops 的 `issue-search` / `issue-create`。不调用 `issue-comment-close`。账本写 `<Path>{roots.state}/specdev/capture.md</Path>`。Issue 正文用 `<Path>{roots.workflows}/specdev/T-triage/issue-record-template.md</Path>`。默认 1 条记录对应一次未来 intake / 一个未来 Change。攒多了再处理:批量 capture,然后逐条 intake 或先走 W;不把多条 locator 写入同一个 source。
|
|
101
|
+
|
|
102
|
+
成功留下仍 open 的 inbox Issue。intake 消费到同一 locator 时把对应行标为 `intaken`。用户放弃为 `waived`。capture 不挡 Archive,因为没有 change 可归档。
|
|
103
|
+
|
|
104
|
+
workspace inbox 计数:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
node <Path>{roots.workflows}/specdev/T-triage/tools/capture-status.mjs</Path> \
|
|
108
|
+
--state-root <Path>{roots.state}/specdev</Path>
|
|
109
|
+
```
|
|
110
|
+
|
|
91
111
|
## 状态与验证
|
|
92
112
|
|
|
93
|
-
运行:
|
|
113
|
+
intake、reconcile 或 publish 运行:
|
|
94
114
|
|
|
95
115
|
```bash
|
|
96
116
|
node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
@@ -98,7 +118,16 @@ node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
|
98
118
|
<Path>{roots.state}/specdev/changes/{change}</Path>
|
|
99
119
|
```
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
capture 在账本存在时运行:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
125
|
+
--capture <Path>{roots.state}/specdev/capture.md</Path>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
缺失 `<Path>{roots.state}/specdev/capture.md</Path>` 合法,不要为了校验去创建它。
|
|
129
|
+
|
|
130
|
+
验证通过后,intake / reconcile / publish 原子重读 source、triage、publish(若请求过)和 change 状态;成功时将 `specdev/triage` 去重加入 `works_run` 并清空 `current_work`;可恢复失败保留 `current_work` 和具体 blocker。capture 不改任何 change 状态。
|
|
102
131
|
|
|
103
132
|
## 完成标准
|
|
104
133
|
|
|
@@ -109,6 +138,7 @@ node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
|
109
138
|
- 未授权时远程写入为零;
|
|
110
139
|
- Reconcile 可从失败检查点幂等恢复;
|
|
111
140
|
- Publish 可从 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` 失败行幂等恢复,cancelled 默认 skip,本地源计入发布数;
|
|
141
|
+
- Capture 可从 `<Path>{roots.state}/specdev/capture.md</Path>` 失败行幂等恢复,Issue 保持 open,不创建 Change;
|
|
112
142
|
- 状态、验证结果和下一 Work 完整路径已返回。
|
|
113
143
|
|
|
114
144
|
## 子文件引用
|
|
@@ -116,11 +146,15 @@ node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
|
116
146
|
- Intake:`<Path>{roots.workflows}/specdev/T-triage/intake-protocol.md</Path>`
|
|
117
147
|
- Reconcile:`<Path>{roots.workflows}/specdev/T-triage/reconcile-protocol.md</Path>`
|
|
118
148
|
- Publish:`<Path>{roots.workflows}/specdev/T-triage/publish-protocol.md</Path>`
|
|
149
|
+
- Capture:`<Path>{roots.workflows}/specdev/T-triage/capture-protocol.md</Path>`
|
|
119
150
|
- Source 模板:`<Path>{roots.workflows}/specdev/T-triage/source-template.md</Path>`
|
|
120
151
|
- Triage 模板:`<Path>{roots.workflows}/specdev/T-triage/triage-template.md</Path>`
|
|
121
152
|
- Publish 模板:`<Path>{roots.workflows}/specdev/T-triage/publish-template.md</Path>`
|
|
153
|
+
- Capture 模板:`<Path>{roots.workflows}/specdev/T-triage/capture-template.md</Path>`
|
|
122
154
|
- 分类映射:`<Path>{roots.workflows}/specdev/T-triage/references/classification-map.md</Path>`
|
|
123
155
|
- 公共投影:`<Path>{roots.workflows}/specdev/T-triage/references/public-projection.md</Path>`
|
|
124
156
|
- Issue 正文:`<Path>{roots.workflows}/specdev/T-triage/issue-body-template.md</Path>`
|
|
157
|
+
- 记事项正文:`<Path>{roots.workflows}/specdev/T-triage/issue-record-template.md</Path>`
|
|
125
158
|
- 关闭评论:`<Path>{roots.workflows}/specdev/T-triage/close-comment-template.md</Path>`
|
|
126
159
|
- 发布计数:`<Path>{roots.workflows}/specdev/T-triage/tools/publish-status.mjs</Path>`
|
|
160
|
+
- inbox 计数:`<Path>{roots.workflows}/specdev/T-triage/tools/capture-status.mjs</Path>`
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Capture Protocol
|
|
2
|
+
|
|
3
|
+
仅在 Triage `mode=capture` 时加载。目标是把尚未成 Change 的记事项写成仍 open 的 GitHub Issue,并留下 workspace 级 `<Path>{roots.state}/specdev/capture.md</Path>`。
|
|
4
|
+
|
|
5
|
+
GitHub 是 inbox 和计数器,不是 tracker,也不是开发权威。capture 不创建、不选择、不改写任何 change。票级完成后投影走 publish,源 Issue 关闭走 reconcile,外部输入冻结走 intake;四者互不覆盖。
|
|
6
|
+
|
|
7
|
+
## 1. 触发与对象
|
|
8
|
+
|
|
9
|
+
用户必须给出至少一条记事项(标题、现在时摘要、分类)。禁止扫整个 workspace 自动发。允许一次授权批量多条;默认 **1 条记录 = 1 次未来 intake / 1 个未来 Change**。v1 不写 `related_locators`,不把 N 条 locator 塞进同一个未来 source。
|
|
10
|
+
|
|
11
|
+
相关但目标不清 → 先 capture 为多条 inbox,以后走 `<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>` 或逐条 intake,不在本协议合并。
|
|
12
|
+
|
|
13
|
+
可选开关:排除 id 列表。目标 repo 默认从 `git remote` 推断,必须展示确认。
|
|
14
|
+
|
|
15
|
+
capture **不得**:
|
|
16
|
+
|
|
17
|
+
- 创建 change 目录或 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 下任何工件;
|
|
18
|
+
- 写 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>` / `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` / `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`;
|
|
19
|
+
- 设置任何 change 的 `current_work`;
|
|
20
|
+
- 加载 `<Path>{roots.workflows}/specdev/common/rules/change-completion.md</Path>`;
|
|
21
|
+
- 调用 `issue-comment-close` 或关闭刚创建的 Issue;
|
|
22
|
+
- 在本地复制 inbox 正文副本;
|
|
23
|
+
- 打 `specdev:published`、`specdev:change`、`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`。
|
|
24
|
+
|
|
25
|
+
只写 `<Path>{roots.state}/specdev/capture.md</Path>` 和经授权的远程 inbox Issue。缺失该账本合法,首次 capture 才创建。
|
|
26
|
+
|
|
27
|
+
## 2. 硬门
|
|
28
|
+
|
|
29
|
+
失败即停,零远程写入。已有账本行保持原 state。
|
|
30
|
+
|
|
31
|
+
1. 每条纳入记录有 id(`YYYY-MM-DD-<kebab>`)、现在时摘要、且 `kind` 为 bug、feature、refactor、investigation、operations、documentation 或 review。禁止 `mixed`。
|
|
32
|
+
2. 调用 `<Path>{roots.skills}/github-npm-ops/SKILL.md</Path>` 能读目标 repo、能 dry-run `issue-create`。本协议只用 `issue-search` / `issue-create`。
|
|
33
|
+
3. 分类映射见 `<Path>{roots.workflows}/specdev/T-triage/references/classification-map.md</Path>`。缺 type 标签时 dry-run 表列出将创建的标签,授权后才 `gh label create`。
|
|
34
|
+
4. 用户看见完整 dry-run 表(id、标题、标签、正文、reason)并给出 **本次** 明确确认。
|
|
35
|
+
|
|
36
|
+
origin 固定 `local`。marker:`specdev:capture:<id>`。
|
|
37
|
+
|
|
38
|
+
## 3. 准备记录
|
|
39
|
+
|
|
40
|
+
按 `<Path>{roots.workflows}/specdev/T-triage/references/public-projection.md</Path>` 脱敏。现在时,不是完成后的过去时,也不是派工祈使句。
|
|
41
|
+
|
|
42
|
+
Issue 正文:`<Path>{roots.workflows}/specdev/T-triage/issue-record-template.md</Path>`。
|
|
43
|
+
|
|
44
|
+
用 `<Path>{roots.workflows}/specdev/T-triage/capture-template.md</Path>` 写或更新 `<Path>{roots.state}/specdev/capture.md</Path>`。先把纳入行标 `planned`,用户排除的标 `skipped:duplicate` 以外的跳过由执行期判定。
|
|
45
|
+
|
|
46
|
+
## 4. 幂等执行
|
|
47
|
+
|
|
48
|
+
对每条纳入记录:
|
|
49
|
+
|
|
50
|
+
1. `issue-search` 查 marker 或精确标题 + `specdev:captured`。
|
|
51
|
+
2. 已存在且 marker 在且仍 open → 账本 `open`,跳过创建。
|
|
52
|
+
3. 已存在且 marker 在但已 closed → 停止该行,展示冲突,不重开、不覆盖。intake 已消耗的行应为 `intaken`,由 intake 协议回写,不由本协议改 closed。
|
|
53
|
+
4. 已存在但无 marker → 停止该行,展示冲突,不覆盖别人的 Issue。
|
|
54
|
+
5. 不存在 → dry-run `issue-create` → 授权后 `--apply` → 保持 **open**。marker 写在正文 Provenance。不评论、不关闭。
|
|
55
|
+
6. 任一步失败:该行 `failed`,记录已完成步骤和下一重试动作;已成功的记录不回滚。
|
|
56
|
+
|
|
57
|
+
`--apply` 只表示调用方完成本次确认,不替代授权。远程失败不补建本地 Change。
|
|
58
|
+
|
|
59
|
+
全部纳入记录到达 `open` 或合法 `skipped:duplicate`,且无 `failed` 时结束。用户明确放弃未发出的行:`waived`。重跑是恢复,不是重发。`intaken` 只由后续 intake 写入。
|
|
60
|
+
|
|
61
|
+
## 5. 校验与计数
|
|
62
|
+
|
|
63
|
+
账本存在时:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
67
|
+
--capture <Path>{roots.state}/specdev/capture.md</Path>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
缺失该文件时不要为了校验去创建它。
|
|
71
|
+
|
|
72
|
+
workspace inbox 计数:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
node <Path>{roots.workflows}/specdev/T-triage/tools/capture-status.mjs</Path> \
|
|
76
|
+
--state-root <Path>{roots.state}/specdev</Path>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
数字只来自 `<Path>{roots.state}/specdev/capture.md</Path>`,不把 GitHub 搜索当权威,也不计入 `published_issues`。
|
|
80
|
+
|
|
81
|
+
## 完成标准
|
|
82
|
+
|
|
83
|
+
- 用户指定的记事项均有对应仍 open 的 GitHub Issue,或账本有合法 skip / waived;
|
|
84
|
+
- 每条已捕获 Issue 带且仅带一个 type 标签,以及 `specdev:captured` 与 `origin:local`;
|
|
85
|
+
- 正文是现在时 inbox 记录,不是派工单,也不是完成后投影;
|
|
86
|
+
- marker 使重跑不重复建贴;
|
|
87
|
+
- 未创建 Change,未写 `current_work`,未调用关闭;
|
|
88
|
+
- `<Path>{roots.state}/specdev/capture.md</Path>` 与远程重读一致,失败行可恢复;
|
|
89
|
+
- 未授权时远程写入为零。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
artifact: capture-index
|
|
4
|
+
mode: capture
|
|
5
|
+
repo: owner/repo
|
|
6
|
+
updated_at: <ISO-8601>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Capture
|
|
10
|
+
|
|
11
|
+
## 捕获计划
|
|
12
|
+
|
|
13
|
+
- **目标 repo:** `owner/repo`
|
|
14
|
+
- **origin:** local
|
|
15
|
+
- **排除 id:** 无 / `<YYYY-MM-DD-slug>`
|
|
16
|
+
- **确认记录:** 无 / 本次授权时间与操作员
|
|
17
|
+
|
|
18
|
+
capture 不创建 Change,不写 `current_work`,不关闭 Issue。GitHub 是 inbox,不是开发权威。
|
|
19
|
+
|
|
20
|
+
## 账本
|
|
21
|
+
|
|
22
|
+
| id | kind | title | labels | number | url | marker | sha256 | state |
|
|
23
|
+
|---|---|---|---|---|---|---|---|---|
|
|
24
|
+
| 2026-09-17-login-timeout | bug | bug: login timeout on empty session | bug, specdev:captured, origin:local | — | — | specdev:capture:2026-09-17-login-timeout | — | planned |
|
|
25
|
+
|
|
26
|
+
`state`:`planned | open | skipped:duplicate | intaken | waived | failed`。
|
|
27
|
+
|
|
28
|
+
`sha256` 覆盖实际发出的 Issue 正文。漂移只警告,不自动重发。
|
|
29
|
+
|
|
30
|
+
## 计数
|
|
31
|
+
|
|
32
|
+
- **inbox_open:** 0
|
|
33
|
+
- **inbox_intaken:** 0
|
|
34
|
+
- **inbox_waived:** 0
|
|
35
|
+
- **skipped:** 0
|
|
36
|
+
- **failed:** 0
|
|
37
|
+
|
|
38
|
+
计数权威是本文件,不是 GitHub 搜索。`inbox_open` 不计 `published_issues`。
|
|
39
|
+
|
|
40
|
+
## 重试
|
|
41
|
+
|
|
42
|
+
无 / 失败行的已完成步骤和下一步。已成功的记录不回滚。
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
- locator 相同但 hash 不同:展示漂移,不覆盖原快照;只有用户确认后才创建新 change,并在新 source 中记录 `supersedes_source`;
|
|
17
17
|
- 没有稳定 locator:按用户指定 change 或新 change 继续。
|
|
18
18
|
|
|
19
|
+
若 `<Path>{roots.state}/specdev/capture.md</Path>` 存在,且某行 `url` 或 marker 对应当前 `canonical_locator`:将该行标为 `intaken`。不得把多条 capture 行合并进同一个 source。默认一条 capture 记录对应一次未来 intake / 一个 Change。相关但目标不清时先走 `<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>`。缺失捕获账本不影响 intake。
|
|
20
|
+
|
|
19
21
|
同时只读扫描永久 ADR、archive 中已关闭的同类请求和当前 active change 标题。发现概念相似项时向用户展示路径与一句话理由,询问是否仍要继续。不自动 wontfix,不新建 `.out-of-scope/` 目录。
|
|
20
22
|
|
|
21
23
|
## 3. 冻结内容
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Captured Record 正文模板
|
|
2
|
+
|
|
3
|
+
capture 创建仍 open 的 GitHub Issue 时使用。先套 `<Path>{roots.workflows}/specdev/T-triage/references/public-projection.md</Path>` 脱敏,再填本模板。占位符不得把 state Path 带出去。现在时;不写「请去实现」。
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
> *This was generated by AI during SpecDev T-triage capture.*
|
|
7
|
+
|
|
8
|
+
## Captured Record
|
|
9
|
+
|
|
10
|
+
**Category:** <bug | feature | refactor | investigation | operations | documentation | review>
|
|
11
|
+
**Origin:** local
|
|
12
|
+
**Status:** inbox, open; not a development ticket
|
|
13
|
+
|
|
14
|
+
**Summary:** <一句话,现在要记下的事实或意图。>
|
|
15
|
+
|
|
16
|
+
**Observed / intended:**
|
|
17
|
+
<现在观察到什么、想记下什么。写「现在怎样 / 想记下什么」,不写「去改哪个文件」,不写派工祈使句。>
|
|
18
|
+
|
|
19
|
+
**Why parked:**
|
|
20
|
+
<为什么先记到 GitHub、还不进 Change。>
|
|
21
|
+
|
|
22
|
+
**Out of scope for this record:**
|
|
23
|
+
<本条不决定的事项;没有则写「无」。本条默认对应一次未来 intake / 一个未来 Change。>
|
|
24
|
+
|
|
25
|
+
**Provenance:**
|
|
26
|
+
- Marker: `specdev:capture:<YYYY-MM-DD-slug>`
|
|
27
|
+
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Classification Map
|
|
2
2
|
|
|
3
|
-
T-triage 本地分类与 GitHub type 标签的对照。publish 只打本表允许的标签;intake 仍把 8 值写在 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`,v1 不回写源 Issue。
|
|
3
|
+
T-triage 本地分类与 GitHub type 标签的对照。publish 与 capture 只打本表允许的标签;intake 仍把 8 值写在 `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`,v1 不回写源 Issue。
|
|
4
4
|
|
|
5
5
|
## SpecDev 分类
|
|
6
6
|
|
|
7
7
|
Change 级 `classification`:`bug | feature | refactor | investigation | operations | documentation | review | mixed`。
|
|
8
8
|
|
|
9
|
-
票级 `kind` 与上相同,去掉 `mixed`。缺省合法:publish 时继承 Change 分类;Change 为 `mixed` 时确认表必须给出每张纳入票的 `kind`。
|
|
9
|
+
票级 `kind` 与上相同,去掉 `mixed`。缺省合法:publish 时继承 Change 分类;Change 为 `mixed` 时确认表必须给出每张纳入票的 `kind`。capture 每条记录必须自带 `kind`,禁止 `mixed`。
|
|
10
10
|
|
|
11
11
|
## GitHub type 标签
|
|
12
12
|
|
|
@@ -23,13 +23,14 @@ Change 级 `classification`:`bug | feature | refactor | investigation | operat
|
|
|
23
23
|
| `review` | `enhancement` | — | `review:` |
|
|
24
24
|
| `mixed` | 禁止直接发布 | 拆到票级 `kind` | — |
|
|
25
25
|
|
|
26
|
-
每个已发布 Issue 恰好一个 type 标签(`refactor` 的额外 `refactor` 不算第二 type)。
|
|
26
|
+
每个已发布 Issue 恰好一个 type 标签(`refactor` 的额外 `refactor` 不算第二 type)。capture 的 inbox Issue 同样恰好一个 type 标签。
|
|
27
27
|
|
|
28
28
|
## 固定附加标签
|
|
29
29
|
|
|
30
|
-
- `specdev:published
|
|
31
|
-
- `
|
|
32
|
-
-
|
|
30
|
+
- `specdev:published`:publish 计数与去重。不得打到 capture 的 inbox Issue。
|
|
31
|
+
- `specdev:captured`:capture inbox 计数与去重。不得打到 publish 的票 Issue 或父索引。与 `specdev:published` 互斥。
|
|
32
|
+
- `origin:local` 或 `origin:intake`:来源。本地对话/粘贴/项目文件为 `local`;冻结自 GitHub Issue/PR 为 `intake`。capture 固定 `origin:local`。
|
|
33
|
+
- 父 Change 索引 Issue 另打 `specdev:change`。`mixed` 的父 Issue 不打 type。capture 不得打 `specdev:change`。
|
|
33
34
|
|
|
34
35
|
## 可选标签
|
|
35
36
|
|
|
@@ -38,11 +39,11 @@ Change 级 `classification`:`bug | feature | refactor | investigation | operat
|
|
|
38
39
|
|
|
39
40
|
## 禁止标签
|
|
40
41
|
|
|
41
|
-
不得打到自发布 Issue
|
|
42
|
+
不得打到自发布 Issue、父索引或 capture inbox Issue 上:
|
|
42
43
|
|
|
43
44
|
`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`、`wontfix`、`duplicate`、`invalid`、`stale`。
|
|
44
45
|
|
|
45
|
-
cancelled
|
|
46
|
+
cancelled 票若被纳入 publish,使用 close reason `not_planned`,不打 `wontfix`。
|
|
46
47
|
|
|
47
48
|
## 标题
|
|
48
49
|
|
|
@@ -50,4 +51,4 @@ cancelled 票若被纳入,使用 close reason `not_planned`,不打 `wontfix`
|
|
|
50
51
|
<前缀> <ticket.title>
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
不把内部 id `T-01` 写入标题。内部 id 只出现在正文 Provenance 和 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`。
|
|
54
|
+
不把内部 id `T-01` 写入标题。内部 id 只出现在正文 Provenance 和 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`。capture 标题同样不加内部 id;capture id 只出现在正文 Provenance 和 `<Path>{roots.state}/specdev/capture.md</Path>`。
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Public Projection
|
|
2
2
|
|
|
3
|
-
T-triage 远程正文的脱敏与耐久规则。intake 不写远程。reconcile
|
|
3
|
+
T-triage 远程正文的脱敏与耐久规则。intake 不写远程。reconcile 关闭评论、publish 的 Issue 正文 / 关闭评论、以及 capture 的 inbox Issue 正文都遵守本文件。
|
|
4
4
|
|
|
5
5
|
## Disclaimer
|
|
6
6
|
|
|
7
|
-
每条远程 Issue
|
|
7
|
+
每条远程 Issue 正文和评论必须以这一行开头。publish:
|
|
8
8
|
|
|
9
9
|
```markdown
|
|
10
10
|
> *This was generated by AI during SpecDev T-triage publish.*
|
|
@@ -16,8 +16,16 @@ reconcile 关闭评论把 `publish` 换成 `reconcile`:
|
|
|
16
16
|
> *This was generated by AI during SpecDev T-triage reconcile.*
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
capture inbox 正文把 `publish` 换成 `capture`:
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
> *This was generated by AI during SpecDev T-triage capture.*
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
## 允许写入远程的字段
|
|
20
26
|
|
|
27
|
+
publish / reconcile(过去时):
|
|
28
|
+
|
|
21
29
|
- 已发生的行为与验收(过去时)
|
|
22
30
|
- 可公开 commit / PR URL
|
|
23
31
|
- 验证结论:定向 / 回归 / E2E 是否跑过、是否通过;未跑时一句原因
|
|
@@ -26,6 +34,13 @@ reconcile 关闭评论把 `publish` 换成 `reconcile`:
|
|
|
26
34
|
- `Part of #<n>` / `Blocked by #<n>`
|
|
27
35
|
- cancelled 原因:`cancelled-already-implemented`(指到已有公开位置)或 `cancelled-out-of-scope`(指到 ADR / 用户决定)
|
|
28
36
|
|
|
37
|
+
capture(现在时):
|
|
38
|
+
|
|
39
|
+
- 现在观察到的现象或想记下的意图(现在时)
|
|
40
|
+
- 分类、`origin:local`、capture id、marker
|
|
41
|
+
- 为什么先停在 inbox、还不进 Change
|
|
42
|
+
- 本条明确不决定的事项
|
|
43
|
+
|
|
29
44
|
项目相对路径只在描述公开模块边界时允许,并注明「当时路径,可能已变」。
|
|
30
45
|
|
|
31
46
|
## 禁止写入远程
|
|
@@ -37,11 +52,11 @@ reconcile 关闭评论把 `publish` 换成 `reconcile`:
|
|
|
37
52
|
- 「请 agent 去实现」的祈使句
|
|
38
53
|
- 文件行号
|
|
39
54
|
|
|
40
|
-
关闭评论是 Evidence 的公共投影,不是副本。不得把 Evidence 原文贴出去。
|
|
55
|
+
关闭评论是 Evidence 的公共投影,不是副本。不得把 Evidence 原文贴出去。capture 没有关闭评论。
|
|
41
56
|
|
|
42
57
|
## 最低关闭评论
|
|
43
58
|
|
|
44
|
-
|
|
59
|
+
即使脱敏后也必须保留(仅 reconcile / publish):
|
|
45
60
|
|
|
46
61
|
1. 结果一句话(通过 / 取消原因)
|
|
47
62
|
2. 公开 commit 或 PR URL;没有则写「无公开 commit/PR」
|
|
@@ -64,3 +79,11 @@ specdev:<change>:publish-index
|
|
|
64
79
|
```
|
|
65
80
|
|
|
66
81
|
reconcile 源 Issue 仍用 `specdev:<change>:completion`,与 publish marker 互不替代。
|
|
82
|
+
|
|
83
|
+
capture inbox:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
specdev:capture:<YYYY-MM-DD-slug>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
只写入 Issue 正文 Provenance。Issue 保持 open。与 `specdev:published` / `specdev:<change>:completion` 互不替代。
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Aggregate inbox counts from the workspace-owned capture.md ledger.
|
|
4
|
+
* Local file is authoritative; this tool does not query GitHub.
|
|
5
|
+
* Missing capture.md is legal and counts as an empty inbox.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const OPEN = "open";
|
|
12
|
+
const INTAKEN = "intaken";
|
|
13
|
+
const WAIVED = "waived";
|
|
14
|
+
const SKIPPED = new Set(["skipped:duplicate"]);
|
|
15
|
+
|
|
16
|
+
function parseFrontmatter(text) {
|
|
17
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
18
|
+
if (!match) return {};
|
|
19
|
+
const meta = {};
|
|
20
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
21
|
+
const idx = line.indexOf(":");
|
|
22
|
+
if (idx === -1) continue;
|
|
23
|
+
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
24
|
+
}
|
|
25
|
+
return meta;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseLedgerStates(body) {
|
|
29
|
+
const rows = [];
|
|
30
|
+
for (const line of body.split(/\r?\n/)) {
|
|
31
|
+
if (!/^\|/.test(line) || /^\|\s*-+/.test(line) || /^\|\s*id\s*\|/i.test(line)) continue;
|
|
32
|
+
const cells = line.split("|").map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
|
|
33
|
+
if (cells.length < 9) continue;
|
|
34
|
+
rows.push({
|
|
35
|
+
id: cells[0],
|
|
36
|
+
kind: cells[1],
|
|
37
|
+
state: cells[8],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return rows;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function summarize(stateRoot) {
|
|
44
|
+
const file = join(stateRoot, "capture.md");
|
|
45
|
+
if (!existsSync(file)) {
|
|
46
|
+
return {
|
|
47
|
+
inbox_open: 0,
|
|
48
|
+
inbox_intaken: 0,
|
|
49
|
+
inbox_waived: 0,
|
|
50
|
+
inbox_skipped: 0,
|
|
51
|
+
inbox_failed: 0,
|
|
52
|
+
records: 0,
|
|
53
|
+
missing: true,
|
|
54
|
+
ledger: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const text = readFileSync(file, "utf8");
|
|
59
|
+
const meta = parseFrontmatter(text);
|
|
60
|
+
const body = text.replace(/^---[\s\S]*?---/, "");
|
|
61
|
+
const rows = parseLedgerStates(body);
|
|
62
|
+
let inbox_open = 0;
|
|
63
|
+
let inbox_intaken = 0;
|
|
64
|
+
let inbox_waived = 0;
|
|
65
|
+
let inbox_skipped = 0;
|
|
66
|
+
let inbox_failed = 0;
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
if (row.state === OPEN) inbox_open += 1;
|
|
69
|
+
else if (row.state === INTAKEN) inbox_intaken += 1;
|
|
70
|
+
else if (row.state === WAIVED) inbox_waived += 1;
|
|
71
|
+
else if (SKIPPED.has(row.state)) inbox_skipped += 1;
|
|
72
|
+
else if (row.state === "failed") inbox_failed += 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
inbox_open,
|
|
77
|
+
inbox_intaken,
|
|
78
|
+
inbox_waived,
|
|
79
|
+
inbox_skipped,
|
|
80
|
+
inbox_failed,
|
|
81
|
+
records: rows.length,
|
|
82
|
+
missing: false,
|
|
83
|
+
ledger: file,
|
|
84
|
+
repo: meta.repo || null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function usage() {
|
|
89
|
+
console.error("Usage: node capture-status.mjs --state-root <specdev-state-root> [--json]");
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function main(argv) {
|
|
94
|
+
let stateRoot = null;
|
|
95
|
+
let json = false;
|
|
96
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
97
|
+
if (argv[i] === "--state-root") {
|
|
98
|
+
stateRoot = argv[i + 1];
|
|
99
|
+
i += 1;
|
|
100
|
+
} else if (argv[i] === "--json") {
|
|
101
|
+
json = true;
|
|
102
|
+
} else {
|
|
103
|
+
usage();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!stateRoot) usage();
|
|
107
|
+
const summary = summarize(stateRoot);
|
|
108
|
+
if (json) {
|
|
109
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const missing = summary.missing ? " (missing ledger, empty inbox)" : "";
|
|
113
|
+
console.log(`inbox_open: ${summary.inbox_open}${missing}`);
|
|
114
|
+
console.log(`inbox_intaken: ${summary.inbox_intaken}`);
|
|
115
|
+
console.log(`inbox_waived: ${summary.inbox_waived}`);
|
|
116
|
+
console.log(`inbox_skipped: ${summary.inbox_skipped}`);
|
|
117
|
+
console.log(`inbox_failed: ${summary.inbox_failed}`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
122
|
+
process.exit(main(process.argv.slice(2)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { summarize };
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
- 来源快照:`<Path>{roots.workflows}/specdev/common/schemas/source.schema.json</Path>`
|
|
41
41
|
- 分诊:`<Path>{roots.workflows}/specdev/common/schemas/triage.schema.json</Path>`
|
|
42
42
|
- 发布账本:`<Path>{roots.workflows}/specdev/common/schemas/publish.schema.json</Path>`
|
|
43
|
+
- 捕获账本:`<Path>{roots.workflows}/specdev/common/schemas/capture.schema.json</Path>`
|
|
43
44
|
- 诊断:`<Path>{roots.workflows}/specdev/common/schemas/diagnosis.schema.json</Path>`
|
|
44
45
|
- 代码审查:`<Path>{roots.workflows}/specdev/common/schemas/code-review.schema.json</Path>`
|
|
45
46
|
- UI 设计包:`<Path>{roots.workflows}/specdev/P-prototype/design-package.schema.json</Path>`
|
|
@@ -9,6 +9,7 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
9
9
|
| 来源快照 | `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
|
|
10
10
|
| 分诊 | `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
|
|
11
11
|
| 发布账本 | `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` | 票级 GitHub 投影的编号、标签、marker、state 和发布计数 | Ticket 契约、Evidence 原文或源 Issue 关闭 |
|
|
12
|
+
| 捕获账本 | `<Path>{roots.state}/specdev/capture.md</Path>` | 尚未成 Change 的记事项、GitHub inbox 编号、标签、marker 和 inbox 计数;缺失合法 | Change、Ticket、Evidence 或已完成票的发布投影 |
|
|
12
13
|
| 诊断 | `<Path>{roots.state}/specdev/changes/{change}/diagnosis.md</Path>` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
|
|
13
14
|
| 设计日志 | `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
|
|
14
15
|
| 设计树 | `<Path>{roots.state}/specdev/changes/{change}/design-tree.json</Path>` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
|
|
@@ -51,7 +52,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
|
|
|
51
52
|
|
|
52
53
|
当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
|
|
53
54
|
|
|
54
|
-
`<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` 为准。
|
|
55
|
+
`<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。GitHub 上由 publish 投影出的 Issue 不是开发权威;发布计数以 `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>` 为准。GitHub 上由 capture 记下的 inbox Issue 也不是开发权威;inbox 计数以 `<Path>{roots.state}/specdev/capture.md</Path>` 为准,缺失该文件视为空 inbox。
|
|
55
56
|
|
|
56
57
|
代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 `<Path>{roots.workflows}/specdev/common/rules/deviation-control.md</Path>` 退回相应工件修订。
|
|
57
58
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
| 场景 | 入口 | 正常出口 |
|
|
5
5
|
|---|---|---|
|
|
6
6
|
| 需要冻结外部来源或审计摄入 | T-triage intake | D / G / W / P / S / C / T |
|
|
7
|
+
| 尚未成 Change,只要先在 GitHub 留一条仍 open 的记事项 | T-triage capture | 停止 / 以后 intake / W |
|
|
7
8
|
| 本地 change 完成且来源可关闭 | T-triage reconcile | A |
|
|
8
9
|
| 本地 change 完成且要把 Ticket 记到 GitHub | T-triage publish | reconcile / A / 停止 |
|
|
9
10
|
| 疑难 bug 或性能回归 | D-diagnose-bugs | S / T / I / R / W |
|
|
@@ -22,4 +23,4 @@
|
|
|
22
23
|
同 change 下一阶段需要当前一手推理且上下文健康时继续;切换 repo/person/harness 或旁路时使用 `<Path>{roots.commands}/handoff.md</Path>`;严格限定且可独立派单时使用 Dispatch Packet;其他长上下文以权威工件路径恢复。平台不支持 clear/compact 时不虚构操作。
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
本地已清晰请求不强制绕行 Triage。完成后要记账才选 publish
|
|
26
|
+
本地已清晰请求不强制绕行 Triage。完成后要记账才选 publish;尚未成 Change、只想先留 inbox 记录才选 capture。publish、reconcile 与 capture 写不同远程对象,可先后执行。G 是单 change Grill,P-prototype 是 UI 设计,不与统一 P-goal-plan 混淆。W 发现候选后逐个交 G/S/T;只有清晰且 Ready 的 child 才交 P。原缺陷诊断 D、风险分诊、远程 reconcile、完成后发布投影和记事项捕获职责不删除。
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- 全局状态:`<Path>{roots.state}/specdev/status.json</Path>`
|
|
10
10
|
- 活跃 change:`<Path>{roots.state}/specdev/changes/</Path>`
|
|
11
11
|
- 历史归档:`<Path>{roots.state}/specdev/archive/</Path>`
|
|
12
|
+
- 捕获账本(可选,缺失合法):`<Path>{roots.state}/specdev/capture.md</Path>`
|
|
12
13
|
|
|
13
14
|
刷新时 CLI 依据 `<Path>{roots.workflows}/specdev/runtime-contract.json</Path>` 处理持久化数据:配置使用 baseline 三方合并,登记的状态 schema 使用显式 migrator,其他 runtime 文件按字节保留。只有字段删除或结构迁移时才在 `<Path>{roots.state}/back/</Path>` 写入 targeted backup;冲突在替换 active 安装前阻塞。`<Path>{roots.state}/back/</Path>`、`<Path>{roots.state}/install.json</Path>`、`<Path>{roots.state}/managed.json</Path>` 与 `<Path>{roots.state}/baselines/</Path>` 均不属于 SpecDev 写入 namespace。
|
|
14
15
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:speculo:specdev:capture:v1",
|
|
4
|
+
"title": "SpecDev Capture Index Frontmatter",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema_version", "artifact", "mode", "repo", "updated_at"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema_version": {"const": 1},
|
|
9
|
+
"artifact": {"const": "capture-index"},
|
|
10
|
+
"mode": {"const": "capture"},
|
|
11
|
+
"repo": {"type": "string", "pattern": "^.+/.+$"},
|
|
12
|
+
"updated_at": {"type": "string", "minLength": 1}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": true
|
|
15
|
+
}
|
|
@@ -11,6 +11,13 @@ node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
|
11
11
|
|
|
12
12
|
`--stage` 只要求该阶段已经拥有的工件;所有已经存在的工件仍会验证。`goal-plan` 还会读取父 change 的 sibling 成员,要求每个成员已有 Ready Spec/Tickets,校验组合 Ticket DAG、唯一父归属、serialization、跨 Ticket 写路径、全局 workspace/实现配额和完成门。省略 stage 时验证当前存在的工件,不会因未来 Work 尚未运行而报错。`--repo` 可选;提供后会把状态中的 SHA、祖先关系、当前分支和完成时 clean 状态与真实 Git 仓库交叉验证。
|
|
13
13
|
|
|
14
|
+
校验 workspace 捕获账本(文件必须已存在;缺失时不要为了校验去创建):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
|
|
18
|
+
--capture <Path>{roots.state}/specdev/capture.md</Path>
|
|
19
|
+
```
|
|
20
|
+
|
|
14
21
|
## 校验 SpecDev 工作流包
|
|
15
22
|
|
|
16
23
|
```bash
|
|
@@ -103,6 +103,20 @@ const FORBIDDEN_PUBLISH_LABELS = new Set([
|
|
|
103
103
|
"stale",
|
|
104
104
|
]);
|
|
105
105
|
const COUNTED_PUBLISH_STATES = new Set(["closed", "created", "commented"]);
|
|
106
|
+
const VALID_CAPTURE_ROW_STATE = new Set([
|
|
107
|
+
"planned",
|
|
108
|
+
"open",
|
|
109
|
+
"skipped:duplicate",
|
|
110
|
+
"intaken",
|
|
111
|
+
"waived",
|
|
112
|
+
"failed",
|
|
113
|
+
]);
|
|
114
|
+
const FORBIDDEN_CAPTURE_LABELS = new Set([
|
|
115
|
+
...FORBIDDEN_PUBLISH_LABELS,
|
|
116
|
+
"specdev:published",
|
|
117
|
+
"specdev:change",
|
|
118
|
+
]);
|
|
119
|
+
const COUNTED_CAPTURE_REMOTE_STATES = new Set(["open", "intaken"]);
|
|
106
120
|
const VALID_DEPTH = new Set(["lite", "standard", "deep"]);
|
|
107
121
|
const VALID_RISK = new Set(["low", "medium", "high", "critical"]);
|
|
108
122
|
const VALID_PLAN_MODES = new Set([
|
|
@@ -213,6 +227,7 @@ const STATE_ARTIFACT_BASENAMES = new Set([
|
|
|
213
227
|
".status.json",
|
|
214
228
|
"triage.md",
|
|
215
229
|
"publish.md",
|
|
230
|
+
"capture.md",
|
|
216
231
|
"diagnosis.md",
|
|
217
232
|
"source.md",
|
|
218
233
|
"architecture-review.md",
|
|
@@ -1011,7 +1026,7 @@ function capabilityChecks(root) {
|
|
|
1011
1026
|
"triage",
|
|
1012
1027
|
[
|
|
1013
1028
|
join(root, "T-triage", "T-triage.md"),
|
|
1014
|
-
["source.md", "intake", "reconcile", "publish", "publish.md", "specdev:published", "唯一权威", "远程写入为零"],
|
|
1029
|
+
["source.md", "intake", "reconcile", "publish", "publish.md", "specdev:published", "capture", "capture.md", "specdev:captured", "唯一权威", "远程写入为零"],
|
|
1015
1030
|
],
|
|
1016
1031
|
],
|
|
1017
1032
|
[
|
|
@@ -1140,6 +1155,11 @@ function capabilityChecks(root) {
|
|
|
1140
1155
|
"common/rules/parent-implementation-orchestration.md",
|
|
1141
1156
|
"common/schemas/implementation-map.schema.json",
|
|
1142
1157
|
"common/schemas/implementation-plan.schema.json",
|
|
1158
|
+
"common/schemas/capture.schema.json",
|
|
1159
|
+
"T-triage/capture-protocol.md",
|
|
1160
|
+
"T-triage/capture-template.md",
|
|
1161
|
+
"T-triage/issue-record-template.md",
|
|
1162
|
+
"T-triage/tools/capture-status.mjs",
|
|
1143
1163
|
]) {
|
|
1144
1164
|
if (!isFile(join(root, required))) errors.push(`missing architecture/wayfinding contract ${required}`);
|
|
1145
1165
|
}
|
|
@@ -1500,6 +1520,99 @@ function validatePublish(path, expectedChange, triage, errors) {
|
|
|
1500
1520
|
return { path, meta, body, rows };
|
|
1501
1521
|
}
|
|
1502
1522
|
|
|
1523
|
+
function parseCaptureLedger(body) {
|
|
1524
|
+
const rows = [];
|
|
1525
|
+
for (const line of body.split(/\r?\n/)) {
|
|
1526
|
+
if (!/^\|/.test(line) || /^\|\s*-+/.test(line) || /^\|\s*id\s*\|/i.test(line)) continue;
|
|
1527
|
+
const cells = line.split("|").map((cell) => cell.trim());
|
|
1528
|
+
const inner = cells.slice(1, cells.length - 1);
|
|
1529
|
+
if (inner.length < 9) continue;
|
|
1530
|
+
rows.push({
|
|
1531
|
+
id: inner[0],
|
|
1532
|
+
kind: inner[1],
|
|
1533
|
+
title: inner[2],
|
|
1534
|
+
labels: inner[3],
|
|
1535
|
+
number: inner[4],
|
|
1536
|
+
url: inner[5],
|
|
1537
|
+
marker: inner[6],
|
|
1538
|
+
sha256: inner[7],
|
|
1539
|
+
state: inner[8],
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
return rows;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function validateCapture(path, errors) {
|
|
1546
|
+
if (!isFile(path)) {
|
|
1547
|
+
errors.push("missing capture.md ledger");
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
const { meta, body } = parseFrontmatter(path);
|
|
1551
|
+
const required = [
|
|
1552
|
+
"schema_version",
|
|
1553
|
+
"artifact",
|
|
1554
|
+
"mode",
|
|
1555
|
+
"repo",
|
|
1556
|
+
"updated_at",
|
|
1557
|
+
];
|
|
1558
|
+
const missing = required.filter((key) => !(key in meta));
|
|
1559
|
+
if (missing.length) errors.push(`capture.md: missing keys ${JSON.stringify(missing)}`);
|
|
1560
|
+
if (meta.schema_version !== 1 || meta.artifact !== "capture-index") {
|
|
1561
|
+
errors.push("capture.md: artifact/schema_version must be capture-index/1");
|
|
1562
|
+
}
|
|
1563
|
+
if (meta.mode !== "capture") errors.push(`capture.md: invalid mode ${meta.mode}`);
|
|
1564
|
+
if (typeof meta.repo !== "string" || !/.+\/.+/.test(meta.repo)) {
|
|
1565
|
+
errors.push("capture.md: repo must be owner/repo");
|
|
1566
|
+
}
|
|
1567
|
+
for (const heading of ["## 捕获计划", "## 账本", "## 计数", "## 重试"]) {
|
|
1568
|
+
if (!body.includes(heading)) errors.push(`capture.md: missing '${heading}'`);
|
|
1569
|
+
}
|
|
1570
|
+
const rows = parseCaptureLedger(body);
|
|
1571
|
+
for (const row of rows) {
|
|
1572
|
+
if (!CHANGE_NAME.test(row.id)) {
|
|
1573
|
+
errors.push(`capture.md: invalid id ${row.id}`);
|
|
1574
|
+
}
|
|
1575
|
+
if (!VALID_CAPTURE_ROW_STATE.has(row.state)) {
|
|
1576
|
+
errors.push(`capture.md: ${row.id}: invalid state ${row.state}`);
|
|
1577
|
+
}
|
|
1578
|
+
const labels = row.labels.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1579
|
+
for (const label of labels) {
|
|
1580
|
+
if (FORBIDDEN_CAPTURE_LABELS.has(label)) {
|
|
1581
|
+
errors.push(`capture.md: ${row.id}: forbidden label ${label}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
const skipped = row.state.startsWith("skipped:") || row.state === "waived" || row.state === "planned" || row.state === "failed";
|
|
1585
|
+
if (!skipped && !VALID_TICKET_KIND.has(row.kind)) {
|
|
1586
|
+
errors.push(`capture.md: ${row.id}: invalid kind ${row.kind}`);
|
|
1587
|
+
}
|
|
1588
|
+
if (COUNTED_CAPTURE_REMOTE_STATES.has(row.state)) {
|
|
1589
|
+
if (!VALID_TICKET_KIND.has(row.kind)) {
|
|
1590
|
+
errors.push(`capture.md: ${row.id}: invalid kind ${row.kind}`);
|
|
1591
|
+
}
|
|
1592
|
+
if (!/^\d+$/.test(row.number)) {
|
|
1593
|
+
errors.push(`capture.md: ${row.id}: ${row.state} row requires issue number`);
|
|
1594
|
+
}
|
|
1595
|
+
if (!String(row.url).startsWith("https://github.com/")) {
|
|
1596
|
+
errors.push(`capture.md: ${row.id}: ${row.state} row requires GitHub url`);
|
|
1597
|
+
}
|
|
1598
|
+
const expectedMarker = `specdev:capture:${row.id}`;
|
|
1599
|
+
if (row.marker !== expectedMarker) {
|
|
1600
|
+
errors.push(`capture.md: ${row.id}: marker must be ${expectedMarker}`);
|
|
1601
|
+
}
|
|
1602
|
+
if (!labels.includes("specdev:captured")) {
|
|
1603
|
+
errors.push(`capture.md: ${row.id}: missing specdev:captured`);
|
|
1604
|
+
}
|
|
1605
|
+
if (!labels.includes("origin:local")) {
|
|
1606
|
+
errors.push(`capture.md: ${row.id}: missing origin:local`);
|
|
1607
|
+
}
|
|
1608
|
+
if (labels.includes("origin:intake")) {
|
|
1609
|
+
errors.push(`capture.md: ${row.id}: capture origin must be local`);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return { path, meta, body, rows };
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1503
1616
|
function validateDiagnosis(path, expectedChange, errors) {
|
|
1504
1617
|
if (!isFile(path)) {
|
|
1505
1618
|
errors.push("missing diagnosis artifact");
|
|
@@ -3157,6 +3270,9 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
3157
3270
|
if (isFile(join(change, "source-issue.md"))) {
|
|
3158
3271
|
errors.push("obsolete source-issue.md is forbidden; use source.md without compatibility fallback");
|
|
3159
3272
|
}
|
|
3273
|
+
if (isFile(join(change, "capture.md"))) {
|
|
3274
|
+
errors.push("capture.md is workspace-owned at specdev/capture.md; change-level copies are forbidden");
|
|
3275
|
+
}
|
|
3160
3276
|
const sourceRequired = stage === "triage";
|
|
3161
3277
|
const sourcePath = join(change, "source.md");
|
|
3162
3278
|
const source = isFile(sourcePath) || sourceRequired
|
|
@@ -3495,7 +3611,7 @@ function printResults(errors, warnings) {
|
|
|
3495
3611
|
}
|
|
3496
3612
|
|
|
3497
3613
|
function usage() {
|
|
3498
|
-
console.error("Usage: node validate-specdev.mjs [--stage <stage>] [--repo <project-root>] <change-directory> | --self-check");
|
|
3614
|
+
console.error("Usage: node validate-specdev.mjs [--stage <stage>] [--repo <project-root>] <change-directory> | --self-check | --capture <capture.md>");
|
|
3499
3615
|
return 2;
|
|
3500
3616
|
}
|
|
3501
3617
|
|
|
@@ -3503,6 +3619,7 @@ function main(argv) {
|
|
|
3503
3619
|
let selfCheckRequested = false;
|
|
3504
3620
|
let stage = null;
|
|
3505
3621
|
let repoRoot = null;
|
|
3622
|
+
let capturePath = null;
|
|
3506
3623
|
const positional = [];
|
|
3507
3624
|
for (let index = 0; index < argv.length; index += 1) {
|
|
3508
3625
|
const arg = argv[index];
|
|
@@ -3518,6 +3635,11 @@ function main(argv) {
|
|
|
3518
3635
|
index += 1;
|
|
3519
3636
|
} else if (arg.startsWith("--repo=")) {
|
|
3520
3637
|
repoRoot = arg.slice("--repo=".length);
|
|
3638
|
+
} else if (arg === "--capture") {
|
|
3639
|
+
capturePath = argv[index + 1] ?? null;
|
|
3640
|
+
index += 1;
|
|
3641
|
+
} else if (arg.startsWith("--capture=")) {
|
|
3642
|
+
capturePath = arg.slice("--capture=".length);
|
|
3521
3643
|
} else if (arg.startsWith("--")) {
|
|
3522
3644
|
return usage();
|
|
3523
3645
|
} else {
|
|
@@ -3526,12 +3648,18 @@ function main(argv) {
|
|
|
3526
3648
|
}
|
|
3527
3649
|
if (stage !== null && !VALID_STAGES.has(stage)) return usage();
|
|
3528
3650
|
if (selfCheckRequested) {
|
|
3529
|
-
if (positional.length || stage !== null) return usage();
|
|
3651
|
+
if (positional.length || stage !== null || capturePath) return usage();
|
|
3530
3652
|
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
3531
3653
|
const root = resolve(scriptDirectory, "..", "..");
|
|
3532
3654
|
const result = selfCheck(root);
|
|
3533
3655
|
return printResults(result.errors, result.warnings);
|
|
3534
3656
|
}
|
|
3657
|
+
if (capturePath) {
|
|
3658
|
+
if (positional.length || stage !== null || repoRoot) return usage();
|
|
3659
|
+
const errors = [];
|
|
3660
|
+
validateCapture(resolve(capturePath), errors);
|
|
3661
|
+
return printResults(errors, []);
|
|
3662
|
+
}
|
|
3535
3663
|
if (positional.length === 1) {
|
|
3536
3664
|
const result = validateChange(resolve(positional[0]), stage, repoRoot);
|
|
3537
3665
|
return printResults(result.errors, result.warnings);
|
|
@@ -3539,7 +3667,7 @@ function main(argv) {
|
|
|
3539
3667
|
return usage();
|
|
3540
3668
|
}
|
|
3541
3669
|
|
|
3542
|
-
export { parseFrontmatter, validateChange, validateTicket, validateMap, pathsOverlap, findCycle };
|
|
3670
|
+
export { parseFrontmatter, validateChange, validateTicket, validateMap, pathsOverlap, findCycle, validateCapture };
|
|
3543
3671
|
|
|
3544
3672
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
3545
3673
|
try { process.exitCode = main(process.argv.slice(2)); }
|