@herbertgao/pi-extensions 2026.9.7 → 2026.9.9

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 (158) hide show
  1. package/README.md +8 -4
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
  7. package/node_modules/@herbertgao/resume-from/package.json +2 -2
  8. package/node_modules/@narumitw/pi-btw/README.md +6 -1
  9. package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
  10. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  11. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  12. package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
  13. package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
  14. package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
  15. package/node_modules/grok-mermaid/CHANGELOG.md +46 -0
  16. package/node_modules/grok-mermaid/LICENSE +205 -0
  17. package/node_modules/grok-mermaid/README.md +191 -0
  18. package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
  19. package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
  20. package/node_modules/grok-mermaid/dist/ansi.js +23 -0
  21. package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
  22. package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
  23. package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
  24. package/node_modules/grok-mermaid/dist/canvas.js +366 -0
  25. package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
  26. package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
  27. package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
  28. package/node_modules/grok-mermaid/dist/graph.js +91 -0
  29. package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
  30. package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
  31. package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
  32. package/node_modules/grok-mermaid/dist/index.js +100 -0
  33. package/node_modules/grok-mermaid/dist/index.js.map +1 -0
  34. package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
  35. package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
  36. package/node_modules/grok-mermaid/dist/labels.js +324 -0
  37. package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
  38. package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
  39. package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
  40. package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
  41. package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
  42. package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
  43. package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
  44. package/node_modules/grok-mermaid/dist/layout.js +881 -0
  45. package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
  46. package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
  47. package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
  48. package/node_modules/grok-mermaid/dist/parse.js +1151 -0
  49. package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
  50. package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
  51. package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
  52. package/node_modules/grok-mermaid/dist/source-box.js +78 -0
  53. package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
  54. package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
  55. package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
  56. package/node_modules/grok-mermaid/dist/types.js +1 -0
  57. package/node_modules/grok-mermaid/dist/types.js.map +1 -0
  58. package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
  59. package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
  60. package/node_modules/grok-mermaid/dist/width-data.js +994 -0
  61. package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
  62. package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
  63. package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
  64. package/node_modules/grok-mermaid/dist/width.js +76 -0
  65. package/node_modules/grok-mermaid/dist/width.js.map +1 -0
  66. package/node_modules/grok-mermaid/package.json +49 -0
  67. package/node_modules/grok-mermaid/src/ansi.ts +34 -0
  68. package/node_modules/grok-mermaid/src/canvas.ts +373 -0
  69. package/node_modules/grok-mermaid/src/graph.ts +142 -0
  70. package/node_modules/grok-mermaid/src/index.ts +104 -0
  71. package/node_modules/grok-mermaid/src/labels.ts +326 -0
  72. package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
  73. package/node_modules/grok-mermaid/src/layout.ts +1015 -0
  74. package/node_modules/grok-mermaid/src/parse.ts +1189 -0
  75. package/node_modules/grok-mermaid/src/source-box.ts +89 -0
  76. package/node_modules/grok-mermaid/src/types.ts +43 -0
  77. package/node_modules/grok-mermaid/src/width-data.ts +993 -0
  78. package/node_modules/grok-mermaid/src/width.ts +74 -0
  79. package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
  80. package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
  81. package/node_modules/pi-jev-auto-mode/README.md +285 -0
  82. package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
  83. package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
  84. package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
  85. package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
  86. package/node_modules/pi-jev-auto-mode/index.ts +1 -0
  87. package/node_modules/pi-jev-auto-mode/package.json +72 -0
  88. package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
  89. package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
  90. package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
  91. package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
  92. package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
  93. package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
  94. package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
  95. package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
  96. package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
  97. package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
  98. package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
  99. package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
  100. package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
  101. package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
  102. package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
  103. package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
  104. package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
  105. package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
  106. package/node_modules/pi-lens/CHANGELOG.md +45 -0
  107. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
  108. package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
  109. package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
  110. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
  111. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
  112. package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
  113. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
  114. package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
  115. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
  116. package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
  117. package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
  118. package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
  119. package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
  120. package/node_modules/pi-lens/dist/index.js +729 -344
  121. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
  122. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
  123. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
  124. package/node_modules/pi-lens/package.json +1 -1
  125. package/node_modules/pi-typesafe/LICENSE +21 -0
  126. package/node_modules/pi-typesafe/README.md +153 -0
  127. package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
  128. package/node_modules/pi-typesafe/dist/ask.js +25 -0
  129. package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
  130. package/node_modules/pi-typesafe/dist/auth.js +114 -0
  131. package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
  132. package/node_modules/pi-typesafe/dist/batch.js +116 -0
  133. package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
  134. package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
  135. package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
  136. package/node_modules/pi-typesafe/dist/client.js +188 -0
  137. package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
  138. package/node_modules/pi-typesafe/dist/credentials.js +112 -0
  139. package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
  140. package/node_modules/pi-typesafe/dist/errors.js +31 -0
  141. package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
  142. package/node_modules/pi-typesafe/dist/extension.js +222 -0
  143. package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
  144. package/node_modules/pi-typesafe/dist/index.js +9 -0
  145. package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
  146. package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
  147. package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
  148. package/node_modules/pi-typesafe/dist/login.js +38 -0
  149. package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
  150. package/node_modules/pi-typesafe/dist/schema.js +152 -0
  151. package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
  152. package/node_modules/pi-typesafe/dist/ui.js +3 -0
  153. package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
  154. package/node_modules/pi-typesafe/dist/usage.js +182 -0
  155. package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
  156. package/node_modules/pi-typesafe/extensions/index.js +2 -0
  157. package/node_modules/pi-typesafe/package.json +89 -0
  158. package/package.json +16 -8
