@freetesh/zeno 0.0.0-stage → 0.8.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.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +10 -0
  3. package/LICENSE +21 -0
  4. package/README.md +191 -2
  5. package/cli/dist/checks/architecture.js +126 -0
  6. package/cli/dist/checks/canonical-values.js +46 -0
  7. package/cli/dist/checks/context-layout.js +42 -0
  8. package/cli/dist/checks/copies.js +73 -0
  9. package/cli/dist/checks/external.js +30 -0
  10. package/cli/dist/checks/index.js +17 -0
  11. package/cli/dist/checks/metrics.js +14 -0
  12. package/cli/dist/checks/types.js +1 -0
  13. package/cli/dist/checks/wiki-frontmatter.js +74 -0
  14. package/cli/dist/commands/add.js +24 -0
  15. package/cli/dist/commands/check.js +19 -0
  16. package/cli/dist/commands/doctor.js +16 -0
  17. package/cli/dist/commands/env.js +6 -0
  18. package/cli/dist/commands/init.js +78 -0
  19. package/cli/dist/commands/learn.js +35 -0
  20. package/cli/dist/commands/standards.js +26 -0
  21. package/cli/dist/commands/status.js +11 -0
  22. package/cli/dist/commands/sync.js +23 -0
  23. package/cli/dist/commands/ui.js +64 -0
  24. package/cli/dist/commands/weigh.js +16 -0
  25. package/cli/dist/config/load.js +96 -0
  26. package/cli/dist/config/schema.js +72 -0
  27. package/cli/dist/doctor.js +31 -0
  28. package/cli/dist/format.js +37 -0
  29. package/cli/dist/index.js +36 -0
  30. package/cli/dist/init.js +120 -0
  31. package/cli/dist/learn.js +69 -0
  32. package/cli/dist/lib/files.js +23 -0
  33. package/cli/dist/lib/frontmatter.js +16 -0
  34. package/cli/dist/lib/system.js +19 -0
  35. package/cli/dist/packs.js +85 -0
  36. package/cli/dist/run.js +45 -0
  37. package/cli/dist/standards.js +42 -0
  38. package/cli/dist/status.js +27 -0
  39. package/cli/dist/sync.js +30 -0
  40. package/cli/dist/ui.js +69 -0
  41. package/package.json +50 -4
  42. package/packs/expo/pack.yaml +5 -0
  43. package/packs/firebase/pack.yaml +5 -0
  44. package/packs/nextjs/pack.yaml +5 -0
  45. package/packs/react/pack.yaml +5 -0
  46. package/packs/revenuecat/pack.yaml +5 -0
  47. package/packs/uniwind/pack.yaml +5 -0
  48. package/skills/ask/SKILL.md +42 -0
  49. package/skills/decision/SKILL.md +33 -0
  50. package/skills/ingest/SKILL.md +45 -0
  51. package/skills/standards/SKILL.md +16 -0
  52. package/skills/weigh/SKILL.md +111 -0
  53. package/skills/wiki-lint/SKILL.md +69 -0
  54. package/standards/README.md +14 -0
  55. package/standards/agent-contract.md +40 -0
  56. package/standards/constitution.md +89 -0
  57. package/standards/context-layout.md +138 -0
  58. package/standards/dev-methods.md +29 -0
  59. package/standards/wiki-schema.md +287 -0
  60. package/templates/dependency-cruiser/app.cjs +23 -0
  61. package/templates/dependency-cruiser/base.cjs +52 -0
  62. package/templates/dependency-cruiser/package.cjs +31 -0
  63. package/templates/dependency-cruiser/root.cjs +15 -0
