@bramburn/pi-model-council 1.6.3 → 1.6.11

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,342 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.6.0] - 2026-06-26
9
+
10
+ ### Changed
11
+ - **Extracted shared runner helpers into `runnerHelpers.ts`.** The two
12
+ runner files (`councilRunner.ts` and `secondOpinionRunner.ts`) had
13
+ near-identical logic for:
14
+ - 3-source API key resolution (settings → registry → env)
15
+ - parse-then-repair-then-fallback pipeline for model responses
16
+ - "call model + timeout + wrap errors with model name" wrapper
17
+
18
+ Per Sandi Metz ("duplication is better than the wrong abstraction"),
19
+ the two runners share these **same-concept** cross-cutting concerns
20
+ while keeping the **different concepts** (fan-out orchestration,
21
+ synthesis step, fallback decision) in their respective files.
22
+ - `resolveOpenRouterApiKey(settings, modelRegistry?)` — shared
23
+ - `parseModelOpinionResponse(rawText)` — shared
24
+ - `callModelWithTimeout(args)` — shared
25
+ - Per-model fan-out, blind-label translation, synthesis prompt
26
+ building → still in `councilRunner.ts`
27
+
28
+ ### Improved (secondOpinionRunner)
29
+ - **Now uses structured output + retry**, matching the council runner.
30
+ Previously `/opinion` was the only path that didn't try JSON schema
31
+ first and retry with structured-output-fallback on the `isStructuredOutputError`
32
+ heuristic. Now both runners follow the same "try JSON schema → retry
33
+ without schema on 4xx/structured-output-unsupported" pattern, so
34
+ opinion responses are more reliable on models that have stricter
35
+ schema requirements.
36
+ - **Pre-flight status updates** during API key resolution, mirroring
37
+ `/council`'s per-step progress. Previously the only status was
38
+ "Second opinion: querying model..." — now also "resolving API key..."
39
+ and "rendering markdown..." before and after the call.
40
+ - **Actionable error format** (`Fix: \`/command\``) for setup errors,
41
+ matching `/council` (v1.2.0). Previously said "Run /opinion-settings";
42
+ now says "Fix: \`/opinion-settings\`".
43
+
44
+ ### Improved (councilRunner)
45
+ - **Pre-flight status**: added "Council: resolving API key..." before
46
+ the existing "validating API key..." step so the user sees activity
47
+ during the registry/env lookup.
48
+
49
+ ### Tests
50
+ - 12 new tests for `runnerHelpers.ts` covering: API key resolution
51
+ from each of the 3 sources, whitespace trimming, registry-throw
52
+ fallback, env fallback, well-formed JSON parsing, markdown fence
53
+ stripping, fallback shape, partial-JSON repair, success and
54
+ failure paths of `callModelWithTimeout`.
55
+ - Total now **111 passing**.
56
+
57
+ ## [1.5.1] - 2026-06-26
58
+
59
+ ### Documentation
60
+ - **Added a comprehensive Installation section to the README.** Previously
61
+ the README jumped straight from "Features" to "Workflow" with no
62
+ explanation of how to actually install the extension. The new section
63
+ covers:
64
+
65
+ - Prerequisites (Pi version, Node.js 22+, OpenRouter key)
66
+ - A comparison table of all 5 install methods (npm, SSH, HTTPS+PAT,
67
+ local clone, one-shot try) with the destination path each lands in
68
+ - A "pin to a tag" recommendation for reproducibility in shared/CI use
69
+ - Step-by-step walkthroughs for each method, including SSH-vs-HTTPS
70
+ tradeoffs and the fine-grained PAT scope needed for HTTPS
71
+ - The `pi -l` flag for project-scoped (team-shareable) installs
72
+ - `pi list` / `pi config` verification commands
73
+ - `pi update --extensions` and overwrite-to-new-tag for updates
74
+ - `pi remove pi-model-council` for uninstall
75
+ - Where each install lands in the filesystem
76
+
77
+ No code changes — docs only.
78
+
79
+ ## [1.5.0] - 2026-06-26
80
+
81
+ ### Security
82
+ - **`commandParser.ts` rewritten as a hardened slash-command parser.**
83
+ Slash-command arguments flow directly into LLM prompts, so the parser
84
+ is both a UX surface and a security surface. Hardening (research-
85
+ grounded by the OWASP GenAI guidance and Trail of Bits' agent
86
+ injection writeups):
87
+
88
+ - **Unicode NFC normalization** of the full input before any
89
+ comparison or tokenisation, so homoglyphs (e.g. Cyrillic 'а'
90
+ vs Latin 'a') can't bypass the mode allow-list.
91
+ - **Control-character stripping** (null bytes, terminal escape
92
+ sequences, ASCII 0x00–0x1F except newline/tab). Raw control
93
+ bytes pasted into a prompt can break terminal rendering, smuggle
94
+ ANSI escapes, or confuse downstream JSON parsers.
95
+ - **Input length limits** enforced at multiple layers:
96
+ - Max 8000 chars on the problem
97
+ - Max 2000 chars on `--understanding`
98
+ - Max 1000 chars per `--constraint` / `--question`
99
+ - Max 4000 chars per token (early rejection in the tokenizer)
100
+ - Max 200 tokens total per command
101
+ - Max 20 constraints and 20 questions
102
+ - **Error messages on unterminated quotes** instead of silently
103
+ truncating the user's prompt at the next whitespace.
104
+ - **Escaped quotes inside strings** — `"He said \"hello\""` is now
105
+ parsed correctly as `He said "hello"`.
106
+ - **Duplicate `--understanding` detection** (rejected).
107
+ - **Clearer error messages** that always include the full usage
108
+ line so the user can recover without grepping docs.
109
+
110
+ ### Changed
111
+ - **DRY'd the two slash-command parsers into one shared implementation.**
112
+ `parseCouncilCommandArgs` and `parseSecondOpinionCommandArgs` were
113
+ ~95% duplicate before. They now share a single `parseCommon()`
114
+ function parameterised by mode allow-list, default mode, question
115
+ field name, and usage string. The single source of truth makes it
116
+ much harder to drift the two parsers out of sync on a future
117
+ change.
118
+
119
+ ### Tests
120
+ - 22 new tests for the command parser covering: quoted/unquoted
121
+ problems, mode aliases (`arch` → `architecture`), short flag aliases
122
+ (`-c`/`-q`/`-u`), escaped quotes, unterminated quotes, missing flag
123
+ values, unknown flags, control-char stripping, oversized problem,
124
+ oversized token, duplicate `--understanding`, constraint/question
125
+ count limits, mode token recognition for opinion vs council.
126
+ - Total now **99 passing**.
127
+
128
+ ## [1.4.0] - 2026-06-26
129
+
130
+ ### Removed
131
+ - **Qdrant persistence layer.** The `qdrantClient.ts` and
132
+ `persistence.ts` files (~290 lines of code) have been removed
133
+ along with the `CouncilDecision.metadata.persisted` and
134
+ `persistenceError` fields. Removed the `COUNCIL_PERSISTENCE_ENABLED`,
135
+ `QDRANT_URL`, `QDRANT_API_KEY`, and `QDRANT_COUNCIL_COLLECTION`
136
+ environment variable lookups.
137
+
138
+ ### Why
139
+ The original v0.1.0 intent was to use Qdrant as a vector store for
140
+ semantic retrieval over past council decisions. The implementation
141
+ never delivered on that promise:
142
+
143
+ - `createDecisionVector` produced a placeholder 8-dim vector of
144
+ mostly constants (one-hot mode, one-hot confidence, etc.) instead
145
+ of a real semantic embedding.
146
+ - No retrieval path existed — nothing ever read from Qdrant to
147
+ influence future council runs.
148
+ - The feature was opt-in via an undocumented env var, off by default,
149
+ and not described in the README setup steps.
150
+
151
+ Per the research-grounded review for v1.4.0, this was dead code
152
+ that added attack surface (a network client) and maintenance burden
153
+ without delivering any user-visible value. Removal simplifies the
154
+ codebase, reduces dependency footprint, and eliminates the
155
+ misleading "Qdrant persistence (optional)" mention in the docs.
156
+
157
+ If semantic retrieval over past decisions becomes a real feature
158
+ request in the future, it should be built as a separate extension
159
+ (`pi-model-council-memory` or similar) with proper embeddings and a
160
+ documented setup flow.
161
+
162
+ ### Migration
163
+ If you somehow had `COUNCIL_PERSISTENCE_ENABLED=true` set, just
164
+ unset it. Nothing else changes.
165
+
166
+ ## [1.3.0] - 2026-06-26
167
+
168
+ ### Changed
169
+ - **Synthesis prompt now uses blind labels (Opinion A/B/C) for council
170
+ members.** Previously the chairman saw model names directly, which
171
+ research shows causes "anchor and prestige bias" — the chairman tends
172
+ to over-weight opinions from the most-recognised model regardless of
173
+ the actual content. The new flow:
174
+ 1. Council members are presented anonymously as Opinion A, B, C...
175
+ 2. The chairman synthesises one decision using only the blind labels
176
+ 3. After the synthesis, the runner resolves each blind label back to
177
+ the actual model id so the final report's `modelNotes` show real
178
+ model names
179
+
180
+ This is the same pattern recommended for LLM-judge ensembles in
181
+ evaluation research and matches what tools like `llm-council` and
182
+ academic ensemble systems use.
183
+ - **Synthesis system prompt rewritten with explicit decision rules**
184
+ (research-grounded):
185
+ - "Compare options on evidence, not on which model said them"
186
+ - "Do NOT blend incompatible views into a mushy compromise"
187
+ - "Do NOT copy any single opinion verbatim"
188
+ - "When confidence is mixed, lower the overall confidence"
189
+ Each rule directly counters a known failure mode from the literature
190
+ (winner-take-all, averaging, hidden disagreement).
191
+ - **Per-model progress indicator during fan-out.** Previously the
192
+ footer showed a single static "Council: querying models..." line.
193
+ Now it updates to e.g. `Council: 2/3 models responded (waiting on
194
+ qwen/qwen3.7-max)` so the user sees real activity.
195
+
196
+ ### Fixed
197
+ - **`extractJsonObject` is now robust to common LLM JSON mistakes.**
198
+ Previously the parser used a naive "first { to last }" substring
199
+ extraction, which could:
200
+ - Mistake a `}` inside a string literal for the closing brace
201
+ - Fail when JSON was truncated mid-stream (e.g. `max_tokens` hit
202
+ before the close brace)
203
+ - Reject otherwise-valid JSON containing Python literals like
204
+ `True` / `False` / `None` or trailing commas before `}` / `]`
205
+ The new implementation:
206
+ - Walks brace-balance forward from the first `{`, ignoring braces
207
+ inside string literals and respecting escaped quotes
208
+ - Falls back to a minimal repair pass for `True`/`False`/`None` and
209
+ trailing commas before parsing
210
+ - Returns a clearer error message including the substring length
211
+ when all repair attempts fail
212
+
213
+ ### Tests
214
+ - 9 new tests for `extractJsonObject` (brace balance, escaped quotes,
215
+ Python literal repair, trailing comma repair, truncated JSON).
216
+ - 1 new test for the blind-label transformation in `buildSynthesisPrompts`.
217
+ - Total now **77 passing**.
218
+
219
+ ## [1.2.0] - 2026-06-26
220
+
221
+ ### Fixed
222
+ - **`saveLatestCouncilReport` / `saveLatestSecondOpinion` now use `ctx.cwd` instead of `process.cwd()`.** Previously, when the extension was launched from a directory other than the project root (e.g. via `cd /elsewhere && pi`), the report was written under `/elsewhere/.pi/council/` instead of `<project>/.pi/council/`. Now it always lands in the project Pi is running in.
223
+ - **`council_decide` tool now also saves the report to disk.** Previously only the `/council` slash command did — when the LLM invoked the tool directly (which is the primary entry point from the agent's perspective), no `.pi/council/last-decision.md` artifact was produced. The behaviour is now consistent regardless of which entry point is used.
224
+ - **Status-key collision between `/council` and `/opinion`.** Both commands previously used the same `setStatus("model-council", ...)` key, so a long-running council and a quick opinion would clobber each other's footer message when invoked concurrently. Split into `"model-council:run"` and `"model-council:opinion"`.
225
+ - **`/council-settings reset` now only resets council settings**, not the opinion model too. The command name implied a narrower scope. The confirm prompt was updated to reflect what actually gets cleared.
226
+ - **`/opinion-settings reset` no longer hardcodes the default model.** It now sources the default from `createDefaultSettings()` so the command stays in lock-step with the rest of the codebase (no drift if the default model ever changes).
227
+
228
+ ### Added
229
+ - **Pre-flight status updates.** `runCouncil` now calls `onStatus` during pre-flight so the user sees `Council: validating API key...` and `Council: verifying configured models are available...` instead of an unresponsive spinner during the OpenRouter ping + model fetch.
230
+ - **Reasoning-capable badge in the model picker.** Models from pi's registry that support extended thinking now display a `[reasoning]` suffix in their second-line description, making it easier to pick a reasoning-tuned model for the synthesis role. (OpenRouter's REST `/models` endpoint doesn't expose this, so the badge only appears when models come from pi's built-in registry.)
231
+ - **Actionable error messages.** Pre-flight errors now start with `Fix: \`/command\`` (e.g. `Fix: \`/council-settings\` to pick replacements.`) following the actionable-error pattern (context + diagnosis + next step) — instead of the more passive `Run /command`.
232
+
233
+ ### Tests
234
+ - 2 new tests for reasoning + contextWindow propagation through the
235
+ registry helper. Total now **67 passing**.
236
+
237
+ ## [1.1.1] - 2026-06-26
238
+
239
+ ### Fixed
240
+ - **Search picker now actually filters as you type.** v1.1.0 used
241
+ `SelectList.setFilter()` internally, which filters with
242
+ `value.startsWith(query)`. Typing "claude" failed to match
243
+ `value: "anthropic/claude-3.5-sonnet"` (the value doesn't *start* with
244
+ "claude"), so the list filtered to empty and looked like typing was
245
+ broken. v1.1.1 manages the filtered list itself using `fuzzyFilter`
246
+ from `@earendil-works/pi-tui` (the same primitive pi's built-in
247
+ `/model` selector uses), so queries now match across the model
248
+ name, id, and any provider tag in the search haystack.
249
+
250
+ ### Changed
251
+ - `searchSelector.ts` no longer depends on `SelectList` (which only
252
+ supports `startsWith` filtering). It renders the list directly with
253
+ `Text` + `Container` children, so any visible-vs-scrolled logic is
254
+ local and easy to follow.
255
+
256
+ ## [1.1.0] - 2026-06-26
257
+
258
+ ### Added
259
+ - **Typeahead-searchable, scrollable model picker** for both
260
+ `/council-settings` and `/opinion-settings`. Replaces the flat
261
+ `ctx.ui.select()` list with a custom component that:
262
+ - Fuzzy-filters the model list as you type (matches model name, id,
263
+ and provider all at once)
264
+ - Scrolls with Up/Down (PageUp/PageDown jump a viewport)
265
+ - Shows the model id as a muted secondary line under the model name
266
+ - Excludes already-picked council members from later steps
267
+ - Falls back to `ctx.ui.select()` in non-TUI modes (RPC, JSON, print)
268
+ so headless environments still work
269
+ - **`searchSelector.ts`** — reusable helper exported for other
270
+ Pi extensions. Renders via `ctx.ui.custom()` with the same
271
+ `Input` + `SelectList` primitives as pi's built-in `/model` selector.
272
+
273
+ ### Changed
274
+ - `/council-settings` flow now shows the searchable picker for all five
275
+ model-selection steps (3 council, 1 synthesis, 1 opinion) plus the
276
+ number of models remaining at each step.
277
+ - `/opinion-settings` collapses the previous two-step (Provider → Model)
278
+ flow into a single searchable picker that searches across all
279
+ configured providers.
280
+
281
+ ## [1.0.0] - 2026-06-26
282
+
283
+ ### Added
284
+ - **First public release.**
285
+ - **Pi-native model discovery** — `/council-settings` now uses
286
+ `ctx.modelRegistry.getAvailable()` filtered by `provider === "openrouter"`
287
+ to list available models. Skips the API-key prompt entirely when Pi
288
+ already knows about OpenRouter (via `OPENROUTER_API_KEY` or `/login openrouter`).
289
+ - **4th synthesis model selection** — when configuring the council, you can
290
+ now pick a separate model that reads the three council opinions and writes
291
+ the final decision. Defaults to "Council Model 1".
292
+ - **Registry-aware API key resolution** — runners now resolve the OpenRouter
293
+ key from three sources in order: `council-settings.json` →
294
+ `modelRegistry.getApiKeyForProvider("openrouter")` → `process.env.OPENROUTER_API_KEY`.
295
+ - **Comprehensive Quick Start workflow in README** — 3-step setup
296
+ (authenticate → configure → use), the 4-model orchestration flow diagram,
297
+ and a clear API-key resolution order.
298
+ - **DISCLAIMER.md** — covers AI accuracy, cost, and third-party terms
299
+ (OpenRouter, model providers, optional Qdrant persistence).
300
+ - **CONTRIBUTING.md** — dev setup, Conventional Commits, PR process.
301
+ - **CODE_OF_CONDUCT.md** — Contributor Covenant v2.1.
302
+ - **SUPPORT.md** — where to ask questions, response times.
303
+ - **Issue and PR templates** — bug report, feature request, PR checklist.
304
+ - **7 new tests** for the registry-based settings path. Suite is now
305
+ 60 tests total.
306
+
307
+ ### Changed
308
+ - `openCouncilSettingsUI` rewritten to prefer the pi model registry.
309
+ The legacy API-key-prompt flow remains as a fallback when the registry
310
+ exposes fewer than 3 OpenRouter models.
311
+ - `CouncilSettings` gains an optional `synthesis.modelId` field (defaults
312
+ to `models.model1` when omitted — fully backward compatible).
313
+ - `formatSettingsForDisplay` shows the synthesis model and the "(using
314
+ pi auth — no key stored locally)" indicator when the settings file
315
+ carries no API key.
316
+ - `package.json` `files` array explicitly ships all 8 markdown docs so
317
+ they're included when the package is installed via npm.
318
+
319
+ ## [0.1.0] - 2024-06-25
320
+
321
+ ### Added
322
+ - Initial private release
323
+ - `/council` command for multi-model coding decisions (3 OpenRouter models)
324
+ - `/opinion` command for single-model second opinions
325
+ - `/council-settings` command for configuring OpenRouter API key and council models
326
+ - `/opinion-settings` command for configuring the second opinion model
327
+ - Settings persistence via `~/.pi/agent/council-settings.json`
328
+ - OpenRouter model discovery and validation
329
+ - Qdrant persistence for council decisions (optional)
330
+ - Full test suite with Vitest
331
+ - Security CI/CD with Gitleaks and npm audit
332
+
333
+ [1.6.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.6.0
334
+ [1.5.1]: https://github.com/bramburn/pi-model-council/releases/tag/v1.5.1
335
+ [1.5.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.5.0
336
+ [1.4.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.4.0
337
+ [1.3.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.3.0
338
+ [1.2.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.2.0
339
+ [1.1.1]: https://github.com/bramburn/pi-model-council/releases/tag/v1.1.1
340
+ [1.1.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.1.0
341
+ [1.0.0]: https://github.com/bramburn/pi-model-council/releases/tag/v1.0.0
342
+ [0.1.0]: https://github.com/bramburn/pi-model-council/releases/tag/v0.1.0
@@ -0,0 +1,134 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behaviour that contributes to a positive environment for our
18
+ community include:
19
+
20
+ - Demonstrating empathy and kindness toward other people
21
+ - Being respectful of differing opinions, viewpoints, and experiences
22
+ - Giving and gracefully accepting constructive feedback
23
+ - Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ - Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behaviour include:
29
+
30
+ - The use of sexualised language or imagery, and sexual attention or
31
+ advances of any kind
32
+ - Trolling, insulting or derogatory comments, and personal or political attacks
33
+ - Public or private harassment
34
+ - Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ - Other conduct that could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behaviour and will take appropriate and fair corrective action in
43
+ response to any behaviour that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces — including the GitHub
54
+ repository, issue tracker, discussions, and any other channels the project uses
55
+ for communication. It also applies when an individual is officially representing
56
+ the community in public spaces (for example, using an official e-mail address,
57
+ posting via an official social media account, or acting as an appointed
58
+ representative at an online or offline event).
59
+
60
+ ## Enforcement
61
+
62
+ Instances of abusive, harassing, or otherwise unacceptable behaviour may be
63
+ reported to the community leaders responsible for enforcement via the GitHub
64
+ Security Advisories page:
65
+
66
+ **https://github.com/bramburn/pi-model-council/security/advisories/new**
67
+
68
+ All complaints will be reviewed and investigated promptly and fairly. All
69
+ community leaders are obligated to respect the privacy and security of the
70
+ reporter of any incident.
71
+
72
+ ## Enforcement Guidelines
73
+
74
+ Community leaders will follow these Community Impact Guidelines in determining
75
+ the consequences of any action they deem in violation of this Code of Conduct:
76
+
77
+ ### 1. Correction
78
+
79
+ **Community Impact**: Use of inappropriate language or other behaviour deemed
80
+ unprofessional or unwelcome in the community.
81
+
82
+ **Consequence**: A private, written warning from community leaders, providing
83
+ clarity around the nature of the violation and an explanation of why the
84
+ behaviour was inappropriate. A public apology may be requested.
85
+
86
+ ### 2. Warning
87
+
88
+ **Community Impact**: A violation through a single incident or series
89
+ of actions.
90
+
91
+ **Consequence**: A warning with consequences for continued behaviour. No
92
+ interaction with the people involved, including unsolicited interaction with
93
+ those enforcing the Code of Conduct, for a specified period of time. This
94
+ includes avoiding interactions in community spaces as well as external channels
95
+ like social media. Violating these terms may lead to a temporary or
96
+ permanent ban.
97
+
98
+ ### 3. Temporary Ban
99
+
100
+ **Community Impact**: A serious violation of community standards, including
101
+ sustained inappropriate behaviour.
102
+
103
+ **Consequence**: A temporary ban from any sort of interaction or public
104
+ communication with the community for a specified period of time. No public or
105
+ private interaction with the people involved is allowed during this period.
106
+ Violating these terms may lead to a permanent ban.
107
+
108
+ ### 4. Permanent Ban
109
+
110
+ **Community Impact**: Demonstrating a pattern of violation of community
111
+ standards, including sustained inappropriate behaviour, harassment of an
112
+ individual, or aggression toward or disparagement of classes of individuals.
113
+
114
+ **Consequence**: A permanent ban from any sort of public interaction within
115
+ the community.
116
+
117
+ ## Attribution
118
+
119
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
120
+ version 2.1, available at
121
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
122
+
123
+ Community Impact Guidelines were inspired by
124
+ [Mozilla's code of conduct enforcement ladder][mozilla coc].
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ [https://www.contributor-covenant.org/faq][faq]. Translations are available
128
+ at [https://www.contributor-covenant.org/translations][translations].
129
+
130
+ [homepage]: https://www.contributor-covenant.org
131
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
132
+ [mozilla coc]: https://github.com/mozilla/diversity
133
+ [faq]: https://www.contributor-covenant.org/faq
134
+ [translations]: https://www.contributor-covenant.org/translations
@@ -0,0 +1,128 @@
1
+ # Contributing to `pi-model-council`
2
+
3
+ Thanks for your interest in improving `pi-model-council`! This document explains how to set up the project locally, the standards we expect from pull requests, and how the review process works.
4
+
5
+ ## Code of Conduct
6
+
7
+ By participating in this project, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it before contributing.
8
+
9
+ ## Reporting issues
10
+
11
+ Before opening an issue:
12
+
13
+ 1. Search existing issues to avoid duplicates.
14
+ 2. Try the latest version on `main` to see if the bug still reproduces.
15
+ 3. Collect relevant details: Pi version, Node version, OS, model names, and a minimal reproduction.
16
+
17
+ Use the issue templates in [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/) when filing:
18
+
19
+ - **Bug report** — something is broken or behaves unexpectedly.
20
+ - **Feature request** — propose a new capability.
21
+
22
+ For security vulnerabilities, do **not** open a public issue. Follow [SECURITY.md](SECURITY.md) instead.
23
+
24
+ ## Development setup
25
+
26
+ ### Prerequisites
27
+
28
+ - **Node.js 22+** (matches the CI runner; older versions may work but are untested)
29
+ - **npm 10+** (ships with Node 22)
30
+ - A Pi install for end-to-end smoke testing (`pi install ./pi-model-council`)
31
+
32
+ ### Clone and install
33
+
34
+ ```bash
35
+ git clone https://github.com/bramburn/pi-model-council.git
36
+ cd pi-model-council
37
+ npm install
38
+ ```
39
+
40
+ ### Available scripts
41
+
42
+ | Script | Purpose |
43
+ |---|---|
44
+ | `npm run typecheck` | Run `tsc --noEmit`; must pass before review. |
45
+ | `npm run lint` | Run ESLint with zero warnings allowed. |
46
+ | `npm test` | Run the Vitest suite once. |
47
+ | `npm run test:watch` | Vitest in watch mode for local development. |
48
+ | `npm run test:coverage` | Vitest with V8 coverage report. |
49
+ | `npm run audit` | `npm audit --audit-level=high` for dependency vulnerabilities. |
50
+
51
+ All four of `typecheck`, `lint`, `test`, and `audit` run in CI on every push and pull request. PRs that fail any of them will not be merged.
52
+
53
+ ## Making changes
54
+
55
+ ### Branch and commit
56
+
57
+ 1. Create a topic branch off `main`:
58
+ ```bash
59
+ git checkout -b feat/short-description
60
+ # or
61
+ git checkout -b fix/short-description
62
+ ```
63
+ 2. Keep commits focused. One logical change per commit is ideal.
64
+ 3. Use the [Conventional Commits](https://www.conventionalcommits.org/) format for commit messages:
65
+ ```
66
+ feat: add per-model timeout override
67
+ fix: redact API key in /council-settings list output
68
+ docs: clarify DISCLAIMER.md cost section
69
+ chore: bump @sinclair/typebox to 0.34.1
70
+ ```
71
+
72
+ ### Code style
73
+
74
+ - TypeScript strict mode is enforced — no `any` unless you add a comment explaining why.
75
+ - Prefer named exports; avoid default exports except for the Pi extension entry point (`index.ts`).
76
+ - Keep functions small and focused. If a function grows past ~80 lines, consider splitting it.
77
+ - Do not add new runtime dependencies without discussion in the issue first.
78
+ - Use the existing `schemas.ts` (TypeBox) for any new tool parameter validation.
79
+ - Keep the dependency footprint minimal — see [SECURITY.md](SECURITY.md#security-dependencies).
80
+
81
+ ### Tests
82
+
83
+ - New behaviour **must** include tests. The Vitest suite lives in `__tests__/`.
84
+ - For new settings logic, follow the pattern in `__tests__/settings.test.ts`.
85
+ - For new UI flows, follow `__tests__/settings-ui.test.ts` (mock the UI surface).
86
+ - For new tools/commands or wiring changes, add a case to `__tests__/smoke.test.ts`.
87
+ - Aim to keep coverage roughly flat or higher; large drops will be flagged in review.
88
+
89
+ ### Documentation
90
+
91
+ - If you change a user-facing command or flag, update `README.md` in the same PR.
92
+ - If you add a new env-var or setting, update `.env.example` and `README.md`.
93
+ - If you change the security posture, update `SECURITY.md`.
94
+ - If your change touches AI-generated content or external APIs, update `DISCLAIMER.md`.
95
+
96
+ ## Pull request process
97
+
98
+ 1. Push your branch and open a PR against `main`.
99
+ 2. Fill in the [pull request template](.github/PULL_REQUEST_TEMPLATE.md).
100
+ 3. Ensure all CI checks pass (typecheck, lint, test, audit).
101
+ 4. Address review feedback by pushing additional commits — do not force-push during review.
102
+ 5. Once approved, a maintainer will squash-merge your PR.
103
+
104
+ ### PR title format
105
+
106
+ Use the same Conventional Commits prefix as your commits:
107
+
108
+ ```
109
+ feat: add model discovery fallback to /council-settings
110
+ fix: handle missing opinion provider gracefully
111
+ docs: clarify README setup steps
112
+ ```
113
+
114
+ The PR title becomes the squash commit message, which feeds into the changelog.
115
+
116
+ ## Release process
117
+
118
+ Releases are cut from `main` by the maintainer. Each release:
119
+
120
+ 1. Bumps the version in `package.json` (semver).
121
+ 2. Adds a dated section to `CHANGELOG.md` summarising user-facing changes.
122
+ 3. Tags the commit and publishes to npm.
123
+
124
+ You do not need to bump versions or edit `CHANGELOG.md` in your PR — that happens at release time.
125
+
126
+ ## Questions?
127
+
128
+ Open a discussion or issue if anything in this guide is unclear. We would rather answer a question than have a contribution blocked by a missing detail.
package/DISCLAIMER.md ADDED
@@ -0,0 +1,62 @@
1
+ # Disclaimer
2
+
3
+ > **tl;dr** — This extension asks third-party AI models for coding advice. That advice can be wrong, it costs real money, and your prompts leave your machine. Read the full disclaimer below before using `/council` or `/opinion`.
4
+
5
+ ## What this extension does
6
+
7
+ `pi-model-council` is a Pi extension that asks one or more AI models for a second opinion on coding decisions. It does not write code itself — it returns suggestions that you (or your primary Pi agent) review and apply.
8
+
9
+ ## AI-generated advice is not guaranteed to be correct
10
+
11
+ The output of `/council` and `/opinion` is produced by large language models accessed through [OpenRouter](https://openrouter.ai). These models:
12
+
13
+ - **Can be confidently wrong.** Code suggestions may compile but be incorrect, insecure, outdated, or unfit for your use case.
14
+ - **Have knowledge cutoffs.** They may miss recent API changes, CVEs, or best practices.
15
+ - **Hallucinate.** They may invent function names, library APIs, or configuration flags that do not exist.
16
+
17
+ **Always review the suggestion before applying it.** Treat the output as one input among many — not as an instruction.
18
+
19
+ ## You are responsible for what you apply
20
+
21
+ This extension does not gate, lint, or sanity-check model output. You are solely responsible for:
22
+
23
+ - Verifying that the suggested code or approach is correct for your project.
24
+ - Running your existing test suite, type checker, and linter after applying any change.
25
+ - Reviewing for security regressions, data loss, or breaking changes.
26
+ - Complying with the licenses of any third-party code the models may have reproduced.
27
+
28
+ ## It costs real money
29
+
30
+ Every invocation calls the OpenRouter API, which charges per token. The full `/council` command makes **at least four** model calls (three council members plus one synthesis call), and may retry on failure. Pricing varies by model.
31
+
32
+ - Check your OpenRouter credit balance before running large deliberations.
33
+ - Use cheaper models for routine decisions; reserve expensive models for high-stakes questions.
34
+ - Revoke your API key immediately if you suspect it has been leaked (see [SECURITY.md](SECURITY.md)).
35
+
36
+ The authors of this extension receive **no portion** of your API spend.
37
+
38
+ ## Your data leaves your machine
39
+
40
+ When you run `/council` or `/opinion`, the following data is sent to OpenRouter (and from there to the configured model providers):
41
+
42
+ - The prompt you typed.
43
+ - File contents that Pi attaches as context for the prompt.
44
+ - The system prompt used by this extension (which does **not** contain your API key).
45
+
46
+ **Do not include secrets, credentials, or production data in prompts.** Strip them from any attached files first. Review [OpenRouter's privacy policy](https://openrouter.ai/privacy) for how they handle your data.
47
+
48
+ ## Third-party terms apply
49
+
50
+ Your use of OpenRouter and the underlying model providers is governed by their respective terms of service, acceptable-use policies, and pricing. This extension is a thin client over their APIs and does not endorse or guarantee the behaviour of any specific model.
51
+
52
+ ## No warranty
53
+
54
+ This software is provided under the MIT License (see [LICENSE](LICENSE)), "as is", without warranty of any kind, express or implied. The authors are not liable for any claim, damages, or other liability arising from the use of this extension or the AI-generated content it returns.
55
+
56
+ ## Not professional advice
57
+
58
+ Nothing produced by this extension constitutes legal, financial, medical, or other professional advice. For questions in those domains, consult a qualified professional.
59
+
60
+ ---
61
+
62
+ If anything in this disclaimer is unclear, please open an issue or pull request to improve it.