@freetesh/zeno 0.9.1 → 0.11.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 +18 -8
- package/cli/dist/checks/canonical-values.js +9 -7
- package/cli/dist/checks/context-size.js +46 -0
- package/cli/dist/checks/index.js +4 -0
- package/cli/dist/checks/pipeline.js +34 -0
- package/cli/dist/checks/wiki-frontmatter.js +14 -6
- package/cli/dist/commands/approve.js +20 -0
- package/cli/dist/commands/learn.js +4 -3
- package/cli/dist/config/load.js +1 -0
- package/cli/dist/config/schema.js +19 -2
- package/cli/dist/index.js +2 -0
- package/cli/dist/init.js +3 -4
- package/cli/dist/learn.js +12 -1
- package/cli/dist/pipeline.js +20 -0
- package/package.json +1 -1
- package/skills/ask/SKILL.md +3 -5
- package/skills/decision/SKILL.md +25 -22
- package/skills/ingest/SKILL.md +12 -13
- package/skills/weigh/SKILL.md +18 -8
- package/skills/wiki-lint/SKILL.md +10 -12
- package/standards/agent-contract.md +3 -2
- package/standards/context-layout.md +13 -23
- package/standards/wiki-schema.md +50 -278
|
@@ -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.11.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
|
@@ -36,7 +36,8 @@ It has two halves that read the same config:
|
|
|
36
36
|
constitution, your wiki and decision records, existing features and tasks,
|
|
37
37
|
cost vs value, and the write-less ladder, then answers **DO IT**, **DO IT
|
|
38
38
|
DIFFERENTLY** or **PUSH BACK** with citations. Push back stops work until you
|
|
39
|
-
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.
|
|
40
41
|
- **No assumptions.** A request is not a fact. Each claim in it ("users want
|
|
41
42
|
this", "it is slow") is checked against the repo and its data sources and
|
|
42
43
|
marked backed or not. Work that rests on an assumption stops until you agree
|
|
@@ -53,8 +54,17 @@ It has two halves that read the same config:
|
|
|
53
54
|
`.context` layout, agent contract). A team adds or replaces them from its own
|
|
54
55
|
folder; a repo can override again. `zeno standards` shows what applies.
|
|
55
56
|
- **Hard rules are checks.** `zeno check` validates layout, wiki frontmatter,
|
|
56
|
-
stale pages,
|
|
57
|
-
checker your config declares.
|
|
57
|
+
stale pages, placeholder text, page and `tasks.md` size caps, prices with no
|
|
58
|
+
canonical home, copied standards, and runs every checker your config declares.
|
|
59
|
+
- **Context stays small.** A line belongs in `.context/` only if an agent would
|
|
60
|
+
get something wrong without it. Decisions are one file per topic, overwritten
|
|
61
|
+
when the choice changes; `tasks.md` holds open work only; history is git's.
|
|
62
|
+
- **Stages stop at their gates.** A `pipeline:` in `zeno.yaml` lists your
|
|
63
|
+
stages in order, with the paths that show each has started. A gated stage
|
|
64
|
+
needs a person to run `zeno approve <stage>` before any later stage starts;
|
|
65
|
+
`zeno check` fails otherwise. Approvals are dated in
|
|
66
|
+
`.context/raw/zeno/gates.jsonl`, so you can see where work waits. A product
|
|
67
|
+
replaces the estate's pipeline by declaring its own.
|
|
58
68
|
- **Each repo's architecture is enforced.** With the `architecture` module, a
|
|
59
69
|
repo keeps its own import rules in `.dependency-cruiser.cjs` files: one at the
|
|
60
70
|
root, or one per workspace under `apps/` and `packages/` (each runs from its
|
|
@@ -140,7 +150,7 @@ doctor:
|
|
|
140
150
|
tools:
|
|
141
151
|
- { tool: pnpm, why: product scripts, hint: npm i -g pnpm }
|
|
142
152
|
wiki:
|
|
143
|
-
types: # added to the
|
|
153
|
+
types: # added to the built-in folders (concepts, entities, decisions, surfaces, synthesis)
|
|
144
154
|
- type: postmortem
|
|
145
155
|
folder: postmortems
|
|
146
156
|
reason: incidents need a timeline and a fix
|
|
@@ -152,7 +162,7 @@ context:
|
|
|
152
162
|
|
|
153
163
|
```yaml
|
|
154
164
|
# <repo>/.claude/zeno.yaml — written by `zeno init`
|
|
155
|
-
zeno: 0.
|
|
165
|
+
zeno: 0.11.0
|
|
156
166
|
kind: expo-app # expo-app | web | content
|
|
157
167
|
modules: [docs, knowledge, code, listing] # all optional; design is added when .context/designs exists, metrics only when asked
|
|
158
168
|
packs: [expo]
|
|
@@ -172,8 +182,8 @@ carry a `reason:`, and `zeno check` lists every override.
|
|
|
172
182
|
| zeno default | What it holds |
|
|
173
183
|
|---|---|
|
|
174
184
|
| `constitution.md` | Principles C-01–C-15 a request is weighed against. A team adds C-20+ in its own `constitution.md`. |
|
|
175
|
-
| `wiki-schema.md` |
|
|
176
|
-
| `context-layout.md` | The `.context/` ten-folder layout, `features/` and `tasks.md` |
|
|
185
|
+
| `wiki-schema.md` | What belongs in the wiki, its folders, decisions, frontmatter and size caps |
|
|
186
|
+
| `context-layout.md` | The `.context/` ten-folder layout, `features/` and `tasks.md` (open work only) |
|
|
177
187
|
| `agent-contract.md` | What every agent does before, during and after work |
|
|
178
188
|
|
|
179
189
|
## Packs
|
|
@@ -202,7 +212,7 @@ hooks/ session start: constitution titles, qmd refresh
|
|
|
202
212
|
standards/ default standards
|
|
203
213
|
packs/ vendor doc packs
|
|
204
214
|
templates/ files init writes: dependency-cruiser base (synced) and starters
|
|
205
|
-
scripts/ log-verdict.mjs — append-only
|
|
215
|
+
scripts/ log-verdict.mjs — append-only verdict log
|
|
206
216
|
cli/ the zeno CLI (TypeScript, vitest; built to cli/dist for npm)
|
|
207
217
|
evals/ plugin evals and the fixture product
|
|
208
218
|
```
|
|
@@ -10,25 +10,27 @@ const PRICE_WORDS = /pric|cost|paywall|plan|tier|subscri|bundle|pack|purchase|bu
|
|
|
10
10
|
function amounts(text) {
|
|
11
11
|
return (text.match(PRICE) ?? []).map((price) => price.replace(/\s/g, ""));
|
|
12
12
|
}
|
|
13
|
+
// A decision's `Rejected:` line names old prices on purpose; they are history, not a second home.
|
|
14
|
+
const REJECTED = /^\*\*Rejected:\*\*/;
|
|
13
15
|
function pricesIn(text) {
|
|
14
|
-
const paragraphs = text.replace(CODE_BLOCK, "").split(/\n\s*\n/);
|
|
16
|
+
const paragraphs = text.replace(CODE_BLOCK, "").split(/\n\s*\n/).filter((paragraph) => !REJECTED.test(paragraph.trim()));
|
|
15
17
|
return [...new Set(paragraphs.filter((paragraph) => PRICE_WORDS.test(paragraph)).flatMap(amounts))];
|
|
16
18
|
}
|
|
17
19
|
function heldAmounts(text) {
|
|
18
20
|
return amounts(text.replace(CODE_BLOCK, ""));
|
|
19
21
|
}
|
|
22
|
+
const folderOf = (file) => file.split("/")[2];
|
|
20
23
|
export const canonicalValues = {
|
|
21
24
|
id: "canonical-values",
|
|
22
25
|
module: "docs",
|
|
23
|
-
description: "a price
|
|
26
|
+
description: "a price stated in a decision or screen note is held by an entity page — one home per fact (C-02)",
|
|
24
27
|
run({ repo }) {
|
|
25
28
|
const pages = wikiPages(repo).map((file) => ({ file, ...parseFrontmatter(readFileSync(join(repo, file), "utf8")) }));
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
// Surfaces are where store prices get restated. Concepts legitimately carry
|
|
29
|
+
const held = new Set(pages.filter((page) => folderOf(page.file) === "entities").flatMap((page) => heldAmounts(page.body)));
|
|
30
|
+
// Decisions and screen notes are where prices get restated. Concepts legitimately carry
|
|
29
31
|
// example amounts (a tax product explains slabs with them).
|
|
30
|
-
return
|
|
31
|
-
.filter((page) =>
|
|
32
|
+
return pages
|
|
33
|
+
.filter((page) => ["decisions", "surfaces"].includes(folderOf(page.file)))
|
|
32
34
|
.flatMap((page) => {
|
|
33
35
|
const orphans = pricesIn(page.body).filter((price) => !held.has(price));
|
|
34
36
|
if (orphans.length === 0)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { REMOVED_WIKI_FOLDERS as REMOVED_FOLDERS } from "../config/schema.js";
|
|
4
|
+
import { wikiPages } from "./wiki-frontmatter.js";
|
|
5
|
+
// Every line here is read by an agent on every task; a cap keeps .context to what it would get wrong without.
|
|
6
|
+
const MAX_LINES = { page: 60, decision: 30, readme: 30, tasks: 100 };
|
|
7
|
+
const DATED_NAME = /^\d{4}-\d{2}-\d{2}-/;
|
|
8
|
+
const lineCount = (repo, file) => readFileSync(join(repo, file), "utf8").trimEnd().split("\n").length;
|
|
9
|
+
export const contextSize = {
|
|
10
|
+
id: "context-size",
|
|
11
|
+
module: "docs",
|
|
12
|
+
description: "pages, decisions, README.md and tasks.md stay under their line caps; decisions are one file per topic; no flow pages",
|
|
13
|
+
run({ repo }) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
const warn = (file, message) => findings.push({ check: "context-size", level: "warn", file, message });
|
|
16
|
+
const overCap = (file, cap) => {
|
|
17
|
+
const lines = lineCount(repo, file);
|
|
18
|
+
if (lines > cap)
|
|
19
|
+
warn(file, `${lines} lines, cap ${cap} — cut what code, designs or git already say`);
|
|
20
|
+
};
|
|
21
|
+
for (const [folder, why] of Object.entries(REMOVED_FOLDERS)) {
|
|
22
|
+
if (existsSync(join(repo, ".context/wiki", folder)))
|
|
23
|
+
warn(`.context/wiki/${folder}`, `${folder}/ is gone: ${why}`);
|
|
24
|
+
}
|
|
25
|
+
for (const file of wikiPages(repo)) {
|
|
26
|
+
const folder = file.split("/")[2];
|
|
27
|
+
if (folder === "synthesis" || folder in REMOVED_FOLDERS)
|
|
28
|
+
continue;
|
|
29
|
+
if (folder === "decisions" && DATED_NAME.test(basename(file))) {
|
|
30
|
+
warn(file, "name a decision by topic and edit it in place — git keeps the history");
|
|
31
|
+
}
|
|
32
|
+
overCap(file, folder === "decisions" ? MAX_LINES.decision : MAX_LINES.page);
|
|
33
|
+
}
|
|
34
|
+
const readme = ".context/README.md";
|
|
35
|
+
const tasks = ".context/tasks.md";
|
|
36
|
+
if (existsSync(join(repo, readme)))
|
|
37
|
+
overCap(readme, MAX_LINES.readme);
|
|
38
|
+
if (existsSync(join(repo, tasks))) {
|
|
39
|
+
overCap(tasks, MAX_LINES.tasks);
|
|
40
|
+
if (/^## (Done|Session Notes)\b/m.test(readFileSync(join(repo, tasks), "utf8"))) {
|
|
41
|
+
warn(tasks, "tasks.md holds only open work — delete done tasks and session notes; the commits record them");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
},
|
|
46
|
+
};
|
package/cli/dist/checks/index.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { architecture } from "./architecture.js";
|
|
2
2
|
import { canonicalValues } from "./canonical-values.js";
|
|
3
3
|
import { contextLayout } from "./context-layout.js";
|
|
4
|
+
import { contextSize } from "./context-size.js";
|
|
4
5
|
import { staleCopies, standardsCopies } from "./copies.js";
|
|
5
6
|
import { commandChecks } from "./external.js";
|
|
6
7
|
import { metricsDefinitions } from "./metrics.js";
|
|
8
|
+
import { pipeline } from "./pipeline.js";
|
|
7
9
|
import { sources } from "./sources.js";
|
|
8
10
|
import { wikiFrontmatter } from "./wiki-frontmatter.js";
|
|
9
11
|
export const ALL_CHECKS = [
|
|
10
12
|
contextLayout,
|
|
11
13
|
wikiFrontmatter,
|
|
14
|
+
contextSize,
|
|
12
15
|
canonicalValues,
|
|
13
16
|
standardsCopies,
|
|
14
17
|
staleCopies,
|
|
15
18
|
architecture,
|
|
16
19
|
metricsDefinitions,
|
|
17
20
|
sources,
|
|
21
|
+
pipeline,
|
|
18
22
|
commandChecks,
|
|
19
23
|
];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readApprovals } from "../pipeline.js";
|
|
4
|
+
/**
|
|
5
|
+
* Stages run in the order zeno.yaml lists them. A stage has started once one
|
|
6
|
+
* of its `done` paths exists; a gated stage must be approved before any later
|
|
7
|
+
* stage starts. Each unapproved gate is reported once, at the first later
|
|
8
|
+
* stage that started.
|
|
9
|
+
*/
|
|
10
|
+
export const pipeline = {
|
|
11
|
+
id: "pipeline",
|
|
12
|
+
module: null,
|
|
13
|
+
description: "no stage starts before every earlier gate is approved",
|
|
14
|
+
run({ repo, config }) {
|
|
15
|
+
const approved = new Set(readApprovals(repo).map((approval) => approval.stage));
|
|
16
|
+
const startedAt = config.pipeline.map((stage) => stage.done.find((path) => existsSync(join(repo, path))));
|
|
17
|
+
return config.pipeline.flatMap((gate, index) => {
|
|
18
|
+
if (!gate.gate || approved.has(gate.id))
|
|
19
|
+
return [];
|
|
20
|
+
const later = config.pipeline.findIndex((_, i) => i > index && startedAt[i] !== undefined);
|
|
21
|
+
if (later === -1)
|
|
22
|
+
return [];
|
|
23
|
+
const stage = config.pipeline[later];
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
check: "pipeline",
|
|
27
|
+
level: "error",
|
|
28
|
+
file: startedAt[later],
|
|
29
|
+
message: `stage "${stage.id}" started before gate "${gate.id}" was approved — review ${gate.id}, then a person runs \`zeno approve ${gate.id}\``,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -2,8 +2,11 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { listFiles } from "../lib/files.js";
|
|
4
4
|
import { parseFrontmatter } from "../lib/frontmatter.js";
|
|
5
|
-
import { WIKI_FOLDER_BY_TYPE } from "../config/schema.js";
|
|
6
|
-
const REQUIRED_FIELDS = ["
|
|
5
|
+
import { REMOVED_WIKI_FOLDERS, WIKI_FOLDER_BY_TYPE } from "../config/schema.js";
|
|
6
|
+
const REQUIRED_FIELDS = ["last-verified"];
|
|
7
|
+
// Filler an agent would read as fact: a section with nothing to say is deleted, not stubbed.
|
|
8
|
+
const PLACEHOLDER = /\bTBD\b|\bnone yet\b|\bpopulate when\b/i;
|
|
9
|
+
const CODE_BLOCK = /```[\s\S]*?```/g;
|
|
7
10
|
const STALE_AFTER_DAYS = 90;
|
|
8
11
|
const DAY_MS = 86_400_000;
|
|
9
12
|
const NOT_PAGES = ["index.md", "CLAUDE.md"];
|
|
@@ -18,7 +21,7 @@ function asDate(value) {
|
|
|
18
21
|
export const wikiFrontmatter = {
|
|
19
22
|
id: "wiki-frontmatter",
|
|
20
23
|
module: "docs",
|
|
21
|
-
description: "every wiki page has
|
|
24
|
+
description: "every wiki page has last-verified and no placeholder text; a type, if set, matches its folder; ids are unique; nothing stale",
|
|
22
25
|
run({ repo, config, today }) {
|
|
23
26
|
const folderByType = { ...WIKI_FOLDER_BY_TYPE, ...Object.fromEntries(config.wiki.types.map((entry) => [entry.type, entry.folder])) };
|
|
24
27
|
// Built-in types' sections are linted by the wiki-lint skill; added types carry theirs in config.
|
|
@@ -28,6 +31,9 @@ export const wikiFrontmatter = {
|
|
|
28
31
|
const filesById = new Map();
|
|
29
32
|
const stale = [];
|
|
30
33
|
for (const file of wikiPages(repo)) {
|
|
34
|
+
const folder = file.split("/")[2];
|
|
35
|
+
if (folder in REMOVED_WIKI_FOLDERS)
|
|
36
|
+
continue; // context-size reports the folder once
|
|
31
37
|
const { data, body } = parseFrontmatter(readFileSync(join(repo, file), "utf8"));
|
|
32
38
|
if (!data) {
|
|
33
39
|
add("error", file, "no frontmatter");
|
|
@@ -36,10 +42,11 @@ export const wikiFrontmatter = {
|
|
|
36
42
|
const missing = REQUIRED_FIELDS.filter((field) => data[field] === undefined || data[field] === null);
|
|
37
43
|
if (missing.length > 0)
|
|
38
44
|
add("error", file, `missing ${missing.join(", ")}`);
|
|
39
|
-
const folder = file.split("/")[2];
|
|
40
45
|
const expected = folderByType[String(data.type)];
|
|
41
46
|
if (data.type && expected !== folder)
|
|
42
47
|
add("error", file, `type ${data.type} does not belong in ${folder}/`);
|
|
48
|
+
if (!data.type && !Object.values(folderByType).includes(folder))
|
|
49
|
+
add("error", file, `${folder}/ is not a wiki folder`);
|
|
43
50
|
const sections = sectionsByType.get(String(data.type));
|
|
44
51
|
if (sections) {
|
|
45
52
|
const headings = new Set(body.split("\n").flatMap((line) => (line.startsWith("## ") ? [line.slice(3).trim()] : [])));
|
|
@@ -47,8 +54,9 @@ export const wikiFrontmatter = {
|
|
|
47
54
|
if (absent.length > 0)
|
|
48
55
|
add("error", file, `missing sections: ${absent.join(", ")}`);
|
|
49
56
|
}
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
const placeholder = body.replace(CODE_BLOCK, "").match(PLACEHOLDER);
|
|
58
|
+
if (placeholder)
|
|
59
|
+
add("warn", file, `placeholder "${placeholder[0]}" — write the fact or delete the section`);
|
|
52
60
|
const verified = asDate(data["last-verified"]);
|
|
53
61
|
const age = verified ? Math.floor((today.getTime() - verified.getTime()) / DAY_MS) : 0;
|
|
54
62
|
if (verified && age > STALE_AFTER_DAYS)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { loadConfig } from "../config/load.js";
|
|
3
|
+
import { recordApproval } from "../pipeline.js";
|
|
4
|
+
export function registerApprove(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("approve")
|
|
7
|
+
.description("record that a person reviewed and approved a pipeline gate, dated now (a person runs this, not an agent)")
|
|
8
|
+
.argument("<stage>", "the gated stage's id in zeno.yaml `pipeline:`")
|
|
9
|
+
.argument("[repo]", "product repo", ".")
|
|
10
|
+
.option("--note <text>", "what was reviewed", "")
|
|
11
|
+
.action((stageId, repo, options) => {
|
|
12
|
+
const root = resolve(repo);
|
|
13
|
+
const gates = loadConfig(root).config.pipeline.filter((stage) => stage.gate).map((stage) => stage.id);
|
|
14
|
+
if (!gates.includes(stageId)) {
|
|
15
|
+
throw new Error(`"${stageId}" is not a gated stage here — gates: ${gates.join(", ") || "none (no pipeline: in zeno.yaml)"}`);
|
|
16
|
+
}
|
|
17
|
+
const approval = recordApproval(root, stageId, options.note);
|
|
18
|
+
console.log(`approved ${stageId} at ${approval.at}`);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -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/config/load.js
CHANGED
|
@@ -89,6 +89,7 @@ export function loadConfig(repo, options = {}) {
|
|
|
89
89
|
weigh: { weights: Object.assign({}, ...pick("weigh").map((weigh) => weigh?.weights)) },
|
|
90
90
|
wiki: { types: mergeBy(pick("wiki").map((wiki) => wiki?.types ?? []), (entry) => entry.type) },
|
|
91
91
|
context: { folders: mergeBy(pick("context").map((context) => context?.folders ?? []), (entry) => entry.folder) },
|
|
92
|
+
pipeline: lastDefined(pick("pipeline")) ?? [],
|
|
92
93
|
};
|
|
93
94
|
if (Object.keys(config.expo ?? {}).length === 0)
|
|
94
95
|
delete config.expo;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const KINDS = ["expo-app", "web", "content"];
|
|
3
3
|
export const MODULES = ["docs", "design", "knowledge", "metrics", "code", "architecture", "listing"];
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The wiki page types every repo has, and the folder each lives in. A repo adds more under `wiki.types`.
|
|
6
|
+
* No flow type: a journey worth keeping is a concept. A surface page is a screen's notes, only where it has real ones.
|
|
7
|
+
*/
|
|
5
8
|
export const WIKI_FOLDER_BY_TYPE = {
|
|
6
9
|
concept: "concepts",
|
|
7
10
|
entity: "entities",
|
|
8
11
|
surface: "surfaces",
|
|
9
|
-
flow: "flows",
|
|
10
12
|
decision: "decisions",
|
|
11
13
|
synthesis: "synthesis",
|
|
12
14
|
};
|
|
15
|
+
/** Wiki folders that no longer exist, and what to do with what was in them. */
|
|
16
|
+
export const REMOVED_WIKI_FOLDERS = {
|
|
17
|
+
flows: "fold a journey worth keeping into a concept page",
|
|
18
|
+
};
|
|
13
19
|
/** The `.context/` folders every repo may have. A repo adds more under `context.folders`. */
|
|
14
20
|
export const CONTEXT_FOLDERS = ["raw", "wiki", "features", "designs", "listing", "marketing", "metrics", "support", "legal", "ops"];
|
|
15
21
|
const checkOverride = z
|
|
@@ -43,6 +49,15 @@ const wikiType = z
|
|
|
43
49
|
message: "a built-in wiki type or its folder cannot be redefined — add a new one",
|
|
44
50
|
});
|
|
45
51
|
const contextFolder = z.object({ folder: kebab, reason }).strict();
|
|
52
|
+
const stage = z
|
|
53
|
+
.object({
|
|
54
|
+
id: kebab,
|
|
55
|
+
/** Paths that exist once the stage has started; any one is enough. */
|
|
56
|
+
done: z.array(z.string().min(1)).min(1),
|
|
57
|
+
/** A person must approve this stage (`zeno approve <id>`) before any later stage starts. */
|
|
58
|
+
gate: z.boolean().default(false),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
46
61
|
const tool = z.object({ tool: z.string(), why: z.string(), hint: z.string() }).strict();
|
|
47
62
|
/** One layer of `.claude/zeno.yaml` as written: every field optional. */
|
|
48
63
|
export const layerSchema = z
|
|
@@ -62,6 +77,8 @@ export const layerSchema = z
|
|
|
62
77
|
weigh: z.object({ weights: z.record(principleId, z.number().nonnegative()) }).partial(),
|
|
63
78
|
wiki: z.object({ types: z.array(wikiType) }).partial(),
|
|
64
79
|
context: z.object({ folders: z.array(contextFolder) }).partial(),
|
|
80
|
+
/** The stages work goes through, in order. A more specific layer replaces the whole list. */
|
|
81
|
+
pipeline: z.array(stage),
|
|
65
82
|
})
|
|
66
83
|
.partial()
|
|
67
84
|
.strict();
|
package/cli/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { registerAdd } from "./commands/add.js";
|
|
5
|
+
import { registerApprove } from "./commands/approve.js";
|
|
5
6
|
import { registerCheck } from "./commands/check.js";
|
|
6
7
|
import { registerDoctor } from "./commands/doctor.js";
|
|
7
8
|
import { registerInit } from "./commands/init.js";
|
|
@@ -29,6 +30,7 @@ registerStandards(program, env);
|
|
|
29
30
|
registerDoctor(program);
|
|
30
31
|
registerUi(program, env);
|
|
31
32
|
registerLearn(program, env);
|
|
33
|
+
registerApprove(program);
|
|
32
34
|
registerWeigh(program);
|
|
33
35
|
program.parseAsync().catch((error) => {
|
|
34
36
|
console.error(`zeno: ${error.message}`);
|
package/cli/dist/init.js
CHANGED
|
@@ -66,12 +66,12 @@ const FIRST_FILE = {
|
|
|
66
66
|
wiki: ".context/wiki/index.md",
|
|
67
67
|
features: ".context/features/index.md",
|
|
68
68
|
};
|
|
69
|
-
function template(file
|
|
69
|
+
function template(file) {
|
|
70
70
|
switch (file) {
|
|
71
71
|
case ".context/README.md":
|
|
72
72
|
return "# .context\n\nProduct material, in the ten folders of the zeno context layout.\n\n## Absent folders\n\n- listing/ — n/a — no store presence yet\n- marketing/, metrics/, support/, legal/ — n/a — not live yet\n- ops/ — n/a — nothing to run yet\n";
|
|
73
73
|
case ".context/tasks.md":
|
|
74
|
-
return
|
|
74
|
+
return "# Tasks\n\n## Now\n\n## Blocked\n\n## Waiting\n";
|
|
75
75
|
case ".context/wiki/index.md":
|
|
76
76
|
return "# Wiki index\n\nPages by type. Schema: the zeno plugin's wiki-schema standard.\n";
|
|
77
77
|
case ".context/features/index.md":
|
|
@@ -98,7 +98,6 @@ export function planInit(repo, answers) {
|
|
|
98
98
|
return { config, create };
|
|
99
99
|
}
|
|
100
100
|
export function applyInit(repo, plan, options) {
|
|
101
|
-
const today = options.today ?? new Date().toISOString().slice(0, 10);
|
|
102
101
|
const write = (file, content) => {
|
|
103
102
|
mkdirSync(dirname(join(repo, file)), { recursive: true });
|
|
104
103
|
writeFileSync(join(repo, file), content);
|
|
@@ -107,7 +106,7 @@ export function applyInit(repo, plan, options) {
|
|
|
107
106
|
const starters = new Map(architectureStarters(repo).map((s) => [s.file, s.template]));
|
|
108
107
|
for (const file of plan.create) {
|
|
109
108
|
const starter = starters.get(file);
|
|
110
|
-
write(file, starter ? readFileSync(join(options.zenoRoot, ARCHITECTURE_TEMPLATES, starter), "utf8") : template(file
|
|
109
|
+
write(file, starter ? readFileSync(join(options.zenoRoot, ARCHITECTURE_TEMPLATES, starter), "utf8") : template(file));
|
|
111
110
|
}
|
|
112
111
|
if (plan.config.modules.includes("knowledge"))
|
|
113
112
|
indexKnowledge(repo, options.name, options.run);
|
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) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
// In raw/, which is append-only: the dates are the record of how long each stage waited.
|
|
4
|
+
export const GATES_LOG = join(".context", "raw", "zeno", "gates.jsonl");
|
|
5
|
+
export function readApprovals(repo) {
|
|
6
|
+
const path = join(repo, GATES_LOG);
|
|
7
|
+
if (!existsSync(path))
|
|
8
|
+
return [];
|
|
9
|
+
return readFileSync(path, "utf8")
|
|
10
|
+
.split("\n")
|
|
11
|
+
.filter((line) => line.trim())
|
|
12
|
+
.map((line) => JSON.parse(line));
|
|
13
|
+
}
|
|
14
|
+
export function recordApproval(repo, stage, note) {
|
|
15
|
+
const approval = { stage, at: new Date().toISOString(), note };
|
|
16
|
+
const path = join(repo, GATES_LOG);
|
|
17
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
18
|
+
appendFileSync(path, `${JSON.stringify(approval)}\n`);
|
|
19
|
+
return approval;
|
|
20
|
+
}
|
package/package.json
CHANGED
package/skills/ask/SKILL.md
CHANGED
|
@@ -15,9 +15,8 @@ Question: `$ARGUMENTS`
|
|
|
15
15
|
- `entity` pages for "what is the value / price / name of X"
|
|
16
16
|
- `synthesis` pages for cross-cutting summaries (use these before chasing individual entities)
|
|
17
17
|
- `concept` pages for "why" or "what does X mean" questions
|
|
18
|
-
- `
|
|
19
|
-
-
|
|
20
|
-
- `decision` pages for "why did we choose X over Y"
|
|
18
|
+
- `decision` pages for "why did we choose X over Y", and its `Rejected:` line for "did we consider Y"
|
|
19
|
+
- for "what does this screen do", the HTML in `.context/designs/` is the spec; its `surfaces/` note, if any, says why
|
|
21
20
|
|
|
22
21
|
3. **Read the candidate pages.** Follow `[[wikilinks]]` one hop if the answer is incomplete. Stop at two hops — if you need three, the wiki is missing a synthesis page; say so.
|
|
23
22
|
|
|
@@ -35,8 +34,7 @@ Question: `$ARGUMENTS`
|
|
|
35
34
|
|
|
36
35
|
## Rules
|
|
37
36
|
|
|
38
|
-
- **Never read `.context/raw/`
|
|
37
|
+
- **Never read `.context/raw/` from this skill.** If the answer requires raw inspection, recommend `/ingest`. Designs may be read for screen questions.
|
|
39
38
|
- **Never invent facts.** If a canonical value isn't in an `entity` page, say "not in the wiki".
|
|
40
39
|
- **Every cited page must actually exist.** Don't fabricate a `[[wikilink]]` to make the answer look authoritative.
|
|
41
|
-
- A page with `status: superseded` is not canonical — follow `superseded-by` to the current page.
|
|
42
40
|
- A page with `last-verified` > 90 days is stale — flag it in the answer ("last verified 2025-11-04 — may be out of date").
|
package/skills/decision/SKILL.md
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: decision
|
|
3
|
-
description: Record a product or technical decision
|
|
3
|
+
description: Record or change a product or technical decision in .context/wiki/decisions/<topic>.md — one file per topic, overwritten when the choice changes. Usage `/decision <topic> — <choice and why>`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are
|
|
6
|
+
You are recording a decision in `.context/wiki/decisions/<topic>.md`.
|
|
7
7
|
|
|
8
|
-
Decision
|
|
8
|
+
Decision: `$ARGUMENTS`
|
|
9
9
|
|
|
10
10
|
## Steps
|
|
11
11
|
|
|
12
|
-
1.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
1. Split `$ARGUMENTS` on the first ` — `: left = topic, right = the choice and why. With no ` — `, ask for the choice.
|
|
13
|
+
2. Slug = the topic in kebab-case, no date (`pricing`, `auth-provider`). Look for an existing file on the same topic first, under any name. If there is one, it is the file to change.
|
|
14
|
+
3. Write the file, at most 30 lines:
|
|
15
|
+
|
|
16
|
+
```markdown
|
|
17
|
+
---
|
|
18
|
+
last-verified: <today>
|
|
19
|
+
---
|
|
20
|
+
# <Topic>
|
|
21
|
+
**Choice:** <one or two lines; link values to their entity page>
|
|
22
|
+
**Why:** <the trigger: data, incident, user feedback, cost>
|
|
23
|
+
**Rejected:** <option (why)> · <option (why)>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
When changing an existing decision, replace the choice and why, and move the
|
|
27
|
+
old choice into `Rejected:` with the reason it was dropped. Keep the rest of `Rejected:`.
|
|
28
|
+
4. Put a price, SKU or limit on its entity page, not here (`canonical-values` checks this).
|
|
29
|
+
5. Add or update the one line in `.context/wiki/index.md`.
|
|
30
|
+
6. Confirm: `decision recorded: <topic> → <path>`.
|
|
28
31
|
|
|
29
32
|
## Rules
|
|
30
33
|
|
|
31
|
-
- One
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
+
- One file per topic, overwritten. Git keeps the old text; never add a dated or `-2` file.
|
|
35
|
+
- No fix lists, audit results or step-by-step history. Those go in the commit message or `CHANGELOG.md`.
|
|
36
|
+
- If the user's text is long, keep the choice, the reason and what was rejected, and drop the rest. Show what you dropped.
|
package/skills/ingest/SKILL.md
CHANGED
|
@@ -9,28 +9,27 @@ Source to ingest: `$ARGUMENTS`
|
|
|
9
9
|
|
|
10
10
|
## Steps
|
|
11
11
|
|
|
12
|
-
1. **Read the wiki schema (the `wiki-schema.md` that `zeno standards` lists — a team's own replaces zeno's; without the CLI, `../../standards/wiki-schema.md` relative to this skill) first.** It defines
|
|
12
|
+
1. **Read the wiki schema (the `wiki-schema.md` that `zeno standards` lists — a team's own replaces zeno's; without the CLI, `../../standards/wiki-schema.md` relative to this skill) first.** It defines the folders, the one test for what belongs, and the caps. Every page you write must conform.
|
|
13
13
|
|
|
14
14
|
2. **Read the raw source** at the path in `$ARGUMENTS`. Accepted: anything under `.context/raw/` or `.context/designs/`, and application source in this repository. Refuse paths outside the repo.
|
|
15
15
|
|
|
16
16
|
3. **Classify** the source into one or more wiki page types:
|
|
17
|
-
- HTML wireframe → `
|
|
18
|
-
- Decision note → `decision`
|
|
17
|
+
- HTML wireframe → nothing; the HTML is the spec. Only a constraint it can't show becomes that screen's `surfaces/<slug>.md` note.
|
|
18
|
+
- Decision note → the `decisions/<topic>.md` file, via the `decision` skill's format
|
|
19
19
|
- Research doc / interview → typically `concept` pages (split if it covers multiple ideas), or one `synthesis` page for a brief that draws a conclusion
|
|
20
20
|
- A new tier / product / canonical value → `entity` page
|
|
21
|
-
- A user journey description →
|
|
21
|
+
- A user journey description → a concept, only if it holds a rule the designs don't show
|
|
22
|
+
- Write nothing an agent could read from the code, the designs or git.
|
|
22
23
|
- Don't force one source into one page — a meeting note can produce multiple pages.
|
|
23
|
-
-
|
|
24
|
+
- Folders are the four in the schema plus any listed under `wiki.types` in the repo's or the estate's `.claude/zeno.yaml`, each with its folder and its required `sections` (use its `sections` headings). Use an added type only when the source fits it.
|
|
24
25
|
|
|
25
26
|
4. **For each page you will create:**
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Fill `Related` with at least two `[[wikilinks]]`. A link to a page that doesn't exist yet is OK — `/wiki-lint` will flag it as a TODO.
|
|
31
|
-
- List the source path under `Sources`, so a future `/wiki-lint` can tell which raw files nothing has read.
|
|
27
|
+
- Filename: the topic, lowercase kebab-case, no date.
|
|
28
|
+
- Check whether a page on the topic already exists. If yes, edit it in place and refresh `last-verified` to today. Keep any `id`.
|
|
29
|
+
- Write only sections with something to say. No placeholders. Stay under the caps (page 60 lines).
|
|
30
|
+
- Link related pages with `[[slug]]` where it helps a reader. List the source path under `Sources`.
|
|
32
31
|
|
|
33
|
-
5. **Update `.context/wiki/index.md`:**
|
|
32
|
+
5. **Update `.context/wiki/index.md`:** one line for the new page, under its folder heading, alphabetical. Don't reformat existing entries.
|
|
34
33
|
|
|
35
34
|
6. **Leave the source where it is.** `.context/raw/` is immutable: a source is "processed" when a wiki page lists it under `Sources`, not when it moves.
|
|
36
35
|
|
|
@@ -39,7 +38,7 @@ Source to ingest: `$ARGUMENTS`
|
|
|
39
38
|
## Rules
|
|
40
39
|
|
|
41
40
|
- Never edit anything under `.context/raw/` or `.context/designs/`.
|
|
42
|
-
- Never delete or rename an existing wiki page from this skill
|
|
41
|
+
- Never delete or rename an existing wiki page from this skill.
|
|
43
42
|
- Never invent canonical values. If the source has a price/string/enum, copy it verbatim; if it's ambiguous, ask the user before writing.
|
|
44
43
|
- Follow the copy rules from the root `CLAUDE.md` for any user-facing strings you transcribe.
|
|
45
44
|
- Today's date is in the session context — use it for `last-verified`.
|
package/skills/weigh/SKILL.md
CHANGED
|
@@ -134,17 +134,27 @@ Proceed only after the user agrees in writing ("agree", or "agree to 1–4").
|
|
|
134
134
|
"Go ahead" to a verdict is not agreement to terms that were never shown. Work
|
|
135
135
|
covers only the terms agreed to.
|
|
136
136
|
|
|
137
|
-
## Logging (
|
|
137
|
+
## Logging (every verdict)
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
142
150
|
|
|
143
151
|
```bash
|
|
144
|
-
node ../../scripts/log-verdict.mjs --verdict push-back --
|
|
145
|
-
--request "<one line>"
|
|
146
|
-
--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>"
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
(path relative to this skill). It writes to `.context/raw/zeno/verdicts.jsonl`
|
|
150
|
-
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.
|
|
@@ -9,10 +9,11 @@ Mode: `$ARGUMENTS` (empty = audit only; `--fix` = audit + auto-fix safe issues).
|
|
|
9
9
|
|
|
10
10
|
## Steps
|
|
11
11
|
|
|
12
|
-
1. **Read the wiki schema** to refresh
|
|
12
|
+
1. **Read the wiki schema** to refresh the folders, the one test and the caps.
|
|
13
13
|
|
|
14
14
|
2. **Run `zeno check` first** if the CLI is installed. It checks frontmatter,
|
|
15
|
-
|
|
15
|
+
placeholders, caps, dated decision names, duplicate IDs, staleness and
|
|
16
|
+
prices held by no entity in under a second,
|
|
16
17
|
plus any checker the repo declares. Start from its output rather than
|
|
17
18
|
re-deriving it.
|
|
18
19
|
|
|
@@ -29,30 +30,27 @@ Mode: `$ARGUMENTS` (empty = audit only; `--fix` = audit + auto-fix safe issues).
|
|
|
29
30
|
|
|
30
31
|
| Check | Rule |
|
|
31
32
|
|---|---|
|
|
32
|
-
| Missing required section | Per the wiki schema § "Page body — required sections" |
|
|
33
33
|
| Broken `[[wikilink]]` | Target page does not exist |
|
|
34
34
|
| Duplicate `id` | Two pages claim the same ID |
|
|
35
35
|
| Contradictory canonical value | Two `entity` pages list the same key with different values |
|
|
36
|
-
| `
|
|
37
|
-
|
|
|
38
|
-
| `type` does not match folder | `type: concept` must live in `concepts/`, etc. A type added under `wiki.types` in `.claude/zeno.yaml` lives in its configured folder and has every heading in its `sections`. |
|
|
36
|
+
| Placeholder text | `TBD`, `none yet`: write the fact or delete the section |
|
|
37
|
+
| `type` does not match folder | If a page sets `type`, it lives in that type's folder |
|
|
39
38
|
|
|
40
39
|
### Warnings
|
|
41
40
|
|
|
42
41
|
| Check | Rule |
|
|
43
42
|
|---|---|
|
|
44
|
-
|
|
|
43
|
+
| Over a cap | Page 60 lines, decision 30, README 30, tasks 100 |
|
|
44
|
+
| Restates code, a design or git | Delete the lines; link the source instead |
|
|
45
|
+
| Two pages on one topic | Merge into one, edited in place |
|
|
45
46
|
| Stale `last-verified` | > 90 days from today. |
|
|
46
47
|
| Unprocessed raw file | A file under `.context/raw/` that no wiki page references in its `Sources`. |
|
|
47
|
-
| Stale synthesis | A `synthesis` page where any `Built from` page was modified after the synthesis's `Last refresh`. |
|
|
48
|
-
| Page missing two outbound links | Body has fewer than two `[[wikilinks]]`. |
|
|
49
48
|
| User-facing string violates copy rules | Check quoted copy against the rules in the root `CLAUDE.md`. Best-effort. |
|
|
50
49
|
|
|
51
50
|
6. **Auto-fix (only if `--fix` is in `$ARGUMENTS`):**
|
|
52
51
|
- Regenerate `.context/wiki/index.md` from scratch: enumerate pages by type, sort alphabetically by slug, keep the structure the file already has.
|
|
53
52
|
- Fix `last-verified` to today's date on any page whose body was edited in the working tree.
|
|
54
|
-
-
|
|
55
|
-
- **Never** auto-fix: contradictory canonical values, missing required sections, broken `[[wikilinks]]`. These need a human decision.
|
|
53
|
+
- **Never** auto-fix: contradictory canonical values, broken `[[wikilinks]]`, merges or cuts. These need a human decision.
|
|
56
54
|
|
|
57
55
|
7. **Report.** Print:
|
|
58
56
|
- Counts: pages, blockers, warnings, auto-fixes applied.
|
|
@@ -63,7 +61,7 @@ Mode: `$ARGUMENTS` (empty = audit only; `--fix` = audit + auto-fix safe issues).
|
|
|
63
61
|
## Rules
|
|
64
62
|
|
|
65
63
|
- **Never edit pages other than auto-fixes above.** Lint reports; the user (or `/ingest`) fixes.
|
|
66
|
-
- **Never delete pages.**
|
|
64
|
+
- **Never delete pages from this skill.** Propose the cut; the user approves it. Git keeps what is deleted.
|
|
67
65
|
- **Never modify `.context/raw/`** except to warn about unprocessed files.
|
|
68
66
|
- If a blocker resists auto-fix, exit non-zero (in CI mode) but always print the full report first.
|
|
69
67
|
- Today's date is in the session context — use it for staleness math (>90 days).
|
|
@@ -8,7 +8,7 @@ file with its own `agent-contract.md` in a configured standards folder.
|
|
|
8
8
|
- Read the repo's `CLAUDE.md` / `AGENTS.md`: stack, layout, the rules that are
|
|
9
9
|
the repo's own.
|
|
10
10
|
- Read `.context/tasks.md`. It is the ordered queue — the top item of `## Now`
|
|
11
|
-
is the next action. After finishing,
|
|
11
|
+
is the next action. After finishing, delete the task.
|
|
12
12
|
- For a non-trivial request, run the weigh gate first (`/zeno:weigh`).
|
|
13
13
|
|
|
14
14
|
## Repository rules
|
|
@@ -40,7 +40,8 @@ function's own name.
|
|
|
40
40
|
|
|
41
41
|
## Before calling work done
|
|
42
42
|
|
|
43
|
-
1.
|
|
43
|
+
1. Update any `.context/` page the change made wrong. Add a page only if an
|
|
44
|
+
agent would get something wrong without it.
|
|
44
45
|
2. Run the repo's own lint, typecheck and tests.
|
|
45
46
|
3. Run `zeno check`. Every checker the repo declares under `commands:` runs
|
|
46
47
|
with it.
|
|
@@ -52,10 +52,10 @@ folder.
|
|
|
52
52
|
|
|
53
53
|
## Naming and frontmatter
|
|
54
54
|
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
55
|
+
- Dated sources (`raw/`) → ISO date: `YYYY-MM-DD-slug.md`.
|
|
56
|
+
- Features → zero-padded number: `NNNN-slug.md`.
|
|
57
|
+
- Everything else is named by topic and edited in place.
|
|
58
|
+
- Frontmatter: `last-verified`, kebab-case keys. See the wiki schema.
|
|
59
59
|
|
|
60
60
|
## Where each kind of document lives
|
|
61
61
|
|
|
@@ -64,8 +64,8 @@ separate folder per name.
|
|
|
64
64
|
|
|
65
65
|
| Document | Home |
|
|
66
66
|
|---|---|
|
|
67
|
-
| ADR | `wiki/decisions
|
|
68
|
-
| RFC | `
|
|
67
|
+
| ADR | `wiki/decisions/<topic>.md`, overwritten when the choice changes |
|
|
68
|
+
| RFC | `features/`; once accepted, the choice goes in `wiki/decisions/` |
|
|
69
69
|
| PRD | `features/`, `type: feature` |
|
|
70
70
|
| Experiment | `features/`, `type: experiment`; the result is a decision page |
|
|
71
71
|
| Initiative | `wiki/synthesis/`, linking its features and decisions |
|
|
@@ -99,16 +99,11 @@ An experiment adds `## Hypothesis`, `## Metric` (a name from
|
|
|
99
99
|
`metrics/definitions.yml`) and `## Result` once it has run.
|
|
100
100
|
|
|
101
101
|
The wiki describes what *is*; `features/` describes what is *next*. A shipped
|
|
102
|
-
feature goes `status: shipped
|
|
102
|
+
feature goes `status: shipped`.
|
|
103
103
|
|
|
104
|
-
## `tasks.md` —
|
|
104
|
+
## `tasks.md` — open work only
|
|
105
105
|
|
|
106
106
|
```markdown
|
|
107
|
-
---
|
|
108
|
-
updated: 2026-09-29
|
|
109
|
-
horizon: 2026-10-13 # this file is stale after this date
|
|
110
|
-
---
|
|
111
|
-
|
|
112
107
|
# Tasks
|
|
113
108
|
|
|
114
109
|
## Now <!-- position is the dependency; do in order -->
|
|
@@ -116,7 +111,7 @@ horizon: 2026-10-13 # this file is stale after this date
|
|
|
116
111
|
2. [ ] T002 Write the capture flows needs:T001 ~3h
|
|
117
112
|
|
|
118
113
|
## Parallel <!-- independent, safe to batch -->
|
|
119
|
-
- [ ] T004
|
|
114
|
+
- [ ] T004 Move the SKU IDs into the wiki ~1h
|
|
120
115
|
|
|
121
116
|
## Blocked
|
|
122
117
|
- [ ] T005 Submit the iOS build
|
|
@@ -125,14 +120,9 @@ horizon: 2026-10-13 # this file is stale after this date
|
|
|
125
120
|
|
|
126
121
|
## Waiting <!-- deferred by time, not by a task -->
|
|
127
122
|
- [ ] T006 Second-language listing wait:2026-11-01
|
|
128
|
-
|
|
129
|
-
## Done
|
|
130
|
-
- [x] T000 Set up .context done:2026-09-29
|
|
131
|
-
|
|
132
|
-
## Session Notes
|
|
133
|
-
### 2026-09-29
|
|
134
|
-
- Done: T000. Learned: … Next: T001.
|
|
135
123
|
```
|
|
136
124
|
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
Delete a task when it is done — the commit records it. No `## Done`, no session
|
|
126
|
+
notes. Cap: 100 lines. A blocker with no unblocking action and no check date is a
|
|
127
|
+
silent leak. A task that needs more than a line and a continuation is a
|
|
128
|
+
`features/` page.
|
package/standards/wiki-schema.md
CHANGED
|
@@ -1,299 +1,71 @@
|
|
|
1
|
-
# Wiki —
|
|
1
|
+
# Wiki — rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`.context/wiki/` holds what an agent would get wrong without being told. `.context/raw/` holds
|
|
4
|
+
sources, never edited. Answer from the wiki, not from raw.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## The test
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
---
|
|
8
|
+
Keep a line only if an agent would make a mistake without it. Never write what the
|
|
9
|
+
code, an HTML design, or `git log` already says. Describe what is true **now**:
|
|
10
|
+
history is git's job.
|
|
20
11
|
|
|
21
|
-
##
|
|
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
|
-
```
|
|
12
|
+
## Folders
|
|
44
13
|
|
|
45
|
-
|
|
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 |
|
|
14
|
+
| Folder | Holds | Example |
|
|
61
15
|
|---|---|---|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
16
|
+
| `concepts/` | a rule or invariant | `money-in-minor-units` |
|
|
17
|
+
| `entities/` | canonical values (prices, SKUs, limits) — the one home for each | `plan-pro` |
|
|
18
|
+
| `decisions/` | one file per topic: the current choice | `pricing` |
|
|
19
|
+
| `surfaces/` | a screen's notes: what its HTML can't show (only screens that have some) | `paywall` |
|
|
20
|
+
| `synthesis/` | research and cross-cutting analysis | `tax-table-verification-ay-2026-27` |
|
|
64
21
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
22
|
+
There is no page per screen: the HTML design is the screen spec. A screen gets a
|
|
23
|
+
`surfaces/<slug>.md` only when it has a constraint or decision invisible in its pixels;
|
|
24
|
+
the design set's notes button shows it, and `routes.js` sets `notes: null` for every
|
|
25
|
+
other screen. A user journey worth keeping is a concept.
|
|
26
|
+
A product may add a folder in `.claude/zeno.yaml` under `wiki.types`, with a reason.
|
|
68
27
|
|
|
69
|
-
|
|
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.
|
|
28
|
+
## Pages
|
|
75
29
|
|
|
76
|
-
|
|
77
|
-
|
|
30
|
+
- Frontmatter: `last-verified: YYYY-MM-DD` is the only required field. `id` is kept
|
|
31
|
+
where code references it (`SOURCE.*`, entity keys).
|
|
32
|
+
- Write only the sections you have something to say in. No `TBD`, no `none yet`.
|
|
33
|
+
- One file per topic, edited in place. No dated names, no `v2`, no supersede chains.
|
|
34
|
+
- Caps: page 60 lines, decision 30. Synthesis is exempt.
|
|
35
|
+
- Link with `[[slug]]` (the file name without `.md`). `index.md` is one line per page.
|
|
78
36
|
|
|
79
|
-
|
|
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.
|
|
37
|
+
## Decisions
|
|
85
38
|
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Frontmatter (mandatory)
|
|
89
|
-
|
|
90
|
-
Every page begins with YAML frontmatter:
|
|
91
|
-
|
|
92
|
-
```yaml
|
|
93
|
-
---
|
|
94
|
-
id: PLAN.PRO # Stable, uppercase, dot-separated. Never changes. Referenced from code.
|
|
95
|
-
type: entity # concept | entity | surface | flow | decision | synthesis
|
|
96
|
-
status: canonical # canonical | draft | superseded
|
|
97
|
-
last-verified: 2026-06-24 # ISO date. /zeno:wiki-lint flags pages > 90 days.
|
|
98
|
-
supersedes: [] # IDs this page replaces (decisions only)
|
|
99
|
-
superseded-by: null # If non-null, this page is no longer canonical
|
|
100
|
-
---
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### ID conventions
|
|
104
|
-
|
|
105
|
-
| Type | ID shape | Example |
|
|
106
|
-
|---|---|---|
|
|
107
|
-
| concept | `CONCEPT.<SLUG>` | `CONCEPT.OFFLINE-FIRST` |
|
|
108
|
-
| entity | `<DOMAIN>.<NAME>` | `PLAN.PRO`, `PLAN.FREE` |
|
|
109
|
-
| surface | `SURFACE.<APP>.<PATH>` | `SURFACE.MOBILE.ONBOARDING.WELCOME` |
|
|
110
|
-
| flow | `FLOW.<SLUG>` | `FLOW.NEW-USER-TO-FIRST-SAVE` |
|
|
111
|
-
| decision | `DECISION.<YYYY-MM-DD>.<SLUG>` | `DECISION.2026-06-24.ANNUAL-PLAN-ONLY` |
|
|
112
|
-
| synthesis | `SYNTHESIS.<SLUG>` | `SYNTHESIS.PRICING` |
|
|
113
|
-
|
|
114
|
-
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.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Page body — required sections
|
|
119
|
-
|
|
120
|
-
Each type has a required section list. `/zeno:wiki-lint` fails the page if any are missing.
|
|
121
|
-
|
|
122
|
-
### `concept`
|
|
123
39
|
```markdown
|
|
124
|
-
## Summary
|
|
125
|
-
One sentence — what the concept is.
|
|
126
|
-
|
|
127
|
-
## Why it matters
|
|
128
|
-
Why this is a load-bearing idea. Tie it to a constraint, an incident, or a principle.
|
|
129
|
-
|
|
130
|
-
## Implications
|
|
131
|
-
Bulleted: what this forces / forbids in practice.
|
|
132
|
-
|
|
133
|
-
## Related
|
|
134
|
-
- [[other-concept]] — relationship
|
|
135
|
-
- [[entity-x]] — where this applies
|
|
136
|
-
|
|
137
|
-
## Sources
|
|
138
|
-
- .context/raw/<source> or .context/designs/<surface>/<flow>/<screen>.html
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### `entity`
|
|
142
|
-
```markdown
|
|
143
|
-
## Summary
|
|
144
|
-
One sentence — what this entity is.
|
|
145
|
-
|
|
146
|
-
## Canonical values
|
|
147
|
-
| Key | Value | Last changed |
|
|
148
|
-
|---|---|---|
|
|
149
|
-
| PRICE.PRO.YEARLY | $49 | 2026-04-12 |
|
|
150
|
-
| STORAGE.PRO | 5 GB | 2026-04-12 |
|
|
151
|
-
|
|
152
|
-
## Related
|
|
153
|
-
- [[concept-x]] — concept this entity instantiates
|
|
154
|
-
- [[entity-y]] — sibling / upgrade-from
|
|
155
|
-
|
|
156
|
-
## Sources
|
|
157
|
-
- .context/raw/<source>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
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.
|
|
161
|
-
|
|
162
|
-
### `surface`
|
|
163
|
-
```markdown
|
|
164
|
-
## Summary
|
|
165
|
-
One sentence — what this screen does.
|
|
166
|
-
|
|
167
|
-
## Raw wireframe
|
|
168
|
-
- .context/designs/<surface>/<flow>/<screen>.html
|
|
169
|
-
|
|
170
|
-
## Copy
|
|
171
|
-
Verbatim user-visible strings. Each must pass the [[copy-voice]] rules.
|
|
172
|
-
|
|
173
|
-
## Flows that touch this surface
|
|
174
|
-
- [[new-user-to-first-save]] — step 4
|
|
175
|
-
|
|
176
|
-
## Entities referenced
|
|
177
|
-
- [[plan-free]]
|
|
178
|
-
- [[privacy-consent]]
|
|
179
|
-
|
|
180
|
-
## Sources
|
|
181
|
-
- .context/raw/<source>
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### `flow`
|
|
185
|
-
```markdown
|
|
186
|
-
## Summary
|
|
187
|
-
One sentence — what journey this is.
|
|
188
|
-
|
|
189
|
-
## Audience
|
|
190
|
-
end-user | admin
|
|
191
|
-
|
|
192
|
-
## Entry
|
|
193
|
-
Where the user arrives from.
|
|
194
|
-
|
|
195
|
-
## Steps
|
|
196
|
-
| # | Step | Surface | Notes |
|
|
197
|
-
|---|---|---|---|
|
|
198
|
-
| 1 | Welcome | [[welcome]] | First launch |
|
|
199
|
-
| 2 | Sign up | [[sign-up]] | Email |
|
|
200
|
-
| ... |
|
|
201
|
-
|
|
202
|
-
## Exit
|
|
203
|
-
The success state.
|
|
204
|
-
|
|
205
|
-
## Escapes
|
|
206
|
-
Back-button, skips, abandonment outcomes.
|
|
207
|
-
|
|
208
|
-
## Sources
|
|
209
|
-
- .context/raw/<source>
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### `decision`
|
|
213
|
-
```markdown
|
|
214
|
-
## Decision
|
|
215
|
-
One-line statement.
|
|
216
|
-
|
|
217
|
-
## Why
|
|
218
|
-
The motivation. Cite the trigger — incident, user feedback, regulator, scope cut.
|
|
219
|
-
|
|
220
|
-
## Impact
|
|
221
|
-
What changes downstream. Which [[entities]] / [[surfaces]] / [[concepts]] update.
|
|
222
|
-
|
|
223
|
-
## Status
|
|
224
|
-
Active | Superseded by [[decision-id]]
|
|
225
|
-
|
|
226
|
-
## Sources
|
|
227
|
-
- .context/raw/<source>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Decisions are append-only. To reverse a decision, write a new one with `supersedes: [DECISION.<old>]` and mark the old one's `superseded-by`.
|
|
231
|
-
|
|
232
|
-
### `synthesis`
|
|
233
|
-
```markdown
|
|
234
|
-
## Summary
|
|
235
|
-
What this synthesis combines.
|
|
236
|
-
|
|
237
|
-
## Built from
|
|
238
|
-
- [[entity-x]]
|
|
239
|
-
- [[entity-y]]
|
|
240
|
-
- [[concept-z]]
|
|
241
|
-
|
|
242
|
-
## Body
|
|
243
|
-
The cross-cutting answer. Every claim links back to a source page.
|
|
244
|
-
|
|
245
|
-
## Last refresh
|
|
246
|
-
2026-06-24 — refresh when any "Built from" page changes.
|
|
247
|
-
|
|
248
|
-
## Sources
|
|
249
|
-
- (synthesis pages cite wiki pages, not raw files)
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Synthesis pages are **derived**. If a source page changes, `/zeno:wiki-lint` marks the synthesis stale.
|
|
253
|
-
|
|
254
40
|
---
|
|
255
|
-
|
|
256
|
-
## Linking
|
|
257
|
-
|
|
258
|
-
- Use `[[page-slug]]` — the filename without `.md`. Match the file exactly (case-sensitive on case-sensitive filesystems).
|
|
259
|
-
- Cross-folder links use the slug, not the path: `[[tier-self]]`, not `[[entities/tier-self]]`.
|
|
260
|
-
- 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.
|
|
261
|
-
- The `Related` section is what makes the graph navigable. Every page must have at least two outbound links (or be flagged as orphan-source).
|
|
262
|
-
|
|
41
|
+
last-verified: 2026-10-27
|
|
263
42
|
---
|
|
43
|
+
# Pricing
|
|
44
|
+
**Choice:** annual plan only, ₹249 ([[plan-pro]]).
|
|
45
|
+
**Why:** monthly churn was 3× annual in the 2026-06 test.
|
|
46
|
+
**Rejected:** monthly plan (churn) · lifetime deal (no recurring revenue).
|
|
47
|
+
```
|
|
264
48
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|---|---|
|
|
269
|
-
| Missing required section | Block — must be fixed |
|
|
270
|
-
| `[[wikilink]]` to a non-existent page | Block — create target or rename link |
|
|
271
|
-
| Orphan page (zero inbound links) | Warn |
|
|
272
|
-
| Two entities claim the same `id` | Block |
|
|
273
|
-
| Two entities have contradictory canonical values for the same key | Block |
|
|
274
|
-
| `last-verified` > 90 days | Warn |
|
|
275
|
-
| `status: superseded` without `superseded-by` | Block |
|
|
276
|
-
| Source file in `.context/raw/` not referenced by any wiki page | Warn |
|
|
277
|
-
| Synthesis page where a `Built from` page changed after `Last refresh` | Warn |
|
|
278
|
-
| Filename does not match the `id`'s slug portion | Block |
|
|
49
|
+
When the choice changes, overwrite the file. `Rejected:` is what stops the same idea
|
|
50
|
+
being proposed again; keep it. Fix lists and audit results go in commit messages or
|
|
51
|
+
`CHANGELOG.md`, not here.
|
|
279
52
|
|
|
280
|
-
|
|
53
|
+
## Data sources
|
|
281
54
|
|
|
282
|
-
|
|
55
|
+
Each tool that holds product data is one entity page, `id: SOURCE.<NAME>`, with a
|
|
56
|
+
`## How to query` section and a `SOURCE.<NAME>.ACCESS` row: `mcp: <server>`,
|
|
57
|
+
`skill: <name>`, `cli: <command>` (also listed in `doctor.tools`) or `manual: <where>`.
|
|
58
|
+
Queries are read-only, one or two per claim, with a row limit. Save counts and
|
|
59
|
+
rates to `raw/YYYY-MM-DD-<source>-<slug>.md`, never rows that name a person. Never
|
|
60
|
+
put a key or token on the page.
|
|
283
61
|
|
|
284
|
-
##
|
|
62
|
+
## Not for
|
|
285
63
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
- **Not for chat logs.** If a chat produces a decision, write a `decision` page; throw the chat away.
|
|
289
|
-
- **Not for drafts of code.** If it would be a comment, it's a comment.
|
|
290
|
-
|
|
291
|
-
---
|
|
64
|
+
Code docs (they go next to the code) · tasks (`tasks.md`) · plans (`features/`) ·
|
|
65
|
+
chat logs · session notes · changelogs.
|
|
292
66
|
|
|
293
|
-
##
|
|
67
|
+
## Checked by `zeno check`
|
|
294
68
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
wiki describes what is, not what is planned. The repo root holds no
|
|
299
|
-
product-knowledge `.md` files.
|
|
69
|
+
`wiki-frontmatter` (last-verified, placeholders, stale > 90 days) · `context-size`
|
|
70
|
+
(caps, dated decision names, no `flows/`) · `canonical-values` (a
|
|
71
|
+
price in a decision or screen note is held by an entity) · `sources`.
|