@namewta/speculo 1.0.13 → 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.
Files changed (41) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/template/canonical/canonical-specdev-goal-plan.md +9 -5
  4. package/template/canonical/canonical-specdev-grill-with-docs.md +6 -4
  5. package/template/canonical/canonical-specdev-spec.md +8 -4
  6. package/template/canonical/canonical-specdev-tickets.md +20 -4
  7. package/template/commands/handoff.md +1 -1
  8. package/template/commands/status.md +1 -1
  9. package/template/workflows/specdev/A-archive-and-consolidate/A-archive-and-consolidate.md +3 -3
  10. package/template/workflows/specdev/I-implement/references/implementation-procedure.md +1 -1
  11. package/template/workflows/specdev/I-init-setup/tracking-template.md +1 -1
  12. package/template/workflows/specdev/INDEX.md +1 -1
  13. package/template/workflows/specdev/README.md +4 -4
  14. package/template/workflows/specdev/T-tickets/ticket-template.md +1 -0
  15. package/template/workflows/specdev/T-triage/T-triage.md +80 -15
  16. package/template/workflows/specdev/T-triage/capture-protocol.md +89 -0
  17. package/template/workflows/specdev/T-triage/capture-template.md +42 -0
  18. package/template/workflows/specdev/T-triage/close-comment-template.md +42 -0
  19. package/template/workflows/specdev/T-triage/intake-protocol.md +8 -2
  20. package/template/workflows/specdev/T-triage/issue-body-template.md +34 -0
  21. package/template/workflows/specdev/T-triage/issue-record-template.md +27 -0
  22. package/template/workflows/specdev/T-triage/publish-protocol.md +92 -0
  23. package/template/workflows/specdev/T-triage/publish-template.md +49 -0
  24. package/template/workflows/specdev/T-triage/reconcile-protocol.md +3 -3
  25. package/template/workflows/specdev/T-triage/references/classification-map.md +54 -0
  26. package/template/workflows/specdev/T-triage/references/public-projection.md +89 -0
  27. package/template/workflows/specdev/T-triage/tools/capture-status.mjs +125 -0
  28. package/template/workflows/specdev/T-triage/tools/publish-status.mjs +148 -0
  29. package/template/workflows/specdev/T-triage/triage-template.md +12 -1
  30. package/template/workflows/specdev/common/README.md +2 -0
  31. package/template/workflows/specdev/common/rules/artifact-contract.md +6 -4
  32. package/template/workflows/specdev/common/rules/change-completion.md +1 -1
  33. package/template/workflows/specdev/common/rules/evidence-and-verification.md +2 -0
  34. package/template/workflows/specdev/common/rules/workflow-routing.md +3 -1
  35. package/template/workflows/specdev/common/rules/workflow-state-and-lifecycle.md +1 -0
  36. package/template/workflows/specdev/common/schemas/capture.schema.json +15 -0
  37. package/template/workflows/specdev/common/schemas/publish.schema.json +21 -0
  38. package/template/workflows/specdev/common/schemas/ticket.schema.json +11 -0
  39. package/template/workflows/specdev/common/schemas/triage.schema.json +3 -1
  40. package/template/workflows/specdev/common/tools/README.md +7 -0
  41. package/template/workflows/specdev/common/tools/validate-specdev.mjs +304 -6