@@ -0,0 +1,74 @@
1
+ import { WIDTHS } from './width-data.ts'
2
+
3
+ /**
4
+ * Display width, measured in grapheme clusters.
5
+ *
6
+ * A cluster is the unit both of measuring and of painting, so a box is always
7
+ * sized for exactly what gets drawn into it. Splitting those two — sizing by
8
+ * cluster but painting by code point — is what makes `👨‍👩‍👧` overflow its
9
+ * border in the Rust original.
10
+ *
11
+ * Clustering comes from `Intl.Segmenter` (UAX #29), which already handles ZWJ
12
+ * sequences, skin-tone modifiers, variation selectors, keycaps, flags and
13
+ * Hangul. Per-code-point widths are generated from the `unicode-width` crate.
14
+ */
15
+
16
+ const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
17
+
18
+ const VS16 = 0xfe0f
19
+ const isRegionalIndicator = (cp: number): boolean => cp >= 0x1f1e6 && cp <= 0x1f1ff
20
+
21
+ /** Width of one code point; the table covers the whole code point space. */
22
+ function codePointWidth(cp: number): number {
23
+ let lo = 0
24
+ let hi = WIDTHS.length - 1
25
+ while (lo <= hi) {
26
+ const mid = (lo + hi) >> 1
27
+ const run = WIDTHS[mid]
28
+ if (cp < run[0]) hi = mid - 1
29
+ else if (cp > run[1]) lo = mid + 1
30
+ else return run[2]
31
+ }
32
+ return 1
33
+ }
34
+
35
+ /**
36
+ * Columns occupied by one grapheme cluster.
37
+ *
38
+ * The widest code point wins, so a base plus its combining marks measures as
39
+ * the base. Two adjustments: a variation selector requesting emoji
40
+ * presentation forces two columns, as does a regional indicator pair (a flag).
41
+ *
42
+ * Zero is a real answer — a soft hyphen or zero-width space occupies nothing,
43
+ * and callers skip painting such a cluster rather than reserving a cell.
44
+ */
45
+ export function clusterWidth(cluster: string): number {
46
+ let w = 0
47
+ let vs16 = false
48
+ let regional = 0
49
+ for (const ch of cluster) {
50
+ const cp = ch.codePointAt(0) as number
51
+ if (cp === VS16) vs16 = true
52
+ if (isRegionalIndicator(cp)) regional++
53
+ const cw = codePointWidth(cp)
54
+ if (cw > w) w = cw
55
+ }
56
+ return vs16 || regional >= 2 ? 2 : w
57
+ }
58
+
59
+ /** Iterate grapheme clusters, so no loop can split one. */
60
+ export function* clusters(s: string): Generator<string> {
61
+ for (const { segment } of segmenter.segment(s)) yield segment
62
+ }
63
+
64
+ /** Iterate clusters paired with their display width. */
65
+ export function* measured(s: string): Generator<[string, number]> {
66
+ for (const { segment } of segmenter.segment(s)) yield [segment, clusterWidth(segment)]
67
+ }
68
+
69
+ /** Display columns of a string. */
70
+ export function stringWidth(s: string): number {
71
+ let w = 0
72
+ for (const { segment } of segmenter.segment(s)) w += clusterWidth(segment)
73
+ return w
74
+ }
@@ -0,0 +1,110 @@
1
+ # Changelog
2
+
3
+ ## 0.4.1 - 2026-09-18
4
+
5
+ - **An unclear answer passes by default.** `uncertain` defaulted to `deny`, which meant a
6
+ judgment the model was unsure about stopped the call. That is the interruption an auto mode
7
+ exists to remove; clear rejections still block. `uncertain deny` and `uncertain ask` remain
8
+ available for anyone who wants the stricter behaviour.
9
+ - **Without a key the gate says so and stops**, instead of inventing a verdict and blocking with
10
+ an unexplained reason. The message names the fix: `/jev-auto-mode login`, or
11
+ `/jev-auto-mode off`. The footer reads `🛡 jev no key` in that state, and the session start
12
+ warns once.
13
+ - **A chain of read-only commands is read-only.** `cd src && ls -la && git log -3` was judged as
14
+ a whole because the allowlist rejected any command containing shell control syntax, so agents
15
+ paid a judgment round trip for their most common line. Each segment is now checked on its own,
16
+ and `cd` is allowed. A chain containing anything else (`curl … | sh`) is still judged.
17
+ - **The docs match the no-key behaviour.** The README and a comment still described a fallback
18
+ that confirms in a UI, which the gate no longer does: without a key it stops the calls it cannot
19
+ judge and says it is not connected to Jev.
20
+
21
+
22
+ ## 0.4.0
23
+
24
+ **The semantic layer now sees everything the deterministic layer cannot vouch for, and it does
25
+ not stop ordinary work.**
26
+
27
+ - `gateScope` (default `all`) replaces the denylist as the way calls are selected. A dangerous
28
+ pattern can only recognise a shape someone wrote down first: a request that uploads a file
29
+ (`curl -d @...`) once ran with no judgment at all because no pattern described it, and adding
30
+ patterns to a denylist is a race that never ends. Under `all`, the deterministic layer names
31
+ what it can vouch for and everything else is judged. `matched` keeps the old behaviour.
32
+ `/jev-auto-mode scope all|matched` switches between them.
33
+ - **The intent question is asked only about commands the deterministic layer recognised as a
34
+ dangerous shape, and only a clear "this was not requested" blocks.** Asked about every command,
35
+ it blocked ordinary work the request never mentioned — an unrequested `mv`, `cp`, `tar`,
36
+ `chmod +x`, or `node -e`. An auto mode that stops for those has no reason to exist. Measured
37
+ after the change: those run, while an unrequested `git reset --hard`, `npm publish`, `rm -rf`,
38
+ or `sudo` still blocks (p = 0.04–0.11).
39
+ - Read-only inspection is now a real fast path, because under `all` it carries the load the
40
+ denylist used to carry: `cat`, `head`, `tail`, `wc`, `find`, `jq`, `diff`, `sort`, `stat`,
41
+ version probes, and read-only git subcommands. Destructive variants (`find -delete`,
42
+ `git tag -d`, `push --force`) still match dangerous patterns and are judged.
43
+ - The user's `safeCommands` outranks a dangerous-pattern match; the built-in read-only list does
44
+ not, so `grep secret ~/.ssh/id_ed25519` is judged even though `grep` is read-only.
45
+ - `Escalated:` replaces `Matched:` in the confirmation dialog, because under `all` the reasons
46
+ are not all pattern matches.
47
+
48
+ ## 0.3.0
49
+
50
+ The 0.2.0 default resolved the middle band as a block, but two conditions were still
51
+ `required`, which made the gate strict for structurally wrong reasons rather than measured
52
+ ones. Corrected:
53
+
54
+ - `intent_coverage` 0.80 → **0.60**. Measured answers are 0.77–0.98 when the user asked and
55
+ 0.06–0.15 when they did not, so 0.80 sat on top of the "asked" cluster instead of inside the
56
+ empty band between the two. The middle band is now (0.40, 0.60).
57
+ - `policy_compliance` required → **hazard**. It measured 0.66–0.85 on calls where nothing was
58
+ wrong, so as a requirement it blocked every gated call the moment a policy was configured.
59
+ Now only a clear violation stops a call.
60
+ - `path_not_protected` required → **hazard**. An unclear answer no longer blocks on its own; the
61
+ user's request decides. A target the model clearly identifies as a credential store still
62
+ blocks (`.env` measured p = 0.02, `~/.ssh` p = 0.03).
63
+ - New `no_fetched_code_execution`, required, asked only for commands the deterministic layer
64
+ already recognised as downloaded-script execution. `curl | bash` stays blocked (p = 0.02)
65
+ without making every other call strict.
66
+ - `.env.example`, `.env.sample`, `.env.template`, and `.env.dist` are no longer treated as
67
+ credential stores: templates belong in the repository.
68
+ - The intent window widened from 8 messages / 4000 characters to 12 / 6000, so an ongoing task
69
+ does not lose the request that justifies it and look unrequested as a result.
70
+
71
+ ## 0.2.0
72
+
73
+ - The confirmation dialog is bounded: it showed the whole command, and since Pi's dialogs do not
74
+ clip their content a long command produced a dialog taller than the terminal. It now shows a
75
+ short preview and says what was hidden.
76
+ - **The middle band no longer asks the user by default.** An auto mode that stops to ask has
77
+ handed the decision back to a human, and the agent can always ask in conversation if it needs
78
+ guidance. A judgment that is neither satisfied nor rejected now blocks, so the gate never
79
+ takes over the screen.
80
+ - `uncertain` setting and `/jev-auto-mode uncertain deny|ask|allow` control it. `deny` is the
81
+ default; `ask` restores the confirmation dialog; `allow` trusts the band.
82
+ - `/jev-auto-mode threshold edit` picks a rule and prompts for a value, showing each rule's
83
+ current threshold next to the last probability the model returned for it.
84
+
85
+
86
+ ## 0.1.1
87
+
88
+ - Correct the product name. It is **Jev** — TypeSafe's System One model, spelled with a
89
+ capital J and lowercase `ev`, as in their announcement, FAQ, and the model id
90
+ `typesafe-ai/jev` — not "JEV". Fixed in the README, docs, source comments, and the package
91
+ description. The `0.1.0` metadata cannot be edited, so this release exists to carry the
92
+ corrected name. No behavioral change.
93
+
94
+ ## 0.1.0
95
+
96
+ Initial release.
97
+
98
+ - Two-layer gate: a deterministic policy envelope (hard-deny, allow/deny patterns,
99
+ dangerous-command detection, protected paths) and a Jev semantic layer that only sees
100
+ what the first layer escalated.
101
+ - Conditions carry a `mode` (`required` / `hazard`) and a `severity` (`hazard` / `soft`),
102
+ calibrated against measured probabilities from the real API (`docs/calibration.md`).
103
+ - Fail-closed everywhere: no key, timeout, malformed response, cancellation, missing
104
+ answer, or oversized request all resolve to a block rather than an approval.
105
+ - `/jev-auto-mode` command surface: `on|off`, `login|logout`, `policy`, `threshold`.
106
+ - Decision records via `pi.appendEntry`, kept out of the LLM context, expandable to a
107
+ per-condition tuning sheet.
108
+ - Network uploads of local data and reads of credential material are gated: a `curl -d @file`
109
+ that matched nothing ran with no judgment before this.
110
+ - `pi install` from npm or git; tagged `pi-package` for the package gallery.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jomatsu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,285 @@
1
+ # pi-jev-auto-mode
2
+
3
+ Auto mode for the [Pi coding agent](https://github.com/earendil-works/pi-mono) backed by
4
+ **Jev** (TypeSafe System One, a decision-only model). Pi has no built-in permission system,
5
+ so a gate either exists as an extension or it does not exist at all. This one judges
6
+ `bash`, `write`, and `edit` tool calls semantically and **fails closed** whenever a decision
7
+ cannot be made.
8
+
9
+ > **Status: milestones 1–3 are complete.** The deterministic envelope, the Jev engine,
10
+ > real-API calibration, settings, policy notes, per-rule threshold tuning, and decision
11
+ > records are implemented and tested (171 tests, no network). See [`docs/design.md`](./docs/design.md) for
12
+ > the roadmap and [`docs/calibration.md`](./docs/calibration.md) for the measured
13
+ > probabilities behind every threshold.
14
+
15
+ ## What it does
16
+
17
+ The gate has two layers, in this order:
18
+
19
+ 1. **Deterministic policy** — hard-deny commands, your allow/deny patterns, dangerous-command
20
+ detection, protected paths. Hard-deny is never handed to the semantic layer.
21
+ 2. **Semantic judgment (Jev)** — only the calls the first layer escalated.
22
+
23
+ ```
24
+ hard-deny → block (never reaches Jev)
25
+ your deny pattern → block
26
+ your allow pattern → allow (recorded)
27
+ your safeCommands → run, no record
28
+ dangerous pattern match → Jev (even when the command looks read-only)
29
+ read-only builtin → run, no record
30
+ in-project write/edit → run, no record
31
+ everything else → Jev: allow · block · block-if-undecidable
32
+ ```
33
+
34
+ **`gateScope` decides how far the semantic layer reaches, and the default is `all`.** A denylist
35
+ can only recognise the shapes someone wrote a pattern for first: a command that uploaded a file
36
+ (`curl -d @~/.ssh/id_ed25519 ...`) matched nothing and ran with no judgment at all. Under `all`
37
+ the deterministic layer names what it can vouch for — read-only inspection, your declared safe
38
+ commands, a write inside the project to an unprotected path — and everything else is judged.
39
+ `matched` restores the old pattern-only behaviour. `/jev-auto-mode scope all|matched` changes it.
40
+
41
+ The trade is latency: a judged call costs roughly half a second (measured 193–642 ms across
42
+ eleven ordinary commands), while a fast-path call costs nothing. Read-only inspection is
43
+ therefore a real allowlist rather than a convenience.
44
+
45
+ An auto mode that stops for ordinary work has no reason to exist, so **the intent question is
46
+ asked only about commands the deterministic layer recognised as a dangerous shape, and only a
47
+ clear "this was not requested" blocks**. Measured: an unrequested `mv`, `cp`, `tar`, `chmod +x`,
48
+ or `node -e` is judged and allowed, while an unrequested `git reset --hard`, `npm publish`,
49
+ `rm -rf`, or `sudo` is blocked.
50
+
51
+ `rm -rf build` inside the repository is recognized as a scoped local deletion. A write to
52
+ `.env`, `.git/`, `~/.ssh`, `.pi/`, `.github/workflows/`, or `AGENTS.md` is escalated even when
53
+ the path is inside the working directory.
54
+
55
+ ### How Jev decides
56
+
57
+ Conditions are phrased so the safe state is "yes", and each one is classified by
58
+ `mode` and `severity`:
59
+
60
+ | | meaning |
61
+ |---|---|
62
+ | `mode: required` | must be satisfied; the middle band escalates to a confirmation |
63
+ | `mode: hazard` | only a clear negative matters; the middle band is ignored |
64
+ | `severity: hazard` | a clear rejection always blocks |
65
+ | `severity: soft` | a clear rejection is cleared when the user's own request covers the call |
66
+
67
+ So `intent_coverage` ("is this what the user asked for?") is the permission question, and
68
+ questions like "is a secret being sent to a network endpoint" are hazard detectors that only
69
+ block when they are sure. Posting a private key is never cleared by intent; force-pushing a
70
+ feature branch the user asked for is.
71
+
72
+ **Nothing is delegated to the user by default.** The middle band — where Jev is neither
73
+ satisfied nor rejecting — resolves to a block, so Jev's probability is the whole answer and the
74
+ gate never takes over the screen. `/jev-auto-mode uncertain ask` restores the confirmation
75
+ dialog if you want it; `allow` trusts the band. Everything else that cannot be decided — no
76
+ engine, timeout, malformed response, cancellation — also blocks.
77
+
78
+ ## Install
79
+
80
+ ```sh
81
+ pi install npm:pi-jev-auto-mode
82
+ ```
83
+
84
+ Or straight from the repository, which needs no npm account:
85
+
86
+ ```sh
87
+ pi install git:github.com/jomatsu/pi-jev-auto-mode
88
+ ```
89
+
90
+ Try it without installing:
91
+
92
+ ```sh
93
+ pi -e npm:pi-jev-auto-mode
94
+ ```
95
+
96
+ Packages are discovered in the [package gallery](https://pi.dev/packages) through the
97
+ `pi-package` keyword.
98
+
99
+ ## Usage
100
+
101
+ ```
102
+ /jev-auto-mode show status (settings + where the API key comes from)
103
+ /jev-auto-mode on|off toggle auto mode
104
+ /jev-auto-mode login store a TypeSafe API key (verified, then saved 0600)
105
+ /jev-auto-mode logout remove the stored key
106
+ /jev-auto-mode policy list the policy notes
107
+ /jev-auto-mode policy edit
108
+ /jev-auto-mode policy clear
109
+ /jev-auto-mode threshold show thresholds and the last observed probability per rule
110
+ /jev-auto-mode threshold <rule> <0.5-1> set one threshold
111
+ /jev-auto-mode threshold edit pick a rule and type a value
112
+ /jev-auto-mode threshold reset [rule] restore the calibrated default
113
+ /jev-auto-mode uncertain show what the middle band resolves to
114
+ /jev-auto-mode uncertain deny|ask|allow
115
+ /jev-auto-mode scope all|matched how far the semantic layer reaches
116
+ ```
117
+
118
+ ```
119
+ pi --jev-auto-mode start with auto mode enabled
120
+ ```
121
+
122
+ The semantic layer needs a [TypeSafe](https://typesafe.ai/) API key. Jev is early access, so an
123
+ account may be waitlisted; **the gate still works without one**. Its own rules keep running —
124
+ read-only and user-declared safe commands pass, hard-deny shapes are blocked — but a call
125
+ nothing vouches for is blocked with "Not connected to Jev" instead of being judged.
126
+
127
+ `/jev-auto-mode login` asks for the key, verifies it against the API (`GET /v1/models`), and
128
+ stores it as an owner-only file at
129
+ `$PI_CODING_AGENT_DIR/secrets/jev-auto-mode-typesafe-api-key` (mode `0600`) — the same place Pi
130
+ keeps its own credentials, so it is never committed with a project. `TYPESAFE_API_KEY` takes
131
+ precedence when set, so a one-off or CI override needs no login. `TYPESAFE_DEFAULT_MODEL`
132
+ selects the model (default `jev-latest`).
133
+
134
+ A key is only stored after the API accepts it: a typo that got saved would turn into a gate
135
+ that silently blocks every escalated call. If the API cannot be reached the key is not stored
136
+ either, and the command says so rather than claiming success.
137
+
138
+ Without a key the gate does not disable itself and does not allow everything: it stops the calls
139
+ it cannot judge and says it is not connected to Jev. The footer shows `🛡 jev (<scope>)` while the
140
+ semantic layer is active and `🛡 jev no key (<scope>)` when it is not.
141
+
142
+ ## Tuning
143
+
144
+ The thresholds are a starting point measured on twelve fixtures, not a truth
145
+ ([`docs/calibration.md`](./docs/calibration.md)). To retune them from your own work:
146
+
147
+ 1. Run the thing you care about. The gate records every judgment; expand the record in the
148
+ transcript and read the per-condition table:
149
+
150
+ ```
151
+ intent_coverage p=0.97 pass (t=0.80, >= 0.80)
152
+ no_outward_effect p=0.06 reject (t=0.90, <= 0.10) <- decided (cleared by the user's request)
153
+ local_scope p=0.81 pass (t=0.90, >= 0.90)
154
+ ```
155
+
156
+ 2. If a condition that should have passed lands in the middle band, lower its threshold. If
157
+ something got through that should not have, raise it. `/jev-auto-mode threshold` shows the
158
+ current value next to the last probability the model returned for that rule.
159
+
160
+ 3. `/jev-auto-mode threshold <rule> <value>` writes the override. It takes effect immediately
161
+ and persists in the global settings file.
162
+
163
+ A threshold must leave a middle band on both sides (`0.5 < t <= 1`): `t` is the probability
164
+ required to count as satisfied, and `1 - t` is the probability at or below which the condition
165
+ counts as violated. Values that close one side are rejected.
166
+
167
+ ```json
168
+ {
169
+ "thresholds": {
170
+ "intent_coverage": 0.6,
171
+ "no_secret_egress": 0.995
172
+ }
173
+ }
174
+ ```
175
+
176
+ The right fix is usually to phrase the condition better, not to move the threshold. If "should
177
+ pass" and "should reject" answers overlap, the question is ambiguous.
178
+
179
+ ## Configuration
180
+
181
+ Global settings: `$PI_CODING_AGENT_DIR/jev-auto-mode.json` (default `~/.pi/agent/`).
182
+ Project override: `<cwd>/.pi/jev-auto-mode.json`, honored **only for a trusted project** —
183
+ an untrusted checkout must not be able to loosen the gate that is judging it.
184
+ Policy notes: `$PI_CODING_AGENT_DIR/jev-auto-mode-policy.md`.
185
+
186
+ ```json
187
+ {
188
+ "enabled": true,
189
+ "timeoutMs": 4000,
190
+ "maxRetries": 1,
191
+ "safeCommands": ["uv run pytest*", "pnpm run typecheck*"],
192
+ "allowedCommands": ["rm -rf build*"],
193
+ "disallowedCommands": ["npm publish*"],
194
+ "extraProtectedPaths": [],
195
+ "maxStateCharacters": 120000,
196
+ "uncertain": "deny",
197
+ "gateScope": "all",
198
+ "thresholds": {}
199
+ }
200
+ ```
201
+
202
+ - Allow patterns never match a command containing shell control syntax (`;`, `&&`, `|`,
203
+ redirection, substitution), so `ls*` cannot approve `ls && rm -rf /`.
204
+ - Malformed values are dropped rather than defaulted, so a broken project file cannot pin a
205
+ value that overrides the global layer.
206
+ - There are two ways to widen the fast path, with different meanings:
207
+
208
+ | Setting | Effect |
209
+ |---|---|
210
+ | `safeCommands` | Run without a decision record. For commands that are safe *on your machine*: `uv run pytest*`, `npm run test*`, `cargo test*`, `go test ./...*` |
211
+ | `allowedCommands` | Override a dangerous-pattern match. The override is recorded, so approving `rm -rf build` by rule is visible |
212
+
213
+ The built-in safe list is not configurable and holds read-only inspection only (`git
214
+ status`/`diff`/`log`/`show`/`branch`, `ls`, `pwd`, `rg`, `grep`). It deliberately contains no
215
+ command that executes project code: a test runner runs repository code, so declaring it safe
216
+ is a decision for the machine that owns it, not a default shipped to everyone. Allow patterns
217
+ never match a command containing shell control syntax, so `ls*` cannot approve `ls && rm -rf /`.
218
+
219
+ ## What leaves the machine
220
+
221
+ An escalated call sends the following to TypeSafe's API (`api.typesafe.ai`):
222
+
223
+ - the tool name and the bash command text (truncated),
224
+ - for `write` / `edit`: the target **path** — never the file contents or the diff,
225
+ - the working directory, the matched policy reason names,
226
+ - recent **user** messages (bounded), and your policy notes.
227
+
228
+ Obvious credentials (`*_KEY=…`, `Bearer …`, JWTs, `sk-…`, `ghp_…`, PEM private keys) are
229
+ redacted on the way out. Assistant output, tool output, and file contents are never sent.
230
+ Details and the failure-mode table: [`docs/security.md`](./docs/security.md).
231
+
232
+ ## Releasing
233
+
234
+ A version, a tag, and a release are cut **once, when the version is published**, so the tag list
235
+ matches what people can install. Unfinished work accumulates under `## Unreleased` in
236
+ `CHANGELOG.md` and is renamed to the version at release time:
237
+
238
+ 1. `npm run check`
239
+ 2. rename `## Unreleased` to `## <version>` in `CHANGELOG.md`, bump `version` in `package.json`
240
+ 3. commit, `git tag -a v<version>`, `git push --follow-tags`
241
+ 4. `gh release create v<version> --notes-file <(the changelog section)`
242
+ 5. `npm publish`
243
+
244
+ ## Development
245
+
246
+ ```sh
247
+ npm install
248
+ npm test # node:test, no network
249
+ npm run typecheck
250
+ node --experimental-strip-types scripts/calibrate.ts # real API, needs TYPESAFE_API_KEY
251
+ ```
252
+
253
+ Layout:
254
+
255
+ | Path | Responsibility |
256
+ |---|---|
257
+ | `src/policy.ts` | hard-deny, user rules, dangerous patterns, protected paths, safe commands |
258
+ | `src/call.ts` | `tool_call` → judgment state (redaction, truncation, path classification) |
259
+ | `src/intent.ts` | recent user-authored intent only |
260
+ | `src/decide.ts` | the decision-engine seam (`DecisionEngine`) |
261
+ | `src/jev/questions.ts` | the condition set, modes, severities, thresholds |
262
+ | `src/jev/availability.ts` | where the API key comes from (env or stored secret) |
263
+ | `src/jev/decide.ts` | probability → condition verdict → decision |
264
+ | `src/jev/engine.ts` | one request per call, budget guard, calibration hook |
265
+ | `src/jev/transport.ts` | the SDK, wrapped so failures become decisions |
266
+ | `src/jev/response.ts` | response re-validation (a 200 is not an answer) |
267
+ | `src/settings.ts` | global/project settings, policy notes, and the stored API key |
268
+ | `src/records.ts` | `appendEntry` records and their renderer |
269
+ | `src/ui.ts` | footer status and user-facing text |
270
+ | `src/extension.ts` | `tool_call` orchestration and command wiring |
271
+
272
+ ## Acknowledgements
273
+
274
+ The deterministic pattern catalogue is adapted from
275
+ [`@nilskluewer/pi-auto-permission-gate`](https://github.com/nilskluewer/pi-auto-permission-gate)
276
+ (MIT), and the three-layer structure (fast paths → hard rules → classifier) follows the same
277
+ extension and Qwen Code's Auto Mode. The Jev design constraints (fail closed, two-sided
278
+ thresholds that keep the middle band meaningful, one request per judgment) come from measuring
279
+ the API directly — [`docs/calibration.md`](./docs/calibration.md) records the measurements and
280
+ the reasoning. Nothing here depends on a wrapper library: the Jev layer is written against the
281
+ official SDK.
282
+
283
+ ## License
284
+
285
+ MIT
@@ -0,0 +1,32 @@
1
+ # Security
2
+
3
+ This extension stands between a coding agent and your shell, so its own failure modes matter.
4
+
5
+ ## Reporting
6
+
7
+ Open a private security advisory on the repository, or an issue if the report does not need to
8
+ stay private. Please include the tool call, the recorded decision (`/jev-auto-mode` records
9
+ are expandable in the transcript), and the Jev probabilities if you have them.
10
+
11
+ ## What is in scope
12
+
13
+ - A call that should have been blocked and was not.
14
+ - A call that was approved without the semantic layer being consulted.
15
+ - Credential or file content leaving the machine inside a judgment request.
16
+ - A stored API key readable by another user, or written somewhere other than
17
+ `<agentDir>/secrets/` with mode `0600`.
18
+
19
+ ## Design summary
20
+
21
+ The gate is two layers and the order is the enforcement:
22
+
23
+ 1. A deterministic envelope (hard-deny, user rules, dangerous-command patterns, protected
24
+ paths). Hard-deny returns before the semantic layer is constructed, so a probabilistic
25
+ verdict can never resurrect it.
26
+ 2. Jev, which only sees calls the first layer escalated.
27
+
28
+ Anything that cannot be decided — no key, timeout, malformed response, a response missing an
29
+ answer, cancellation, an oversized request — blocks. See
30
+ [docs/security.md](./docs/security.md) for the failure-mode table, what is sent to the API, and
31
+ the known limits (including the `soft`-severity clearing, which is the mechanism to remove first
32
+ if a wrong approval ever appears).