Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d9c4ab8
feat(sessions): add BoundedSessionMap shared module
frap129 Aug 30, 2026
7609449
refactor(session-store): compose BoundedSessionMap for eviction
frap129 Aug 30, 2026
73f6077
refactor(rule-delivery): compose BoundedSessionMap for eviction
frap129 Aug 30, 2026
4651492
refactor(file-observation): compose BoundedSessionMap for eviction
frap129 Aug 30, 2026
5e42129
refactor(sessions): make BoundedSessionMap the sole value owner
frap129 Aug 30, 2026
c59df4b
refactor(session-store): store states in BoundedSessionMap directly
frap129 Aug 30, 2026
c83cc8a
refactor(rule-delivery): store delivery state in BoundedSessionMap di…
frap129 Aug 30, 2026
db29487
refactor(file-observation): store sessions in BoundedSessionMap directly
frap129 Aug 30, 2026
0dbb580
docs: align SessionState and eviction docs with sole-owner BoundedSes…
frap129 Aug 30, 2026
c10dd78
test(session-store): add missing name field to rule snapshot fixture
frap129 Aug 30, 2026
2729277
refactor(sessions): add minBound option for at-least-one bounds
frap129 Aug 30, 2026
0ae029c
refactor(delivery): use minBound instead of duplicated clamp idiom
frap129 Aug 30, 2026
c48a3b2
refactor(session-store): document why bound methods delegate to the map
frap129 Aug 30, 2026
cd972ed
refactor(sessions): delete the module header comment
frap129 Aug 30, 2026
84e4933
style: Remove dumb AI comments
frap129 Aug 30, 2026
eefba89
docs(rules): update runtime, doc-sync, and hotspot rules for module r…
frap129 Aug 30, 2026
02206c7
refactor(src): group modules into six domains with colocated tests
frap129 Aug 31, 2026
bd1ad0d
chore: V1
frap129 Sep 3, 2026
b04800d
ci(release): publish via npm trusted publishing (OIDC)
frap129 Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/release-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write
id-token: write

jobs:
release-beta:
Expand Down Expand Up @@ -45,10 +46,11 @@ jobs:
- name: Verify package contents
run: bun pm pack --dry-run

- name: Install npm with trusted publishing support
run: npm install -g npm@latest

- name: Publish to npm (beta)
run: bun publish --access public --tag beta
env:
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public --tag beta

- name: Create GitHub Release
uses: softprops/action-gh-release@26e8ad27a09a225049a7075d7ec1caa2df6ff332 # v2
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Expand All @@ -34,10 +35,11 @@ jobs:
- name: Verify package contents
run: bun pm pack --dry-run

- name: Install npm with trusted publishing support
run: npm install -g npm@latest

- name: Publish to npm
run: bun publish --access public
env:
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public

- name: Create GitHub Release
uses: softprops/action-gh-release@26e8ad27a09a225049a7075d7ec1caa2df6ff332 # v2
Expand Down
5 changes: 3 additions & 2 deletions .opencode/rules/10-runtime-consistency.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
---
globs:
- 'src/runtime.ts'
- 'src/utils.ts'
- 'src/message-context.ts'
- 'src/mcp-tools.ts'
- 'src/runtime-context.ts'
- 'src/runtime-chat.ts'
---

# Runtime Consistency

- Use shared message-context helpers for prompt and part extraction. Do not duplicate extraction loops in runtime hooks.
- Keep CI/env boolean detection on one parser path (`parseEnvBoolean` / `isTruthyEnvValue`) across all provider checks.
- Keep CI/env boolean detection on `parseEnvBoolean` in `src/runtime-context.ts`; do not inline truthiness checks per provider.
- Route all plugin console output through the gated helpers in `src/debug.ts`; use UI state or intentional thrown errors for user-visible behavior that must remain available when debug logging is disabled.
5 changes: 4 additions & 1 deletion .opencode/rules/11-readme-and-doc-sync.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
globs:
- 'README.md'
- 'AGENTS.md'
- 'CONTEXT.md'
- 'docs/**/*.md'
keywords:
- 'readme'
Expand All @@ -13,5 +15,6 @@ match: any
# README and Documentation Sync

