cc-devflow 4.5.15 → 4.5.16

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 (52) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +5 -0
  2. package/.claude/skills/cc-act/SKILL.md +1 -1
  3. package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +3 -0
  4. package/.claude/skills/cc-act/scripts/render-pr-brief.sh +113 -33
  5. package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
  6. package/.claude/skills/cc-dev/PLAYBOOK.md +6 -3
  7. package/.claude/skills/cc-dev/SKILL.md +10 -7
  8. package/.claude/skills/cc-dev/scripts/ensure-work-branch.sh +117 -0
  9. package/.claude/skills/cc-dev/scripts/prepare-change-worktree.sh +135 -0
  10. package/.claude/skills/cc-investigate/CHANGELOG.md +15 -0
  11. package/.claude/skills/cc-investigate/SKILL.md +85 -8
  12. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +56 -0
  13. package/.claude/skills/cc-investigate/references/investigation-contract.md +1 -0
  14. package/.claude/skills/cc-plan/CHANGELOG.md +15 -0
  15. package/.claude/skills/cc-plan/SKILL.md +70 -6
  16. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +41 -0
  17. package/.claude/skills/cc-plan/references/planning-contract.md +1 -0
  18. package/.claude/skills/cc-pr-review/CHANGELOG.md +9 -0
  19. package/.claude/skills/cc-pr-review/PLAYBOOK.md +3 -0
  20. package/.claude/skills/cc-pr-review/SKILL.md +30 -1
  21. package/.claude/skills/cc-review/CHANGELOG.md +10 -0
  22. package/.claude/skills/cc-review/SKILL.md +53 -9
  23. package/.claude/skills/cc-review/references/implementation-review-branch.md +1 -0
  24. package/.claude/skills/cc-review/references/plan-review-branch.md +1 -0
  25. package/.claude/skills/cc-review/references/review-methods.md +30 -0
  26. package/.claude/skills/cc-roadmap/CHANGELOG.md +6 -0
  27. package/.claude/skills/cc-roadmap/SKILL.md +1 -1
  28. package/.claude/skills/cc-roadmap/scripts/lib/roadmap-tracking/markdown.js +274 -69
  29. package/.claude/skills/cc-roadmap/scripts/lib/roadmap-tracking/schema.js +69 -15
  30. package/CHANGELOG.md +10 -4
  31. package/README.md +1 -1
  32. package/README.zh-CN.md +1 -1
  33. package/docs/examples/example-bindings.json +8 -8
  34. package/docs/examples/full-design-blocked/BACKLOG.md +12 -1
  35. package/docs/examples/full-design-blocked/README.md +1 -1
  36. package/docs/examples/full-design-blocked/ROADMAP.md +2 -2
  37. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/task.md +23 -1
  38. package/docs/examples/full-design-blocked/roadmap.json +7 -2
  39. package/docs/examples/local-handoff/BACKLOG.md +12 -1
  40. package/docs/examples/local-handoff/README.md +1 -1
  41. package/docs/examples/local-handoff/ROADMAP.md +2 -2
  42. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/task.md +23 -1
  43. package/docs/examples/local-handoff/roadmap.json +7 -2
  44. package/docs/examples/pdca-loop/BACKLOG.md +12 -1
  45. package/docs/examples/pdca-loop/README.md +1 -1
  46. package/docs/examples/pdca-loop/ROADMAP.md +2 -2
  47. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/task.md +23 -1
  48. package/docs/examples/pdca-loop/roadmap.json +7 -2
  49. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -1
  50. package/lib/skill-runtime/__tests__/config.test.js +7 -2
  51. package/lib/skill-runtime/config.js +38 -6
  52. package/package.json +1 -1
@@ -19,6 +19,36 @@ Pick every method needed by the current risk. This is a routing map, not a findi
19
19
 
20
20
  Selected methods stay in scratch reasoning and final response/task updates. Do not write process files.
21
21
 
