enola-guides 0.1.2 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df9b7b5b2b3fc6589f9353c4f2198b08283fe80fafd2dacda91dea2d66c5a2fb
4
- data.tar.gz: 20172bf3167db5fb3d690d401f3313d38861d10f86f7010607c4ccae98353b68
3
+ metadata.gz: 8b0ff999485e94088e964c4dd465bb5c1a26fea1a6dece3ed79b301e644786a8
4
+ data.tar.gz: 2c015c5cb721592b33c66e274108ead2f537a50e706d0a18d7a97880e9f78664
5
5
  SHA512:
6
- metadata.gz: f705dc88218a61a94d6261b624e6744d8fd14212e6524b062f2f9f9520751013968f4a208382861516265ac4ca41db11ca483e09acf52792120aa704f01251a0
7
- data.tar.gz: 65b0c4c5cd94ca30b85d3528737489c83c0ff73b8a7b3402dd2d39c45742af8ca679cfaef2ec04ea8837e3ac5d92a1881960b2a7429df198e758ef7b7d8c8435
6
+ metadata.gz: 581a958308b2e03bd6014a30f73007e7b9e6f70cfc5d97dad82d5e733f520c87b557983d3da041ee4234c7d392d510c67ddcf31a145d166909ad791e95ca187c
7
+ data.tar.gz: b2b684408b5ec182a1edb87f36483f5159af9520e60c144062b1d9e28f550c4a4176f2c3b256e73a8681b1c776a89aab5999b9816c310692331685e44b14e604
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1 (2026-08-23)
4
+
5
+ enola v0.4.4 released the constraints program the guides describe
6
+ (enola-labs/enola#247 merged); the README says released rather than landing,
7
+ and the install guide names v0.4.4 as the minimum.
8
+
9
+ ## 0.2.0 (2026-08-22)
10
+
11
+ What a knowledge base gets. Four guides (a knowledge base with enola,
12
+ hooks, the skills as they run, the wrapper), four hook templates
13
+ (intent-first gate, turn record, stop-time debt, session refresh), five
14
+ skills as a knowledge base runs them (deepdive, ingest check, drift,
15
+ citation hygiene, zoom out) and a worked example knowledge base with two
16
+ anchored pages, receipts and a finding ledger.
17
+
3
18
  ## 0.1.2 (2026-08-22)
4
19
 
5
20
  One law per file: the Rails shop, the cluster laws, the maintenance-task laws
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # enola-guides
2
2
 
3
- How to use what is landing in [enola](https://github.com/enola-labs/enola)
4
- ([enola-labs/enola#247](https://github.com/enola-labs/enola/pull/247)): a
3
+ How to use what [enola](https://github.com/enola-labs/enola) ships since
4
+ v0.4.4 ([enola-labs/enola#247](https://github.com/enola-labs/enola/pull/247)): a
5
5
  Ruby DSL for writing architecture constraints, Prism and Rubydex as
6
6
  providers that run together with enola's own extractors, recipes a team
7
7
  reviews, laws that read the whole graph, and an MCP your agent uses during
@@ -22,7 +22,9 @@ thirteen laws in the Ruby DSL, one file each, from "jobs never invoke controller
22
22
  "billing keeps to its own tables" and "a mutating action is authorized",
23
23
  with Prism and Rubydex configured as providers beside the extractors.
24
24
  [`examples/ember-app`](examples/ember-app): an Ember application bound to
25
- the conventions recipe in one block. [`examples/laws`](examples/laws/README.md):
25
+ the conventions recipe in one block. [`examples/knowledge-base`](examples/knowledge-base/README.md):
26
+ two pages anchored to code, a cluster config, receipts and a finding ledger,
27
+ the smallest knowledge base the skills and hooks run against. [`examples/laws`](examples/laws/README.md):
26
28
  twenty-six laws, one directory each, from foreign keys on a tenant column
27
29
  to parts that may not cycle, each a complete declaration you copy and run.
28
30
 
@@ -43,6 +45,16 @@ to parts that may not cycle, each a complete declaration you copy and run.
43
45
  census, the runtime capture.
44
46
  8. [Beyond one repository](guides/08-beyond-one-repo.md): the cluster, the
45
47
  seams, the laws across them, the history.
48
+ 9. [A knowledge base with enola](guides/09-a-knowledge-base-with-enola.md):
49
+ what a wiki of decisions gets when its pages anchor to code and every
50
+ number it states was read from a receipt.
51
+ 10. [Hooks](guides/10-hooks.md): the four agent hooks a knowledge base runs
52
+ (intent-first gate, turn record, stop-time debt, session refresh).
53
+ 11. [The skills, as they run](guides/11-the-skills-as-they-run.md): deepdive
54
+ before shaping, claim check while ingesting, drift on a schedule,
55
+ citation hygiene, the blast radius.
56
+ 12. [The wrapper](guides/12-the-wrapper.md): the thin command the skills
57
+ call, the named skip, the receipts and the ledger.
46
58
 
47
59
  ## Skills
48
60
 
@@ -52,7 +64,12 @@ before grepping, grade after changing), `enola-laws` (write a law as a
52
64
  sentence with a reason, lint it, read what it verdicts),
53
65
  `enola-grade-before-push` (the delta is the deliverable, a declined grade
54
66
  is not a pass), `enola-intent-first` (read the governing decision before
55
- the file moves). Copy a directory into your agent's skills folder.
67
+ the file moves), and the five a knowledge base runs: `enola-deepdive`,
68
+ `enola-ingest-check`, `enola-drift`, `enola-citation-hygiene`,
69
+ `enola-zoom-out`, each described as it runs in
70
+ [guide 11](guides/11-the-skills-as-they-run.md). Copy a directory into
71
+ your agent's skills folder; the hooks they pair with are under
72
+ [`templates/hooks/`](templates/hooks).
56
73
 
57
74
  ## Templates and recipes
58
75
 
@@ -0,0 +1,20 @@
1
+ # A knowledge base with enola, minimal
2
+
3
+ The smallest knowledge base that gets what [guide 09](../../guides/09-a-knowledge-base-with-enola.md) describes:
4
+ two pages anchored to code with `enola_intent`, a cluster config, the
5
+ committed receipts, and the finding ledger. The skills under
6
+ [`templates/skills`](../../templates/skills) and the hooks under
7
+ [`templates/hooks`](../../templates/hooks) run against exactly these
8
+ files.
9
+
10
+ - `enola-cluster.yaml`: the members, with `knowledge` pointing at the wiki.
11
+ - `wiki/shop/permanent/architecture.md`: a page whose `enola_intent`
12
+ anchors it to `app/jobs/**` and `app/models/**`, with one receipt
13
+ citation.
14
+ - `wiki/shop/permanent/public-surface.md`: a page anchored to the
15
+ controllers it describes.
16
+ - `wiki/_state/enola/receipts.json`: what `kb enola generate` recorded.
17
+ - `wiki/_state/enola/findings.json`: the ledger, one judged finding.
18
+
19
+ `governing_intent app/jobs/order_mailer_job.rb` returns the first page;
20
+ `kb enola citations` verifies its citation against the receipts file.
@@ -0,0 +1,7 @@
1
+ cluster: shop
2
+ knowledge: wiki
3
+ repos:
4
+ - path: ../rails-shop
5
+ label: shop
6
+ - path: ../ember-app
7
+ label: shop-ui
@@ -0,0 +1,10 @@
1
+ {
2
+ "findings": {
3
+ "cycles|app/jobs -> app/models -> app/jobs": {
4
+ "verdict": "accepted",
5
+ "at": "2026-08-22",
6
+ "reason": "real; the mailer path",
7
+ "landed": "wiki/shop/permanent/architecture.md"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "repos": {
3
+ "shop": {
4
+ "fingerprint": "8d1e5a7b93c2f0e4d6a18b7c3e9f0a2d4c6b8e1f3a5c7d9e0b2f4a6c8e0d2c40a",
5
+ "git_commit": "3f2a9c1d4e5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c",
6
+ "enola_version": "0.1.22",
7
+ "providers": ["prism", "rubydex"]
8
+ },
9
+ "shop-ui": {
10
+ "fingerprint": "2c7e9b1d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a6c8e",
11
+ "git_commit": "9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c",
12
+ "enola_version": "0.1.22",
13
+ "providers": []
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Shop architecture
3
+ enola_intent:
4
+ governs:
5
+ - app/jobs/**
6
+ - app/models/**
7
+ decisions:
8
+ - jobs enqueue, models decide; a job never calls back into a job
9
+ ---
10
+
11
+ # Shop architecture
12
+
13
+ Orders are written by `OrderService` only; the two other writers the
14
+ graph reported in 2026-08 were folded into it. `Order` has 41 dependents
15
+ (receipt shop@3f2a9c1, fingerprint 8d1e…c40a).
16
+
17
+ The cycle `app/jobs -> app/models -> app/jobs` through the mailer path is
18
+ real and accepted in the ledger; it is cut when the mailer moves under
19
+ `app/services`.
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Public surface
3
+ enola_intent:
4
+ governs:
5
+ - app/controllers/api/**
6
+ decisions:
7
+ - every route under /api has at least one consumer in shop-ui
8
+ ---
9
+
10
+ # Public surface
11
+
12
+ Fourteen routes under `/api`, all matched by a consumer in `shop-ui`
13
+ (coverage report, receipt shop@3f2a9c1). A route that stops matching is
14
+ a finding of `unused-routes`, judged in the ledger before any removal.
@@ -10,9 +10,9 @@ invisible to tools that look at one file at a time, and they are what enola
10
10
  reports.
11
11
 
12
12
  This guide takes you from nothing to a graded change. Everything here is
13
- written for the release that carries the constraints vocabulary (the one
14
- this repository's recipes and templates target); if your `enola --version`
15
- is older, upgrade first.
13
+ written for enola v0.4.4 or later, the release that carries the constraints
14
+ vocabulary this repository's recipes and templates target; if your
15
+ `enola --version` is older, upgrade first.
16
16
 
17
17
  ## 1. Install
18
18
 
@@ -0,0 +1,148 @@
1
+ # A knowledge base with enola
2
+
3
+ A team that keeps decisions beside its code (a wiki, ADRs, a design log)
4
+ can compile them into the graph. Then a file answers which decision
5
+ governs it, a decision answers which code it covers, a structural claim
6
+ in prose can be re-checked mechanically, and a finding judged once stays
7
+ judged. This guide is that practice, as it runs day to day on a
8
+ twenty-repository knowledge base.
9
+
10
+ ## Pages compile into the graph
11
+
12
+ A markdown page carrying an `enola_intent:` block is an intent fact when
13
+ the snapshot is taken, and every anchor on it is an edge to a file:
14
+
15
+ ```yaml
16
+ ---
17
+ title: Charges run through one service
18
+ enola_intent:
19
+ page:
20
+ type: decision
21
+ status: accepted
22
+ scope:
23
+ - shop
24
+ relations:
25
+ - rel: supersedes
26
+ to: wiki/adrs/charges-in-controllers.md
27
+ anchors:
28
+ - repo: shop
29
+ path: app/billing/charge.rb
30
+ - repo: shop
31
+ path: app/billing/refund.rb
32
+ ---
33
+ ```
34
+
35
+ Derive the block rather than hand-maintain it: if your pages already cite
36
+ the code they describe (`sources:` lines naming repository paths), the
37
+ anchors are those citations, and a small script stamps the block from
38
+ them and checks the derivation in CI. enola validates the block; keeping
39
+ it truthful to your conventions is yours.
40
+
41
+ Index the knowledge base as a member of the cluster, last, so its pages
42
+ compile over the code they anchor:
43
+
44
+ ```yaml
45
+ repos:
46
+ - ../shop
47
+ - ../mobile
48
+ - .
49
+ ```
50
+
51
+ ## Ask the graph what governs a file
52
+
53
+ ```bash
54
+ enola plan -paths app/billing/charge.rb .
55
+ ```
56
+
57
+ and over MCP, `governing_intent` for a file or a symbol: the pages whose
58
+ anchors cover it, with their relation trails (what supersedes what, what
59
+ depends on what), and for a page, which code its anchors cover. A file no
60
+ page governs is an answer too, and the difference between "no page
61
+ governs this" and "the graph was not asked" must stay visible: a missing
62
+ snapshot is a named skip, never silence.
63
+
64
+ This is the call behind the [intent-first gate](10-hooks.md): before an
65
+ agent's first edit into a repository, the governing pages are read.
66
+
67
+ ## Laws over the pages
68
+
69
+ Because anchors are facts, laws can read them (see [writing
70
+ laws](04-writing-laws.md)):
71
+
72
+ ```ruby
73
+ part :billing, files: "app/billing/**"
74
+ part :old_way, governed_by: "wiki/adrs/*.md status:superseded"
75
+ part :new_way, governed_by: "wiki/adrs/charges-in-one-service.md"
76
+
77
+ law "billing is documented" do
78
+ billing.must_be_governed
79
+ why "code under a decision needs the page that decided it"
80
+ end
81
+
82
+ law "the new way never reaches the old" do
83
+ new_way.must_not_call :old_way
84
+ why "a superseded decision's code is on its way out, not a dependency"
85
+ end
86
+ ```
87
+
88
+ ## Receipts are the committed state
89
+
90
+ Graph artifacts (`.enola/`, the cluster's output directory) are
91
+ machine-local and ignored. What enters version control is the receipt:
92
+ one entry per repository with the snapshot's fingerprint, the commit it
93
+ was taken at, the enola version and the providers that ran. Keep them in
94
+ one file the knowledge base owns:
95
+
96
+ ```json
97
+ {"repos": {"shop": {"git_commit": "…", "content_digest": "sha256:…", "enola_version": "0.5.0"}}}
98
+ ```
99
+
100
+ Two rules follow. **Regenerate the whole cluster, never one member**: a
101
+ snapshot scoped to one repository replaces the union rather than
102
+ updating its slice, and everything that reads the union then answers
103
+ from a narrower world without saying so. **Not comparable is not a
104
+ pass**: when a member's snapshot changed enola version or provider set,
105
+ the grade declines; re-pin and regenerate rather than reading silence as
106
+ clean.
107
+
108
+ ## Citations with a grammar
109
+
110
+ A structural claim in prose ("`internal/facts` has 157 dependents")
111
+ cites the receipt it was read from, in one fixed shape, so a script can
112
+ re-check it against the committed receipts and flag the ones whose
113
+ digest moved. The shape is yours to choose; what matters is that there
114
+ is one, that one extractor parses it, and that a periodic sweep lists
115
+ every citation with a verdict (verified, stale, malformed). A claim
116
+ nobody can re-check is a sentence, not a fact.
117
+
118
+ ## Findings are judged once
119
+
120
+ The explainers report candidates, not verdicts: a cycle at 0.7
121
+ confidence, a god class, an unused route. Before a finding grounds a
122
+ page, confirm it against the code and record the judgment in a ledger
123
+ beside the receipts, one entry per finding signature: accepted (and
124
+ where it landed), rejected, or permanent noise. The next session reads
125
+ the ledger instead of re-deciding, and the periodic sweep reports a
126
+ ledger entry whose finding no longer exists, which is the code moving
127
+ past a recorded judgment.
128
+
129
+ ## Pulled, with one push
130
+
131
+ Skills fetch the graph while already reasoning about something: a
132
+ deepdive asks `impact_analysis` and `governing_intent`, an ingest checks
133
+ a claim with `query_facts` while the source is open, a drift sweep
134
+ regenerates and diffs receipts. Nothing lands on a board because the
135
+ graph said so. The one exception is the session-end hook, which speaks
136
+ only when the session's change introduced a structural regression, or
137
+ when it could not grade at all; a gate nobody remembers to run is no gate.
138
+
139
+ ## The wrapper
140
+
141
+ Every skill calls a thin command the knowledge base owns rather than
142
+ the binary: `kb enola generate | receipt | diff | findings | judge | impact
143
+ | govern | citations | baseline | check | coverage | doctor`. It resolves
144
+ the binary, the cluster config and the receipts file, and when any is
145
+ absent it prints one line saying so and exits 0. That is what keeps a
146
+ skill from failing on a machine without enola, and what keeps "the graph
147
+ was not asked" distinguishable from "the graph agreed". The [wrapper
148
+ guide](12-the-wrapper.md) has its shape.
@@ -0,0 +1,101 @@
1
+ # Hooks: the graph at the moments that matter
2
+
3
+ A hook fires at a moment nobody has to remember: a session starting, the
4
+ first edit into a repository, a turn ending, a push. Each one here reads
5
+ the graph at that moment, blocks at most once, and forces a reading,
6
+ never a verdict. The scripts are in [`templates/hooks/`](../templates/hooks);
7
+ they are written for Claude Code's hook protocol (JSON on stdin, exit 2
8
+ to block with the message on stderr) and translate to any agent with
9
+ hooks.
10
+
11
+ ## enola's own pair
12
+
13
+ ```bash
14
+ enola install --hooks
15
+ ```
16
+
17
+ wires two: **session start** restores the snapshot and serves it over
18
+ MCP; **stop** grades the session's delta against the pinned baseline and
19
+ speaks only when the change introduced a regression or could not be
20
+ compared. `enola doctor` reports when each last fired and what it
21
+ concluded; a hook that never fires is the failure to look for.
22
+
23
+ ## The intent-first gate (PreToolUse on Edit and Write)
24
+
25
+ The first write into each repository per piece of work is blocked once.
26
+ The block carries two things: the pages that govern the file
27
+ (`governing_intent`, or `enola plan -paths <file>` when the MCP is not
28
+ in reach) and the pre-edit contract (the declared laws binding the path
29
+ and the blast radius over the current snapshot). The agent reads, then
30
+ retries, and the retry proceeds. The gate re-arms on every user message,
31
+ so each ask gets its own forced stop and no piece of work gets two.
32
+
33
+ What it refuses to do: decide. If the governing page contradicts the
34
+ intended change, the contract says amend the page first; the hook only
35
+ makes the page impossible to miss. When no page governs the file, it
36
+ says so and asks whether the work should be shaped. When the graph is
37
+ absent, it says so and lets the write through; a missing graph is a
38
+ named skip, never a wall.
39
+
40
+ `templates/hooks/intent-first-gate.sh` gates Edit and Write. The brain's
41
+ own gate also classifies shell commands as writes (a `sed -i`, a
42
+ redirect, a `cp` into the repository); the template marks where that
43
+ classifier goes and leaves it out, because the rules for what counts as a
44
+ write depend on your tree.
45
+
46
+ ## The stop-time debt (Stop)
47
+
48
+ When the turn ends, two debts can block it once each:
49
+
50
+ - **Code moved, no spec moved.** Files changed in a governed repository
51
+ this turn and no governing page did. The block asks for either the
52
+ amendment or an explicit sentence, in the reply, saying why the pages
53
+ as written already cover the change.
54
+ - **A push went out with no grade.** A `git push` to a repository
55
+ happened this turn and no `enola check` ran. The block asks for the
56
+ grade, read, before finishing.
57
+
58
+ Both read a small per-session record the PostToolUse hook writes
59
+ (`templates/hooks/record.sh`): which files moved, whether a push
60
+ happened, whether a check ran. The debt hook is the mechanical half of
61
+ two rules a team can otherwise only ask people to remember.
62
+
63
+ ## The union refresh (SessionStart)
64
+
65
+ When the session starts, compare the paired repository's HEAD with the
66
+ commit the committed receipt records. When they differ, regenerate that
67
+ member's slice of the union in the background and say so in one line;
68
+ when they agree, say that. The agent's first structural question then
69
+ reads a graph that matches the checkout it is about to edit.
70
+
71
+ ## Wiring
72
+
73
+ Claude Code, in `.claude/settings.json`:
74
+
75
+ ```json
76
+ {
77
+ "hooks": {
78
+ "SessionStart": [{"matcher": "startup|resume", "hooks": [
79
+ {"type": "command", "command": "enola hook session-start"},
80
+ {"type": "command", "command": "\"${CLAUDE_PROJECT_DIR:-.}/hooks/session-refresh.sh\""}]}],
81
+ "PreToolUse": [{"matcher": "Edit|Write|MultiEdit", "hooks": [
82
+ {"type": "command", "command": "\"${CLAUDE_PROJECT_DIR:-.}/hooks/intent-first-gate.sh\""}]}],
83
+ "PostToolUse": [{"matcher": "Edit|Write|MultiEdit|Bash", "hooks": [
84
+ {"type": "command", "command": "\"${CLAUDE_PROJECT_DIR:-.}/hooks/record.sh\""}]}],
85
+ "UserPromptSubmit": [{"hooks": [
86
+ {"type": "command", "command": "\"${CLAUDE_PROJECT_DIR:-.}/hooks/record.sh\" reset"}]}],
87
+ "Stop": [{"hooks": [
88
+ {"type": "command", "command": "enola hook stop"},
89
+ {"type": "command", "command": "\"${CLAUDE_PROJECT_DIR:-.}/hooks/stop-debt.sh\""}]}]
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## What it feels like
95
+
96
+ An agent asked to change a billing service edits nothing until it has
97
+ read the decision that put charges through one service; it grades the
98
+ change before the push and writes the grade into the pull request; and
99
+ when it ends the turn having changed code under a decision without
100
+ touching the decision, it is asked to account for that once. None of
101
+ this needs the agent to know enola exists.
@@ -0,0 +1,81 @@
1
+ # The skills, as they run
2
+
3
+ A skill is a named piece of practice an agent follows. Each of these asks
4
+ the graph one specific question at one specific moment; the value is the
5
+ moment, not the prompt. They are described here as they run on a
6
+ twenty-repository knowledge base, and shipped under
7
+ [`templates/skills/`](../templates/skills) to copy.
8
+
9
+ ## Before shaping work: the deepdive (`enola-deepdive`)
10
+
11
+ Shaping a change starts with three graph questions, answered before a
12
+ line of the plan is written:
13
+
14
+ - `impact_analysis` on every symbol the work would change: fan-in,
15
+ fan-out, named callers. A symbol with 157 dependents is a different
16
+ plan from one with 3.
17
+ - `governing_intent` on the paths: which decisions already cover the
18
+ area, so the plan amends a page rather than contradicting it.
19
+ - `query_insights` for the area: what the explainers already computed (a
20
+ cycle someone must have introduced, a layer crossed, a dead route), so
21
+ the plan inherits judged findings instead of rediscovering them.
22
+
23
+ The plan cites the receipt it read from, so a later reader can re-check
24
+ the numbers.
25
+
26
+ ## Before building: the grade (`enola-grade-before-push`)
27
+
28
+ `set_baseline` once, early; after the change, `generate_snapshot` and
29
+ `diff_snapshot`; the delta (introduced, resolved, edges added) goes into
30
+ the pull request in one sentence. A declined comparison is not a pass.
31
+ This is also the [stop-time debt](10-hooks.md) hook's demand.
32
+
33
+ ## While ingesting a source: the claim check (`enola-ingest-check`)
34
+
35
+ A page that states a structural fact (a stack, a public surface, a
36
+ route, a table) checks it while the source is open: `query_facts` for
37
+ the fact, `impact_analysis` for the count. A wrong claim ingested today
38
+ is cited by a page tomorrow and reasoned from by an agent next week, so
39
+ verification is cheapest now. For an incremental ingest, each changed
40
+ path runs through `governing_intent`: the pages whose anchors cover a
41
+ changed file are exactly the synthesis that may have drifted, and a
42
+ changed file no page governs is a gap the ingest may be about to fill.
43
+
44
+ ## The periodic sweep: drift (`enola-drift`)
45
+
46
+ On a schedule, or before publishing: regenerate the cluster, diff the
47
+ committed receipts (fingerprint and commit per member), and read
48
+ `coverage_report` for which cross-repository edges resolved and which did
49
+ not. Receipts that moved name the members whose pages want a re-read.
50
+ Not a comparison of findings between two runs; that is the grade's job.
51
+
52
+ ## Knowledge hygiene: citations and the ledger (`enola-citation-hygiene`)
53
+
54
+ Every receipt citation in prose gets a verdict against the committed
55
+ receipts: verified, stale (the digest moved), malformed. Stale ones are
56
+ re-read, not rewritten. The finding ledger is swept the same way: an
57
+ accepted finding whose evidence no longer exists in the graph is the
58
+ code moving past a judgment, and the page that cites it is re-read.
59
+
60
+ ## Stepping back: the blast radius (`enola-zoom-out`)
61
+
62
+ When deep in a change, one call answers how it sits in the whole:
63
+ `impact_analysis` for the symbols in hand, `find_path` between the
64
+ part being changed and the parts that consume it, `governing_intent`
65
+ for the decisions around it. The answer is one paragraph of technical
66
+ fit the agent can stand on, beside the product fit it reasons out.
67
+
68
+ ## Writing and explaining laws (`enola-laws`)
69
+
70
+ Covered in [writing laws](04-writing-laws.md): lint before verdict,
71
+ `constraints explain` before arguing with a verdict, ratchet before
72
+ strict.
73
+
74
+ ## What makes these skills and not prompts
75
+
76
+ Each names the call, the moment, and the refusal: what the agent does
77
+ when the graph is absent (say so, proceed, never pretend), when the
78
+ comparison declines (re-pin, never read as clean), when a finding is a
79
+ candidate (confirm against the code, judge once, record). That is the
80
+ practice the hooks enforce and the skills describe; the two are one
81
+ contract read from two sides.
@@ -0,0 +1,69 @@
1
+ # The wrapper
2
+
3
+ Skills call a command the knowledge base owns, not the binary. The
4
+ wrapper is thin: it knows where the binary, the cluster config and the
5
+ receipts file live, it records receipts after a generate, and when
6
+ anything is absent it prints one line saying so and exits 0. That is the
7
+ whole design, and it is what lets every skill run on a machine without
8
+ enola without failing, while keeping "the graph was not asked" a
9
+ different sentence from "the graph agreed".
10
+
11
+ ## The operations
12
+
13
+ | Op | Answers |
14
+ |---|---|
15
+ | `generate` | snapshot the cluster, then record every member's receipt |
16
+ | `receipt [member]` | what was recorded |
17
+ | `diff` | which members' live receipts drifted from the recorded ones |
18
+ | `findings [--repo] [--all]` | the explainers' findings across the cluster, attributed to the member their evidence names, with judged ones hidden |
19
+ | `judge <signature> accepted\|rejected\|noise` | record a verdict in the ledger |
20
+ | `impact <symbol>` | fan-in, fan-out, named callers, read from the on-disk facts |
21
+ | `govern <path or page>` | the pages governing a file, or the code a page anchors |
22
+ | `citations` | every receipt citation in prose with its verdict |
23
+ | `baseline pin\|show\|clear [member]` | the "before" a change is graded against |
24
+ | `check [member]` | the grade, reported, never gating |
25
+ | `coverage` | which cross-repository edges resolved |
26
+ | `doctor` | whether the hooks fire |
27
+
28
+ ## The shape, in any language
29
+
30
+ ```
31
+ resolve binary -> absent: print "enola: not installed, the graph was not asked"; exit 0
32
+ resolve config -> absent: print the path looked for; exit 0
33
+ resolve receipts -> absent on a read op: print; exit 0
34
+ run the op -> on generate, read each member's .enola/receipt.json
35
+ and write fingerprint, commit, version, providers
36
+ into the receipts file
37
+ exit codes -> the binary's, except check: report, exit 0
38
+ ```
39
+
40
+ Three things to keep:
41
+
42
+ - **Receipts are the only committed state.** `generate` writes them;
43
+ nothing else does. `diff` compares them to the live ones; it is not a
44
+ comparison of findings.
45
+ - **Findings read from disk, not the MCP server.** `impact` and
46
+ `findings` parse the fact and insight files, so scheduled and headless
47
+ runs keep working when no server is up.
48
+ - **`check` reports; it never gates.** The binary exits 1 on a
49
+ regression and the wrapper does not propagate it: no graph-dependent
50
+ step may stop work, so the verdict is surfaced for a person to weigh.
51
+ Exit 3 from the binary means the snapshots were not comparable; the
52
+ wrapper prints that as "not asked", never as a pass.
53
+
54
+ ## The ledger beside the receipts
55
+
56
+ ```json
57
+ {"findings": {"cycles|app/jobs -> app/models -> app/jobs": {"verdict": "accepted", "at": "2026-08-22", "reason": "real; the mailer path", "landed": "wiki/shop/permanent/architecture.md"}}}
58
+ ```
59
+
60
+ One entry per finding signature, written on judgment. There is no
61
+ pending state: nothing enumerates unjudged findings as work, because a
62
+ finding nobody bet on is an observation.
63
+
64
+ ## Why not call the binary directly
65
+
66
+ Every skill would then carry the absent-binary branch, the config
67
+ resolution and the receipt bookkeeping, and they would drift. The
68
+ wrapper is where those live once, which is why the guides say `kb enola
69
+ impact` where the brain that runs this says `brain.py enola impact`.
data/lib/enola-guides.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Enola
4
4
  module Guides
5
- VERSION = "0.1.2"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # PreToolUse on Edit|Write|MultiEdit: block the first write into each
3
+ # repository per piece of work, once, with the governing pages and the
4
+ # pre-edit contract on stderr. The retry proceeds; the gate forces the
5
+ # reading, never the verdict. Re-armed by record.sh on every user message.
6
+ #
7
+ # Where a shell-command classifier would go: a Bash tool call whose text
8
+ # carries a write shape (sed -i, a redirect, tee, cp or mv into the tree,
9
+ # an interpreter fed a heredoc) is a write too. What counts depends on your
10
+ # tree; gate it the same way once you have decided.
11
+ set -u
12
+ input=$(cat 2>/dev/null || true)
13
+ sid=$(jq -r '.session_id // "nosession"' <<<"$input" 2>/dev/null)
14
+ file=$(jq -r '.tool_input.file_path // empty' <<<"$input" 2>/dev/null)
15
+ [ -n "$file" ] || exit 0
16
+
17
+ top=$(git -C "$(dirname "$file")" rev-parse --show-toplevel 2>/dev/null) || exit 0
18
+ case "$file" in
19
+ "$top"/.enola/*|"$top"/wiki/_views/*|"$top"/wiki/_state/*) exit 0 ;;
20
+ esac
21
+ rel="${file#"$top"/}"
22
+ repo=$(basename "$top")
23
+
24
+ state="${TMPDIR:-/tmp}/intent-first-$(id -u)"
25
+ mkdir -p "$state"
26
+ ack="$state/${sid}-${repo}.ack"
27
+ [ -e "$ack" ] && exit 0
28
+ touch "$ack"
29
+
30
+ govern=""
31
+ if command -v enola >/dev/null 2>&1; then
32
+ govern=$(cd "$top" && timeout 30 enola plan -paths "$rel" . 2>/dev/null | sed -n '/^Plan report/,$p' | head -40)
33
+ fi
34
+ [ -n "$govern" ] || govern="(enola plan unavailable: no binary or no snapshot for this checkout; named skip, consult the decision records by hand)"
35
+
36
+ cat >&2 <<EOF
37
+ intent-first gate (${repo}): first write for this piece of work.
38
+
39
+ Before ${rel} moves, read what governs it and check the intended change
40
+ against it. If the change deviates from what the governing page records,
41
+ amend the page first; if nothing governs this work, say so and ask
42
+ whether it should be shaped.
43
+
44
+ ${govern}
45
+
46
+ Retrying proceeds. This gate re-arms on every user message; within one
47
+ piece of work it will not fire again for ${repo}.
48
+ EOF
49
+ exit 2
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # PostToolUse on Edit|Write|MultiEdit|Bash: record what this turn did, so
3
+ # the stop-time debt hook can account for it. With "reset" as the first
4
+ # argument (UserPromptSubmit): a new user message begins a new piece of
5
+ # work, so clear the record and the gate's acknowledgements.
6
+ set -u
7
+ state="${TMPDIR:-/tmp}/intent-first-$(id -u)"
8
+ mkdir -p "$state"
9
+ input=$(cat 2>/dev/null || true)
10
+ sid=$(jq -r '.session_id // "nosession"' <<<"$input" 2>/dev/null)
11
+ turn="$state/${sid}.turn"
12
+
13
+ if [ "${1:-}" = "reset" ]; then
14
+ rm -f "$state/${sid}"-*.ack "$turn" "$state/${sid}.debt-blocked" "$state/${sid}.push-blocked"
15
+ exit 0
16
+ fi
17
+
18
+ record() { printf '%s\n' "$1" >> "$turn"; }
19
+
20
+ tool=$(jq -r '.tool_name // empty' <<<"$input" 2>/dev/null)
21
+ if [ "$tool" = "Bash" ]; then
22
+ cmd=$(jq -r '.tool_input.command // empty' <<<"$input" 2>/dev/null)
23
+ [ -n "$cmd" ] || exit 0
24
+ printf '%s' "$cmd" | grep -qE '(^|[[:space:];&|])git[[:space:]]+push' && record "push"
25
+ printf '%s' "$cmd" | grep -qE 'enola[[:space:]]+check' && record "check"
26
+ exit 0
27
+ fi
28
+
29
+ file=$(jq -r '.tool_input.file_path // .tool_response.filePath // empty' <<<"$input" 2>/dev/null)
30
+ [ -n "$file" ] || exit 0
31
+ top=$(git -C "$(dirname "$file")" rev-parse --show-toplevel 2>/dev/null) || exit 0
32
+ rel="${file#"$top"/}"
33
+ case "$rel" in
34
+ wiki/*.md|docs/decisions/*.md|docs/adrs/*.md) record "spec $rel" ;;
35
+ *) record "code $rel" ;;
36
+ esac
37
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # SessionStart: when a cluster member's HEAD moved past the commit the
3
+ # committed receipts record, regenerate the cluster in the background and
4
+ # say so in one line; when they agree, say that. The agent's first
5
+ # structural question then reads a graph that matches the checkout.
6
+ #
7
+ # RECEIPTS is the knowledge base's committed receipts file; CLUSTER the
8
+ # cluster config; MEMBER the repository to compare (default: this one).
9
+ set -u
10
+ RECEIPTS="${RECEIPTS:-wiki/_state/enola/receipts.json}"
11
+ CLUSTER="${CLUSTER:-enola-cluster.yaml}"
12
+ MEMBER="${MEMBER:-.}"
13
+ command -v enola >/dev/null 2>&1 || { echo "enola hooks: no binary on PATH; the graph was not asked (named skip)"; exit 0; }
14
+ [ -f "$RECEIPTS" ] || { echo "enola hooks: no receipts at $RECEIPTS; the graph was not asked (named skip)"; exit 0; }
15
+
16
+ label=$(basename "$(cd "$MEMBER" && pwd -P)")
17
+ head=$(git -C "$MEMBER" rev-parse HEAD 2>/dev/null || true)
18
+ recorded=$(python3 - "$RECEIPTS" "$label" <<'PY' 2>/dev/null
19
+ import json, sys
20
+ r = json.load(open(sys.argv[1]))
21
+ print(((r.get("repos") or r).get(sys.argv[2]) or {}).get("git_commit") or "")
22
+ PY
23
+ )
24
+ if [ -n "$head" ] && [ "$head" != "$recorded" ]; then
25
+ log="${TMPDIR:-/tmp}/enola-refresh-$(id -u).log"
26
+ (nohup enola --generate "$CLUSTER" > "$log" 2>&1 &)
27
+ echo "enola hooks: $label is at ${head:0:11}, the recorded receipt at ${recorded:0:11}; regenerating the cluster in the background (log $log)."
28
+ else
29
+ echo "enola hooks: the recorded receipt matches $label at ${head:0:11}."
30
+ fi
31
+ exit 0
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ # Stop: block the end of the turn once when this turn pushed without a
3
+ # grade, and once when code moved under decision records and no record
4
+ # moved. Reads the per-session record written by record.sh.
5
+ set -u
6
+ state="${TMPDIR:-/tmp}/intent-first-$(id -u)"
7
+ input=$(cat 2>/dev/null || true)
8
+ sid=$(jq -r '.session_id // "nosession"' <<<"$input" 2>/dev/null)
9
+ turn="$state/${sid}.turn"
10
+ [ -f "$turn" ] || exit 0
11
+
12
+ if grep -q '^push$' "$turn" && ! grep -q '^check$' "$turn" && [ ! -e "$state/${sid}.push-blocked" ]; then
13
+ touch "$state/${sid}.push-blocked"
14
+ cat >&2 <<'EOF'
15
+ push without a grade: this turn pushed and ran no enola check.
16
+
17
+ Before finishing: run `enola check <repo>` against the pinned baseline
18
+ and read the grade. A declined grade is not a pass; re-pin and grade
19
+ again. This check blocks once per turn.
20
+ EOF
21
+ exit 2
22
+ fi
23
+
24
+ if grep -q '^code' "$turn" && ! grep -q '^spec' "$turn" && [ ! -e "$state/${sid}.debt-blocked" ]; then
25
+ touch "$state/${sid}.debt-blocked"
26
+ summary=$(awk -F'\t' '$1 == "code" { print " " $2 }' "$turn" | sort -u | head -15)
27
+ cat >&2 <<EOF
28
+ intent debt: code moved this turn, and no decision record did.
29
+
30
+ Changed without a record moving:
31
+ ${summary}
32
+
33
+ Before finishing: either amend the governing record to match what was
34
+ built, or state explicitly, in your reply, why the records as written
35
+ already cover the change. This check blocks once per turn.
36
+ EOF
37
+ exit 2
38
+ fi
39
+ exit 0
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: enola-citation-hygiene
3
+ description: Use during grooming; gives every receipt citation in prose a verdict against the committed receipts and sweeps the finding ledger for judgments the code has moved past
4
+ ---
5
+
6
+ # enola-citation-hygiene: every citation gets a verdict
7
+
8
+ 1. `kb enola citations`: each receipt citation in prose is verified, stale
9
+ (the digest moved) or malformed.
10
+ 2. Stale: re-read the cited page against the current facts and amend the
11
+ sentence; never update the citation alone, the sentence is what may be
12
+ wrong.
13
+ 3. Malformed: fix the citation to the recorded form.
14
+ 4. `kb enola findings --all`: for each accepted finding in the ledger,
15
+ confirm its evidence still exists in the graph. Evidence gone means
16
+ the code moved past the judgment; re-read the page that landed it and
17
+ record the new state.
18
+ 5. Rejected and noise verdicts stay; they keep the finding hidden on
19
+ purpose.
20
+
21
+ A citation is a promise that a number was read, not remembered; this
22
+ skill keeps the promise current.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: enola-deepdive
3
+ description: Use before shaping or planning a change; asks the graph three questions (impact, governing decisions, existing findings) and writes the answers into the plan with the receipt they came from
4
+ ---
5
+
6
+ # enola-deepdive: ask the graph before writing the plan
7
+
8
+ 1. Name the symbols the change would touch. For each:
9
+ `kb enola impact <Symbol>` (or `impact_analysis` over MCP). Record fan-in,
10
+ fan-out and the named callers. A symbol with 157 dependents is a
11
+ different plan from one with 3.
12
+ 2. For the paths: `kb enola govern <path>` (`governing_intent`). The pages
13
+ returned are the decisions already covering the area; the plan amends
14
+ one of them, it does not contradict it in silence.
15
+ 3. For the area: `query_insights` filtered to it. Cycles, layer crossings,
16
+ dead routes and hotspots are already computed and some are already
17
+ judged in the ledger; inherit them.
18
+ 4. Write the three answers into the plan, each with the receipt
19
+ (`kb enola receipt <member>`) it was read from.
20
+ 5. If the graph is absent or the receipts are stale (`kb enola diff`), say
21
+ so in the plan and proceed; never fill the numbers from memory.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: enola-drift
3
+ description: Use on a schedule or before publishing; regenerates the cluster, compares committed receipts to live ones, and names the members whose pages want a re-read
4
+ ---
5
+
6
+ # enola-drift: which members moved
7
+
8
+ 1. `kb enola generate` to regenerate the cluster and record receipts.
9
+ 2. `kb enola diff`: each member whose fingerprint or commit moved is
10
+ listed. Those are the members whose pages may describe a structure that
11
+ no longer exists.
12
+ 3. `kb enola coverage` (`coverage_report`): which cross-repository edges
13
+ resolved and which did not. An edge that stopped resolving is a
14
+ consumer a page may still claim.
15
+ 4. For each moved member: `kb enola govern <member path>` and re-read the
16
+ pages returned against the new facts. Amend what changed; leave what
17
+ did not.
18
+ 5. Commit the receipts with the page amendments, in one commit.
19
+
20
+ This is a comparison of receipts, not of findings. Findings between two
21
+ runs are the grade's job (`enola-grade-before-push`).
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: enola-ingest-check
3
+ description: Use while ingesting a source into the knowledge base; checks every structural claim against the graph while the source is open, and routes changed paths to the pages that govern them
4
+ ---
5
+
6
+ # enola-ingest-check: verify the claim while the source is open
7
+
8
+ 1. When a page is about to state a structural fact (a stack, a public
9
+ surface, a route, a table, a count): `query_facts` for the fact,
10
+ `kb enola impact <Symbol>` for the count. Write what the graph says,
11
+ and cite the receipt.
12
+ 2. For an incremental ingest, for each changed path:
13
+ `kb enola govern <path>`. The pages returned are the synthesis that may
14
+ have drifted; re-read them against the change. A changed path no page
15
+ governs is a gap the ingest is about to fill, so anchor the new page to
16
+ it with `enola_intent`.
17
+ 3. A claim the graph cannot answer (product intent, history, a reason) is
18
+ stated as the source's claim, not as a fact of the code.
19
+ 4. If the graph is absent, the page says the claim is unverified; it does
20
+ not silently become a fact.
21
+
22
+ A wrong claim ingested today is cited tomorrow and reasoned from next
23
+ week; verification is cheapest now.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: enola-zoom-out
3
+ description: Use when deep in a change and the question is how it sits in the whole; answers the technical blast radius in one paragraph from the graph, beside the product fit reasoned out
4
+ ---
5
+
6
+ # enola-zoom-out: the blast radius in one paragraph
7
+
8
+ 1. `kb enola impact <Symbol>` for the symbols in hand: fan-in, named callers.
9
+ 2. `find_path` from the part being changed to the parts that consume it:
10
+ the route the change travels.
11
+ 3. `kb enola govern <path>`: the decisions around it.
12
+ 4. Write one paragraph of technical fit: what depends on this, through
13
+ what, under which decisions. Put it beside the product fit, which the
14
+ graph cannot answer.
15
+ 5. If the paragraph names a dependent the plan did not, the plan is
16
+ incomplete; return to `enola-deepdive`.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enola-guides
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muhamed Isabegovic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-22 00:00:00.000000000 Z
11
+ date: 2026-08-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Ready-to-use templates, presets, skills and instructions for adopting
@@ -25,6 +25,12 @@ files:
25
25
  - README.md
26
26
  - examples/ember-app/enola/constraints/ember.yaml
27
27
  - examples/ember-app/enola/recipes/ember-conventions.yaml
28
+ - examples/knowledge-base/README.md
29
+ - examples/knowledge-base/enola-cluster.yaml
30
+ - examples/knowledge-base/wiki/_state/enola/findings.json
31
+ - examples/knowledge-base/wiki/_state/enola/receipts.json
32
+ - examples/knowledge-base/wiki/shop/permanent/architecture.md
33
+ - examples/knowledge-base/wiki/shop/permanent/public-surface.md
28
34
  - examples/laws/README.md
29
35
  - examples/laws/clean-rings/README.md
30
36
  - examples/laws/clean-rings/enola/constraints/clean.yaml
@@ -103,6 +109,10 @@ files:
103
109
  - guides/06-hooks-and-agents.md
104
110
  - guides/07-providers.md
105
111
  - guides/08-beyond-one-repo.md
112
+ - guides/09-a-knowledge-base-with-enola.md
113
+ - guides/10-hooks.md
114
+ - guides/11-the-skills-as-they-run.md
115
+ - guides/12-the-wrapper.md
106
116
  - lib/enola-guides.rb
107
117
  - recipes/README.md
108
118
  - recipes/ember-conventions.yaml
@@ -114,9 +124,18 @@ files:
114
124
  - templates/enola-check.sh
115
125
  - templates/enola-cluster.yaml
116
126
  - templates/enola-findings.rb
127
+ - templates/hooks/intent-first-gate.sh
128
+ - templates/hooks/record.sh
129
+ - templates/hooks/session-refresh.sh
130
+ - templates/hooks/stop-debt.sh
131
+ - templates/skills/enola-citation-hygiene/SKILL.md
132
+ - templates/skills/enola-deepdive/SKILL.md
133
+ - templates/skills/enola-drift/SKILL.md
117
134
  - templates/skills/enola-grade-before-push/SKILL.md
135
+ - templates/skills/enola-ingest-check/SKILL.md
118
136
  - templates/skills/enola-intent-first/SKILL.md
119
137
  - templates/skills/enola-laws/SKILL.md
138
+ - templates/skills/enola-zoom-out/SKILL.md
120
139
  - templates/skills/enola/SKILL.md
121
140
  homepage: https://github.com/misabegovic/enola-guides
122
141
  licenses: