Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
181d08a
feat(context): prefilter guidance rules against Claude Code session logs
napetrov Oct 31, 2026
9f518c4
fix(context): read over-deep session lines as unreadable; memoise key…
napetrov Oct 31, 2026
a05fb34
fix(context): keep a prompt with a raw U+2028 whole; match paths from…
napetrov Oct 31, 2026
dcf73d2
fix(ci): register context prefilter for the CI gates
napetrov Oct 31, 2026
a5aa50a
feat(context): read Goose session stores in context prefilter
napetrov Nov 1, 2026
5f32521
fix(context): a Goose session of unreadable messages is no session
napetrov Nov 1, 2026
9329bc8
fix(context): a file name does not match a longer name or its backup
napetrov Nov 1, 2026
027b8e4
Address review: read each file once, keep unreadable Goose diagnostic…
napetrov Nov 1, 2026
f427c57
Address review: match rules by their real triggers and paths
napetrov Nov 1, 2026
0fae24a
Address review: regression tests for the prefilter and Goose fixes
napetrov Nov 1, 2026
6b519d2
docs: state the prefilter's trigger, correction and status rules
napetrov Nov 1, 2026
27e620e
Address review: keyless rules trigger on prompts; shell segments skip…
napetrov Nov 1, 2026
a8d3b2b
fix(context): a shell match ends at its last word; quoted arguments c…
napetrov Nov 1, 2026
7af97be
docs: say echo/printf arguments and comments are not path mentions
antonwolfy Nov 1, 2026
b024307
context prefilter: warn when the guidance file has no rules
antonwolfy Nov 1, 2026
67c65ee
fix(context): quoted text, heredoc bodies and bare names in the prefi…
napetrov Nov 1, 2026
aafa091
fix(prefilter): read a command with the shell walker; escaped quotes …
napetrov Nov 1, 2026
39260e4
docs: say a quoted path with a space does not count as a path mention
antonwolfy Nov 1, 2026
58f7428
fix(context): read guidance keys and commands the same way; quoted he…
napetrov Nov 1, 2026
2683108
Count a file named like a lowercase command key as no action
antonwolfy Nov 1, 2026
b52dbcf
Merge remote-tracking branch 'intel/main' into ce-session-import
napetrov Nov 2, 2026
8497905
fix(context): read heredoc delimiters, comments and quoted names the …
napetrov Nov 2, 2026
a5ebf19
fix(context): function bodies, comments and redirections in the shell…
napetrov Nov 2, 2026
9035930
fix(context): a function head in quotes or comments, unclosed bodies,…
napetrov Nov 2, 2026
12ee241
fix(context): comment braces in function bodies, leading redirections…
napetrov Nov 2, 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
2 changes: 1 addition & 1 deletion dokimi/benchmarks/spec.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ future:
# that matched almost nothing, max_docs one that escaped; raise it with each new doc.
golden_manifest:
min_docs: 10
max_docs: 37
max_docs: 38
include:
- docs/**/*.md
exclude:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!--
Copyright (C) 2026 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->

### Added

- **`context prefilter`** — reads Claude Code session logs (`~/.claude/projects/<project>/*.jsonl`) and reports, per rule of an AGENTS.md or CLAUDE.md, how often the situation the rule describes came up, whether the agent ran the command or touched the path it names, and how often the user pushed back on it. The result is a ranked list of candidates for ablation, not a verdict: a rule that is rare or carries a protection tag (a negation, version gate, ordering or threshold) is listed but not ranked. Deterministic, no model call, no transcript text printed; `--json` gives the full report. Goose session stores (`sessions.db`) are read too. See [session-prefilter.md](docs/session-prefilter.md).
3 changes: 3 additions & 0 deletions dokimi/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ code.
- [context-window.md](context-window.md) — read a run's context window from
its Claude Code trace with `cli.py context trace`: start-up load, growth,
compactions, tool-result weight. No LLM call.
- [session-prefilter.md](session-prefilter.md) — which AGENTS.md or CLAUDE.md rules
the Claude Code session logs triggered, acted on and were corrected on, with
`cli.py context prefilter`: candidates for ablation, not verdicts. No LLM call.
- [run-consistency.md](run-consistency.md) — repeated runs of one task with
`cli.py context consistency`: result stability (pass^k), path stability
(tool mix, order, actions, strategies), process-contract conformance.
Expand Down
5 changes: 5 additions & 0 deletions dokimi/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,11 @@ separate profiles: result (`pass@k`, `pass^k`), path (tool-mix JSD, edit
distance of tool and action sequences, resource spread, strategy groups) and
conformance to a YAML process contract; `python cli.py context consistency`.
Guide: [run-consistency.md](run-consistency.md).
`dokimi/context/session_log.py` reads a Claude Code session log into prompt and
tool events; `dokimi/context/prefilter.py` matches a guidance file's rules
against them (trigger, acted, corrected) and ranks candidates for ablation.
Both are deterministic; `python cli.py context prefilter`. Guide:
[session-prefilter.md](session-prefilter.md).

## Shared building blocks