@@ -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 };
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Aggregate published-issue counts from active + archive publish.md ledgers.
4
+ * Local files are authoritative; this tool does not query GitHub.
5
+ */
6
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const COUNTED = new Set(["closed", "created", "commented"]);
11
+ const SKIPPED = new Set(["skipped:cancelled", "skipped:excluded"]);
12
+
13
+ function parseFrontmatter(text) {
14
+ const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
15
+ if (!match) return {};
16
+ const meta = {};
17
+ for (const line of match[1].split(/\r?\n/)) {
18
+ const idx = line.indexOf(":");
19
+ if (idx === -1) continue;
20
+ meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
21
+ }
22
+ return meta;
23
+ }
24
+
25
+ function parseLedgerStates(body) {
26
+ const states = [];
27
+ for (const line of body.split(/\r?\n/)) {
28
+ if (!/^\|/.test(line) || /^\|\s*-+/.test(line) || /^\|\s*ticket\s*\|/i.test(line)) continue;
29
+ const cells = line.split("|").map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
30
+ if (cells.length < 8) continue;
31
+ states.push(cells[7]);
32
+ }
33
+ return { states };
34
+ }
35
+
36
+ function collectLedgers(stateRoot) {
37
+ const roots = [
38
+ join(stateRoot, "changes"),
39
+ join(stateRoot, "archive"),
40
+ ];
41
+ const files = [];
42
+ function walk(dir, depth = 0) {
43
+ if (!existsSync(dir) || depth > 6) return;
44
+ for (const name of readdirSync(dir)) {
45
+ const p = join(dir, name);
46
+ let st;
47
+ try {
48
+ st = statSync(p);
49
+ } catch {
50
+ continue;
51
+ }
52
+ if (st.isDirectory()) walk(p, depth + 1);
53
+ else if (name === "publish.md") files.push(p);
54
+ }
55
+ }
56
+ for (const root of roots) walk(root);
57
+ return files;
58
+ }
59
+
60
+ function summarize(stateRoot) {
61
+ const files = collectLedgers(stateRoot);
62
+ let published = 0;
63
+ let skipped = 0;
64
+ let failed = 0;
65
+ let originLocal = 0;
66
+ let originIntake = 0;
67
+ const byChange = [];
68
+
69
+ for (const file of files) {
70
+ const text = readFileSync(file, "utf8");
71
+ const meta = parseFrontmatter(text);
72
+ const body = text.replace(/^---[\s\S]*?---/, "");
73
+ const { states } = parseLedgerStates(body);
74
+ const origin = meta.origin === "intake" ? "intake" : "local";
75
+ let changePublished = 0;
76
+ let changeSkipped = 0;
77
+ let changeFailed = 0;
78
+ for (const state of states) {
79
+ if (COUNTED.has(state)) {
80
+ changePublished += 1;
81
+ if (origin === "intake") originIntake += 1;
82
+ else originLocal += 1;
83
+ } else if (SKIPPED.has(state)) {
84
+ changeSkipped += 1;
85
+ } else if (state === "failed") {
86
+ changeFailed += 1;
87
+ }
88
+ }
89
+ published += changePublished;
90
+ skipped += changeSkipped;
91
+ failed += changeFailed;
92
+ byChange.push({
93
+ change: meta.change || file,
94
+ origin,
95
+ publish_action: meta.publish_action || "unknown",
96
+ published: changePublished,
97
+ skipped: changeSkipped,
98
+ failed: changeFailed,
99
+ });
100
+ }
101
+
102
+ return {
103
+ published_issues: published,
104
+ origin: { local: originLocal, intake: originIntake },
105
+ publish_skipped: skipped,
106
+ publish_failed: failed,
107
+ ledgers: files.length,
108
+ changes: byChange,
109
+ };
110
+ }
111
+
112
+ function usage() {
113
+ console.error("Usage: node publish-status.mjs --state-root <specdev-state-root> [--json]");
114
+ process.exit(2);
115
+ }
116
+
117
+ function main(argv) {
118
+ let stateRoot = null;
119
+ let json = false;
120
+ for (let i = 0; i < argv.length; i += 1) {
121
+ if (argv[i] === "--state-root") {
122
+ stateRoot = argv[i + 1];
123
+ i += 1;
124
+ } else if (argv[i] === "--json") {
125
+ json = true;
126
+ } else {
127
+ usage();
128
+ }
129
+ }
130
+ if (!stateRoot) usage();
131
+ const summary = summarize(stateRoot);
132
+ if (json) {
133
+ console.log(JSON.stringify(summary, null, 2));
134
+ return 0;
135
+ }
136
+ console.log(
137
+ `published_issues: ${summary.published_issues} (origin:local ${summary.origin.local}, origin:intake ${summary.origin.intake})`,
138
+ );
139
+ console.log(`publish_skipped: ${summary.publish_skipped}`);
140
+ console.log(`publish_failed: ${summary.publish_failed}`);
141
+ return 0;
142
+ }
143
+
144
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
145
+ process.exit(main(process.argv.slice(2)));
146
+ }
147
+
148
+ export { summarize };
@@ -9,6 +9,8 @@ risk: medium
9
9
  route: specdev/wayfinder
10
10
  ready_for_implementation: false
11
11
  external_action: not-applicable
12
+ publish_action: not-requested
13
+ publish: null
12
14
  updated_at: <ISO-8601>
13
15
  ---
14
16
 
@@ -40,4 +42,13 @@ updated_at: <ISO-8601>
40
42
  - **授权记录:** 无
41
43
  - **尝试与结果:** 无
42
44
 
