# Prompt — Tier 2-D · `scripts/new-backlog.mjs` ID Generator

> **触发方式**：用户 → executor：`请按 docs/internal/_prompts/tier-2d-backlog-id-generator.prompt.md 执行`
> **角色**：executor
> **plan owner**：Claude Code（已写完本 prompt）
> **关联**：v0.4.0 Sprint D（pickup `next-session-pickup-2026-05-15-v0-4-0.md` §5）；前置 Sprint A `529bb366` / Sprint B `0db2bfd0` / Sprint C `5546ae0b` 已 ship

---

## 1. 背景

memory `feedback_backlog-id-collision` 规则：用户新加 backlog entry 前必须 grep **全部 SoT 文件**已用 ID（Active + 已完成 + tracker timeline 引用），挑最大数字 +1。凭印象选号会撞 ID。

**手工 grep 工作量大易漏**（3 个 SoT 文件 + 同 prefix 多变种如 `BRIDGE` vs `BRIDGE-MOCKUP` vs `BRIDGE-DESIGN`）。Tier 2-D 把这个 grep+max+stub 流程做成 CLI 工具。

**估时**：~2h（pickup tracker）。

---

## 2. Scope

**新建 1 个文件 + 改 1 个文件**：

| # | 文件 | 改动 |
|---|---|---|
| 1 | `scripts/new-backlog.mjs` | **新建** Node ESM script |
| 2 | `package.json` | 加 `"new-backlog": "node scripts/new-backlog.mjs"` 到 scripts 段 |

**禁止**：
- 不写到 backlog.md（script print-only，user 手动 copy-paste）
- 不改 backlog.md / STATUS.md / tracker.md
- 不加 test（script 是 dev 工具，~50 行简单逻辑，纯手测）
- 不引入新 dependency（仅用 Node 内置 `node:fs` + `node:path` + `node:url`）

---

## 3. 精确实施 spec

### 3.1 `scripts/new-backlog.mjs`（新建）

**CLI 用法**：

```bash
pnpm new-backlog <prefix>
# 例：
pnpm new-backlog CANONICAL
pnpm new-backlog BRIDGE-MOCKUP
pnpm new-backlog INFRA
```

**核心逻辑**：

```js
#!/usr/bin/env node
// scripts/new-backlog.mjs
// Usage: pnpm new-backlog <prefix>
// Prints next available backlog ID + entry template stub.

import { readFileSync, existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'

const __dirname = dirname(fileURLToPath(import.meta.url))
const REPO_ROOT = resolve(__dirname, '..')

// SoT files to scan for used IDs.
// Order matters only for diagnostic clarity — all are scanned.
const SOT_FILES = [
  'docs/internal/backlog.md',
  'docs/STATUS.md',
  'docs/internal/retrospection/design-spec-canonical-alignment-tracker.md',
]

// Allowed prefixes per backlog.md "维护规则" header.
const VALID_PREFIXES = [
  'CANONICAL',
  'BRIDGE',
  'BRIDGE-MOCKUP',
  'BRIDGE-DESIGN',
  'EXTRACT',
  'META',
  'INFRA',
]

const [,, prefix] = process.argv

if (!prefix) {
  console.error('Usage: pnpm new-backlog <prefix>')
  console.error('Valid prefixes:', VALID_PREFIXES.join(', '))
  process.exit(1)
}

if (!VALID_PREFIXES.includes(prefix)) {
  console.error(`Invalid prefix: ${prefix}`)
  console.error('Valid prefixes:', VALID_PREFIXES.join(', '))
  process.exit(1)
}

// Collect used IDs across SoT files for this exact prefix.
// Regex anchors avoid prefix bleed (e.g., "BRIDGE" must not match "BRIDGE-MOCKUP-001").
// `\b` word boundary + literal prefix + `-` + digits + `\b` ensures specificity.
const usedIds = new Set()
const idPattern = new RegExp(`\\b${prefix}-(\\d+)\\b`, 'g')

const missingFiles = []
for (const file of SOT_FILES) {
  const abs = resolve(REPO_ROOT, file)
  if (!existsSync(abs)) {
    missingFiles.push(file)
    continue
  }
  const content = readFileSync(abs, 'utf8')
  let match
  while ((match = idPattern.exec(content)) !== null) {
    usedIds.add(parseInt(match[1], 10))
  }
}

if (missingFiles.length > 0) {
  console.error(`Warning: missing SoT file(s): ${missingFiles.join(', ')}`)
}

const sortedUsed = [...usedIds].sort((a, b) => a - b)
const maxId = sortedUsed.length > 0 ? sortedUsed[sortedUsed.length - 1] : 0
const nextId = maxId + 1
const newId = `${prefix}-${String(nextId).padStart(3, '0')}`
const today = new Date().toISOString().slice(0, 10)

const stub = `### ${newId}: <短标题>

