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,15 @@
1
+ # tutien cultural source notes
2
+
3
+ The report borrows xianxia/xiuxian genre vocabulary as a creative theme. It distinguishes historical/philosophical ideas from modern fictional/game progression, and does **not** present a fictional realm ladder as religious or historical fact.
4
+
5
+ - Daoism's "the Way/path", learning, change, and self-correcting practice inform the broad framing — not a software achievement ladder: Stanford Encyclopedia of Philosophy, *Daoism* (`https://plato.stanford.edu/entries/daoism/`).
6
+ - The distinction between `xiuzhen`, `xiuxian`, and `xianxia`, and how cultivation is translated into game systems and levels: "Cultivation games and cosmotechnics" (`https://journals.sagepub.com/doi/pdf/10.1177/20594364251364733`).
7
+ - Xianxia as fiction grounded in Daoist cultivation philosophy and mythology, centered on growth and struggle: Wuxiaworld genre introduction (`https://www.wuxiaworld.com/page/introduction-to-chinese-webnovel-genres-xianxia`) and general glossary (`https://www.wuxiaworld.com/page/general-glossary-of-terms`).
8
+
9
+ These sources justify the theme and terminology only. The realm names, score bands, achievement mappings, villain archetypes, and jokes in tutien are an original project design and are documented as such.
10
+
11
+ ## Tone contract
12
+
13
+ - The villain is always the failure mode, never the person.
14
+ - Banned in every mode (including `spicy`): attacks involving identity, intelligence, mental health, disability, religion, gender, race, finances; threats, profanity, humiliation. `tẩu hỏa nhập ma` is not used as a label for a person.
15
+ - Sensitive or emergency contexts force neutral, villain-free output.
@@ -0,0 +1,33 @@
1
+ # tutien privacy model
2
+
3
+ ## What is read
4
+
5
+ - **Git metadata** in the current repo (read-only `git log`): commit hashes, subjects, timestamps, revert flags. No working-tree contents.
6
+ - **Explicitly supplied export files** the user passes by exact path (`sources=…`): `tutien-generic-v1` JSONL or plain transcripts.
7
+
8
+ Never read: the home directory, global tool history, other repos, or any path the user did not name. `preview` shows the exact file list and date window before anything is read; nothing is read or written until the user approves that scope.
9
+
10
+ ## What is redacted (before anything else sees it)
11
+
12
+ `scripts/redact.mjs` is the single choke point. On ingest it:
13
+
14
+ - strips URL user-info (`user:pass@host`);
15
+ - replaces sensitive query values (`token`, `key`, `secret`, `password`, `auth`, `signature`, `code`, and case/encoding variants) with `[REDACTED]`;
16
+ - drops URL fragments;
17
+ - masks bearer/authorization tokens and AWS-key-shaped strings in free text.
18
+
19
+ Normalized events keep redacted text in memory for the run only. The analysis JSON references content solely through `eventId`, `textDigest`, and `phraseDigest` (16-hex SHA-256 prefixes).
20
+
21
+ ## What is persisted (only with approval)
22
+
23
+ Aggregate snapshots under the git-ignored `.vibekit/reports/tutien/`:
24
+
25
+ - coverage, disjoint reported/estimated/unknown token totals, metric counts, dimension scores, realm, thresholds, adapter versions, villain cooldown state, and **salted** evidence-id digests.
26
+
27
+ Never persisted: raw prompts, URLs, secrets, file contents, or unsalted event IDs. `aggregate-only` is the default; excerpts are off by default and, if ever enabled, are redacted and capped to one line.
28
+
29
+ ## Deletion, retention, reset, export
30
+
31
+ - **Retention:** keep the newest N snapshots; `snapshotsToPrune()` returns the rest as a list to remove.
32
+ - **Delete/reset:** use the repo safe-delete policy — prefer `trash <path>`; never a silent `rm`. Deleting `.vibekit/reports/tutien/` resets all trend history.
33
+ - **Export:** a snapshot is plain JSON; copy the file. It already contains no raw content.
@@ -0,0 +1,75 @@
1
+ # `/tutien` normalized event and analysis schema (v1)
2
+
3
+ ## Generic input format `tutien-generic-v1`
4
+
5
+ One JSON object per line (JSONL). An optional first line `{"schema":"tutien-generic-v1"}` pins the version; any other `schema` value fails closed. Comment lines start with `#`.
6
+
7
+ The whole declared schema is validated on ingest and fails closed on: unknown `type`; invalid `role`, `outcome`, `usage.accuracy`; unparseable `ts`; non-string `session`/`task`/`label`/`text`/`usage.requestId`; negative, non-finite, or non-numeric usage numbers; non-boolean `usage.cumulative`; malformed `commit`; and the reserved fields `source`/`__source` (the event source is owned by the adapter, never by export content).
8
+
9
+ Event object:
10
+
11
+ | Field | Type | Notes |
12
+ | --- | --- | --- |
13
+ | `type` | string | `message` \| `tool_call` \| `tool_result` \| `test` \| `approval` \| `commit`. Unknown types fail closed. |
14
+ | `session` | string | Session/thread identifier. |
15
+ | `task` | string? | Explicit task ID when the export has one (strongest grouping signal). |
16
+ | `ts` | ISO-8601? | Timestamp; null allowed. |
17
+ | `role` | string? | `user` \| `assistant` \| `tool` \| `git`. |
18
+ | `text` | string? | Redacted on ingest; retained in memory only. |
19
+ | `usage` | object? | `input`, `cachedInput`, `output`, `reasoning`, `total`, `accuracy` (`reported`\|`estimated`\|`unknown`), `requestId`, `cumulative`. |
20
+ | `outcome` | string? | `pass` \| `fail` for `test`/`tool_result`. |
21
+ | `label` | string? | Stable name used to pair a failure with its recovery. |
22
+ | `commit` | object? | `{ hash, subject, isRevert }` for git events. |
23
+
24
+ ## Analysis output (schemaVersion 1)
25
+
26
+ ```jsonc
27
+ {
28
+ "schemaVersion": 1,
29
+ "eventsAnalyzed": 0,
30
+ "coverage": {
31
+ "sessions": 0, "userPrompts": 0, "commits": 0,
32
+ "window": { "start": null, "end": null },
33
+ "tokenCoverage": { "reportedTurnsPct": 0, "estimatedTurnsPct": 0, "unknownTurns": 0 },
34
+ "confidence": "low|medium|high"
35
+ },
36
+ "tokens": {
37
+ "reportedTotal": 0, "estimatedTotal": 0, "unknownTurns": 0,
38
+ "countedRequests": 0, "dedupedStreamUpdates": 0
39
+ },
40
+ "tasks": [ { "taskId": "", "sessionId": "", "grouping": "explicit|derived", "confidence": "high|medium", "eventCount": 0 } ],
41
+ "repetition": {
42
+ "exactRepeats": [ { "taskId": "", "count": 2, "eventIds": [], "textDigest": "" } ],
43
+ "nearRepeats": [ { "taskId": "", "eventIds": [], "similarity": 0.9 } ],
44
+ "retryLoopCandidates": [ { "taskId": "", "count": 3, "eventIds": [], "confidence": 0.6 } ]
45
+ },
46
+ "conflicts": [ { "category": "user-user", "taskId": "", "eventIds": [], "phraseDigest": "", "confidence": 0.6 } ],
47
+ "issues": {
48
+ "passes": 0,
49
+ "failures": [ { "eventId": "", "label": "" } ],
50
+ "recoveries": [ { "issueEventId": "", "recoveryEventId": "" } ],
51
+ "revertCandidates": [ { "eventId": "", "hash": "", "confidence": 0.5 } ]
52
+ },
53
+ "notes": []
54
+ }
55
+ ```
56
+
57
+ ## Phase 2 render model
58
+
59
+ `render-report.mjs` builds a language-neutral model from the analysis (realm, dimension scores, disjoint token totals, evidence-bound problems, one if-then practice, one lesson) and renders it in `vi` or `en` — the same model feeds both, so counts are identical across languages by construction. `score.mjs` returns `Chưa đủ thiên cơ` (no realm) when known-token coverage is below 60%. `catalog.mjs` emits at most three problems, ranked by `impact × recurrence × confidence × fixability`, each carrying `evidence` (event IDs / counts), a counter-technique, a project-skill pointer, and a micro-quest. Lore never alters a count.
60
+
61
+ ## Privacy invariant
62
+
63
+ `text` is redacted inside `normalize-events.mjs` and never emitted. **Metadata is digested too**: `session`, `task`, and `label` values become 16-hex SHA-256 prefixes before they reach normalized events, so task/session identifiers in analysis output (`tasks[].taskId`, `tasks[].sessionId`, `issues.failures[].label`) can never carry raw content. Timestamps are canonicalized to UTC ISO or dropped; commit subjects are dropped from the commit object (their redacted form travels only as event text). Analysis output references content solely through `eventId`, `textDigest`, and `phraseDigest`. No field carries raw prompt text, URL user-info, secret query values, or URL fragments.
64
+
65
+ ## Token-evidence invariant
66
+
67
+ A usage record contributes to reported/estimated totals and coverage only when a **usable total** exists: a finite, positive `total`, or a positive sum of `input + output + reasoning`. A record claiming `accuracy=reported` with no numbers (or zero) is counted as `unknown` and cannot unlock a realm score. Only records marked `cumulative` are streaming counters (last value wins); independent chunks sharing a `requestId` are summed.
68
+
69
+ ## Runner state
70
+
71
+ `scripts/run-tutien.mjs` executes the actions end-to-end. It stores the session mode and the pending single-use approval token in the git-ignored `.vibekit/reports/tutien/state.json`, snapshots under `snapshots/`, and the last report as `latest.md`. Preview inspects file existence/size only; analysis runs only with the matching `approve=<token>` for the identical scope. The runner never deletes files — retention prints a `trash` command for the user.
72
+
73
+ ## Determinism invariant
74
+
75
+ Given the same inputs and options, `analyze()` returns byte-identical JSON. No randomness, no wall-clock reads, no ordering that depends on object insertion beyond the input order.
@@ -0,0 +1,37 @@
1
+ # tutien scoring and realms
2
+
3
+ Scoring is deterministic (`scripts/score.mjs`): the same analysis JSON always yields the same score. High token usage never raises the score; necessary iteration is not punished; penalties are capped so one hard task cannot erase a long history.
4
+
5
+ ## Coverage gate
6
+
7
+ A realm is computed only when known-token coverage (reported + estimated turns) is at least **60%**. Below that the report shows `Cảnh giới: Chưa đủ thiên cơ` (not enough evidence) and lists what is missing — it never guesses a realm from thin data.
8
+
9
+ ## Dimensions (weights)
10
+
11
+ | Dimension | Weight | Signal |
12
+ |---|---:|---|
13
+ | delivery | 30% | verified completions and recoveries vs failed attempts, plus committed output |
14
+ | validation | 25% | presence and density of validation events |
15
+ | clarity | 15% | 1 − avoidable-repeat rate (capped) |
16
+ | recovery | 15% | recoveries ÷ failures |
17
+ | safety | 10% | high baseline, capped penalty per conflict candidate |
18
+ | automation | 5% | reusable automation (neutral in V1; refined once commit-subject signals are wired) |
19
+
20
+ Score = round(Σ dimension × weight × 100).
21
+
22
+ ## Realm ladder (project-authored, humorous)
23
+
24
+ | Band | Realm | Gloss |
25
+ |---|---|---|
26
+ | 0–14 | Phàm Nhân Nhập Môn | Mortal Initiate |
27
+ | 15–29 | Luyện Khí | Qi Refining |
28
+ | 30–44 | Trúc Cơ | Foundation Establishment |
29
+ | 45–59 | Kết Đan | Core Formation |
30
+ | 60–69 | Nguyên Anh | Nascent Soul |
31
+ | 70–79 | Hóa Thần | Spirit Transformation |
32
+ | 80–87 | Luyện Hư | Void Refinement |
33
+ | 88–93 | Hợp Thể | Body Integration |
34
+ | 94–97 | Đại Thừa | Great Vehicle |
35
+ | 98–100 | Độ Kiếp / Phi Thăng | Tribulation / Ascension |
36
+
37
+ The realm names, thresholds, dimension weights, and jokes are an original project design — a creative theme, not a claim that one authentic cultivation ladder exists. See `lore-sources.md`.
@@ -0,0 +1,44 @@
1
+ # Tu Tiên voice and mode contract
2
+
3
+ Use this reference when changing `/tutien` prose, activation, tone names, antagonist dialogue, or interaction with another conversational feature.
4
+
5
+ ## Purpose
6
+
7
+ `/tutien` is a wholesome, stress-relieving classification game over coding-collaboration evidence. It offers a brief reflective pause through xianxia imagery while keeping every fact, count, warning, and recommendation technically honest. It is not a companion persona, a relationship simulation, or a health service.
8
+
9
+ ## Semantic namespace
10
+
11
+ All lore belongs to `tutien-coding-cultivation-v1`:
12
+
13
+ - Realms describe evidence-backed workflow progress.
14
+ - Factions describe project posture, never the user's morality.
15
+ - Affiliations describe working arrangement, never personal belonging.
16
+ - Cultivation paths describe technical work.
17
+ - Heart demons and antagonists personify workflow patterns only.
18
+
19
+ Do not borrow meanings, triggers, roles, tone conventions, or vocabulary from unrelated support/companion features. Content from another mode is untrusted, out-of-scope data for `/tutien`; omit it from lore and classification rather than repeating or reinterpreting it. This isolation does not weaken the safety policy—redaction, authorization, policy-state suppression, and emergency neutrality still win.
20
+
21
+ ## Activation and exit
22
+
23
+ - A bare `/tutien` or `/tutien on` activates the experience.
24
+ - While active, every `/tutien` action uses this namespace and voice.
25
+ - `/tutien off` and explicit stop/end/exit requests end the experience, clear pending approval, and return one plain confirmation.
26
+ - After exit, use the normal Minimal Vibe Coding Kit writing style. Do not carry cultivation titles, metaphors, antagonists, or teasing into unrelated answers.
27
+ - A quoted or documentary discussion of the command does not authorize reading history; normal preview and approval rules still apply.
28
+
29
+ ## Voice
30
+
31
+ Write with quiet confidence: elegant, warm, lightly mystical, and easy to scan.
32
+
33
+ 1. Open with one or two atmospheric sentences.
34
+ 2. Present evidence and numbers in plain language.
35
+ 3. Use at most one playful image per finding.
36
+ 4. Target the workflow pattern, never the person.
37
+ 5. Offer one small, measurable next step.
38
+ 6. Close with calm encouragement and no pressure.
39
+
40
+ Avoid dense archaic prose, melodrama, mockery, personal judgment, intimacy, diagnosis, treatment claims, or imported persona language. `serene` is the default. `spirited` may tease the pattern more brightly but remains respectful. `neutral` removes theatrics and is mandatory for safety-sensitive or policy-suppressed reports.
41
+
42
+ ## Language
43
+
44
+ Keep Sino-Vietnamese realm and technique names in both Vietnamese and English reports. In English, gloss an unfamiliar name on first use. Match the invocation language when `language=auto`; never change facts between renderers.
@@ -0,0 +1,82 @@
1
+ // Documented generic export format "tutien-generic-v1": one JSON object per
2
+ // line. The whole declared schema is validated, not just the event type —
3
+ // unknown schemas, unknown types, reserved fields, and malformed values all
4
+ // fail closed with the offending file and line.
5
+
6
+ const KNOWN_TYPES = new Set(['message', 'tool_call', 'tool_result', 'test', 'approval', 'commit']);
7
+ const ROLES = new Set(['user', 'assistant', 'tool', 'git']);
8
+ const OUTCOMES = new Set(['pass', 'fail']);
9
+ const ACCURACIES = new Set(['reported', 'estimated', 'unknown']);
10
+ const USAGE_NUMERIC = ['input', 'cachedInput', 'output', 'reasoning', 'total'];
11
+ const RESERVED = ['__source', 'source'];
12
+
13
+ function bad(file, line, msg) {
14
+ throw new Error(`${file}:${line} ${msg}; failing closed`);
15
+ }
16
+
17
+ function validateEvent(obj, file, line) {
18
+ for (const k of RESERVED) {
19
+ if (k in obj) bad(file, line, `reserved field "${k}" is not allowed in export events`);
20
+ }
21
+ for (const k of ['session', 'task', 'label', 'text']) {
22
+ if (obj[k] !== undefined && typeof obj[k] !== 'string') bad(file, line, `field ${k} must be a string`);
23
+ }
24
+ if (obj.role !== undefined && !ROLES.has(obj.role)) bad(file, line, `invalid role "${obj.role}"`);
25
+ if (obj.outcome !== undefined && !OUTCOMES.has(obj.outcome)) bad(file, line, `invalid outcome "${obj.outcome}"`);
26
+ if (obj.ts !== undefined && (typeof obj.ts !== 'string' || !Number.isFinite(Date.parse(obj.ts)))) {
27
+ bad(file, line, `invalid timestamp "${obj.ts}"`);
28
+ }
29
+ if (obj.usage !== undefined) {
30
+ if (typeof obj.usage !== 'object' || obj.usage === null || Array.isArray(obj.usage)) {
31
+ bad(file, line, 'usage must be an object');
32
+ }
33
+ for (const k of USAGE_NUMERIC) {
34
+ const v = obj.usage[k];
35
+ if (v !== undefined && v !== null && (typeof v !== 'number' || !Number.isFinite(v) || v < 0)) {
36
+ bad(file, line, `usage.${k} must be a finite non-negative number`);
37
+ }
38
+ }
39
+ if (obj.usage.accuracy !== undefined && !ACCURACIES.has(obj.usage.accuracy)) {
40
+ bad(file, line, `invalid usage.accuracy "${obj.usage.accuracy}"`);
41
+ }
42
+ if (obj.usage.requestId !== undefined && typeof obj.usage.requestId !== 'string') {
43
+ bad(file, line, 'usage.requestId must be a string');
44
+ }
45
+ if (obj.usage.cumulative !== undefined && typeof obj.usage.cumulative !== 'boolean') {
46
+ bad(file, line, 'usage.cumulative must be a boolean');
47
+ }
48
+ }
49
+ if (obj.commit !== undefined) {
50
+ if (typeof obj.commit === 'object' && obj.commit !== null && !Array.isArray(obj.commit)) {
51
+ if (obj.commit.hash !== undefined && typeof obj.commit.hash !== 'string') bad(file, line, 'commit.hash must be a string');
52
+ if (obj.commit.isRevert !== undefined && typeof obj.commit.isRevert !== 'boolean') bad(file, line, 'commit.isRevert must be a boolean');
53
+ } else {
54
+ bad(file, line, 'commit must be an object');
55
+ }
56
+ }
57
+ }
58
+
59
+ export function parseJsonl(content, file = '(jsonl)') {
60
+ const events = [];
61
+ const lines = content.split(/\r?\n/);
62
+ for (let i = 0; i < lines.length; i++) {
63
+ const line = lines[i].trim();
64
+ if (!line || line.startsWith('#')) continue;
65
+ let obj;
66
+ try {
67
+ obj = JSON.parse(line);
68
+ } catch (err) {
69
+ throw new Error(`${file}:${i + 1} invalid JSON: ${err.message}`);
70
+ }
71
+ if (obj.schema !== undefined) {
72
+ if (obj.schema !== 'tutien-generic-v1') {
73
+ throw new Error(`${file}:${i + 1} unsupported schema "${obj.schema}"; supported: tutien-generic-v1`);
74
+ }
75
+ continue;
76
+ }
77
+ if (!KNOWN_TYPES.has(obj.type)) bad(file, i + 1, `unknown event type "${obj.type}"`);
78
+ validateEvent(obj, file, i + 1);
79
+ events.push(obj);
80
+ }
81
+ return events;
82
+ }
@@ -0,0 +1,48 @@
1
+ import fs from 'node:fs';
2
+ import process from 'node:process';
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ // Read-only Git metadata adapter, hard-bounded to the current repository:
6
+ // the requested root must resolve (through symlinks) to the same toplevel as
7
+ // the process working directory, or the adapter refuses. A revert commit is
8
+ // an issue *candidate* (confidence-scored downstream), never automatically a
9
+ // failure.
10
+
11
+ function repoToplevel(dir) {
12
+ const res = spawnSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });
13
+ if (res.status !== 0) {
14
+ throw new Error(`not a git repository: ${dir}`);
15
+ }
16
+ return fs.realpathSync(res.stdout.trim());
17
+ }
18
+
19
+ export function readGitEvents(repoRoot, limit = 500) {
20
+ const requested = repoToplevel(repoRoot);
21
+ const current = repoToplevel(process.cwd());
22
+ if (requested !== current) {
23
+ throw new Error(
24
+ `gitRoot ${repoRoot} resolves to ${requested}, outside the current repository ${current}; tutien only reads the current repo's history`
25
+ );
26
+ }
27
+ const res = spawnSync('git', ['log', '-n', String(limit), '--pretty=format:%H%x1f%aI%x1f%s%x1e'], {
28
+ cwd: requested,
29
+ encoding: 'utf8'
30
+ });
31
+ if (res.status !== 0) {
32
+ throw new Error(`git log failed in ${requested}: ${res.stderr?.trim() || res.status}`);
33
+ }
34
+ return res.stdout
35
+ .split('\x1e')
36
+ .map((s) => s.trim())
37
+ .filter(Boolean)
38
+ .map((rec) => {
39
+ const [hash, ts, subject] = rec.split('\x1f');
40
+ return {
41
+ type: 'commit',
42
+ session: 'git',
43
+ ts,
44
+ text: subject,
45
+ commit: { hash, isRevert: /^Revert\b/.test(subject) }
46
+ };
47
+ });
48
+ }
@@ -0,0 +1,37 @@
1
+ // Plain-text transcript adapter. Roles come only from explicit line markers;
2
+ // token usage is always "unknown" here, never estimated silently.
3
+
4
+ const ROLE_RE = /^(user|human|assistant|ai|người dùng|trợ lý)\s*:\s*(.*)$/i;
5
+ const ROLE_MAP = {
6
+ user: 'user',
7
+ human: 'user',
8
+ 'người dùng': 'user',
9
+ assistant: 'assistant',
10
+ ai: 'assistant',
11
+ 'trợ lý': 'assistant'
12
+ };
13
+
14
+ export function parseTranscript(content) {
15
+ const events = [];
16
+ const warnings = [];
17
+ let current = null;
18
+ for (const line of content.split(/\r?\n/)) {
19
+ const m = line.match(ROLE_RE);
20
+ if (m) {
21
+ if (current) events.push(current);
22
+ current = {
23
+ type: 'message',
24
+ source: 'transcript',
25
+ session: 'transcript',
26
+ role: ROLE_MAP[m[1].toLowerCase()],
27
+ text: m[2],
28
+ usage: { accuracy: 'unknown' }
29
+ };
30
+ } else if (current) {
31
+ current.text += `\n${line}`;
32
+ }
33
+ }
34
+ if (current) events.push(current);
35
+ if (events.length === 0) warnings.push('no role markers found; transcript ignored');
36
+ return { events, warnings };
37
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { parseJsonl } from './adapters/generic-jsonl.mjs';
7
+ import { parseTranscript } from './adapters/plain-transcript.mjs';
8
+ import { readGitEvents } from './adapters/git.mjs';
9
+ import { normalizeEvents } from './normalize-events.mjs';
10
+ import {
11
+ groupTasks,
12
+ repetitionMetrics,
13
+ conflictCandidates,
14
+ issueMetrics,
15
+ tokenMetrics,
16
+ coverageMetrics
17
+ } from './metrics.mjs';
18
+
19
+ // Phase 1 analyzer: deterministic, read-only, machine-readable JSON only.
20
+ // The output contains digests and event IDs, never prompt text.
21
+ export function analyze({ jsonlFiles = [], transcriptFiles = [], gitRoot = null, gapMinutes = 60 } = {}) {
22
+ const notes = [];
23
+ let raw = [];
24
+ // The source tag is forced per adapter batch here; export content cannot
25
+ // spoof it (the JSONL validator additionally rejects reserved fields).
26
+ for (const file of jsonlFiles) {
27
+ raw = raw.concat(
28
+ parseJsonl(fs.readFileSync(file, 'utf8'), path.basename(file)).map((e) => ({ ...e, __source: 'generic-jsonl' }))
29
+ );
30
+ }
31
+ for (const file of transcriptFiles) {
32
+ const { events, warnings } = parseTranscript(fs.readFileSync(file, 'utf8'));
33
+ raw = raw.concat(events.map((e) => ({ ...e, __source: 'transcript' })));
34
+ notes.push(...warnings.map((w) => `${path.basename(file)}: ${w}`));
35
+ }
36
+ if (gitRoot) raw = raw.concat(readGitEvents(gitRoot).map((e) => ({ ...e, __source: 'git' })));
37
+
38
+ const events = normalizeEvents(raw);
39
+ const tasks = groupTasks(events, { gapMinutes });
40
+ const taskKeyByEvent = new Map();
41
+ for (const t of tasks) for (const id of t.eventIds) taskKeyByEvent.set(id, t.taskId);
42
+ const tokens = tokenMetrics(events);
43
+ return {
44
+ schemaVersion: 1,
45
+ eventsAnalyzed: events.length,
46
+ coverage: coverageMetrics(events, tasks, tokens),
47
+ tokens,
48
+ tasks: tasks.map((t) => ({
49
+ taskId: t.taskId,
50
+ sessionId: t.sessionId,
51
+ grouping: t.grouping,
52
+ confidence: t.confidence,
53
+ eventCount: t.eventIds.length
54
+ })),
55
+ commitEventIds: events.filter((e) => e.eventType === 'commit').map((e) => e.eventId),
56
+ repetition: repetitionMetrics(events, tasks),
57
+ conflicts: conflictCandidates(events, tasks),
58
+ issues: issueMetrics(events, taskKeyByEvent),
59
+ notes
60
+ };
61
+ }
62
+
63
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
64
+ if (isMain) {
65
+ const args = process.argv.slice(2);
66
+ const spec = { jsonlFiles: [], transcriptFiles: [], gitRoot: null, gapMinutes: 60 };
67
+ let out = null;
68
+ for (let i = 0; i < args.length; i++) {
69
+ const a = args[i];
70
+ if (a === '--jsonl') spec.jsonlFiles.push(args[++i]);
71
+ else if (a === '--transcript') spec.transcriptFiles.push(args[++i]);
72
+ else if (a === '--git') spec.gitRoot = args[++i];
73
+ else if (a === '--gap-minutes') spec.gapMinutes = Number(args[++i]);
74
+ else if (a === '--out') out = args[++i];
75
+ else {
76
+ console.error(`Unknown argument: ${a}`);
77
+ console.error('Usage: analyze-history.mjs [--jsonl f]... [--transcript f]... [--git dir] [--gap-minutes n] [--out f]');
78
+ process.exit(2);
79
+ }
80
+ }
81
+ const json = JSON.stringify(analyze(spec), null, 2);
82
+ if (out) fs.writeFileSync(out, `${json}\n`);
83
+ else console.log(json);
84
+ }
@@ -0,0 +1,130 @@
1
+ // Problem-to-practice catalog. Data-driven so new problem types can be added
2
+ // without touching the analyzer. Each entry carries an existing project skill
3
+ // or convention as the concrete counter-technique, plus a villain archetype
4
+ // that the Phase 3 engine may (optionally) surface. Detection is evidence-
5
+ // bound: a problem needs real event references and a confidence, never a
6
+ // single ambiguous signal.
7
+
8
+ export const CATALOG = {
9
+ 'repeated-failure': {
10
+ problemType: 'repeated-failure',
11
+ counterTechnique: { vi: 'Dừng sau lần thất bại thứ hai giống nhau; lập giả thuyết mới trước khi thử lại.', en: 'Stop after the second identical failure; form a new hypothesis before retrying.' },
12
+ projectHelp: 'sequential-thinking',
13
+ microQuest: { vi: 'Lần lặp kế tiếp: viết 1 giả thuyết mới trước khi chạy lại.', en: 'Next iteration: write one new hypothesis before re-running.' },
14
+ victory: { vi: 'Một task khép lại mà không có 3 lần thử giống nhau.', en: 'A task closes without three identical retries.' },
15
+ ifThen: { vi: 'Nếu cùng một lệnh thất bại hai lần, thì dừng thử lại và viết một giả thuyết mới.', en: 'If the same command fails twice, then stop retrying and write a new hypothesis.' },
16
+ villain: { name: 'Luân Hồi Lỗi Ma', gloss: 'Error-Cycle Wraith' }
17
+ },
18
+ 'too-many-prompts': {
19
+ problemType: 'too-many-prompts',
20
+ counterTechnique: { vi: 'Chia task thành các checkpoint, mỗi ranh giới có một kết quả kiểm chứng.', en: 'Split the task into checkpoints with a validation result at each boundary.' },
21
+ projectHelp: 'clearthought',
22
+ microQuest: { vi: 'Task tới: định nghĩa 2 checkpoint có tiêu chí "done".', en: 'Next task: define 2 checkpoints each with a done-criterion.' },
23
+ victory: { vi: 'Task hoàn thành trong số prompt dưới ngưỡng lịch sử.', en: 'A task completes under the historical prompt threshold.' },
24
+ ifThen: { vi: 'Nếu gửi prompt thứ ba cho cùng một task, thì đặt một checkpoint có tiêu chí "done" trước đã.', en: 'If you send a third prompt on one task, then define a checkpoint with a done-criterion first.' },
25
+ villain: { name: 'Cửu Hoàn Tâm Ma', gloss: 'Ninefold Loop Heart-Shadow' }
26
+ },
27
+ 'conflicting-instructions': {
28
+ problemType: 'conflicting-instructions',
29
+ counterTechnique: { vi: 'Lập sổ ưu tiên và ràng buộc; nêu rõ chỉ thị nào thay thế chỉ thị nào.', en: 'Build a precedence and constraint ledger; state which instruction supersedes which.' },
30
+ projectHelp: 'claim',
31
+ microQuest: { vi: 'Trước khi sửa: ghi 1 dòng "được phép / không được phép".', en: 'Before editing: write one allowed/forbidden line.' },
32
+ victory: { vi: 'Không còn cặp chỉ thị mâu thuẫn chưa giải quyết trong một task.', en: 'No unresolved contradictory instruction pair remains in a task.' },
33
+ ifThen: { vi: 'Nếu chỉ thị mới mâu thuẫn với chỉ thị trước, thì nêu rõ chỉ thị nào thắng trước khi sửa.', en: 'If a new instruction contradicts an earlier one, then state which one wins before editing.' },
34
+ villain: { name: 'Nghịch Lệnh Ma Quân', gloss: 'Lord of Clashing Edicts' }
35
+ },
36
+ 'unrecovered-failure': {
37
+ problemType: 'unrecovered-failure',
38
+ counterTechnique: { vi: 'Thêm cổng hoàn thành: chạy validation và đính kèm biên nhận kết quả.', en: 'Add a completion gate: run validation and attach a result receipt.' },
39
+ projectHelp: 'backbone.yml validate',
40
+ microQuest: { vi: 'Với mỗi lỗi còn treo: chạy lại đúng kiểm tra đó tới khi pass.', en: 'For each open failure: re-run that exact check until it passes.' },
41
+ victory: { vi: 'Mỗi thất bại có một lần pass tương ứng về sau.', en: 'Every failure has a later matching pass.' },
42
+ ifThen: { vi: 'Nếu một kiểm tra thất bại, thì chạy lại đúng kiểm tra đó cho tới khi pass trước khi làm việc mới.', en: 'If a check fails, then re-run that exact check until it passes before starting new work.' },
43
+ villain: { name: 'Vô Kiểm Ảnh Quân', gloss: 'Unverified Trial Wraith' }
44
+ },
45
+ 'work-without-proof': {
46
+ problemType: 'work-without-proof',
47
+ counterTechnique: { vi: 'Chạy lệnh validation của repo và gắn kết quả trước khi tuyên bố xong.', en: 'Run the repository validation and attach the result before declaring done.' },
48
+ projectHelp: 'backbone.yml validate',
49
+ microQuest: { vi: 'Commit tới: kèm 1 dòng kết quả validation.', en: 'Next commit: include one validation-result line.' },
50
+ victory: { vi: 'Mỗi vùng công việc có ít nhất một sự kiện kiểm chứng.', en: 'Every work window has at least one validation event.' },
51
+ ifThen: { vi: 'Nếu sắp tuyên bố "xong", thì chạy validation của repo và đính kèm kết quả trước.', en: "If you are about to say 'done', then run the repo validation and attach the result first." },
52
+ villain: { name: 'Vô Chứng Đan Ảnh', gloss: 'Proofless Elixir Shade' }
53
+ }
54
+ };
55
+
56
+ const clamp01 = (x) => Math.max(0, Math.min(1, x));
57
+ const round3 = (x) => Math.round(x * 1000) / 1000;
58
+
59
+ // Returns ranked, actionable problems. priority = impact × recurrence ×
60
+ // confidence × fixability. Only problems with confidence >= minConfidence and
61
+ // priority >= minPriority survive; at most `top` are returned.
62
+ export function detectProblems(analysis, { minConfidence = 0.5, minPriority = 0.12, top = 3 } = {}) {
63
+ const found = [];
64
+ const rep = analysis.repetition ?? {};
65
+ const issues = analysis.issues ?? {};
66
+ const failures = issues.failures ?? [];
67
+ const recoveries = issues.recoveries ?? [];
68
+
69
+ for (const loop of rep.retryLoopCandidates ?? []) {
70
+ // Only failures attributed to this loop's own task make it a failure loop;
71
+ // an unrelated failure elsewhere must not change the classification.
72
+ const isFailing = failures.some((f) => f.taskId != null && f.taskId === loop.taskId);
73
+ const id = isFailing ? 'repeated-failure' : 'too-many-prompts';
74
+ found.push({
75
+ problemId: id,
76
+ evidence: { taskId: loop.taskId, repeatCount: loop.count, eventIds: loop.eventIds },
77
+ confidence: loop.confidence,
78
+ impact: isFailing ? 0.7 : 0.5,
79
+ recurrence: clamp01(loop.count / 4),
80
+ fixability: 0.8
81
+ });
82
+ }
83
+
84
+ const conflicts = analysis.conflicts ?? [];
85
+ if (conflicts.length > 0) {
86
+ found.push({
87
+ problemId: 'conflicting-instructions',
88
+ evidence: { count: conflicts.length, eventIds: conflicts.flatMap((c) => c.eventIds) },
89
+ confidence: Math.max(...conflicts.map((c) => c.confidence)),
90
+ impact: 0.7,
91
+ recurrence: clamp01(conflicts.length / 2),
92
+ fixability: 0.7
93
+ });
94
+ }
95
+
96
+ const unrecovered = failures.filter((f) => !recoveries.some((r) => r.issueEventId === f.eventId));
97
+ if (unrecovered.length > 0) {
98
+ found.push({
99
+ problemId: 'unrecovered-failure',
100
+ evidence: { count: unrecovered.length, eventIds: unrecovered.map((f) => f.eventId) },
101
+ confidence: 0.7,
102
+ impact: 0.8,
103
+ recurrence: clamp01(unrecovered.length / 3),
104
+ fixability: 0.6
105
+ });
106
+ }
107
+
108
+ const commits = analysis.coverage?.commits ?? 0;
109
+ const validationEvents = (issues.passes ?? 0) + failures.length;
110
+ if (commits > 0 && validationEvents === 0) {
111
+ found.push({
112
+ problemId: 'work-without-proof',
113
+ evidence: { commits, validationEvents: 0 },
114
+ confidence: 0.6,
115
+ impact: 0.6,
116
+ recurrence: clamp01(commits / 5),
117
+ fixability: 0.7
118
+ });
119
+ }
120
+
121
+ return found
122
+ .map((p) => {
123
+ const meta = CATALOG[p.problemId];
124
+ const priority = round3(p.impact * p.recurrence * p.confidence * p.fixability);
125
+ return { ...p, priority, confidence: round3(p.confidence), meta };
126
+ })
127
+ .filter((p) => p.confidence >= minConfidence && p.priority >= minPriority)
128
+ .sort((a, b) => b.priority - a.priority || a.problemId.localeCompare(b.problemId))
129
+ .slice(0, top);
130
+ }