43
- 外部动作只投影最终完成,不替代本地状态、Ticket、Map 或 Evidence。
45
+ 外部动作只投影来源 Issue 的最终完成,不替代本地状态、Ticket、Map 或 Evidence。
46
+
47
+ ## 发布投影
48
+
49
+ - **publish_action:** not-requested / pending / published / publish-failed / waived
50
+ - **账本:** 无 / `<Path>{roots.state}/specdev/changes/{change}/publish.md</Path>`
51
+ - **origin:** local / intake
52
+ - **计数:** 见账本;workspace 汇总用 publish-status
53
+
54
+ 发布投影只记账已完成 Ticket,不把 GitHub 提升为开发权威。`publish_action` 与 `external_action` 分立。
@@ -39,6 +39,8 @@
39
39
  - Wayfinder Ticket:`<Path>{roots.workflows}/specdev/common/schemas/wayfinder-ticket.schema.json</Path>`
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
+ - 发布账本:`<Path>{roots.workflows}/specdev/common/schemas/publish.schema.json</Path>`
43
+ - 捕获账本:`<Path>{roots.workflows}/specdev/common/schemas/capture.schema.json</Path>`
42
44
  - 诊断:`<Path>{roots.workflows}/specdev/common/schemas/diagnosis.schema.json</Path>`
43
45
  - 代码审查:`<Path>{roots.workflows}/specdev/common/schemas/code-review.schema.json</Path>`
44
46
  - UI 设计包:`<Path>{roots.workflows}/specdev/P-prototype/design-package.schema.json</Path>`
@@ -7,20 +7,22 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
7
7
  | 工件 | 具体位置 | 必须决定 | 不应决定 |
8
8
  |---|---|---|---|
9
9
  | 来源快照 | `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` | 原始请求、捕获时间、locator、hash 和关闭能力 | 当前产品合同或实现状态 |
10
- | 分诊 | `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>` | 请求类别、影响、风险、缺失输入、下一 work 和远程 reconcile 状态 | 详细实现方案或开发进度 |
10
+ | 分诊 | `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>` | 请求类别、影响、风险、缺失输入、下一 work、源 Issue reconcile 状态和 publish_action | 详细实现方案、开发进度或票级发布账本 |
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 或已完成票的发布投影 |
11
13
  | 诊断 | `<Path>{roots.state}/specdev/changes/{change}/diagnosis.md</Path>` | 复现、证据、根因、修复不变量和回归契约 | 未经验证的修复实现 |
12
14
  | 设计日志 | `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>` | 讨论轨迹、确认、延后、替代与废弃结论 | 当前架构权威摘要 |
13
15
  | 设计树 | `<Path>{roots.state}/specdev/changes/{change}/design-tree.json</Path>` | 决策节点、依赖、当前 frontier、轮次与共识状态 | 领域真相或架构决定正文 |
14
16
  | Change 领域上下文 | `<Path>{roots.state}/specdev/changes/{change}/CONTEXT.md</Path>` | 本 change 已确认、供下游使用的领域术语和语义 | 永久领域知识或临时会议记录 |
15
17
  | Change 架构决策 | `<Path>{roots.state}/specdev/changes/{change}/ADR.md</Path>` | 已成为本 change 下游合同的架构决策、原因、后果和替代关系 | 永久项目 ADR 或尚未决定的方案集合 |
16
18
  | Spec | `<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>` | 用户问题、外部行为、范围、验收合同、非功能要求和已锁定实现约束 | 文件级施工步骤 |
17
- | Ticket | `<Path>{roots.state}/specdev/changes/{change}/ticket/{ticket-file}.md</Path>` | 单一垂直切片的行为、决策、范围、路径所有权、执行路线和验证证据 | 跨 Ticket 里程碑治理 |
19
+ | Ticket | `<Path>{roots.state}/specdev/changes/{change}/ticket/{ticket-file}.md</Path>` | 单一垂直切片的行为、决策、范围、路径所有权、执行路线和验证证据 | 跨 Ticket 里程碑治理或远程 Issue 编号 |
18
20
  | Tickets Map | `<Path>{roots.state}/specdev/changes/{change}/tickets-map.md</Path>` | 总体实施背景、项目 Skill 最低调用路由、依赖 DAG、合同覆盖、Ready 投影、并行候选和路径冲突 | 单 Ticket 的完整实现契约 |
19
21
  | Goal Plan | `<Path>{roots.state}/specdev/changes/{change}/goal-plan.md</Path>` | 跨 Ticket 调度、Gate、共享所有权、迁移顺序、集成和偏差治理 | 复制 Ticket 全文 |
