@krtclcdy/tersio 2.25.6 → 2.25.7

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 (188) hide show
  1. package/README.md +30 -7
  2. package/dist/bench/code-out/pfull-0/debounce.js +13 -0
  3. package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
  4. package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
  5. package/dist/bench/code-out/pfull-1/debounce.js +13 -0
  6. package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
  7. package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
  8. package/dist/bench/code-out/pfull-2/debounce.js +13 -0
  9. package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
  10. package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
  11. package/dist/bench/code-out/pfull-3/debounce.js +12 -0
  12. package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
  13. package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
  14. package/dist/bench/code-out/pfull-4/debounce.js +10 -0
  15. package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
  16. package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
  17. package/dist/bench/code-out/plite-0/debounce.js +13 -0
  18. package/dist/bench/code-out/plite-0/deepClone.js +18 -0
  19. package/dist/bench/code-out/plite-0/groupBy.js +15 -0
  20. package/dist/bench/code-out/plite-1/debounce.js +13 -0
  21. package/dist/bench/code-out/plite-1/deepClone.js +17 -0
  22. package/dist/bench/code-out/plite-1/groupBy.js +11 -0
  23. package/dist/bench/code-out/plite-2/debounce.js +12 -0
  24. package/dist/bench/code-out/plite-2/deepClone.js +17 -0
  25. package/dist/bench/code-out/plite-2/groupBy.js +14 -0
  26. package/dist/bench/code-out/plite-3/debounce.js +12 -0
  27. package/dist/bench/code-out/plite-3/deepClone.js +17 -0
  28. package/dist/bench/code-out/plite-3/groupBy.js +14 -0
  29. package/dist/bench/code-out/plite-4/debounce.js +12 -0
  30. package/dist/bench/code-out/plite-4/deepClone.js +17 -0
  31. package/dist/bench/code-out/plite-4/groupBy.js +14 -0
  32. package/dist/bench/code-out/pultra-0/debounce.js +12 -0
  33. package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
  34. package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
  35. package/dist/bench/code-out/pultra-1/debounce.js +10 -0
  36. package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
  37. package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
  38. package/dist/bench/code-out/pultra-2/debounce.js +13 -0
  39. package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
  40. package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
  41. package/dist/bench/code-out/pultra-3/debounce.js +13 -0
  42. package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
  43. package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
  44. package/dist/bench/code-out/pultra-4/debounce.js +13 -0
  45. package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
  46. package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
  47. package/dist/bench/code-out/run-0/debounce.js +12 -0
  48. package/dist/bench/code-out/run-0/deepClone.js +17 -0
  49. package/dist/bench/code-out/run-0/groupBy.js +14 -0
  50. package/dist/bench/code-out/run-1/debounce.js +13 -0
  51. package/dist/bench/code-out/run-1/deepClone.js +15 -0
  52. package/dist/bench/code-out/run-1/groupBy.js +13 -0
  53. package/dist/bench/code-out/run-2/debounce.js +12 -0
  54. package/dist/bench/code-out/run-2/deepClone.js +18 -0
  55. package/dist/bench/code-out/run-2/groupBy.js +13 -0
  56. package/dist/bench/code-out/run-3/debounce.js +12 -0
  57. package/dist/bench/code-out/run-3/deepClone.js +18 -0
  58. package/dist/bench/code-out/run-3/groupBy.js +14 -0
  59. package/dist/bench/code-out/run-4/debounce.js +12 -0
  60. package/dist/bench/code-out/run-4/deepClone.js +16 -0
  61. package/dist/bench/code-out/run-4/groupBy.js +13 -0
  62. package/dist/cli/banner.js +1 -2
  63. package/dist/cli/common.js +1 -1
  64. package/dist/cli/doctor.js +48 -12
  65. package/dist/cli/hosts.js +1 -2
  66. package/dist/cli/install.js +7 -2
  67. package/dist/cli/manifest.js +25 -5
  68. package/dist/cli/uninstall.js +1 -1
  69. package/dist/extensions/caveman/index.js +6 -2
  70. package/dist/extensions/caveman/rule-lite.md +77 -0
  71. package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
  72. package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
  73. package/dist/extensions/caveman/rule.md +1 -1
  74. package/dist/extensions/combo/index.js +26 -7
  75. package/dist/extensions/pstack/index.js +3 -1
  76. package/dist/extensions/pstack/rule.md +88 -12
  77. package/dist/extensions/rtk/session.js +18 -10
  78. package/dist/extensions/shared/rtk-eligible.js +84 -0
  79. package/dist/extensions/shared/rtk-gain.js +1 -1
  80. package/dist/extensions/shared/sqlite.js +7 -13
  81. package/dist/extensions/shared/status.js +1 -2
  82. package/dist/extensions/shared/usage-ledger.js +102 -20
  83. package/extensions/caveman/index.ts +6 -2
  84. package/extensions/caveman/rule-lite.md +77 -0
  85. package/extensions/caveman/rule-megacave-lite.md +59 -0
  86. package/extensions/caveman/rule-megacave-ultra.md +59 -0
  87. package/extensions/caveman/rule.md +1 -1
  88. package/extensions/combo/index.ts +27 -7
  89. package/extensions/pstack/index.ts +3 -1
  90. package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
  91. package/extensions/pstack/playbooks/autonomous-run.md +13 -0
  92. package/extensions/pstack/playbooks/autopilot-full.md +13 -0
  93. package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
  94. package/extensions/pstack/playbooks/babysit.md +23 -0
  95. package/extensions/pstack/playbooks/bug-fix.md +15 -0
  96. package/extensions/pstack/playbooks/eval.md +25 -0
  97. package/extensions/pstack/playbooks/feature.md +21 -0
  98. package/extensions/pstack/playbooks/hillclimb.md +21 -0
  99. package/extensions/pstack/playbooks/investigation.md +14 -0
  100. package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
  101. package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
  102. package/extensions/pstack/playbooks/orchestrate.md +111 -0
  103. package/extensions/pstack/playbooks/pause-safely.md +10 -0
  104. package/extensions/pstack/playbooks/perf-issue.md +25 -0
  105. package/extensions/pstack/playbooks/prototype.md +14 -0
  106. package/extensions/pstack/playbooks/refactoring.md +16 -0
  107. package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
  108. package/extensions/pstack/playbooks/session-pickup.md +11 -0
  109. package/extensions/pstack/playbooks/shipping.md +17 -0
  110. package/extensions/pstack/playbooks/trace-forensics.md +14 -0
  111. package/extensions/pstack/playbooks/visual-parity.md +11 -0
  112. package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
  113. package/extensions/pstack/principles/attack-the-premise.md +22 -0
  114. package/extensions/pstack/principles/boundary-discipline.md +33 -0
  115. package/extensions/pstack/principles/build-the-lever.md +22 -0
  116. package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
  117. package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
  118. package/extensions/pstack/principles/experience-first.md +18 -0
  119. package/extensions/pstack/principles/explain-the-number.md +16 -0
  120. package/extensions/pstack/principles/fix-root-causes.md +23 -0
  121. package/extensions/pstack/principles/foundational-thinking.md +20 -0
  122. package/extensions/pstack/principles/guard-the-context-window.md +11 -0
  123. package/extensions/pstack/principles/laziness-protocol.md +9 -0
  124. package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
  125. package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
  126. package/extensions/pstack/principles/minimize-reader-load.md +17 -0
  127. package/extensions/pstack/principles/model-the-domain.md +23 -0
  128. package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
  129. package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
  130. package/extensions/pstack/principles/prove-it-works.md +21 -0
  131. package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
  132. package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
  133. package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
  134. package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
  135. package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
  136. package/extensions/pstack/principles/type-system-discipline.md +17 -0
  137. package/extensions/pstack/references/bugbot-triage.md +142 -0
  138. package/extensions/pstack/rule.md +88 -12
  139. package/extensions/pstack/skills/architect/SKILL.md +78 -0
  140. package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
  141. package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
  142. package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
  143. package/extensions/pstack/skills/arena/SKILL.md +47 -0
  144. package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
  145. package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
  146. package/extensions/pstack/skills/bro/SKILL.md +6 -0
  147. package/extensions/pstack/skills/correct/SKILL.md +31 -0
  148. package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
  149. package/extensions/pstack/skills/how/SKILL.md +35 -0
  150. package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
  151. package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
  152. package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
  153. package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
  154. package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
  155. package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
  156. package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
  157. package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
  158. package/extensions/pstack/skills/reflect/SKILL.md +66 -0
  159. package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
  160. package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
  161. package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
  162. package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
  163. package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
  164. package/extensions/pstack/skills/swarm/SKILL.md +45 -0
  165. package/extensions/pstack/skills/tdd/SKILL.md +41 -0
  166. package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
  167. package/extensions/pstack/skills/unslop/SKILL.md +61 -0
  168. package/extensions/pstack/skills/why/SKILL.md +97 -0
  169. package/extensions/pstack/skills/why/references/epistemics.md +71 -0
  170. package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
  171. package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
  172. package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
  173. package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
  174. package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
  175. package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
  176. package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
  177. package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
  178. package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
  179. package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
  180. package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
  181. package/extensions/rtk/session.ts +17 -9
  182. package/extensions/shared/rtk-eligible.ts +83 -0
  183. package/extensions/shared/rtk-gain.ts +1 -1
  184. package/extensions/shared/sqlite.ts +7 -13
  185. package/extensions/shared/status.ts +1 -2
  186. package/extensions/shared/usage-ledger.ts +97 -19
  187. package/extensions/timing/index.ts +1 -2
  188. package/package.json +7 -4
