minimal-vibe-coding-kit 0.4.1 → 0.5.0

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 (207) hide show
  1. package/.agents/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +28 -3
  2. package/.agents/skills/claim/SKILL.md +82 -0
  3. package/.agents/skills/prompt-sharpener/SKILL.md +54 -0
  4. package/.agents/skills/tutien/SKILL.md +74 -0
  5. package/.agents/skills/tutien/references/classification.md +64 -0
  6. package/.agents/skills/tutien/references/lore-sources.md +15 -0
  7. package/.agents/skills/tutien/references/privacy.md +33 -0
  8. package/.agents/skills/tutien/references/schema.md +75 -0
  9. package/.agents/skills/tutien/references/scoring-and-realms.md +37 -0
  10. package/.agents/skills/tutien/references/voice-and-mode.md +44 -0
  11. package/.agents/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  12. package/.agents/skills/tutien/scripts/adapters/git.mjs +48 -0
  13. package/.agents/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  14. package/.agents/skills/tutien/scripts/analyze-history.mjs +84 -0
  15. package/.agents/skills/tutien/scripts/catalog.mjs +130 -0
  16. package/.agents/skills/tutien/scripts/classify.mjs +311 -0
  17. package/.agents/skills/tutien/scripts/command.mjs +107 -0
  18. package/.agents/skills/tutien/scripts/compare.mjs +41 -0
  19. package/.agents/skills/tutien/scripts/metrics.mjs +306 -0
  20. package/.agents/skills/tutien/scripts/normalize-events.mjs +68 -0
  21. package/.agents/skills/tutien/scripts/redact.mjs +30 -0
  22. package/.agents/skills/tutien/scripts/render-report.mjs +423 -0
  23. package/.agents/skills/tutien/scripts/run-tutien.mjs +272 -0
  24. package/.agents/skills/tutien/scripts/score.mjs +97 -0
  25. package/.agents/skills/tutien/scripts/snapshot.mjs +76 -0
  26. package/.agents/skills/tutien/scripts/villains.mjs +196 -0
  27. package/.agents/skills/vibekit-init/SKILL.md +2 -2
  28. package/.claude/settings.json +10 -3
  29. package/.claude/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +28 -3
  30. package/.claude/skills/claim/SKILL.md +82 -0
  31. package/.claude/skills/prompt-sharpener/SKILL.md +54 -0
  32. package/.claude/skills/tutien/SKILL.md +74 -0
  33. package/.claude/skills/tutien/references/classification.md +64 -0
  34. package/.claude/skills/tutien/references/lore-sources.md +15 -0
  35. package/.claude/skills/tutien/references/privacy.md +33 -0
  36. package/.claude/skills/tutien/references/schema.md +75 -0
  37. package/.claude/skills/tutien/references/scoring-and-realms.md +37 -0
  38. package/.claude/skills/tutien/references/voice-and-mode.md +44 -0
  39. package/.claude/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  40. package/.claude/skills/tutien/scripts/adapters/git.mjs +48 -0
  41. package/.claude/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  42. package/.claude/skills/tutien/scripts/analyze-history.mjs +84 -0
  43. package/.claude/skills/tutien/scripts/catalog.mjs +130 -0
  44. package/.claude/skills/tutien/scripts/classify.mjs +311 -0
  45. package/.claude/skills/tutien/scripts/command.mjs +107 -0
  46. package/.claude/skills/tutien/scripts/compare.mjs +41 -0
  47. package/.claude/skills/tutien/scripts/metrics.mjs +306 -0
  48. package/.claude/skills/tutien/scripts/normalize-events.mjs +68 -0
  49. package/.claude/skills/tutien/scripts/redact.mjs +30 -0
  50. package/.claude/skills/tutien/scripts/render-report.mjs +423 -0
  51. package/.claude/skills/tutien/scripts/run-tutien.mjs +272 -0
  52. package/.claude/skills/tutien/scripts/score.mjs +97 -0
  53. package/.claude/skills/tutien/scripts/snapshot.mjs +76 -0
  54. package/.claude/skills/tutien/scripts/villains.mjs +196 -0
  55. package/.claude/skills/vibekit-init/SKILL.md +2 -2
  56. package/.codex/rules/vibekit.rules +77 -0
  57. package/.codex-plugin/plugin.json +1 -1
  58. package/.cursor/cli.json +18 -0
  59. package/.cursor/settings.json +15 -8
  60. package/.cursor/skills/claim/SKILL.md +82 -0
  61. package/.cursor/skills/prompt-sharpener/SKILL.md +54 -0
  62. package/.cursor/skills/tutien/SKILL.md +74 -0
  63. package/.cursor/skills/tutien/references/classification.md +64 -0
  64. package/.cursor/skills/tutien/references/lore-sources.md +15 -0
  65. package/.cursor/skills/tutien/references/privacy.md +33 -0
  66. package/.cursor/skills/tutien/references/schema.md +75 -0
  67. package/.cursor/skills/tutien/references/scoring-and-realms.md +37 -0
  68. package/.cursor/skills/tutien/references/voice-and-mode.md +44 -0
  69. package/.cursor/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  70. package/.cursor/skills/tutien/scripts/adapters/git.mjs +48 -0
  71. package/.cursor/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  72. package/.cursor/skills/tutien/scripts/analyze-history.mjs +84 -0
  73. package/.cursor/skills/tutien/scripts/catalog.mjs +130 -0
  74. package/.cursor/skills/tutien/scripts/classify.mjs +311 -0
  75. package/.cursor/skills/tutien/scripts/command.mjs +107 -0
  76. package/.cursor/skills/tutien/scripts/compare.mjs +41 -0
  77. package/.cursor/skills/tutien/scripts/metrics.mjs +306 -0
  78. package/.cursor/skills/tutien/scripts/normalize-events.mjs +68 -0
  79. package/.cursor/skills/tutien/scripts/redact.mjs +30 -0
  80. package/.cursor/skills/tutien/scripts/render-report.mjs +423 -0
  81. package/.cursor/skills/tutien/scripts/run-tutien.mjs +272 -0
  82. package/.cursor/skills/tutien/scripts/score.mjs +97 -0
  83. package/.cursor/skills/tutien/scripts/snapshot.mjs +76 -0
  84. package/.cursor/skills/tutien/scripts/villains.mjs +196 -0
  85. package/.grok/README.md +13 -0
  86. package/.grok/config.example.toml +13 -0
  87. package/.grok/config.toml +29 -0
  88. package/.grok/rules/safe-delete.md +11 -0
  89. package/.grok/rules/security.md +5 -0
  90. package/.grok/rules/vibe-core.md +8 -0
  91. package/.grok/skills/agentshield-security-review/SKILL.md +75 -0
  92. package/.grok/skills/agentshield-security-review/agents/openai.yaml +5 -0
  93. package/.grok/skills/agentshield-security-review/references/native-install.md +97 -0
  94. package/.grok/skills/agentshield-security-review/references/report-template.md +54 -0
  95. package/.grok/skills/agentshield-security-review/references/review-checklist.md +45 -0
  96. package/.grok/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +135 -0
  97. package/.grok/skills/autoresearch-coding/README.md +15 -0
  98. package/.grok/skills/autoresearch-coding/SKILL.md +101 -0
  99. package/.grok/skills/autoresearch-coding/references/experiment-contract.md +13 -0
  100. package/.grok/skills/autoresearch-coding/references/metric-extraction.md +8 -0
  101. package/.grok/skills/autoresearch-coding/references/result-ledger.md +15 -0
  102. package/.grok/skills/autoresearch-coding/scripts/log_result.py +24 -0
  103. package/.grok/skills/autoresearch-coding/scripts/run_logged.py +30 -0
  104. package/.grok/skills/claim/SKILL.md +82 -0
  105. package/.grok/skills/clearthought/SKILL.md +100 -0
  106. package/.grok/skills/clearthought/examples/decision-framework.md +441 -0
  107. package/.grok/skills/clearthought/examples/metagame-examples.md +536 -0
  108. package/.grok/skills/clearthought/examples/sequential-thinking.md +380 -0
  109. package/.grok/skills/clearthought/references/output-schemas.md +494 -0
  110. package/.grok/skills/clearthought/references/parameter-reference.md +482 -0
  111. package/.grok/skills/coding-level/SKILL.md +34 -0
  112. package/.grok/skills/coding-level/references/level-0.md +131 -0
  113. package/.grok/skills/coding-level/references/level-1.md +118 -0
  114. package/.grok/skills/coding-level/references/level-2.md +140 -0
  115. package/.grok/skills/coding-level/references/level-3.md +142 -0
  116. package/.grok/skills/coding-level/references/level-4.md +152 -0
  117. package/.grok/skills/coding-level/references/level-5.md +84 -0
  118. package/.grok/skills/daily-workflow-curator/SKILL.md +52 -0
  119. package/.grok/skills/memento/SKILL.md +36 -0
  120. package/.grok/skills/parallel-analysis/SKILL.md +160 -0
  121. package/.grok/skills/path-sensitive-shell-safety/SKILL.md +69 -0
  122. package/.grok/skills/path-sensitive-shell-safety/references/workflow.md +127 -0
  123. package/.grok/skills/prompt-sharpener/SKILL.md +54 -0
  124. package/.grok/skills/reviewing-4p-priorities/SKILL.md +72 -0
  125. package/.grok/skills/reviewing-4p-priorities/examples.md +298 -0
  126. package/.grok/skills/sequential-thinking/SKILL.md +106 -0
  127. package/.grok/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
  128. package/.grok/skills/sequential-thinking/examples/branching-exploration.md +474 -0
  129. package/.grok/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
  130. package/.grok/skills/sequential-thinking/examples/revision-pattern.md +304 -0
  131. package/.grok/skills/sequential-thinking/references/output-schema.md +483 -0
  132. package/.grok/skills/sequential-thinking/references/parameters.md +463 -0
  133. package/.grok/skills/sequential-thinking/references/patterns.md +666 -0
  134. package/.grok/skills/tutien/SKILL.md +74 -0
  135. package/.grok/skills/tutien/references/classification.md +64 -0
  136. package/.grok/skills/tutien/references/lore-sources.md +15 -0
  137. package/.grok/skills/tutien/references/privacy.md +33 -0
  138. package/.grok/skills/tutien/references/schema.md +75 -0
  139. package/.grok/skills/tutien/references/scoring-and-realms.md +37 -0
  140. package/.grok/skills/tutien/references/voice-and-mode.md +44 -0
  141. package/.grok/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  142. package/.grok/skills/tutien/scripts/adapters/git.mjs +48 -0
  143. package/.grok/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  144. package/.grok/skills/tutien/scripts/analyze-history.mjs +84 -0
  145. package/.grok/skills/tutien/scripts/catalog.mjs +130 -0
  146. package/.grok/skills/tutien/scripts/classify.mjs +311 -0
  147. package/.grok/skills/tutien/scripts/command.mjs +107 -0
  148. package/.grok/skills/tutien/scripts/compare.mjs +41 -0
  149. package/.grok/skills/tutien/scripts/metrics.mjs +306 -0
  150. package/.grok/skills/tutien/scripts/normalize-events.mjs +68 -0
  151. package/.grok/skills/tutien/scripts/redact.mjs +30 -0
  152. package/.grok/skills/tutien/scripts/render-report.mjs +423 -0
  153. package/.grok/skills/tutien/scripts/run-tutien.mjs +272 -0
  154. package/.grok/skills/tutien/scripts/score.mjs +97 -0
  155. package/.grok/skills/tutien/scripts/snapshot.mjs +76 -0
  156. package/.grok/skills/tutien/scripts/villains.mjs +196 -0
  157. package/.grok/skills/vibekit-init/SKILL.md +52 -0
  158. package/.grok/skills/visual-design-loop/SKILL.md +103 -0
  159. package/.grok/skills/visual-design-loop/agents/openai.yaml +4 -0
  160. package/.vibekit/docs/BACKBONE_REFERENCE.md +3 -1
  161. package/.vibekit/docs/INSTALL.md +11 -7
  162. package/.vibekit/docs/SECURITY_MODEL.md +1 -1
  163. package/.vibekit/docs/TOOLING_GUIDE.md +5 -0
  164. package/.vibekit/docs/templates/CONTEXT_TEMPLATE.md +1 -1
  165. package/.vibekit/init/CLAUDE-template.md +3 -0
  166. package/.vibekit/init/FIRST_PROMPT.md +1 -1
  167. package/.vibekit/init/FIRST_TIME_INIT.md +2 -2
  168. package/.vibekit/scripts/doctor.mjs +11 -4
  169. package/.vibekit/scripts/mvck.mjs +27 -12
  170. package/.vibekit/scripts/pack-dry-run.mjs +38 -3
  171. package/.vibekit/scripts/test-install.mjs +8 -0
  172. package/.vibekit/scripts/validate-kit.mjs +116 -41
  173. package/.vibekit/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +28 -3
  174. package/.vibekit/skills/claim/SKILL.md +82 -0
  175. package/.vibekit/skills/prompt-sharpener/SKILL.md +54 -0
  176. package/.vibekit/skills/skills-manifest.json +27 -0
  177. package/.vibekit/skills/tutien/SKILL.md +74 -0
  178. package/.vibekit/skills/tutien/references/classification.md +64 -0
  179. package/.vibekit/skills/tutien/references/lore-sources.md +15 -0
  180. package/.vibekit/skills/tutien/references/privacy.md +33 -0
  181. package/.vibekit/skills/tutien/references/schema.md +75 -0
  182. package/.vibekit/skills/tutien/references/scoring-and-realms.md +37 -0
  183. package/.vibekit/skills/tutien/references/voice-and-mode.md +44 -0
  184. package/.vibekit/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
  185. package/.vibekit/skills/tutien/scripts/adapters/git.mjs +48 -0
  186. package/.vibekit/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
  187. package/.vibekit/skills/tutien/scripts/analyze-history.mjs +84 -0
  188. package/.vibekit/skills/tutien/scripts/catalog.mjs +130 -0
  189. package/.vibekit/skills/tutien/scripts/classify.mjs +311 -0
  190. package/.vibekit/skills/tutien/scripts/command.mjs +107 -0
  191. package/.vibekit/skills/tutien/scripts/compare.mjs +41 -0
  192. package/.vibekit/skills/tutien/scripts/metrics.mjs +306 -0
  193. package/.vibekit/skills/tutien/scripts/normalize-events.mjs +68 -0
  194. package/.vibekit/skills/tutien/scripts/redact.mjs +30 -0
  195. package/.vibekit/skills/tutien/scripts/render-report.mjs +423 -0
  196. package/.vibekit/skills/tutien/scripts/run-tutien.mjs +272 -0
  197. package/.vibekit/skills/tutien/scripts/score.mjs +97 -0
  198. package/.vibekit/skills/tutien/scripts/snapshot.mjs +76 -0
  199. package/.vibekit/skills/tutien/scripts/villains.mjs +196 -0
  200. package/.vibekit/skills/vibekit-init/SKILL.md +2 -2
  201. package/AGENTS.md +2 -2
  202. package/CHANGELOG.md +40 -0
  203. package/README.md +60 -51
  204. package/backbone.yml +2 -1
  205. package/docs/README.vi.md +61 -52
  206. package/docs/README.zh-CN.md +277 -0
  207. package/package.json +15 -4