- When adding, removing, or renaming production modules, update the README Project Structure section in the same change.
- Keep `AGENTS.md` and `CONTEXT.md` aligned with the module layout and runtime behavior they describe.
- Keep architecture docs aligned with current hook/runtime behavior and supported rule filters.
- Remove stale references to deprecated behavior as part of the same PR that changes behavior.
- Remove stale references to deprecated behavior (including dead directory listings like `openspec/`) as part of the same PR that changes behavior.
2 changes: 1 addition & 1 deletion .opencode/rules/12-hotspot-guardrails.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ globs:

# Hotspot Guardrails

- In `src/utils.ts`, do not add new unrelated responsibilities. Prefer splitting by domain (discovery, metadata, matching, message paths).
- Keep `src/utils.ts` a compatibility re-export facade; add new logic to domain modules instead. `src/api-surface.typecheck.ts` enforces intentional exports.
- In `src/runtime.ts`, extract shared helpers before adding additional inline transformation logic.
- In `src/index.test.ts`, prefer creating or expanding module-focused test files instead of growing the monolithic suite.
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- Use Aube and install with `aube install --frozen-lockfile`. Run checks in this order: `aube run lint` -> `aubx tsc --noEmit` -> `aube run test:run`.
- There is no `typecheck` script; typecheck with `aubx tsc --noEmit`.
- Run one colocated Vitest file with `aube run test:run src/<name>.test.ts` (or a path under `tui/`). `tsconfig.json` excludes test/spec files, so `tsc` does not typecheck them.
- Run one colocated Vitest file with `aube run test:run src/<domain>/<name>.test.ts` (or a path under `tui/`). `tsconfig.json` excludes test/spec files, so `tsc` does not typecheck them.
- `docs/silent-message-implementation.md` describes a superseded design; current delivery is synthetic parts via `chat.message`.