@@ -0,0 +1,29 @@
1
+ # Development methods
2
+
3
+ zeno does not run a method; it checks that the method left evidence a later
4
+ reader (or the weigh gate) can follow. A team replaces this file with its own
5
+ `dev-methods.md` in a configured standards folder.
6
+
7
+ | Method | The evidence it leaves |
8
+ |---|---|
9
+ | Spec-driven | A `features/` page with acceptance scenarios, written before the code. Code and tests cite its ID (`FEAT-0031`) or a wiki ID (`FLOW.…`, `DECISION.…`). |
10
+ | Test-driven | A test that failed before the change and passes after. Bug fixes start with the failing test. The test path goes in the feature page or decision. |
11
+ | Data-driven | A named metric in `metrics/definitions.yml`, the query that measured it, and a dated snapshot in `raw/`. The decision cites the snapshot, not a memory of a dashboard. |
12
+
13
+ ## Which one, when
14
+
15
+ - **New behaviour users see** → spec-driven: the feature page comes first.
16
+ - **Logic that can break** (money, parsing, data writes, a bug fix) → test-driven.
17
+ - **A bet on what users want** → data-driven: an experiment page with its metric
18
+ before the build, the result as a decision after.
19
+
20
+ They combine. A paywall change is a spec (what users see), tests (the price
21
+ logic) and an experiment (does conversion move).
22
+
23
+ ## Rules
24
+
25
+ 1. No evidence, not done. "It works" without a test, a scenario or a number is
26
+ an opinion.
27
+ 2. Evidence lives in the repo (C-03). A dashboard link alone is not evidence —
28
+ it changes; the snapshot in `raw/` does not.
29
+ 3. When a method keeps being skipped, make it a check (C-09), not a longer doc.
@@ -0,0 +1,287 @@
1
+ # Wiki — schema and rules
2
+
3
+ This folder is a **Claude-maintained knowledge base** for the product. The model is the LLM-Wiki pattern: `.context/raw/` holds immutable sources, `.context/wiki/` holds synthesised pages with `[[wikilinks]]`, this file defines the schema.
4
+
5
+ **Read this file before creating or editing any page under `.context/wiki/`.**
6
+
7
+ ---
8
+
9
+ ## Three layers
10
+
11
+ | Layer | Path | Mutability | Who writes |
12
+ |---|---|---|---|
13
+ | Raw | `.context/raw/`, `.context/designs/` | **Immutable** — never edit | Humans (or external systems) |
14
+ | Wiki | `.context/wiki/` | Mutable, schema-bound | Claude (via `/zeno:ingest`, `/zeno:ask`, `/zeno:wiki-lint`) |
15
+ | Index | `.context/wiki/index.md` | Auto-maintained | `/zeno:ingest` and `/zeno:wiki-lint` |
16
+
17
+ The wiki is the **only** layer Claude reads for `/zeno:ask`. Never answer from `.context/raw/` directly — the synthesis must be written down or it doesn't exist.
18
+
19
+ ---
20
+
21
+ ## Page types
22
+
23
+ Every page in `.context/wiki/` belongs to exactly one type. The folder name is the type.
24
+
25
+ | Type | Folder | What it owns | Example |
26
+ |---|---|---|---|
27
+ | `concept` | `concepts/` | An idea, principle, or invariant. Prose-shaped. | `offline-first`, `money-in-minor-units` |
28
+ | `entity` | `entities/` | A thing with canonical values (price, name, enum). Table-shaped. | `plan-pro`, `plan-free` |
29
+ | `surface` | `surfaces/` | One screen / route / email / callable. Links to a raw wireframe. | `onboarding-welcome` |
30
+ | `flow` | `flows/` | A user journey. Ordered list of `[[surface]]` steps. | `new-user-to-first-save` |
31
+ | `decision` | `decisions/` | One ADR. Filename = `YYYY-MM-DD-slug.md`. Append-only. | `2026-06-24-annual-plan-only` |
32
+ | `synthesis` | `synthesis/` | Cross-cutting page built from many entities/concepts. Refreshable. | `pricing`, `launch-checklist` |
33
+
34
+ A page cannot be two types. If it feels like it is, it's two pages — split it.
35
+
36
+ A product adds a type in `.claude/zeno.yaml`, with the reason and the `## `
37
+ sections every page of it must have:
38
+
39
+ ```yaml
40
+ wiki:
41
+ types:
42
+ - { type: postmortem, folder: postmortems, reason: …, sections: [Summary, Timeline, Root cause, Fix, Sources] }
43
+ ```
44
+
45
+ `zeno check` fails a page of that type that misses a section. Its ID is
46
+ `<TYPE>.<SLUG>` (`POSTMORTEM.MIDNIGHT-RESET`). The six built-in types and their
47
+ folders cannot be redefined. Try the six first — an added type is a shape every
48
+ reader and every product must learn.
49
+
50
+ ### Data sources
51
+
52
+ Each tool that holds product data (Datadog, Sentry, Snowflake, an analytics
53
+ console) is one `entity` page, `id: SOURCE.<NAME>`, in `entities/`:
54
+
55
+ ```markdown
56
+ ## Summary
57
+ What this source holds, in one sentence.
58
+
59
+ ## Canonical values
60
+ | Key | Value | Last changed |
61
+ |---|---|---|
62
+ | SOURCE.SENTRY.PROJECT | streaks-mobile | 2026-10-23 |
63
+ | SOURCE.SENTRY.ACCESS | Sentry MCP server | 2026-10-23 |
64
+
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.
68
+ ```
69
+
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.
73
+
74
+ ---
75
+
76
+ ## Frontmatter (mandatory)
77
+
78
+ Every page begins with YAML frontmatter:
79
+
80
+ ```yaml
81
+ ---
82
+ id: PLAN.PRO # Stable, uppercase, dot-separated. Never changes. Referenced from code.
83
+ type: entity # concept | entity | surface | flow | decision | synthesis
84
+ status: canonical # canonical | draft | superseded
85
+ last-verified: 2026-06-24 # ISO date. /zeno:wiki-lint flags pages > 90 days.
86
+ supersedes: [] # IDs this page replaces (decisions only)
87
+ superseded-by: null # If non-null, this page is no longer canonical
88
+ ---
89
+ ```
90
+
91
+ ### ID conventions
92
+
93
+ | Type | ID shape | Example |
94
+ |---|---|---|
95
+ | concept | `CONCEPT.<SLUG>` | `CONCEPT.OFFLINE-FIRST` |
96
+ | entity | `<DOMAIN>.<NAME>` | `PLAN.PRO`, `PLAN.FREE` |
97
+ | surface | `SURFACE.<APP>.<PATH>` | `SURFACE.MOBILE.ONBOARDING.WELCOME` |
98
+ | flow | `FLOW.<SLUG>` | `FLOW.NEW-USER-TO-FIRST-SAVE` |
99
+ | decision | `DECISION.<YYYY-MM-DD>.<SLUG>` | `DECISION.2026-06-24.ANNUAL-PLAN-ONLY` |
100
+ | synthesis | `SYNTHESIS.<SLUG>` | `SYNTHESIS.PRICING` |
101
+
102
+ Once assigned, an ID never changes. If a page is renamed, the ID stays. If the thing it describes is killed, mark `status: superseded` and set `superseded-by`; do not delete.
103
+
104
+ ---
105
+
106
+ ## Page body — required sections
107
+
108
+ Each type has a required section list. `/zeno:wiki-lint` fails the page if any are missing.
109
+
110
+ ### `concept`
111
+ ```markdown
112
+ ## Summary
113
+ One sentence — what the concept is.
114
+
115
+ ## Why it matters
116
+ Why this is a load-bearing idea. Tie it to a constraint, an incident, or a principle.
117
+
118
+ ## Implications
119
+ Bulleted: what this forces / forbids in practice.
120
+
121
+ ## Related
122
+ - [[other-concept]] — relationship
123
+ - [[entity-x]] — where this applies
124
+
125
+ ## Sources
126
+ - .context/raw/<source> or .context/designs/<surface>/<flow>/<screen>.html
127
+ ```
128
+
129
+ ### `entity`
130
+ ```markdown
131
+ ## Summary
132
+ One sentence — what this entity is.
133
+
134
+ ## Canonical values
135
+ | Key | Value | Last changed |
136
+ |---|---|---|
137
+ | PRICE.PRO.YEARLY | $49 | 2026-04-12 |
138
+ | STORAGE.PRO | 5 GB | 2026-04-12 |
139
+
140
+ ## Related
141
+ - [[concept-x]] — concept this entity instantiates
142
+ - [[entity-y]] — sibling / upgrade-from
143
+
144
+ ## Sources
145
+ - .context/raw/<source>
146
+ ```
147
+
148
+ The `Canonical values` table is the single source of truth. Any value here that disagrees with the codebase is a bug — fix the code or update this page.
149
+
150
+ ### `surface`
151
+ ```markdown
152
+ ## Summary
153
+ One sentence — what this screen does.
154
+
155
+ ## Raw wireframe
156
+ - .context/designs/<surface>/<flow>/<screen>.html
157
+
158
+ ## Copy
159
+ Verbatim user-visible strings. Each must pass the [[copy-voice]] rules.
160
+
161
+ ## Flows that touch this surface
162
+ - [[new-user-to-first-save]] — step 4
163
+
164
+ ## Entities referenced
165
+ - [[plan-free]]
166
+ - [[privacy-consent]]
167
+
168
+ ## Sources
169
+ - .context/raw/<source>
170
+ ```
171
+
172
+ ### `flow`
173
+ ```markdown
174
+ ## Summary
175
+ One sentence — what journey this is.
176
+
177
+ ## Audience
178
+ end-user | admin
179
+
180
+ ## Entry
181
+ Where the user arrives from.
182
+
183
+ ## Steps
184
+ | # | Step | Surface | Notes |
185
+ |---|---|---|---|
186
+ | 1 | Welcome | [[welcome]] | First launch |
187
+ | 2 | Sign up | [[sign-up]] | Email |
188
+ | ... |
189
+
190
+ ## Exit
191
+ The success state.
192
+
193
+ ## Escapes
194
+ Back-button, skips, abandonment outcomes.
195
+
196
+ ## Sources
197
+ - .context/raw/<source>
198
+ ```
199
+
200
+ ### `decision`
201
+ ```markdown
202
+ ## Decision
203
+ One-line statement.
204
+
205
+ ## Why
206
+ The motivation. Cite the trigger — incident, user feedback, regulator, scope cut.
207
+
208
+ ## Impact
209
+ What changes downstream. Which [[entities]] / [[surfaces]] / [[concepts]] update.
210
+
211
+ ## Status
212
+ Active | Superseded by [[decision-id]]
213
+
214
+ ## Sources
215
+ - .context/raw/<source>
216
+ ```
217
+
218
+ Decisions are append-only. To reverse a decision, write a new one with `supersedes: [DECISION.<old>]` and mark the old one's `superseded-by`.
219
+
220
+ ### `synthesis`
221
+ ```markdown
222
+ ## Summary
223
+ What this synthesis combines.
224
+
225
+ ## Built from
226
+ - [[entity-x]]
227
+ - [[entity-y]]
228
+ - [[concept-z]]
229
+
230
+ ## Body
231
+ The cross-cutting answer. Every claim links back to a source page.
232
+
233
+ ## Last refresh
234
+ 2026-06-24 — refresh when any "Built from" page changes.
235
+
236
+ ## Sources
237
+ - (synthesis pages cite wiki pages, not raw files)
238
+ ```
239
+
240
+ Synthesis pages are **derived**. If a source page changes, `/zeno:wiki-lint` marks the synthesis stale.
241
+
242
+ ---
243
+
244
+ ## Linking
245
+
246
+ - Use `[[page-slug]]` — the filename without `.md`. Match the file exactly (case-sensitive on case-sensitive filesystems).
247
+ - Cross-folder links use the slug, not the path: `[[tier-self]]`, not `[[entities/tier-self]]`.
248
+ - An `[[unknown-link]]` is a TODO marker — `/zeno:wiki-lint` reports it. Never silently delete a broken link; either create the target page or rename the link.
249
+ - The `Related` section is what makes the graph navigable. Every page must have at least two outbound links (or be flagged as orphan-source).
250
+
251
+ ---
252
+
253
+ ## Lint rules (what `/zeno:wiki-lint` enforces)
254
+
255
+ | Rule | Failure |
256
+ |---|---|
257
+ | Missing required section | Block — must be fixed |
258
+ | `[[wikilink]]` to a non-existent page | Block — create target or rename link |
259
+ | Orphan page (zero inbound links) | Warn |
260
+ | Two entities claim the same `id` | Block |
261
+ | Two entities have contradictory canonical values for the same key | Block |
262
+ | `last-verified` > 90 days | Warn |
263
+ | `status: superseded` without `superseded-by` | Block |
264
+ | Source file in `.context/raw/` not referenced by any wiki page | Warn |
265
+ | Synthesis page where a `Built from` page changed after `Last refresh` | Warn |
266
+ | Filename does not match the `id`'s slug portion | Block |
267
+
268
+ `/zeno:wiki-lint --fix` auto-fixes: filename ↔ id mismatches, missing `last-verified` (sets to today if the page is touched), index regeneration.
269
+
270
+ ---
271
+
272
+ ## What the wiki is NOT for
273
+
274
+ - **Not for code documentation.** Docstrings stay in code. The wiki is product-shaped, not API-shaped.
275
+ - **Not for transient task tracking.** Use `TaskCreate` / PR descriptions / commit messages for ephemeral work.
276
+ - **Not for chat logs.** If a chat produces a decision, write a `decision` page; throw the chat away.
277
+ - **Not for drafts of code.** If it would be a comment, it's a comment.
278
+
279
+ ---
280
+
281
+ ## Source of truth
282
+
283
+ `.context/wiki/` is the canonical knowledge base, and every page in it follows
284
+ the schema above — there are no plan-shaped exceptions. What is *next* lives in
285
+ `.context/features/`, and the ordered queue lives in `.context/tasks.md`; the
286
+ wiki describes what is, not what is planned. The repo root holds no
287
+ product-knowledge `.md` files.
@@ -0,0 +1,23 @@
1
+ /* global module */
2
+ // This app's architecture rules, enforced by `zeno check` (architecture module).
3
+ // zeno wrote this starter once; it is yours now. Add the layers this app has
4
+ // and keep the why in a wiki concept page.
5
+ // Rule reference: https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md
6
+
7
+ /** @type {import('dependency-cruiser').IConfiguration} */
8
+ module.exports = {
9
+ extends: "../../.dependency-cruiser.base.cjs",
10
+ forbidden: [
11
+ {
12
+ name: "no-app-to-app",
13
+ comment: "An app never imports another app; share code through packages/.",
14
+ severity: "error",
15
+ from: {},
16
+ to: { path: "^\\.\\./[^./]" },
17
+ },
18
+ ],
19
+ options: {
20
+ // The level `zeno ui` draws the architecture graph at: one box per match.
21
+ reporterOptions: { archi: { collapsePattern: "^src/[^/]+/[^/]+|^src/[^/]+|^[^/.][^/]*|^\\.\\./\\.\\./packages/[^/]+" } },
22
+ },
23
+ };
@@ -0,0 +1,52 @@
1
+ /* global module, require */
2
+ // Managed by zeno — `zeno sync` rewrites this file from zeno's template, so edits here are lost.
3
+ // Put this repo's own rules in the .dependency-cruiser.cjs files that extend it.
4
+ // Each workspace runs on its own because apps map `@/` to their own src, and
5
+ // dependency-cruiser reads one tsconfig per run.
6
+
7
+ const { existsSync } = require("node:fs");
8
+
9
+ // recommended's no-orphans exceptions (dot files, .d.ts, tsconfig, babel/webpack configs),
10
+ // restated because the package does not export its rule files…
11
+ const KNOWN_CONFIG_FILES = [
12
+ "(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$",
13
+ "\\.d\\.(c|m)?ts$",
14
+ "(^|/)tsconfig\\.json$",
15
+ "(^|/)(?:babel|webpack)\\.config\\.(?:js|cjs|mjs|ts|json)$",
16
+ ];
17
+ // …plus files a framework or tool loads by convention, so nothing imports them.
18
+ const ENTRY_POINTS = [
19
+ "(^|/)src/app/[^.]*(page|layout|route|loading|error|not-found|template|default)\\.tsx?$", // Next.js app router
20
+ "(^|/)src/pages/[^.]+\\.tsx?$", // Next.js pages router
21
+ "^src/app/[^.]+\\.tsx?$", // Expo Router: every file under app/ is a route
22
+ "^src/(instrumentation|middleware)\\.ts$", // Next.js hooks
23
+ "^(scripts|plugins|eslint|e2e|public)/", // run by a tool, not imported
24
+ "(^|/)__mocks__/", // jest mocks
25
+ "^(src/)?index\\.ts$", // a package's entry
26
+ "(^|/)codegen\\.ts$", // GraphQL codegen script
27
+ ];
28
+
29
+ /** @type {import('dependency-cruiser').IConfiguration} */
30
+ module.exports = {
31
+ extends: "dependency-cruiser/configs/recommended",
32
+ forbidden: [
33
+ {
34
+ name: "no-orphans",
35
+ comment: "Nothing imports this file: use it or delete it. Add a real entry point to ENTRY_POINTS instead.",
36
+ severity: "warn",
37
+ from: { orphan: true, pathNot: [...KNOWN_CONFIG_FILES, ...ENTRY_POINTS] },
38
+ to: {},
39
+ },
40
+ ],
41
+ options: {
42
+ // Record imports into a sibling workspace, but leave cruising it to that workspace's own run.
43
+ doNotFollow: { path: ["node_modules", "^\\.\\./"] },
44
+ // Specs and tooling configs are not app code.
45
+ exclude: { path: "^(dist|build|out|generated|coverage|\\.next|\\.expo|\\.turbo)/|^tests/|\\.(spec|test)\\.tsx?$|(^|/)[^/]+\\.config\\.(c|m)?(j|t)s$|(^|/)\\.dependency-cruiser" },
46
+ tsPreCompilationDeps: true,
47
+ // Each run starts in its workspace, so this is that workspace's tsconfig (if it has one).
48
+ ...(existsSync("tsconfig.json") ? { tsConfig: { fileName: "tsconfig.json" } } : {}),
49
+ // Packages like uuid 13 export only `node`/`default` conditions.
50
+ enhancedResolveOptions: { exportsFields: ["exports"], conditionNames: ["import", "require", "node", "default", "types"] },
51
+ },
52
+ };
@@ -0,0 +1,31 @@
1
+ /* global module */
2
+ // This package's architecture rules, enforced by `zeno check` (architecture module).
3
+ // zeno wrote this starter once; it is yours now.
4
+
5
+ /** @type {import('dependency-cruiser').IConfiguration} */
6
+ module.exports = {
7
+ extends: "../../.dependency-cruiser.base.cjs",
8
+ forbidden: [
9
+ {
10
+ name: "no-package-to-app",
11
+ comment: "packages/ are shared; they never depend on an app.",
12
+ severity: "error",
13
+ from: {},
14
+ to: { path: "^\\.\\./\\.\\./apps/" },
15
+ },
16
+ {
17
+ name: "no-orphans",
18
+ comment: "Off here: apps import a package's files through subpath exports, which a run inside the package cannot see.",
19
+ severity: "ignore",
20
+ from: { orphan: true },
21
+ to: {},
22
+ },
23
+ {
24
+ name: "no-duplicate-dep-types",
25
+ comment: "Off here: a shared package lists react and friends as both peer and dev dependency on purpose.",
26
+ severity: "ignore",
27
+ from: {},
28
+ to: {},
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,15 @@
1
+ /* global module */
2
+ // This repo's architecture rules, enforced by `zeno check` (architecture module).
3
+ // zeno wrote this starter once; it is yours now. Add the layers this product has
4
+ // (see the rule reference below) and keep the why in a wiki concept page.
5
+ // Rule reference: https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md
6
+
7
+ /** @type {import('dependency-cruiser').IConfiguration} */
8
+ module.exports = {
9
+ extends: "./.dependency-cruiser.base.cjs",
10
+ forbidden: [],
11
+ options: {
12
+ // The level `zeno ui` draws the architecture graph at: one box per match.
13
+ reporterOptions: { archi: { collapsePattern: "^src/[^/]+|^[^/]+" } },
14
+ },
15
+ };