baesian 0.0.0-stage → 1.0.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 (69) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +217 -2
  3. package/dist/agents.js +249 -0
  4. package/dist/api.js +13 -0
  5. package/dist/bin.js +14 -0
  6. package/dist/ci.js +82 -0
  7. package/dist/cite.js +66 -0
  8. package/dist/cli.js +831 -0
  9. package/dist/client/assets/alegreya-cyrillic-ext-wght-italic-DzWRXZb0.woff2 +0 -0
  10. package/dist/client/assets/alegreya-cyrillic-ext-wght-normal-CN0-BfWD.woff2 +0 -0
  11. package/dist/client/assets/alegreya-cyrillic-wght-italic-Dhrv2xfk.woff2 +0 -0
  12. package/dist/client/assets/alegreya-cyrillic-wght-normal-CU5lb38f.woff2 +0 -0
  13. package/dist/client/assets/alegreya-greek-ext-wght-italic-CoUI9EEu.woff2 +0 -0
  14. package/dist/client/assets/alegreya-greek-ext-wght-normal-C8PPRmwJ.woff2 +0 -0
  15. package/dist/client/assets/alegreya-greek-wght-italic-CVt9-Jr4.woff2 +0 -0
  16. package/dist/client/assets/alegreya-greek-wght-normal-BPx8yWcZ.woff2 +0 -0
  17. package/dist/client/assets/alegreya-latin-ext-wght-italic-BIuo_2sR.woff2 +0 -0
  18. package/dist/client/assets/alegreya-latin-ext-wght-normal-o8axeAGu.woff2 +0 -0
  19. package/dist/client/assets/alegreya-latin-wght-italic-Bxkcgmge.woff2 +0 -0
  20. package/dist/client/assets/alegreya-latin-wght-normal-BG9rTJo4.woff2 +0 -0
  21. package/dist/client/assets/alegreya-vietnamese-wght-italic-BAPuWgnQ.woff2 +0 -0
  22. package/dist/client/assets/alegreya-vietnamese-wght-normal-DOV9JYup.woff2 +0 -0
  23. package/dist/client/assets/archivo-latin-ext-wdth-normal-7khWdh9v.woff2 +0 -0
  24. package/dist/client/assets/archivo-latin-wdth-normal-DY7AcnAa.woff2 +0 -0
  25. package/dist/client/assets/archivo-vietnamese-wdth-normal-rJmnGBSt.woff2 +0 -0
  26. package/dist/client/assets/index-B3CFhLg-.css +1 -0
  27. package/dist/client/assets/index-DUcPUPI_.js +15 -0
  28. package/dist/client/index.html +15 -0
  29. package/dist/facts.js +278 -0
  30. package/dist/folder-storage.js +71 -0
  31. package/dist/frontmatter.js +38 -0
  32. package/dist/gate-paths.js +77 -0
  33. package/dist/gate.js +237 -0
  34. package/dist/git.js +100 -0
  35. package/dist/globs.js +120 -0
  36. package/dist/hookblock.js +146 -0
  37. package/dist/hooks.js +911 -0
  38. package/dist/ids.js +23 -0
  39. package/dist/login.js +301 -0
  40. package/dist/mcp.js +303 -0
  41. package/dist/merges.js +849 -0
  42. package/dist/modes.js +245 -0
  43. package/dist/notify.js +91 -0
  44. package/dist/ops.js +381 -0
  45. package/dist/passkey.js +86 -0
  46. package/dist/personal.js +163 -0
  47. package/dist/practice-answers.js +42 -0
  48. package/dist/proposals.js +197 -0
  49. package/dist/quote.js +28 -0
  50. package/dist/report.js +261 -0
  51. package/dist/routing.js +411 -0
  52. package/dist/schema.js +1178 -0
  53. package/dist/server.js +514 -0
  54. package/dist/similarity.js +45 -0
  55. package/dist/sshsig.js +159 -0
  56. package/dist/state.js +135 -0
  57. package/dist/storage.js +61 -0
  58. package/dist/store.js +2259 -0
  59. package/dist/style.js +408 -0
  60. package/dist/sync.js +797 -0
  61. package/dist/trailers.js +121 -0
  62. package/dist/trust.js +222 -0
  63. package/dist/usage-fields.js +161 -0
  64. package/dist/vault.js +849 -0
  65. package/dist/verify.js +443 -0
  66. package/dist/version.js +70 -0
  67. package/dist/wait.js +449 -0
  68. package/dist/wikilinks.js +25 -0
  69. package/package.json +71 -4
