@wrongstack/plugins 0.281.0 → 0.281.1
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/README.md +325 -34
- package/dist/auto-doc.js +22 -7
- package/dist/branch-guard.js +18 -4
- package/dist/changelog-writer.js +12 -1
- package/dist/checkpoint.js +25 -3
- package/dist/context-pins.js +11 -2
- package/dist/git-autocommit.js +5 -3
- package/dist/index.js +232 -67
- package/dist/notify-hub.js +26 -1
- package/dist/semver-bump.js +43 -9
- package/dist/spec-linker.js +24 -3
- package/dist/template-engine.js +44 -29
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @wrongstack/plugins
|
|
2
2
|
|
|
3
3
|
First-party plugin collection for [WrongStack](https://github.com/WrongStack/WrongStack).
|
|
4
|
-
|
|
5
|
-
automatically for every `wstack` session
|
|
4
|
+
Thirty-six focused, single-purpose plugins ship in this package. Core safety
|
|
5
|
+
plugins load automatically for every `wstack` session; provider-wire plugins
|
|
6
|
+
are opt-in because they can change model-call semantics.
|
|
6
7
|
|
|
7
8
|
## What this is
|
|
8
9
|
|
|
@@ -23,25 +24,40 @@ under the `BUILTIN_PLUGIN_FACTORIES` array. To opt out, add
|
|
|
23
24
|
|---|---|---|---|---|
|
|
24
25
|
| 1 | [`auto-doc`](./src/auto-doc) | `auto_doc` | — | JSDoc/TSDoc generation with `dry_run` preview |
|
|
25
26
|
| 2 | [`git-autocommit`](./src/git-autocommit) | `git_autocommit` | — | AI-written conventional commits; warns on simultaneous worktrees |
|
|
26
|
-
| 3 | [`shell-check`](./src/shell-check) | `shellcheck` | — | Runs `shellcheck` on files
|
|
27
|
-
| 4 | [`cost-tracker`](./src/cost-tracker) | `cost_summary`, `cost_reset`, `cost_export` | — |
|
|
28
|
-
| 5 | [`file-watcher`](./src/file-watcher) | `watch_start`, `watch_stop`, `watch_list` | — |
|
|
29
|
-
| 6 | [`cron`](./src/cron) | `cron_schedule`, `cron_list`, `cron_cancel` | — |
|
|
30
|
-
| 7 | [`template-engine`](./src/template-engine) | `template_expand`, `template_render`, `template_create`, `template_list` | — |
|
|
31
|
-
| 8 | [`semver-bump`](./src/semver-bump) | `semver_bump`, `semver_current`, `semver_changelog` | — |
|
|
32
|
-
| 9 | [`secret-scanner`](./src/secret-scanner) | `secret_scanner_status`, `secret_scanner_test` | `PreToolUse`
|
|
33
|
-
| 10 | [`todo-tracker`](./src/todo-tracker) | `
|
|
34
|
-
| 11 | [`token-budget`](./src/token-budget) | `token_budget_status` | `Stop`
|
|
35
|
-
| 12 | [`lint-gate`](./src/lint-gate) | `lint_gate_status` | `PreToolUse`
|
|
36
|
-
| 13 | [`branch-guard`](./src/branch-guard) | `branch_guard_status` | `PreToolUse`
|
|
37
|
-
| 14 | [`diff-summary`](./src/diff-summary) | `diff_summary_status` | `PostToolUse`
|
|
38
|
-
| 15 | [`commit-validator`](./src/commit-validator) | `commit_validator_status` | `PreToolUse`
|
|
39
|
-
| 16 | [`format-on-save`](./src/format-on-save) | `format_on_save_status` | `PostToolUse`
|
|
40
|
-
| 17 | [`test-runner-gate`](./src/test-runner-gate) | `test_gate_status` | `PostToolUse`
|
|
41
|
-
| 18 | [`import-organizer`](./src/import-organizer) | `import_organizer_status` | `PostToolUse`
|
|
42
|
-
| 19 | [`todo-listener`](./src/todo-listener) | `todo_listener_status` | `PostToolUse`
|
|
43
|
-
| 20 | [`session-recap`](./src/session-recap) | `session_recap_status` | `Stop` | Posts a
|
|
44
|
-
| 21 | [`spec-linker`](./src/spec-linker) | `spec_linker_status` | `
|
|
27
|
+
| 3 | [`shell-check`](./src/shell-check) | `shellcheck` | — | Runs `shellcheck` on files or directories |
|
|
28
|
+
| 4 | [`cost-tracker`](./src/cost-tracker) | `cost_summary`, `cost_reset`, `cost_export` | — | Tracks per-model token usage and estimated USD cost |
|
|
29
|
+
| 5 | [`file-watcher`](./src/file-watcher) | `watch_start`, `watch_stop`, `watch_list` | — | Watches project files and emits debounced change events |
|
|
30
|
+
| 6 | [`cron`](./src/cron) | `cron_schedule`, `cron_list`, `cron_cancel` | — | Schedules recurring in-session actions |
|
|
31
|
+
| 7 | [`template-engine`](./src/template-engine) | `template_expand`, `template_render`, `template_create`, `template_list` | — | Expands templates with variables, conditionals, and loops |
|
|
32
|
+
| 8 | [`semver-bump`](./src/semver-bump) | `semver_bump`, `semver_current`, `semver_changelog` | — | Infers version bumps and changelogs from conventional commits |
|
|
33
|
+
| 9 | [`secret-scanner`](./src/secret-scanner) | `secret_scanner_status`, `secret_scanner_test` | `PreToolUse` + `PostToolUse` | Blocks/redacts input secrets and warns on output leaks |
|
|
34
|
+
| 10 | [`todo-tracker`](./src/todo-tracker) | `todo_tracker_*` | — | Persistent project-scoped backlog that survives sessions |
|
|
35
|
+
| 11 | [`token-budget`](./src/token-budget) | `token_budget_status` | `Stop` + `PostToolUse` | Tracks token use and can warn/stop at configured limits |
|
|
36
|
+
| 12 | [`lint-gate`](./src/lint-gate) | `lint_gate_status` | `PreToolUse` | Lints would-be write/edit content before mutation |
|
|
37
|
+
| 13 | [`branch-guard`](./src/branch-guard) | `branch_guard_status` | `PreToolUse` | Blocks or warns on commits, pushes, and merges on protected branches |
|
|
38
|
+
| 14 | [`diff-summary`](./src/diff-summary) | `diff_summary_status` | `PostToolUse` | Injects compact git diff context after write/edit |
|
|
39
|
+
| 15 | [`commit-validator`](./src/commit-validator) | `commit_validator_status` | `PreToolUse` | Validates conventional-commit messages |
|
|
40
|
+
| 16 | [`format-on-save`](./src/format-on-save) | `format_on_save_status` | `PostToolUse` | Runs formatter after write/edit |
|
|
41
|
+
| 17 | [`test-runner-gate`](./src/test-runner-gate) | `test_gate_status` | `PostToolUse` | Runs relevant tests after source edits |
|
|
42
|
+
| 18 | [`import-organizer`](./src/import-organizer) | `import_organizer_status` | `PostToolUse` | Organizes imports and applies safe fixes after write/edit |
|
|
43
|
+
| 19 | [`todo-listener`](./src/todo-listener) | `todo_listener_status` | `PostToolUse` | Broadcasts todo status changes to the project mailbox |
|
|
44
|
+
| 20 | [`session-recap`](./src/session-recap) | `session_recap_status` | `Stop` | Posts a compact session recap to the project mailbox |
|
|
45
|
+
| 21 | [`spec-linker`](./src/spec-linker) | `spec_linker_status` | `PreToolUse` + `PostToolUse` | Detects unlinked plugin references in markdown docs |
|
|
46
|
+
| 22 | [`loop-breaker`](./src/loop-breaker) | `loop_breaker_status` | `PreToolUse` | Detects repeated tool-call loops and oscillations |
|
|
47
|
+
| 23 | [`path-guard`](./src/path-guard) | `path_guard_status` | `PreToolUse` | Guards protected paths from writes and destructive shell commands |
|
|
48
|
+
| 24 | [`context-pins`](./src/context-pins) | `pin_add`, `pin_remove`, `pin_list` | System prompt contributor | Persists short pinned facts across compaction/session boundaries |
|
|
49
|
+
| 25 | [`checkpoint`](./src/checkpoint) | `checkpoint_create`, `checkpoint_list`, `checkpoint_restore` | `PreToolUse` | Captures file snapshots before risky edits and restores them on demand |
|
|
50
|
+
| 26 | [`error-lens`](./src/error-lens) | `error_lens_history` | `PostToolUse` | Summarizes failed tool output and repeated failure patterns |
|
|
51
|
+
| 27 | [`dep-guard`](./src/dep-guard) | `dep_guard_status` | `PreToolUse` | Supervises dependency installs for deny-list and supply-chain warnings |
|
|
52
|
+
| 28 | [`config-validator`](./src/config-validator) | `config_validator_status` | `PostToolUse` | Validates edited JSON/JSONC/YAML/TOML config files |
|
|
53
|
+
| 29 | [`notify-hub`](./src/notify-hub) | `notify_send`, `notify_hub_status` | `Stop` + event subscriptions | Sends session/tool notifications to a configured webhook |
|
|
54
|
+
| 30 | [`changelog-writer`](./src/changelog-writer) | `changelog_add`, `changelog_preview`, `changelog_write` | — | Collects and writes Keep-a-Changelog entries |
|
|
55
|
+
| 31 | [`injection-shield`](./src/injection-shield) | `injection_shield_status` | `PostToolUse` | Warns when tool output contains prompt-injection patterns |
|
|
56
|
+
| 32 | [`llm-cache`](./src/llm-cache) | `llm_cache_status`, `llm_cache_clear` | Provider runner wrapper | Opt-in cache for deterministic provider requests |
|
|
57
|
+
| 33 | [`model-router`](./src/model-router) | `model_router_status` | Provider runner wrapper | Opt-in routing of provider calls by declarative rules |
|
|
58
|
+
| 34 | [`prompt-firewall`](./src/prompt-firewall) | `prompt_firewall_status` | Provider runner wrapper | Opt-in provider-wire prompt/secret scanning and redaction/blocking |
|
|
59
|
+
| 35 | [`auto-escalate`](./src/auto-escalate) | `auto_escalate_status` | `onError` extension | Opt-in retry/escalation ladder for transient provider errors |
|
|
60
|
+
| 36 | [`token-throttle`](./src/token-throttle) | `token_throttle_status` | Provider runner wrapper | Opt-in rolling-window token throttling for provider calls |
|
|
45
61
|
|
|
46
62
|
### Removed plugins (use built-in tools instead)
|
|
47
63
|
|
|
@@ -101,7 +117,7 @@ with a bundled `PRICING` table as the baseline (Layer 3) and a
|
|
|
101
117
|
`pricingOverrides` config field as the top-priority escape hatch
|
|
102
118
|
(Layer 1). The `pricingOverrides` field is the user-facing tool for
|
|
103
119
|
correcting a specific model's price without waiting for a plugin
|
|
104
|
-
release. See [cost-tracker
|
|
120
|
+
release. See the [`cost-tracker`](./src/cost-tracker) source for
|
|
105
121
|
the full lookup chain.
|
|
106
122
|
|
|
107
123
|
```jsonc
|
|
@@ -174,7 +190,7 @@ has already run, the hook cannot block — instead it injects
|
|
|
174
190
|
`additionalContext` so the LLM knows not to echo, store, or commit
|
|
175
191
|
the leaked value.
|
|
176
192
|
|
|
177
|
-
Three modes (`config.extensions['secret-scanner'].mode`):
|
|
193
|
+
Three modes (`config.extensions['secret-scanner'].mode`) for [`secret-scanner`](./src/secret-scanner):
|
|
178
194
|
- **`block` (default)**: returns `HookOutcome{ decision: 'block', reason }`
|
|
179
195
|
- **`redact`**: returns `HookOutcome{ decision: 'allow', modifiedInput, additionalContext }` with the offending strings replaced by `[REDACTED:type]`
|
|
180
196
|
- **`allow`**: only logs; never blocks
|
|
@@ -246,7 +262,7 @@ mutates `ctx.todos`). The plugin never touches `ctx.todos` directly
|
|
|
246
262
|
|
|
247
263
|
**Storage**: per-project JSON at the path provided by
|
|
248
264
|
`paths.projectDir` (via the host's wiring) or via the explicit
|
|
249
|
-
`
|
|
265
|
+
[`todo-tracker`](./src/todo-tracker) `filePath` config field.
|
|
250
266
|
|
|
251
267
|
```jsonc
|
|
252
268
|
// Explicit override (use when the host doesn't supply paths.projectDir)
|
|
@@ -451,9 +467,10 @@ the file was reformatted.
|
|
|
451
467
|
```
|
|
452
468
|
|
|
453
469
|
Biome detection runs once at setup(). If biome is not installed, the
|
|
454
|
-
hook is a silent no-op. Works alongside lint-gate
|
|
455
|
-
the write) and diff-summary
|
|
456
|
-
|
|
470
|
+
hook is a silent no-op. Works alongside [`lint-gate`](./src/lint-gate)
|
|
471
|
+
(which lints BEFORE the write) and [`diff-summary`](./src/diff-summary)
|
|
472
|
+
(which shows the diff AFTER) — together they form a complete write
|
|
473
|
+
pipeline: lint → write → format → diff.
|
|
457
474
|
|
|
458
475
|
### 17. `test-runner-gate` — automatic test execution
|
|
459
476
|
|
|
@@ -655,7 +672,7 @@ context. Increase `includeTranscriptTail` for more history.
|
|
|
655
672
|
Two hooks working together:
|
|
656
673
|
|
|
657
674
|
**PostToolUse** (always active when the plugin is enabled):
|
|
658
|
-
scans markdown files for *unlinked* references to one of the
|
|
675
|
+
scans markdown files for *unlinked* references to one of the 36
|
|
659
676
|
known plugins and surfaces them to the LLM via
|
|
660
677
|
`additionalContext`. The plugin does NOT modify the file — it
|
|
661
678
|
only injects a low-noise context block listing the unlinked
|
|
@@ -679,7 +696,7 @@ the PostToolUse context tells the LLM what to fix.
|
|
|
679
696
|
|
|
680
697
|
**Detection rules** (both hooks):
|
|
681
698
|
- Source matches `config.fileGlobs` (default: `**/*.md`, `**/*.mdx`)
|
|
682
|
-
- The reference matches one of the
|
|
699
|
+
- The reference matches one of the 36 known plugin names
|
|
683
700
|
(case-insensitive)
|
|
684
701
|
- It is NOT already wrapped in a markdown link `[name](...)` or
|
|
685
702
|
inline code `` `name` ``
|
|
@@ -713,6 +730,281 @@ LLM (or the user) decide whether to fix it, instead of
|
|
|
713
730
|
silently rewriting the file on every save. Opt into `autoFix`
|
|
714
731
|
once you're confident in the rewrite.
|
|
715
732
|
|
|
733
|
+
### 22 — `loop-breaker`
|
|
734
|
+
|
|
735
|
+
**Tools**: `loop_breaker_status`
|
|
736
|
+
**Hooks**: `PreToolUse`
|
|
737
|
+
|
|
738
|
+
Detects repeated tool calls before they execute. It tracks exact-repeat
|
|
739
|
+
streaks and A-B-A-B oscillations, then either blocks the call or injects a
|
|
740
|
+
warning depending on `mode`.
|
|
741
|
+
|
|
742
|
+
```jsonc
|
|
743
|
+
{
|
|
744
|
+
"extensions": {
|
|
745
|
+
"loop-breaker": {
|
|
746
|
+
"enabled": true,
|
|
747
|
+
"mode": "block", // "block" | "warn"
|
|
748
|
+
"repeatThreshold": 3,
|
|
749
|
+
"oscillationThreshold": 2
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### 23 — `path-guard`
|
|
756
|
+
|
|
757
|
+
**Tools**: `path_guard_status`
|
|
758
|
+
**Hooks**: `PreToolUse`
|
|
759
|
+
|
|
760
|
+
Protects sensitive paths from accidental writes/edits and destructive shell
|
|
761
|
+
commands. Defaults cover lockfiles, `.env`, `.git`, and migration-like paths;
|
|
762
|
+
use `mode: "warn"` for advisory-only enforcement.
|
|
763
|
+
|
|
764
|
+
```jsonc
|
|
765
|
+
{
|
|
766
|
+
"extensions": {
|
|
767
|
+
"path-guard": {
|
|
768
|
+
"enabled": true,
|
|
769
|
+
"mode": "block",
|
|
770
|
+
"protected": [".env*", ".git/**", "**/migrations/**"]
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### 24 — `context-pins`
|
|
777
|
+
|
|
778
|
+
**Tools**: `pin_add`, `pin_remove`, `pin_list`
|
|
779
|
+
**Hooks**: system prompt contributor
|
|
780
|
+
|
|
781
|
+
Stores short durable facts and injects them into the system prompt so they
|
|
782
|
+
survive context compaction. When the host supplies a project directory, pins
|
|
783
|
+
persist to a per-project JSON file; otherwise the plugin falls back to
|
|
784
|
+
in-memory state.
|
|
785
|
+
|
|
786
|
+
```jsonc
|
|
787
|
+
pin_add({ label: "api", text: "Use the v2 billing endpoint for invoices." })
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### 25 — `checkpoint`
|
|
791
|
+
|
|
792
|
+
**Tools**: `checkpoint_create`, `checkpoint_list`, `checkpoint_restore`
|
|
793
|
+
**Hooks**: `PreToolUse`
|
|
794
|
+
|
|
795
|
+
Captures file snapshots before risky write/edit operations and lets the agent
|
|
796
|
+
restore a snapshot later. Restore never deletes files that did not exist at
|
|
797
|
+
capture time; it reports them instead.
|
|
798
|
+
|
|
799
|
+
```jsonc
|
|
800
|
+
checkpoint_create({ paths: ["src/service.ts"], label: "before refactor" })
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### 26 — `error-lens`
|
|
804
|
+
|
|
805
|
+
**Tools**: `error_lens_history`
|
|
806
|
+
**Hooks**: `PostToolUse`
|
|
807
|
+
|
|
808
|
+
Distills failed command/tool output into compact diagnostics: likely error
|
|
809
|
+
line, source frames, repeat count, and a capped history for follow-up
|
|
810
|
+
inspection.
|
|
811
|
+
|
|
812
|
+
```jsonc
|
|
813
|
+
{
|
|
814
|
+
"extensions": {
|
|
815
|
+
"error-lens": { "enabled": true, "maxFrames": 5, "historyLimit": 10 }
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### 27 — `dep-guard`
|
|
821
|
+
|
|
822
|
+
**Tools**: `dep_guard_status`
|
|
823
|
+
**Hooks**: `PreToolUse`
|
|
824
|
+
|
|
825
|
+
Supervises dependency installs before they run. It can block deny-listed
|
|
826
|
+
packages, warn on typosquat lookalikes, and flag unpinned versions or risky
|
|
827
|
+
install sources.
|
|
828
|
+
|
|
829
|
+
```jsonc
|
|
830
|
+
{
|
|
831
|
+
"extensions": {
|
|
832
|
+
"dep-guard": {
|
|
833
|
+
"enabled": true,
|
|
834
|
+
"mode": "block",
|
|
835
|
+
"deny": ["event-stream"],
|
|
836
|
+
"warnUnpinned": true
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### 28 — `config-validator`
|
|
843
|
+
|
|
844
|
+
**Tools**: `config_validator_status`
|
|
845
|
+
**Hooks**: `PostToolUse`
|
|
846
|
+
|
|
847
|
+
Validates edited config files immediately after write/edit. Supported formats
|
|
848
|
+
include JSON, JSONC, YAML, and TOML-like files depending on extension.
|
|
849
|
+
|
|
850
|
+
```jsonc
|
|
851
|
+
{
|
|
852
|
+
"extensions": {
|
|
853
|
+
"config-validator": {
|
|
854
|
+
"enabled": true,
|
|
855
|
+
"extensions": [".json", ".jsonc", ".yaml", ".yml", ".toml"]
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### 29 — `notify-hub`
|
|
862
|
+
|
|
863
|
+
**Tools**: `notify_send`, `notify_hub_status`
|
|
864
|
+
**Hooks**: `Stop` + event subscriptions
|
|
865
|
+
|
|
866
|
+
Sends compact JSON notifications to a configured webhook for session stops,
|
|
867
|
+
tool errors, budget threshold events, and manual `notify_send` calls. Empty
|
|
868
|
+
`webhookUrl` means the plugin idles.
|
|
869
|
+
|
|
870
|
+
```jsonc
|
|
871
|
+
{
|
|
872
|
+
"extensions": {
|
|
873
|
+
"notify-hub": {
|
|
874
|
+
"webhookUrl": "https://hooks.example.test/wrongstack",
|
|
875
|
+
"events": ["session.stop", "tool.error"],
|
|
876
|
+
"timeoutMs": 5000
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### 30 — `changelog-writer`
|
|
883
|
+
|
|
884
|
+
**Tools**: `changelog_add`, `changelog_preview`, `changelog_write`
|
|
885
|
+
|
|
886
|
+
Collects pending Keep-a-Changelog entries during a session and writes them
|
|
887
|
+
under `## [Unreleased]`. It can also collect conventional-commit subjects and
|
|
888
|
+
map them to changelog sections.
|
|
889
|
+
|
|
890
|
+
```jsonc
|
|
891
|
+
changelog_add({ section: "Fixed", text: "Prevent template path traversal." })
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### 31 — `injection-shield`
|
|
895
|
+
|
|
896
|
+
**Tools**: `injection_shield_status`
|
|
897
|
+
**Hooks**: `PostToolUse`
|
|
898
|
+
|
|
899
|
+
Scans tool output for prompt-injection patterns such as attempts to override
|
|
900
|
+
instructions, exfiltrate secrets, or treat retrieved content as higher-priority
|
|
901
|
+
commands. It warns via additional context; it does not modify tool output.
|
|
902
|
+
|
|
903
|
+
```jsonc
|
|
904
|
+
{
|
|
905
|
+
"extensions": {
|
|
906
|
+
"injection-shield": { "enabled": true, "tools": "*", "maxFindings": 5 }
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### 32 — `llm-cache`
|
|
912
|
+
|
|
913
|
+
**Tools**: `llm_cache_status`, `llm_cache_clear`
|
|
914
|
+
**Hooks**: provider runner wrapper (opt-in)
|
|
915
|
+
|
|
916
|
+
Caches identical deterministic provider requests and short-circuits the
|
|
917
|
+
provider call on a hit. It is disabled by default because caching changes
|
|
918
|
+
provider-call semantics; sampled/high-temperature calls are intentionally not
|
|
919
|
+
cached.
|
|
920
|
+
|
|
921
|
+
```jsonc
|
|
922
|
+
{
|
|
923
|
+
"extensions": {
|
|
924
|
+
"llm-cache": { "enabled": true, "maxEntries": 256, "ttlMs": 300000 }
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### 33 — `model-router`
|
|
930
|
+
|
|
931
|
+
**Tools**: `model_router_status`
|
|
932
|
+
**Hooks**: provider runner wrapper (opt-in)
|
|
933
|
+
|
|
934
|
+
Routes provider calls to alternate models based on declarative rules such as
|
|
935
|
+
input size, tool usage, or dry-run mode. Disabled by default because it changes
|
|
936
|
+
which model serves a turn.
|
|
937
|
+
|
|
938
|
+
```jsonc
|
|
939
|
+
{
|
|
940
|
+
"extensions": {
|
|
941
|
+
"model-router": {
|
|
942
|
+
"enabled": true,
|
|
943
|
+
"dryRun": false,
|
|
944
|
+
"rules": []
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
### 34 — `prompt-firewall`
|
|
951
|
+
|
|
952
|
+
**Tools**: `prompt_firewall_status`
|
|
953
|
+
**Hooks**: provider runner wrapper (opt-in)
|
|
954
|
+
|
|
955
|
+
Scans provider requests and, optionally, responses for credential leaks and
|
|
956
|
+
prompt-risk patterns. Modes are `warn`, `redact`, and `block`; the plugin is
|
|
957
|
+
disabled by default because redact/block can alter or stop provider calls.
|
|
958
|
+
|
|
959
|
+
```jsonc
|
|
960
|
+
{
|
|
961
|
+
"extensions": {
|
|
962
|
+
"prompt-firewall": { "enabled": true, "mode": "redact", "scanResponse": true }
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### 35 — `auto-escalate`
|
|
968
|
+
|
|
969
|
+
**Tools**: `auto_escalate_status`
|
|
970
|
+
**Hooks**: `onError` extension (opt-in)
|
|
971
|
+
|
|
972
|
+
Retries transient provider failures with the next model in a configured
|
|
973
|
+
escalation ladder. Disabled by default because it changes error-recovery
|
|
974
|
+
behavior and can increase cost.
|
|
975
|
+
|
|
976
|
+
```jsonc
|
|
977
|
+
{
|
|
978
|
+
"extensions": {
|
|
979
|
+
"auto-escalate": {
|
|
980
|
+
"enabled": true,
|
|
981
|
+
"escalation": ["gpt-5-mini", "gpt-5.5"]
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### 36 — `token-throttle`
|
|
988
|
+
|
|
989
|
+
**Tools**: `token_throttle_status`
|
|
990
|
+
**Hooks**: provider runner wrapper (opt-in)
|
|
991
|
+
|
|
992
|
+
Applies a rolling-window token/minute budget to provider calls. When the next
|
|
993
|
+
request would exceed the configured rate, the wrapper delays the call instead
|
|
994
|
+
of failing it.
|
|
995
|
+
|
|
996
|
+
```jsonc
|
|
997
|
+
{
|
|
998
|
+
"extensions": {
|
|
999
|
+
"token-throttle": {
|
|
1000
|
+
"enabled": true,
|
|
1001
|
+
"tokensPerMinute": 120000,
|
|
1002
|
+
"maxDelayMs": 30000
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
```
|
|
1007
|
+
|
|
716
1008
|
## Configuration patterns
|
|
717
1009
|
|
|
718
1010
|
There are two surfaces for plugin configuration:
|
|
@@ -747,11 +1039,10 @@ To disable a single built-in without removing its config:
|
|
|
747
1039
|
|
|
748
1040
|
## H1 audit pattern
|
|
749
1041
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
without leaking resources. The pattern was formalized after a
|
|
1042
|
+
All built-in plugins that hold module-scope state follow a strict lifecycle
|
|
1043
|
+
to survive hot-reload without leaking resources. This includes file-backed
|
|
1044
|
+
stores, timers, watchers, hook counters, in-memory caches, provider wrappers,
|
|
1045
|
+
and notification state. The pattern was formalized after a
|
|
755
1046
|
2026-06-03 audit (the "H1 audit") found that several plugins kept
|
|
756
1047
|
their state inside the `setup()` closure, where the loader's
|
|
757
1048
|
`WeakMap<Plugin, PluginAPI>` could not reach it during teardown —
|
package/dist/auto-doc.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { resolve, isAbsolute, relative } from 'path';
|
|
2
|
+
|
|
1
3
|
// src/auto-doc/index.ts
|
|
2
4
|
var AUTO_DOC_API_VERSION = "^0.1.10";
|
|
5
|
+
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
6
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
7
|
+
const root = resolve(cwd);
|
|
8
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
9
|
+
const rel = relative(root, resolved);
|
|
10
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
3
13
|
var state = {
|
|
4
14
|
invocationCount: 0,
|
|
5
15
|
/** Doc comments whose prose came from the LLM this session. */
|
|
@@ -170,14 +180,19 @@ async function runAutoDoc(input, api) {
|
|
|
170
180
|
const maxLlmEntities = typeof extConfig["maxLlmEntities"] === "number" && extConfig["maxLlmEntities"] >= 0 ? extConfig["maxLlmEntities"] : 25;
|
|
171
181
|
const results = [];
|
|
172
182
|
let llmBudget = maxLlmEntities;
|
|
173
|
-
for (const
|
|
183
|
+
for (const rawFile of input.files) {
|
|
184
|
+
const safeFile = resolveProjectPath(rawFile);
|
|
185
|
+
if (!safeFile) {
|
|
186
|
+
api.log.warn(`auto-doc: skipped file outside project directory: ${rawFile}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
174
189
|
try {
|
|
175
190
|
const { readFileSync, writeFileSync } = await import('fs');
|
|
176
191
|
let content;
|
|
177
192
|
try {
|
|
178
|
-
content = readFileSync(
|
|
193
|
+
content = readFileSync(safeFile, "utf-8");
|
|
179
194
|
} catch {
|
|
180
|
-
api.log.warn(`auto-doc: could not read file ${
|
|
195
|
+
api.log.warn(`auto-doc: could not read file ${safeFile}`);
|
|
181
196
|
continue;
|
|
182
197
|
}
|
|
183
198
|
const entities = parseSource(content);
|
|
@@ -205,14 +220,14 @@ async function runAutoDoc(input, api) {
|
|
|
205
220
|
}
|
|
206
221
|
if (!doc) doc = generateDocComment(entity, includeTypes);
|
|
207
222
|
modified = injectDocComment(modified, entity, doc);
|
|
208
|
-
results.push({ file, entity: entity.name, source });
|
|
223
|
+
results.push({ file: safeFile, entity: entity.name, source });
|
|
209
224
|
}
|
|
210
225
|
if (!input.dry_run && results.length > 0) {
|
|
211
|
-
writeFileSync(
|
|
212
|
-
api.log.info(`auto-doc: updated ${
|
|
226
|
+
writeFileSync(safeFile, modified, "utf-8");
|
|
227
|
+
api.log.info(`auto-doc: updated ${safeFile}`);
|
|
213
228
|
}
|
|
214
229
|
} catch (err) {
|
|
215
|
-
api.log.error(`auto-doc: error processing ${
|
|
230
|
+
api.log.error(`auto-doc: error processing ${safeFile}: ${err}`);
|
|
216
231
|
}
|
|
217
232
|
}
|
|
218
233
|
return {
|
package/dist/branch-guard.js
CHANGED
|
@@ -95,9 +95,21 @@ var plugin = {
|
|
|
95
95
|
default: "block",
|
|
96
96
|
description: '"block" refuses the call; "warn" injects context but lets it through.'
|
|
97
97
|
},
|
|
98
|
-
blockCommit: {
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
blockCommit: {
|
|
99
|
+
type: "boolean",
|
|
100
|
+
default: true,
|
|
101
|
+
description: "Block commits on protected branches."
|
|
102
|
+
},
|
|
103
|
+
blockPush: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
default: true,
|
|
106
|
+
description: "Block pushes from protected branches."
|
|
107
|
+
},
|
|
108
|
+
blockMerge: {
|
|
109
|
+
type: "boolean",
|
|
110
|
+
default: true,
|
|
111
|
+
description: "Block merges into protected branches."
|
|
112
|
+
}
|
|
101
113
|
}
|
|
102
114
|
},
|
|
103
115
|
setup(api) {
|
|
@@ -115,6 +127,7 @@ var plugin = {
|
|
|
115
127
|
state.invocationCount += 1;
|
|
116
128
|
let gitOp = null;
|
|
117
129
|
if (toolName === "git_autocommit") {
|
|
130
|
+
if (inp["dry_run"] === true) return;
|
|
118
131
|
gitOp = { type: "commit", snippet: "git_autocommit" };
|
|
119
132
|
} else if (toolName === "bash") {
|
|
120
133
|
const command = inp["command"];
|
|
@@ -129,6 +142,7 @@ var plugin = {
|
|
|
129
142
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
130
143
|
const opVerb = gitOp.type === "commit" ? "committing to" : gitOp.type === "push" ? "pushing from" : "merging into";
|
|
131
144
|
const hasUncommitted = detectUncommittedChanges(cwd);
|
|
145
|
+
const retryStep = toolName === "git_autocommit" ? "retry git_autocommit" : `git ${gitOp.type} ...`;
|
|
132
146
|
const suggestionParts = [];
|
|
133
147
|
if (hasUncommitted) {
|
|
134
148
|
suggestionParts.push("git stash");
|
|
@@ -137,7 +151,7 @@ var plugin = {
|
|
|
137
151
|
if (hasUncommitted) {
|
|
138
152
|
suggestionParts.push("git stash pop");
|
|
139
153
|
}
|
|
140
|
-
suggestionParts.push(
|
|
154
|
+
suggestionParts.push(retryStep);
|
|
141
155
|
const suggestion = suggestionParts.join(" \u2192 ");
|
|
142
156
|
const reason = `branch-guard: refused to ${gitOp.type} on protected branch '${branch}'. You're on a protected branch. Use a feature branch instead.
|
|
143
157
|
` + (hasUncommitted ? `You have uncommitted changes. Safe workflow:
|
package/dist/changelog-writer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { resolve, isAbsolute, relative } from 'path';
|
|
2
3
|
|
|
3
4
|
// src/changelog-writer/index.ts
|
|
4
5
|
var state = {
|
|
@@ -16,12 +17,19 @@ var DEFAULTS = {
|
|
|
16
17
|
collectCommits: true,
|
|
17
18
|
maxEntries: 200
|
|
18
19
|
};
|
|
20
|
+
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
21
|
+
const root = resolve(cwd);
|
|
22
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
23
|
+
const rel = relative(root, resolved);
|
|
24
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
19
27
|
function readConfig(raw) {
|
|
20
28
|
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
21
29
|
const r = raw;
|
|
22
30
|
return {
|
|
23
31
|
enabled: r["enabled"] !== false,
|
|
24
|
-
filePath: typeof r["filePath"] === "string" && r["filePath"].length > 0 ? r["filePath"] : DEFAULTS.filePath,
|
|
32
|
+
filePath: typeof r["filePath"] === "string" && r["filePath"].length > 0 ? resolveProjectPath(r["filePath"]) ?? "" : resolveProjectPath(DEFAULTS.filePath) ?? DEFAULTS.filePath,
|
|
25
33
|
collectCommits: r["collectCommits"] !== false,
|
|
26
34
|
maxEntries: typeof r["maxEntries"] === "number" && r["maxEntries"] >= 10 ? r["maxEntries"] : DEFAULTS.maxEntries
|
|
27
35
|
};
|
|
@@ -291,6 +299,9 @@ var plugin = {
|
|
|
291
299
|
mutating: true,
|
|
292
300
|
async execute(input) {
|
|
293
301
|
if (!cfg.enabled) return { ok: false, error: "changelog-writer is disabled" };
|
|
302
|
+
if (!cfg.filePath) {
|
|
303
|
+
return { ok: false, error: "filePath must stay within the current project directory" };
|
|
304
|
+
}
|
|
294
305
|
if (state.entries.length === 0) {
|
|
295
306
|
return { ok: false, error: "no pending entries \u2014 add some with changelog_add first" };
|
|
296
307
|
}
|
package/dist/checkpoint.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync, statSync, readFileSync } from 'fs';
|
|
2
|
-
import { dirname } from 'path';
|
|
2
|
+
import { dirname, resolve, isAbsolute, relative } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/checkpoint/index.ts
|
|
5
5
|
var state = {
|
|
@@ -26,6 +26,13 @@ function readConfig(raw) {
|
|
|
26
26
|
maxFileBytes: typeof r["maxFileBytes"] === "number" && r["maxFileBytes"] >= 1024 ? r["maxFileBytes"] : DEFAULTS.maxFileBytes
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
30
|
+
const root = resolve(cwd);
|
|
31
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
32
|
+
const rel = relative(root, resolved);
|
|
33
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
29
36
|
function captureFile(path, maxBytes) {
|
|
30
37
|
try {
|
|
31
38
|
const st = statSync(path);
|
|
@@ -92,7 +99,9 @@ var plugin = {
|
|
|
92
99
|
const ti = input.toolInput ?? {};
|
|
93
100
|
const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"];
|
|
94
101
|
if (typeof raw !== "string" || raw.length === 0) return;
|
|
95
|
-
const
|
|
102
|
+
const safePath = resolveProjectPath(raw);
|
|
103
|
+
if (!safePath) return;
|
|
104
|
+
const captured = captureFile(safePath, cfg.maxFileBytes);
|
|
96
105
|
if (captured === "too-large") {
|
|
97
106
|
state.skippedLarge += 1;
|
|
98
107
|
return;
|
|
@@ -134,9 +143,15 @@ var plugin = {
|
|
|
134
143
|
const paths = Array.isArray(input.paths) ? input.paths.filter((p) => typeof p === "string" && p.length > 0) : [];
|
|
135
144
|
if (paths.length === 0) return { ok: false, error: "paths must not be empty" };
|
|
136
145
|
const files = [];
|
|
146
|
+
const rejectedOutsideProject = [];
|
|
137
147
|
let skipped = 0;
|
|
138
148
|
for (const p of paths) {
|
|
139
|
-
const
|
|
149
|
+
const safePath = resolveProjectPath(p);
|
|
150
|
+
if (!safePath) {
|
|
151
|
+
rejectedOutsideProject.push(p);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const captured = captureFile(safePath, cfg.maxFileBytes);
|
|
140
155
|
if (captured === "too-large") {
|
|
141
156
|
skipped += 1;
|
|
142
157
|
state.skippedLarge += 1;
|
|
@@ -144,6 +159,13 @@ var plugin = {
|
|
|
144
159
|
}
|
|
145
160
|
files.push(captured);
|
|
146
161
|
}
|
|
162
|
+
if (rejectedOutsideProject.length > 0) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
error: "paths must stay within the current project directory",
|
|
166
|
+
rejectedOutsideProject
|
|
167
|
+
};
|
|
168
|
+
}
|
|
147
169
|
if (files.length === 0) {
|
|
148
170
|
return { ok: false, error: "all files were skipped (too large)" };
|
|
149
171
|
}
|
package/dist/context-pins.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
-
import { dirname } from 'path';
|
|
2
|
+
import { dirname, resolve, isAbsolute, relative } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/context-pins/index.ts
|
|
5
5
|
var state = {
|
|
@@ -16,12 +16,21 @@ var DEFAULTS = {
|
|
|
16
16
|
maxPins: 20,
|
|
17
17
|
maxPinChars: 500
|
|
18
18
|
};
|
|
19
|
+
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
20
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) return "";
|
|
21
|
+
const root = resolve(cwd);
|
|
22
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
23
|
+
const rel = relative(root, resolved);
|
|
24
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
19
27
|
function readConfig(raw) {
|
|
20
28
|
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
21
29
|
const r = raw;
|
|
30
|
+
const rawPath = typeof r["filePath"] === "string" ? r["filePath"] : DEFAULTS.filePath;
|
|
22
31
|
return {
|
|
23
32
|
enabled: r["enabled"] !== false,
|
|
24
|
-
filePath:
|
|
33
|
+
filePath: rawPath ? resolveProjectPath(rawPath) ?? "" : "",
|
|
25
34
|
maxPins: typeof r["maxPins"] === "number" && r["maxPins"] >= 1 && r["maxPins"] <= 100 ? r["maxPins"] : DEFAULTS.maxPins,
|
|
26
35
|
maxPinChars: typeof r["maxPinChars"] === "number" && r["maxPinChars"] >= 20 ? r["maxPinChars"] : DEFAULTS.maxPinChars
|
|
27
36
|
};
|