20
22
  | Implementation Map | `<Path>{roots.state}/specdev/changes/{change}/implementation-map.md</Path>` | Ready 成员、组合 Ticket inventory、跨 change dependency/serialization 与 revision | 创建或改写子 Spec、Ticket 或实现细节 |
21
23
  | Implementation Plan | `<Path>{roots.state}/specdev/changes/{change}/implementation-plan.md</Path>` | 父 Lead、全局 workspace/实现上限、frontier/Wave/locks/integration queue 和可恢复进度投影 | 改写子 change 权威或伪造完成 |
22
24
  | Implementation Orchestration Evidence | `<Path>{roots.state}/specdev/changes/{change}/evidence/implementation-orchestration.md</Path>` | 成员完成、组合 Ticket 顺序/锁、repository integration、整体验证、漂移和残余风险 | 新产品/架构决定或单 Ticket Evidence 替代品 |
23
- | Evidence | `<Path>{roots.state}/specdev/changes/{change}/evidence/{ticket-id}.md</Path>` | 实际修改、命令、结果、验收映射、偏差、风险和提交引用 | 新的产品或架构决策 |
25
+ | Evidence | `<Path>{roots.state}/specdev/changes/{change}/evidence/{ticket-id}.md</Path>` | 实际修改、命令、结果、验收映射、偏差、风险和提交引用 | 新的产品或架构决策或远程 Issue 正文 |
24
26
  | Change 学习图解 | `<Path>{roots.state}/specdev/changes/{change}/learning/index.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/learning/{number}_{topic}.md</Path>` | 面向零专业背景读者解释当前 change 的已验证工件、实现和测试事实;索引按序号持续追加 | 产品决定、架构决定、实现授权或 Learning workflow 知识 |
25
27
  | 代码审查 | `<Path>{roots.state}/specdev/changes/{change}/reviews/CR-###.md</Path>` | 固定点、标准轴和规范轴 finding | 实施修复或合并两轴排名 |
26
28
  | UI 设计包 | `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/design-system.md</Path>`、`<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/comparison/</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/final/</Path>` | 项目 UI 证据、功能风格候选、逐层用户决定、设计 token、交互合同和可运行 HTML/CSS/JS 投影 | 生产 UI 实现或替用户确认高影响偏好 |
@@ -50,7 +52,7 @@ Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永
50
52
 
51
53
  当前 change 决定与永久知识冲突时,必须在 LOG/ADR 中显式说明替代关系;它只约束当前 change,直到 A 决定是否提升并更新永久版本。
52
54
 
53
- `<Path>{roots.state}/specdev/changes/{change}/source.md</Path>` 只对“原始输入是什么”具有权威;后续用户决定、ADR 和 Spec 可以显式演进该意图。远程来源在摄入后发生变化不会自动改写本地合同,必须重新 Triage。
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。
54
56
 
55
57
  代码事实可以证明计划已过时,但不能静默改写用户目标或已接受契约。出现这种情况时,按 `<Path>{roots.workflows}/specdev/common/rules/deviation-control.md</Path>` 退回相应工件修订。
56
58
 
