@freetesh/zeno 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "zeno",
4
- "version": "0.12.0",
4
+ "version": "0.13.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
@@ -162,34 +162,33 @@ wiki:
162
162
  context:
163
163
  folders: # added to the ten .context folders
164
164
  - { folder: research, reason: interview notes, kept apart from raw/ }
165
- copies: # files every product copies from one source repo
165
+ copies: # files products copy from one source repo, in named sets
166
166
  source: charades # a folder beside the estate config
167
- files:
168
- - scripts/play-upload.mjs # same path; a product carries it or skips it
169
- - { from: .context/designs/mobile/index.html, to: [.context/designs/mobile/index.html, .context/designs/web/index.html], named: true }
170
- - { from: .context/designs/mobile/shared.css, to: [.context/designs/mobile/shared.css], until: "PALETTE — " }
167
+ sets:
168
+ play-upload: [scripts/play-upload.mjs] # the same path on both sides
169
+ design-shell-web:
170
+ - { from: .context/designs/mobile/index.html, to: .context/designs/web/index.html, named: true }
171
+ - { from: .context/designs/mobile/shared.css, to: .context/designs/web/shared.css, until: "PALETTE — " }
171
172
  ```
172
173
 
173
174
  ```yaml
174
175
  # <repo>/.claude/zeno.yaml — written by `zeno init`
175
- zeno: 0.11.0
176
+ zeno: 0.13.0
176
177
  kind: expo-app # expo-app | web | content
177
178
  modules: [docs, knowledge, code, listing] # all optional; design is added when .context/designs exists, metrics only when asked
178
179
  packs: [expo]
179
- checks:
180
- stale-copies: { enabled: false, reason: "Cursor is still used here" }
181
180
  weigh:
182
181
  weights: { C-05: 2 }
183
182
  name: Chitragupt # how the product is written; `named` copies swap it in
184
183
  copies:
185
- skip: # shared files this product does without, or keeps its own of
186
- - { file: scripts/release-android.mjs, reason: built on EAS from a tag }
184
+ use: [play-upload, design-shell-web] # the sets this product uses; only these are held to the source
187
185
  ```
188
186
 
189
187
  Scalars: the most specific layer wins. `packs` and `standards` add up across
190
188
  layers; `commands`, `doctor.tools`, `wiki.types` and `context.folders` merge
191
- by name. Built-in types and folders cannot be removed or redefined. A check turned off must
192
- carry a `reason:`, and `zeno check` lists every override.
189
+ by name; `copies.use` adds up. Built-in types and folders cannot be removed or
190
+ redefined. A repo names only what it uses: a check applies through the modules
191
+ it turns on, and there is no setting to skip one.
193
192
 
194
193
  ## Standards
195
194
 
@@ -17,6 +17,7 @@ export const commandChecks = {
17
17
  run({ repo, config }) {
18
18
  return config.commands
19
19
  .filter((command) => !command.if || existsSync(join(repo, command.if)))
20
+ .filter((command) => !command.module || config.modules.includes(command.module))
20
21
  .flatMap((command) => {
21
22
  const result = spawnSync("sh", ["-c", command.run], { cwd: repo, encoding: "utf8" });
22
23
  if (result.error) {
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { basename, dirname, join, normalize } from "node:path";
4
+ import { usedCopies } from "./shared-files.js";
4
5
  // A `.context/` path not preceded by something that makes it part of a longer one (another
5
6
  // product's, an absolute path, a URL). A leading `./` means "beside this file".
6
7
  const PATH = /(?<![\w/~.-])(\.\/)?(\.context\/[\w./@+-]*)/g;
@@ -28,7 +29,8 @@ function literalPart(token, restOfLine) {
28
29
  }
29
30
  export const refs = {
30
31
  id: "refs",
31
- module: null,
32
+ // A module, not always on: a tool whose docs describe other repos' .context/ (zeno, canon) does not use it.
33
+ module: "refs",
32
34
  description: "every .context/ path a tracked file names exists; a [[link]] names a page (a link to a page not yet written is a note)",
33
35
  run({ repo, config }) {
34
36
  const listed = git(repo, ["ls-files", "-z"]);
@@ -36,7 +38,7 @@ export const refs = {
36
38
  return [];
37
39
  const files = listed.stdout.split("\0").filter((file) => file && !BINARY.test(file));
38
40
  // A copy of the source repo's file carries the source's paths; the product cannot edit it.
39
- const copied = new Set(config.copies.files.flatMap((file) => (typeof file === "string" ? [file] : (file.to ?? [file.from]))));
41
+ const copied = new Set(usedCopies(config).map((pair) => pair.to));
40
42
  const pages = new Set(files.filter((f) => f.startsWith(".context/wiki/") && f.endsWith(".md")).map((f) => basename(f, ".md")));
41
43
  const features = new Set(files.flatMap((f) => (FEATURE_FILE.exec(f) ? [`FEAT-${FEATURE_FILE.exec(f)[1]}`] : [])));
42
44
  const resolves = (target) => pages.has(target) || features.has(target) || existsSync(join(repo, ".context", `${target}.md`));
@@ -3,10 +3,10 @@ import { basename, join, resolve } from "node:path";
3
3
  import { loadConfig } from "../config/load.js";
4
4
  // zeno init writes the README heading `# .context — <Name>`; a product without a `name:` is read from it.
