@namewta/speculo 0.2.11 → 0.2.13

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/dist/src/cli.js CHANGED
@@ -3,31 +3,52 @@ import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { initSpeculo } from "./index.js";
5
5
  import { migrateSpeculo } from "./migrate.js";
6
+ import { checkForUpdate, formatVersionBanner, } from "./version.js";
6
7
  function usage() {
7
8
  return [
8
9
  "Usage:",
9
10
  " speculo init [--all] [target]",
10
11
  " speculo migrate [--apply] [target]",
12
+ " speculo version",
11
13
  "",
12
14
  "Commands:",
13
15
  " init Install or refresh Speculo core assets and selected workflow packages.",
14
16
  " Existing workflow state under speculo/.speculo/ is never overwritten.",
15
17
  " migrate Preview migration from v2 or transitional v3 state to the current v3 contract.",
16
18
  " Pass --apply to perform the staged, rollback-safe migration.",
19
+ " version Print the current Speculo version and check for updates.",
17
20
  "",
18
21
  "Options:",
19
22
  " --all Select every workflow and fully refresh all assets during init.",
20
23
  " --apply Apply a migration; without this flag migrate is always dry-run.",
21
24
  ].join("\n");
22
25
  }
26
+ function isInteractive() {
27
+ return process.stdin.isTTY === true;
28
+ }
29
+ async function showVersionWithCheck(packageRoot, packageName) {
30
+ const info = await checkForUpdate(packageRoot, packageName);
31
+ console.log(formatVersionBanner(info));
32
+ return info;
33
+ }
34
+ async function confirmContinue() {
35
+ if (!isInteractive()) {
36
+ return true;
37
+ }
38
+ const { confirm } = await import("@inquirer/prompts");
39
+ return confirm({
40
+ message: "是否继续运行 speculo init?",
41
+ default: true,
42
+ });
43
+ }
23
44
  async function main(argv) {
24
45
  const allFlag = argv.includes("--all");
25
46
  const applyFlag = argv.includes("--apply");
26
47
  const positional = argv.filter((argument) => argument !== "--all" && argument !== "--apply");
27
48
  const [command, targetArg, extra] = positional;
28
- if (!command || command === "--help" || command === "-h") {
49
+ if (command === "--help" || command === "-h") {
29
50
  console.log(usage());
30
- return command ? 0 : 1;
51
+ return 0;
31
52
  }
32
53
  if (extra) {
33
54
  console.error("Unexpected argument: " + extra);
@@ -36,14 +57,29 @@ async function main(argv) {
36
57
  }
37
58
  const target = resolve(targetArg ?? ".");
38
59
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
60
+ const packageName = "@namewta/speculo";
39
61
  try {
40
- if (command === "init") {
62
+ // ── version ──────────────────────────────────────────────
63
+ if (command === "version") {
64
+ await showVersionWithCheck(packageRoot, packageName);
65
+ return 0;
66
+ }
67
+ // ── init (or no command → default to init) ───────────────
68
+ if (!command || command === "init") {
41
69
  if (applyFlag) {
42
70
  throw new Error("--apply is only valid with `speculo migrate`.");
43
71
  }
44
- const result = await initSpeculo(target, {
72
+ // Show version info and check for updates
73
+ await showVersionWithCheck(packageRoot, packageName);
74
+ // Confirm before proceeding
75
+ const proceed = await confirmContinue();
76
+ if (!proceed) {
77
+ console.log("已取消。");
78
+ return 0;
79
+ }
80
+ const result = await initSpeculo(targetArg ?? ".", {
45
81
  packageRoot,
46
- all: allFlag,
82
+ all: command === "init" ? allFlag : false,
47
83
  });
48
84
  console.log(result.mode === "init"
49
85
  ? "Speculo initialized in " + result.target
@@ -53,6 +89,7 @@ async function main(argv) {
53
89
  }
54
90
  return 0;
55
91
  }
92
+ // ── migrate ──────────────────────────────────────────────
56
93
  if (command === "migrate") {
57
94
  if (allFlag) {
58
95
  throw new Error("--all is only valid with `speculo init`.");
@@ -79,6 +116,7 @@ async function main(argv) {
79
116
  }
80
117
  return 0;
81
118
  }
119
+ // ── update (deprecated) ──────────────────────────────────
82
120
  if (command === "update") {
83
121
  console.error("Warning: `speculo update` is deprecated. Use `speculo init`.");
84
122
  const result = await initSpeculo(target, {
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export type VersionInfo = {
2
+ local: string;
3
+ latest: string | null;
4
+ isOutdated: boolean;
5
+ };
6
+ export declare function isLocalOutdated(local: string, latest: string): boolean;
7
+ export declare function getLocalVersion(packageRoot: string): Promise<string>;
8
+ export declare function fetchLatestVersion(packageName: string): Promise<string | null>;
9
+ export declare function checkForUpdate(packageRoot: string, packageName: string): Promise<VersionInfo>;
10
+ export declare function formatVersionBanner(info: VersionInfo): string;
@@ -0,0 +1,87 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { get } from "node:https";
3
+ import { join } from "node:path";
4
+ function parseVersion(version) {
5
+ return version.split(".").map((part) => {
6
+ const num = parseInt(part, 10);
7
+ return Number.isNaN(num) ? 0 : num;
8
+ });
9
+ }
10
+ export function isLocalOutdated(local, latest) {
11
+ const localParts = parseVersion(local);
12
+ const latestParts = parseVersion(latest);
13
+ const length = Math.max(localParts.length, latestParts.length);
14
+ for (let index = 0; index < length; index += 1) {
15
+ const left = localParts[index] ?? 0;
16
+ const right = latestParts[index] ?? 0;
17
+ if (right > left)
18
+ return true;
19
+ if (left > right)
20
+ return false;
21
+ }
22
+ return false;
23
+ }
24
+ export async function getLocalVersion(packageRoot) {
25
+ const packagePath = join(packageRoot, "package.json");
26
+ const raw = await readFile(packagePath, "utf8");
27
+ const packageJson = JSON.parse(raw);
28
+ if (!packageJson.version || typeof packageJson.version !== "string") {
29
+ throw new Error("Missing or invalid version in " + packagePath);
30
+ }
31
+ return packageJson.version;
32
+ }
33
+ export async function fetchLatestVersion(packageName) {
34
+ return new Promise((resolve) => {
35
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
36
+ const request = get(url, { timeout: 5000 }, (response) => {
37
+ if (response.statusCode !== 200) {
38
+ response.resume();
39
+ resolve(null);
40
+ return;
41
+ }
42
+ let data = "";
43
+ response.on("data", (chunk) => {
44
+ data += chunk.toString();
45
+ });
46
+ response.on("end", () => {
47
+ try {
48
+ const parsed = JSON.parse(data);
49
+ resolve(parsed.version && typeof parsed.version === "string"
50
+ ? parsed.version
51
+ : null);
52
+ }
53
+ catch {
54
+ resolve(null);
55
+ }
56
+ });
57
+ });
58
+ request.on("error", () => resolve(null));
59
+ request.on("timeout", () => {
60
+ request.destroy();
61
+ resolve(null);
62
+ });
63
+ });
64
+ }
65
+ export async function checkForUpdate(packageRoot, packageName) {
66
+ const local = await getLocalVersion(packageRoot);
67
+ const latest = await fetchLatestVersion(packageName);
68
+ const isOutdated = latest !== null && isLocalOutdated(local, latest);
69
+ return { local, latest, isOutdated };
70
+ }
71
+ export function formatVersionBanner(info) {
72
+ const lines = [];
73
+ lines.push("");
74
+ lines.push(" Speculo v" + info.local);
75
+ if (info.latest !== null) {
76
+ if (info.isOutdated) {
77
+ lines.push(" 最新版本: v" + info.latest + "(已有新版本可用!)");
78
+ lines.push(" 升级命令: npm i -g @namewta/speculo@" + info.latest);
79
+ }
80
+ else {
81
+ lines.push(" 已是最新版本 ✓");
82
+ }
83
+ }
84
+ lines.push("");
85
+ return lines.join("\n");
86
+ }
87
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namewta/speculo",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Workflow-packaged specification-driven development assets with install, update, and migration tooling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -127,7 +127,7 @@ description: >
127
127
 
128
128
  1. **重新验证**:路径包含检查、预检重跑(确认计划生成后无新 change 插入)、store 存在性重验。
129
129
  2. **执行顺序**:
130
- a. **归档移动**(原子批处理):创建月目录 → 移动 change 目录 → 更新 `.status.json` → 更新 `status.json#active`
130
+ a. **归档移动**(原子批处理):创建月目录 → 移动 change 目录 → 更新 `.status.json` → `status.json` 的 `active` 移除对应条目,追加到 `completed` 数组
131
131
  b. **知识合并写入**:创建 lazy stores(如 `adr/`、`context/` 不存在则创建)→ 写入新 ADR → 合并术语到 `context/` → 标记 superseded ADR
132
132
  c. **清理**:删除已批准文件 → 合并已批准内容 → 改写已批准条目
133
133
  3. 任一步骤失败:报告已完成/失败清单,停止,不猜测成功。
@@ -136,7 +136,7 @@ description: >
136
136
 
137
137
  1. 重读源路径:归档 change 必须不存在于 `changes_root/`。
138
138
  2. 重读目标路径:归档 change 完整存在于 `archive_root/<YYYY-MM>/`,知识 store 内容正确。
139
- 3. 重读 `status.json`:`active` 数组不包含已归档 change
139
+ 3. 重读 `status.json`:`active` 数组不包含已归档 change 条目,`completed` 数组已追加对应归档记录。
140
140
  4. 重读归档 `.status.json`:`change_status: archived`、`archived: true`、`archive_path` 一致。
141
141
  5. 对照知识 stores:新内容存在,无不期望的修改。
142
142
  6. 任一不一致 → `blocked`,报告具体差异;全部通过 → `verified`。
@@ -26,7 +26,8 @@
26
26
 
27
27
  归档执行后将对 `status.json` 做如下变更:
28
28
 
29
- - `active` 数组移除:`["2026-07-15-add-auth", "2026-07-10-fix-timezone"]`
29
+ - `active` 数组移除对应 change 条目
30
+ - `completed` 数组追加归档记录(`change`、`path`、`archived_at`、`archive_path`)
30
31
  - 每个归档 change 的 `.status.json` 更新:`change_status: archived`, `archived: true`
31
32
 
32
33
  ## 阻塞项详情
@@ -10,7 +10,7 @@
10
10
  - `.status.json` 可解析,`change_status` 字段存在且值为 `completed`。
11
11
  - 源位于 `changes_root/<change>` 且真实存在。
12
12
  - 目标位于 `archive_root/<YYYY-MM>/<change>`(YYYY-MM 从 change 名称提取),目标目录不存在。
13
- - Workflow `status.json` 与 change 状态一致:change 出现在 `active` 数组中。
13
+ - Workflow `status.json` 与 change 状态一致:change 条目出现在 `active` 数组中(通过 `change` 字段匹配),且 `result` 为 `"completed"`。
14
14
  - 若 worktree 模式:已合并回目标分支并清理;未合并则记录 `blocked`。
15
15
  - **任一预检失败阻塞整批操作**(批量原子性)。
16
16
 
@@ -18,7 +18,7 @@
18
18
 
19
19
  1. 创建 `archive_root/<YYYY-MM>/` 月目录(如不存在)。
20
20
  2. 将 `changes_root/<change>/` 整个目录移动到 `archive_root/<YYYY-MM>/<change>/`。使用原子移动(mv/rename),不用复制后删除。
21
- 3. 从 workflow `status.json#active` 数组中移除该 change 条目。
21
+ 3. 从 workflow `status.json` 的 `active` 数组中移除该 change 条目,追加归档记录到 `completed` 数组(`change`、`path`、`archived_at`、`archive_path`)。
22
22
  4. 更新已移动的 `.status.json`:
23
23
  - `change_status: archived`
24
24
  - `archived: true`
@@ -41,8 +41,8 @@
41
41
 
42
42
  1. 源路径不存在(移动成功)。
43
43
  2. 目标路径完整存在,内容与移动前一致。
44
- 3. Workflow `status.json#active` 已移除该 change
44
+ 3. Workflow `status.json` 的 `active` 数组已移除该 change 条目,`completed` 数组已追加对应归档记录。
45
45
  4. 归档目录 `.status.json` 字段一致(`change_status: archived`、`archived: true`、`archive_path` 正确)。
46
46
  5. 验证失败时报告已完成/未完成清单,不猜测成功。
47
47
 
48
- 完成标准:源不存在、目标完整、active 索引已移除、归档状态字段一致。
48
+ 完成标准:源不存在、目标完整、active 索引已移除且 completed 已追加、归档状态字段一致。
@@ -23,7 +23,7 @@ keywords: [归档, 沉淀, 知识持久化, 清理, ADR, 词汇表, 研究]
23
23
 
24
24
  ### 2. 扫描已完成变更
25
25
 
26
- 遍历 `<Path>{roots.state}/specdev/changes/</Path>`,读取每个变更的 `.status.json`,筛选 `change_status: completed`。收集每个已完成变更的 ADR.md、CONTEXT.md、LOG.md 及 research/ 子目录产物。
26
+ 遍历 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组,筛选 `result: "completed"` 的 change;同时遍历 `<Path>{roots.state}/specdev/changes/</Path>`,读取每个变更的 `.status.json` 作为补充(`change_status: completed`)。收集每个已完成变更的 ADR.md、CONTEXT.md、LOG.md 及 research/ 子目录产物。
27
27
 
28
28
  **完成标准**:每个已完成变更的元数据和知识产物已收集;无可读产物的变更已标注原因。
29
29
 
@@ -10,7 +10,7 @@
10
10
  2. **状态可解析**:`.status.json` 可解析,`change_status` 字段存在且值为 `completed`。
11
11
  3. **源存在**:源目录 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 真实存在。
12
12
  4. **目标不冲突**:目标目录 `<Path>{roots.state}/specdev/archive/<YYYY-MM>/<change>/</Path>` 不存在(YYYY-MM 从 change 名称提取)。
13
- 5. **状态一致**:workflow `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中包含该 change(或不包含但 change 自身状态为 completed——此时记录警告但不阻塞)。
13
+ 5. **状态一致**:`<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中存在该 change 的条目(通过 `change` 字段匹配),且 `result` 为 `"completed"`(或不匹配但 change 自身 `.status.json` 状态为 completed——此时记录警告但不阻塞)。
14
14
  6. **worktree 已合并**:若使用了 worktree 隔离模式,确认已合并回目标分支并清理;未合并则记录 `blocked`。
15
15
 
16
16
  ## 归档移动步骤
@@ -19,7 +19,7 @@
19
19
 
20
20
  1. 创建 `<Path>{roots.state}/specdev/archive/<YYYY-MM>/</Path>` 月目录(如不存在)。
21
21
  2. 将 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 整个目录原子移动到 `<Path>{roots.state}/specdev/archive/<YYYY-MM>/<change>/</Path>`。使用 mv/rename,不用复制后删除。
22
- 3. 从 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中移除该 change
22
+ 3. 从 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中移除该 change 的条目,追加归档记录到 `completed` 数组(`change`、`path`、`archived_at`、`archive_path`)。
23
23
  4. 更新已移动的 `.status.json`:
24
24
  - `change_status: "archived"`
25
25
  - `archived: true`
@@ -42,7 +42,7 @@
42
42
 
43
43
  1. 源路径不存在(移动成功)。
44
44
  2. 目标路径完整存在,内容与移动前一致。
45
- 3. `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组已移除该 change
45
+ 3. `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组已移除该 change 条目,`completed` 数组已追加归档记录。
46
46
  4. 归档目录 `.status.json` 字段一致(`change_status: archived`、`archived: true`、`archive_path` 正确)。
47
47
  5. 验证失败时报告已完成/未完成清单,不猜测成功。
48
48
 
@@ -17,13 +17,14 @@ keywords: [设计, 访谈, 领域建模, ADR, 决策记录, 词汇表, 设计轨
17
17
 
18
18
  ### 1. 启动变更
19
19
 
20
- 创建 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 目录(`{change}` 为 `<YYYY-MM-DD>-<topic>` 格式),初始化三个空文件模板:
20
+ 创建 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 目录(`{change}` 为 `<YYYY-MM-DD>-<topic>` 格式),初始化三个空文件模板及状态文件:
21
21
 
22
+ - `<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>` — 初始状态(`change_status: "active"`、`created_at` 为当前时间、`completed_at: null`、`archived: false`、`archive_path: null`)
22
23
  - `<Path>{roots.state}/specdev/changes/{change}/ADR.md</Path>` — 架构决策记录,仅含 `# 架构决策记录` 标题
23
24
  - `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>` — 设计决策日志,含 `# 设计决策日志` 标题及维护规则说明
24
25
  - `<Path>{roots.state}/specdev/changes/{change}/CONTEXT.md</Path>` — 领域词汇表,含 `# {主题} 领域词汇表` 标题及一两句描述
25
26
 
26
- **完成标准**:变更目录 `<YYYY-MM-DD>-<topic>` 已创建,初始 ADR.md/LOG.md/CONTEXT.md 已就位。
27
+ **完成标准**:变更目录 `<YYYY-MM-DD>-<topic>` 已创建,初始 .status.json、ADR.mdLOG.mdCONTEXT.md 已就位。
27
28
 
28
29
  ### 2. 访谈
29
30
 
@@ -6,7 +6,7 @@ specdev workflow 的变更以 markdown 目录形式存储在 `{roots.state}/spec
6
6
 
7
7
  - 每个变更一个目录:`{roots.state}/specdev/changes/<YYYY-MM-DD>-<topic>/`
8
8
  - 例如:`changes/2026-07-21-add-auth-layer/`
9
- - 当前活跃变更通过 `{roots.state}/specdev/status.json` 的 `active` 数组追踪
9
+ - 当前活跃变更通过 `{roots.state}/specdev/status.json` 的 `active` 数组追踪,每个条目为包含 `change`、`current_work`、`works_run`、`result` 等字段的对象
10
10
  - 归档变更移至:`{roots.state}/specdev/archive/YYYY-MM/<change>/`
11
11
  - 例如:`archive/2026-07/2026-07-21-add-auth-layer/`
12
12
  - 变更目录内的工作产物由各 work 定义,典型结构:
@@ -24,17 +24,24 @@ specdev workflow 的变更以 markdown 目录形式存储在 `{roots.state}/spec
24
24
  - `status.json` 结构:
25
25
  ```jsonc
26
26
  {
27
- "schema_version": 1,
27
+ "schema_version": 2,
28
28
  "workflow": "specdev",
29
29
  "active": [
30
- "2026-07-21-add-auth-layer"
31
- ]
30
+ {
31
+ "change": "2026-07-21-add-auth-layer",
32
+ "current_work": "specdev/grill-with-docs",
33
+ "works_run": [],
34
+ "result": null
35
+ }
36
+ ],
37
+ "work_history": [],
38
+ "completed": []
32
39
  }
33
40
  ```
34
41
 
35
42
  ## 当 work 说"发布到变更目录"时
36
43
 
37
- 在 `{roots.state}/specdev/changes/<change>/` 下创建或更新指定文件。如果变更目录尚未加入 `active` 数组,将其追加到 `status.json` 的 `active` 中。
44
+ 在 `{roots.state}/specdev/changes/<change>/` 下创建或更新指定文件。如果变更目录尚未加入 `active` 数组,将其作为新条目(`{ change, current_work: null, works_run: [], result: null }`)追加到 `status.json` 的 `active` 中。
38
45
 
39
46
  例如:`S-spec` 说"将规格发布到变更目录" → 写入 `<Path>{roots.state}/specdev/changes/<change>/spec.md</Path>`。
40
47
 
@@ -44,7 +51,7 @@ specdev workflow 的变更以 markdown 目录形式存储在 `{roots.state}/spec
44
51
 
45
52
  ## 当 work 说"归档变更"时
46
53
 
47
- 将变更目录从 `{roots.state}/specdev/changes/<change>/` 移动到 `{roots.state}/specdev/archive/YYYY-MM/<change>/`(YYYY-MM 取变更日期中的年月),并从 `status.json` 的 `active` 数组中移除。
54
+ 将变更目录从 `{roots.state}/specdev/changes/<change>/` 移动到 `{roots.state}/specdev/archive/YYYY-MM/<change>/`(YYYY-MM 取变更日期中的年月),从 `status.json` 的 `active` 数组中移除对应条目,追加归档记录到 `completed` 数组。
48
55
 
49
56
  ## Wayfinding 操作
50
57
 
@@ -41,25 +41,46 @@ keywords: [specdev, 软件研发, 设计, spec, tickets, 寻路, TDD, 实现,
41
41
 
42
42
  1. **解析运行时** — 解析 workspace 配置和 workflow/state roots。已解析时复用。
43
43
  2. **选择 change** — 读取 `<Path>{roots.state}/specdev/status.json</Path>`:
44
- - 用户指定 → 直接使用
45
- - 唯一活跃 change → 直接使用
46
- - 无活跃 创建 `changes/<YYYY-MM-DD>-<kebab-topic>/`,注册到 `active` 数组
47
- - 多个候选 → 先消歧
44
+ - 用户指定 → 在 `active` 数组中查找匹配 `change` 字段的条目
45
+ - 唯一活跃 change → 直接使用 `active[0]`
46
+ - 无活跃(`active` 为空数组)→ 创建 `changes/<YYYY-MM-DD>-<kebab-topic>/`,追加条目 `{ change, current_work: null, works_run: [], result: null }` 到 `active`
47
+ - 多个候选 → 列出 `active` 中各 change,由用户消歧
48
48
 
49
49
  ## 状态字段
50
50
 
51
51
  `<Path>{roots.state}/specdev/status.json</Path>` 包含以下字段:
52
52
 
53
- - **`schema_version`**(数字)— 状态 schema 版本号,当前为 1
53
+ - **`schema_version`**(数字)— 状态 schema 版本号,当前为 2
54
54
  - **`workflow`**(字符串)— workflow 标识,固定为 `"specdev"`
55
- - **`active`**(字符串数组)— 当前活跃 change 的目录名列表,每个元素为 `"YYYY-MM-DD-<topic>"` 格式。空数组表示无活跃 change
56
- - **`current_work`**(字符串或 null)— 当前正在执行的 work id,如 `"specdev/grill-with-docs"`。无正在执行的 work 时为 null
55
+ - **`active`**(对象数组)— 当前活跃 change 的状态条目,每个条目包含:
56
+ - `change` change 目录名,格式 `"YYYY-MM-DD-<topic>"`
57
+ - `current_work` — 该 change 当前正在执行的 work id,如 `"specdev/grill-with-docs"`。无正在执行的 work 时为 null
58
+ - `works_run` — 该 change 已执行过的 work id 列表
59
+ - `result` — 该 change 的整体结果:null(进行中)或 `"completed"`(全部 work 已完成)
60
+ - `claimed_tickets` —(可选,W-wayfinder 使用)当前被领取的 ticket 名称列表,用于并发会话跳过
57
61
  - **`work_history`**(对象数组)— work 调用记录,每条包含:
62
+ - `change` — 所属 change 目录名
58
63
  - `work_id` — work 标识
59
64
  - `started_at` — 开始时间(ISO 8601)
60
65
  - `completed_at` — 完成时间(ISO 8601),未完成时为 null
61
66
  - `result` — 完成结果,如 `"completed"`、`"aborted"`
62
- - `artifacts` 产物的项目相对路径列表
67
+ - **`completed`**(对象数组)— 已归档 change 记录,每条包含:
68
+ - `change` — change 目录名
69
+ - `path` — 归档前 change 目录的相对路径
70
+ - `archived_at` — 归档时间(ISO 8601)
71
+ - `archive_path` — 归档目标路径的相对路径
72
+
73
+ ### Per-change 状态文件
74
+
75
+ 每个 change 目录内维护 `<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>`,追踪该 change 的个体状态:
76
+
77
+ - **`change_status`**(字符串)— change 生命周期状态:`"active"`(进行中)、`"completed"`(全部 work 完成)、`"archived"`(已归档)
78
+ - **`created_at`**(ISO 8601 字符串)— change 创建时间
79
+ - **`completed_at`**(ISO 8601 字符串或 null)— change 完成时间,进行中时为 null
80
+ - **`archived`**(布尔值)— 是否已归档,默认 false
81
+ - **`archive_path`**(字符串或 null)— 归档目标路径的相对路径,未归档时为 null
82
+
83
+ 创建 change 时由首个 work(如 `T-triage` 步骤 3 或 `G-grill-with-docs` 步骤 1)写入初始 `.status.json`,`change_status` 初始为 `"active"`。change 内所有 work 完成后,由最后一个 work 更新 `change_status` 为 `"completed"`。归档时由 `A-archive-and-consolidate` 更新为 `"archived"`。
63
84
 
64
85
  ## 路径分配
65
86
 
@@ -77,14 +98,15 @@ keywords: [specdev, 软件研发, 设计, spec, tickets, 寻路, TDD, 实现,
77
98
 
78
99
  <!-- AUTO-INDEX-START -->
79
100
 
80
- - **A-archive-and-consolidate** — 归档与沉淀:将已完成变更归档至 archive/,智能评估变更产物与现有 adr/、context/、research/ 知识库的差异,执行创建/更新/合并/废弃,审计清理陈旧内容,确保知识始终最新。
101
+ - **A-archive-and-consolidate** — 归档与沉淀:将已完成变更归档至 archive/,并智能评估、提取持久化知识到 adr/、context/、research/ 知识库——与现有知识逐项比对,执行创建/更新/合并/废弃,确保知识始终最新。
81
102
  - **D-diagnose-bugs** — 诊断:针对疑难 bug 建立诊断循环——构建紧凑反馈回路、复现最小化、可证伪假设排名、插桩定位根因,确认后移交 I-implement 修复。
82
103
  - **G-grill-with-docs** — 设计访谈(带文档):无情访谈打磨设计,同时持续产出 ADR.md、LOG.md 和 CONTEXT.md 三个领域文档。在设计讨论中捕获术语定义、记录架构决策、保存完整设计轨迹。
83
104
  - **I-implement** — 实现:基于 spec 或 tickets 实现工作——以深层模块设计原则指导架构、以 TDD 红绿循环驱动编码、以双轴审查把关质量。
84
105
  - **I-init-setup** — 初始化设置:为 specdev workflow 配置变更追踪、领域文档布局、状态标签和语言偏好。首次使用其他 specdev works 前运行一次。
85
- - **P-goal-plan** — 目标规划:将 spec、tickets 和参考权威综合为一份目标规划文档——编排多 ticket 里程碑的约束、质量门禁和执行协议,桥接"已有 tickets"到"协调执行 20+ tickets"
106
+ - **P-goal-plan** — 目标规划:将 spec、tickets 和参考权威综合为一份目标规划文档——编排多 ticket 里程碑的约束、质量门禁和执行协议,桥接"已有 tickets"到"协调执行 20+ tickets"
86
107
  - **S-spec** — 编写 Spec:将当前对话综合为一份完整的 spec 文档,包含问题陈述、解决方案、用户故事、实现决策和测试决策,持久化到变更目录。
87
108
  - **T-tickets** — 拆分 Tickets:将 spec 或计划拆分为一组曳光弹式垂直切片 tickets,每个声明阻塞边,持久化到变更目录。支持宽重构的扩展-收缩排序。
109
+ - **T-triage** — Issue 分诊:将外部 issue 摄入并分诊为本地 change:深度理解上下文后写入 source-issue.md 与 triage.md,再推荐下一 work(G-grill / S-spec / I-implement / D-diagnose 等)。
88
110
  - **W-wayfinder** — 寻路:为超出单次会话容量的大块工作绘制共享地图,逐个解决调查 tickets 直到通往目标的路径清晰可见。支持研究和决策型 ticket 类型。
89
111
 
90
112
  <!-- AUTO-INDEX-END -->
@@ -59,7 +59,7 @@ keywords: [目标规划, 编排, 里程碑, 门禁, Lead, Subagent, 合同, 参
59
59
 
60
60
  ### 6. 写入产物与停止
61
61
 
62
- 将完整 goal-plan.md 写入 `<Path>{roots.state}/specdev/changes/{change}/goal-plan.md</Path>`。更新 `<Path>{roots.state}/specdev/status.json</Path>` 记录 work 完成状态与产物路径。
62
+ 将完整 goal-plan.md 写入 `<Path>{roots.state}/specdev/changes/{change}/goal-plan.md</Path>`。更新 `<Path>{roots.state}/specdev/status.json</Path>`:在 `work_history` 中追加条目(含 `change`、`work_id`、`started_at`、`completed_at`、`result`),在 `active` 中更新当前 change 条目的 `works_run` 列表。
63
63
 
64
64
  向用户汇报产物摘要(ticket 数量、门禁层级、合同/参考权威引用、关键约束),明确询问进入实现阶段(`<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>`)或需要进一步修订。
65
65
 
@@ -0,0 +1,76 @@
1
+ ---
2
+ id: specdev/triage
3
+ type: workflow-entry
4
+ workflow: specdev
5
+ name: Issue 分诊
6
+ description: 将外部 issue 摄入并分诊为本地 change:深度理解上下文后写入 source-issue.md 与 triage.md,再推荐下一 work(G-grill / S-spec / I-implement / D-diagnose 等)。
7
+ keywords: [分诊, triage, issue, 摄入, 变更引导, needs-triage]
8
+ ---
9
+
10
+ # Issue 分诊
11
+
12
+ 将外部 issue **分诊**为本地 change 引导:摄入 → 深度理解 → 写入 `source-issue.md` 与 `triage.md` → 推荐下一 work 后停止。本 work 只落本地产物;ADR / LOG / CONTEXT / spec / ticket 与 tracker 写回由其他 work 或 `common/triage` skill 负责。
13
+
14
+ 产物路径:`<Path>{roots.state}/specdev/changes/{change}/</Path>`(`{change}` = `<YYYY-MM-DD>-<topic>`)。
15
+
16
+ ## 流程
17
+
18
+ ### 1. 摄入 Issue
19
+
20
+ 加载 `<Path>{roots.workflows}/specdev/T-triage/intake-rules.md</Path>`。将 `#N`、URL、粘贴正文或口头描述规范化为统一结构(source、title、body、comments、来源标识)。`gh` 可用则拉取;否则请用户粘贴或补齐最小字段。
21
+
22
+ **完成标准**:标题、正文、评论与来源标识齐全,或已标注 paste/manual 且最小字段已齐。
23
+
24
+ ### 2. 深度理解
25
+
26
+ 加载 `<Path>{roots.workflows}/specdev/T-triage/understanding-rules.md</Path>`。行为契约对齐 `<Path>{roots.workflows}/specdev/common/triage/AGENT-BRIEF.md</Path>`;范围外只读去重遵循 `<Path>{roots.workflows}/specdev/common/triage/OUT-OF-SCOPE.md</Path>`。
27
+
28
+ 读取永久 `<Path>{roots.state}/specdev/adr/</Path>`、`<Path>{roots.state}/specdev/context/</Path>`(若有);按领域概念探查代码库;扫描 `.out-of-scope/`;对 bug 做轻量可复现判定;归类 `bug` | `enhancement` 并列出具体信息缺口。
29
+
30
+ **完成标准**:类别已判定;冗余与范围外结果已报告;验证结果或缺口已明确;足以起草行为摘要或已穷尽缺失问题。
31
+
32
+ ### 3. 选择 / 创建 Change
33
+
34
+ 读 `<Path>{roots.state}/specdev/status.json</Path>`(INDEX 启动协议):新 issue 默认创建 `changes/<YYYY-MM-DD>-<kebab-topic>/`;仅用户声明续作时复用 active;多候选先消歧。在 `active` 数组中追加条目 `{ change, current_work: "specdev/triage", works_run: [], result: null }`;`work_history` 追加进行中记录(含 `change` 字段,缺字段时按 INDEX 补齐)。创建 change 目录后写入初始 `<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>`(`change_status: "active"`、`created_at` 为当前时间)。
35
+
36
+ **完成标准**:`{change}` 目录存在;`active` 含该 change;`current_work` 为 `"specdev/triage"`。
37
+
38
+ ### 4. 写入分诊产物
39
+
40
+ 加载 `<Path>{roots.workflows}/specdev/T-triage/artifact-templates.md</Path>`,仅写入:
41
+
42
+ 1. `<Path>{roots.state}/specdev/changes/{change}/source-issue.md</Path>` — 原文快照
43
+ 2. `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>` — 分诊结论、行为契约草案、推荐 status 与 next work
44
+
45
+ 推荐 status 只记在 `triage.md`。标签字符串可读 `<Path>{roots.state}/specdev/.config/status-labels.md</Path>`(若有),否则用角色名。
46
+
47
+ **完成标准**:两文件已落盘;含类别、推荐 status、行为契约草案与 next work;无残留 `[TODO:]`(`needs-info` 的信息缺口列表除外)。
48
+
49
+ ### 5. 路由推荐
50
+
51
+ 加载 `<Path>{roots.workflows}/specdev/T-triage/routing-rules.md</Path>`。首匹配恰好一条主推荐(可附一条备选),写入 `triage.md` 并展示 Path 与理由。
52
+
53
+ **完成标准**:主推荐已展示;下游尚未启动。
54
+
55
+ ### 6. 停止并交接
56
+
57
+ 更新 `<Path>{roots.state}/specdev/status.json</Path>`:`active` 中对应条目的 `current_work = null`;`work_history` 对应条目补 `completed_at`、`result`(无 `artifacts` 字段)。汇报 change、类别、status、next work、产物路径;明确询问是否进入推荐 work。用户确认前保持代码与下游不动。
58
+
59
+ **完成标准**:status 已更新;用户已收到摘要与确认问题;本 work 结束。
60
+
61
+ ## 子文件引用
62
+
63
+ | 文件 | 内容 | 触发条件 |
64
+ |------|------|----------|
65
+ | `<Path>{roots.workflows}/specdev/T-triage/intake-rules.md</Path>` | gh / 粘贴摄入、字段规范化 | 步骤 1 |
66
+ | `<Path>{roots.workflows}/specdev/T-triage/understanding-rules.md</Path>` | 理解清单、冗余与范围外 | 步骤 2 |
67
+ | `<Path>{roots.workflows}/specdev/T-triage/artifact-templates.md</Path>` | 两产物模板 | 步骤 4 |
68
+ | `<Path>{roots.workflows}/specdev/T-triage/routing-rules.md</Path>` | 首匹配路由与话术 | 步骤 5 |
69
+ | `<Path>{roots.workflows}/specdev/common/triage/AGENT-BRIEF.md</Path>` | 行为契约原则 | 步骤 2/4 起草摘要 |
70
+ | `<Path>{roots.workflows}/specdev/common/triage/OUT-OF-SCOPE.md</Path>` | 范围外只读去重 | 步骤 2 扫描时 |
71
+
72
+ ## 依赖关系
73
+
74
+ - **上游**:外部 issue(`gh` 或用户);可选永久 adr/context;可选 `.out-of-scope/`
75
+ - **下游**(确认后):`<Path>{roots.workflows}/specdev/G-grill-with-docs/G-grill-with-docs.md</Path>`、`<Path>{roots.workflows}/specdev/S-spec/S-spec.md</Path>`、`<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>`、`<Path>{roots.workflows}/specdev/D-diagnose-bugs/D-diagnose-bugs.md</Path>`、`<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>`
76
+ - **并列**:`<Path>{roots.workflows}/specdev/common/triage/SKILL.md</Path>` 仍可独立做 tracker 状态机;本 work 只引用其 AGENT-BRIEF / OUT-OF-SCOPE
@@ -0,0 +1,122 @@
1
+ # 分诊产物模板
2
+
3
+ 本 work 在 change 目录**仅**写入以下两个文件。路径:
4
+
5
+ - `<Path>{roots.state}/specdev/changes/{change}/source-issue.md</Path>`
6
+ - `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`
7
+
8
+ 行为契约章节对齐 `<Path>{roots.workflows}/specdev/common/triage/AGENT-BRIEF.md</Path>`,但落在本地文件,而非 tracker 评论。
9
+
10
+ 推荐 status 角色字符串:若存在 `<Path>{roots.state}/specdev/.config/status-labels.md</Path>`,使用其「标签」列;否则使用角色名本身(`needs-triage` / `needs-info` / `ready-for-agent` / `ready-for-human` / `wontfix`)。
11
+
12
+ ---
13
+
14
+ ## source-issue.md
15
+
16
+ ```markdown
17
+ # Source Issue
18
+
19
+ - **Source:** gh | paste | manual
20
+ - **Kind:** issue | pr | manual
21
+ - **ID:** #N 或 n/a
22
+ - **URL:** <url 或空>
23
+ - **Fetched at:** <ISO-8601>
24
+ - **Author:** <作者>
25
+ - **Labels:** <逗号分隔,或无>
26
+ - **State:** <open/closed 或空>
27
+
28
+ ## Title
29
+
30
+ <标题>
31
+
32
+ ## Body
33
+
34
+ <正文原文>
35
+
36
+ ## Comments
37
+
38
+ ### @<author> (<date>)
39
+
40
+ <body>
41
+
42
+ <!-- 无评论时写:_无评论_ -->
43
+ ```
44
+
45
+ 填写规则:
46
+
47
+ - 保留摄入时快照;远端后续编辑不自动同步
48
+ - 评论按时间顺序;过长可摘要并注明「已截断,完整内容见远端 #N」
49
+ - `manual` 来源:Body 由问题描述 + 期望行为组成,并在顶部注明「非远端快照」
50
+
51
+ ---
52
+
53
+ ## triage.md
54
+
55
+ ```markdown
56
+ # Triage: <一句话主题>
57
+
58
+ - **Change:** <YYYY-MM-DD-kebab-topic>
59
+ - **Category:** bug | enhancement
60
+ - **Recommended status:** needs-triage | needs-info | ready-for-agent | ready-for-human | wontfix
61
+ - **Recommended next work:** <显示名> | none
62
+ - **Source:** [./source-issue.md](./source-issue.md)
63
+ - **Verification:** confirmed | not-reproduced | needs-info | n/a
64
+
65
+ ## 问题摘要
66
+
67
+ <用户可理解的一两句>
68
+
69
+ ## 理解结论
70
+
71
+ - **代码库现状:** <相关模块/接口与现状行为>
72
+ - **验证结果:** <同上 Verification,可附命令/证据>
73
+ - **冗余 / 范围外:** none | 已实现于… | 匹配 `.out-of-scope/<concept>.md`(用户选择:确认/重新考虑/不相关)
74
+
75
+ ## 行为契约草案
76
+
77
+ **Current behavior:**
78
+ <当前发生什么>
79
+
80
+ **Desired behavior:**
81
+ <完成后应发生什么;含边界与错误条件>
82
+
83
+ **Key interfaces:**
84
+ - `<TypeOrFn>` — 需要改变什么以及为什么
85
+ - …
86
+
87
+ **Acceptance criteria:**
88
+ - [ ] <可独立验证的标准 1>
89
+ - [ ] <可独立验证的标准 2>
90
+
91
+ **Out of scope:**
92
+ - <本 change 明确不做的事项>
93
+
94
+ ## 信息缺口
95
+
96
+ <!-- needs-info 或仍有开放问题时填写;否则写「无」 -->
97
+
98
+ - <具体可回答的问题 1>
99
+ - <具体可回答的问题 2>
100
+
101
+ ## 推荐下一 Work
102
+
103
+ - **主推荐:** <Path>{roots.workflows}/specdev/<Work>/<Work>.md</Path> 或 `none`
104
+ - **理由:** <对应 routing-rules 首匹配条件的一句话>
105
+ - **备选:** <可选一条 Path 或 none>
106
+ - **停止说明:** 用户确认前不启动下游 work
107
+ ```
108
+
109
+ 填写规则:
110
+
111
+ - 一句话主题来自 issue 标题的 kebab 压缩语义,与 change 目录名一致或为其可读版
112
+ - `Recommended next work` 显示名与路由表一致(如「设计访谈」「编写 Spec」「实现」「诊断」「寻路」「none」)
113
+ - 行为契约不足时:status 倾向 `needs-info`,next 为 `none`,缺口章节穷尽
114
+ - 已实现或用户确认拒绝:status `wontfix`,next `none`;范围外文件仅提示,默认不自动创建
115
+ - 无残留 `[TODO:]` 占位符
116
+
117
+ ## 完成检查
118
+
119
+ - 两文件均已存在于 `{change}` 目录
120
+ - `triage.md` 元数据五行齐全(Change / Category / Recommended status / Recommended next work / Source)
121
+ - 行为契约五块齐全(Current / Desired / Key interfaces / AC / Out of scope)——`needs-info` 时 AC 可较少,但缺口必须穷尽
122
+ - 主推荐 Path 使用 `{roots.workflows}` 别名,或为字面 `none`
@@ -0,0 +1,71 @@
1
+ # 摄入规则
2
+
3
+ 将外部 issue 或 PR 规范化为会话内统一结构,供后续理解与落盘使用。本步骤只读取远端或用户输入,不写回 tracker。
4
+
5
+ ## 远程可用性
6
+
7
+ 按顺序探测:
8
+
9
+ 1. 当前目录处于 git 仓库内
10
+ 2. `gh` 可执行
11
+ 3. `gh auth status` 成功
12
+
13
+ 三者皆满足 → **远程可用**。任一项失败 → 按粘贴 / 口头路径处理,向用户说明原因。
14
+
15
+ ## 远程拉取
16
+
17
+ 用户给出 `#N`、纯数字编号、issue URL 或 PR URL 时:
18
+
19
+ **Issue(优先)**
20
+
21
+ ```bash
22
+ gh issue view <n> --json number,title,body,author,labels,url,createdAt,state,comments
23
+ ```
24
+
25
+ 人类可读备选:`gh issue view <n> --comments`。
26
+
27
+ **PR(用户明确给了 PR,或 issue view 失败且 `gh pr view <n>` 成功)**
28
+
29
+ ```bash
30
+ gh pr view <n> --json number,title,body,author,labels,url,createdAt,state,comments,files
31
+ ```
32
+
33
+ PR 按「附带代码的 issue」处理:正文 + 评论 + 变更文件列表进入内部结构;diff 摘要可记入 `body` 附录或 comments 旁注。仍只写本地产物。
34
+
35
+ **解析 `#N`**:先尝试 `gh pr view N`,再 `gh issue view N`(或按用户声明的类型二选一)。
36
+
37
+ **失败回退**:网络错误、无权限、编号不存在 → 向用户说明,并请粘贴标题 + 正文 + 关键评论。不中止分诊。
38
+
39
+ ## 粘贴与口头
40
+
41
+ | 来源 | 条件 | 处理 |
42
+ |------|------|------|
43
+ | `paste` | 用户粘贴全文(可含评论) | 拆出标题、正文、评论块;缺评论则 `comments: []` |
44
+ | `manual` | 仅口头 / 碎片描述 | 索取最小字段:标题、问题描述、期望行为;可选复现步骤 |
45
+
46
+ ## 规范化内部结构
47
+
48
+ 无论来源,统一为:
49
+
50
+ | 字段 | 说明 |
51
+ |------|------|
52
+ | `source` | `gh` \| `paste` \| `manual` |
53
+ | `kind` | `issue` \| `pr` \| `manual` |
54
+ | `number` | 编号,无则 `n/a` |
55
+ | `title` | 标题 |
56
+ | `body` | 正文(markdown 原文) |
57
+ | `url` | 远端 URL,无则空 |
58
+ | `author` | 作者登录名或「用户」 |
59
+ | `labels` | 标签字符串数组 |
60
+ | `state` | open/closed 等,未知则空 |
61
+ | `comments` | `{ author, created_at, body }[]` |
62
+ | `fetched_at` | ISO-8601 摄入时间 |
63
+
64
+ 后续步骤只消费此结构;写入 `source-issue.md` 时按 `<Path>{roots.workflows}/specdev/T-triage/artifact-templates.md</Path>` 展开。
65
+
66
+ ## 完成检查
67
+
68
+ - 标题非空
69
+ - 正文非空(`manual` 时问题描述 + 期望行为可拼成 body)
70
+ - `source` 与 `fetched_at` 已设
71
+ - 远程路径下 `number` 与 `url` 尽量齐全;失败回退已标注
@@ -0,0 +1,70 @@
1
+ # 路由规则
2
+
3
+ 在 `triage.md` 已具备类别、验证结果、信息缺口与行为契约草案后,按**首匹配**(从上到下)选定恰好一条主推荐。可附一条备选。推荐后**停止**,等用户确认再加载对应 work 入口。
4
+
5
+ ## 首匹配表
6
+
7
+ | # | 条件 | Recommended status | 主推荐 |
8
+ |---|------|--------------------|--------|
9
+ | 1 | 已实现,或用户确认拒绝 / wontfix | `wontfix` | `none` |
10
+ | 2 | 关键信息不足(无法写出可测 AC 或无法判断类别/复现) | `needs-info` | `none` |
11
+ | 3 | `bug`,可复现(confirmed),根因未知 | `ready-for-agent` 或 `needs-triage` | **诊断** |
12
+ | 4 | `bug`,AC 清晰,范围小,修复点/模块清楚 | `ready-for-agent` | **实现** |
13
+ | 5 | `enhancement`,设计未定或接口仍开放 | `needs-triage` 或 `ready-for-human` | **设计访谈** |
14
+ | 6 | `enhancement`,设计已定,足以写 PRD | `ready-for-agent` | **编写 Spec** |
15
+ | 7 | 工作超单会话、通往目标的路径仍在迷雾中 | `needs-triage` | **寻路** |
16
+ | 8 | 以上皆非(默认) | `needs-triage` | **设计访谈** |
17
+
18
+ 规则 1 的补充:用户确认拒绝 enhancement 时,可**提示**写入项目根 `.out-of-scope/<concept>.md`(格式见 `<Path>{roots.workflows}/specdev/common/triage/OUT-OF-SCOPE.md</Path>`);默认不自动写。已实现关闭不写 `.out-of-scope/`。
19
+
20
+ 规则 3 vs 4:「根因未知」= 知道坏在哪一类症状,但不知哪个模块/不变量失败;「修复点清楚」= 已能指出接口或模块级落点。
21
+
22
+ 规则 7 可选:仅当用户或理解结论明确「多会话 / 战争迷雾」时命中;否则落入默认规则 8。
23
+
24
+ ## 入口 Path
25
+
26
+ | 显示名 | Path |
27
+ |--------|------|
28
+ | 设计访谈 | `<Path>{roots.workflows}/specdev/G-grill-with-docs/G-grill-with-docs.md</Path>` |
29
+ | 编写 Spec | `<Path>{roots.workflows}/specdev/S-spec/S-spec.md</Path>` |
30
+ | 实现 | `<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>` |
31
+ | 诊断 | `<Path>{roots.workflows}/specdev/D-diagnose-bugs/D-diagnose-bugs.md</Path>` |
32
+ | 寻路 | `<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>` |
33
+ | none | 字面 `none`(向用户提问或结束分诊) |
34
+
35
+ ## 推荐话术
36
+
37
+ 向用户展示时使用固定骨架:
38
+
39
+ ```markdown
40
+ ## 分诊结论
41
+
42
+ - **Change:** `{change}`
43
+ - **类别:** bug | enhancement
44
+ - **推荐 status:** …
45
+ - **主推荐:** <显示名> → <Path>…
46
+ - **理由:** <对应上表条件的一句话>
47
+ - **备选:** <可选>
48
+
49
+ 产物:
50
+ - `<Path>{roots.state}/specdev/changes/{change}/source-issue.md</Path>`
51
+ - `<Path>{roots.state}/specdev/changes/{change}/triage.md</Path>`
52
+
53
+ 是否进入主推荐 work?确认前我不会启动下游或修改项目代码。
54
+ ```
55
+
56
+ `needs-info` 时:话术改为列出「信息缺口」中的具体问题,主推荐写 `none`,并说明补齐后可再次运行本 work 或直接指定下游。
57
+
58
+ `none` + `wontfix` 时:说明已实现位置或拒绝理由;询问是否需要记录到 `.out-of-scope/`(仅 enhancement 拒绝)。
59
+
60
+ ## 停止规则
61
+
62
+ - 本步只推荐与展示;用户确认「进入 X」后再加载对应 Path,并移交 change 名、`triage.md` 路径与行为契约要点
63
+ - 用户选择备选或否决时,先更新 `triage.md` 推荐字段,再结束或按新选择移交
64
+
65
+ ## 完成检查
66
+
67
+ - 从上到下只命中一条主推荐
68
+ - Path 使用上表别名格式(或 `none`)
69
+ - 用户已看到理由与确认问题
70
+ - 下游仍处于未启动状态
@@ -0,0 +1,102 @@
1
+ # 深度理解规则
2
+
3
+ 在写入分诊产物之前,对摄入的 issue 完成可检查的理解。行为契约对齐 `<Path>{roots.workflows}/specdev/common/triage/AGENT-BRIEF.md</Path>`:
4
+
5
+ - **持久优于精确**——描述接口、类型与行为契约;少绑易变文件路径与行号
6
+ - **行为而非过程**——写系统应做什么,不写如何实现
7
+ - **完整验收标准**——每条可独立验证
8
+ - **明确范围外**——写清本 change 不做什么
9
+
10
+ 范围外只读去重遵循 `<Path>{roots.workflows}/specdev/common/triage/OUT-OF-SCOPE.md</Path>`。
11
+
12
+ ## 检查清单(逐项完成)
13
+
14
+ ### 1. 问题一句话
15
+
16
+ 用一句用户可理解的话概括「出了什么问题 / 要什么能力」。
17
+
18
+ ### 2. 类别
19
+
20
+ 判定恰好一个:
21
+
22
+ - `bug`——现有行为不符合预期
23
+ - `enhancement`——新功能或对现有能力的改进
24
+
25
+ 依据不足时倾向 `enhancement` 并在信息缺口中写清「请确认是回归还是新需求」。
26
+
27
+ ### 3. 当前行为 vs 期望行为
28
+
29
+ - **当前**:代码库与 issue 共同描述的现状(bug 为故障表现;enhancement 为建立其上的基线)
30
+ - **期望**:完成后应发生什么;含已知边界与错误条件
31
+
32
+ 未知部分列入信息缺口,不编造。
33
+
34
+ ### 4. 代码库探查
35
+
36
+ - 先读 `<Path>{roots.state}/specdev/adr/</Path>` 与 `<Path>{roots.state}/specdev/context/</Path>`(若存在),使用既有术语与决策
37
+ - 按**领域概念**搜索(不仅是 issue 措辞)
38
+ - 记录相关模块、类型、函数签名或配置形态——行为级命名优先
39
+ - 报告查找范围,便于用户质疑遗漏
40
+
41
+ ### 5. 冗余(已实现)
42
+
43
+ 若请求行为已在代码库中存在:
44
+
45
+ - 指向存在位置(模块/接口名 + 简要证据)
46
+ - 倾向推荐 status `wontfix`、next work `none`
47
+ - 已实现路径只指向代码位置;`.out-of-scope/` 仅用于被拒绝的 enhancement
48
+
49
+ ### 6. 范围外匹配(只读)
50
+
51
+ 读取项目根 `.out-of-scope/*.md`(目录不存在则跳过):
52
+
53
+ - 按**概念相似**匹配(如「night theme」≈ `dark-mode`)
54
+ - 有匹配则呈现文件路径与拒绝理由,请用户选择:
55
+ - **确认**——仍拒绝 → next `none`;可提示用户自行追加 prior request(本 work **默认不自动写** `.out-of-scope/`)
56
+ - **重新考虑**——进入正常分诊
57
+ - **不相关**——继续正常分诊
58
+
59
+ ### 7. 信息缺口
60
+
61
+ 列出具体、可回答的问题。每条应能独立关闭一个决策或验证点。
62
+
63
+ - 好:「在 Node 20 + macOS 上执行 `speculo init` 后的完整终端输出是什么?」
64
+ - 坏:「请提供更多信息。」
65
+
66
+ 无缺口则写「无」。
67
+
68
+ ### 8. Bug 验证(轻量)
69
+
70
+ 仅 `bug` 类别:
71
+
72
+ | 结果 | 含义 |
73
+ |------|------|
74
+ | `confirmed` | 按报告步骤复现成功,或代码路径明确支撑该故障 |
75
+ | `not-reproduced` | 按步骤未能复现;记录尝试环境与命令 |
76
+ | `needs-info` | 步骤不足,无法尝试复现 |
77
+ | `n/a` | 非 bug |
78
+
79
+ 完整反馈回路、插桩与假设排名由 `<Path>{roots.workflows}/specdev/D-diagnose-bugs/D-diagnose-bugs.md</Path>` 负责。本步只需确认「是否像真 bug」及可复现性档位。
80
+
81
+ ## 输出给后续步骤的结论包
82
+
83
+ 步骤 4 写入 `triage.md` 前,会话中应具备:
84
+
85
+ | 字段 | 来源 |
86
+ |------|------|
87
+ | 类别 | 清单 §2 |
88
+ | 问题摘要 | 清单 §1 |
89
+ | 当前 / 期望行为 | 清单 §3 |
90
+ | 关键接口(草案) | 清单 §4 |
91
+ | 验收标准(草案) | 自期望行为拆出;不足则进缺口 |
92
+ | 范围外 | 清单 §6 + 显式不做项 |
93
+ | 验证结果 | 清单 §8 |
94
+ | 冗余 / 范围外结论 | 清单 §5–6 |
95
+ | 信息缺口 | 清单 §7 |
96
+ | 完备性 | 足以写契约 → 可路由下游;否则 `needs-info` |
97
+
98
+ ## 完成检查
99
+
100
+ - 八项均有结论或明确「不适用」
101
+ - 用户已看到已实现 / 范围外匹配(若有)并给出方向(或已 AFK 默认继续)
102
+ - 信息缺口每条可操作
@@ -33,7 +33,7 @@ Wayfinder 默认进行**规划**:每个 ticket 解决一个决策,当地图
33
33
 
34
34
  **前沿查询:** 前沿上的 tickets 是指:checkbox 未勾选(开放)、其"被阻塞于"中列出的所有 tickets 均已勾选(无阻塞)、且尚未被领取的 tickets。Agent 通过阅读地图文件本身即可识别前沿。
35
35
 
36
- **领取机制:** 当一个 agent 会话开始处理某个 ticket 时,它应在 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中记录当前处理的 ticket 名称,以便并发会话跳过它。处理完成后从 `active` 中移除。`active` 中存在记录即为领取标记。
36
+ **领取机制:** 当一个 agent 会话开始处理某个 ticket 时,它应在 `<Path>{roots.state}/specdev/status.json</Path>` 的当前 change 的 `active` 条目中,将 ticket 名称追加到 `claimed_tickets` 数组,以便并发会话跳过它。处理完成后从 `claimed_tickets` 中移除。`claimed_tickets` 中存在记录即为领取标记。
37
37
 
38
38
  ### 地图正文
39
39
 
@@ -93,7 +93,7 @@ Wayfinder 默认进行**规划**:每个 ticket 解决一个决策,当地图
93
93
 
94
94
  每个 ticket 携带一个类型标签 —— 以下之一:`research`、`prototype`、`grilling`、`task`(参见下方 [Ticket 类型](#ticket-类型))。
95
95
 
96
- **领取机制:** 一个会话通过将其名称写入 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组来**领取**一个 ticket,在开始任何工作**之前**领取,以便并发会话跳过它。该记录*就是*领取标记:一个开放、未被领取的 ticket 是未被领取的。
96
+ **领取机制:** 一个会话通过将其名称追加到 `<Path>{roots.state}/specdev/status.json</Path>` 的当前 change 的 `active` 条目中的 `claimed_tickets` 数组来**领取**一个 ticket,在开始任何工作**之前**领取,以便并发会话跳过它。该记录*就是*领取标记:一个开放、未被领取的 ticket 是未被领取的。
97
97
 
98
98
  **阻塞关系:** 使用 ticket 标题在"被阻塞于"字段中声明依赖。这很关键,因为它使前沿在地图文件中*可视化*呈现 —— 人类无需额外工具就能看到哪些可以开始。当一个 ticket 的所有阻塞 tickets 都已勾选(已解决)时,该 ticket 是**未被阻塞的**;**前沿**是开放(未勾选)、未被阻塞、未被领取的 tickets —— 即已知的边界。
99
99
 
@@ -163,7 +163,7 @@ Wayfinder 默认进行**规划**:每个 ticket 解决一个决策,当地图
163
163
 
164
164
  **完成标准**:地图的低分辨率视图已加载,当前状态已理解。
165
165
 
166
- 2. **选择 ticket。** 如果用户指定了一个,使用它。否则按顺序选择第一个前沿 ticket(开放、未被阻塞、未被领取)。**领取它**:在任何工作之前将 ticket 名称写入 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组。
166
+ 2. **选择 ticket。** 如果用户指定了一个,使用它。否则按顺序选择第一个前沿 ticket(开放、未被阻塞、未被领取)。**领取它**:在任何工作之前将 ticket 名称追加到 `<Path>{roots.state}/specdev/status.json</Path>` 的当前 change 的 `active` 条目中的 `claimed_tickets` 数组。
167
167
 
168
168
  **完成标准**:一个前沿 ticket 已被选中并领取。
169
169
 
@@ -171,7 +171,7 @@ Wayfinder 默认进行**规划**:每个 ticket 解决一个决策,当地图
171
171
 
172
172
  **完成标准**:ticket 的问题已解决,答案已记录。
173
173
 
174
- 4. **记录解决方案:** 在 ticket 的"答案"小节中填写答案,将 checkbox 从 `- [ ]` 改为 `- [x]`,在地图的 Decisions-so-far 中**追加一条上下文指针**:`- [ticket 标题] —— 答案的一句话概括`。从 `<Path>{roots.state}/specdev/status.json</Path>` 的 `active` 数组中移除该 ticket。
174
+ 4. **记录解决方案:** 在 ticket 的"答案"小节中填写答案,将 checkbox 从 `- [ ]` 改为 `- [x]`,在地图的 Decisions-so-far 中**追加一条上下文指针**:`- [ticket 标题] —— 答案的一句话概括`。从 `<Path>{roots.state}/specdev/status.json</Path>` 的当前 change 的 `active` 条目中的 `claimed_tickets` 数组中移除该 ticket。
175
175
 
176
176
  **完成标准**:ticket checkbox 已勾选,Decisions-so-far 已更新,active 数组已清理。
177
177
 
@@ -206,4 +206,4 @@ Wayfinder 默认进行**规划**:每个 ticket 解决一个决策,当地图
206
206
  | `<Path>{roots.state}/specdev/changes/{change}/map.md</Path>` | 地图持久化文件 |
207
207
 
208
208
  状态追踪:
209
- - `<Path>{roots.state}/specdev/status.json</Path>` —— `active` 数组记录当前领取的 ticket
209
+ - `<Path>{roots.state}/specdev/status.json</Path>` —— `active` 条目中的 `claimed_tickets` 数组记录当前领取的 ticket
@@ -1,5 +1,7 @@
1
1
  {
2
- "schema_version": 1,
2
+ "schema_version": 2,
3
3
  "workflow": "specdev",
4
- "active": []
4
+ "active": [],
5
+ "work_history": [],
6
+ "completed": []
5
7
  }