@@ -0,0 +1,66 @@
1
+ # Sentry Error History
2
+
3
+ ## How to search it
4
+
5
+ Use the Sentry MCP.
6
+
7
+ 1. **Orient.** If you don't know the project slug and organization:
8
+
9
+ ```
10
+ find_organizations
11
+ find_projects
12
+ ```
13
+
14
+ 2. **Search for issues related to the target.**
15
+
16
+ ```
17
+ search_issues (natural language, e.g., "errors in PaymentService timeout", "unhandled exceptions in uploadFile")
18
+ ```
19
+
20
+ Build queries from the exception class names the target handles, its function or class name, the error message strings it checks for, and its file path.
21
+
22
+ 3. **Narrow by release and time window.**
23
+
24
+ ```
25
+ search_issue_events (filter by release, time, environment, trace ID, tags)
26
+ get_issue_tag_values (for an issue, see distribution across versions, users, environments)
27
+ ```
28
+
29
+ For a suspected issue, check **first seen** and **last seen** (does the stop line up with the target's ship date?), the **affected releases** (which saw it, and which was the fix?), and the **frequency trajectory** (did it spike, then get resolved?).
30
+
31
+ 4. **Pull the full event for context.**
32
+
33
+ ```
34
+ get_sentry_resource (pass a Sentry URL or type+ID)
35
+ ```
36
+
37
+ Does the stack trace pass through the target code? Do the tags and breadcrumbs match the conditions the target defends against?
38
+
39
+ 5. **Check releases that landed near the target.**
40
+
41
+ ```
42
+ find_releases (around the commit date of the target)
43
+ ```
44
+
45
+ Cross-reference release version with the PR's merge date.
46
+
47
+ 6. **Use Seer sparingly.**
48
+
49
+ ```
50
+ analyze_issue_with_seer
51
+ ```
52
+
53
+ Seer's AI root-cause analyses are a hypothesis generator. Treat them as inference, not authoritative. The actual events and stack traces are the primary evidence.
54
+
55
+ ## What to return
56
+
57
+ For each relevant issue:
58
+ - Issue ID and title
59
+ - Project and organization
60
+ - First seen / last seen timestamps
61
+ - Event count (and sampling rate if known)
62
+ - Affected releases
63
+ - A representative stack trace snippet showing relevance to the target (verbatim excerpt, not summary)
64
+ - First/last-seen correlation with the target's ship date
65
+ - Link to the issue
66
+ - Any author comments or resolution notes
@@ -0,0 +1,17 @@
1
+ # Slack Conversations
2
+
3
+ ## How to search it
4
+
5
+ Slack MCP tools vary. Check which Slack MCP is available and inspect its tool schema first. It may require `mcp_auth`. If authentication fails, stop and report the gap.
6
+
7
+ 1. **Author-bounded search.** Messages from the PR author around the PR merge date. Limits scope dramatically and often hits gold.
8
+ 3. **PR URL search.** Slack often links PRs when they're reviewed or discussed. Search for the PR URL (or just `/pull/<number>`).
9
+ 4. **Error string search.** If the code handles a specific error, search for the error string. Incident threads often surface.
10
+
11
+ ## Common pitfalls
12
+
13
+ - **Channel archaeology limits.** Very old messages may be gone due to retention policies. If you can't find anything before a certain date, note the retention cliff.
14
+ - **Unsearched DMs.** Many decisions happen in DMs that aren't searchable. You'll miss them. That's a known limitation.
15
+ - **Speculative jokes as "decisions."** Slack is casual. "Lol just do the thing" isn't a decision, even if it preceded the commit. Look for considered discussion.
16
+ - **Context collapse in single messages.** Without the thread, a single message often reads differently than in context. Always fetch threads.
17
+ - **Auth failures.** If the MCP isn't authenticated, stop. Don't make up findings. Report that Slack wasn't searchable.
@@ -0,0 +1,129 @@
1
+ # Synthesizer Prompt Template
2
+
3
+ Fill in the placeholders.
4
+
5
+ ---
6
+
7
+ You are answering a "why" question about a piece of code by synthesizing findings from investigators who searched different historical sources (source control, issue / ticket tracker, long-form documents, real-time team chat, infrastructure observability, error / exception tracking, product analytics warehouse, and code comments). Produce a confidence-weighted, evidence-cited narrative that says honestly what the evidence supports, and just as honestly what it doesn't.
8
+
9
+ ## The Question
10
+
11
+ > {QUESTION}
12
+
13
+ ## The Code Anchor
14
+
15
+ **Target files:** {FILES_WITH_LINE_RANGES}
16
+
17
+ **Key symbols:** {SYMBOLS}
18
+
19
+ ## Investigator Findings
20
+
21
+ {ALL_INVESTIGATOR_FINDINGS}
22
+
23
+ ## Sources That Weren't Searched
24
+
25
+ {SKIPPED_SOURCES_WITH_REASONS}
26
+
27
+ ## Epistemics Framework
28
+
29
+ You MUST follow `references/epistemics.md`. Read it in full before writing. The key rules:
30
+
31
+ 1. Every claim must sit in one tier: **Direct**, **Supported**, **Inferred**, **Speculative**, **Unknown**. The tier sets its section and its phrasing.
32
+ 2. Every Direct/Supported claim must have a citation (PR #, ticket ID, doc URL, chat permalink, commit hash, or file:line).
33
+ 3. Inferred and Speculative claims must use hedged language ("appears to", "likely", "suggests", "one possibility is").
34
+ 4. Never cite code as evidence for its own intent.
35
+ 5. Document gaps. Don't fill them with plausible-sounding guesses.
36
+ 6. Treat a hypothesis embedded in the user's question as a candidate, not a conclusion. Check it independently.
37
+
38
+ ## Instructions
39
+
40
+ 1. **Read all investigator findings** and weigh the evidence yourself. Investigators gathered raw evidence, not conclusions.
41
+ 2. **Merge overlaps.** When several investigators cite the same PR, ticket, or doc, give it one authoritative reference.
42
+ 3. **Surface contradictions.** When two items disagree, show both. Don't pick one.
43
+ 4. **Calibrate each claim.** Find its evidence and tier. Direct: cite it and state it plainly. Inferred: hedge it and show the inference. Speculative: mark it. No evidence: put it in the gaps.
44
+ 5. **Verify citations by spot-checking.** If you're uncertain a cited item exists or says what the investigator claims, check it by reading the codebase or calling MCP tools. Don't propagate errors. Do not write files, commit, or modify external state.
45
+ 6. **Don't overreach.** The user will act on this. Leave an open question open rather than fill it with a confident guess.
46
+
47
+ ## Output Format
48
+
49
+ Write for the user, in this exact structure:
50
+
51
+ ---
52
+
53
+ ### The Question
54
+
55
+ Restate the user's question in one or two sentences.
56
+
57
+ ### The Code in Question
58
+
59
+ File paths, line ranges, key symbols. Two or three lines to orient a cold reader.
60
+
61
+ ### What We Found
62
+
63
+ Claims with textual evidence, each quoted or paraphrased and cited precisely:
64
+
65
+ - **[Direct]** {Claim}. Source: [PR #123](url) / ticket ID / file:line. {Brief quote or paraphrase.}
66
+ - **[Supported]** {Claim}. Evidence: {list of items and what each contributes}.
67
+
68
+ `[Direct]` is single-source, explicit evidence. `[Supported]` is multiple indirect items converging on a conclusion.
69
+
70
+ ### What We Can Reasonably Infer
71
+
72
+ Claims stated nowhere but well-supported by indirect evidence. Each bullet must make the inference chain visible ("Given A and B, it's likely that C") and must use hedged language ("appears to", "likely", "suggests", "is consistent with"):
73
+
74
+ - **[Inferred]** {Hedged claim}. Reasoning: {the specific evidence and the inference step}.
75
+
76
+ Skip this section if there's nothing to infer.
77
+
78
+ ### Competing Hypotheses
79
+
80
+ If the evidence fits multiple stories, present each. Don't force a winner the record doesn't support. For each hypothesis:
81
+
82
+ - **Hypothesis:** {one-sentence statement}
83
+ - **Evidence for:** {specific items}
84
+ - **Evidence against or missing:** {what would need to be true but isn't, or what counter-signals exist}
85
+
86
+ Skip this section if there's a single clear answer.
87
+
88
+ ### What We Don't Know
89
+
90
+ Specific gaps: "We searched the issue tracker for [query1], [query2], [query3] and found no issue discussing the rate-limit threshold", not "We don't know why." Include:
91
+
92
+ - Questions that went unanswered
93
+ - Searches that returned nothing
94
+ - Sources that were unavailable, and why (such as a missing real-time team chat MCP)
95
+ - People who would likely know but whom you can't ask
96
+
97
+ ### Sources Consulted
98
+
99
+ What was actually searched, so the user can judge coverage and redirect:
100
+
101
+ - **Source control history**: {file paths}, {number of commits reviewed}, PRs #{numbers}, and code comments searched. Or "Not searched. This should not happen because git and `gh` are always expected."
102
+ - **Issue / ticket tracker**: {ticket IDs and keyword searches}. Or "Not searched. No matching MCP available in this environment."
103
+ - **Long-form documents**: {page titles and search queries}. Or "Not searched. No matching MCP available in this environment."
104
+ - **Real-time team chat**: {channels searched, date ranges, queries}. Or "Not searched. No matching MCP available in this environment."
105
+ - **Infrastructure observability**: {dashboards, monitors, metrics, logs, traces, or incidents searched}. Or "Not searched. No matching MCP available in this environment."
106
+ - **Error / exception tracking**: {issues, events, or releases searched}. Or "Not searched. No matching MCP available in this environment."
107
+ - **Product analytics warehouse**: {fully-qualified tables queried, the time windows, and the numeric summaries (counts, percentiles, first/last-seen timestamps) that bore on the question}. Or "Not searched. No matching MCP available in this environment."
108
+
109
+ ### Confidence Summary
110
+
111
+ One or two sentences on overall confidence, e.g.:
112
+
113
+ > "The core rationale (A) is well-supported by direct PR and ticket evidence. The specific threshold value (100) is inferred from the surrounding context but not explicitly documented. The question of whether this was driven by a customer request could not be answered. No relevant issue tracker or long-form doc content surfaced, and real-time team chat search was unavailable."
114
+
115
+ ---
116
+
117
+ ## Quality Check Before Returning
118
+
119
+ Revise if any item fails:
120
+
121
+ 1. Does every claim in "What We Found" have a citation? If not, add one or move it to "Inferred" or "Hypotheses."
122
+ 2. Is the phrasing tier-appropriate? (Direct claims can use "because". Inferred claims cannot.)
123
+ 3. Did you surface every contradiction you noticed, or quietly pick one?
124
+ 4. Does "What We Don't Know" name specific gaps? If it's empty or missing, be suspicious. Historical investigations almost always have gaps.
125
+ 5. Did you check any hypothesis embedded in the question against the evidence rather than rubber-stamp it?
126
+ 6. Did you cite code as evidence for its own intent? Remove it. Code is mechanics, not motivation.
127
+ 7. Is the tone calibrated? A confident-sounding answer on weak evidence is the exact failure this skill exists to prevent.
128
+
129
+ The value of this output is its honesty, not its authority. A reader who takes it to the original author, an engineering lead, or a product manager should know what's known, what's inferred, and what's missing, so they can ask the right follow-up questions. Optimize for being useful, not for looking decisive.
@@ -23,8 +23,11 @@ function setRtkProcessEnabled(enabled: boolean): void {
23
23
  else process.env.RTK_DISABLED = '1';
24
24
  }
25
25
 
26
- // rtk's contract: name the rewritten surface, never the binary — a hand-typed `rtk` fights the rewrite.
27
- const RTK_PROMPT = `RTK guidance active. What you run is condensed automatically: searches (grep, rg), listings (ls, find, cat as read), package and test runs (bun, npm run, pnpm run), version control (git), and type and lint checks (tsc, eslint). Every signal kept, costly noise dropped. Treat a condensed result as complete. Re-run something unfiltered only when its result is empty, garbled, or contradicts its exit code. A command with no condensed form passes through unchanged.`;
26
+ // rtk's contract: name the rewritten surface first and the binary second — a hand-typed `rtk` in front of a
27
+ // rewritten command fights the rewrite, and the direct calls cover only what the rewrite cannot express.
28
+ const RTK_PROMPT = `RTK guidance active. What you run is condensed automatically, in the host's own rewritten form: searches (grep, rg, ast-grep), listings (ls, find, tree), reads (cat), package and test runs (bun, npm, pnpm, npx, cargo, go, pytest, vitest, jest, ctest, tsc), version control and forges (git, gh, glab), containers and clusters (docker, kubectl), cloud and platform CLIs (aws, oc, dotnet), PHP tooling (phpunit, pest, phpstan, pint, ecs), lint and format (eslint, ruff, mypy, prettier, sqlfluff, playwright, prisma, next), and data tools (jq, curl, wget, psql, wc, make, tail, docker logs). The runner's own subcommand, such as \`rtk bun run test\`, keeps the failing test name, the assertion, and the file and line. Every signal kept, costly noise dropped. Treat a condensed result as complete. Re-run something unfiltered only when its result is empty, garbled, or contradicts its exit code.
29
+
30
+ Call rtk directly for what the rewrite cannot express: \`rtk read <file> --level aggressive\` for an overview, \`--max-lines N\` for a preview, \`rtk smart <file>\` for a two-line summary, \`rtk summary <cmd>\` for a heuristic one, \`rtk err <cmd>\` for errors only, \`rtk log <file>\` to dedupe logs, \`rtk json <file>\` with \`--keys-only\` for shape, plus \`rtk deps\`, \`rtk diff\`, \`rtk env\`, \`rtk ast-grep\`, and \`rtk test <cmd>\` for a runner without its own form. A command with no condensed form passes through unchanged, so wrap it: \`rtk err node build.js\`, and \`rtk find -name '*.ts'\` in place of fd. Never type \`rtk\` in front of a command listed above.`;
28
31
 
29
32
  export default function rtkSessionExtension(pi: ExtensionApi): void {
30
33
  let enabled = DEFAULT_ENABLED;
@@ -73,27 +76,32 @@ export default function rtkSessionExtension(pi: ExtensionApi): void {
73
76
  pi.registerTool?.({
74
77
  name: 'rtk_run',
75
78
  label: 'RTK Run',
76
- description: 'Run the installed `rtk` binary for compact command output when RTK mode is enabled.',
79
+ description: 'Run a shell command through the installed `rtk` binary when RTK mode is on, and run it plainly when RTK mode is off.',
77
80
  parameters: stringArrayToolParams(pi, 'args', {
78
81
  minItems: 1,
79
82
  description: "Arguments passed to rtk, e.g. ['git','status'] or ['read','src/index.ts']",
80
83
  }),
81
84
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
82
85
  // A subagent's own flag sits at its start-up default, so honour shared state too.
83
- if (!(enabled || getSharedComboState().rtk === 'on')) return fail('RTK mode is off. Run /rtk on for this session, or use bash explicitly.');
86
+ const rtkOn = enabled || getSharedComboState().rtk === 'on';
84
87
  if (typeof pi.exec !== 'function') return fail('This host cannot run rtk.');
85
- onUpdate?.({ content: [{ type: 'text', text: `rtk ${params.args.join(' ')}` }], details: { phase: 'start' } });
88
+ // RTK off: run the command as given rather than answering "mode is off". The tool is the only one left under --no-tools, and a refusal there made the model spend turns re-calling it. rtk is the condenser, never the command.
89
+ const cwd = ctx?.cwd || pi.cwd;
90
+ const bin = rtkOn ? resolveRtkBinary() : null;
91
+ if (rtkOn && bin === null) return fail('RTK mode is on but the rtk binary is not installed. Run: tersio update');
92
+ const argv = params.args;
93
+ onUpdate?.({ content: [{ type: 'text', text: `${bin === null ? '' : 'rtk '}${argv.join(' ')}` }], details: { phase: 'start' } });
86
94
  try {
87
95
  // A GUI-launched host may lack ~/.bun/bin on PATH, so prefer the resolved path.
88
- const result = await pi.exec(resolveRtkBinary() ?? 'rtk', params.args, { signal, cwd: ctx?.cwd || pi.cwd });
96
+ const result = await pi.exec(bin ?? argv[0], argv, { signal, cwd });
89
97
  const text = [result.stdout, result.stderr].filter(Boolean).join('\n');
90
98
  return {
91
99
  isError: result.code !== 0,
92
- content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
93
- details: { code: result.code, killed: result.killed, enabled },
100
+ content: [{ type: 'text', text: text || `exited ${result.code}` }],
101
+ details: { code: result.code, killed: result.killed, enabled, condensed: bin !== null },
94
102
  };
95
103
  } catch (e) {
96
- return fail(`rtk could not run: ${e instanceof Error ? e.message : String(e)}`);
104
+ return fail(`${bin === null ? argv[0] : 'rtk'} could not run: ${e instanceof Error ? e.message : String(e)}`);
97
105
  }
98
106
  },
99
107
  });
@@ -0,0 +1,83 @@
1
+ // Eligible command heads, taken from rtk's own contract rather than a hardcoded guess.
2
+ // `rtk rewrite <probe>` prints a rewrite and exits 0 or 3 when rtk handles the command,
3
+ // and exits 1 when no equivalent exists. Each probe below is a representative workload
4
+ // command, because subcommand-scoped tools such as `gh` rewrite `gh pr list` but not `gh`.
5
+ import { execFileSync } from 'node:child_process';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { normalizeRtkVersion, tersioDataPath } from './utils.ts';
9
+
10
+ /** Verified against rtk 1.0.1; the drift test fails when rtk's set moves. */
11
+ export const FALLBACK_ELIGIBLE_HEADS: readonly string[] = [
12
+ 'rtk', 'git', 'gh', 'glab', 'docker', 'kubectl', 'psql', 'aws', 'bunx', 'npx',
13
+ 'pytest', 'mypy', 'cat', 'grep', 'rg', 'find', 'tree', 'diff', 'ls', 'ast-grep',
14
+ 'wget', 'prettier', 'playwright', 'prisma', 'jest', 'vitest', 'ctest', 'lint',
15
+ 'wc', 'tsc', 'curl', 'cargo', 'npm', 'pnpm', 'bun', 'go', 'ruff', 'next',
16
+ 'dotnet', 'oc', 'jq', 'make',
17
+ ];
18
+
19
+ const PROBES: Record<string, string> = {
20
+ git: 'git log', gh: 'gh pr list', glab: 'glab mr list', docker: 'docker ps',
21
+ kubectl: 'kubectl get pods', psql: 'psql --version', aws: 'aws --version',
22
+ bunx: 'bunx --version', npx: 'npx --version', pytest: 'pytest --version',
23
+ mypy: 'mypy --version', cat: 'cat README.md', grep: 'grep rtk README.md',
24
+ rg: 'rg rtk README.md', find: 'find . -name x', tree: 'tree .', diff: 'diff a b',
25
+ ls: 'ls -la', 'ast-grep': 'ast-grep --version', wget: 'wget --version',
26
+ prettier: 'prettier --version', playwright: 'playwright --version', prisma: 'prisma --version',
27
+ jest: 'jest --version', vitest: 'vitest --version', ctest: 'ctest --version',
28
+ lint: 'lint .', wc: 'wc -l x', tsc: 'tsc --version', curl: 'curl --version',
29
+ cargo: 'cargo test', npm: 'npm run build', pnpm: 'pnpm run build', bun: 'bun run test',
30
+ go: 'go test ./...', ruff: 'ruff check .', next: 'next build', dotnet: 'dotnet build',
31
+ oc: 'oc get pods', jq: 'jq . x', make: 'make',
32
+ };
33
+
34
+ function probe(bin: string, command: string): boolean {
35
+ try {
36
+ const out = execFileSync(bin, ['rewrite', command], { encoding: 'utf8', timeout: 5000, windowsHide: true });
37
+ return out.trim().length > 0;
38
+ } catch (e) {
39
+ // Non-zero exit throws; exit 3 still prints a rewrite, exits 1 and 2 do not.
40
+ const err = e as { status?: number; stdout?: string | Buffer };
41
+ if (err.status !== 0 && err.status !== 3) return false;
42
+ return String(err.stdout ?? '').trim().length > 0;
43
+ }
44
+ }
45
+
46
+ /** One set of heads for this rtk build, read straight from its rewrite contract. */
47
+ export function deriveEligibleHeads(bin: string): Set<string> {
48
+ const heads = new Set<string>(['rtk']);
49
+ for (const [head, command] of Object.entries(PROBES)) {
50
+ if (probe(bin, command)) heads.add(head);
51
+ }
52
+ return heads;
53
+ }
54
+
55
+ function rtkVersion(bin: string): string {
56
+ try {
57
+ return normalizeRtkVersion(execFileSync(bin, ['--version'], { encoding: 'utf8', timeout: 5000, windowsHide: true }));
58
+ } catch {
59
+ return '';
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Heads rtk rewrites, cached per binary version so an upgrade re-derives once.
65
+ * Without a binary, or on any read failure, the verified fallback stands.
66
+ */
67
+ export function eligibleHeads(bin: string | null): ReadonlySet<string> {
68
+ if (!bin) return new Set(FALLBACK_ELIGIBLE_HEADS);
69
+ const version = rtkVersion(bin);
70
+ const file = tersioDataPath('rtk-eligible.json', 'rtk-eligible.json');
71
+ try {
72
+ const cached = JSON.parse(fs.readFileSync(file, 'utf8')) as { version?: unknown; heads?: unknown };
73
+ if (cached.version === version && Array.isArray(cached.heads) && cached.heads.every((h) => typeof h === 'string')) {
74
+ return new Set(cached.heads as string[]);
75
+ }
76
+ } catch { /* derive below */ }
77
+ const heads = deriveEligibleHeads(bin);
78
+ try {
79
+ fs.mkdirSync(path.dirname(file), { recursive: true });
80
+ fs.writeFileSync(file, `${JSON.stringify({ version, heads: [...heads] })}\n`, 'utf8');
81
+ } catch { /* the cache is best-effort */ }
82
+ return heads;
83
+ }
@@ -1,4 +1,4 @@
1
- // Savings read from rtk history, never estimated; modes bench in docs/BENCHMARK.md.
1
+ // Savings read from rtk history, never estimated; modes bench in docs/BENCHMARK_GUIDE.md.
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
@@ -1,14 +1,9 @@
1
- // One place that runs SQL against a database file.
2
- //
3
- // Bun and Node 22.5+ carry SQLite in process, so the `sqlite3` binary is a fallback rather than a
4
- // requirement: hosts without it kept silently reading nothing. Reads come back exactly as the sqlite3
5
- // CLI printed them -- rows on their own lines, cells joined by tabs, NULL an empty cell, one trailing
6
- // newline -- because every caller already parses that shape. The engine rungs take one statement per
7
- // read, so a caller with several queries calls once per query.
8
- //
9
- // The write path leaves the journal mode alone. The store's own schema sets WAL so a reader (the
10
- // dashboard) never blocks a writer, and changing the mode needs exclusive access: with a reader holding
11
- // the file the pragma fails, which is a failure reported after the caller's rows have already committed.
1
+ // One place that runs SQL against a database file. Bun and Node 22.5+ carry SQLite in process, so the
2
+ // `sqlite3` binary is a fallback: hosts without it kept silently reading nothing. Reads return the sqlite3
3
+ // CLI shape (one row per line, cells tab-joined, NULL an empty cell) because every caller parses that.
4
+ // The write path leaves the journal mode alone: the schema sets WAL so a reader never blocks a writer,
5
+ // and the pragma needs exclusive access, so with a reader holding the file it fails after the caller's
6
+ // rows have already committed (see docs/ERRORS.md).
12
7
  import { execFileSync } from 'node:child_process';
13
8
  import fs from 'node:fs';
14
9
  import { createRequire } from 'node:module';
@@ -65,8 +60,7 @@ function engine(): Engine | null {
65
60
  } catch { /* not Bun, or no SQLite in this build */ }
66
61
  try {
67
62
  const { DatabaseSync } = requireOptional('node:sqlite') as { DatabaseSync: new (db: string, opts?: { readOnly?: boolean }) => NodeDatabase };
68
- // Rows arrive keyed by column name unless the statement returns arrays. A duplicate name collapses
69
- // its key and shifts every later cell, so a Node that cannot return arrays is no engine at all.
63
+ // Rows arrive keyed by column name unless the statement returns arrays: a duplicate name collapses its key and shifts every later cell, so a Node that cannot return arrays is no engine.
70
64
  const probe = new DatabaseSync(':memory:');
71
65
  try {
72
66
  if (typeof probe.prepare('SELECT 1').setReturnArrays !== 'function') throw new Error('no positional rows');
@@ -38,8 +38,7 @@ let remembered: ExtensionCtx | undefined;
38
38
  let lastText: string | undefined;
39
39
  let lastUi: UiApi | undefined;
40
40
 
41
- // Paint the host footer bar; same text on the same UI is skipped, a new ctx repaints it.
42
- // One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
41
+ // Paint the host footer bar; same text on the same UI is skipped. One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
43
42
  export function announceStatus(ctx: ExtensionCtx | undefined): void {
44
43
  const state = getSharedComboState();
45
44
  const text = formatStatus(state);
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, tersioDataPath } from './utils.ts';
6
+ import { eligibleHeads } from './rtk-eligible.ts';
6
7
  import { sqliteRead } from './sqlite.ts';
7
8
 
8
9
  export type UsageKind = 'command' | 'toggle' | 'install' | 'update' | 'rtk-audit';
@@ -273,6 +274,55 @@ export function dayKey(ts: string | number): string | null {
273
274
  const p = (n: number): string => String(n).padStart(2, '0');
274
275
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
275
276
  }
277
+
278
+ // OpenCode tool parts carry the command in the message blob; sqliteRead is line-based, so newlines are flattened in SQL.
279
+ const OC_BASH_CMD = "replace(replace(json_extract(j.value,'$.state.input.command'), char(10), ' '), char(13), ' ')";
280
+ const OC_BASH_TOOL = "json_extract(j.value,'$.type')='tool' AND (json_extract(j.value,'$.name')='bash' OR json_extract(j.value,'$.name')='shell')";
281
+ const ocBashLegacy = `SELECT message.id, json_extract(message.data,'$.sessionID'), ${OC_BASH_CMD} FROM message, json_each(message.data,'$.content') j WHERE json_extract(message.data,'$.role')='assistant' AND ${OC_BASH_TOOL}`;
282
+ const ocBashCurrent = `SELECT session_message.id, session_message.session_id, ${OC_BASH_CMD} FROM session_message, json_each(session_message.data,'$.content') j WHERE session_message.type='assistant' AND ${OC_BASH_TOOL}`;
283
+
284
+ export interface OpencodeBashRow {
285
+ id: string;
286
+ sessionId: string;
287
+ command: string;
288
+ }
289
+
290
+ /** Every executed bash command in an OpenCode db, v2 first and the legacy table second. */
291
+ export function readOpencodeBashRows(db: string): OpencodeBashRow[] {
292
+ const rows: OpencodeBashRow[] = [];
293
+ try {
294
+ if (!fs.existsSync(db)) return rows;
295
+ let out = '';
296
+ try { out += sqliteRead(db, ocBashCurrent); } catch { /* no v2 table */ }
297
+ try { out += sqliteRead(db, ocBashLegacy); } catch { /* no legacy table */ }
298
+ for (const line of out.split('\n')) {
299
+ if (!line.trim()) continue;
300
+ const [id, sessionId, command] = line.split('\t');
301
+ if (typeof command !== 'string' || !command.trim()) continue;
302
+ rows.push({ id: id ?? '', sessionId: sessionId ?? '', command });
303
+ }
304
+ } catch { /* an unreadable db is a skip, not a failure */ }
305
+ return rows;
306
+ }
307
+
308
+ // Pre-v2 OpenCode stores one message per JSON file; the same tool-part shape holds the command.
309
+ function opencodeFileBash(file: string): { commands: string[]; sessionId: string } {
310
+ try {
311
+ const message = JSON.parse(fs.readFileSync(file, 'utf8')) as { sessionID?: unknown; content?: unknown };
312
+ const parts = Array.isArray(message.content) ? message.content : [];
313
+ const commands: string[] = [];
314
+ for (const part of parts) {
315
+ const p = part as { type?: unknown; name?: unknown; state?: { input?: { command?: unknown } } };
316
+ if (p?.type !== 'tool') continue;
317
+ if (p.name !== 'bash' && p.name !== 'shell') continue;
318
+ const command = p.state?.input?.command;
319
+ if (typeof command === 'string' && command) commands.push(command);
320
+ }
321
+ return { commands, sessionId: typeof message.sessionID === 'string' ? message.sessionID : '' };
322
+ } catch {
323
+ return { commands: [], sessionId: '' };
324
+ }
325
+ }
276
326
  export function walkJsonl(dir: string, out: string[], cap: number, ext = '.jsonl'): void {
277
327
  let entries: fs.Dirent[];
278
328
  try {
@@ -290,11 +340,6 @@ export function walkJsonl(dir: string, out: string[], cap: number, ext = '.jsonl
290
340
  // Recent rows are a newest-first window over lifetime history; aggregates keep everything.
291
341
  export const RECENT_LIMIT = 2000;
292
342
  const FREE_SUFFIX = /(?::free|-free)$/i;
293
- const RTK_ELIGIBLE_HEADS = new Set([
294
- 'rtk', 'git', 'grep', 'rg', 'find', 'cat', 'ls', 'tree', 'diff', 'log',
295
- 'npm', 'npx', 'pnpm', 'bun', 'bunx', 'cargo', 'go', 'python', 'pytest',
296
- 'ruff', 'mypy', 'docker', 'kubectl', 'psql', 'aws', 'gh', 'glab', 'wc',
297
- ]);
298
343
  // Parse buffer shared by live reads and usage.db syncs, so the store is a cache and never a fork.
299
344
  export interface SessionAccum {
300
345
  byModel: Record<string, TokenBreakdown>;
@@ -650,17 +695,28 @@ export function agentOfSessionText(text: string): string {
650
695
  return 'main';
651
696
  }
652
697
 
653
- const adoptionFileCache = new Map<string, { size: number; mtimeMs: number; counts: { bashCalls: number; eligibleCalls: number; rtkCalls: number } }>();
698
+ const adoptionFileCache = new Map<string, { size: number; mtimeMs: number; heads: string; counts: BashCounts }>();
654
699
 
655
- function parseAdoptionFile(file: string): { counts: { bashCalls: number; eligibleCalls: number; rtkCalls: number }; sessions: number } {
656
- const counts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
700
+ interface BashCounts { bashCalls: number; eligibleCalls: number; rtkCalls: number }
701
+
702
+ function countBashCommands(commands: readonly string[], eligible: ReadonlySet<string>, counts: BashCounts): void {
703
+ for (const command of commands) {
704
+ counts.bashCalls += 1;
705
+ const head = leadBinary(command);
706
+ if (eligible.has(head)) counts.eligibleCalls += 1;
707
+ if (head === 'rtk') counts.rtkCalls += 1;
708
+ }
709
+ }
710
+
711
+ function parseAdoptionFile(file: string, eligible: ReadonlySet<string>): { counts: BashCounts; sessions: number } {
712
+ const counts: BashCounts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
657
713
  let text: string;
658
714
  try {
659
715
  text = fs.readFileSync(file, 'utf8');
660
716
  } catch {
661
717
  return { counts, sessions: 0 };
662
718
  }
663
- let sawBash = false;
719
+ const commands: string[] = [];
664
720
  for (const line of text.split('\n')) {
665
721
  if (!line.includes('tool_execution_start')) continue;
666
722
  try {
@@ -671,15 +727,11 @@ function parseAdoptionFile(file: string): { counts: { bashCalls: number; eligibl
671
727
  };
672
728
  if (row.type !== 'custom' || row.customType !== 'tool_execution_start' || row.data?.toolName !== 'bash') continue;
673
729
  const command = row.data.args?.command;
674
- if (typeof command !== 'string') continue;
675
- sawBash = true;
676
- counts.bashCalls += 1;
677
- const head = leadBinary(command);
678
- if (RTK_ELIGIBLE_HEADS.has(head)) counts.eligibleCalls += 1;
679
- if (head === 'rtk') counts.rtkCalls += 1;
730
+ if (typeof command === 'string') commands.push(command);
680
731
  } catch { /* skip corrupt line */ }
681
732
  }
682
- return { counts, sessions: sawBash ? 1 : 0 };
733
+ countBashCommands(commands, eligible, counts);
734
+ return { counts, sessions: commands.length > 0 ? 1 : 0 };
683
735
  }
684
736
 
685
737
  export function clearRtkAdoptionCache(): void {
@@ -687,6 +739,10 @@ export function clearRtkAdoptionCache(): void {
687
739
  }
688
740
 
689
741
  export function readRtkAdoption(): RtkAdoption {
742
+ // The eligible set comes from rtk's rewrite contract, cached per binary version.
743
+ const eligible = eligibleHeads(resolveRtkBinary());
744
+ // A cached count is only valid for the head set that produced it.
745
+ const headsKey = [...eligible].sort().join(',');
690
746
  const files: string[] = [];
691
747
  for (const dir of sessionsDirs()) walkJsonl(dir, files, 2000);
692
748
  const live = new Set(files);
@@ -705,9 +761,9 @@ export function readRtkAdoption(): RtkAdoption {
705
761
  continue;
706
762
  }
707
763
  let entry = adoptionFileCache.get(file);
708
- if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs) {
709
- const parsed = parseAdoptionFile(file);
710
- entry = { size: stat.size, mtimeMs: stat.mtimeMs, counts: parsed.counts };
764
+ if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs || entry.heads !== headsKey) {
765
+ const parsed = parseAdoptionFile(file, eligible);
766
+ entry = { size: stat.size, mtimeMs: stat.mtimeMs, heads: headsKey, counts: parsed.counts };
711
767
  adoptionFileCache.set(file, entry);
712
768
  sessions += parsed.sessions;
713
769
  } else if (entry.counts.bashCalls > 0) {
@@ -717,6 +773,28 @@ export function readRtkAdoption(): RtkAdoption {
717
773
  eligibleCalls += entry.counts.eligibleCalls;
718
774
  rtkCalls += entry.counts.rtkCalls;
719
775
  }
776
+ // OpenCode keeps its own store: a v2 sqlite db, or one JSON file per message before v2.
777
+ const opencode: BashCounts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
778
+ const opencodeSessions = new Set<string>();
779
+ if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DB !== undefined) {
780
+ for (const row of readOpencodeBashRows(opencodeDbPath())) {
781
+ countBashCommands([row.command], eligible, opencode);
782
+ if (row.sessionId) opencodeSessions.add(row.sessionId);
783
+ }
784
+ }
785
+ const ocFiles: string[] = [];
786
+ if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DIR !== undefined) {
787
+ walkJsonl(opencodeSessionsDir(), ocFiles, 5000, '.json');
788
+ for (const file of ocFiles) {
789
+ const parsed = opencodeFileBash(file);
790
+ countBashCommands(parsed.commands, eligible, opencode);
791
+ if (parsed.sessionId) opencodeSessions.add(parsed.sessionId);
792
+ }
793
+ }
794
+ bashCalls += opencode.bashCalls;
795
+ eligibleCalls += opencode.eligibleCalls;
796
+ rtkCalls += opencode.rtkCalls;
797
+ sessions += opencodeSessions.size;
720
798
  const missedCalls = Math.max(0, eligibleCalls - rtkCalls);
721
799
  return { sessions, bashCalls, eligibleCalls, rtkCalls, missedCalls, adoptionPct: eligibleCalls ? (rtkCalls / eligibleCalls) * 100 : 0 };
722
800
  }
@@ -1,5 +1,4 @@
1
- // pi records a response's total duration but no first-token latency, so TTFT reads null there.
2
- // omp writes its own `ttft` and OpenCode records `time.streamed`, so this fills the gap only for pi.
1
+ // pi records total duration but no first-token latency, so TTFT reads null there; omp writes its own `ttft` and OpenCode records `time.streamed`, so this fills the gap only for pi.
3
2
  import type { ExtensionApi } from '../shared/types.ts';
4
3
 
5
4
  interface AssistantMessage {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.25.6",
4
- "description": "✂️ Token saver and usage tracker for coding agents: terse Caveman replies, compact RTK shell output, lean Ponytail code calls, and a gain dashboard — one-command combo presets over one shared extension tree.",
3
+ "version": "2.25.7",
4
+ "description": "✂️ Token saver and usage tracker for coding agents: terse Caveman replies, compact RTK shell output, lean Ponytail code, pstack rigor, and a gain dashboard — one-command combos over one shared extension tree.",
5
5
  "keywords": [
6
6
  "token-saving",
7
7
  "ai-tools",
@@ -128,13 +128,16 @@
128
128
  "dashboard/dist/**/*",
129
129
  "extensions/**/*.ts",
130
130
  "extensions/caveman/rule.md",
131
+ "extensions/caveman/rule-lite.md",
132
+ "extensions/caveman/rule-megacave-lite.md",
133
+ "extensions/caveman/rule-megacave-ultra.md",
131
134
  "extensions/caveman/rule-ultra.md",
132
135
  "extensions/caveman/rule-megacave.md",
133
- "extensions/pstack/rule.md"
136
+ "extensions/pstack/**/*.md"
134
137
  ],
135
138
  "scripts": {
136
139
  "clean": "rm -rf dist dashboard/dist",
137
- "build": "tsc && bun run build:dashboard && cp extensions/caveman/*.md dist/extensions/caveman/ && cp extensions/pstack/*.md dist/extensions/pstack/",
140
+ "build": "tsc && bun run build:dashboard && cp extensions/caveman/*.md dist/extensions/caveman/ && cp extensions/pstack/*.md dist/extensions/pstack/ && cp -R extensions/pstack/principles extensions/pstack/playbooks extensions/pstack/skills extensions/pstack/references dist/extensions/pstack/",
138
141
  "build:dashboard": "bun install --cwd dashboard/app --frozen-lockfile && bun run --cwd dashboard/app build",
139
142
  "prepublishOnly": "bun run build",
140
143
  "check": "tsc --noEmit",