- **优先级**：<High | Medium | Low>
- **发现时间**：${today}
- **触发查看条件**：<什么场景下会想起这条 — 必填，避免成为"永远的未来 TODO">
- **阻塞关系**：<被谁阻塞 / 阻塞了谁；无则写"独立">
- **现状**：
  - <bullet 1>
  - <bullet 2>
- **建议路径**：
  - <方案 / 估时 / 顺路项>
`

console.log(`Next available ID: ${newId}`)
console.log(`Prefix: ${prefix}`)
console.log(`Used IDs in SoT (${sortedUsed.length}): ${sortedUsed.join(', ') || '(none)'}`)
console.log('---')
console.log('Copy the following stub into docs/internal/backlog.md "## Active" section:')
console.log('')
console.log(stub)
```

**实现要点**：
- Pure ESM (`import` syntax, project uses `"type": "module"`)
- 仅用 Node 内置模块 (`node:fs`, `node:path`, `node:url`)
- Prefix 规则严格按 backlog.md 顶部"维护规则"段（CANONICAL / BRIDGE / BRIDGE-MOCKUP / BRIDGE-DESIGN / EXTRACT / META / INFRA）
- Regex `\b${prefix}-(\d+)\b` 用 word boundary 避免 `BRIDGE` 误匹配 `BRIDGE-MOCKUP-001`
- 缺 SoT 文件时 warning 不 fail（防止文件路径变迁导致 hard fail）
- ID 用 3 位 zero-padding（跟现有 `CANONICAL-011` / `BRIDGE-005` 范式一致）
- stub 必含"触发查看条件"+"阻塞关系"（backlog.md 顶部规则强制）

### 3.2 `package.json` scripts 段

在 `"scripts": { ... }` 内加一条（位置按字母序穿插或放在 `prepublishOnly` 附近，按现有顺序惯例）：

```diff
   "scripts": {
+    "new-backlog": "node scripts/new-backlog.mjs",
     ...
   }
```

具体位置由 executor 看现有 scripts 段排序习惯决定（字母序 / 功能分组 / 任何已存在的约定）。

---

## 4. Verify

依次手测下面 5 个场景，**每条都要 exit 0 + 输出符合预期**：

```bash
# Case 1: 已存在的 prefix，应输出 next ID
pnpm new-backlog CANONICAL
# Expected: Next available ID: CANONICAL-012 (or higher if more exist)
# Used IDs 列表含 001-011 等已知 ID

# Case 2: 有 sub-prefix 的 prefix 命名隔离
pnpm new-backlog BRIDGE
# Expected: Next ID = BRIDGE-006 (max existing is 005)
# Used IDs **不能**含 BRIDGE-MOCKUP-001 等（regex 隔离正确）

pnpm new-backlog BRIDGE-MOCKUP
# Expected: Next ID = BRIDGE-MOCKUP-006 (max 005 已有)
# Used IDs 含 BRIDGE-MOCKUP 系列 001-005

# Case 3: 从未用过的 prefix
pnpm new-backlog META
# Expected: Next ID = META-001 (或检查实际有无 META-XXX 占用)
# Used IDs: (none) 或者 list

# Case 4: 无效 prefix
pnpm new-backlog FAKE
# Expected: exit 1 + error message "Invalid prefix: FAKE" + Valid prefixes list

# Case 5: 缺参数
pnpm new-backlog
# Expected: exit 1 + Usage message + Valid prefixes list
```

**额外 verify**：
```bash
# 整 release pipeline 不能 regress
pnpm run prepublishOnly
# Expected: exit 0, 10 strict gates green
```

任一不符 → STOP，列出失败 stdout + 你的判断，**不要 commit**。

---

## 5. 报告格式（STOP 后必交）

```markdown
## Tier 2-D 完成报告

### Diff 状态
`git status --short`（**未 commit**）：
[stat 输出]

### 改动文件验收清单
- [ ] D1 scripts/new-backlog.mjs（新建，~80 行）
- [ ] D2 package.json（新增 1 行 npm script）

### 手测结果（5 个 case）
- Case 1 (CANONICAL): Next ID = ?
- Case 2a (BRIDGE): Next ID = ?，sub-prefix 隔离 ?
- Case 2b (BRIDGE-MOCKUP): Next ID = ?
- Case 3 (META): Next ID = ?
- Case 4 (FAKE invalid): exit code = ?
- Case 5 (no arg): exit code = ?

### Verify 结果
- prepublishOnly: [10 strict gates 全通过 / fail]

### 边界 case / plan owner 决策项
[列任何犹豫点：
- package.json scripts 段插入位置
- VALID_PREFIXES 列表是否漏了某个（建议读 backlog.md 顶部维护规则段最新版）
- ID 数字位数（3 位 vs 现有 ID 实际位数 — 如 CANONICAL-011 是 3 位但 BRIDGE-005 也是 3 位）
- ...]

### 未做（按 prompt §6）
- ❌ commit / push / stash
- ❌ 写入 backlog.md（script 只 print，user 手动 paste）
- ❌ 加 test 文件
```

---

## 6. 严格不做的事（executor 边界）

- ❌ **commit — plan owner 审完后用户决定**（同 phase-6.4 / 6.6 范式；prompt 内**不含 commit message 模板**是有意为之）
- ❌ push / stash / amend 任何 git 操作
- ❌ 改 backlog.md 内容（script 是 print-only generator，user 手动 paste 入 backlog.md）
- ❌ 改 STATUS.md / tracker.md / divergences.md / decision JSON
- ❌ 加 vitest test 文件（script 是 dev 工具，纯手测就够）
- ❌ 引入新 npm dependency（仅 Node 内置模块）
- ❌ 用 commonjs `require` 语法（项目 `"type": "module"`，必须 ESM `import`）
- ❌ 自创新 prefix（VALID_PREFIXES 严格按 backlog.md 顶部"维护规则"段；不要加 SCRIPT / TIER / PHASE 之类的）
- ❌ Script 写入文件（生成 stub 是 console.log 输出，不是文件写入）
- ❌ 编造 "项目 contract / convention / rule" 当 adapt 理由（参考 memory `feedback_review-result-vs-rationale`）

如发现 spec 跟现状不一致（如 backlog.md 维护规则改了某 prefix / package.json scripts 已有 `new-backlog` 同名 key）→ **STOP** 报告，不自决修正。

---

## 7. 完成后 STOP

把报告交给 plan owner，由 plan owner 复审 + 用户决定：

- 是否 commit Tier 2-D
- 是否进 v0.4.0 release wrap-up（flip 3 decisions resolved + changeset + version bump + tag push + Packages 页 verify + STATUS/tracker sync + retrospect）

Tier 2-D 收尾后**重大解锁**：v0.4.0 主线 100% done，可直接进 release wrap-up。

**绝对不在 executor 阶段做的事**：commit / push / tag / changeset / publish / STATUS sync / tracker sync。