package/LICENSE.md ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Gus Ellerm
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,218 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/wordmark-dark.png">
4
+ <img src="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/wordmark.png" alt="Baesian" width="280">
5
+ </picture>
6
+ </p>
2
7
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
8
+ <p align="center"><b>Swipe review for the decisions your coding agents shouldn't make alone.</b><br>
9
+ They put each one to you as a card, and you choose before you see their pick.</p>
10
+
11
+ <p align="center">
12
+ <a href="https://baesian.dev">baesian.dev</a> ·
13
+ <a href="#try-the-demo">Try the demo</a> ·
14
+ <a href="#how-it-works">How it works</a> ·
15
+ <a href="#set-it-up">Set it up</a> ·
16
+ <a href="https://github.com/GusEllerm/baesian/blob/main/docs/setup.md">Setup guide</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <img src="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/demo.gif" width="74%" alt="Answering two agents' decision cards in Baesian: choose first, then see the agent's pick, while each agent's terminal gets the answer">
21
+ <img src="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/phone.png" width="22%" alt="A Baesian card held on a phone until it lifts, with its four answers named at the screen's edges">
22
+ </p>
23
+ <p align="center"><sub>At your desk, two agents building a made-up bakery's shop stop for you: you choose, see their pick,
24
+ overrule one, and each agent's terminal gets your answer. On your phone, you hold a card until it lifts and drag it to
25
+ an answer.</sub></p>
26
+
27
+ Your Claude Code and Codex agents make design decisions all day, and a few of them are hard to undo: where a
28
+ signed-in user's session lives, how prices are stored, what deleting an account removes. Baesian has the agent put
29
+ those to you as cards, before it builds on them. You pick one of its two options before you see which it chose, then
30
+ confirm or overrule it. Your answer goes back to the agent, and into git with the code it shapes.
31
+
32
+ ## Try the demo
33
+
34
+ You need Node 22 or later, and git.
35
+
36
+ ```sh
37
+ git clone https://github.com/GusEllerm/baesian.git
38
+ cd baesian
39
+ npm install
40
+ npm run demo
41
+ ```
42
+
43
+ The demo makes a throwaway repo where two agents are building Crumb, a made-up bakery's online shop, and have filed
44
+ five decisions. It opens the client, and its terminal shows what each agent gets as you answer.
45
+
46
+ ## How it works
47
+
48
+ 1. **An agent files a card** when a decision is hard to undo or isn't its call alone: a schema, a public API, a
49
+ dependency, CI, security, anything user-facing. `baesian init` tells your agents when, in `AGENTS.md`.
50
+ 2. **You choose first,** between its two options, then see which it picked and why, and confirm or switch.
51
+ 3. **The agent waits as long as the decision needs:** on most cards it carries on and gets your answer at its next
52
+ pause; on a *blocking* card it waits for you; on a *hard* card, the default for security, privacy, data loss, money
53
+ and publishing, it never goes ahead without you, and you hold to confirm.
54
+ 4. **Your answer goes back to the agent,** and into git, committed with the code it shapes.
55
+
56
+ Two things keep decisions from slipping past: a **commit gate** blocks a commit that adds a dependency, changes a
57
+ schema or touches CI until a card covers it or the agent says why it's safe; and the **plugin** sends an agent that
58
+ asks you to choose in chat back to file a card.
59
+
60
+ ## Why choose before you see the pick?
61
+
62
+ Seeing the agent's case first anchors you: a confident argument is easy to agree with. Choosing first records, for
63
+ each kind of decision, how often you'd have picked what the agent picked. That record is how Baesian suggests which
64
+ kinds you could leave to the agent. When you keep answering one kind the same way, Baesian offers to make it a rule
65
+ your agents follow without asking.
66
+
67
+ ## Decisions live in git
68
+
69
+ Each branch carries its own cards, committed with its code, so decisions merge with the code they shaped. The client's
70
+ **Your decisions, by branch** (press B) draws that history: where each branch began, what was decided on it, and every
71
+ merge with what it brought.
72
+
73
+ <p align="center"><img src="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/branches.png" width="100%" alt="Baesian's Branches drawer on its own history: merges and cards by branch, with one card opened to the three commits that cite it"></p>
74
+ <p align="center"><sub>Baesian's own decisions on one evening: worktrees branching off v1/next, the cards filed on them
75
+ and the commits citing those cards, merging back.</sub></p>
76
+
77
+ Every commit names the cards it builds on, so if you change an answer later, the agent is told which commits built on
78
+ the old one. In CI, `baesian verify` checks each commit's decisions, and holds a merge until its cards are answered.
79
+
80
+ <p align="center"><img src="https://raw.githubusercontent.com/GusEllerm/baesian/main/docs/media/checkpoints.png" width="100%" alt="Four commits on feat/checkout, each carrying the decisions as they stood; when an answer later changes, the agent is told which commits built on the old one"></p>
81
+ <p align="center"><sub>Each commit carries the decisions as they stood. When an answer changes, the commits that built on
82
+ the old one are named.</sub></p>
83
+
84
+ ## Set it up
85
+
86
+ You need Node 22 or later, git, and Claude Code or Codex. The
87
+ [setup guide](https://github.com/GusEllerm/baesian/blob/main/docs/setup.md) has each step in full.
88
+
89
+ ### 1. Install the CLI
90
+
91
+ ```sh
92
+ npm install -g baesian
93
+ ```
94
+
95
+ ### 2. Make an account
96
+
97
+ Sign up at [baesian.dev](https://baesian.dev) and make a passkey. Add one on each device you answer from, your phone
98
+ included.
99
+
100
+ ### 3. Set up your repo
101
+
102
+ ```sh
103
+ cd your-repo
104
+ baesian init
105
+ baesian login
106
+ ```
107
+
108
+ `init` sets up the commit gate and tells your agents about Baesian. `login` links the repo to baesian.dev: approve
109
+ this machine on the page it prints, then run the commit it prints.
110
+
111
+ ### 4. Install the plugin
112
+
113
+ In Claude Code:
114
+
115
+ ```text
116
+ /plugin marketplace add GusEllerm/baesian
117
+ /plugin install baesian@baesian
118
+ ```
119
+
120
+ <details>
121
+ <summary><b>Claude Code in VS Code, and Codex</b></summary>
122
+
123
+ In the VS Code extension, run the same from a terminal, then start a new conversation:
124
+
125
+ ```sh
126
+ claude plugin marketplace add GusEllerm/baesian
127
+ claude plugin install baesian@baesian
128
+ ```
129
+
130
+ In Codex:
131
+
132
+ ```sh
133
+ codex plugin marketplace add GusEllerm/baesian
134
+ codex plugin add baesian@baesian
135
+ ```
136
+
137
+ Codex runs the plugin's hooks only after you accept its "Hooks need review" prompt in interactive mode, and again after
138
+ any update.
139
+ </details>
140
+
141
+ <details>
142
+ <summary><b>Updating</b></summary>
143
+
144
+ Update the CLI and the plugin together, to the same release: `npm install -g baesian`, then
145
+ `claude plugin update baesian@baesian` (in Codex, `codex plugin marketplace upgrade baesian`, then
146
+ `codex plugin add baesian@baesian`). When they differ, the plugin tells your agent to ask you to update. Each release's
147
+ notes say what else to run again; [the setup guide](https://github.com/GusEllerm/baesian/blob/main/docs/setup.md#updating)
148
+ has the details.
149
+ </details>
150
+
151
+ <details>
152
+ <summary><b>Checking decisions in CI</b></summary>
153
+
154
+ ```sh
155
+ baesian ci --github
156
+ ```
157
+
158
+ sets the repository variable CI reads its keys from: the service's, a spare it keeps offline, and your passkeys'. Then
159
+ add the workflow steps it prints, which run the release that printed them until you run `baesian ci` again. See
160
+ [the setup guide](https://github.com/GusEllerm/baesian/blob/main/docs/setup.md#5-check-decisions-in-ci).
161
+ </details>
162
+
163
+ <details>
164
+ <summary><b>Without an account, or in a repo you share</b></summary>
165
+
166
+ `baesian init` then `baesian serve` gives you the client on your own machine, with nothing sent anywhere; `baesian
167
+ login` links the repo later. In a repo whose other contributors don't use Baesian, `baesian init --personal` keeps
168
+ everything in your clone and commits nothing. Both are in
169
+ [the setup guide](https://github.com/GusEllerm/baesian/blob/main/docs/setup.md#without-an-account).
170
+ </details>
171
+
172
+ ## Releases
173
+
174
+ Each release's notes, with anything to run again after updating, are on
175
+ [GitHub Releases](https://github.com/GusEllerm/baesian/releases). From 1.0, every 1.x release reads the files earlier
176
+ ones wrote, and keeps the hosted service's protocol and the commands, flags, exit codes and MCP tools your agents are
177
+ told to use: the setup guide's [Compatibility](https://github.com/GusEllerm/baesian/blob/main/docs/setup.md#compatibility)
178
+ section says how. To report a security problem, write to security@baesian.dev
179
+ ([SECURITY.md](https://github.com/GusEllerm/baesian/blob/main/SECURITY.md)).
180
+
181
+ ## Development
182
+
183
+ To work on Baesian, build it from a clone and link it onto your PATH, in place of the `baesian` from npm:
184
+
185
+ ```sh
186
+ git clone https://github.com/GusEllerm/baesian.git
187
+ cd baesian
188
+ npm install
189
+ npm run build && npm link
190
+ ```
191
+
192
+ To try the plugin from your clone, add its marketplace by path, `/plugin marketplace add /path/to/baesian`, then install
193
+ it as above. Both marketplaces are named `baesian`, so remove the one from GitHub first:
194
+ `/plugin marketplace remove baesian`.
195
+
196
+ ```sh
197
+ npm test # unit tests
198
+ npm run e2e # browser tests, with Playwright
199
+ npm run typecheck
200
+ npm run readme-media # remakes this README's images (needs ffmpeg)
201
+ ```
202
+
203
+ The project's memory is the Obsidian vault in
204
+ [`docs/vault/`](https://github.com/GusEllerm/baesian/blob/main/docs/vault/Home.md): the design, the decision log, and a
205
+ log of each working session. Start at `Home.md`.
206
+
207
+ ## Licence
208
+
209
+ Baesian is source-available under the
210
+ [Functional Source License 1.1](https://github.com/GusEllerm/baesian/blob/main/LICENSE.md) (FSL-1.1-ALv2): you may use, change
211
+ and run it for any purpose except a product or service that competes with it, and each version becomes available under
212
+ the Apache License 2.0 two years after its release.
213
+
214
+ ## Why "Baesian"
215
+
216
+ It's a dating app for decisions. Your agents line up their options, you swipe on the one you'd pick before you see
217
+ which one they picked, and over time you build up preferences for each kind of decision. The decisions you keep
218
+ agreeing on become rules, so your agents can make them for you from then on.
package/dist/agents.js ADDED
@@ -0,0 +1,249 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { cite } from "./cite.js";
4
+ // Tier 1 (D27): plain instructions any harness can read. `baesian init` keeps this block in AGENTS.md.
5
+ export const BEGIN = "<!-- baesian:begin -->";
6
+ export const END = "<!-- baesian:end -->";
7
+ export const AGENTS_BLOCK = `${BEGIN}
8
+ ## Decisions (Baesian)
9
+
10
+ Design decisions that are hard to undo go to a person as cards; you don't make them silently. Run
11
+ \`baesian inbox\` before starting work, and again when you finish a task: it shows new verdicts on this branch's
12
+ cards, to act on then rather than mid-task.
13
+
14
+ **File a card** (\`baesian template > card.md\`, fill it in, \`baesian file card.md\`) when a decision
15
+ changes a schema or on-disk format; changes a public API, URL scheme or module boundary; adds a runtime
16
+ dependency; changes CI, infrastructure or deployment; is user-facing; publishes something; touches
17
+ security or privacy; spends money; deletes or overwrites stored data; or would break something to
18
+ reverse. Otherwise decide yourself. Every card states its strongest alternative, and the one question
19
+ the choice turns on (the crux). A topic names one question: reuse one from \`baesian topics\` only for the
20
+ same question, and give a follow-on question its own, narrower topic. The reviewer chooses before seeing
21
+ your pick, your case or the card's body; put what they need in order to choose, kept neutral, in
22
+ \`crux_fact\`, and background in the body. Make your case in \`rationale\`, and the alternative's best case
23
+ in \`alternative_rationale\`, as you'd argue it if it were your pick. The decision record is committed
24
+ with the code, so don't paste secrets, credentials or private output into a card.
25
+
26
+ A choice you'd put to the reviewer in chat goes as a card, even when they're in the conversation: say
27
+ "it's in your deck" and don't list the options there, so they still choose before seeing your pick. A
28
+ choice the reviewer has already answered in chat is filed all the same, with their answer as your pick and
29
+ their words in \`answered_in_chat\`, for them to confirm with one tap; otherwise, file it before you ask. A
30
+ change that only restores what a card or rule decided cites it, and needs no card.
31
+
32
+ **The commit gate** detects some of these decisions (dependencies, schemas and migrations, CI and
33
+ deploy files) and blocks a commit until each one is covered. Either cite a card whose \`touches\`
34
+ include it, \`Baesian-Card: <id>\`, or, for a two-way door, acknowledge it with a reason:
35
+ \`Baesian-Ack: dep:httpx <why this is fine>\`. When it blocks, the gate prints the lines to paste.
36
+ \`baesian check\` previews it before you commit. Trailers go at the end of the message, after a blank
37
+ line.
38
+
39
+ - \`level: provisional\` (the default): carry on with your proposal. \`blocking\`: run \`baesian wait <id>\`
40
+ before building on it. \`hard\` (the default for publishing, security, privacy, money and data loss): never
41
+ proceed without a verdict. A level is a minimum only when the card is shown: the repo's preferences may
42
+ hold a card back instead, and \`baesian file\` says so. A held card means go ahead with your pick, as
43
+ provisional, citing it as usual; it counts as settled at merge, and the reviewer may answer it later.
44
+ - \`baesian wait <id>\` exits 0 when resolved (use the decision. If it went against your pick and you hold
45
+ a fact the reviewer lacked, or a check that would settle it, you may reply once:
46
+ \`baesian reply <id> "<the fact or check>"\`. Not your pick again); 4 when answered otherwise (returned:
47
+ read the comment, then \`baesian revise <id>\` or \`baesian withdraw <id>\`); 5 with no verdict yet
48
+ (proceed provisionally); 6 with no verdict on a hard card (don't proceed: park the task or stop); 7 while
49
+ the reviewer is still there (run \`baesian wait <id>\` again); 8 when the card is held back (go ahead with
50
+ your pick, as provisional). Each run lasts at most 100 seconds.
51
+ - If you end your turn with cards still with the reviewer whose answers you'll act on (blocking or hard
52
+ ones, or provisional ones), and your harness wakes you when a background command finishes (Claude Code
53
+ does), leave \`baesian wait <id> <id>… --until-answered\` running in the background, or \`--deck <deck>\`
54
+ for your open cards in a deck. It has no time limit: it ends when every card is answered (0 when all are
55
+ resolved, else 4), or sooner when one is returned (4) or held back (8). When it ends, act on the answers
56
+ it printed; \`baesian inbox\` won't repeat them. Without that (Codex), answers come with the next message.
57
+ - If an answer asks for a rule (\`wait\` and \`inbox\` say so), draft it, with the gate patterns it covers:
58
+ \`baesian rule <id> "<the rule>" --covers dep:pytest-*\`. It goes to the reviewer as a card.
59
+ - If you're a long-lived agent, set \`BAESIAN_AGENT\` to your name; otherwise your harness's name is used.
60
+ - If \`baesian file\` exits 3, the decision may already be open or decided: attach with
61
+ \`baesian attach <id>\`, or follow the decision. File with \`--new\` only to argue against one, and set
62
+ \`supersedes:\`. If yours is a different question on the same subject, give it a narrower topic
63
+ (\`client/rule-drafts\` beside \`client/rules\`) and list what it builds on in \`depends_on\`.
64
+ - Commits that build on a card carry the trailer \`Baesian-Card: <id>\`. The gate blocks a commit that
65
+ builds on a rejected card, or on a hard card that isn't resolved yet.
66
+ - Never record a verdict yourself, or write the repo's preference files: only a person answers, and sets
67
+ the preferences, in the Baesian client.
68
+ ${END}`;
69
+ /**
70
+ * Who is running a command (D102): `--agent`, then `$BAESIAN_AGENT` (a long-lived agent's own name), then the
71
+ * name the harness gives itself, then "agent". The MCP server passes the name its client gave in the protocol, which
72
+ * comes before the harness's variables, since a stdio server isn't given them all (P18).
73
+ */
74
+ export function agentName(opt, env = process.env, client) {
75
+ return opt ?? (env.BAESIAN_AGENT?.trim() || undefined) ?? clientAgentName(client) ?? harnessName(env) ?? "agent";
76
+ }
77
+ /** An MCP client's name as an agent's: Claude Code's and Codex's become the names their commands get; others stay. */
78
+ export function clientAgentName(client) {
79
+ const name = client?.trim();
80
+ if (!name)
81
+ return undefined;
82
+ if (/^claude[-_ ]?code\b/i.test(name))
83
+ return "claude-code";
84
+ if (/^codex\b/i.test(name))
85
+ return "codex";
86
+ return name;
87
+ }
88
+ /** The harness's own name, from the variables it sets for the commands it runs. */
89
+ /**
90
+ * The harness session a command runs in, when the harness says: Claude Code gives its commands
91
+ * `CLAUDE_CODE_SESSION_ID`, and Codex `CODEX_THREAD_ID`, each the same ID its hooks receive as `session_id`. So an
92
+ * agent's own `baesian inbox` reads its session's place, as the plugin's hooks do (B-ghhym), and never hears twice.
93
+ */
94
+ export function harnessSession(env = process.env) {
95
+ return env.CLAUDE_CODE_SESSION_ID?.trim() || env.CODEX_THREAD_ID?.trim() || undefined;
96
+ }
97
+ /**
98
+ * The sessions a command's model is looked up by (B-k0w7d), in order: Claude Code's `CLAUDE_CODE_SESSION_ID`; and in
99
+ * Codex `CODEX_SESSION_ID`, which Codex's source names but which wasn't seen set, then `CODEX_THREAD_ID`, which rehearse
100
+ * found equal to the hooks' `session_id` in Codex 0.166.0. Codex's source fills the hooks' `session_id` from the session
101
+ * and the variable from the thread, so in a thread of its own (a subagent's, say) they may differ: then no file
102
+ * matches, and the card has no model rather than a wrong one.
103
+ */
104
+ export function modelSessions(env = process.env) {
105
+ const ids = [env.CLAUDE_CODE_SESSION_ID, env.CODEX_SESSION_ID, env.CODEX_THREAD_ID].map((v) => v?.trim()).filter((v) => Boolean(v));
106
+ return [...new Set(ids)];
107
+ }
108
+ export function harnessName(env) {
109
+ // Claude Code sets AI_AGENT to "<name>_<version>_<role>", for example "claude-code_2-1-314_agent".
110
+ const ai = env.AI_AGENT?.trim();
111
+ if (ai)
112
+ return ai.split("_")[0] || undefined;
113
+ if (env.CLAUDE_CODE_ENTRYPOINT || env.CLAUDECODE)
114
+ return "claude-code";
115
+ // Codex sets no AI_AGENT, but gives the commands it runs a per-session CODEX_THREAD_ID (seen in 0.166.0, by
116
+ // rehearse's agent, with no model calls).
117
+ if (env.CODEX_THREAD_ID)
118
+ return "codex";
119
+ return undefined;
120
+ }
121
+ /**
122
+ * The block for a repo, with its vault's guidance (M6, D173): with a vault, one sentence points agents to its routing
123
+ * notes and its rules, to read when deciding whether to file. They're guidance: the gate takes rules only from the store.
124
+ */
125
+ export function agentsBlock(vault) {
126
+ if (!vault)
127
+ return AGENTS_BLOCK;
128
+ const read = [...vault.notes.map((n) => `\`${n}\``), `the rules in force, in \`${vault.rules}\``];
129
+ const sentence = `Before deciding whether to file, read this repo's guidance in its vault: ${read.length > 2 ? `${read.slice(0, -1).join(", ")} and ${read.at(-1)}` : read.join(" and ")}.`;
130
+ return AGENTS_BLOCK.replace("\n\n**The commit gate**", `\n\n${sentence}\n\n**The commit gate**`);
131
+ }
132
+ /** Write or refresh the Baesian block in `AGENTS.md`, leaving everything outside the markers alone. */
133
+ export function writeAgentsBlock(toplevel, vault) {
134
+ const path = join(toplevel, "AGENTS.md");
135
+ const block = agentsBlock(vault);
136
+ if (!existsSync(path)) {
137
+ writeFileSync(path, `# Agent instructions\n\n${block}\n`);
138
+ return "created";
139
+ }
140
+ const text = readFileSync(path, "utf8");
141
+ const start = text.indexOf(BEGIN);
142
+ const end = text.indexOf(END);
143
+ if (start < 0 || end < start) {
144
+ writeFileSync(path, `${text.replace(/\s*$/, "")}\n\n${block}\n`);
145
+ return "appended";
146
+ }
147
+ const next = text.slice(0, start) + block + text.slice(end + END.length);
148
+ if (next === text)
149
+ return "unchanged";
150
+ writeFileSync(path, next);
151
+ return "updated";
152
+ }
153
+ /** In personal mode, what the block says about the record, which isn't committed there. */
154
+ const RECORD_SENTENCE = "The decision record is committed\nwith the code, so don't paste secrets, credentials or private output into a card.";
155
+ /**
156
+ * What an agent is told in a clone that uses Baesian in personal mode (B-4rwyz), where AGENTS.md says nothing of it: the
157
+ * plugin's session-start hook hands it over once a session (`baesian inbox --quiet`). It's `AGENTS_BLOCK`'s instructions,
158
+ * after a paragraph on what personal mode changes for the agent.
159
+ */
160
+ export function personalInstructions() {
161
+ const body = AGENTS_BLOCK.slice(BEGIN.length, -END.length).trim();
162
+ if (!body.includes(RECORD_SENTENCE))
163
+ throw new Error("AGENTS_BLOCK no longer says the record is committed in the words personal mode replaces");
164
+ return [
165
+ `Baesian, in personal mode${cite("B-4rwyz")}: this clone uses Baesian for the person you're working with, and the repo's`,
166
+ "collaborators don't, so AGENTS.md doesn't mention it and nothing of Baesian's is committed. Its instructions still apply",
167
+ "to you, as below. The commit gate checks your commits as usual, and takes `Baesian-Card` and `Baesian-Ack` trailers out",
168
+ "of the message once it has, into this clone's ledger. Keep card IDs and Baesian itself out of anything others see: code,",
169
+ "comments, commit messages, pull requests. `baesian inbox` says when this clone's setup needs fixing.",
170
+ "",
171
+ body.replace(RECORD_SENTENCE, "The decision record stays in this\nclone, but a card reaches the reviewer's website in a linked repo, so don't paste secrets, credentials\nor private output into a card."),
172
+ ].join("\n");
173
+ }
174
+ /** Claude Code reads CLAUDE.md, not AGENTS.md. Warn when CLAUDE.md exists and doesn't import it. */
175
+ export function claudeMdMissesAgents(toplevel) {
176
+ const path = join(toplevel, "CLAUDE.md");
177
+ return existsSync(path) && !/@AGENTS\.md/.test(readFileSync(path, "utf8"));
178
+ }
179
+ /**
180
+ * The card's fields, with their notes, in the template's order: one table, so `baesian template` and the MCP filing
181
+ * tool's schema can't drift apart (D167).
182
+ */
183
+ export const CARD_FIELDS = [
184
+ { field: "title", value: '""', note: 'a neutral short name: "Where the access token lives"' },
185
+ { field: "question", value: '""', note: "the decision, as a neutral question" },
186
+ { field: "doing", value: '""', note: "what you were doing when it came up: \"writing the server's file watcher\"" },
187
+ { field: "topic", value: '""', note: "area/subject, naming this one question; reuse one from `baesian topics` only for the same question" },
188
+ { field: "touches", value: "[]", note: "any of: schema, public-api, dependency, infra, user-facing, publishes, security, privacy, money, data-loss" },
189
+ { field: "reverse", value: '""', note: "how costly it is to undo: cheap, moderate or one-way" },
190
+ { field: "proposal", value: '""', note: "your pick, in a few words" },
191
+ { field: "proposal_then", value: '""', note: "what happens if it's chosen, read after \"then\": \"the relay keeps…\"" },
192
+ { field: "alternative", value: '""', note: "the strongest alternative, in a few words" },
193
+ { field: "alternative_then", value: '""', note: "what happens if it's chosen, read after \"then\"" },
194
+ { field: "crux", value: '""', note: "the one question the choice turns on" },
195
+ { field: "crux_proposal", value: '""', note: 'the answer that leads to your pick: "If it does"' },
196
+ { field: "crux_alternative", value: '""', note: "the answer that leads to the alternative: \"If it doesn't\"" },
197
+ { field: "crux_preference", value: "true", optional: true, note: "the crux is about what the reviewer values, not a fact" },
198
+ { field: "crux_fact", value: '""', optional: true, note: "what the reviewer needs to choose, kept neutral: the only context they see first" },
199
+ { field: "rationale", value: '""', optional: true, note: "why you pick the proposal" },
200
+ { field: "alternative_rationale", value: '""', optional: true, note: "the best case for the alternative: why you'd pick it, if it were your pick" },
201
+ {
202
+ field: "answered_in_chat", value: '""', optional: true,
203
+ note: "only if the reviewer already answered it in chat: what they said, quoted and short; your pick is their answer, which they confirm with one tap",
204
+ },
205
+ {
206
+ field: "wrong_if", value: '""', optional: true,
207
+ note: "what would show your pick is wrong: required if reverse is one-way, touches includes security or privacy, or a premise is set",
208
+ line: "(what would show your pick is wrong: required if reverse is one-way,",
209
+ },
210
+ {
211
+ field: "check", value: '""', optional: true,
212
+ note: "where to look to check wrong_if: required with it",
213
+ line: " touches includes security or privacy, or a premise is set; check says where to look)",
214
+ },
215
+ { field: "premise", value: '""', optional: true, note: "a claim an option rests on that nobody has checked; give premise_option and premise_check with it", line: "(a claim an option rests on that nobody has checked," },
216
+ { field: "premise_option", value: "proposal", optional: true, note: "which option the premise rests on: proposal or alternative", line: " which option rests on it," },
217
+ { field: "premise_check", value: '""', optional: true, note: "how to check the premise", line: " and how to check it)" },
218
+ { field: "acted", value: '""', optional: true, note: "what you've already built for one option; give acted_option with it", line: "(what you've already built for one option," },
219
+ { field: "acted_option", value: "proposal", optional: true, note: "which option you've already built for: proposal or alternative", line: " and which)" },
220
+ { field: "serves", value: '""', optional: true, note: "the criterion your pick serves, when the crux is a preference" },
221
+ { field: "also_considered", value: "[]", optional: true, note: "other options, by name" },
222
+ { field: "rules", value: "[R-xxxxx]", optional: true, note: "rules this decision bears on" },
223
+ { field: "proposal_breaks", value: "[]", optional: true, note: "decisions or rules your pick would replace or break" },
224
+ { field: "alternative_breaks", value: "[]", optional: true, note: "decisions or rules the alternative would replace or break", line: "" },
225
+ { field: "level", value: "blocking", optional: true, note: "provisional by default; hard by default for publishes, security, privacy, money, data-loss; a minimum only if the card is shown" },
226
+ { field: "depends_on", value: "[B-xxxxx]", optional: true, note: "the cards this decision builds on", line: "" },
227
+ { field: "deck", value: "storage-layer", optional: true, note: "your branch's deck by default" },
228
+ { field: "supersedes", value: "", template: false, note: "a decision this card argues against, filed with new; it becomes superseded once this card resolves" },
229
+ { field: "modules_touched", value: "", template: false, note: "how many modules the decision touches" },
230
+ { field: "confidence", value: "", template: false, note: "your confidence in your pick, from 0 to 1: recorded, not used for routing" },
231
+ { field: "domain", value: "", template: false, note: "routing tags" },
232
+ { field: "evidence", value: "", template: false, note: "links that bear on the decision" },
233
+ {
234
+ field: "secret_reason", value: "", template: false,
235
+ note: "why you file a card that looks like it holds a secret: in a hosted repo filing refuses one without it (B-jfkv9). Kept on the card, where reviewers see it",
236
+ },
237
+ ];
238
+ function templateLine(f) {
239
+ const head = f.optional ? `# ${f.field}: ${f.value}` : `${f.field}: ${f.value}`;
240
+ const comment = f.line ?? (f.optional ? `(${f.note})` : `# ${f.note}`);
241
+ // The comments start in one column, and a field too long for it keeps a space before its own.
242
+ return comment ? `${head.padEnd((f.optional ? 27 : 23) - 1)} ${comment}` : head;
243
+ }
244
+ export const CARD_TEMPLATE = `---
245
+ ${CARD_FIELDS.filter((f) => f.template !== false).map(templateLine).join("\n")}
246
+ # Below the closing line, optionally: background, shown with your case once the reviewer has chosen.
247
+ ---
248
+ `;
249
+ //# sourceMappingURL=agents.js.map
package/dist/api.js ADDED
@@ -0,0 +1,13 @@
1
+ /** The fields a verdict may be sent with (`ApiVerdictInput`); anything else is refused. The local server and the relay share them. */
2
+ export const VERDICT_FIELDS = new Set(["card", "revision", "response", "choice", "comment", "first_choice", "make_rule", "retire_rules", "should_have_asked"]);
3
+ /** The fields a preference change may be sent with (`ApiPreferencesInput`); anything else is refused. */
4
+ export const PREFERENCE_FIELDS = new Set([
5
+ "replaces", "preset", "mode", "rate_agent_decides", "rate_sample", "burn_in", "reason", "survey_see", "survey_fine",
6
+ "gate_schema", "gate_infra", "gate_api", "gate_ignore",
7
+ ...["public_api", "schema", "dependency", "infra", "user_facing", "security", "privacy", "other"].map((r) => `row_${r}`),
8
+ ]);
9
+ /** The fields a practice entry may be sent with (`ApiPracticeInput`); anything else is refused. The local server and the relay share them. */
10
+ export const PRACTICE_FIELDS = new Set(["kind", "card", "revision", "answered", "blind", "final", "test", "mark", "practice"]);
11
+ /** The milliseconds after which a waiting marker counts as gone; the same as the store's. */
12
+ export const WAITING_STALE_MS = 90_000;
13
+ //# sourceMappingURL=api.js.map
package/dist/bin.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "./cli.js";
3
+ // A reader that stops early (`baesian show B-x | head -1`) closes the pipe it reads: what's left to write has nowhere to
4
+ // go, so the command ends there, quietly, as a shell's own tools do, rather than with Node's unhandled EPIPE. Any other
5
+ // failure to write is still one.
6
+ for (const stream of [process.stdout, process.stderr]) {
7
+ stream.on("error", (e) => {
8
+ if (e.code === "EPIPE")
9
+ process.exit(0);
10
+ throw e;
11
+ });
12
+ }
13
+ process.exitCode = await main();
14
+ //# sourceMappingURL=bin.js.map