minimal-vibe-coding-kit 0.4.2 → 0.5.1

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 (163) hide show
  1. package/.agents/skills/claim/SKILL.md +82 -0
  2. package/.agents/skills/tutien/SKILL.md +97 -0
  3. package/.agents/skills/tutien/references/adaptive-response.md +68 -0
  4. package/.agents/skills/tutien/references/classification.md +64 -0
  5. package/.agents/skills/tutien/references/lore-sources.md +43 -0
  6. package/.agents/skills/tutien/references/privacy.md +45 -0
  7. package/.agents/skills/tutien/references/schema.md +106 -0
  8. package/.agents/skills/tutien/references/scoring-and-realms.md +37 -0
  9. package/.agents/skills/tutien/references/story-system.md +176 -0
  10. package/.agents/skills/tutien/references/vi-style-guide.md +89 -0
  11. package/.agents/skills/tutien/references/voice-and-mode.md +59 -0
  12. package/.agents/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  13. package/.agents/skills/tutien/scripts/adapters/git.mjs +48 -0
  14. package/.agents/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  15. package/.agents/skills/tutien/scripts/analyze-history.mjs +84 -0
  16. package/.agents/skills/tutien/scripts/catalog.mjs +130 -0
  17. package/.agents/skills/tutien/scripts/classify.mjs +338 -0
  18. package/.agents/skills/tutien/scripts/command.mjs +126 -0
  19. package/.agents/skills/tutien/scripts/compare.mjs +41 -0
  20. package/.agents/skills/tutien/scripts/metrics.mjs +306 -0
  21. package/.agents/skills/tutien/scripts/normalize-events.mjs +68 -0
  22. package/.agents/skills/tutien/scripts/project-profile.mjs +202 -0
  23. package/.agents/skills/tutien/scripts/redact.mjs +30 -0
  24. package/.agents/skills/tutien/scripts/render-report.mjs +479 -0
  25. package/.agents/skills/tutien/scripts/response-brief.mjs +103 -0
  26. package/.agents/skills/tutien/scripts/run-tutien.mjs +350 -0
  27. package/.agents/skills/tutien/scripts/score.mjs +97 -0
  28. package/.agents/skills/tutien/scripts/snapshot.mjs +76 -0
  29. package/.agents/skills/tutien/scripts/story-ledger.mjs +241 -0
  30. package/.agents/skills/tutien/scripts/villains.mjs +196 -0
  31. package/.claude/skills/claim/SKILL.md +82 -0
  32. package/.claude/skills/tutien/SKILL.md +97 -0
  33. package/.claude/skills/tutien/references/adaptive-response.md +68 -0
  34. package/.claude/skills/tutien/references/classification.md +64 -0
  35. package/.claude/skills/tutien/references/lore-sources.md +43 -0
  36. package/.claude/skills/tutien/references/privacy.md +45 -0
  37. package/.claude/skills/tutien/references/schema.md +106 -0
  38. package/.claude/skills/tutien/references/scoring-and-realms.md +37 -0
  39. package/.claude/skills/tutien/references/story-system.md +176 -0
  40. package/.claude/skills/tutien/references/vi-style-guide.md +89 -0
  41. package/.claude/skills/tutien/references/voice-and-mode.md +59 -0
  42. package/.claude/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  43. package/.claude/skills/tutien/scripts/adapters/git.mjs +48 -0
  44. package/.claude/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  45. package/.claude/skills/tutien/scripts/analyze-history.mjs +84 -0
  46. package/.claude/skills/tutien/scripts/catalog.mjs +130 -0
  47. package/.claude/skills/tutien/scripts/classify.mjs +338 -0
  48. package/.claude/skills/tutien/scripts/command.mjs +126 -0
  49. package/.claude/skills/tutien/scripts/compare.mjs +41 -0
  50. package/.claude/skills/tutien/scripts/metrics.mjs +306 -0
  51. package/.claude/skills/tutien/scripts/normalize-events.mjs +68 -0
  52. package/.claude/skills/tutien/scripts/project-profile.mjs +202 -0
  53. package/.claude/skills/tutien/scripts/redact.mjs +30 -0
  54. package/.claude/skills/tutien/scripts/render-report.mjs +479 -0
  55. package/.claude/skills/tutien/scripts/response-brief.mjs +103 -0
  56. package/.claude/skills/tutien/scripts/run-tutien.mjs +350 -0
  57. package/.claude/skills/tutien/scripts/score.mjs +97 -0
  58. package/.claude/skills/tutien/scripts/snapshot.mjs +76 -0
  59. package/.claude/skills/tutien/scripts/story-ledger.mjs +241 -0
  60. package/.claude/skills/tutien/scripts/villains.mjs +196 -0
  61. package/.codex-plugin/plugin.json +1 -1
  62. package/.cursor/skills/claim/SKILL.md +82 -0
  63. package/.cursor/skills/tutien/SKILL.md +97 -0
  64. package/.cursor/skills/tutien/references/adaptive-response.md +68 -0
  65. package/.cursor/skills/tutien/references/classification.md +64 -0
  66. package/.cursor/skills/tutien/references/lore-sources.md +43 -0
  67. package/.cursor/skills/tutien/references/privacy.md +45 -0
  68. package/.cursor/skills/tutien/references/schema.md +106 -0
  69. package/.cursor/skills/tutien/references/scoring-and-realms.md +37 -0
  70. package/.cursor/skills/tutien/references/story-system.md +176 -0
  71. package/.cursor/skills/tutien/references/vi-style-guide.md +89 -0
  72. package/.cursor/skills/tutien/references/voice-and-mode.md +59 -0
  73. package/.cursor/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  74. package/.cursor/skills/tutien/scripts/adapters/git.mjs +48 -0
  75. package/.cursor/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  76. package/.cursor/skills/tutien/scripts/analyze-history.mjs +84 -0
  77. package/.cursor/skills/tutien/scripts/catalog.mjs +130 -0
  78. package/.cursor/skills/tutien/scripts/classify.mjs +338 -0
  79. package/.cursor/skills/tutien/scripts/command.mjs +126 -0
  80. package/.cursor/skills/tutien/scripts/compare.mjs +41 -0
  81. package/.cursor/skills/tutien/scripts/metrics.mjs +306 -0
  82. package/.cursor/skills/tutien/scripts/normalize-events.mjs +68 -0
  83. package/.cursor/skills/tutien/scripts/project-profile.mjs +202 -0
  84. package/.cursor/skills/tutien/scripts/redact.mjs +30 -0
  85. package/.cursor/skills/tutien/scripts/render-report.mjs +479 -0
  86. package/.cursor/skills/tutien/scripts/response-brief.mjs +103 -0
  87. package/.cursor/skills/tutien/scripts/run-tutien.mjs +350 -0
  88. package/.cursor/skills/tutien/scripts/score.mjs +97 -0
  89. package/.cursor/skills/tutien/scripts/snapshot.mjs +76 -0
  90. package/.cursor/skills/tutien/scripts/story-ledger.mjs +241 -0
  91. package/.cursor/skills/tutien/scripts/villains.mjs +196 -0
  92. package/.grok/skills/claim/SKILL.md +82 -0
  93. package/.grok/skills/tutien/SKILL.md +97 -0
  94. package/.grok/skills/tutien/references/adaptive-response.md +68 -0
  95. package/.grok/skills/tutien/references/classification.md +64 -0
  96. package/.grok/skills/tutien/references/lore-sources.md +43 -0
  97. package/.grok/skills/tutien/references/privacy.md +45 -0
  98. package/.grok/skills/tutien/references/schema.md +106 -0
  99. package/.grok/skills/tutien/references/scoring-and-realms.md +37 -0
  100. package/.grok/skills/tutien/references/story-system.md +176 -0
  101. package/.grok/skills/tutien/references/vi-style-guide.md +89 -0
  102. package/.grok/skills/tutien/references/voice-and-mode.md +59 -0
  103. package/.grok/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  104. package/.grok/skills/tutien/scripts/adapters/git.mjs +48 -0
  105. package/.grok/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  106. package/.grok/skills/tutien/scripts/analyze-history.mjs +84 -0
  107. package/.grok/skills/tutien/scripts/catalog.mjs +130 -0
  108. package/.grok/skills/tutien/scripts/classify.mjs +338 -0
  109. package/.grok/skills/tutien/scripts/command.mjs +126 -0
  110. package/.grok/skills/tutien/scripts/compare.mjs +41 -0
  111. package/.grok/skills/tutien/scripts/metrics.mjs +306 -0
  112. package/.grok/skills/tutien/scripts/normalize-events.mjs +68 -0
  113. package/.grok/skills/tutien/scripts/project-profile.mjs +202 -0
  114. package/.grok/skills/tutien/scripts/redact.mjs +30 -0
  115. package/.grok/skills/tutien/scripts/render-report.mjs +479 -0
  116. package/.grok/skills/tutien/scripts/response-brief.mjs +103 -0
  117. package/.grok/skills/tutien/scripts/run-tutien.mjs +350 -0
  118. package/.grok/skills/tutien/scripts/score.mjs +97 -0
  119. package/.grok/skills/tutien/scripts/snapshot.mjs +76 -0
  120. package/.grok/skills/tutien/scripts/story-ledger.mjs +241 -0
  121. package/.grok/skills/tutien/scripts/villains.mjs +196 -0
  122. package/.vibekit/docs/BACKBONE_REFERENCE.md +2 -0
  123. package/.vibekit/docs/INSTALL.md +3 -1
  124. package/.vibekit/init/CLAUDE-template.md +2 -0
  125. package/.vibekit/scripts/mvck.mjs +5 -5
  126. package/.vibekit/scripts/pack-dry-run.mjs +38 -3
  127. package/.vibekit/scripts/validate-kit.mjs +34 -24
  128. package/.vibekit/skills/claim/SKILL.md +82 -0
  129. package/.vibekit/skills/skills-manifest.json +27 -0
  130. package/.vibekit/skills/tutien/SKILL.md +97 -0
  131. package/.vibekit/skills/tutien/references/adaptive-response.md +68 -0
  132. package/.vibekit/skills/tutien/references/classification.md +64 -0
  133. package/.vibekit/skills/tutien/references/lore-sources.md +43 -0
  134. package/.vibekit/skills/tutien/references/privacy.md +45 -0
  135. package/.vibekit/skills/tutien/references/schema.md +106 -0
  136. package/.vibekit/skills/tutien/references/scoring-and-realms.md +37 -0
  137. package/.vibekit/skills/tutien/references/story-system.md +176 -0
  138. package/.vibekit/skills/tutien/references/vi-style-guide.md +89 -0
  139. package/.vibekit/skills/tutien/references/voice-and-mode.md +59 -0
  140. package/.vibekit/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  141. package/.vibekit/skills/tutien/scripts/adapters/git.mjs +48 -0
  142. package/.vibekit/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  143. package/.vibekit/skills/tutien/scripts/analyze-history.mjs +84 -0
  144. package/.vibekit/skills/tutien/scripts/catalog.mjs +130 -0
  145. package/.vibekit/skills/tutien/scripts/classify.mjs +338 -0
  146. package/.vibekit/skills/tutien/scripts/command.mjs +126 -0
  147. package/.vibekit/skills/tutien/scripts/compare.mjs +41 -0
  148. package/.vibekit/skills/tutien/scripts/metrics.mjs +306 -0
  149. package/.vibekit/skills/tutien/scripts/normalize-events.mjs +68 -0
  150. package/.vibekit/skills/tutien/scripts/project-profile.mjs +202 -0
  151. package/.vibekit/skills/tutien/scripts/redact.mjs +30 -0
  152. package/.vibekit/skills/tutien/scripts/render-report.mjs +479 -0
  153. package/.vibekit/skills/tutien/scripts/response-brief.mjs +103 -0
  154. package/.vibekit/skills/tutien/scripts/run-tutien.mjs +350 -0
  155. package/.vibekit/skills/tutien/scripts/score.mjs +97 -0
  156. package/.vibekit/skills/tutien/scripts/snapshot.mjs +76 -0
  157. package/.vibekit/skills/tutien/scripts/story-ledger.mjs +241 -0
  158. package/.vibekit/skills/tutien/scripts/villains.mjs +196 -0
  159. package/CHANGELOG.md +51 -0
  160. package/README.md +53 -47
  161. package/docs/README.vi.md +14 -8
  162. package/docs/README.zh-CN.md +279 -0
  163. package/package.json +9 -3