Expand Down
136 changes: 136 additions & 0 deletions dokimi/docs/reference/cli-interface.json
Original file line number Diff line number Diff line change
Expand Up @@ -3392,6 +3392,142 @@
"subcommand_required": null,
"subcommands": {}
},
"prefilter": {
"allow_abbrev": true,
"params": [
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": null,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "guidance",
"kind": "argument",
"nargs": null,
"position": 0,
"required": true,
"type": null
},
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": null,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "sessions",
"kind": "argument",
"nargs": "+",
"position": 1,
"required": true,
"type": null
},
{
"action": "_HelpAction",
"choices": null,
"const": null,
"default": "==SUPPRESS==",
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": true,
"key": "--help/-h",
"kind": "option",
"nargs": 0,
"position": null,
"required": false,
"type": null
},
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": 0.2,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "--in-use-rate",
"kind": "option",
"nargs": null,
"position": null,
"required": false,
"type": "dokimi.commands.context._unit_rate#9396aeedb2c0"
},
{
"action": "_StoreTrueAction",
"choices": null,
"const": true,
"default": false,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": true,
"key": "--json",
"kind": "option",
"nargs": 0,
"position": null,
"required": false,
"type": null
},
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": 5,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "--min-sessions",
"kind": "option",
"nargs": null,
"position": null,
"required": false,
"type": "dokimi.cli.build_parser.<locals>.positive_int#faf57db5ff9f"
},
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": 3,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "--rare-max",
"kind": "option",
"nargs": null,
"position": null,
"required": false,
"type": "dokimi.commands.context._non_negative_int#3a500817a521"
},
{
"action": "_StoreAction",
"choices": null,
"const": null,
"default": 10,
"group_key": null,
"group_required": null,
"hidden": false,
"is_flag": false,
"key": "--window",
"kind": "option",
"nargs": null,
"position": null,
"required": false,
"type": "dokimi.cli.build_parser.<locals>.positive_int#faf57db5ff9f"
}
],
"path": "dokimi context prefilter",
"subcommand_required": null,
"subcommands": {}
},
"trace": {
"allow_abbrev": true,
"params": [
Expand Down
141 changes: 141 additions & 0 deletions dokimi/docs/session-prefilter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<!--
Copyright (C) 2026 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->

# Guidance rules against session logs

Measuring whether an AGENTS.md or CLAUDE.md helps means an ablation: run the
agent with and without a rule and compare outcomes. That is expensive, so
`context prefilter` picks which rules are worth one. It reads the coding
agent's own session logs and asks, per rule: did the situation the rule
describes come up, did the agent act on it, and did the user correct the agent
on it. The output is a ranked list of **candidates for ablation**. It is not a
verdict, and it never says a rule should be cut.

Trigger rate is only a prefilter. A rule that rarely comes up can be the one
that prevents the costly failure, so a rare or protected rule is listed and
never ranked as waste.

```bash
python cli.py context prefilter AGENTS.md "$HOME/.claude/projects/<project>/*.jsonl"
python cli.py context prefilter AGENTS.md "$HOME/.claude/projects/<project>/*.jsonl" --json
```

The guidance file is split into rules with the span segmentation the novelty
layer uses (`dokimi/novelty/spans.py`): each sentence, list item or table row
that has a keyword is a claim. Headings and code blocks are not claims. No
model is called; every match is lexical.

## What is measured per rule

- **Trigger.** An *episode* where the situation came up: a prompt the user
typed, or a tool call's inputs (command, paths, search patterns, description),
share at least half of the rule's keywords (stemmed, stop words dropped,
rounded up). A rule with no prose keywords (only backticked commands or paths, or a
protection tag) triggers on a prompt that names one of them.
Repeats within the window after a trigger are the same episode.
- **Acted.** Within `--window` events either side of the trigger, a tool call
ran a command or touched a path the rule names in backticks (`` `make ci-local` ``,
`` `changelog.d/` ``). A command counts when it runs the named command at the start of
a pipeline stage or compound command (after `uv run`, `sudo`, environment assignments, or
`then`, `do`, `(`), so an `echo`, a `grep` argument or a `#` comment that names it does not.
The key is read the same way as the command, so a wrapper in a key (`` `python -m pytest` ``)
matches the same wrapped command, and a quoted argument with a space is one word on both sides. A path counts when a tool call
names it in a path input or shell command, excluding shell comments, heredoc bodies,
echo/printf arguments (their redirection targets still count) and quoted sentences: a quoted
argument with a space in it is text, so `git commit -m "update docs/guide.md"` does not act on
the file, while `cat "docs/guide.md"` does. A quoted path that itself contains a space is read as a
sentence and does not count. A `>` inside quotes is text, not a redirection. A `<` reads its file, and a
shell function's definition does not count as acting: its body is ignored (up to the brace that closes it,
quotes and nested groups counted), and calls of the function are not followed.
A capitalised bare file name in backticks (`` `Makefile` ``) matches an edit of that file, or a
shell command that names it as an argument (`cat Makefile`); an `echo` of it does not. A lowercase bare word
(`` `pytest` ``) is a command, so an argument naming it (`grep pytest`) does not count. A path with a
space in its name (`` `docs/my guide.md` ``) is a path, not a command. A Windows path
matches with backslashes too.
A rule that names neither is not measurable here: its
`acted` is `null` and it carries the flag `acted_not_measurable`.
- **Corrected.** Within `--window` events after the trigger, the user's next
prompt (the first prompt after it, and only that one) pushes back (`no`, `don't`, `instead`, `forgot`, `revert`, ...) and
shares a keyword with the rule or names its command or path. Two or more
episodes with corrections on half or more of them carry `user_overrides`:
check the wording before anything else.

Not counted: the agent's own text, tool results (what a file or command
printed), subagent briefs (a `user` record with `isSidechain`), and records
Claude Code injects (`isMeta`, such as CLAUDE.md echoed into the context). A
memory echo of a rule would otherwise trigger that rule on every session.

## Statuses

| Status | Meaning | Ranked |
| --- | --- | --- |
| `candidate_for_ablation` | Triggered in fewer than `--in-use-rate` of the sessions, or never. Ranked fewest triggered sessions first, then lowest acted share. | yes |
| `in_use` | Triggered in at least `--in-use-rate` of the sessions. | no |
| `rare_not_ranked` | Triggered in 1 through `--rare-max` sessions. Few sessions are too little evidence to call a rule unused. | no |
| `kept_protected` | The rule carries a protection tag: a negation, a version gate, an ordering constraint or a threshold (the same tags the novelty layer uses). Its failure case is rare by design. | no |
| `insufficient_corpus` | Fewer than `--min-sessions` sessions were read. | no |

Precedence: `kept_protected`, then `insufficient_corpus`, then a rule with no trigger
(a candidate), then `rare_not_ranked`, then `in_use`; the rest are candidates. `--rare-max`
takes 0 or more and `--in-use-rate` a number from 0 to 1. A file named twice (a repeat, an
overlapping glob, a symlink to it) is read once.

The table prints the same counts; the JSON has every claim with its status,
keys (commands, paths, keywords), counts, rates and flags. Defaults: window 10
events, at least 5 sessions, rare at most 3 sessions, in use at 20% of sessions.

The JSON is printed, not stored: dokimi writes no file in this format, so it
has no schema under `dokimi/schemas/`. `format_version` marks its shape. A
change that stores it must first add a versioned schema there.

## Session logs read

This slice reads **Claude Code** session logs: JSON lines under
`~/.claude/projects/<project>/`, with `user` and `assistant` records and
`tool_use` blocks. A quoted glob expands. A file that cannot be read, or holds
no prompt and no tool call, is reported and the rest are still read; the exit
code is 1 when any file failed. A line that is valid JSON but not an object, or a
record whose message or content has the wrong shape, is reported by line number and
skipped; summaries and tool results are recognized and not reported.

**Goose** sessions are read from its SQLite store, `sessions.db` under the Goose
data directory (`~/.local/share/goose/sessions/` on Linux). Each session in the
store is one session here. The layout comes from the block/goose source
(session storage and message serialization), not from a sample store: a
message's `content_json` is a list of blocks, a `text` block of a user message
is a prompt, and a `toolRequest` block carries the tool's arguments. A message whose
content is not a list of blocks is reported by message id and skipped. A message
whose `metadata_json` marks it not user-visible is not a prompt. The store is
opened read-only. Pass the `.db` file in place of, or next to, JSONL logs. The
reader is `dokimi/context/goose_log.py`.

## Privacy and offline use

The command reads session logs and the guidance file on the local disk and
prints counts and the guidance's own rule text. It prints no transcript text,
calls no model and opens no network connection. Warnings name a file, not its
content. The tests use synthetic fixtures only.

## Limits

- Keyword matching is lexical. A rule phrased with words the agent never uses
for its situation has a low trigger rate for that reason alone, which is why
a rule's text is read before any ablation, not after.
- "Acted" needs a named command or path. Most prose rules have none; for them
the report says `null`, not zero.
- A quoted argument with no space is read as a path, so a commit message of one
word that names a file (`git commit -m "docs/guide.md"`) counts as acting on it.
- Corrections are read from the next prompts only, so a correction given much
later is missed.

An LLM matcher could judge each episode's relevance and whether the agent
complied. It is not implemented here. A plausible shape: one call per session
over the rules whose keywords matched, which for a corpus of 200 sessions is
roughly 200 calls with a rule list and a short excerpt each. That is an
estimate, not a measurement.

Related: [context-window.md](context-window.md) (what one run's window held),
[run-consistency.md](run-consistency.md) (whether repeated runs agree),
[skill-novelty.md](skill-novelty.md) (the rule-level novelty of a skill).
1 change: 1 addition & 0 deletions dokimi/dokimi/cli_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class Tombstone:
Leaf(("tools", "preference"), PAID),
Leaf(("context", "trace"), FREE),
Leaf(("context", "consistency"), FREE),
Leaf(("context", "prefilter"), FREE),
Leaf(("runs", "list"), FREE),
Leaf(("runs", "show"), FREE),
Leaf(("backends",), FREE),
Expand Down
Loading
Loading