## Architecture
Expand All @@ -21,7 +21,7 @@
- The `"./tui"` package export must point to `./dist/tui/index.js`, not raw `./tui/index.tsx`: OpenCode/Bun does not reliably remap `.js` relative imports when loading raw TSX, while those targets exist only after the TypeScript build.
- OpenCode caches npm plugin specs by their literal specifier; an existing `~/.cache/opencode/packages/opencode-rules@latest` wrapper pins the version resolved when it was created and does not refresh when `latest` changes. Clear that cache or use an explicit new version when validating a release.
- tsconfig is strict-plus (`exactOptionalPropertyTypes`, `noUnusedLocals`/`noUnusedParameters`, `verbatimModuleSyntax`), so type-only imports and unused symbols will fail typecheck even though lint passes.
- `src/utils.ts` is the compatibility re-export facade; add logic to domain modules instead. `src/api-surface.typecheck.ts` enforces intentionally private exports during `tsc`.
- Server source is grouped by domain: `src/rules/` (discovery, metadata, filter, hooks), `src/delivery/` (delivery engine composed behind `createRuleDelivery` plus codec and history port), `src/session/` (session/matched-rule state, file observations, message extraction), `src/runtime/` (orchestrator, client adapter, tool-hook flow, match context, chat capture), `src/detection/` (git-branch, project-fingerprint, mcp-tools), `src/shared/` (debug, bounded-session-map). `src/api-surface.typecheck.ts` enforces intentionally private exports during `tsc`.
- Do not edit generated `dist/`; `tsc` builds it from `src/` and `tui/`.
- This repo dogfoods its own plugin: `.opencode/rules/*.md` are injected into sessions and contain additional scoped guardrails.
- When adding/removing/renaming production modules, update the README "Project Structure" section in the same change (`.opencode/rules/11-readme-and-doc-sync.md`).
Expand Down
98 changes: 58 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,29 +492,40 @@ The following shows the key source modules. Additional test files (`*.test.ts`)
opencode-rules/
├── src/
│ ├── index.ts # Main plugin entry point and exports
│ ├── runtime.ts # OpenCodeRulesRuntime class (hook orchestration)
│ ├── file-observation-context.ts # Runtime-owned per-session File-observation store (bounded LRU; feeds globs/fileContains matching and earliest-dispatch admission; live events only)
│ ├── session-working-context.ts # Runtime-owned Working context (path-only compaction projection, history prefetch, never a matching source)
│ ├── file-observation.ts # File-observation normalization for read/write/edit/apply_patch/lsp (live events; history parts feed path-only Working context)
│ ├── rule-delivery.ts # Durable/transient delivery, Hook queues, and identity ledger
│ ├── rule-delivery-codec.ts # Delivery identifiers, formats, history decoding, and transient presence facts
│ ├── rule-delivery-history.ts # Raw history port for delivery decoding
│ ├── runtime-context.ts # Context-building helpers (match context, project detection)
│ ├── runtime-chat.ts # Chat message handling and text extraction
│ ├── rule-discovery.ts # Rule file scanning, discovery, and per-session snapshots
│ ├── rule-metadata.ts # YAML frontmatter parsing
│ ├── rule-filter.ts # Rule matching against context, lifetime classification (globs, fileContains, keywords, tools, runtime)
│ ├── message-paths.ts # Legacy path-extraction compatibility facade
│ ├── message-context.ts # User prompt extraction from message parts
│ ├── session-store.ts # Per-session state management
│ ├── project-fingerprint.ts # Project type detection (Node.js, Python, etc.)
│ ├── mcp-tools.ts # MCP tool ID extraction
│ ├── git-branch.ts # Git branch detection
│ ├── matched-rules-state.ts # Persists Matched-rule state for TUI
│ ├── debug.ts # Debug logging utilities
│ ├── utils.ts # Re-export facade for backwards compatibility
│ ├── test-fixtures.ts # Shared test fixtures and builders
│ └── *.test.ts # Unit/integration tests in src
│ ├── api-surface.typecheck.ts # Type-level privacy contract (checked by tsc)
│ ├── rules/
│ │ ├── rule-discovery.ts # Rule file scanning, discovery, and per-session snapshots
│ │ ├── rule-metadata.ts # YAML frontmatter parsing
│ │ ├── rule-filter.ts # Rule matching against context, lifetime classification (globs, fileContains, keywords, tools, runtime)
│ │ └── rule-hooks.ts # Hook evaluation against serialized tool args
│ ├── delivery/
│ │ ├── rule-delivery.ts # Delivery engine composing the seams below (durable/transient delivery, Hook queues, identity ledger)
│ │ ├── delivery-state.ts # Per-session delivery state and operation serialization
│ │ ├── delivery-ledger.ts # History seeding and rule-admission persistence
│ │ ├── delivery-transient.ts # Transient dispatch presence and turn tracking
│ │ ├── rule-delivery-codec.ts # Delivery identifiers, formats, history decoding, and transient presence facts
│ │ └── rule-delivery-history.ts # Raw history port for delivery decoding
│ ├── session/
│ │ ├── session-store.ts # Per-session state management
│ │ ├── matched-rules-state.ts # Persists Matched-rule state for TUI
│ │ ├── file-observation.ts # File-observation normalization for read/write/edit/apply_patch/lsp (live events; history parts feed path-only Working context)
│ │ ├── file-observation-context.ts # Runtime-owned per-session File-observation store (bounded LRU; feeds globs/fileContains matching and earliest-dispatch admission; live events only)
│ │ ├── session-working-context.ts # Runtime-owned Working context (path-only compaction projection, history prefetch, never a matching source)
│ │ └── message-extraction.ts # File-path, prompt, and session-ID extraction from message parts
│ ├── runtime/
│ │ ├── orchestrator.ts # OpenCodeRulesRuntime class (hook orchestration)
│ │ ├── client-adapter.ts # OpenCode client port (history reads, no-reply admission, tool-ID/MCP queries)
│ │ ├── tool-hook-flow.ts # PreToolUse/PostToolUse evaluation, blockers, side-effects, Hook queuing
│ │ ├── match-context.ts # Context-building helpers (match context, project detection)
│ │ └── chat-capture.ts # Chat message handling and text extraction
│ ├── detection/
│ │ ├── project-fingerprint.ts # Project type detection (Node.js, Python, etc.)
│ │ ├── mcp-tools.ts # MCP tool ID extraction
│ │ └── git-branch.ts # Git branch detection
│ └── shared/
│ ├── bounded-session-map.ts # Shared internal LRU-bounded per-session map (sole value owner; unstamped reads; optional eviction protection)
│ └── debug.ts # Debug logging utilities
├── tui/
│ ├── index.tsx # TUI entrypoint, exports { id, tui }
│ ├── slots/
Expand All @@ -526,31 +537,38 @@ opencode-rules/
│ └── opencode-plugin-tui.d.ts # Vendored type shim
├── docs/
│ └── rules.md # Detailed usage documentation
├── openspec/ # Project specifications and proposals
└── dist/ # Compiled JavaScript output
```

#### Key Module Responsibilities

The following highlights the primary runtime modules:

- **runtime.ts** - Orchestrates hooks (`tool.execute.before`, `chat.message`, `experimental.chat.*`)
- **rule-delivery.ts** - Owns durable/transient delivery, matched Hook queues, history reconstruction, and the identity ledger
- **rule-delivery-codec.ts** - Encodes durable/transient delivery and decodes durable history facts plus transient presence facts
- **rule-delivery-history.ts** - Defines the raw host-history port used by delivery decoding
- **runtime-context.ts** - Builds `RuleMatchContext` from session state and environment
- **runtime-chat.ts** - Extracts text from chat message parts for keyword matching
- **rule-discovery.ts** - Recursively scans directories for `.md`/`.mdc` rule files
- **rule-metadata.ts** - Parses YAML frontmatter into typed `RuleMetadata`
- **rule-filter.ts** - Matches rules against context (file-observation family: globs + fileContains, keywords, tools, runtime filters) and classifies each match as session-durable or ephemeral
- **message-paths.ts** - Compatibility facade for the legacy path-extraction API; runtime matching uses normalized File observations
- **message-context.ts** - Extracts user prompt text, slash commands, and session IDs from message parts
- **session-store.ts** - Manages per-session state with LRU eviction
- **project-fingerprint.ts** - Detects project type from marker files (e.g., `package.json`)
- **mcp-tools.ts** - Maps connected MCP clients to tool IDs for `tools` condition matching
- **git-branch.ts** - Resolves current git branch for `branch` condition matching
- **matched-rules-state.ts** - Persists Matched-rule state to `~/.opencode/state/opencode-rules/{sessionId}.json` for TUI consumption (atomic writes, per-session queuing)
- **utils.ts** - Thin facade re-exporting from decomposed modules
- **runtime/orchestrator.ts** - Orchestrates hooks (`tool.execute.before`, `chat.message`, `experimental.chat.*`)
- **runtime/client-adapter.ts** - Isolates the OpenCode client port: history reads, no-reply admission via `session.prompt`, tool-ID/MCP queries
- **runtime/tool-hook-flow.ts** - Evaluates PreToolUse/PostToolUse hooks, throws on blockers, runs side-effects, queues matched Hook content
- **delivery/rule-delivery.ts** - Owns durable/transient delivery composed over per-session state, ledger, and transient seams
- **delivery/delivery-state.ts** - Per-session delivery state with operation serialization
- **delivery/delivery-ledger.ts** - History seeding and rule-admission persistence
- **delivery/delivery-transient.ts** - Transient dispatch presence facts and per-turn tracking
- **delivery/rule-delivery-codec.ts** - Encodes durable/transient delivery and decodes durable history facts plus transient presence facts
- **delivery/rule-delivery-history.ts** - Defines the raw host-history port used by delivery decoding
- **runtime/match-context.ts** - Builds `RuleMatchContext` from session state and environment
- **runtime/chat-capture.ts** - Extracts text from chat message parts for keyword matching
- **rules/rule-discovery.ts** - Recursively scans directories for `.md`/`.mdc` rule files
- **rules/rule-metadata.ts** - Parses YAML frontmatter into typed `RuleMetadata`
- **rules/rule-filter.ts** - Matches rules against context (file-observation family: globs + fileContains, keywords, tools, runtime filters) and classifies each match as session-durable or ephemeral
- **rules/rule-hooks.ts** - Evaluates rule hooks against serialized tool arguments
- **session/message-extraction.ts** - Extracts file paths, user prompt text, slash commands, and session IDs from message parts
- **session/session-store.ts** - Manages per-session state with LRU eviction
- **session/file-observation-context.ts** - Bounded per-session File-observation store feeding globs/fileContains matching
- **session/session-working-context.ts** - Path-only Working context with history prefetch and compaction projection
- **detection/project-fingerprint.ts** - Detects project type from marker files (e.g., `package.json`)
- **detection/mcp-tools.ts** - Maps connected MCP clients to tool IDs for `tools` condition matching
- **detection/git-branch.ts** - Resolves current git branch for `branch` condition matching
- **session/matched-rules-state.ts** - Persists Matched-rule state to `~/.opencode/state/opencode-rules/{sessionId}.json` for TUI consumption (atomic writes, per-session queuing)
- **shared/bounded-session-map.ts** - LRU-bounded per-session map shared across stores
- **shared/debug.ts** - Gated debug logging utilities

### TUI Sidebar

Expand Down
6 changes: 3 additions & 3 deletions docs/compaction-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ Per-session state is stored in `sessionStateMap` with the following structure:
interface SessionState {
workingContextPaths: Set<string>; // Current working set of file paths
lastUserPrompt?: string; // Latest user message text
lastUpdated: number; // Timestamp for LRU cache pruning
workingContextSeeded: boolean; // Flag: first successful seeding source completed
lastModelID?: string; // Latest model ID
lastAgentType?: string; // Latest agent type
Expand All @@ -85,8 +84,9 @@ Delivery bookkeeping (dedup ledger, pending Hook queues, rescan flag) lives in
the runtime-owned `RuleDelivery` instance, not in SessionState.

- Maximum of 100 concurrent sessions in memory (LRU eviction)
- Each entry is tagged with `lastUpdated` for age tracking
- Sessions are automatically pruned when limit is exceeded
- Eviction is owned by the internal `BoundedSessionMap` each store composes;
entries are stamped on write/read access and the least-recently-stamped
session is pruned when the limit is exceeded
- Compaction invalidates durable delivery identities; the next transformed request rebuilds them from surviving synthetic delivery metadata, missing durable rules are re-appended, and ephemeral rules are recomputed per request

## Data Flow
Expand Down
2 changes: 0 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export default [
'@typescript-eslint/no-explicit-any': 'off',
},
},
// TUI production files
{
files: ['tui/**/*.ts', 'tui/**/*.tsx'],
ignores: [
Expand Down Expand Up @@ -73,7 +72,6 @@ export default [
],
},
},
// TUI test files
{
files: [
'tui/**/*.test.ts',
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencode-rules",
"version": "1.0.0-beta01",
"version": "1.0.0",
"description": "OpenCode plugin that discovers and injects markdown rules into system prompts",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down Expand Up @@ -81,10 +81,10 @@
}
},
"devDependencies": {
"@opencode-ai/plugin": "^1.18.23",
"@opencode-ai/sdk": "^1.18.23",
"@opentui/core": "^0.5.8",
"@opentui/solid": "^0.5.8",
"@opencode-ai/plugin": "^1.18.27",
"@opencode-ai/sdk": "^1.18.27",
"@opentui/core": "^0.5.10",
"@opentui/solid": "^0.5.10",
"@types/node": "^20.19.43",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
Expand Down
Loading
Loading