@freetesh/zeno 0.8.0 → 0.9.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "zeno",
4
- "version": "0.8.0",
4
+ "version": "0.9.1",
5
5
  "description": "Keeps your domain knowledge and AI rules local and enforces them, so Claude hallucinates less.",
6
6
  "author": {
7
7
  "name": "Ritesh Firodiya",
package/README.md CHANGED
@@ -1,10 +1,28 @@
1
- # zeno
1
+ <p align="center">
2
+ <img src=".github/assets/icon.svg" width="96" height="96" alt="zeno icon">
3
+ </p>
2
4
 
3
- A productivity tool for the whole product development lifecycle (PDLC), from
4
- idea to release. zeno keeps your domain knowledge, decisions and AI rules local,
5
- in your own repos, and enforces them: Claude searches them before it answers,
6
- weighs a request against them before it acts, and `zeno check` fails when code
7
- or docs break them. The AI hallucinates less, and your team ships faster.
5
+ <h1 align="center">zeno</h1>
6
+
7
+ <p align="center">
8
+ <b>Facts, not assumptions.</b> Your rules, kept in your repo. Enforced.<br>
9
+ A Claude Code plugin and CLI · MIT
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@freetesh/zeno"><img alt="npm" src="https://img.shields.io/npm/v/@freetesh/zeno"></a>
14
+ <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-blue"></a>
15
+ </p>
16
+
17
+ zeno keeps your domain knowledge, decisions and AI rules in your own repos, and
18
+ enforces them. Claude searches them before it answers. It weighs each request
19
+ against them before it acts, and it checks every claim in the request against
20
+ facts. `zeno check` fails when code or docs break a rule. The AI guesses less,
21
+ and you redo less work.
22
+
23
+ <p align="center">
24
+ <img src=".github/assets/demo.svg" alt="zeno weighs a request. Two of three claims have no facts and the request breaks a decided rule, so it pushes back and stops until the user brings facts, agrees to terms, or drops it.">
25
+ </p>
8
26
 
9
27
  It has two halves that read the same config:
10
28
 
@@ -19,6 +37,15 @@ It has two halves that read the same config:
19
37
  cost vs value, and the write-less ladder, then answers **DO IT**, **DO IT
20
38
  DIFFERENTLY** or **PUSH BACK** with citations. Push back stops work until you
21
39
  give a reason; overrides are logged and feed `zeno learn`.
40
+ - **No assumptions.** A request is not a fact. Each claim in it ("users want
41
+ this", "it is slow") is checked against the repo and its data sources and
42
+ marked backed or not. Work that rests on an assumption stops until you agree
43
+ to written, numbered terms. Replies use plain words.
44
+ - **Your data sources, your tools.** zeno has no connectors. A repo describes
45
+ each tool that holds its data (Sentry, Datadog, Snowflake) on a `SOURCE.*`
46
+ wiki page that names the MCP server, skill or CLI that reads it. The gate
47
+ queries it, read-only, to back or reject a claim, and `zeno check` fails a
48
+ source page that does not say how to read it.
22
49
  - **Knowledge that is found, not remembered.** Every repo's `.context/wiki` and
23
50
  feature board is indexed with [QMD](https://github.com/tobi/qmd) (local hybrid
24
51
  search). Claude is told to search before answering.
@@ -78,17 +105,6 @@ starter `.dependency-cruiser.cjs` to your layers. `evals/fixtures/streaks` is a
78
105
  small product with a wiki and decision records, if you want to see the
79
106
  `.context/` layout before touching your own repo.
80
107
 
81
- For local development of zeno itself, link the repo as a skills-dir plugin so
82
- edits apply on the next session: `ln -s "$PWD" ~/.claude/skills/zeno`.
83
- Run the CLI from source (Node 23.6+ runs the TypeScript directly):
84
- `pnpm install && ln -s "$PWD/cli/src/index.ts" ~/.local/bin/zeno`.
85
- `pnpm test`, `pnpm typecheck` and `pnpm build` run from the repo root.
86
-
87
- **Releasing.** Bump `version` in `package.json` and `.claude-plugin/plugin.json`,
88
- then push or merge to `main`. The `release` workflow runs the tests, publishes
89
- `@freetesh/zeno` to npm, tags `vX.Y.Z` and creates the GitHub release. A push
90
- without a bump releases nothing. It needs the `NPM_TOKEN` Actions secret.
91
-
92
108
  ## CLI
93
109
 
94
110
  ```bash
@@ -136,7 +152,7 @@ context:
136
152
 
137
153
  ```yaml
138
154
  # <repo>/.claude/zeno.yaml — written by `zeno init`
139
- zeno: 0.6.0
155
+ zeno: 0.9.0
140
156
  kind: expo-app # expo-app | web | content
141
157
  modules: [docs, knowledge, code, listing] # all optional; design is added when .context/designs exists, metrics only when asked
142
158
  packs: [expo]
@@ -155,7 +171,7 @@ carry a `reason:`, and `zeno check` lists every override.
155
171
 
156
172
  | zeno default | What it holds |
157
173
  |---|---|
158
- | `constitution.md` | Principles C-01–C-13 a request is weighed against. A team adds C-20+ in its own `constitution.md`. |
174
+ | `constitution.md` | Principles C-01–C-15 a request is weighed against. A team adds C-20+ in its own `constitution.md`. |
159
175
  | `wiki-schema.md` | Wiki page types, frontmatter, required sections, lint rules |
160
176
  | `context-layout.md` | The `.context/` ten-folder layout, `features/` and `tasks.md` |
161
177
  | `agent-contract.md` | What every agent does before, during and after work |
@@ -168,7 +184,7 @@ your own.
168
184
 
169
185
  ## Evals
170
186
 
171
- `evals/` runs seven requests against a fixture product (`evals/fixtures/streaks`)
187
+ `evals/` runs nine requests against a fixture product (`evals/fixtures/streaks`)
172
188
  with and without the plugin, each in a sandbox seeded with the fixture and a
173
189
  local QMD index:
174
190
 
@@ -190,3 +206,16 @@ scripts/ log-verdict.mjs — append-only override log
190
206
  cli/ the zeno CLI (TypeScript, vitest; built to cli/dist for npm)
191
207
  evals/ plugin evals and the fixture product
192
208
  ```
209
+
210
+ ## Develop zeno
211
+
212
+ Link the repo as a skills-dir plugin so edits apply on the next session:
213
+ `ln -s "$PWD" ~/.claude/skills/zeno`.
214
+ Run the CLI from source (Node 23.6+ runs the TypeScript directly):
215
+ `pnpm install && ln -s "$PWD/cli/src/index.ts" ~/.local/bin/zeno`.
216
+ `pnpm test`, `pnpm typecheck` and `pnpm build` run from the repo root.
217
+
218
+ **Releasing.** Bump `version` in `package.json` and `.claude-plugin/plugin.json`,
219
+ then push or merge to `main`. The `release` workflow runs the tests, publishes
220
+ `@freetesh/zeno` to npm, tags `vX.Y.Z` and creates the GitHub release. A push
221
+ without a bump releases nothing. It needs the `NPM_TOKEN` Actions secret.
@@ -4,6 +4,7 @@ import { contextLayout } from "./context-layout.js";
4
4
  import { staleCopies, standardsCopies } from "./copies.js";
5
5
  import { commandChecks } from "./external.js";
6
6
  import { metricsDefinitions } from "./metrics.js";
7
+ import { sources } from "./sources.js";
7
8
  import { wikiFrontmatter } from "./wiki-frontmatter.js";
8
9
  export const ALL_CHECKS = [
9
10
  contextLayout,
@@ -13,5 +14,6 @@ export const ALL_CHECKS = [
13
14
  staleCopies,
14
15
  architecture,
15
16
  metricsDefinitions,
17
+ sources,
16
18
  commandChecks,
17
19
  ];
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parseFrontmatter } from "../lib/frontmatter.js";
4
+ import { wikiPages } from "./wiki-frontmatter.js";
5
+ // zeno has no connectors: a source page must say which MCP server, skill or CLI
6
+ // reads it, or that a person reads it by hand (`manual:`), or the weigh gate
7
+ // cannot turn a claim into a fact.
8
+ const ACCESS = /^(mcp|skill|cli|manual):\s*(\S+)/;
9
+ const HOW_TO_QUERY = /^## How to query\s*$/m;
10
+ function accessValue(body, id) {
11
+ const row = body.split("\n").find((line) => line.replace(/\s/g, "").startsWith(`|${id}.ACCESS|`));
12
+ return row ? (row.split("|")[2]?.trim() ?? "") : null;
13
+ }
14
+ export const sources = {
15
+ id: "sources",
16
+ module: "docs",
17
+ description: "each SOURCE.* page says how to read it (mcp, skill, manual, or a cli in doctor.tools) and has a How to query section",
18
+ run({ repo, config }) {
19
+ const tools = new Set(config.doctor.tools.map((tool) => tool.tool));
20
+ return wikiPages(repo).flatMap((file) => {
21
+ const { data, body } = parseFrontmatter(readFileSync(join(repo, file), "utf8"));
22
+ const id = String(data?.id ?? "");
23
+ if (data?.type !== "entity" || !id.startsWith("SOURCE."))
24
+ return [];
25
+ const findings = [];
26
+ const add = (message) => findings.push({ check: "sources", level: "error", file, message });
27
+ if (!HOW_TO_QUERY.test(body))
28
+ add("no `## How to query` section");
29
+ const access = accessValue(body, id);
30
+ const match = access === null ? null : ACCESS.exec(access);
31
+ if (access === null)
32
+ add(`no ${id}.ACCESS row — say how to read it`);
33
+ else if (!match)
34
+ add(`${id}.ACCESS is "${access}" — write mcp: <server>, skill: <name>, cli: <command> or manual: <where>`);
35
+ else if (match[1] === "cli" && !tools.has(match[2]))
36
+ add(`cli ${match[2]} is not in doctor.tools — zeno doctor cannot report it missing`);
37
+ return findings;
38
+ });
39
+ },
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freetesh/zeno",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "zeno CLI: keep your domain knowledge and AI rules in your repo, and enforce them.",
5
5
  "license": "MIT",