@@ -0,0 +1,311 @@
1
+ // Tu Tiên project-classification and progression system. Three orthogonal
2
+ // axes plus a knowledge taxonomy and seven progression metrics:
3
+ // - Dao faction = the PROJECT's ethical/risk posture;
4
+ // - affiliation = the cultivator's ORGANIZATIONAL mode (never ethics:
5
+ // Tán Tu is independence, not the opposite of Tà Tu);
6
+ // - paths = the project's TECHNICAL nature (multi-select).
7
+ //
8
+ // One fail-closed POLICY STATE governs the whole report. Only `clear` may
9
+ // enable lore, villains, recommendations, score, realm, and positive
10
+ // progression:
11
+ // - declared-stop : user declared Tà Đạo / Tà Tu — a stop signal;
12
+ // - needs-review : intent-to-harm signals — no Dao assignment,
13
+ // no gamification, ask for human review;
14
+ // - authorization-required : Ma Đạo without a valid authorization slug;
15
+ // - clear : lawful constructive work.
16
+ // The engine NEVER auto-assigns Tà Đạo / Tà Tu (intent to harm cannot be
17
+ // judged lexically; difficulty is not evil), and never labels harmful text
18
+ // "righteous" — harm produces an undetermined faction and needs-review.
19
+
20
+ import crypto from 'node:crypto';
21
+
22
+ const clamp01 = (x) => Math.max(0, Math.min(1, x));
23
+ const round3 = (x) => Math.round(x * 1000) / 1000;
24
+ const sha16 = (s) => crypto.createHash('sha256').update(s).digest('hex').slice(0, 16);
25
+
26
+ export const FACTIONS = {
27
+ 'chinh-dao': { name: 'Chính Đạo', gloss: { vi: 'chính phái — hợp pháp, minh bạch, tạo giá trị cho người dùng', en: 'Righteous Dao — lawful, transparent, constructive, user-beneficial' }, rank: 'normal' },
28
+ 'bang-mon': { name: 'Bàng Môn / Kỳ Đạo', gloss: { vi: 'phi truyền thống — thử nghiệm, sáng tạo, chuyên biệt, không gây hại', en: 'Heterodox Dao — unconventional, experimental, creative, specialized; not harmful' }, rank: 'normal' },
29
+ 'ma-dao': { name: 'Ma Đạo', gloss: { vi: 'kỹ thuật rủi ro cao / đối kháng — cần ủy quyền và giám sát rõ ràng', en: 'Demonic Dao — high-risk, adversarial, restricted technical work requiring explicit authorization and oversight' }, rank: 'restricted' },
30
+ 'ta-dao': { name: 'Tà Đạo', gloss: { vi: 'cố ý gây hại, bóc lột, phi pháp — không bao giờ là con đường tu luyện', en: 'Evil Dao — intentionally harmful, exploitative, unlawful — never a progression path' }, rank: 'forbidden' }
31
+ };
32
+ // Not a real faction — the result of refusing to classify harmful intent.
33
+ const UNDETERMINED_FACTION = { id: 'undetermined', name: 'Chưa phân định', gloss: { vi: 'chưa gán đạo — cần review con người', en: 'undetermined — needs human review' }, rank: 'review' };
34
+
35
+ export const AFFILIATIONS = {
36
+ 'tong-mon': { name: 'Tông Môn Đệ Tử', gloss: { vi: 'làm việc trong đội nhóm / tổ chức / quy trình có sẵn', en: 'works within a team, organization, or established process' } },
37
+ 'tan-tu': { name: 'Tán Tu', gloss: { vi: 'tu luyện độc lập, không thuộc tổ chức cố định', en: 'works independently, outside a fixed organization or tradition' } },
38
+ 'khach-khanh': { name: 'Khách Khanh', gloss: { vi: 'chuyên gia bên ngoài / cộng tác tạm thời', en: 'external expert or temporary contributor' } },
39
+ 'an-tu': { name: 'Ẩn Tu', gloss: { vi: 'nghiên cứu riêng tư, dài hạn, chuyên sâu', en: 'private, long-term, deeply focused research' } }
40
+ };
41
+
42
+ export const PATHS = {
43
+ kiem: { name: 'Kiếm Tu', gloss: { vi: 'triển khai phần mềm trực tiếp, giải quyết vấn đề nhanh', en: 'direct implementation and fast problem-solving' } },
44
+ tran: { name: 'Trận Tu', gloss: { vi: 'kiến trúc, DevOps, hạ tầng, mạng, điều phối hệ thống', en: 'architecture, DevOps, infrastructure, networking, orchestration' } },
45
+ phu: { name: 'Phù Tu', gloss: { vi: 'prompt, script, tự động hóa, workflow lặp lại được', en: 'prompts, scripts, automation, repeatable workflows' } },
46
+ khi: { name: 'Khí Tu', gloss: { vi: 'công cụ, thư viện, thành phần tái sử dụng, nền tảng', en: 'tools, libraries, reusable components, platform engineering' } },
47
+ dan: { name: 'Đan Tu', gloss: { vi: 'xử lý dữ liệu, tối ưu, biến đổi, tổng hợp tài nguyên', en: 'data processing, optimization, transformation, resource synthesis' } },
48
+ y: { name: 'Y Tu', gloss: { vi: 'debug, bảo trì, khắc phục sự cố, hỗ trợ kỹ thuật', en: 'debugging, maintenance, incident recovery, support' } },
49
+ huyen: { name: 'Huyễn Tu', gloss: { vi: 'UI/UX, animation, thiết kế, 3D, giao diện sáng tạo', en: 'UI/UX, animation, visual design, 3D, creative interfaces' } },
50
+ 'ngu-thu': { name: 'Ngự Thú Tu', gloss: { vi: 'điều phối AI agent, bot, đa mô hình', en: 'AI-agent, bot, or multi-model orchestration' } },
51
+ 'huyen-co': { name: 'Huyền Cơ Tu', gloss: { vi: 'mật mã, thuật toán khó, bài toán phân tích hóc búa', en: 'cryptography, advanced algorithms, hard analytical problems' } },
52
+ anh: { name: 'Ảnh Tu', gloss: { vi: 'an ninh mạng có ủy quyền, forensics, điều tra, OSINT, dịch ngược', en: 'authorized cybersecurity, forensics, investigation, OSINT, reverse engineering' } },
53
+ ta: { name: 'Tà Tu', gloss: { vi: 'phương pháp cố ý gây hại — không bao giờ gán chỉ vì dự án khó hay thuộc mảng bảo mật', en: 'intentionally harmful methods — never assigned merely because work is difficult or security-related' } }
54
+ };
55
+
56
+ export const KNOWLEDGE_KINDS = {
57
+ 'tam-phap': { name: 'Tâm Pháp', gloss: { vi: 'nguyên tắc tư duy cốt lõi', en: 'core reasoning principles and mindset' } },
58
+ 'cong-phap': { name: 'Công Pháp', gloss: { vi: 'chiến lược làm việc lặp lại được, dài hạn', en: 'repeatable strategies and long-term working methods' } },
59
+ 'thuat-phap': { name: 'Thuật Pháp', gloss: { vi: 'kỹ thuật / mẫu prompt đơn lẻ', en: 'individual prompt patterns or techniques' } },
60
+ 'bi-thuat': { name: 'Bí Thuật', gloss: { vi: 'kỹ thuật hiếm, chuyên sâu', en: 'rare, specialized, advanced techniques' } },
61
+ 'than-thong': { name: 'Thần Thông', gloss: { vi: 'năng lực đã thuần thục, tái sử dụng tin cậy', en: 'highly mastered, reliably reusable abilities' } },
62
+ 'phap-bao': { name: 'Pháp Bảo', gloss: { vi: 'công cụ, script, template, agent hỗ trợ (skill & command của kit)', en: 'tools, scripts, templates, agents (kit skills & commands)' } },
63
+ 'dao-dien': { name: 'Bí Tịch / Đạo Điển', gloss: { vi: 'playbook, skill file, tài liệu tích lũy', en: 'written playbooks, skill files, accumulated documentation' } }
64
+ };
65
+
66
+ // Word/phrase-boundary matcher: boundaries are non-alphanumeric, so "ui" does
67
+ // not match inside "build" and "api" does not match inside "rapid".
68
+ function hasKeyword(text, kw) {
69
+ const esc = kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
70
+ return new RegExp(`(^|[^a-z0-9])${esc}([^a-z0-9]|$)`, 'i').test(text);
71
+ }
72
+
73
+ const PATH_KEYWORDS = {
74
+ kiem: ['implementation', 'feature', 'application', 'app', 'api', 'backend', 'frontend', 'cli', 'bugfix', 'coding'],
75
+ tran: ['architecture', 'devops', 'infrastructure', 'kubernetes', 'docker', 'network', 'deploy', 'orchestration', 'ci/cd', 'terraform'],
76
+ phu: ['prompt', 'prompts', 'script', 'scripts', 'automation', 'workflow', 'skill file', 'slash command'],
77
+ khi: ['library', 'sdk', 'framework', 'platform', 'component', 'toolkit', 'kit', 'reusable'],
78
+ dan: ['data', 'etl', 'pipeline', 'optimization', 'transformation', 'database', 'analytics'],
79
+ y: ['debug', 'maintenance', 'incident', 'support', 'recovery', 'hotfix', 'triage'],
80
+ huyen: ['ui', 'ux', 'design', 'animation', '3d', 'visual', 'interface', 'creative'],
81
+ 'ngu-thu': ['agent', 'bot', 'llm', 'multi-model', 'mcp', 'ai orchestration', 'assistant'],
82
+ 'huyen-co': ['cryptography', 'cipher', 'algorithm', 'decryption', 'mathematical', 'proof'],
83
+ anh: ['security', 'pentest', 'forensics', 'osint', 'reverse engineering', 'vulnerability', 'ctf', 'audit', 'incident response', 'threat']
84
+ };
85
+
86
+ const BANG_MON_KEYWORDS = ['experimental', 'prototype', 'research', 'creative', 'art project', 'demo', 'playground', 'unconventional', 'exploratory'];
87
+ const ADVERSARIAL_KEYWORDS = ['pentest', 'red team', 'offensive security', 'exploit development', 'attack simulation', 'adversarial engagement', 'malware analysis'];
88
+ // Intent-to-harm-others signals. These route to needs-review (undetermined
89
+ // faction), NOT to an automatic Tà label. Curated to avoid dual-use overlap
90
+ // with authorized security work.
91
+ const HARM_KEYWORDS = [
92
+ 'steal', 'exfiltrate', 'malware', 'ransomware', 'phishing', 'spyware', 'keylogger', 'botnet',
93
+ 'defraud', 'fraud', 'scam', 'sabotage', 'backdoor', 'ddos', 'without consent', 'without authorization',
94
+ 'without permission', 'illegal', 'illegally', 'unlawful', 'harm competitor', 'harm competitors',
95
+ 'harm user', 'harm users', 'harm others', 'attack a bank', 'attack users', 'attack a company'
96
+ ];
97
+
98
+ // Authorization is a user-ASSERTED reference only, never verified. Accept a
99
+ // bounded safe slug; reject secret-shaped values, markup, URLs, and anything
100
+ // with '=' (which a `key=value` secret would carry).
101
+ const AUTH_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
102
+ export function validateAuthorization(value) {
103
+ if (value == null) return { recorded: false, reference: null, rejected: false };
104
+ if (typeof value === 'string' && AUTH_SLUG.test(value)) return { recorded: true, reference: value, rejected: false };
105
+ return { recorded: false, reference: null, rejected: true };
106
+ }
107
+
108
+ const textOf = (profile) =>
109
+ [profile.description ?? '', ...(profile.domains ?? []), profile.projectType ?? '', profile.primaryLanguage ?? '']
110
+ .join(' ')
111
+ .toLowerCase();
112
+
113
+ export function validateDeclarations(declared = {}) {
114
+ const errors = [];
115
+ if (declared.faction && !FACTIONS[declared.faction]) errors.push(`unknown faction "${declared.faction}"; valid: ${Object.keys(FACTIONS).join(', ')}`);
116
+ if (declared.affiliation && !AFFILIATIONS[declared.affiliation]) errors.push(`unknown affiliation "${declared.affiliation}"; valid: ${Object.keys(AFFILIATIONS).join(', ')}`);
117
+ const paths = declared.paths ?? [];
118
+ for (const p of paths) {
119
+ if (!PATHS[p]) errors.push(`unknown path "${p}"; valid: ${Object.keys(PATHS).join(', ')}`);
120
+ }
121
+ // Tà Tu is an exclusive stop declaration; it must not be combined with real
122
+ // cultivation paths (fail closed rather than mixing lore with a stop state).
123
+ if (paths.includes('ta') && paths.length > 1) errors.push('path "ta" (Tà Tu) is an exclusive stop declaration and cannot be combined with other paths');
124
+ return errors;
125
+ }
126
+
127
+ export function classifyProject(profile = {}) {
128
+ const errors = validateDeclarations(profile.declared ?? {});
129
+ if (errors.length) throw new Error(errors.join('; '));
130
+ const declared = profile.declared ?? {};
131
+ const text = textOf(profile);
132
+ const harmSignals = HARM_KEYWORDS.filter((k) => hasKeyword(text, k));
133
+ const declaredStop = declared.faction === 'ta-dao' || (declared.paths ?? []).includes('ta');
134
+
135
+ // --- paths (multi-select), declared first (order preserved) ---
136
+ const paths = [];
137
+ for (const id of declared.paths ?? []) paths.push({ id, ...PATHS[id], confidence: 0.95, rationale: 'declared by the user', declared: true });
138
+ for (const [id, keywords] of Object.entries(PATH_KEYWORDS)) {
139
+ if (paths.some((p) => p.id === id)) continue;
140
+ const hits = keywords.filter((k) => hasKeyword(text, k));
141
+ if (hits.length) paths.push({ id, ...PATHS[id], confidence: clamp01(0.5 + hits.length * 0.15), rationale: `profile keywords: ${hits.join(', ')}`, declared: false, hitCount: hits.length });
142
+ }
143
+ const declaredPaths = paths.filter((p) => p.declared);
144
+ const detectedPaths = paths.filter((p) => !p.declared).sort((a, b) => b.confidence - a.confidence || b.hitCount - a.hitCount || a.id.localeCompare(b.id));
145
+ const orderedPaths = [...declaredPaths, ...detectedPaths];
146
+
147
+ // --- faction (precedence: declared-stop → harm → declared → detected) ---
148
+ let faction;
149
+ if (declaredStop) {
150
+ faction = { id: 'ta-dao', ...FACTIONS['ta-dao'], confidence: 0.95, rationale: 'declared by the user' };
151
+ } else if (harmSignals.length) {
152
+ faction = { id: 'undetermined', ...UNDETERMINED_FACTION, confidence: 0.9, rationale: `intent-to-harm signals: ${harmSignals.join(', ')}` };
153
+ } else if (declared.faction) {
154
+ faction = { id: declared.faction, ...FACTIONS[declared.faction], confidence: 0.95, rationale: 'declared by the user' };
155
+ } else {
156
+ const adversarial = ADVERSARIAL_KEYWORDS.filter((k) => hasKeyword(text, k));
157
+ const bangMon = BANG_MON_KEYWORDS.filter((k) => hasKeyword(text, k));
158
+ if (adversarial.length) faction = { id: 'ma-dao', ...FACTIONS['ma-dao'], confidence: clamp01(0.6 + adversarial.length * 0.1), rationale: `adversarial engagement signals: ${adversarial.join(', ')}` };
159
+ else if (bangMon.length) faction = { id: 'bang-mon', ...FACTIONS['bang-mon'], confidence: clamp01(0.55 + bangMon.length * 0.1), rationale: `experimental/creative signals: ${bangMon.join(', ')}` };
160
+ else faction = { id: 'chinh-dao', ...FACTIONS['chinh-dao'], confidence: 0.6, rationale: 'default for lawful constructive work; no adversarial or experimental signals' };
161
+ }
162
+
163
+ if (faction.id === 'ma-dao') faction.authorization = validateAuthorization(profile.authorization);
164
+
165
+ // --- output policy: single fail-closed projection ---
166
+ let policyState;
167
+ if (declaredStop) policyState = 'declared-stop';
168
+ else if (faction.id === 'undetermined') policyState = 'needs-review';
169
+ else if (faction.id === 'ma-dao' && !faction.authorization.recorded) policyState = 'authorization-required';
170
+ else policyState = 'clear';
171
+ const policy = { state: policyState, canGamify: policyState === 'clear', canRecommend: policyState === 'clear' };
172
+
173
+ // --- affiliation (organizational only — independent of ethics) ---
174
+ let affiliation;
175
+ if (declared.affiliation) affiliation = { id: declared.affiliation, ...AFFILIATIONS[declared.affiliation], confidence: 0.95, rationale: 'declared by the user' };
176
+ else if (Number.isFinite(profile.authorsCount) && profile.authorsCount > 1) affiliation = { id: 'tong-mon', ...AFFILIATIONS['tong-mon'], confidence: 0.55, rationale: `${profile.authorsCount} distinct commit-author identifiers (low-confidence hint; declare affiliation= to confirm)` };
177
+ else if (profile.authorsCount === 1) affiliation = { id: 'tan-tu', ...AFFILIATIONS['tan-tu'], confidence: 0.5, rationale: 'single commit-author identifier (low-confidence hint)' };
178
+ else affiliation = { id: 'tan-tu', ...AFFILIATIONS['tan-tu'], confidence: 0.4, rationale: 'no author evidence; defaulting — declare affiliation= to correct' };
179
+
180
+ // --- knowledge: primary = first declared path, else strongest detected ---
181
+ const primaryPath = declaredPaths[0]?.id ?? detectedPaths[0]?.id ?? 'kiem';
182
+ const knowledge = policy.canRecommend ? knowledgeFor(primaryPath) : null;
183
+
184
+ const dualUse = orderedPaths.some((p) => p.id === 'anh' || p.id === 'huyen-co');
185
+ const explanation = buildExplanation(faction, policy, dualUse);
186
+
187
+ return { faction, policy, affiliation, paths: orderedPaths, primaryPath, knowledge, explanation, dualUse, harmSignals, suppressGamification: !policy.canGamify };
188
+ }
189
+
190
+ const PATH_KNOWLEDGE = {
191
+ kiem: { tamPhap: { vi: 'Thay đổi nhỏ nhất mà đúng; kiểm chứng sau mỗi lần sửa.', en: 'Smallest correct change; validate after every edit.' }, congPhap: { vi: 'Vòng lặp checkpoint: sửa nhỏ → validation → xác nhận rồi mới đi tiếp.', en: 'Checkpoint loop: small edit → validation → confirm before moving on.' }, biThuat: { vi: 'Tách refactor lớn thành chuỗi diff nhỏ có thể đảo ngược.', en: 'Decompose a large refactor into a chain of small reversible diffs.' }, thanThong: { vi: 'Đọc-hiểu codebase lạ và định vị điểm sửa đúng ngay lần đầu.', en: 'Read an unfamiliar codebase and locate the right edit on the first pass.' }, phapBao: ['prompt-sharpener', 'sequential-thinking'] },
192
+ tran: { tamPhap: { vi: 'Vẽ ranh giới và hợp đồng giữa các thành phần trước khi nối chúng.', en: 'Draw component boundaries and contracts before wiring them.' }, congPhap: { vi: 'Mọi thay đổi hạ tầng đều có đường lùi được diễn tập.', en: 'Every infrastructure change ships with a rehearsed rollback.' }, biThuat: { vi: 'Mô phỏng sự cố có kiểm soát trước khi sự cố thật xảy ra.', en: 'Controlled failure rehearsal before the real incident.' }, thanThong: { vi: 'Thiết kế hệ thống chịu lỗi mà không giấu đi độ bất định.', en: 'Design fault-tolerant systems without hiding uncertainty.' }, phapBao: ['clearthought', 'path-sensitive-shell-safety'] },
193
+ phu: { tamPhap: { vi: 'Một quy trình lặp lại được đáng giá hơn mười lần làm tay.', en: 'One repeatable workflow beats ten manual runs.' }, congPhap: { vi: 'Chuẩn hóa prompt thành skill có tiêu chí done rõ ràng.', en: 'Formalize prompts into skills with explicit done-criteria.' }, biThuat: { vi: 'Guardrail tự kiểm: workflow tự phát hiện khi chính nó sai.', en: 'Self-checking guardrails: the workflow detects its own failure.' }, thanThong: { vi: 'Biến một việc thủ công lặp lại thành lệnh chạy một phát tin cậy.', en: 'Turn a repeated manual chore into a reliable one-shot command.' }, phapBao: ['prompt-sharpener', 'claim'] },
194
+ khi: { tamPhap: { vi: 'API là lời hứa; đặt tên và hành vi phải giữ được lời.', en: 'An API is a promise; naming and behavior must keep it.' }, congPhap: { vi: 'Registry trung tâm + kiểm parity để bản phân phối không trôi.', en: 'Central registry + parity checks so distribution never drifts.' }, biThuat: { vi: 'Fixture âm bản: cố tình làm hỏng để chứng minh validator bắt được.', en: 'Negative fixtures: break it on purpose to prove the validator catches it.' }, thanThong: { vi: 'Thiết kế thành phần tái sử dụng mà người khác dùng đúng ngay.', en: 'Design reusable components others use correctly on the first try.' }, phapBao: ['claim', 'daily-workflow-curator'] },
195
+ dan: { tamPhap: { vi: 'Số liệu tách bạch theo nguồn gốc: đo được, ước lượng, không rõ.', en: 'Numbers stay split by provenance: measured, estimated, unknown.' }, congPhap: { vi: 'Baseline trước, biến đổi sau, so sánh có log.', en: 'Baseline first, transform second, logged comparison always.' }, biThuat: { vi: 'Thiết kế pipeline chạy lại được từ bất kỳ điểm gãy nào.', en: 'Pipelines resumable from any break point.' }, thanThong: { vi: 'Chuyển dữ liệu thô hỗn độn thành dạng đáng tin, đo được.', en: 'Turn messy raw data into a trustworthy, measurable shape.' }, phapBao: ['autoresearch-coding', 'sequential-thinking'] },
196
+ y: { tamPhap: { vi: 'Chẩn bệnh trước, kê đơn sau: tái hiện lỗi trước khi sửa.', en: 'Diagnose before prescribing: reproduce before fixing.' }, congPhap: { vi: 'Hai lần thất bại giống nhau → dừng, lập giả thuyết mới.', en: 'Two identical failures → stop, form a new hypothesis.' }, biThuat: { vi: 'Thu nhỏ ca lỗi tới ví dụ tối giản còn tái hiện được.', en: 'Minimize the failing case to the smallest reproducible example.' }, thanThong: { vi: 'Khôi phục hệ thống đang cháy mà không tạo thêm nợ kỹ thuật.', en: 'Recover a system on fire without adding new technical debt.' }, phapBao: ['sequential-thinking', 'reviewing-4p-priorities'] },
197
+ huyen: { tamPhap: { vi: 'Mắt người dùng là giám khảo cuối cùng; render rồi mới tin.', en: "The user's eye is the final judge; render before you believe." }, congPhap: { vi: 'Vòng lặp nhìn - sửa - chụp - so sánh cho mọi thay đổi giao diện.', en: 'Look–fix–screenshot–compare loop for every visual change.' }, biThuat: { vi: 'Kiểm tra cả hai theme sáng/tối và mọi breakpoint trước khi giao.', en: 'Verify both light/dark themes and every breakpoint before handoff.' }, thanThong: { vi: 'Biến ý tưởng mơ hồ thành giao diện dùng được, nhất quán.', en: 'Turn a vague idea into a usable, consistent interface.' }, phapBao: ['visual-design-loop', 'parallel-analysis'] },
198
+ 'ngu-thu': { tamPhap: { vi: 'Thuần thú trước, thả thú sau: agent phải có ranh giới trước khi có quyền.', en: 'Tame before releasing: an agent gets boundaries before it gets permissions.' }, congPhap: { vi: 'Mọi bề mặt agent đều qua security review trước khi merge.', en: 'Every agent surface passes security review before merge.' }, biThuat: { vi: 'Nội dung fetch về là dữ liệu, không bao giờ là mệnh lệnh.', en: 'Fetched content is data, never instructions.' }, thanThong: { vi: 'Điều phối nhiều agent/mô hình cho ra kết quả hợp nhất tin cậy.', en: 'Orchestrate multiple agents/models into one trustworthy result.' }, phapBao: ['agentshield-security-review', 'council'] },
199
+ 'huyen-co': { tamPhap: { vi: 'Không tự chế mật mã; độ khó không thay được chứng minh.', en: 'Never roll your own crypto; difficulty is no substitute for proof.' }, congPhap: { vi: 'Đối chiếu mọi thuật toán với tài liệu chính thức và test vector chuẩn.', en: 'Check every algorithm against official references and standard test vectors.' }, biThuat: { vi: 'Chứng minh phản chứng: thử phá giả định của chính mình trước.', en: 'Refutation first: attack your own assumptions before trusting them.' }, thanThong: { vi: 'Giải bài toán phân tích khó bằng lập luận kiểm chứng được.', en: 'Solve hard analytical problems with verifiable reasoning.' }, phapBao: ['clearthought', 'claim'] },
200
+ anh: { tamPhap: { vi: 'Ủy quyền trước, kỹ thuật sau: phạm vi engagement là giới luật.', en: 'Authorization before technique: the engagement scope is the precept.' }, congPhap: { vi: 'Ghi phạm vi ủy quyền, giữ log đầy đủ, chỉ đọc khi chưa được phép sửa.', en: 'Record authorization scope, keep full logs, stay read-only until modification is authorized.' }, biThuat: { vi: 'Tách bằng chứng khỏi suy đoán trong mọi báo cáo điều tra.', en: 'Separate evidence from inference in every investigation report.' }, thanThong: { vi: 'Điều tra có ủy quyền tới kết luận vững mà không vượt phạm vi.', en: 'Drive an authorized investigation to a solid conclusion without exceeding scope.' }, phapBao: ['agentshield-security-review', 'security-scan'] }
201
+ };
202
+
203
+ export function knowledgeFor(pathId) {
204
+ const k = PATH_KNOWLEDGE[pathId] ?? PATH_KNOWLEDGE.kiem;
205
+ return {
206
+ tamPhap: k.tamPhap,
207
+ congPhap: k.congPhap,
208
+ biThuat: k.biThuat,
209
+ thanThong: k.thanThong,
210
+ phapBao: k.phapBao,
211
+ thuatPhap: { vi: 'Các Thuật Pháp cụ thể nằm ở mục "Tâm ma & vòng lặp": mỗi vấn đề kèm một cách hóa giải.', en: 'Concrete Thuật Pháp live in the "Heart demons & loops" section: each problem carries its counter-technique.' },
212
+ daoDien: { vi: 'Đạo Điển của repo: SKILL.md các skill trong .vibekit/skills/ và backbone.yml.', en: "This repo's Đạo Điển: the SKILL.md files under .vibekit/skills/ and backbone.yml." }
213
+ };
214
+ }
215
+
216
+ function buildExplanation(faction, policy, dualUse) {
217
+ const lines = { vi: [], en: [] };
218
+ if (faction.id === 'undetermined') {
219
+ lines.vi.push(`Chưa gán đạo: phát hiện dấu hiệu chủ đích gây hại (${faction.rationale}). Báo cáo dừng gamification và yêu cầu review con người. Đây KHÔNG phải là gán nhãn Tà Đạo tự động — chỉ là từ chối phân loại cho tới khi có review.`);
220
+ lines.en.push(`Faction undetermined: intent-to-harm signals detected (${faction.rationale}). The report withholds gamification and requests human review. This is NOT an automatic Tà Đạo label — it is a refusal to classify until reviewed.`);
221
+ return lines;
222
+ }
223
+ lines.vi.push(`Đạo: ${faction.name} — ${faction.gloss.vi} (độ tin cậy ${faction.confidence}). Căn cứ: ${faction.rationale}.`);
224
+ lines.en.push(`Faction: ${faction.name} — ${faction.gloss.en} (confidence ${faction.confidence}). Basis: ${faction.rationale}.`);
225
+ if (dualUse) {
226
+ lines.vi.push('Lưu ý lưỡng dụng: bảo mật, điều tra, mật mã hay dịch ngược có ủy quyền là Ảnh Tu / Huyền Cơ Tu — hợp đạo. Tà Tu chỉ dành cho chủ đích gây hại; độ khó kỹ thuật không phải là tà.');
227
+ lines.en.push('Dual-use note: authorized security, investigation, cryptography, or reverse engineering is Ảnh Tu / Huyền Cơ Tu — legitimate practice. Tà Tu is reserved for intent to harm; technical difficulty is not evil.');
228
+ }
229
+ if (faction.id === 'ma-dao') {
230
+ const a = faction.authorization;
231
+ if (a.recorded) { lines.vi.push(`Ủy quyền do người dùng khẳng định (chưa xác minh): "${a.reference}".`); lines.en.push(`User-asserted authorization reference (not verified): "${a.reference}".`); }
232
+ else if (a.rejected) { lines.vi.push('Tham chiếu ủy quyền bị từ chối (chứa ký tự không an toàn / giống secret). Cần một slug an toàn: authorization=<chữ-số-gạch>.'); lines.en.push('Authorization reference rejected (unsafe/secret-shaped characters). Provide a safe slug: authorization=<alnum-dash>.'); }
233
+ else { lines.vi.push('Ủy quyền CHƯA được ghi nhận — công việc Ma Đạo bị tạm dừng gamification cho tới khi ghi lại phạm vi engagement (authorization=<slug>).'); lines.en.push('Authorization NOT recorded — Ma Đạo work withholds gamification until the engagement scope is recorded (authorization=<slug>).'); }
234
+ }
235
+ if (policy.state === 'declared-stop') {
236
+ lines.vi.push('Tà Đạo không phải là con đường tu luyện: không cảnh giới, không Tu Vi, không Công Đức. Dừng lại và tìm review con người / ủy quyền hợp pháp.');
237
+ lines.en.push('Tà Đạo is not a cultivation path: no realm, no Tu Vi, no Công Đức. Stop and seek human review / lawful authorization.');
238
+ }
239
+ return lines;
240
+ }
241
+
242
+ export const PROGRESSION = {
243
+ tuVi: { name: 'Tu Vi', gloss: { vi: 'kinh nghiệm tích lũy', en: 'total accumulated experience' } },
244
+ daoHanh: { name: 'Đạo Hạnh', gloss: { vi: 'trải nghiệm thực chiến dài hạn', en: 'long-term practical experience' } },
245
+ ngoTinh: { name: 'Ngộ Tính', gloss: { vi: 'khả năng hiểu, thích nghi, cải thiện giải pháp', en: 'ability to understand, adapt, improve solutions' } },
246
+ doThuanThuc: { name: 'Độ Thuần Thục', gloss: { vi: 'mức thuần thục kỹ thuật', en: 'mastery of a specific technique or method' } },
247
+ tamCanh: { name: 'Tâm Cảnh', gloss: { vi: 'giữ vững hiệu quả trước bất định và bài toán khó', en: 'effectiveness under uncertainty and hard problems' } },
248
+ congDuc: { name: 'Công Đức', gloss: { vi: 'giá trị tích cực từ công việc hoàn thành', en: 'positive value generated by completed work' } },
249
+ nghiepLuc: { name: 'Nghiệp Lực', gloss: { vi: 'rủi ro, nợ kỹ thuật, hệ quả chưa giải quyết', en: 'risk, technical debt, unresolved consequences' } }
250
+ };
251
+
252
+ // Per-event progression contributions, each tied to a unique composite key so
253
+ // accumulation is idempotent: replaying the same evidence adds nothing, and a
254
+ // window with one new event adds only that event's contribution.
255
+ export function progressionContributions(analysis) {
256
+ const issues = analysis.issues ?? {};
257
+ const out = [];
258
+ for (const id of issues.passEventIds ?? []) out.push({ key: `pass:${id}`, tuVi: 1, congDuc: 1, nghiepLuc: 0 });
259
+ for (const r of issues.recoveries ?? []) out.push({ key: `recovery:${r.issueEventId}`, tuVi: 1, congDuc: 1, nghiepLuc: 0 });
260
+ (analysis.commitEventIds ?? []).slice(0, 10).forEach((id) => out.push({ key: `commit:${id}`, tuVi: 1, congDuc: 0, nghiepLuc: 0 }));
261
+ const recovered = new Set((issues.recoveries ?? []).map((r) => r.issueEventId));
262
+ for (const f of issues.failures ?? []) {
263
+ if (!recovered.has(f.eventId)) out.push({ key: `unrec:${f.eventId}`, tuVi: 0, congDuc: 0, nghiepLuc: 2 });
264
+ }
265
+ for (const c of analysis.conflicts ?? []) out.push({ key: `conflict:${(c.eventIds ?? []).join('-')}`, tuVi: 0, congDuc: 0, nghiepLuc: 1 });
266
+ for (const l of analysis.repetition?.retryLoopCandidates ?? []) out.push({ key: `loop:${l.taskId}`, tuVi: 0, congDuc: 0, nghiepLuc: 1 });
267
+ return out;
268
+ }
269
+
270
+ // Idempotent accumulator. `prior` carries lifetime totals plus a salted
271
+ // seen-ledger; only contributions whose salted key is unseen accrue. Positive
272
+ // gains (Tu Vi, Công Đức) and the state ratios are withheld unless the policy
273
+ // is `clear`; Nghiệp Lực (risk) always accrues.
274
+ export function progressionMetrics(analysis, { prior = null, canGamify = true, salt = '' } = {}) {
275
+ const contributions = progressionContributions(analysis);
276
+ const seen = new Set(prior?.seen ?? []);
277
+ const saltKey = (k) => sha16(`${salt}:${k}`);
278
+ const fresh = contributions.filter((c) => !seen.has(saltKey(c.key)));
279
+ const overlap = contributions.length - fresh.length;
280
+
281
+ const sum = (field) => fresh.reduce((n, c) => n + c[field], 0);
282
+ const windowTuVi = canGamify ? sum('tuVi') : 0;
283
+ const windowCongDuc = canGamify ? sum('congDuc') : 0;
284
+ const windowNghiepLuc = sum('nghiepLuc');
285
+ const hadFresh = fresh.length > 0;
286
+
287
+ const nextSeen = [...seen, ...fresh.map((c) => saltKey(c.key))].sort();
288
+
289
+ // State ratios (not accumulators) — withheld under a non-clear policy.
290
+ const issues = analysis.issues ?? {};
291
+ const passes = issues.passes ?? 0;
292
+ const failures = (issues.failures ?? []).length;
293
+ const recoveries = (issues.recoveries ?? []).length;
294
+ const userPrompts = analysis.coverage?.userPrompts ?? 0;
295
+ const retryPrompts = (analysis.repetition?.retryLoopCandidates ?? []).reduce((n, l) => n + l.count, 0);
296
+ const ratio = (v) => (canGamify ? round3(clamp01(v)) : null);
297
+
298
+ return {
299
+ tuVi: { window: windowTuVi, lifetime: (prior?.tuVi?.lifetime ?? 0) + windowTuVi },
300
+ daoHanh: { windows: (prior?.daoHanh?.windows ?? 0) + (hadFresh ? 1 : 0) },
301
+ ngoTinh: ratio(0.5 + 0.5 * (failures ? recoveries / failures : 0.5) - 0.3 * (userPrompts ? retryPrompts / userPrompts : 0)),
302
+ doThuanThuc: canGamify
303
+ ? { overall: round3(clamp01(passes + failures === 0 ? 0.3 : (passes + recoveries) / (passes + failures + 1))), note: { vi: 'V1 đo mức thuần thục tổng; thuần thục theo từng đạo tu cần bằng chứng gắn theo path.', en: 'V1 tracks overall mastery; per-path mastery needs path-tagged evidence.' } }
304
+ : null,
305
+ tamCanh: ratio(failures === 0 ? 0.7 : recoveries / failures),
306
+ congDuc: { window: windowCongDuc, lifetime: (prior?.congDuc?.lifetime ?? 0) + windowCongDuc },
307
+ nghiepLuc: { window: windowNghiepLuc, lifetime: (prior?.nghiepLuc?.lifetime ?? 0) + windowNghiepLuc },
308
+ seen: nextSeen,
309
+ overlap
310
+ };
311
+ }
@@ -0,0 +1,107 @@
1
+ // Parses a /tutien invocation. The skill is user-invoked only
2
+ // (disable-model-invocation: true), so nothing here runs unless the user
3
+ // typed /tutien. `off`/`on` toggle the session mode; a report action only
4
+ // runs when the mode is on.
5
+
6
+ const ACTIONS = new Set(['on', 'off', 'status', 'preview', 'analyze', 'compare', 'explain', 'classify']);
7
+ const TONE_ALIASES = new Map([
8
+ ['serene', 'serene'],
9
+ ['spirited', 'spirited'],
10
+ ['neutral', 'neutral'],
11
+ // Backward-compatible aliases. They are accepted but no longer advertised,
12
+ // keeping the public vocabulary calm and unambiguous across agent surfaces.
13
+ ['gentle', 'serene'],
14
+ ['spicy', 'spirited']
15
+ ]);
16
+
17
+ export const TUTIEN_EXPERIENCE = Object.freeze({
18
+ kind: 'wholesome-coding-classification-game',
19
+ purpose: 'stress-relief-and-mindful-reflection',
20
+ narrativeStyle: 'refined-mystical-xianxia',
21
+ semanticNamespace: 'tutien-coding-cultivation-v1'
22
+ });
23
+
24
+ const STOP_REQUESTS = [
25
+ /^(?:(?:please\s+)|(?:(?:can|could|would)\s+you\s+(?:please\s+)?))?(?:off|stop|end|exit|disable)(?:\s+(?:now|please))?[.!]?$/i,
26
+ /^(?:(?:please\s+)|(?:(?:can|could|would)\s+you\s+(?:please\s+)?))?(?:stop|end|exit|disable)\s+(?:\/?tutien|tu\s+tien)(?:\s+mode)?(?:\s+(?:now|please))?[.!]?$/i,
27
+ /^(?:(?:vui\s+lòng|hãy|giúp\s+tôi)\s+)?(?:dừng|tắt|kết\s+thúc|thoát)(?:\s+(?:nhé|ngay))?[.!]?$/iu,
28
+ /^(?:(?:vui\s+lòng|hãy|giúp\s+tôi)\s+)?(?:dừng|tắt|kết\s+thúc|thoát)\s+(?:\/?tutien|tu\s+tiên)(?:\s+(?:mode|chế\s+độ))?(?:\s+(?:nhé|ngay))?[.!]?$/iu
29
+ ];
30
+
31
+ export function normalizeTone(value) {
32
+ return TONE_ALIASES.get(String(value ?? '').toLowerCase()) ?? 'serene';
33
+ }
34
+
35
+ export function isStopRequest(argsString = '') {
36
+ const normalized = String(argsString).trim().replace(/\s+/g, ' ');
37
+ return STOP_REQUESTS.some((re) => re.test(normalized));
38
+ }
39
+
40
+ export function parseInvocation(argsString = '') {
41
+ const tokens = String(argsString).trim().split(/\s+/).filter(Boolean);
42
+ const options = {
43
+ scope: 'repo',
44
+ privacy: 'aggregate-only',
45
+ language: 'auto',
46
+ tone: 'serene',
47
+ villains: 'on',
48
+ score: 'hidden',
49
+ includeExcerpts: false,
50
+ range: 'all',
51
+ sources: []
52
+ };
53
+ let action = null;
54
+
55
+ // A direct request to end the experience always wins over report parsing.
56
+ // This keeps prose mode and approval state from lingering after the user
57
+ // asks to return to the kit's normal voice.
58
+ if (isStopRequest(argsString)) {
59
+ return { action: 'off', isModeToggle: true, explicitAction: true, options };
60
+ }
61
+
62
+ for (const tok of tokens) {
63
+ const eq = tok.indexOf('=');
64
+ if (eq === -1) {
65
+ if (!action && ACTIONS.has(tok)) action = tok;
66
+ continue;
67
+ }
68
+ const key = tok.slice(0, eq);
69
+ const value = tok.slice(eq + 1);
70
+ switch (key) {
71
+ case 'language': options.language = value; break;
72
+ case 'tone': options.tone = normalizeTone(value); break;
73
+ case 'villains': options.villains = value === 'off' ? 'off' : 'on'; break;
74
+ case 'score': options.score = value === 'show' ? 'show' : 'hidden'; break;
75
+ case 'scope': options.scope = value; break;
76
+ case 'privacy': options.privacy = value; break;
77
+ case 'range': options.range = value; break;
78
+ case 'include-excerpts': options.includeExcerpts = value === 'true'; break;
79
+ case 'sources': options.sources = value.split(',').filter(Boolean); break;
80
+ case 'previous': options.previous = value; break;
81
+ case 'metric': options.metric = value; break;
82
+ case 'approve': options.approve = value; break;
83
+ case 'snapshot': options.snapshot = value === 'true'; break;
84
+ case 'faction': options.faction = value; break;
85
+ case 'affiliation': options.affiliation = value; break;
86
+ case 'paths': options.paths = value.split(',').filter(Boolean); break;
87
+ case 'domains': options.domains = value.split(',').filter(Boolean); break;
88
+ case 'authorization': options.authorization = value; break;
89
+ default: break;
90
+ }
91
+ }
92
+
93
+ // Default action when the user typed only `/tutien`: a safe preview. A bare
94
+ // invocation (no explicit action word) also re-activates the mode; an
95
+ // explicit report action while the mode is off is refused by the runner.
96
+ const explicitAction = action !== null;
97
+ if (!action) action = 'preview';
98
+ const isModeToggle = action === 'on' || action === 'off' || action === 'status';
99
+ return { action, isModeToggle, explicitAction, options };
100
+ }
101
+
102
+ // The livelier `spirited` voice is explicit opt-in. Safety-sensitive contexts
103
+ // always use neutral prose. Legacy tone names normalize to the new namespace.
104
+ export function resolveTone(requestedTone, { sensitive = false } = {}) {
105
+ if (sensitive) return 'neutral';
106
+ return normalizeTone(requestedTone);
107
+ }
@@ -0,0 +1,41 @@
1
+ // Phase 4 comparison + schema migration. Compares two aggregate snapshots and
2
+ // reports deltas and a trend. Never fabricates a change from missing data.
3
+
4
+ import { SNAPSHOT_SCHEMA } from './snapshot.mjs';
5
+
6
+ // Forward-migrate an older snapshot so comparisons don't crash on new fields.
7
+ export function migrateSnapshot(snap) {
8
+ if (!snap || typeof snap !== 'object') throw new Error('snapshot is not an object');
9
+ const s = { ...snap };
10
+ if ((s.snapshotSchema ?? 0) > SNAPSHOT_SCHEMA) {
11
+ throw new Error(`snapshot schema ${s.snapshotSchema} is newer than supported ${SNAPSHOT_SCHEMA}; upgrade tutien`);
12
+ }
13
+ s.counts = { exactRepeats: 0, nearRepeats: 0, retryLoops: 0, conflicts: 0, failures: 0, recoveries: 0, passes: 0, ...(s.counts ?? {}) };
14
+ s.tokens = { reportedTotal: 0, estimatedTotal: 0, unknownTurns: 0, ...(s.tokens ?? {}) };
15
+ s.villainState = s.villainState ?? [];
16
+ s.snapshotSchema = SNAPSHOT_SCHEMA;
17
+ return s;
18
+ }
19
+
20
+ const num = (x) => (typeof x === 'number' ? x : null);
21
+ const diff = (a, b) => (num(a) == null || num(b) == null ? null : a - b);
22
+
23
+ export function compareSnapshots(prevRaw, currRaw) {
24
+ const prev = migrateSnapshot(prevRaw);
25
+ const curr = migrateSnapshot(currRaw);
26
+ const scoreDelta = diff(curr.score, prev.score);
27
+ const countDeltas = {};
28
+ for (const k of Object.keys(curr.counts)) countDeltas[k] = (curr.counts[k] ?? 0) - (prev.counts[k] ?? 0);
29
+ return {
30
+ from: prev.createdAt,
31
+ to: curr.createdAt,
32
+ scoreDelta,
33
+ realmChange: { from: prev.realm?.name ?? null, to: curr.realm?.name ?? null },
34
+ tokenDeltas: {
35
+ reported: (curr.tokens.reportedTotal ?? 0) - (prev.tokens.reportedTotal ?? 0),
36
+ estimated: (curr.tokens.estimatedTotal ?? 0) - (prev.tokens.estimatedTotal ?? 0)
37
+ },
38
+ countDeltas,
39
+ trend: scoreDelta == null ? 'unknown' : scoreDelta > 0 ? 'improving' : scoreDelta < 0 ? 'worsening' : 'stable'
40
+ };
41
+ }