@@ -29,6 +29,6 @@ Owner 原子更新 `<Path>{roots.state}/specdev/changes/{change}/.status.json</P
29
29
 
30
30
  ## 远程来源与归档
31
31
 
32
- 远程动作不参与本地完成判定。Triage 为 `pending-close`/`close-failed` 时先 reconcile;`closed`、`waived` 或 `not-applicable` 才允许 Archive。归档后工件只读。
32
+ 远程动作不参与本地完成判定。Triage `external_action` 为 `pending-close`/`close-failed` 时先 reconcile;`closed`、`waived` 或 `not-applicable` 才允许 Archive。Triage `publish_action` 为 `pending`/`publish-failed` 时先恢复或结束 publish;`not-requested`、`published` 或 `waived` 才允许 Archive。`not-requested` 是默认,未点过 publish 的 change 不被新模式绑架。归档后工件只读。
33
33
 
34
34
  **完成标准**:完成声明可由本地工件、Git 与验证重建;只有一个 owner 命中;失败 candidate 不污染父分支。
@@ -51,3 +51,5 @@ required Ticket Done 必须有 source commit、通过 candidate、父分支 resu
51
51
  Direct Spec Evidence 至少包含:用户批准与轻量合同、Lead、实施前/最终 checkpoint、实际路径、定向/回归/E2E 命令及环境、验收映射、未运行项、偏差、残余风险和提交授权状态。
52
52
 
53
53
  父实现 change 的 Implementation Orchestration Evidence 不能替代子 Evidence。它至少记录最终 Map revision、全部成员最终状态和子证据指针、dependency/serialization 实际顺序、跨 change 合同检查、aggregate 命令/环境/结果、stale candidate 处理、偏差和残余风险。任何成员未 completed 或整体验证未通过时不得形成父完成证据。
54
+
55
+ Evidence 原文不出仓库。对 GitHub 的公共投影由 T-triage publish 按公共投影规则生成,不替代本文件的完整记录。
@@ -4,7 +4,9 @@
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 |
9
+ | 本地 change 完成且要把 Ticket 记到 GitHub | T-triage publish | reconcile / A / 停止 |
8
10
  | 疑难 bug 或性能回归 | D-diagnose-bugs | S / T / I / R / W |
9
11
  | 模糊但可通过决策访谈收敛 | G-grill-with-docs | P / S / T / W |
10
12
  | 大需求包含多个未界定 change 或未知路径 | W-wayfinder | G / P / D / S / T |
@@ -21,4 +23,4 @@
21
23
  同 change 下一阶段需要当前一手推理且上下文健康时继续;切换 repo/person/harness 或旁路时使用 `<Path>{roots.commands}/handoff.md</Path>`;严格限定且可独立派单时使用 Dispatch Packet;其他长上下文以权威工件路径恢复。平台不支持 clear/compact 时不虚构操作。
22
24
 
23
25
 
24
- 本地已清晰请求不强制绕行 Triage。G 是单 change Grill,P-prototype 是 UI 设计,不与统一 P-goal-plan 混淆。W 发现候选后逐个交 G/S/T;只有清晰且 Ready 的 child 才交 P。原缺陷诊断 D、风险分诊和远程 reconcile 职责不删除。
26
+ 本地已清晰请求不强制绕行 Triage。完成后要记账才选 publish;尚未成 Change、只想先留 inbox 记录才选 capturepublish、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
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:speculo:specdev:publish:v1",
4
+ "title": "SpecDev Publish Ledger Frontmatter",
5
+ "type": "object",
6
+ "required": ["schema_version", "artifact", "change", "mode", "repo", "publish_action", "include_cancelled", "origin", "updated_at"],
7
+ "properties": {
8
+ "schema_version": {"const": 1},
9
+ "artifact": {"const": "publish"},
10
+ "change": {"type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*$"},
11
+ "mode": {"const": "publish"},
12
+ "repo": {"type": "string", "pattern": "^.+/.+$"},
13
+ "publish_action": {"enum": ["pending", "published", "publish-failed", "waived"]},
14
+ "include_cancelled": {"type": "boolean"},
15
+ "parent_issue": {"type": ["integer", "null"]},
16
+ "origin": {"enum": ["local", "intake"]},
17
+ "published_at": {"type": ["string", "null"]},
18
+ "updated_at": {"type": "string", "minLength": 1}
19
+ },
20
+ "additionalProperties": true
21
+ }
@@ -54,6 +54,17 @@
54
54
  "cancelled"
55
55
  ]
56
56
  },
57
+ "kind": {
58
+ "enum": [
59
+ "bug",
60
+ "feature",
61
+ "refactor",
62
+ "investigation",
63
+ "operations",
64
+ "documentation",
65
+ "review"
66
+ ]
67
+ },
57
68
  "planning_depth": {
58
69
  "enum": [
59
70
  "lite",
@@ -8,13 +8,15 @@
8
8
  "schema_version": {"const": 1},
9
9
  "artifact": {"const": "triage"},
10
10
  "change": {"type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*$"},
11
- "mode": {"enum": ["intake", "reconcile"]},
11
+ "mode": {"enum": ["intake", "reconcile", "publish"]},
12
12
  "source": {"type": "string", "minLength": 1},
13
13
  "classification": {"enum": ["bug", "feature", "refactor", "investigation", "operations", "documentation", "review", "mixed"]},
14
14
  "risk": {"enum": ["low", "medium", "high", "critical"]},
15
15
  "route": {"type": "string", "pattern": "^specdev/[a-z0-9]+(?:-[a-z0-9]+)*$"},
16
16
  "ready_for_implementation": {"type": "boolean"},
17
17
  "external_action": {"enum": ["not-applicable", "pending-close", "closed", "close-failed", "waived"]},
18
+ "publish_action": {"enum": ["not-requested", "pending", "published", "publish-failed", "waived"]},
19
+ "publish": {"type": ["string", "null"]},
18
20
  "updated_at": {"type": "string", "minLength": 1}
19
21
  },
20
22
  "additionalProperties": true
@@ -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