@@ -0,0 +1,479 @@
1
+ // Phase 2 evidence ledger. buildReportModel() turns analysis JSON into a
2
+ // language-neutral model (facts only). renderMarkdown() provides a stable
3
+ // inspection and audit view in the active language. It is deliberately not a
4
+ // user-facing response template: the agent composes that response adaptively
5
+ // from this model and the live project/story context. The ledger NEVER changes
6
+ // a count from the analysis; it only selects wording.
7
+
8
+ import { scoreReport, DIMENSIONS } from './score.mjs';
9
+ import { detectProblems } from './catalog.mjs';
10
+ import { buildVillains } from './villains.mjs';
11
+ import { classifyProject, localizeRationale, progressionMetrics, PROGRESSION } from './classify.mjs';
12
+ import { resolveTone, TUTIEN_EXPERIENCE } from './command.mjs';
13
+
14
+ // A context is sensitive (humor forced neutral) when unrecovered failures or
15
+ // conflicts are present, or when the caller passes an emergency signal
16
+ // (secret exposure, security finding, outage, data loss, user distress).
17
+ export function isSensitive(analysis, options = {}) {
18
+ if (options.emergency) return true;
19
+ const signals = options.signals ?? {};
20
+ if (signals.secretExposure || signals.securityHighFinding || signals.outage || signals.dataLoss || signals.userDistress) return true;
21
+ const issues = analysis.issues ?? {};
22
+ const failures = issues.failures ?? [];
23
+ const recoveries = issues.recoveries ?? [];
24
+ const unrecovered = failures.some((f) => !recoveries.some((r) => r.issueEventId === f.eventId));
25
+ return unrecovered || (analysis.conflicts ?? []).length > 0;
26
+ }
27
+
28
+ function resolveProjectHelp(projectHelp, profile) {
29
+ if (!profile) return projectHelp;
30
+ if (projectHelp === 'backbone.yml validate') return profile.recommendedValidation ?? null;
31
+ if (['sequential-thinking', 'clearthought', 'claim'].includes(projectHelp) && profile.kitInstalled !== true) return null;
32
+ return projectHelp;
33
+ }
34
+
35
+ export function buildReportModel(analysis, options = {}) {
36
+ const score = scoreReport(analysis);
37
+ const rawProblems = detectProblems(analysis);
38
+ const tokens = analysis.tokens ?? {};
39
+ const coverage = analysis.coverage ?? {};
40
+
41
+ // Optional cultivation classification from a project profile. One
42
+ // fail-closed policy state governs the whole report: only `clear` enables
43
+ // realm/score/villains/recommendations/positive progression. Everything
44
+ // else (declared-stop, needs-review, authorization-required) suppresses
45
+ // them at the DATA level — score/realm/dimensions become null, not hidden.
46
+ let cultivation = null;
47
+ let canGamify = true;
48
+ if (options.profile) {
49
+ const classification = classifyProject(options.profile);
50
+ canGamify = classification.policy.canGamify;
51
+ const progression = progressionMetrics(analysis, {
52
+ prior: options.priorProgression ?? null,
53
+ canGamify,
54
+ salt: options.progressionSalt ?? ''
55
+ });
56
+ cultivation = { classification, progression };
57
+ }
58
+ const suppressed = !canGamify;
59
+
60
+ const sensitive = isSensitive(analysis, options) || suppressed;
61
+ const tone = resolveTone(options.tone, { sensitive });
62
+ const { villains } = buildVillains(rawProblems, {
63
+ tone,
64
+ villains: options.villains ?? 'on',
65
+ threshold: options.villainThreshold,
66
+ emergency: sensitive,
67
+ seedInputs: options.seedInputs,
68
+ context: options.context,
69
+ priorVillains: options.priorVillains
70
+ });
71
+ const villainByProblem = new Map(villains.map((v) => [v.problemId, v]));
72
+
73
+ // Recommendations and antagonist framing are part of the game layer. A
74
+ // non-clear policy keeps only neutral evidence/risks, so these are absent at
75
+ // the data level rather than merely hidden by the renderer.
76
+ const problems = suppressed ? [] : rawProblems;
77
+ // One implementation-intention practice, specific to the top problem's
78
+ // catalog entry — a conflict gets a conflict rule, not a retry rule.
79
+ const ifThen = suppressed
80
+ ? null
81
+ : problems.length
82
+ ? problems[0].meta.ifThen
83
+ : {
84
+ vi: 'Nếu bắt đầu một nhiệm vụ mới, hãy viết tiêu chí hoàn thành trước khi sửa mã nguồn.',
85
+ en: 'If you start a new task, then write its done-criterion before editing code.'
86
+ };
87
+
88
+ const topProjectHelp = problems.length ? resolveProjectHelp(problems[0].meta.projectHelp, options.profile) : null;
89
+ const lesson = suppressed
90
+ ? null
91
+ : problems.length
92
+ ? { skill: topProjectHelp, technique: problems[0].meta.counterTechnique }
93
+ : { skill: options.profile?.recommendedValidation ?? (options.profile ? null : 'backbone.yml validate'), technique: { vi: 'Giữ vòng lặp: sửa nhỏ, chạy kiểm chứng, xác nhận.', en: 'Keep the loop: small edit, run validation, confirm.' } };
94
+
95
+ return {
96
+ schemaVersion: 1,
97
+ experience: { ...TUTIEN_EXPERIENCE },
98
+ composition: {
99
+ finalResponse: 'agent-authored',
100
+ ledgerRole: 'evidence-scaffold-only',
101
+ adaptTo: ['current-user-request', 'project-specific-facts', 'living-chronicle', 'recent-response-rhythm'],
102
+ preserve: ['facts', 'confidence', 'policy', 'privacy', 'continuity'],
103
+ vary: ['structure', 'opening', 'scene', 'pacing', 'dialogue', 'technical-density', 'closing-image']
104
+ },
105
+ tone,
106
+ villainsShown: villains.length,
107
+ villainCards: villains,
108
+ cultivation,
109
+ policyState: cultivation?.classification.policy.state ?? 'clear',
110
+ suppressGamification: suppressed,
111
+ showScore: options.score === 'show' && !suppressed,
112
+ coverage: {
113
+ sessions: coverage.sessions ?? 0,
114
+ userPrompts: coverage.userPrompts ?? 0,
115
+ commits: coverage.commits ?? 0,
116
+ window: coverage.window ?? { start: null, end: null },
117
+ reportedTurnsPct: coverage.tokenCoverage?.reportedTurnsPct ?? 0,
118
+ estimatedTurnsPct: coverage.tokenCoverage?.estimatedTurnsPct ?? 0,
119
+ confidence: coverage.confidence ?? 'low'
120
+ },
121
+ tokens: {
122
+ reportedTotal: tokens.reportedTotal ?? 0,
123
+ estimatedTotal: tokens.estimatedTotal ?? 0,
124
+ unknownTurns: tokens.unknownTurns ?? 0,
125
+ dedupedStreamUpdates: tokens.dedupedStreamUpdates ?? 0
126
+ },
127
+ // Under a suppressed policy these are null (absent), not merely hidden.
128
+ realm: suppressed ? null : score.realm,
129
+ enoughEvidence: suppressed ? false : score.enoughEvidence,
130
+ score: suppressed ? null : score.score,
131
+ dimensions: suppressed ? [] : DIMENSIONS.map((d) => ({ key: d.key, weight: d.weight, value: score.dimensions[d.key] })),
132
+ missing: suppressed ? [] : score.missing,
133
+ problems: problems.map((p) => {
134
+ const v = villainByProblem.get(p.problemId);
135
+ return {
136
+ problemType: p.meta.problemType,
137
+ confidence: p.confidence,
138
+ priority: p.priority,
139
+ evidence: p.evidence,
140
+ counterTechnique: p.meta.counterTechnique,
141
+ projectHelp: resolveProjectHelp(p.meta.projectHelp, options.profile),
142
+ microQuest: p.meta.microQuest,
143
+ victory: p.meta.victory,
144
+ villain: v ? { name: v.archetype.name, gloss: v.archetype.gloss, role: v.role, tone: v.tone, challenge: v.challenge } : null
145
+ };
146
+ }),
147
+ ifThen,
148
+ lesson
149
+ };
150
+ }
151
+
152
+ const L = {
153
+ vi: {
154
+ title: 'Tu tiên tĩnh ký',
155
+ neutralTitle: 'Bản ghi rà soát quy trình',
156
+ opening: {
157
+ serene: 'Mây nhẹ qua sơn môn, một trang đạo ký lặng lẽ mở ra. Đây là cuộc dạo chơi phân loại nếp cộng tác: thở chậm, nhìn rõ, rồi chọn một bước vừa sức.',
158
+ spirited: 'Chuông ngọc khẽ ngân nơi sơn môn; đạo ký mở sang trang mới. Ta cùng nhìn dấu vết công việc bằng một chút hóm hỉnh và một bước tiến thật rõ ràng.',
159
+ neutral: 'Bản ghi này trình bày bằng giọng bình tĩnh, trực tiếp; phần diễn xướng tu tiên tạm dừng để ưu tiên an toàn và sự rõ ràng.'
160
+ },
161
+ thienCo: 'Thiên cơ trong tầm mắt',
162
+ canhGioi: 'Cảnh giới bên hiên mây',
163
+ linhThach: 'Linh thạch đã tiêu',
164
+ tamMa: 'Tâm ma trên đạo lộ',
165
+ congPhap: 'Một thức cho lần đột phá tới',
166
+ baiHoc: 'Công pháp mang theo hôm nay',
167
+ phuongPhap: 'Đạo điển và nguồn số liệu',
168
+ closingTitle: 'Khép lại đạo ký',
169
+ closing: {
170
+ clear: 'Đạo lộ yên không có nghĩa đạo tâm được phép ngủ quên. Giữ bước sửa nhỏ và chứng cứ thật; chuông kiểm chứng vẫn treo dưới mái sơn môn, chờ lần ai đó định đi tắt.',
171
+ challenge: 'Tâm ma đã để dấu chân rõ đến thế mà còn vòng qua nó thì chỉ là tự dâng thêm lương khô cho địch. Hạ đúng một nút thắt bằng kiểm chứng thật; ngoài sơn môn, bóng áo của nó vẫn chưa tan.',
172
+ paused: 'Bản ghi dừng tại đây. Hãy hoàn tất bước rà soát hoặc ủy quyền cần thiết trước; không có cảnh giới nào quan trọng hơn một ranh giới an toàn.'
173
+ },
174
+ sessions: 'phiên', prompts: 'lượt yêu cầu', commits: 'bản ghi Git',
175
+ sessionOne: 'phiên', promptOne: 'lượt yêu cầu', commitOne: 'bản ghi Git', windowOne: 'kỳ báo cáo',
176
+ window: 'Khoảng thời gian', confidence: 'Độ tin cậy',
177
+ reported: 'Số liệu do nhà cung cấp báo cáo (chính xác)', estimated: 'Ước lượng cục bộ', unknown: 'Lượt chưa rõ số token',
178
+ dedup: 'Cập nhật luồng đã gộp',
179
+ score: 'Điểm', evidence: 'Bằng chứng', counter: 'Cách hóa giải', quest: 'Nhiệm vụ nhỏ', victory: 'Điều kiện thắng',
180
+ help: 'Trợ giúp sẵn có', ifthen: 'Nếu…thì', notEnough: 'Chưa đủ thiên cơ để tính cảnh giới',
181
+ noProblems: 'Không phát hiện tâm ma nào vượt ngưỡng bằng chứng. Giữ vững đạo tâm.',
182
+ method: 'Phân tích tất định phiên bản 1. Số đếm là các dấu hiệu có mức tin cậy; phần diễn giải không thay đổi số liệu.',
183
+ repeatEv: (n) => `${n} lần lặp hoặc thử lại trong một nhiệm vụ (dấu hiệu).`,
184
+ conflictEv: (n) => `${n} cặp chỉ thị mâu thuẫn (dấu hiệu).`,
185
+ unrecEv: (n) => `${n} thất bại chưa có lần kiểm chứng đạt tương ứng.`,
186
+ proofEv: (n) => `${n} bản ghi Git nhưng không có sự kiện kiểm chứng.`,
187
+ daoSection: 'Đạo và truyền thừa',
188
+ faction: 'Đạo', affiliation: 'Thân phận', pathsL: 'Đạo tu',
189
+ affNote: 'Thân phận là vị thế tổ chức, không phải đạo đức. Tán Tu không phải mặt đối lập của Tà Tu.',
190
+ tienCanh: 'Tiến cảnh',
191
+ lifetime: 'tích lũy', windowsL: 'kỳ báo cáo',
192
+ nghiepWarn: 'Nghiệp lực chưa hóa giải. Hãy ưu tiên xử lý các mục còn treo ở trên.',
193
+ suppressedProgression: 'Chính sách đang tạm dừng: không cộng Tu Vi, Công Đức hay các chỉ số tích cực; chỉ còn Nghiệp Lực.',
194
+ policyNotice: {
195
+ 'declared-stop': 'Tà Đạo không phải con đường tu luyện: không cảnh giới, không Tu Vi, không Công Đức và không cốt truyện. Dừng lại để con người rà soát hoặc xác nhận ủy quyền hợp pháp.',
196
+ 'needs-review': 'Phát hiện dấu hiệu chủ đích gây hại: chưa gán đạo, tạm dừng phần trò chơi và cần con người rà soát trước khi tiếp tục.',
197
+ 'authorization-required': 'Công việc Ma Đạo cần ủy quyền: ghi lại phạm vi công việc bằng `authorization=<slug>` trước khi mở khóa cảnh giới và tiến cảnh.'
198
+ },
199
+ suppressedRealmNotice: {
200
+ 'declared-stop': 'Không tính cảnh giới cho Tà Đạo. Dừng lại để rà soát hoặc xác nhận ủy quyền.',
201
+ 'needs-review': 'Không tính cảnh giới: dấu hiệu gây hại cần được con người rà soát.',
202
+ 'authorization-required': 'Không tính cảnh giới: công việc Ma Đạo chưa có ủy quyền được ghi nhận.'
203
+ },
204
+ lineageIntro: 'Ba đường phân loại dưới đây chỉ nói về dự án và nếp làm việc; không phải nhãn dán cho con người.',
205
+ progressionIntro: 'Tiến cảnh là chiếc gương nhỏ để quan sát thói quen, không phải thước đo giá trị của đạo hữu.',
206
+ problemIntro: 'Mỗi tâm ma dưới đây chỉ là tên gọi vui cho một mẫu quy trình có bằng chứng. Nhận diện để buông nhẹ, không phải để tự trách.',
207
+ detailSeparator: ': ',
208
+ reasonSeparator: '; ',
209
+ coverageSeparator: ', ',
210
+ knowledge: {
211
+ tamPhap: 'Tâm pháp', congPhap: 'Công pháp', biThuat: 'Bí thuật', thanThong: 'Thần thông',
212
+ phapBao: 'Pháp bảo', thuatPhap: 'Thuật pháp', daoDien: 'Bí tịch và đạo điển'
213
+ },
214
+ confidenceLevels: { high: 'cao', medium: 'vừa', low: 'thấp' },
215
+ dimensionLabels: {
216
+ delivery: 'hoàn thành', validation: 'kiểm chứng', clarity: 'rõ ràng', recovery: 'khôi phục', safety: 'an toàn', automation: 'tự động hóa'
217
+ },
218
+ roleLabels: { boss: 'đại địch', minor: 'địch thủ' },
219
+ problemLabels: {
220
+ 'repeated-failure': 'Lỗi lặp lại',
221
+ 'too-many-prompts': 'Quá nhiều lượt yêu cầu',
222
+ 'conflicting-instructions': 'Chỉ thị mâu thuẫn',
223
+ 'unrecovered-failure': 'Thất bại chưa khôi phục',
224
+ 'work-without-proof': 'Công việc thiếu bằng chứng'
225
+ }
226
+ },
227
+ en: {
228
+ title: 'Quiet Cultivation Chronicle',
229
+ neutralTitle: 'Coding Workflow Review',
230
+ opening: {
231
+ serene: 'Mist drifts past the mountain gate, and a quiet page of the Dao record opens. This is a mindful game of sorting collaboration patterns: breathe, observe, then choose one measured step.',
232
+ spirited: 'A jade bell sounds softly at the mountain gate; the Dao record turns a fresh page. We will meet the evidence with light wit and leave with one clear step forward.',
233
+ neutral: 'This record uses calm, direct language; cultivation theatrics are paused so safety and clarity come first.'
234
+ },
235
+ thienCo: 'Signs Beneath the Quiet Sky',
236
+ canhGioi: 'Realm at the Mountain Gate',
237
+ linhThach: 'Spirit Stones Spent (Tokens)',
238
+ tamMa: 'Heart Demons Along the Dao Path',
239
+ congPhap: 'One Technique for the Next Breakthrough',
240
+ baiHoc: "Today's Practice to Carry Forward",
241
+ phuongPhap: 'Dao Record & Evidence Sources',
242
+ closingTitle: 'Closing the Dao Record',
243
+ closing: {
244
+ clear: 'A quiet path is no excuse for a sleeping discipline. Keep the edits small and the proof honest; the validation bell is still hanging beneath the eaves, waiting for the next attempted shortcut.',
245
+ challenge: 'The flaw has left tracks clear enough that walking around it would only feed the enemy. Break one knot with real proof; beyond the gate, its sleeve has not yet vanished into the mist.',
246
+ paused: 'This record pauses here. Complete the required review or authorization first; no realm matters more than a sound safety boundary.'
247
+ },
248
+ sessions: 'sessions', prompts: 'prompts', commits: 'commits',
249
+ sessionOne: 'session', promptOne: 'prompt', commitOne: 'commit', windowOne: 'reporting window',
250
+ window: 'Window', confidence: 'Confidence',
251
+ reported: 'provider-reported (exact)', estimated: 'locally estimated', unknown: 'unknown turns',
252
+ dedup: 'streaming updates deduplicated',
253
+ score: 'Score', evidence: 'Evidence', counter: 'Counter-technique', quest: 'Micro-quest', victory: 'Victory',
254
+ help: 'Project help', ifthen: 'If—then', notEnough: 'Not enough evidence to compute a realm yet',
255
+ noProblems: 'No heart demon crossed the evidence threshold. Hold your Dao heart.',
256
+ method: 'Deterministic analysis v1; counts are confidence-scored candidates; lore never changes the numbers.',
257
+ repeatEv: (n) => `${n} repeats/retries within one task (candidate).`,
258
+ conflictEv: (n) => `${n} contradictory instruction pairs (candidate).`,
259
+ unrecEv: (n) => `${n} failures without a matching later pass.`,
260
+ proofEv: (n) => `${n} commits but no validation event.`,
261
+ daoSection: 'Dao & Lineage',
262
+ faction: 'Faction', affiliation: 'Affiliation', pathsL: 'Cultivation paths',
263
+ affNote: 'Affiliation is organizational status, not ethics — Tán Tu is not the opposite of Tà Tu.',
264
+ tienCanh: 'Progression',
265
+ lifetime: 'lifetime', windowsL: 'reporting windows',
266
+ nghiepWarn: 'Unresolved Nghiệp Lực — clear the open items above first.',
267
+ suppressedProgression: 'Policy is withholding gains: no Tu Vi, Công Đức, or positive metrics — only Nghiệp Lực (risk) remains.',
268
+ policyNotice: {
269
+ 'declared-stop': 'Tà Đạo is not a cultivation path: no realm, no Tu Vi/Công Đức, no lore. Stop and seek human review / lawful authorization.',
270
+ 'needs-review': 'Intent-to-harm signals detected: faction undetermined, gamification withheld, human review required before proceeding.',
271
+ 'authorization-required': 'Ma Đạo work requires authorization: record the engagement scope (authorization=<slug>) before any realm or progression is unlocked.'
272
+ },
273
+ suppressedRealmNotice: {
274
+ 'declared-stop': 'No realm is computed for Tà Đạo. Stop and seek review / authorization.',
275
+ 'needs-review': 'No realm computed: harmful-intent signals require human review.',
276
+ 'authorization-required': 'No realm computed: Ma Đạo work has no recorded authorization.'
277
+ },
278
+ lineageIntro: 'These three classifications describe the project and its working patterns; they are never labels for the person.',
279
+ progressionIntro: 'Progression is a small mirror for observing habits, not a measure of anyone\'s worth.',
280
+ problemIntro: 'Each heart demon below is a playful name for an evidence-backed workflow pattern—something to notice and release, never a reason for self-blame.',
281
+ detailSeparator: ' — ',
282
+ reasonSeparator: ' — ',
283
+ coverageSeparator: ' / ',
284
+ knowledge: {
285
+ tamPhap: 'Tâm Pháp', congPhap: 'Công Pháp', biThuat: 'Bí Thuật', thanThong: 'Thần Thông',
286
+ phapBao: 'Pháp Bảo', thuatPhap: 'Thuật Pháp', daoDien: 'Bí Tịch / Đạo Điển'
287
+ },
288
+ confidenceLevels: {},
289
+ dimensionLabels: {},
290
+ roleLabels: {},
291
+ problemLabels: {}
292
+ }
293
+ };
294
+
295
+ const pick = (obj, lang) => obj[lang] ?? obj.en ?? obj.vi;
296
+ const localized = (map, value) => map?.[String(value)] ?? value;
297
+ const joinDetail = (left, right, t) => `${left}${t.detailSeparator ?? ' — '}${right}`;
298
+ const joinReason = (left, right, t) => `${left}${t.reasonSeparator ?? ' — '}${right}`;
299
+
300
+ function bar(value) {
301
+ const filled = Math.round(value * 10);
302
+ return '█'.repeat(filled) + '░'.repeat(10 - filled);
303
+ }
304
+
305
+ function realmName(realm, lang) {
306
+ return lang === 'vi' ? realm.name : `${realm.name} — ${realm.gloss}`;
307
+ }
308
+
309
+ function missingReason(reason, lang) {
310
+ if (lang === 'vi' && reason === 'token usage coverage below 60%; supply provider usage metadata to compute a realm') {
311
+ return 'Độ phủ số liệu token dưới 60%; cần bổ sung dữ liệu sử dụng từ nhà cung cấp để tính cảnh giới.';
312
+ }
313
+ return reason;
314
+ }
315
+
316
+ function classificationLines(classification, lang, t) {
317
+ const c = classification;
318
+ const out = [`## ${t.daoSection}`, t.lineageIntro];
319
+ out.push(`- ${t.faction}: ${joinDetail(`**${c.faction.name}**`, pick(c.faction.gloss, lang), t)} (${t.confidence} ${localized(t.confidenceLevels, c.faction.confidence)})`);
320
+ for (const line of pick(c.explanation, lang)) out.push(` - ${line}`);
321
+ if (c.policy.state !== 'clear') out.push(` - ${t.policyNotice[c.policy.state]}`);
322
+ out.push(`- ${t.affiliation}: ${joinDetail(`**${c.affiliation.name}**`, pick(c.affiliation.gloss, lang), t)} (${t.confidence} ${localized(t.confidenceLevels, c.affiliation.confidence)}; ${localizeRationale(c.affiliation.rationale, lang)})`);
323
+ out.push(` - ${t.affNote}`);
324
+ if (c.paths.length) {
325
+ out.push(`- ${t.pathsL}: ${c.paths.map((p) => `**${p.name}** (${localized(t.confidenceLevels, p.confidence)})`).join(', ')}`);
326
+ for (const p of c.paths) out.push(` - ${p.name}: ${joinReason(pick(p.gloss, lang), localizeRationale(p.rationale, lang), t)}`);
327
+ }
328
+ // Knowledge recommendations only under a clear policy.
329
+ const k = c.knowledge;
330
+ if (k) {
331
+ out.push(`- ${t.knowledge.tamPhap}: ${pick(k.tamPhap, lang)}`);
332
+ out.push(`- ${t.knowledge.congPhap}: ${pick(k.congPhap, lang)}`);
333
+ out.push(`- ${t.knowledge.biThuat}: ${pick(k.biThuat, lang)}`);
334
+ out.push(`- ${t.knowledge.thanThong}: ${pick(k.thanThong, lang)}`);
335
+ if (k.phapBao.length) out.push(`- ${t.knowledge.phapBao}: ${k.phapBao.map((s) => `\`${s}\``).join(', ')}`);
336
+ out.push(`- ${t.knowledge.thuatPhap}: ${pick(k.thuatPhap, lang)}`);
337
+ out.push(`- ${t.knowledge.daoDien}: ${pick(k.daoDien, lang)}`);
338
+ }
339
+ return out;
340
+ }
341
+
342
+ function cultivationLines(cultivation, lang, t) {
343
+ const out = classificationLines(cultivation.classification, lang, t);
344
+ const pr = cultivation.progression;
345
+ const gamified = cultivation.classification.policy.canGamify;
346
+ out.push('', `## ${t.tienCanh}`, t.progressionIntro);
347
+ if (gamified) {
348
+ out.push(`- ${PROGRESSION.tuVi.name}: ${joinDetail(`+${pr.tuVi.window} (${t.lifetime} ${pr.tuVi.lifetime})`, pick(PROGRESSION.tuVi.gloss, lang), t)}`);
349
+ const windowUnit = pr.daoHanh.windows === 1 ? t.windowOne : t.windowsL;
350
+ out.push(`- ${PROGRESSION.daoHanh.name}: ${joinDetail(`${pr.daoHanh.windows} ${windowUnit}`, pick(PROGRESSION.daoHanh.gloss, lang), t)}`);
351
+ out.push(`- ${joinDetail(`${PROGRESSION.ngoTinh.name} \`${bar(pr.ngoTinh)}\` ${Math.round(pr.ngoTinh * 100)}%`, pick(PROGRESSION.ngoTinh.gloss, lang), t)}`);
352
+ out.push(`- ${joinDetail(`${PROGRESSION.doThuanThuc.name} \`${bar(pr.doThuanThuc.overall)}\` ${Math.round(pr.doThuanThuc.overall * 100)}%`, pick(pr.doThuanThuc.note, lang), t)}`);
353
+ out.push(`- ${joinDetail(`${PROGRESSION.tamCanh.name} \`${bar(pr.tamCanh)}\` ${Math.round(pr.tamCanh * 100)}%`, pick(PROGRESSION.tamCanh.gloss, lang), t)}`);
354
+ out.push(`- ${PROGRESSION.congDuc.name}: ${joinDetail(`+${pr.congDuc.window} (${t.lifetime} ${pr.congDuc.lifetime})`, pick(PROGRESSION.congDuc.gloss, lang), t)}`);
355
+ } else {
356
+ // Suppressed policy: only Nghiệp Lực (risk) survives; no positive gains.
357
+ out.push(`- ${t.suppressedProgression}`);
358
+ }
359
+ out.push(`- ${PROGRESSION.nghiepLuc.name}: ${joinDetail(`${pr.nghiepLuc.window} (${t.lifetime} ${pr.nghiepLuc.lifetime})`, pick(PROGRESSION.nghiepLuc.gloss, lang), t)}`);
360
+ if (pr.nghiepLuc.window > 0) out.push(` - ${t.nghiepWarn}`);
361
+ return out;
362
+ }
363
+
364
+ // Standalone classification markdown for the `classify` runner action (no
365
+ // history analysis involved — profile only).
366
+ export function renderClassificationMarkdown(classification, language = 'en') {
367
+ const lang = L[language] ? language : 'en';
368
+ return classificationLines(classification, lang, L[lang]).join('\n');
369
+ }
370
+
371
+ function problemEvidenceLine(p, t) {
372
+ const e = p.evidence;
373
+ switch (p.problemType) {
374
+ case 'repeated-failure':
375
+ case 'too-many-prompts': return t.repeatEv(e.repeatCount);
376
+ case 'conflicting-instructions': return t.conflictEv(e.count);
377
+ case 'unrecovered-failure': return t.unrecEv(e.count);
378
+ case 'work-without-proof': return t.proofEv(e.commits);
379
+ default: return '';
380
+ }
381
+ }
382
+
383
+ export function renderMarkdown(model, language = 'en') {
384
+ const lang = L[language] ? language : 'en';
385
+ const t = L[lang];
386
+ const cov = model.coverage;
387
+ const tok = model.tokens;
388
+ const out = [];
389
+
390
+ const title = model.suppressGamification ? t.neutralTitle : t.title;
391
+ out.push(`# ${title}`, '', `> ${t.opening[model.tone] ?? t.opening.serene}`, '');
392
+
393
+ out.push(`## ${t.thienCo}`);
394
+ const sessionUnit = cov.sessions === 1 ? t.sessionOne : t.sessions;
395
+ const promptUnit = cov.userPrompts === 1 ? t.promptOne : t.prompts;
396
+ const commitUnit = cov.commits === 1 ? t.commitOne : t.commits;
397
+ out.push(`- ${cov.sessions} ${sessionUnit}${t.coverageSeparator}${cov.userPrompts} ${promptUnit}${t.coverageSeparator}${cov.commits} ${commitUnit}`);
398
+ out.push(`- ${t.window}: ${cov.window.start ?? '—'} … ${cov.window.end ?? '—'}`);
399
+ out.push(`- ${t.reported}: ${Math.round(cov.reportedTurnsPct * 100)}% · ${t.estimated}: ${Math.round(cov.estimatedTurnsPct * 100)}%`);
400
+ out.push(`- ${t.confidence}: ${localized(t.confidenceLevels, cov.confidence)}`, '');
401
+
402
+ out.push(`## ${t.canhGioi}`);
403
+ if (model.suppressGamification) {
404
+ out.push(t.suppressedRealmNotice[model.policyState] ?? t.suppressedRealmNotice['declared-stop']);
405
+ } else if (!model.enoughEvidence) {
406
+ out.push(`**${realmName(model.realm, lang)}** — ${t.notEnough}.`);
407
+ for (const m of model.missing) out.push(`- ${missingReason(m, lang)}`);
408
+ } else {
409
+ out.push(`**${realmName(model.realm, lang)}**${model.showScore ? ` · ${t.score}: ${model.score}/100` : ''}`);
410
+ for (const d of model.dimensions) {
411
+ out.push(`- ${localized(t.dimensionLabels, d.key)} \`${bar(d.value)}\` ${Math.round(d.value * 100)}% (${Math.round(d.weight * 100)}%)`);
412
+ }
413
+ }
414
+ out.push('');
415
+
416
+ if (model.cultivation) {
417
+ out.push(...cultivationLines(model.cultivation, lang, t), '');
418
+ }
419
+
420
+ out.push(`## ${t.linhThach}`);
421
+ out.push(`- ${t.reported}: ${tok.reportedTotal}`);
422
+ out.push(`- ${t.estimated}: ${tok.estimatedTotal}`);
423
+ out.push(`- ${t.unknown}: ${tok.unknownTurns}`);
424
+ if (tok.dedupedStreamUpdates > 0) out.push(`- ${t.dedup}: ${tok.dedupedStreamUpdates}`);
425
+ out.push('');
426
+
427
+ if (!model.suppressGamification) {
428
+ out.push(`## ${t.tamMa}`, t.problemIntro);
429
+ if (model.problems.length === 0) {
430
+ out.push(t.noProblems);
431
+ } else {
432
+ for (const p of model.problems) {
433
+ if (p.villain) {
434
+ const label = lang === 'vi' ? p.villain.name : `${p.villain.name} — ${p.villain.gloss}`;
435
+ out.push(`### ${label} (${localized(t.roleLabels, p.villain.role)}, ${t.confidence} ${p.confidence})`);
436
+ out.push(`> ${pick(p.villain.challenge, lang)}`);
437
+ } else {
438
+ out.push(`### ${localized(t.problemLabels, p.problemType)} (${t.confidence} ${p.confidence})`);
439
+ }
440
+ // A challenge is always immediately followed by evidence + counter + victory.
441
+ out.push(`- ${t.evidence}: ${problemEvidenceLine(p, t)}`);
442
+ out.push(`- ${t.counter}: ${pick(p.counterTechnique, lang)}`);
443
+ if (p.projectHelp) out.push(`- ${t.help}: \`${p.projectHelp}\``);
444
+ out.push(`- ${t.quest}: ${pick(p.microQuest, lang)}`);
445
+ out.push(`- ${t.victory}: ${pick(p.victory, lang)}`);
446
+ }
447
+ }
448
+ out.push('');
449
+
450
+ out.push(`## ${t.congPhap}`);
451
+ out.push(`- **${t.ifthen}:** ${pick(model.ifThen, lang)}`, '');
452
+
453
+ out.push(`## ${t.baiHoc}`);
454
+ out.push(`- ${model.lesson.skill ? joinDetail(`\`${model.lesson.skill}\``, pick(model.lesson.technique, lang), t) : pick(model.lesson.technique, lang)}`, '');
455
+ }
456
+
457
+ out.push(`## ${t.phuongPhap}`);
458
+ out.push(`- ${t.method}`, '');
459
+
460
+ out.push(`## ${t.closingTitle}`);
461
+ const closingKey = model.suppressGamification ? 'paused' : (model.problems.length ? 'challenge' : 'clear');
462
+ out.push(t.closing[closingKey]);
463
+
464
+ return out.join('\n');
465
+ }
466
+
467
+ export function renderReport(analysis, options = {}) {
468
+ const model = buildReportModel(analysis, options);
469
+ const language = resolveLanguage(options.language, options);
470
+ return { model, language, markdown: renderMarkdown(model, language) };
471
+ }
472
+
473
+ // language=auto -> detect from the invocation text; explicit code overrides;
474
+ // fallback to conversation language then English. V1 templates: vi, en.
475
+ export function resolveLanguage(requested, { invocationText = '', conversationLanguage = 'en' } = {}) {
476
+ if (requested && requested !== 'auto') return L[requested] ? requested : 'en';
477
+ if (/[À-ỹ]/.test(invocationText) || /\b(hãy|giúp|của|và|không)\b/i.test(invocationText)) return 'vi';
478
+ return L[conversationLanguage] ? conversationLanguage : 'en';
479
+ }
@@ -0,0 +1,103 @@
1
+ // Compact, aggregate-only handoff between the deterministic analyzer and the
2
+ // agent-authored response. It carries facts and constraints, never stock prose.
3
+
4
+ const localizeMissing = (reason, language) => {
5
+ if (language !== 'vi') return reason;
6
+ if (reason === 'token usage coverage below 60%; supply provider usage metadata to compute a realm') {
7
+ return 'Độ phủ số liệu token dưới 60%; cần bổ sung dữ liệu sử dụng từ nhà cung cấp để tính cảnh giới.';
8
+ }
9
+ return reason;
10
+ };
11
+
12
+ function localized(value, language) {
13
+ if (!value || typeof value !== 'object') return value ?? null;
14
+ return value[language] ?? value.en ?? value.vi ?? null;
15
+ }
16
+
17
+ function problemEvidence(problem) {
18
+ const evidence = problem.evidence ?? {};
19
+ if (problem.problemType === 'repeated-failure' || problem.problemType === 'too-many-prompts') return { repeatCount: evidence.repeatCount ?? 0 };
20
+ if (problem.problemType === 'conflicting-instructions') return { count: evidence.count ?? 0 };
21
+ if (problem.problemType === 'unrecovered-failure') return { count: evidence.count ?? 0 };
22
+ if (problem.problemType === 'work-without-proof') return { commits: evidence.commits ?? 0 };
23
+ return {};
24
+ }
25
+
26
+ export function buildResponseBrief(model, options = {}) {
27
+ const language = options.language === 'vi' ? 'vi' : 'en';
28
+ const profile = options.profile ?? {};
29
+ const classification = model.cultivation?.classification ?? null;
30
+ const progression = model.cultivation?.progression ?? null;
31
+ return {
32
+ schema: 'tutien-response-brief-v1',
33
+ semanticNamespace: model.experience.semanticNamespace,
34
+ language,
35
+ tone: model.tone,
36
+ policy: {
37
+ state: model.policyState,
38
+ suppressGamification: model.suppressGamification
39
+ },
40
+ project: {
41
+ id: profile.projectId ?? 'repo',
42
+ type: profile.projectType ?? null,
43
+ primaryLanguage: profile.primaryLanguage ?? null,
44
+ domains: profile.domains ?? [],
45
+ stack: profile.stack ?? [],
46
+ packageManager: profile.packageManager ?? null,
47
+ validationCommands: profile.validationCommands ?? [],
48
+ recommendedValidation: profile.recommendedValidation ?? null,
49
+ metadataSources: profile.metadataSources ?? [],
50
+ kitInstalled: profile.kitInstalled === true
51
+ },
52
+ evidence: {
53
+ coverage: model.coverage,
54
+ tokens: model.tokens,
55
+ realm: model.realm ? { name: model.realm.name, score: model.score } : null,
56
+ enoughEvidence: model.enoughEvidence,
57
+ missing: (model.missing ?? []).map((reason) => localizeMissing(reason, language)),
58
+ dimensions: model.dimensions
59
+ },
60
+ cultivation: classification ? {
61
+ faction: classification.faction?.id ?? null,
62
+ affiliation: classification.affiliation?.id ?? null,
63
+ paths: (classification.paths ?? []).map((item) => item.id),
64
+ progression: progression ? {
65
+ tuVi: progression.tuVi?.window ?? 0,
66
+ congDuc: progression.congDuc?.window ?? 0,
67
+ nghiepLuc: progression.nghiepLuc?.window ?? 0,
68
+ daoHanhWindows: progression.daoHanh?.windows ?? 0
69
+ } : null
70
+ } : null,
71
+ findings: (model.problems ?? []).map((problem) => ({
72
+ type: problem.problemType,
73
+ confidence: problem.confidence,
74
+ priority: problem.priority,
75
+ evidence: problemEvidence(problem),
76
+ counterTechnique: localized(problem.counterTechnique, language),
77
+ projectHelp: problem.projectHelp ?? null,
78
+ microQuest: localized(problem.microQuest, language),
79
+ victory: localized(problem.victory, language)
80
+ })),
81
+ nextPractice: model.ifThen ? localized(model.ifThen, language) : null,
82
+ story: options.storyContext ? {
83
+ evidenceKey: options.storyContext.evidenceKey,
84
+ language: options.storyContext.language,
85
+ style: options.storyContext.style,
86
+ focus: options.storyContext.focus,
87
+ canWriteChapter: options.storyContext.canWriteChapter
88
+ } : null,
89
+ artifacts: { evidenceLedger: options.ledgerPath ?? null },
90
+ composition: {
91
+ finalResponse: 'agent-authored',
92
+ evidenceLedgerIsNotFinalResponse: true,
93
+ deriveFrom: ['current-user-request', 'project-facts', 'approved-evidence', 'living-chronicle', 'recent-response-rhythm'],
94
+ preserve: model.composition.preserve,
95
+ vary: model.composition.vary,
96
+ prohibitions: ['stock-heading-sequence', 'stock-opening', 'stock-closing', 'raw-prompt-text', 'raw-event-ids'],
97
+ vietnameseEndingIntent: language === 'vi'
98
+ ? 'Kết cà khịa, luôn giáo huấn, không tâng bốc và kéo dài dư âm vai diễn.'
99
+ : null
100
+ }
101
+ };
102
+ }
103
+