@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +42 -21
- package/cli/dist/checks/sources.js +5 -4
- package/cli/dist/commands/learn.js +4 -3
- package/cli/dist/learn.js +12 -1
- package/package.json +1 -1
- package/skills/weigh/SKILL.md +24 -11
- package/standards/wiki-schema.md +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "zeno",
|
|
4
|
-
"version": "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
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src=".github/assets/icon.svg" width="96" height="96" alt="zeno icon">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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.
|
|
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-
|
|
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
|
|
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
|
|
7
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
package/skills/weigh/SKILL.md
CHANGED
|
@@ -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 `
|
|
53
|
-
|
|
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 (
|
|
137
|
+
## Logging (every verdict)
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 --
|
|
142
|
-
--request "<one line>"
|
|
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.
|
package/standards/wiki-schema.md
CHANGED
|
@@ -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
|
|
71
|
-
`
|
|
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
|
|