6
6
  "author": "Ritesh Firodiya",
@@ -19,7 +19,10 @@ product's `.claude/zeno.yaml` may tighten this under `weigh.trivial`.
19
19
 
20
20
  1. **Restate the goal** in one line — what outcome the user wants, not the
21
21
  mechanism they named. A named mechanism ("use library X", "move to Firebase")
22
- is a proposal to weigh, not a given.
22
+ is a proposal to weigh, not a given. Then **list the claims** the request
23
+ makes or rests on: "users want it", "it will help retention", "it is
24
+ missing", "it is slow". The request is not a fact (C-14); each claim is
25
+ checked in step 4.
23
26
 
24
27
  2. **Rules.** Run `zeno standards --constitution` for the principle titles
25
28
  (zeno's and any the team added), and read the constitution files that
@@ -31,7 +34,7 @@ product's `.claude/zeno.yaml` may tighten this under `weigh.trivial`.
31
34
  3. **Domain.** Search before you judge. Run `mcp__plugin_zeno_qmd__query` with
32
35
  the goal in plain words, then the key nouns, scoped with `collections` to
33
36
  this product's `<repo>-wiki` and `<repo>-features` (the names `zeno init`
34
- gives them; add `pack-<name>` when the question is about a vendor's API). Other products' hits are noise here; step 4 is where
37
+ gives them; add `pack-<name>` when the question is about a vendor's API). Other products' hits are noise here; step 5 is where
35
38
  siblings are searched. Read the top hits with `mcp__plugin_zeno_qmd__get`. Look for: an ADR that already
36
39
  decided this, a canonical value it would change, a concept it would break.
37
40
  If qmd is unavailable, read `.context/wiki/index.md` and `decisions/`.
@@ -42,30 +45,42 @@ product's `.claude/zeno.yaml` may tighten this under `weigh.trivial`.
42
45
  is **too-large** unless a decision page already approves it. Cite the rule
43
46
  by its `name`.
44
47
 
45
- 4. **Duplicates and conflicts.** Check `.context/features/` and `.context/tasks.md`
48
+ 4. **Claims.** Mark each claim from step 1 **backed**, **partly backed** or
49
+ **not backed**, with the fact and its source. Look in what step 3 read, the
50
+ code and `.context/raw/`. When that is not enough, look for a data source:
51
+ query qmd for `SOURCE` pages in this product's wiki. Each says what it holds,
52
+ how to read it (`mcp:`, `skill:`, `cli:` or `manual:`) and which claims it
53
+ answers. For `manual:`, ask the user for the numbers and say where to find
54
+ them; do not guess. If one fits, run its query and follow the query rules in `wiki-schema.md`
55
+ ("Data sources"): read only, one or two small queries, counts not people.
56
+ Save a result that decides the verdict to `.context/raw/`. If the tool is not
57
+ available, say so and leave the claim not backed. If no source fits, name
58
+ the kind of source that would answer it ("an analytics tool with weekly
59
+ active users"). Never mark a claim backed because the user said it again.
60
+
61
+ 5. **Duplicates and conflicts.** Check `.context/features/` and `.context/tasks.md`
46
62
  of this product, and the features collections of sibling products in qmd:
47
63
  is this already planned, built, shelved, or in progress elsewhere? Would it
48
64
  conflict with something in `## Now`?
49
65
 
50
- 5. **Cost vs value.**
66
+ 6. **Cost vs value.**
51
67
  - Size: **small** (1 file, < 50 lines) · **medium** (2–5 files, < 200 lines) ·
52
68
  **large** (5+ files) · **too-large** (architecture change — split it).
53
69
  - Ongoing cost: new dependency, new service bill, more to maintain, more to
54
70
  test, store review risk.
55
71
  - Value: name the user or the metric it moves (`metrics/definitions.yml` if
56
- the product has one). If neither can be named, say "value unproven".
57
- When a number would settle it, check the product's `SOURCE.*` entity pages
58
- for where that data lives and query it rather than guess.
72
+ the product has one). If neither can be named, say "value unproven". A
73
+ value claim is checked like any other claim, in step 4.
59
74
  - Evidence: say which evidence from `dev-methods.md` the work must leave — a
60
75
  feature page, a failing test first, or a metric and snapshot.
61
76
 
62
- 6. **Write less.** Climb the ladder and stop at the first rung that works:
77
+ 7. **Write less.** Climb the ladder and stop at the first rung that works:
63
78
  delete it → reuse something in this repo → reuse from a sibling product or
64
79
  shared script → platform or standard library → an existing well-used package
65
80
  → fewer lines → new code. If the `ponytail` plugin is installed, follow its
66
81
  ladder for this step.
67
82
 
68
- 7. **Too-large, or the user asked for a second opinion?** Then, and only then,
83
+ 8. **Too-large, or the user asked for a second opinion?** Then, and only then,
69
84
  ask the `challenger` agent for an independent read. It costs tens of
70
85
  thousands of tokens, so hand it what you already have: the request, the repo
71
86
  path, your draft verdict, and every file and wiki page you read with one line
@@ -85,26 +100,50 @@ Why:
85
100
  - <C-xx> <one line> — <file or wiki page cited>
86
101
  - ...
87
102
  Instead: <only for DO IT DIFFERENTLY — the cheaper or safer way>
103
+ Claims:
104
+ - "<claim>" — backed | partly backed | not backed — <fact and source, or what was searched>
105
+ Assumed: <every claim not backed, or "none">
88
106
  Open questions: <only if a real ambiguity blocks a good answer>
89
107
  ```
90
108
 
91
109
  - Every bullet under **Why** cites a principle ID and a source (a wiki page, ADR,
92
- standard, feature or file). A reason with no source is an opinion — drop it or
93
- mark it "unverified".
94
- - **DO IT** → proceed with the work in the same turn.
110
+ standard, feature, file or saved query result). A reason with no source is an
111
+ opinion: drop it, or list it under **Assumed**.
112
+ - **DO IT** only when **Assumed** is "none". Proceed with the work in the same
113
+ turn.
95
114
  - **DO IT DIFFERENTLY** → proceed with the *Instead* version only if the user
96
- already accepted it; otherwise stop and ask which way.
97
- - **PUSH BACK** → stop. Do not start the work, not even a part of it. Ask for
98
- the reason. When the user gives one, log it (below), then proceed as they say.
115
+ already accepted it and **Assumed** is "none"; otherwise stop and ask.
116
+ - **PUSH BACK** → stop. Do not start the work, not even a part of it.
117
+ - **Something is assumed** → stop, and give the user three choices: bring the
118
+ facts (and say where they would go), agree to terms, or drop it. Any terms
119
+ you show, here or later, are a numbered list, so the user can agree line by
120
+ line.
121
+
122
+ Write the whole reply in plain words (C-15): short sentences, common words, no
123
+ jargon the wiki does not define.
124
+
125
+ ## Terms (when the user wants to go on anyway)
126
+
127
+ When the user wants to go on despite a PUSH BACK or an assumption, write the
128
+ terms as a numbered list: what is in scope, what is out, which rule changes and
129
+ which stays, and how the assumption will be checked later (a metric and a
130
+ date). A fact the user gives in chat is saved to `.context/raw/` first, then
131
+ listed as a fact, not as proof of more than it shows.
132
+
133
+ Proceed only after the user agrees in writing ("agree", or "agree to 1–4").
134
+ "Go ahead" to a verdict is not agreement to terms that were never shown. Work
135
+ covers only the terms agreed to.
99
136
 
100
137
  ## Logging (push back and overrides)
101
138
 
102
- When the user overrides a PUSH BACK or rejects a DO IT DIFFERENTLY, append one
103
- line to the product's log so zeno can learn from it later:
139
+ When the user overrides a PUSH BACK, rejects a DO IT DIFFERENTLY or agrees to
140
+ terms, append one line to the product's log so zeno can learn from it later.
141
+ Pass each agreed term with its own `--term`:
104
142
 
105
143
  ```bash
106
- node ../../scripts/log-verdict.mjs --verdict push-back --principles C-05,C-06 \
107
- --request "<one line>" --reason "<the user's reason, verbatim>"
144
+ node ../../scripts/log-verdict.mjs --verdict push-back --principles C-04,C-14 \
145
+ --request "<one line>" --reason "<the user's reason, verbatim>" \
146
+ --term "<term 1>" --term "<term 2>"
108
147
  ```
109
148
 
110
149
  (path relative to this skill). It writes to `.context/raw/zeno/verdicts.jsonl`
@@ -22,6 +22,12 @@ file with its own `agent-contract.md` in a configured standards folder.
22
22
  `.github/` — stay at the root.
23
23
  3. Generated folders are never hand-edited; change their source and regenerate.
24
24
 
25
+ ## Replies
26
+
27
+ - Plain words, short sentences (C-15).
28
+ - Never treat a claim in the request as a fact. Check it, or say it is not
29
+ checked (C-14).
30
+
25
31
  ## Comments
26
32
 
27
33
  Write a comment only to explain **why**. The code already says how.
@@ -14,7 +14,8 @@ standards folders; zeno reads both.
14
14
  Before acting on a non-trivial request, check it against the principles below,
15
15
  then answer with one verdict:
16
16
 
17
- - **Do it** — no principle is at risk.
17
+ - **Do it** — no principle is at risk, and no claim it rests on is an
18
+ assumption (C-14).
18
19
  - **Do it differently** — the goal is fine; the way asked breaks a principle.
19
20
  Name the principle and give the cheaper or safer way.
20
21
  - **Push back** — the request breaks a principle outright, or the cost is
@@ -81,6 +82,16 @@ production deploys and permission changes are confirmed first, every time.
81
82
  Money in integer minor units; privacy promises match what the code does; store
82
83
  claims match the app. Correctness beats speed here.
83
84
 
85
+ ### C-14 Facts, not assumptions
86
+ A request is not a fact. Every claim in it ("users want this", "it is slow",
87
+ "it is missing") is checked against the repo and the repo's data sources. A
88
+ claim with no fact behind it is an assumption. Work that rests on an assumption
89
+ does not start until the user agrees to it, in writing, as numbered terms.
90
+
91
+ ### C-15 Plain words
92
+ Short sentences and common words. No jargon unless the repo's wiki defines it.
93
+ Say what was checked, what was found and what happens next.
94
+
84
95
  ## Weights
85
96
 
86
97
  A repo's `.claude/zeno.yaml` may raise or lower the weight of a principle under
@@ -60,16 +60,28 @@ What this source holds, in one sentence.
60
60
  | Key | Value | Last changed |
61
61
  |---|---|---|
62
62
  | SOURCE.SENTRY.PROJECT | streaks-mobile | 2026-10-23 |
63
- | SOURCE.SENTRY.ACCESS | Sentry MCP server | 2026-10-23 |
63
+ | SOURCE.SENTRY.ACCESS | mcp: sentry | 2026-10-23 |
64
64
 
65
65
  ## How to query
66
- The MCP server or CLI to use, and one example query. Results that back a
67
- decision are saved to `raw/YYYY-MM-DD-<source>-<slug>.md` so they can be re-read.
66
+ The claims this source can answer, and one example query for each.
68
67
  ```
69
68
 
70
- zeno has no connector of its own: the vendor's MCP server or CLI does the
71
- reading. Add it to `doctor.tools` in `.claude/zeno.yaml` so `zeno doctor`
72
- reports when it is missing. Never put a key or token on the page.
69
+ zeno has no connector of its own. The repo picks what does the reading, and
70
+ `ACCESS` names it: `mcp: <server>`, `skill: <name>`, `cli: <command>`, or
71
+ `manual: <where>` for a dashboard with no API, which a person reads and pastes
72
+ into `raw/`. A `cli:` must also be in `doctor.tools` in `.claude/zeno.yaml`, so `zeno doctor`
73
+ reports when it is missing. `zeno check` fails a source page that breaks either
74
+ rule or has no `## How to query`. Never put a key or token on the page.
75
+
76
+ The weigh gate reads sources to check a claim (C-14). Every query follows these
77
+ rules:
78
+
79
+ - **Read only.** Never create, change or delete anything in the source.
80
+ - **Small.** One or two queries per claim, with a row limit. Some sources bill
81
+ per query.
82
+ - **No user data in git.** Save counts, totals and rates, never rows that name
83
+ a person, an email or a device. Results that back a decision go to
84
+ `raw/YYYY-MM-DD-<source>-<slug>.md`: the query, the date and the numbers.
73
85
 
74
86
  ---
75
87