@freetesh/zeno 0.9.0 → 0.10.0

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.9.0",
4
+ "version": "0.10.0",
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
 
@@ -18,7 +36,8 @@ It has two halves that read the same config:
18
36
  constitution, your wiki and decision records, existing features and tasks,
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
- give a reason; overrides are logged and feed `zeno learn`.
39
+ give a reason. Every verdict is logged; `zeno learn` shows how often the gate
40
+ stops you and turns repeated overrides into proposals.
22
41
  - **No assumptions.** A request is not a fact. Each claim in it ("users want
23
42
  this", "it is slow") is checked against the repo and its data sources and
24
43
  marked backed or not. Work that rests on an assumption stops until you agree
@@ -87,17 +106,6 @@ starter `.dependency-cruiser.cjs` to your layers. `evals/fixtures/streaks` is a
87
106
  small product with a wiki and decision records, if you want to see the
88
107
  `.context/` layout before touching your own repo.
89
108
 
90
- For local development of zeno itself, link the repo as a skills-dir plugin so
91
- edits apply on the next session: `ln -s "$PWD" ~/.claude/skills/zeno`.
92
- Run the CLI from source (Node 23.6+ runs the TypeScript directly):
93
- `pnpm install && ln -s "$PWD/cli/src/index.ts" ~/.local/bin/zeno`.
94
- `pnpm test`, `pnpm typecheck` and `pnpm build` run from the repo root.
95
-
96
- **Releasing.** Bump `version` in `package.json` and `.claude-plugin/plugin.json`,
97
- then push or merge to `main`. The `release` workflow runs the tests, publishes
98
- `@freetesh/zeno` to npm, tags `vX.Y.Z` and creates the GitHub release. A push
99
- without a bump releases nothing. It needs the `NPM_TOKEN` Actions secret.
100
-
101
109
  ## CLI
102
110
 
103
111
  ```bash
@@ -145,7 +153,7 @@ context:
145
153
 
146
154
  ```yaml
147
155
  # <repo>/.claude/zeno.yaml — written by `zeno init`
148
- zeno: 0.6.0
156
+ zeno: 0.9.0
149
157
  kind: expo-app # expo-app | web | content
150
158
  modules: [docs, knowledge, code, listing] # all optional; design is added when .context/designs exists, metrics only when asked
151
159
  packs: [expo]
@@ -164,7 +172,7 @@ carry a `reason:`, and `zeno check` lists every override.
164
172
 
165
173
  | zeno default | What it holds |
166
174
  |---|---|
167
- | `constitution.md` | Principles C-01–C-13 a request is weighed against. A team adds C-20+ in its own `constitution.md`. |
175
+ | `constitution.md` | Principles C-01–C-15 a request is weighed against. A team adds C-20+ in its own `constitution.md`. |
168
176
  | `wiki-schema.md` | Wiki page types, frontmatter, required sections, lint rules |
169
177
  | `context-layout.md` | The `.context/` ten-folder layout, `features/` and `tasks.md` |
170
178
  | `agent-contract.md` | What every agent does before, during and after work |
@@ -195,7 +203,20 @@ hooks/ session start: constitution titles, qmd refresh
195
203
  standards/ default standards
196
204
  packs/ vendor doc packs
197
205
  templates/ files init writes: dependency-cruiser base (synced) and starters
198
- scripts/ log-verdict.mjs — append-only override log
206
+ scripts/ log-verdict.mjs — append-only verdict log
199
207
  cli/ the zeno CLI (TypeScript, vitest; built to cli/dist for npm)
200
208
  evals/ plugin evals and the fixture product
201
209
  ```
210
+
211
+ ## Develop zeno
212
+
213
+ Link the repo as a skills-dir plugin so edits apply on the next session:
214
+ `ln -s "$PWD" ~/.claude/skills/zeno`.
215
+ Run the CLI from source (Node 23.6+ runs the TypeScript directly):
216
+ `pnpm install && ln -s "$PWD/cli/src/index.ts" ~/.local/bin/zeno`.
217
+ `pnpm test`, `pnpm typecheck` and `pnpm build` run from the repo root.
218
+
219
+ **Releasing.** Bump `version` in `package.json` and `.claude-plugin/plugin.json`,
220
+ then push or merge to `main`. The `release` workflow runs the tests, publishes
221
+ `@freetesh/zeno` to npm, tags `vX.Y.Z` and creates the GitHub release. A push
222
+ without a bump releases nothing. It needs the `NPM_TOKEN` Actions secret.
@@ -3,8 +3,9 @@ import { join } from "node:path";
3
3
  import { parseFrontmatter } from "../lib/frontmatter.js";
4
4
  import { wikiPages } from "./wiki-frontmatter.js";
5
5
  // zeno has no connectors: a source page must say which MCP server, skill or CLI
6
- // reads it, or the weigh gate cannot turn a claim into a fact.
7
- const ACCESS = /^(mcp|skill|cli):\s*(\S+)/;
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+)/;
8
9
  const HOW_TO_QUERY = /^## How to query\s*$/m;
9
10
  function accessValue(body, id) {
10
11
  const row = body.split("\n").find((line) => line.replace(/\s/g, "").startsWith(`|${id}.ACCESS|`));
@@ -13,7 +14,7 @@ function accessValue(body, id) {
13
14
  export const sources = {
14
15
  id: "sources",
15
16
  module: "docs",
16
- description: "each SOURCE.* page says how to read it (mcp, skill or a cli in doctor.tools) and has a How to query section",
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",
17
18
  run({ repo, config }) {
18
19
  const tools = new Set(config.doctor.tools.map((tool) => tool.tool));
19
20
  return wikiPages(repo).flatMap((file) => {
@@ -30,7 +31,7 @@ export const sources = {
30
31
  if (access === null)
31
32
  add(`no ${id}.ACCESS row — say how to read it`);
32
33
  else if (!match)
33
- add(`${id}.ACCESS is "${access}" — write mcp: <server>, skill: <name> or cli: <command>`);
34
+ add(`${id}.ACCESS is "${access}" — write mcp: <server>, skill: <name>, cli: <command> or manual: <where>`);
34
35
  else if (match[1] === "cli" && !tools.has(match[2]))
35
36
  add(`cli ${match[2]} is not in doctor.tools — zeno doctor cannot report it missing`);
36
37
  return findings;
@@ -1,11 +1,11 @@
1
- import { proposals, readDecisions, readVerdicts, recordDecision } from "../learn.js";
1
+ import { proposals, readDecisions, readVerdicts, recordDecision, summarise } from "../learn.js";
2
2
  import { findProducts } from "../run.js";
3
3
  import { requireEstate } from "./env.js";
4
4
  const DEFAULT_THRESHOLD = 3;
5
5
  export function registerLearn(program, env) {
6
6
  program
7
7
  .command("learn")
8
- .description("turn overridden verdicts across products into proposals to change the rules")
8
+ .description("show how often the gate stops work, and turn overrides across products into proposals to change the rules")
9
9
  .option("--threshold <n>", "overrides of one principle before it is proposed", String(DEFAULT_THRESHOLD))
10
10
  .option("--approve <id>", "mark a proposal approved (then edit the standard)")
11
11
  .option("--reject <id>", "mark a proposal rejected; it returns only with new evidence")
@@ -23,7 +23,8 @@ export function registerLearn(program, env) {
23
23
  console.log(`${options.approve ? "approved" : "rejected"} ${target}`);
24
24
  return;
25
25
  }
26
- console.log(`${entries.length} overridden verdict(s) logged across the estate`);
26
+ const s = summarise(entries);
27
+ console.log(`${s.total} verdict(s) logged: ${s.proceeded} went ahead, ${s.stopped} stopped, ${s.overridden} overridden, ${s.dropped} dropped`);
27
28
  if (found.length === 0)
28
29
  console.log("no proposals");
29
30
  for (const proposal of found) {
package/cli/dist/learn.js CHANGED
@@ -1,5 +1,14 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
+ export const OUTCOMES = ["proceeded", "stopped", "overridden", "dropped"];
4
+ const outcomeOf = (entry) => entry.outcome ?? "overridden";
5
+ /** How often the gate let work through, stopped it, or was overridden. */
6
+ export function summarise(entries) {
7
+ const counts = Object.fromEntries(OUTCOMES.map((o) => [o, 0]));
8
+ for (const entry of entries)
9
+ counts[outcomeOf(entry)] += 1;
10
+ return { total: entries.length, ...counts };
11
+ }
3
12
  const LOG = join(".context", "raw", "zeno", "verdicts.jsonl");
4
13
  const MAX_REASONS = 5;
5
14
  function readJsonLines(path) {
@@ -41,7 +50,9 @@ function toProposal(id, summary, entries) {
41
50
  * times, or one reason given in two or more products. A decided proposal comes
42
51
  * back only when its count has grown past what it was when decided.
43
52
  */
44
- export function proposals(entries, options) {
53
+ export function proposals(all, options) {
54
+ // Only an override says a rule may be wrong; a verdict the user followed says nothing about it.
55
+ const entries = all.filter((entry) => outcomeOf(entry) === "overridden");
45
56
  const found = [];
46
57
  for (const [principle, group_] of group(entries, (e) => e.principles)) {
47
58
  if (group_.length >= options.threshold) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freetesh/zeno",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
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",
@@ -49,8 +49,9 @@ product's `.claude/zeno.yaml` may tighten this under `weigh.trivial`.
49
49
  **not backed**, with the fact and its source. Look in what step 3 read, the
50
50
  code and `.context/raw/`. When that is not enough, look for a data source:
51
51
  query qmd for `SOURCE` pages in this product's wiki. Each says what it holds,
52
- how to read it (`mcp:`, `skill:` or `cli:`) and which claims it answers. If
53
- one fits, run its query and follow the query rules in `wiki-schema.md`
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`
54
55
  ("Data sources"): read only, one or two small queries, counts not people.
55
56
  Save a result that decides the verdict to `.context/raw/`. If the tool is not
56
57
  available, say so and leave the claim not backed. If no source fits, name
@@ -114,7 +115,9 @@ Open questions: <only if a real ambiguity blocks a good answer>
114
115
  already accepted it and **Assumed** is "none"; otherwise stop and ask.
115
116
  - **PUSH BACK** → stop. Do not start the work, not even a part of it.
116
117
  - **Something is assumed** → stop, and give the user three choices: bring the
117
- facts (and say where they would go), agree to terms, or drop it.
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.
118
121
 
119
122
  Write the whole reply in plain words (C-15): short sentences, common words, no
120
123
  jargon the wiki does not define.
@@ -131,17 +134,27 @@ Proceed only after the user agrees in writing ("agree", or "agree to 1–4").
131
134
  "Go ahead" to a verdict is not agreement to terms that were never shown. Work
132
135
  covers only the terms agreed to.
133
136
 
134
- ## Logging (push back and overrides)
137
+ ## Logging (every verdict)
135
138
 
136
- When the user overrides a PUSH BACK, rejects a DO IT DIFFERENTLY or agrees to
137
- terms, append one line to the product's log so zeno can learn from it later.
138
- Pass each agreed term with its own `--term`:
139
+ Log every verdict once, when you know what happened next. A trivial request
140
+ that skipped the gate is not logged. `--outcome` is one of:
141
+
142
+ - `proceeded` — work went ahead as the verdict said (DO IT, or an accepted
143
+ *Instead*).
144
+ - `stopped` — you stopped and asked, and the user has not answered yet. Log it
145
+ when you stop; if the user later overrides, log a second line.
146
+ - `overridden` — the user went on against a PUSH BACK, rejected a DO IT
147
+ DIFFERENTLY, or agreed to terms. `--reason` is required: the user's reason,
148
+ verbatim. Pass each agreed term with its own `--term`.
149
+ - `dropped` — the user chose to drop the request.
139
150
 
140
151
  ```bash
141
- node ../../scripts/log-verdict.mjs --verdict push-back --principles C-04,C-14 \
142
- --request "<one line>" --reason "<the user's reason, verbatim>" \
143
- --term "<term 1>" --term "<term 2>"
152
+ node ../../scripts/log-verdict.mjs --verdict push-back --outcome overridden \
153
+ --principles C-04,C-14 --request "<one line>" \
154
+ --reason "<the user's reason, verbatim>" --term "<term 1>" --term "<term 2>"
144
155
  ```
145
156
 
146
157
  (path relative to this skill). It writes to `.context/raw/zeno/verdicts.jsonl`
147
- in the current repo. Never edit or delete existing lines.
158
+ in the current repo. Never edit or delete existing lines. `zeno learn` reads
159
+ the log across products: how often the gate stops work, and which rules keep
160
+ being overridden.
@@ -67,8 +67,9 @@ The claims this source can answer, and one example query for each.
67
67
  ```
68
68
 
69
69
  zeno has no connector of its own. The repo picks what does the reading, and
70
- `ACCESS` names it: `mcp: <server>`, `skill: <name>` or `cli: <command>`. A
71
- `cli:` must also be in `doctor.tools` in `.claude/zeno.yaml`, so `zeno doctor`
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`
72
73
  reports when it is missing. `zeno check` fails a source page that breaks either
73
74
  rule or has no `## How to query`. Never put a key or token on the page.
74
75