22
+ ## ASCII Branch Chains
23
+
24
+ For any plan, investigation, PR, broad implementation, or code-smell finding, include a compact ASCII tree in the durable task update or review output. Keep `|--`, `` `-- ``, `|`, spaces, and punctuation ASCII; write labels, explanations, findings, and evidence summaries in the configured output language. Resolve language from `task.md` `Output language`, PR/task/handoff language fields, then the current conversation language.
25
+
26
+ Label table:
27
+
28
+ | Semantic slot | en | zh-CN |
29
+ | --- | --- | --- |
30
+ | reviewChain | Review Chain | 审查链 |
31
+ | findingMarker | FINDING | 问题 |
32
+ | source | Source | 来源 |
33
+ | faultNode | Fault node | 错误节点 |
34
+ | whyWrong | why wrong | 错误原因 |
35
+ | firstAffectedSeam | first affected seam | 首个受影响边界 |
36
+ | downstreamImpact | Downstream impact | 下游影响 |
37
+ | fixRoute | Fix route | 修复路线 |
38
+
39
+ ```text
40
+ <reviewChain>
41
+ <findingMarker>: <severity + short name>
42
+ |-- <source>: <task / diff / PR / log / prompt / provider contract>
43
+ |-- <faultNode>: <file / section / behavior>
44
+ | |-- <whyWrong>: <violated contract or smell>
45
+ | `-- <firstAffectedSeam>: <public seam / caller / artifact>
46
+ |-- <downstreamImpact>: <user / operator / release / maintenance>
47
+ `-- <fixRoute>: <cc-plan / cc-investigate / cc-do / cc-check / cc-act / stop>
48
+ ```
49
+
50
+ Trace upstream to the first supported source and downstream to the affected public seam. If prompt text, agent instructions, model/provider parameters, or generated artifacts are part of the chain, name the exact prompt/provider contract or write `unknown -> Evidence Request`.
51
+
22
52
  ## Review Nodes
23
53
 
24
54
  Before findings, mentally create ordered review nodes:
@@ -1,5 +1,11 @@
1
1
  # Roadmap Skill Changelog
2
2
 
3
+ ## v5.3.1 - 2026-05-17
4
+
5
+ - make generated `ROADMAP.md` and deprecated `BACKLOG.md` projections render headings, labels, empty placeholders, and readiness booleans in Chinese when `outputPolicy.documentLanguage` is `zh-CN`
6
+ - render `Output language` metadata in generated roadmap and backlog projections and keep v3 backlog metadata aligned with roadmap state metadata
7
+ - preserve parsing for both English and Chinese projection labels so rerenders do not lose roadmap state after localization
8
+
3
9
  ## v5.3.0 - 2026-05-11
4
10
 
5
11
  - add the Roadmap Funnel Protocol with fixed F0-F9 rounds for direction mode, demand reality, status quo, specific human/sponsor, wedge/lake boundary, observation signal, future fit, premise challenge, alternatives, and route approval
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cc-roadmap
3
- version: 5.3.0
3
+ version: 5.3.1
4
4
  description: "Use when defining, resetting, or narrowing project direction, stage order, or backlog priority before a concrete requirement enters the PDCA loop."
5
5
  triggers:
6
6
  - "帮我定路线图"
@@ -9,7 +9,6 @@ const {
9
9
  ROADMAP_COLUMNS,
10
10
  ROADMAP_HEADER_TO_KEY,
11
11
  formatBacklogValue,
12
- formatBoolean,
13
12
  formatCell,
14
13
  formatInlineCode,
15
14
  formatList,
@@ -21,6 +20,169 @@ const {
21
20
  parseList
22
21
  } = require('./schema');
23
22
 
23
+ const DOCUMENT_LABELS = {
24
+ en: {
25
+ roadmapTitle: 'ROADMAP',
26
+ backlogTitle: 'BACKLOG',
27
+ implementationTracking: 'Implementation Tracking',
28
+ technicalArchitecture: 'Technical Architecture',
29
+ roadmapStateSource: 'Roadmap state source',
30
+ trackingSource: 'Tracking source',
31
+ outputLanguage: 'Output language',
32
+ deprecatedProjection: '> Deprecated projection. Edit `roadmap.json` instead.',
33
+ backlogMeta: 'Backlog Meta',
34
+ roadmapVersion: 'Roadmap version',
35
+ skillVersion: 'Skill version',
36
+ lastSynced: 'Last synced',
37
+ currentFocusStage: 'Current focus stage',
38
+ queue: 'Queue',
39
+ dependencyHandoff: 'Dependency Handoff',
40
+ serialSpine: 'Serial spine',
41
+ parallelReadyNextWave: 'Parallel-ready next wave',
42
+ notesOnBlockers: 'Notes on blockers',
43
+ projectDirectionHandoff: 'Project Direction Handoff',
44
+ projectDirectionMode: 'Project direction mode',
45
+ projectDirectionRationale: 'Direction mode rationale',
46
+ directionQuestionsSelected: 'Direction-specific questions selected',
47
+ directionQuestionsSkipped: 'Direction-specific questions skipped',
48
+ directionGuardrailsApplied: 'Direction guardrails applied',
49
+ planningPosture: 'Planning posture',
50
+ evidenceMaturity: 'Evidence maturity',
51
+ readyForReqPlan: 'Ready For Req-Plan',
52
+ parked: 'Parked',
53
+ yes: 'Yes',
54
+ no: 'No',
55
+ readyEmpty:
56
+ '- RM-001:\n - Primary Capability:\n - Secondary Capabilities:\n - Why now:\n - Success signal:\n - Entry constraints:\n - Capability gap:\n - Expected spec delta:\n - Open risks:\n - First planning question:\n - Required context to load:\n - Depends On:\n - Parallel With:\n - Why this is ready now:',
57
+ parkedEmpty: '- RM-XXX:\n - Reason parked:\n - Trigger to reopen:\n - Missing evidence:',
58
+ readyFields: {
59
+ primaryCapability: 'Primary Capability',
60
+ secondaryCapabilities: 'Secondary Capabilities',
61
+ whyNow: 'Why now',
62
+ successSignal: 'Success signal',
63
+ entryConstraints: 'Entry constraints',
64
+ capabilityGap: 'Capability gap',
65
+ expectedSpecDelta: 'Expected spec delta',
66
+ openRisks: 'Open risks',
67
+ firstPlanningQuestion: 'First planning question',
68
+ requiredContextToLoad: 'Required context to load',
69
+ dependsOn: 'Depends On',
70
+ parallelWith: 'Parallel With',
71
+ whyReadyNow: 'Why this is ready now'
72
+ },
73
+ parkedFields: {
74
+ parkedReason: 'Reason parked',
75
+ triggerToReopen: 'Trigger to reopen',
76
+ missingEvidence: 'Missing evidence'
77
+ },
78
+ roadmapColumns: ROADMAP_COLUMNS.map(([label, key]) => [label, key]),
79
+ backlogColumns: BACKLOG_QUEUE_COLUMNS.map(([label, key]) => [label, key])
80
+ },
81
+ 'zh-CN': {
82
+ roadmapTitle: '路线图',
83
+ backlogTitle: '待办队列',
84
+ implementationTracking: '执行跟踪',
85
+ technicalArchitecture: '技术架构',
86
+ roadmapStateSource: '路线图状态源',
87
+ trackingSource: '跟踪源',
88
+ outputLanguage: 'Output language',
89
+ deprecatedProjection: '> 已废弃投影。请编辑 `roadmap.json`。',
90
+ backlogMeta: '待办元信息',
91
+ roadmapVersion: '路线图版本',
92
+ skillVersion: 'Skill 版本',
93
+ lastSynced: '最近同步',
94
+ currentFocusStage: '当前聚焦阶段',
95
+ queue: '队列',
96
+ dependencyHandoff: '依赖交接',
97
+ serialSpine: '串行主线',
98
+ parallelReadyNextWave: '下一波可并行事项',
99
+ notesOnBlockers: '阻塞说明',
100
+ projectDirectionHandoff: '项目方向交接',
101
+ projectDirectionMode: '项目方向模式',
102
+ projectDirectionRationale: '方向模式理由',
103
+ directionQuestionsSelected: '已选择的方向问题',
104
+ directionQuestionsSkipped: '已跳过的方向问题',
105
+ directionGuardrailsApplied: '已应用的方向护栏',
106
+ planningPosture: '规划姿态',
107
+ evidenceMaturity: '证据成熟度',
108
+ readyForReqPlan: '可进入 Req-Plan',
109
+ parked: '暂存',
110
+ yes: '是',
111
+ no: '否',
112
+ readyEmpty:
113
+ '- RM-001:\n - 主能力:\n - 次能力:\n - 为什么现在做:\n - 成功信号:\n - 进入约束:\n - 能力缺口:\n - 预期规格变化:\n - 未决风险:\n - 首个规划问题:\n - 必须加载的上下文:\n - 依赖:\n - 可并行:\n - 为什么现在已就绪:',
114
+ parkedEmpty: '- RM-XXX:\n - 暂存原因:\n - 重新打开触发条件:\n - 缺失证据:',
115
+ readyFields: {
116
+ primaryCapability: '主能力',
117
+ secondaryCapabilities: '次能力',
118
+ whyNow: '为什么现在做',
119
+ successSignal: '成功信号',
120
+ entryConstraints: '进入约束',
121
+ capabilityGap: '能力缺口',
122
+ expectedSpecDelta: '预期规格变化',
123
+ openRisks: '未决风险',
124
+ firstPlanningQuestion: '首个规划问题',
125
+ requiredContextToLoad: '必须加载的上下文',
126
+ dependsOn: '依赖',
127
+ parallelWith: '可并行',
128
+ whyReadyNow: '为什么现在已就绪'
129
+ },
130
+ parkedFields: {
131
+ parkedReason: '暂存原因',
132
+ triggerToReopen: '重新打开触发条件',
133
+ missingEvidence: '缺失证据'
134
+ },
135
+ roadmapColumns: [
136
+ ['RM-ID', 'rmId'],
137
+ ['事项', 'item'],
138
+ ['阶段', 'stage'],
139
+ ['优先级', 'priority'],
140
+ ['主能力', 'primaryCapability'],
141
+ ['次能力', 'secondaryCapabilities'],
142
+ ['预期规格变化', 'expectedSpecDelta'],
143
+ ['依赖', 'dependsOn'],
144
+ ['状态', 'status'],
145
+ ['REQ', 'req'],
146
+ ['进度', 'progress']
147
+ ],
148
+ backlogColumns: [
149
+ ['RM-ID', 'rmId'],
150
+ ['标题', 'item'],
151
+ ['来源阶段', 'stage'],
152
+ ['优先级', 'priority'],
153
+ ['主能力', 'primaryCapability'],
154
+ ['次能力', 'secondaryCapabilities'],
155
+ ['能力缺口', 'capabilityGap'],
156
+ ['预期规格变化', 'expectedSpecDelta'],
157
+ ['证据', 'evidence'],
158
+ ['依赖', 'dependsOn'],
159
+ ['可并行', 'parallelWith'],
160
+ ['未知项', 'unknowns'],
161
+ ['下一决策', 'nextDecision'],
162
+ ['就绪', 'ready']
163
+ ]
164
+ }
165
+ };
166
+
167
+ function labelsFor(tracking) {
168
+ return DOCUMENT_LABELS[tracking?.outputPolicy?.documentLanguage] || DOCUMENT_LABELS.en;
169
+ }
170
+
171
+ function outputLanguageFor(tracking) {
172
+ return tracking?.outputPolicy?.documentLanguage || DEFAULT_TRACKING.outputPolicy?.documentLanguage || 'en';
173
+ }
174
+
175
+ function extractAnySection(markdown, headings) {
176
+ for (const heading of headings) {
177
+ const section = extractSection(markdown, heading);
178
+ if (section) {
179
+ return section;
180
+ }
181
+ }
182
+
183
+ return null;
184
+ }
185
+
24
186
  function splitRow(line) {
25
187
  return line
26
188
  .trim()
@@ -98,7 +260,10 @@ function parseTable(sectionBody, headerMap, buildRow) {
98
260
  }
99
261
 
100
262
  function trackingFromRoadmap(markdown) {
101
- const section = extractSection(markdown, 'Implementation Tracking');
263
+ const section = extractAnySection(markdown, [
264
+ 'Implementation Tracking',
265
+ DOCUMENT_LABELS['zh-CN'].implementationTracking
266
+ ]);
102
267
  if (!section) {
103
268
  return [];
104
269
  }
@@ -135,6 +300,7 @@ function parseBacklogMeta(sectionBody) {
135
300
  currentFocusStage: ''
136
301
  };
137
302
  let lastSyncedAt = '';
303
+ let documentLanguage = '';
138
304
 
139
305
  for (const line of lines) {
140
306
  const match = /^-\s+([^:]+):\s*(.*)$/.exec(line);
@@ -145,18 +311,20 @@ function parseBacklogMeta(sectionBody) {
145
311
  const label = normalizeHeader(match[1]);
146
312
  const value = normalizeCell(match[2].replace(/`/g, ''));
147
313
 
148
- if (label === 'roadmap version') {
314
+ if (label === 'roadmap version' || label === '路线图版本') {
149
315
  meta.roadmapVersion = value;
150
- } else if (label === 'skill version') {
316
+ } else if (label === 'skill version' || label === 'skill 版本') {
151
317
  meta.skillVersion = value;
152
- } else if (label === 'last synced') {
318
+ } else if (label === 'last synced' || label === '最近同步') {
153
319
  lastSyncedAt = value;
154
- } else if (label === 'current focus stage') {
320
+ } else if (label === 'current focus stage' || label === '当前聚焦阶段') {
155
321
  meta.currentFocusStage = value;
322
+ } else if (label === 'output language') {
323
+ documentLanguage = value;
156
324
  }
157
325
  }
158
326
 
159
- return { meta, lastSyncedAt };
327
+ return { meta, lastSyncedAt, documentLanguage };
160
328
  }
161
329
 
162
330
  function parseBacklogQueue(sectionBody) {
@@ -279,11 +447,11 @@ function parseDependencyHandoff(sectionBody) {
279
447
  const label = normalizeHeader(match[1]);
280
448
  const value = normalizeCell(match[2].replace(/`/g, ''));
281
449
 
282
- if (label === 'serial spine') {
450
+ if (label === 'serial spine' || label === '串行主线') {
283
451
  next.serialSpine = value;
284
- } else if (label === 'parallel ready next wave') {
452
+ } else if (label === 'parallel ready next wave' || label === '下一波可并行事项') {
285
453
  next.parallelReadyNextWave = value;
286
- } else if (label === 'notes on blockers') {
454
+ } else if (label === 'notes on blockers' || label === '阻塞说明') {
287
455
  next.notesOnBlockers = value;
288
456
  }
289
457
  }
@@ -327,26 +495,35 @@ function mergeItemMaps(baseItems, incomingItems) {
327
495
  function trackingFromBacklog(markdown, baseTracking) {
328
496
  const next = normalizeTracking(baseTracking);
329
497
 
330
- const metaSection = extractSection(markdown, 'Backlog Meta');
498
+ const metaSection = extractAnySection(markdown, ['Backlog Meta', DOCUMENT_LABELS['zh-CN'].backlogMeta]);
331
499
  if (metaSection) {
332
500
  const parsed = parseBacklogMeta(metaSection.body);
333
501
  next.backlogMeta = parsed.meta;
334
502
  if (parsed.lastSyncedAt) {
335
503
  next.lastSyncedAt = parsed.lastSyncedAt;
336
504
  }
505
+ if (parsed.documentLanguage) {
506
+ next.outputPolicy.documentLanguage = parsed.documentLanguage;
507
+ }
337
508
  }
338
509
 
339
- const queueSection = extractSection(markdown, 'Queue');
510
+ const queueSection = extractAnySection(markdown, ['Queue', DOCUMENT_LABELS['zh-CN'].queue]);
340
511
  if (queueSection) {
341
512
  next.items = mergeItemMaps(next.items, parseBacklogQueue(queueSection.body));
342
513
  }
343
514
 
344
- const dependencySection = extractSection(markdown, 'Dependency Handoff');
515
+ const dependencySection = extractAnySection(markdown, [
516
+ 'Dependency Handoff',
517
+ DOCUMENT_LABELS['zh-CN'].dependencyHandoff
518
+ ]);
345
519
  if (dependencySection) {
346
520
  next.dependencyHandoff = parseDependencyHandoff(dependencySection.body);
347
521
  }
348
522
 
349
- const readySection = extractSection(markdown, 'Ready For Req-Plan');
523
+ const readySection = extractAnySection(markdown, [
524
+ 'Ready For Req-Plan',
525
+ DOCUMENT_LABELS['zh-CN'].readyForReqPlan
526
+ ]);
350
527
  if (readySection) {
351
528
  next.items = mergeItemMaps(
352
529
  next.items,
@@ -373,7 +550,7 @@ function trackingFromBacklog(markdown, baseTracking) {
373
550
  );
374
551
  }
375
552
 
376
- const parkedSection = extractSection(markdown, 'Parked');
553
+ const parkedSection = extractAnySection(markdown, ['Parked', DOCUMENT_LABELS['zh-CN'].parked]);
377
554
  if (parkedSection) {
378
555
  next.items = mergeItemMaps(
379
556
  next.items,
@@ -408,8 +585,10 @@ function trackingFromMarkdown(roadmapMarkdown = '', backlogMarkdown = '') {
408
585
  }
409
586
 
410
587
  function renderRoadmapTable(tracking) {
411
- const header = `| ${ROADMAP_COLUMNS.map(([label]) => label).join(' | ')} |`;
412
- const separator = `|${ROADMAP_COLUMNS.map(() => '------').join('|')}|`;
588
+ const labels = labelsFor(tracking);
589
+ const columns = labels.roadmapColumns;
590
+ const header = `| ${columns.map(([label]) => label).join(' | ')} |`;
591
+ const separator = `|${columns.map(() => '------').join('|')}|`;
413
592
  const rows = tracking.items.length
414
593
  ? tracking.items.map((item) =>
415
594
  [
@@ -450,13 +629,16 @@ function isRoadmapState(tracking) {
450
629
  }
451
630
 
452
631
  function buildTrackingBody(roadmapFile, trackingFile, tracking) {
632
+ const labels = labelsFor(tracking);
453
633
  const relativePath = path.relative(path.dirname(roadmapFile), trackingFile).replace(/\\/g, '/');
454
634
  const displayPath = relativePath || path.basename(trackingFile);
455
- const sourceLabel = isRoadmapState(tracking) ? 'Roadmap state source' : 'Tracking source';
635
+ const sourceLabel = isRoadmapState(tracking) ? labels.roadmapStateSource : labels.trackingSource;
636
+ const outputLanguage = outputLanguageFor(tracking);
456
637
 
457
638
  return [
458
639
  '',
459
640
  `- ${sourceLabel}: \`${displayPath}\``,
641
+ `- ${labels.outputLanguage}: ${outputLanguage}`,
460
642
  '',
461
643
  '<!-- roadmap-tracking:start -->',
462
644
  renderRoadmapTable(tracking),
@@ -476,6 +658,7 @@ function formatMermaidLabel(value) {
476
658
  }
477
659
 
478
660
  function renderArchitectureDiagram(tracking) {
661
+ const labels = labelsFor(tracking);
479
662
  const architecture = tracking.architecture || {};
480
663
  const nodes = Array.isArray(architecture.nodes) ? architecture.nodes : [];
481
664
  const edges = Array.isArray(architecture.edges) ? architecture.edges : [];
@@ -484,7 +667,7 @@ function renderArchitectureDiagram(tracking) {
484
667
  return '';
485
668
  }
486
669
 
487
- const lines = ['## Technical Architecture', '', '```mermaid', 'flowchart TD'];
670
+ const lines = [`## ${labels.technicalArchitecture}`, '', '```mermaid', 'flowchart TD'];
488
671
 
489
672
  nodes.forEach((node) => {
490
673
  const id = formatMermaidId(node.id);
@@ -509,20 +692,28 @@ function renderArchitectureDiagram(tracking) {
509
692
  }
510
693
 
511
694
  function renderRoadmapDocument({ original = '# ROADMAP\n', roadmapFile, trackingFile, tracking }) {
512
- const section = extractSection(original, 'Implementation Tracking');
695
+ const labels = labelsFor(tracking);
696
+ const baseOriginal = original.trim() === '# ROADMAP' ? `# ${labels.roadmapTitle}\n` : original;
697
+ const section = extractAnySection(baseOriginal, [
698
+ 'Implementation Tracking',
699
+ labels.implementationTracking
700
+ ]);
513
701
  const body = buildTrackingBody(roadmapFile, trackingFile, tracking);
514
- const nextSection = `## Implementation Tracking${body}`;
702
+ const nextSection = `## ${labels.implementationTracking}${body}`;
515
703
  const architectureSection = isRoadmapState(tracking) ? `\n${renderArchitectureDiagram(tracking)}` : '';
516
704
 
517
705
  const rendered = section
518
- ? `${original.slice(0, section.start)}${nextSection}${original.slice(section.end)}`
519
- : `${original.replace(/\s*$/, '')}\n\n${nextSection}\n`;
706
+ ? `${baseOriginal.slice(0, section.start)}${nextSection}${baseOriginal.slice(section.end)}`
707
+ : `${baseOriginal.replace(/\s*$/, '')}\n\n${nextSection}\n`;
520
708
 
521
709
  if (!architectureSection.trim()) {
522
710
  return rendered;
523
711
  }
524
712
 
525
- const existingArchitecture = extractSection(rendered, 'Technical Architecture');
713
+ const existingArchitecture = extractAnySection(rendered, [
714
+ 'Technical Architecture',
715
+ labels.technicalArchitecture
716
+ ]);
526
717
  if (existingArchitecture) {
527
718
  return `${rendered.slice(0, existingArchitecture.start)}${architectureSection.trimEnd()}\n${rendered.slice(existingArchitecture.end)}`;
528
719
  }
@@ -530,9 +721,15 @@ function renderRoadmapDocument({ original = '# ROADMAP\n', roadmapFile, tracking
530
721
  return `${rendered.replace(/\s*$/, '')}\n\n${architectureSection}`;
531
722
  }
532
723
 
724
+ function renderLocalizedBoolean(value, labels) {
725
+ return value ? labels.yes : labels.no;
726
+ }
727
+
533
728
  function renderBacklogQueue(tracking) {
534
- const header = `| ${BACKLOG_QUEUE_COLUMNS.map(([label]) => label).join(' | ')} |`;
535
- const separator = `|${BACKLOG_QUEUE_COLUMNS.map(() => '------').join('|')}|`;
729
+ const labels = labelsFor(tracking);
730
+ const columns = labels.backlogColumns;
731
+ const header = `| ${columns.map(([label]) => label).join(' | ')} |`;
732
+ const separator = `|${columns.map(() => '------').join('|')}|`;
536
733
  const queueItems = tracking.items.filter((item) => !item.backlog.parked);
537
734
  const rows = queueItems.length
538
735
  ? queueItems.map((item) =>
@@ -550,7 +747,7 @@ function renderBacklogQueue(tracking) {
550
747
  formatList(item.backlog.parallelWith),
551
748
  formatBacklogValue(item.backlog.unknowns),
552
749
  formatBacklogValue(item.backlog.nextDecision),
553
- formatBoolean(item.backlog.ready)
750
+ renderLocalizedBoolean(item.backlog.ready, labels)
554
751
  ].join(' | ')
555
752
  )
556
753
  : [
@@ -568,7 +765,7 @@ function renderBacklogQueue(tracking) {
568
765
  '-',
569
766
  '-',
570
767
  '-',
571
- 'No'
768
+ labels.no
572
769
  ].join(' | ')
573
770
  ];
574
771
 
@@ -576,104 +773,112 @@ function renderBacklogQueue(tracking) {
576
773
  }
577
774
 
578
775
  function renderReadyForReqPlan(tracking) {
776
+ const labels = labelsFor(tracking);
777
+ const fields = labels.readyFields;
579
778
  const readyItems = tracking.items.filter((item) => item.backlog.ready && !item.backlog.parked);
580
779
  if (!readyItems.length) {
581
- return '- RM-001:\n - Primary Capability:\n - Secondary Capabilities:\n - Why now:\n - Success signal:\n - Entry constraints:\n - Capability gap:\n - Expected spec delta:\n - Open risks:\n - First planning question:\n - Required context to load:\n - Depends On:\n - Parallel With:\n - Why this is ready now:';
780
+ return labels.readyEmpty;
582
781
  }
583
782
 
584
783
  return readyItems
585
784
  .map((item) =>
586
785
  [
587
786
  `- ${item.rmId}:`,
588
- ` - Primary Capability: ${formatInlineCode(item.primaryCapability)}`,
589
- ` - Secondary Capabilities: ${item.secondaryCapabilities.length ? formatInlineCode(item.secondaryCapabilities.join(', ')) : '`-`'}`,
590
- ` - Why now: ${formatBacklogValue(item.backlog.whyNow)}`,
591
- ` - Success signal: ${formatBacklogValue(item.backlog.successSignal)}`,
592
- ` - Entry constraints: ${formatBacklogValue(item.backlog.entryConstraints)}`,
593
- ` - Capability gap: ${formatBacklogValue(item.backlog.capabilityGap)}`,
594
- ` - Expected spec delta: ${formatBacklogValue(item.expectedSpecDelta)}`,
595
- ` - Open risks: ${formatBacklogValue(item.backlog.openRisks)}`,
596
- ` - First planning question: ${formatBacklogValue(item.backlog.firstPlanningQuestion)}`,
597
- ` - Required context to load: ${formatBacklogValue(item.backlog.requiredContextToLoad)}`,
598
- ` - Depends On: ${item.dependsOn.length ? formatInlineCode(item.dependsOn.join(', ')) : '`-`'}`,
599
- ` - Parallel With: ${item.backlog.parallelWith.length ? formatInlineCode(item.backlog.parallelWith.join(', ')) : '`-`'}`,
600
- ` - Why this is ready now: ${formatBacklogValue(item.backlog.whyReadyNow)}`
787
+ ` - ${fields.primaryCapability}: ${formatInlineCode(item.primaryCapability)}`,
788
+ ` - ${fields.secondaryCapabilities}: ${item.secondaryCapabilities.length ? formatInlineCode(item.secondaryCapabilities.join(', ')) : '`-`'}`,
789
+ ` - ${fields.whyNow}: ${formatBacklogValue(item.backlog.whyNow)}`,
790
+ ` - ${fields.successSignal}: ${formatBacklogValue(item.backlog.successSignal)}`,
791
+ ` - ${fields.entryConstraints}: ${formatBacklogValue(item.backlog.entryConstraints)}`,
792
+ ` - ${fields.capabilityGap}: ${formatBacklogValue(item.backlog.capabilityGap)}`,
793
+ ` - ${fields.expectedSpecDelta}: ${formatBacklogValue(item.expectedSpecDelta)}`,
794
+ ` - ${fields.openRisks}: ${formatBacklogValue(item.backlog.openRisks)}`,
795
+ ` - ${fields.firstPlanningQuestion}: ${formatBacklogValue(item.backlog.firstPlanningQuestion)}`,
796
+ ` - ${fields.requiredContextToLoad}: ${formatBacklogValue(item.backlog.requiredContextToLoad)}`,
797
+ ` - ${fields.dependsOn}: ${item.dependsOn.length ? formatInlineCode(item.dependsOn.join(', ')) : '`-`'}`,
798
+ ` - ${fields.parallelWith}: ${item.backlog.parallelWith.length ? formatInlineCode(item.backlog.parallelWith.join(', ')) : '`-`'}`,
799
+ ` - ${fields.whyReadyNow}: ${formatBacklogValue(item.backlog.whyReadyNow)}`
601
800
  ].join('\n')
602
801
  )
603
802
  .join('\n\n');
604
803
  }
605
804
 
606
805
  function renderParked(tracking) {
806
+ const labels = labelsFor(tracking);
807
+ const fields = labels.parkedFields;
607
808
  const parkedItems = tracking.items.filter((item) => item.backlog.parked);
608
809
  if (!parkedItems.length) {
609
- return '- RM-XXX:\n - Reason parked:\n - Trigger to reopen:\n - Missing evidence:';
810
+ return labels.parkedEmpty;
610
811
  }
611
812
 
612
813
  return parkedItems
613
814
  .map((item) =>
614
815
  [
615
816
  `- ${item.rmId}:`,
616
- ` - Reason parked: ${formatBacklogValue(item.backlog.parkedReason)}`,
617
- ` - Trigger to reopen: ${formatBacklogValue(item.backlog.triggerToReopen)}`,
618
- ` - Missing evidence: ${formatBacklogValue(item.backlog.missingEvidence)}`
817
+ ` - ${fields.parkedReason}: ${formatBacklogValue(item.backlog.parkedReason)}`,
818
+ ` - ${fields.triggerToReopen}: ${formatBacklogValue(item.backlog.triggerToReopen)}`,
819
+ ` - ${fields.missingEvidence}: ${formatBacklogValue(item.backlog.missingEvidence)}`
619
820
  ].join('\n')
620
821
  )
621
822
  .join('\n\n');
622
823
  }
623
824
 
624
825
  function renderProjectDirectionHandoff(tracking) {
826
+ const labels = labelsFor(tracking);
625
827
  const context = tracking.context || {};
626
828
 
627
829
  return [
628
- `- Project direction mode: ${formatBacklogValue(context.projectDirectionMode)}`,
629
- `- Direction mode rationale: ${formatBacklogValue(context.projectDirectionRationale)}`,
630
- `- Direction-specific questions selected: ${formatList(context.directionQuestionsSelected || [])}`,
631
- `- Direction-specific questions skipped: ${formatList(context.directionQuestionsSkipped || [])}`,
632
- `- Direction guardrails applied: ${formatList(context.directionGuardrailsApplied || [])}`,
633
- `- Planning posture: ${formatBacklogValue(context.planningPosture)}`,
634
- `- Evidence maturity: ${formatBacklogValue(context.evidenceMaturity)}`
830
+ `- ${labels.projectDirectionMode}: ${formatBacklogValue(context.projectDirectionMode)}`,
831
+ `- ${labels.projectDirectionRationale}: ${formatBacklogValue(context.projectDirectionRationale)}`,
832
+ `- ${labels.directionQuestionsSelected}: ${formatList(context.directionQuestionsSelected || [])}`,
833
+ `- ${labels.directionQuestionsSkipped}: ${formatList(context.directionQuestionsSkipped || [])}`,
834
+ `- ${labels.directionGuardrailsApplied}: ${formatList(context.directionGuardrailsApplied || [])}`,
835
+ `- ${labels.planningPosture}: ${formatBacklogValue(context.planningPosture)}`,
836
+ `- ${labels.evidenceMaturity}: ${formatBacklogValue(context.evidenceMaturity)}`
635
837
  ].join('\n');
636
838
  }
637
839
 
638
840
  function renderBacklogDocument({ backlogFile, trackingFile, tracking }) {
841
+ const labels = labelsFor(tracking);
639
842
  const relativePath = path.relative(path.dirname(backlogFile), trackingFile).replace(/\\/g, '/');
640
843
  const displayPath = relativePath || path.basename(trackingFile);
641
- const sourceLabel = isRoadmapState(tracking) ? 'Roadmap state source' : 'Tracking source';
844
+ const sourceLabel = isRoadmapState(tracking) ? labels.roadmapStateSource : labels.trackingSource;
845
+ const outputLanguage = outputLanguageFor(tracking);
642
846
  const deprecationNotice = isRoadmapState(tracking)
643
- ? ['> Deprecated projection. Edit `roadmap.json` instead.', '']
847
+ ? [labels.deprecatedProjection, '']
644
848
  : [];
645
849
 
646
850
  return [
647
- '# BACKLOG',
851
+ `# ${labels.backlogTitle}`,
648
852
  '',
649
853
  ...deprecationNotice,
650
- '## Backlog Meta',
854
+ `## ${labels.backlogMeta}`,
651
855
  '',
652
- `- Roadmap version: ${formatInlineCode(tracking.backlogMeta.roadmapVersion)}`,
653
- `- Skill version: ${formatInlineCode(tracking.backlogMeta.skillVersion)}`,
654
- `- Last synced: ${formatInlineCode(tracking.lastSyncedAt)}`,
655
- `- Current focus stage: ${formatInlineCode(tracking.backlogMeta.currentFocusStage)}`,
856
+ `- ${labels.roadmapVersion}: ${formatInlineCode(tracking.backlogMeta.roadmapVersion)}`,
857
+ `- ${labels.skillVersion}: ${formatInlineCode(tracking.backlogMeta.skillVersion)}`,
858
+ `- ${labels.lastSynced}: ${formatInlineCode(tracking.lastSyncedAt)}`,
859
+ `- ${labels.currentFocusStage}: ${formatInlineCode(tracking.backlogMeta.currentFocusStage)}`,
656
860
  `- ${sourceLabel}: \`${displayPath}\``,
861
+ `- ${labels.outputLanguage}: ${outputLanguage}`,
657
862
  '',
658
- '## Queue',
863
+ `## ${labels.queue}`,
659
864
  '',
660
865
  renderBacklogQueue(tracking),
661
866
  '',
662
- '## Dependency Handoff',
867
+ `## ${labels.dependencyHandoff}`,
663
868
  '',
664
- `- Serial spine: ${formatBacklogValue(tracking.dependencyHandoff.serialSpine)}`,
665
- `- Parallel-ready next wave: ${formatBacklogValue(tracking.dependencyHandoff.parallelReadyNextWave)}`,
666
- `- Notes on blockers: ${formatBacklogValue(tracking.dependencyHandoff.notesOnBlockers)}`,
869
+ `- ${labels.serialSpine}: ${formatBacklogValue(tracking.dependencyHandoff.serialSpine)}`,
870
+ `- ${labels.parallelReadyNextWave}: ${formatBacklogValue(tracking.dependencyHandoff.parallelReadyNextWave)}`,
871
+ `- ${labels.notesOnBlockers}: ${formatBacklogValue(tracking.dependencyHandoff.notesOnBlockers)}`,
667
872
  '',
668
- '## Project Direction Handoff',
873
+ `## ${labels.projectDirectionHandoff}`,
669
874
  '',
670
875
  renderProjectDirectionHandoff(tracking),
671
876
  '',
672
- '## Ready For Req-Plan',
877
+ `## ${labels.readyForReqPlan}`,
673
878
  '',
674
879
  renderReadyForReqPlan(tracking),
675
880
  '',
676
- '## Parked',
881
+ `## ${labels.parked}`,
677
882
  '',
678
883
  renderParked(tracking),
679
884
  ''