5
5
  const README_NAME = /^# \.context — (.+)$/m;
6
- function pairsOf(file) {
7
- if (typeof file === "string")
8
- return [{ from: file, to: file, required: true, named: false }];
9
- return (file.to ?? [file.from]).map((to) => ({ from: file.from, to, required: false, named: file.named ?? false, until: file.until }));
6
+ const pairOf = (set, file) => typeof file === "string" ? { set, from: file, to: file, named: false } : { set, from: file.from, to: file.to ?? file.from, named: file.named ?? false, until: file.until };
7
+ /** Every file of every set the product uses, as source → product paths. */
8
+ export function usedCopies(config) {
9
+ return config.copies.use.flatMap((set) => (config.copies.sets[set] ?? []).map((file) => pairOf(set, file)));
10
10
  }
11
11
  function nameOf(repo, config) {
12
12
  if (config.name)
@@ -14,82 +14,70 @@ function nameOf(repo, config) {
14
14
  const readme = join(repo, ".context", "README.md");
15
15
  return existsSync(readme) ? README_NAME.exec(readFileSync(readme, "utf8"))?.[1]?.trim() : undefined;
16
16
  }
17
- function above(text, marker) {
17
+ function split(text, marker) {
18
18
  const lines = text.split("\n");
19
19
  const at = lines.findIndex((line) => line.includes(marker));
20
- return (at === -1 ? lines : lines.slice(0, at)).join("\n");
20
+ return at === -1 ? null : { shared: lines.slice(0, at).join("\n"), own: lines.slice(at).join("\n") };
21
21
  }
22
- function below(text, marker) {
23
- const lines = text.split("\n");
24
- const at = lines.findIndex((line) => line.includes(marker));
25
- return at === -1 ? "" : lines.slice(at).join("\n");
26
- }
27
- /** Compares every shared file of `repo` with the source repo. Skipped files and files absent from the source are left out. */
22
+ /** Compares every file of every set the product uses with the source repo. */
28
23
  export function compareCopies(repo, config) {
29
- const { source, files, skip } = config.copies;
30
- if (!source || files.length === 0 || resolve(source) === resolve(repo))
24
+ const { source, sets, use } = config.copies;
25
+ if (use.length === 0 || !source || resolve(source) === resolve(repo))
31
26
  return [];
32
- const skipped = new Set(skip.map((entry) => entry.file));
27
+ const unknown = use.filter((set) => !sets[set]).map((set) => ({ pair: { set, from: "", to: "", named: false }, state: "unknown-set" }));
33
28
  const productName = nameOf(repo, config);
34
29
  const sourceName = nameOf(source, loadConfig(source).config);
35
- const states = [];
36
- for (const pair of files.flatMap(pairsOf)) {
37
- const from = join(source, pair.from);
38
- const to = join(repo, pair.to);
39
- if (skipped.has(pair.to) || !existsSync(from))
40
- continue;
41
- if (!existsSync(to)) {
42
- if (pair.required)
43
- states.push({ pair, state: "missing" });
44
- continue;
45
- }
46
- if (pair.named && (!productName || !sourceName)) {
47
- states.push({ pair, state: "unnamed" });
48
- continue;
49
- }
50
- const original = readFileSync(from, "utf8");
51
- const current = readFileSync(to, "utf8");
52
- let want = pair.named ? original.replaceAll(sourceName, productName) : original;
53
- let have = current;
54
- if (pair.until) {
55
- want = above(want, pair.until);
56
- have = above(current, pair.until);
57
- }
58
- if (want === have)
59
- states.push({ pair, state: "same" });
60
- // Without the marker the product's own part cannot be told from the shared one, so nothing is written.
61
- else if (pair.until && !current.includes(pair.until))
62
- states.push({ pair, state: "drift" });
63
- else
64
- states.push({ pair, state: "drift", want: pair.until ? `${want}\n${below(current, pair.until)}` : want });
65
- }
66
- return states;
30
+ return [
31
+ ...unknown,
32
+ ...usedCopies(config).map((pair) => {
33
+ const from = join(source, pair.from);
34
+ const to = join(repo, pair.to);
35
+ if (!existsSync(from))
36
+ return { pair, state: "no-source" };
37
+ if (!existsSync(to))
38
+ return { pair, state: "missing" };
39
+ if (pair.named && (!productName || !sourceName))
40
+ return { pair, state: "unnamed" };
41
+ const original = pair.named ? readFileSync(from, "utf8").replaceAll(sourceName, productName) : readFileSync(from, "utf8");
42
+ const current = readFileSync(to, "utf8");
43
+ if (!pair.until)
44
+ return original === current ? { pair, state: "same" } : { pair, state: "drift", want: original };
45
+ const theirs = split(original, pair.until);
46
+ const ours = split(current, pair.until);
47
+ if (theirs && ours && theirs.shared === ours.shared)
48
+ return { pair, state: "same" };
49
+ // Without the marker the product's own part cannot be told from the shared one, so nothing is written.
50
+ return { pair, state: "drift", want: theirs && ours ? `${theirs.shared}\n${ours.own}` : undefined };
51
+ }),
52
+ ];
67
53
  }
68
54
  /** Overwrites each drifted copy with the source's. Never adds a missing file. Returns the paths written. */
69
55
  export function syncCopies(repo, config) {
70
- const written = [];
71
- for (const { pair, state, want } of compareCopies(repo, config)) {
56
+ return compareCopies(repo, config).flatMap(({ pair, state, want }) => {
72
57
  if (state !== "drift" || want === undefined)
73
- continue;
58
+ return [];
74
59
  writeFileSync(join(repo, pair.to), want);
75
- written.push(pair.to);
76
- }
77
- return written;
60
+ return [pair.to];
61
+ });
78
62
  }
79
63
  export const sharedFiles = {
80
64
  id: "shared-files",
81
65
  module: null,
82
- description: "files copied from the copies.source repo are identical to it; one a product does without is skipped with a reason",
66
+ description: "every file of every copies set the product uses exists and matches copies.source",
83
67
  run({ repo, config }) {
84
- const source = config.copies.source ? basename(config.copies.source) : "";
68
+ const source = config.copies.source ? basename(config.copies.source) : "copies.source";
85
69
  return compareCopies(repo, config).flatMap(({ pair, state }) => {
86
- const finding = (message) => [{ check: "shared-files", level: "error", file: pair.to, message }];
70
+ const finding = (file, message) => [{ check: "shared-files", level: "error", file, message }];
71
+ if (state === "unknown-set")
72
+ return finding(".claude/zeno.yaml", `copies.use names ${pair.set}, which no copies.sets defines`);
73
+ if (state === "no-source")
74
+ return finding(pair.to, `${source} has no ${pair.from} (set ${pair.set})`);
87
75
  if (state === "missing")
88
- return finding(`missing — copy it from ${source}, or add it under copies.skip with a reason`);
76
+ return finding(pair.to, `missing — set ${pair.set} is in use; copy it from ${source}`);
89
77
  if (state === "unnamed")
90
- return finding(`no product name to compare with — set name: in zeno.yaml`);
78
+ return finding(pair.to, "no product name to compare with — set name: in zeno.yaml");
91
79
  if (state === "drift")
92
- return finding(`differs from ${source}${pair.until ? " above the shared block's end" : ""} — \`zeno copies --sync\` copies it`);
80
+ return finding(pair.to, `differs from ${source}${pair.until ? " above the shared block's end" : ""} — \`zeno copies --sync\` copies it`);
93
81
  return [];
94
82
  });
95
83
  },
@@ -14,13 +14,13 @@ function accessValue(body, id) {
14
14
  export const sources = {
15
15
  id: "sources",
16
16
  module: "docs",
17
- description: "each SOURCE.* page says how to read it (mcp, skill, manual, or a cli in doctor.tools) and has a How to query section",
17
+ description: "each SOURCE.* entity page says how to read it (mcp, skill, manual, or a cli in doctor.tools) and has a How to query section",
18
18
  run({ repo, config }) {
19
19
  const tools = new Set(config.doctor.tools.map((tool) => tool.tool));
20
20
  return wikiPages(repo).flatMap((file) => {
21
21
  const { data, body } = parseFrontmatter(readFileSync(join(repo, file), "utf8"));
22
22
  const id = String(data?.id ?? "");
23
- if (data?.type !== "entity" || !id.startsWith("SOURCE."))
23
+ if (!file.startsWith(".context/wiki/entities/") || !id.startsWith("SOURCE."))
24
24
  return [];
25
25
  const findings = [];
26
26
  const add = (message) => findings.push({ check: "sources", level: "error", file, message });
@@ -85,7 +85,6 @@ export function loadConfig(repo, options = {}) {
85
85
  standards: [...new Set(layers.flatMap((layer) => (layer.data.standards ?? []).map((dir) => resolve(layer.base, dir))))],
86
86
  commands: mergeBy(pick("commands").map((list) => list ?? []), (command) => command.id),
87
87
  doctor: { tools: mergeBy(pick("doctor").map((doctor) => doctor?.tools ?? []), (tool) => tool.tool) },
88
- checks: Object.assign({}, ...pick("checks")),
89
88
  weigh: { weights: Object.assign({}, ...pick("weigh").map((weigh) => weigh?.weights)) },
90
89
  wiki: { types: mergeBy(pick("wiki").map((wiki) => wiki?.types ?? []), (entry) => entry.type) },
91
90
  context: { folders: mergeBy(pick("context").map((context) => context?.folders ?? []), (entry) => entry.folder) },
@@ -93,8 +92,8 @@ export function loadConfig(repo, options = {}) {
93
92
  name: lastDefined(pick("name")),
94
93
  copies: {
95
94
  source: lastDefined(layers.map((layer) => (layer.data.copies?.source ? resolve(layer.base, layer.data.copies.source) : undefined))),
96
- files: lastDefined(pick("copies").map((copies) => copies?.files)) ?? [],
97
- skip: mergeBy(pick("copies").map((copies) => copies?.skip ?? []), (entry) => entry.file),
95
+ sets: Object.assign({}, ...pick("copies").map((copies) => copies?.sets)),
96
+ use: [...new Set(pick("copies").flatMap((copies) => copies?.use ?? []))],
98
97
  },
99
98
  };
100
99
  if (config.name === undefined)
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export const KINDS = ["expo-app", "web", "content"];
3
- export const MODULES = ["docs", "design", "knowledge", "metrics", "code", "architecture", "listing"];
3
+ export const MODULES = ["docs", "design", "knowledge", "metrics", "code", "architecture", "listing", "refs"];
4
4
  /**
5
5
  * The wiki page types every repo has, and the folder each lives in. A repo adds more under `wiki.types`.
6
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.
@@ -18,11 +18,6 @@ export const REMOVED_WIKI_FOLDERS = {
18
18
  };
19
19
  /** The `.context/` folders every repo may have. A repo adds more under `context.folders`. */
20
20
  export const CONTEXT_FOLDERS = ["raw", "wiki", "features", "designs", "listing", "marketing", "metrics", "support", "legal", "ops"];
21
- const checkOverride = z
22
- .object({ enabled: z.boolean().default(true), reason: z.string().min(1).optional() })
23
- .refine((check) => check.enabled || check.reason !== undefined, {
24
- message: "a check turned off needs a `reason:` — an override nobody can explain is drift",
25
- });
26
21
  const principleId = z.string().regex(/^C-\d{2}$/, "principle IDs look like C-07");
27
22
  const command = z
28
23
  .object({
@@ -31,6 +26,8 @@ const command = z
31
26
  run: z.string().min(1),
32
27
  /** Only run when this path exists in the repo. */
33
28
  if: z.string().optional(),
29
+ /** Only run in a repo that uses this module. */
30
+ module: z.enum(MODULES).optional(),
34
31
  })
35
32
  .strict();
36
33
  const kebab = z.string().regex(/^[a-z0-9-]+$/, "use lowercase-kebab");
@@ -49,17 +46,13 @@ const wikiType = z
49
46
  message: "a built-in wiki type or its folder cannot be redefined — add a new one",
50
47
  });
51
48
  const contextFolder = z.object({ folder: kebab, reason }).strict();
52
- /**
53
- * A file every product carries a copy of. A plain path is the same path on both sides and every
54
- * product must carry it or skip it with a reason; an object maps one source file onto product
55
- * paths that exist only where the product has that part (a design surface, say).
56
- */
49
+ /** A file copied from the source repo: the same path on both sides, or `from` one path `to` another. */
57
50
  const copyFile = z.union([
58
51
  z.string().min(1),
59
52
  z
60
53
  .object({
61
54
  from: z.string().min(1),
62
- to: z.array(z.string().min(1)).min(1).optional(),
55
+ to: z.string().min(1).optional(),
63
56
  /** Identical except that the source product's name reads as this product's. */
64
57
  named: z.boolean().optional(),
65
58
  /** Compared only above the first line containing this text; below it is the product's own. */
@@ -67,7 +60,6 @@ const copyFile = z.union([
67
60
  })
68
61
  .strict(),
69
62
  ]);
70
- const copySkip = z.object({ file: z.string().min(1), reason }).strict();
71
63
  const stage = z
72
64
  .object({
73
65
  id: kebab,
@@ -92,7 +84,6 @@ export const layerSchema = z
92
84
  standards: z.array(z.string()),
93
85
  commands: z.array(command),
94
86
  doctor: z.object({ tools: z.array(tool) }).partial(),
95
- checks: z.record(z.string(), checkOverride),
96
87
  weigh: z.object({ weights: z.record(principleId, z.number().nonnegative()) }).partial(),
97
88
  wiki: z.object({ types: z.array(wikiType) }).partial(),
98
89
  context: z.object({ folders: z.array(contextFolder) }).partial(),
@@ -100,13 +91,16 @@ export const layerSchema = z
100
91
  pipeline: z.array(stage),
101
92
  /** How the product is written in prose, for files shared with only the name changed. */
102
93
  name: z.string().min(1),
103
- /** Files copied from one source repo into every product, and the ones a product does without. */
104
- copies: z.object({ source: z.string().min(1), files: z.array(copyFile), skip: z.array(copySkip) }).partial().strict(),
94
+ /**
95
+ * Files copied from one source repo. The estate names sets of them; a product lists the sets it
96
+ * uses, and only those are held equal to the source.
97
+ */
98
+ copies: z.object({ source: z.string().min(1), sets: z.record(kebab, z.array(copyFile).min(1)), use: z.array(kebab) }).partial().strict(),
105
99
  })
106
100
  .partial()
107
101
  .strict();
108
102
  export const MODULES_BY_KIND = {
109
- "expo-app": ["docs", "knowledge", "code", "listing"],
110
- web: ["docs", "knowledge", "code"],
111
- content: ["docs", "knowledge"],
103
+ "expo-app": ["docs", "knowledge", "code", "listing", "refs"],
104
+ web: ["docs", "knowledge", "code", "refs"],
105
+ content: ["docs", "knowledge", "refs"],
112
106
  };
@@ -18,7 +18,7 @@ export function formatReports(reports, options = {}) {
18
18
  errors += count.errors;
19
19
  warnings += count.warnings;
20
20
  const clean = report.findings.length === 0;
21
- if (clean && options.quiet && report.overrides.length === 0)
21
+ if (clean && options.quiet)
22
22
  continue;
23
23
  const summary = clean ? "ok" : `${plural(count.errors, "error")}, ${plural(count.warnings, "warning")}`;
24
24
  lines.push(`${report.name} ${summary}`);
@@ -26,8 +26,6 @@ export function formatReports(reports, options = {}) {
26
26
  const message = finding.message.replace(/\n/g, "\n ");
27
27
  lines.push(` ${MARK[finding.level]} ${finding.check} ${finding.file ?? ""} ${message}`.replace(/ +$/, ""));
28
28
  }
29
- for (const override of report.overrides)
30
- lines.push(` override ${override.check}: ${override.reason}`);
31
29
  }
32
30
  lines.push(`${plural(reports.length, "product")} · ${plural(errors, "error")} · ${plural(warnings, "warning")}`);
33
31
  return lines.join("\n");
package/cli/dist/init.js CHANGED
@@ -69,7 +69,7 @@ const FIRST_FILE = {
69
69
  function template(file) {
70
70
  switch (file) {
71
71
  case ".context/README.md":
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";
72
+ return "# .context\n\nProduct material, in the zeno context layout. Start at [`tasks.md`](tasks.md).\n";
73
73
  case ".context/tasks.md":
74
74
  return "# Tasks\n\n## Now\n\n## Blocked\n\n## Waiting\n";
75
75
  case ".context/wiki/index.md":
package/cli/dist/run.js CHANGED
@@ -4,7 +4,7 @@ import { ALL_CHECKS } from "./checks/index.js";
4
4
  import { loadConfig } from "./config/load.js";
5
5
  import { isDirectory } from "./lib/files.js";
6
6
  export function runChecks(repo, options) {
7
- const report = { repo, name: basename(repo), findings: [], skipped: [], overrides: [] };
7
+ const report = { repo, name: basename(repo), findings: [], skipped: [] };
8
8
  if (!isDirectory(repo)) {
9
9
  report.findings.push({ check: "repo", level: "error", message: `no folder at ${repo}` });
10
10
  return report;
@@ -18,11 +18,6 @@ export function runChecks(repo, options) {
18
18
  return report;
19
19
  }
20
20
  for (const check of options.checks ?? ALL_CHECKS) {
21
- const override = config.checks[check.id];
22
- if (override && !override.enabled) {
23
- report.overrides.push({ check: check.id, reason: override.reason ?? "" });
24
- continue;
25
- }
26
21
  if (check.module && !config.modules.includes(check.module)) {
27
22
  report.skipped.push({ check: check.id, why: `module ${check.module} is off` });
28
23
  continue;
@@ -21,7 +21,6 @@ export function buildStatus(repo, options) {
21
21
  `kind ${config.kind}`,
22
22
  `modules ${config.modules.join(", ")}`,
23
23
  `config ${configLine}`,
24
- `checks ${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}` +
25
- (report.overrides.length > 0 ? `, ${report.overrides.length} override(s)` : ""),
24
+ `checks ${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}`,
26
25
  ].join("\n");
27
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freetesh/zeno",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "zeno CLI: keep your domain knowledge and AI rules in your repo, and enforce them.",
5
5
  "license": "MIT",
6
6
  "author": "Ritesh Firodiya",
@@ -7,7 +7,7 @@ folder.
7
7
 
8
8
  ```
9
9
  <repo>/.context/
10
- ├── README.md what this is, and which folders are absent and why
10
+ ├── README.md what this is, and where to start
11
11
  ├── tasks.md the ordered queue. The top item is the next action.
12
12
  ├── raw/ sources you didn't write. Immutable.
13
13
  ├── wiki/ everything you concluded. Schema: wiki-schema.md
@@ -30,7 +30,8 @@ folder.
30
30
  with the reason: `context: { folders: [{ folder: research, reason: … }] }`.
31
31
  The ten cannot be removed.
32
32
  2. **`README.md` and `tasks.md` are the only files at `.context/` root.**
33
- `README.md` lists every absent folder as `n/a — <why>`.
33
+ A repo names only what it uses. Its README, its `zeno.yaml` and its docs never
34
+ list a folder, file or tool it does without; absence is shown by absence.
34
35
  3. **Nothing is created empty.** A folder appears with its first file — git does
35
36
  not track empty folders, so a placeholder disappears on clone. `raw/` is the
36
37
  one exception and carries a `.gitkeep`.