@freetesh/canon 0.15.0 → 0.16.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 (57) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +17 -2
  4. package/cli/dist/api.d.ts +2 -2
  5. package/cli/dist/api.js +2 -2
  6. package/cli/dist/checks/{external.js → commands.js} +2 -0
  7. package/cli/dist/checks/index.js +4 -3
  8. package/cli/dist/checks/stale-copies.d.ts +2 -0
  9. package/cli/dist/checks/stale-copies.js +18 -0
  10. package/cli/dist/checks/{copies.d.ts → standards-copies.d.ts} +0 -1
  11. package/cli/dist/checks/{copies.js → standards-copies.js} +0 -16
  12. package/cli/dist/commands/add.d.ts +1 -1
  13. package/cli/dist/commands/add.js +1 -1
  14. package/cli/dist/commands/check.d.ts +1 -1
  15. package/cli/dist/commands/check.js +2 -2
  16. package/cli/dist/commands/copies.d.ts +1 -1
  17. package/cli/dist/commands/hooks.d.ts +1 -1
  18. package/cli/dist/commands/hooks.js +2 -2
  19. package/cli/dist/commands/init.d.ts +1 -1
  20. package/cli/dist/commands/init.js +2 -2
  21. package/cli/dist/commands/learn.d.ts +1 -1
  22. package/cli/dist/commands/learn.js +2 -2
  23. package/cli/dist/commands/pipeline.d.ts +1 -1
  24. package/cli/dist/{search → commands}/search.js +1 -1
  25. package/cli/dist/commands/standards.d.ts +1 -1
  26. package/cli/dist/commands/status.d.ts +1 -1
  27. package/cli/dist/commands/sync.d.ts +1 -1
  28. package/cli/dist/commands/sync.js +1 -1
  29. package/cli/dist/commands/ui.d.ts +1 -1
  30. package/cli/dist/commands/ui.js +2 -2
  31. package/cli/dist/config/load.js +16 -4
  32. package/cli/dist/config/schema.d.ts +21 -2
  33. package/cli/dist/config/schema.js +12 -2
  34. package/cli/dist/format.d.ts +1 -1
  35. package/cli/dist/index.js +2 -2
  36. package/cli/dist/init.js +2 -2
  37. package/cli/dist/packs.d.ts +3 -1
  38. package/cli/dist/status.d.ts +1 -1
  39. package/cli/dist/status.js +1 -1
  40. package/package.json +2 -3
  41. package/packs/expo/pack.yaml +1 -1
  42. package/packs/firebase/pack.yaml +1 -1
  43. package/packs/react/pack.yaml +1 -1
  44. package/packs/revenuecat/pack.yaml +1 -1
  45. package/packs/uniwind/pack.yaml +1 -1
  46. /package/cli/dist/{run.d.ts → check.d.ts} +0 -0
  47. /package/cli/dist/{run.js → check.js} +0 -0
  48. /package/cli/dist/checks/{external.d.ts → commands.d.ts} +0 -0
  49. /package/cli/dist/checks/{metrics.d.ts → metrics-definitions.d.ts} +0 -0
  50. /package/cli/dist/checks/{metrics.js → metrics-definitions.js} +0 -0
  51. /package/cli/dist/{search → commands}/mcp.d.ts +0 -0
  52. /package/cli/dist/{search → commands}/mcp.js +0 -0
  53. /package/cli/dist/{search → commands}/search.d.ts +0 -0
  54. /package/cli/dist/{commands → lib}/env.d.ts +0 -0
  55. /package/cli/dist/{commands → lib}/env.js +0 -0
  56. /package/cli/dist/{search → lib}/qmd.d.ts +0 -0
  57. /package/cli/dist/{search → lib}/qmd.js +0 -0
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "name": "canon",
9
9
  "source": "./",
10
- "version": "0.15.0",
10
+ "version": "0.16.0",
11
11
  "description": "Keeps your domain knowledge and AI rules in your repos and enforces them, so Claude hallucinates less."
12
12
  }
13
13
  ],
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "canon",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Keeps your domain knowledge and AI rules in your repos and enforces them, so Claude hallucinates less."
5
5
  }
package/README.md CHANGED
@@ -100,6 +100,7 @@ estate: true
100
100
  standards: [.canon/standards] # estate rules; same name replaces a canon default
101
101
  commands: # your own checkers, run by `canon check`
102
102
  - { id: designs, run: ./scripts/check-designs.sh, if: .context/designs, module: design }
103
+ - { id: designs-first, run: test -d .context/designs, kinds: [mobile, web] } # only repos that ship screens
103
104
  copies: # files products copy from one source repo, in named sets
104
105
  source: design-system
105
106
  sets:
@@ -110,8 +111,8 @@ copies: # files products copy from one source repo,
110
111
 
111
112
  ```yaml
112
113
  # <repo>/.claude/canon.yaml — written by `canon init`
113
- canon: 0.14.0
114
- kind: expo-app # expo-app | web | content
114
+ canon: 0.16.0
115
+ kind: mobile # mobile | web | mobile-and-web | package | content
115
116
  modules: [docs, knowledge, code, listing, refs]
116
117
  packs: [expo]
117
118
  name: Acme # how the product is written; `named` copies swap it in
@@ -138,6 +139,20 @@ evals/ plugin evals and the fixture product
138
139
  cli/ the canon CLI (TypeScript, vitest); `@freetesh/canon/api` for tools built on it
139
140
  ```
140
141
 
142
+ Inside `cli/src/`:
143
+
144
+ ```
145
+ index.ts the program: registers every command
146
+ api.ts what other tools import (`@freetesh/canon/api`)
147
+ commands/ one file per command — options and output only
148
+ checks/ one file per check, named by its id; index.ts lists them
149
+ config/ .claude/canon.yaml: schema, layered loading, estate discovery
150
+ lib/ shared helpers: files, frontmatter, env, qmd, system calls
151
+ <name>.ts the logic the commands call: check (+ format for its output), init, packs (add), sync, …
152
+ ```
153
+
154
+ Tests mirror it in `cli/test/`.
155
+
141
156
  ## Develop
142
157
 
143
158
  `pnpm check` runs `canon check` on this repo, the typecheck and every test.
package/cli/dist/api.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { findEstate } from "./config/load.ts";
2
- export { findProducts } from "./run.ts";
3
- export { qmdQuery, qmdRefresh } from "./search/qmd.ts";
2
+ export { findProducts } from "./check.ts";
3
+ export { qmdQuery, qmdRefresh } from "./lib/qmd.ts";
package/cli/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // What other tools build on: the canon knowledge site reads repos the way `canon check --all` finds them.
2
2
  export { findEstate } from "./config/load.js";
3
- export { findProducts } from "./run.js";
4
- export { qmdQuery, qmdRefresh } from "./search/qmd.js";
3
+ export { findProducts } from "./check.js";
4
+ export { qmdQuery, qmdRefresh } from "./lib/qmd.js";
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { kindCovers } from "../config/schema.js";
4
5
  const MAX_OUTPUT_LINES = 15;
5
6
  function tail(text) {
6
7
  return text.trim().split("\n").slice(-MAX_OUTPUT_LINES).join("\n");
@@ -18,6 +19,7 @@ export const commandChecks = {
18
19
  return config.commands
19
20
  .filter((command) => !command.if || existsSync(join(repo, command.if)))
20
21
  .filter((command) => !command.module || config.modules.includes(command.module))
22
+ .filter((command) => !command.kinds || command.kinds.some((kind) => kindCovers(config.kind, kind)))
21
23
  .flatMap((command) => {
22
24
  const result = spawnSync("sh", ["-c", command.run], { cwd: repo, encoding: "utf8" });
23
25
  if (result.error) {
@@ -2,11 +2,12 @@ import { architecture } from "./architecture.js";
2
2
  import { canonicalValues } from "./canonical-values.js";
3
3
  import { contextLayout } from "./context-layout.js";
4
4
  import { contextSize } from "./context-size.js";
5
- import { staleCopies, standardsCopies } from "./copies.js";
6
- import { commandChecks } from "./external.js";
5
+ import { staleCopies } from "./stale-copies.js";
6
+ import { standardsCopies } from "./standards-copies.js";
7
+ import { commandChecks } from "./commands.js";
7
8
  import { features } from "./features.js";
8
9
  import { listing } from "./listing.js";
9
- import { metricsDefinitions } from "./metrics.js";
10
+ import { metricsDefinitions } from "./metrics-definitions.js";
10
11
  import { pipeline } from "./pipeline.js";
11
12
  import { refs } from "./refs.js";
12
13
  import { sharedFiles } from "./shared-files.js";
@@ -0,0 +1,2 @@
1
+ import type { Check } from "./types.ts";
2
+ export declare const staleCopies: Check;
@@ -0,0 +1,18 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ // Instruction files for agents other than Claude Code. They are copies of
4
+ // AGENTS.md that nobody reads, so they only drift.
5
+ const OTHER_AGENT_FILES = [".github/copilot-instructions.md", ".cursorrules", ".windsurfrules"];
6
+ export const staleCopies = {
7
+ id: "stale-copies",
8
+ module: null,
9
+ description: "no instruction files for agents that are not used (Copilot, Cursor, Windsurf)",
10
+ run({ repo }) {
11
+ return OTHER_AGENT_FILES.filter((file) => existsSync(join(repo, file))).map((file) => ({
12
+ check: "stale-copies",
13
+ level: "warn",
14
+ file,
15
+ message: "instructions for an agent not in use — delete, Claude reads AGENTS.md",
16
+ }));
17
+ },
18
+ };
@@ -1,3 +1,2 @@
1
1
  import type { Check } from "./types.ts";
2
- export declare const staleCopies: Check;
3
2
  export declare const standardsCopies: Check;
@@ -1,25 +1,9 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- // Instruction files for agents other than Claude Code. They are copies of
4
- // AGENTS.md that nobody reads, so they only drift.
5
- const OTHER_AGENT_FILES = [".github/copilot-instructions.md", ".cursorrules", ".windsurfrules"];
6
3
  // Fraction of a shared standard's lines that must appear in a product file
7
4
  // before it counts as a copy rather than a reference.
8
5
  const COPY_THRESHOLD = 0.6;
9
6
  const STANDARD_COPIES = [{ file: ".context/wiki/CLAUDE.md", standard: "standards/wiki-schema.md" }];
10
- export const staleCopies = {
11
- id: "stale-copies",
12
- module: null,
13
- description: "no instruction files for agents that are not used (Copilot, Cursor, Windsurf)",
14
- run({ repo }) {
15
- return OTHER_AGENT_FILES.filter((file) => existsSync(join(repo, file))).map((file) => ({
16
- check: "stale-copies",
17
- level: "warn",
18
- file,
19
- message: "instructions for an agent not in use — delete, Claude reads AGENTS.md",
20
- }));
21
- },
22
- };
23
7
  function meaningfulLines(text) {
24
8
  return new Set(text
25
9
  .split("\n")
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import { type Env } from "./env.ts";
2
+ import { type Env } from "../lib/env.ts";
3
3
  export declare function registerAdd(program: Command, env: Env): void;
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { CACHE_DIR, fetchText, runCommand } from "../lib/system.js";
3
3
  import { addPack, fetchPackDocs, findPack, listPacks } from "../packs.js";
4
- import { requireEstate } from "./env.js";
4
+ import { requireEstate } from "../lib/env.js";
5
5
  export function registerAdd(program, env) {
6
6
  program
7
7
  .command("add")
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import { type Env } from "./env.ts";
2
+ import { type Env } from "../lib/env.ts";
3
3
  export declare function registerCheck(program: Command, env: Env): void;
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { exitCode, formatReports } from "../format.js";
3
- import { findProducts, runChecks } from "../run.js";
4
- import { requireEstate } from "./env.js";
3
+ import { findProducts, runChecks } from "../check.js";
4
+ import { requireEstate } from "../lib/env.js";
5
5
  export function registerCheck(program, env) {
6
6
  program
7
7
  .command("check")
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerCopies(program: Command, env: Env): void;
@@ -1,5 +1,5 @@
1
1
  import type { Command } from "commander";
2
- import { type Env } from "./env.ts";
2
+ import { type Env } from "../lib/env.ts";
3
3
  export declare const HOOK = "#!/bin/sh\n# installed by `canon hooks install` \u2014 run it again rather than editing this file.\n# A machine without canon pushes unchecked, as it would without any hook.\ncommand -v canon >/dev/null 2>&1 || exit 0\n# The product is the folder that owns .git, not the folder being pushed from: a worktree shares these hooks.\nroot=$(cd \"$(git rev-parse --git-common-dir)/..\" && pwd) || exit 0\nexec canon check \"$root\"\n";
4
4
  /** Writes the pre-push hook into one repo. Returns a one-line outcome. */
5
5
  export declare function installHook(repo: string): string;
@@ -1,8 +1,8 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { chmodSync, existsSync, lstatSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
- import { findProducts } from "../run.js";
5
- import { requireEstate } from "./env.js";
4
+ import { findProducts } from "../check.js";
5
+ import { requireEstate } from "../lib/env.js";
6
6
  const MARK = "installed by `canon hooks install`";
7
7
  // Hooks written by the tools this command replaced (the estate script, then zeno); they are ours to overwrite.
8
8
  const OLD_MARKS = [MARK, "installed by ~/git/bin/install-hooks", "installed by `zeno hooks install`"];
@@ -1,3 +1,3 @@
1
1
  import { type Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerInit(program: Command, env: Env): void;
@@ -1,7 +1,7 @@
1
1
  import { basename, resolve } from "node:path";
2
2
  import * as prompts from "@clack/prompts";
3
3
  import { Option } from "commander";
4
- import { KINDS, MODULES } from "../config/schema.js";
4
+ import { KINDS, kindCovers, MODULES } from "../config/schema.js";
5
5
  import { RULES_FILES } from "../checks/architecture.js";
6
6
  import { applyInit, parseChoices, planInit } from "../init.js";
7
7
  import { listPacks } from "../packs.js";
@@ -29,7 +29,7 @@ async function askAnswers(inferred, packs) {
29
29
  });
30
30
  if (cancelled(modules))
31
31
  return null;
32
- const forKind = packs.filter((pack) => pack.kinds.includes(kind));
32
+ const forKind = packs.filter((pack) => pack.kinds.some((wanted) => kindCovers(kind, wanted)));
33
33
  if (forKind.length === 0)
34
34
  return { kind, modules, packs: [] };
35
35
  const chosen = await prompts.multiselect({
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import { type Env } from "./env.ts";
2
+ import { type Env } from "../lib/env.ts";
3
3
  export declare function registerLearn(program: Command, env: Env): void;
@@ -1,6 +1,6 @@
1
1
  import { proposals, readDecisions, readVerdicts, recordDecision, summarise } from "../learn.js";
2
- import { findProducts } from "../run.js";
3
- import { requireEstate } from "./env.js";
2
+ import { findProducts } from "../check.js";
3
+ import { requireEstate } from "../lib/env.js";
4
4
  const DEFAULT_THRESHOLD = 3;
5
5
  export function registerLearn(program, env) {
6
6
  program
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerPipeline(program: Command, env: Env): void;
@@ -1,4 +1,4 @@
1
- import { qmdQuery } from './qmd.js';
1
+ import { qmdQuery } from '../lib/qmd.js';
2
2
  export function registerSearch(program) {
3
3
  program
4
4
  .command('search')
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerStandards(program: Command, env: Env): void;
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerStatus(program: Command, env: Env): void;
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import type { Env } from "./env.ts";
2
+ import type { Env } from "../lib/env.ts";
3
3
  export declare function registerSync(program: Command, env: Env): void;
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { exitCode, formatReports } from "../format.js";
3
3
  import { CACHE_DIR, fetchText, runCommand } from "../lib/system.js";
4
- import { runChecks } from "../run.js";
4
+ import { runChecks } from "../check.js";
5
5
  import { syncRepo } from "../sync.js";
6
6
  export function registerSync(program, env) {
7
7
  program
@@ -1,3 +1,3 @@
1
1
  import type { Command } from "commander";
2
- import { type Env } from "./env.ts";
2
+ import { type Env } from "../lib/env.ts";
3
3
  export declare function registerUi(program: Command, env: Env): void;
@@ -4,9 +4,9 @@ import { join } from "node:path";
4
4
  import { architectureGraph } from "../checks/architecture.js";
5
5
  import { formatReports } from "../format.js";
6
6
  import { CACHE_DIR } from "../lib/system.js";
7
- import { findProducts, runChecks } from "../run.js";
7
+ import { findProducts, runChecks } from "../check.js";
8
8
  import { buildSite, quartzConfig } from "../ui.js";
9
- import { requireEstate } from "./env.js";
9
+ import { requireEstate } from "../lib/env.js";
10
10
  // Quartz v5 has no tagged release yet, so pin the exact commit: an unpinned
11
11
  // clone could change the site under us on the next fresh install.
12
12
  const QUARTZ_REPO = "https://github.com/jackyzha0/quartz";
@@ -36,14 +36,26 @@ function appRoots(repo) {
36
36
  const children = readdirSync(apps, { withFileTypes: true }).filter((entry) => entry.isDirectory());
37
37
  return [repo, ...children.map((entry) => join(apps, entry.name))];
38
38
  }
39
+ /** A root package.json that ships a CLI or exports a library. */
40
+ function isPackage(repo) {
41
+ const file = join(repo, "package.json");
42
+ if (!existsSync(file))
43
+ return false;
44
+ const pkg = JSON.parse(readFileSync(file, "utf8"));
45
+ return Boolean(pkg.bin || pkg.exports || pkg.main) && !pkg.private;
46
+ }
39
47
  export function inferKind(repo) {
40
48
  const roots = appRoots(repo);
41
49
  const anyHas = (markers) => roots.some((root) => markers.some((file) => existsSync(join(root, file))));
42
- if (anyHas(EXPO_MARKERS))
43
- return "expo-app";
44
- if (anyHas(WEB_MARKERS))
50
+ const mobile = anyHas(EXPO_MARKERS);
51
+ const web = anyHas(WEB_MARKERS);
52
+ if (mobile && web)
53
+ return "mobile-and-web";
54
+ if (mobile)
55
+ return "mobile";
56
+ if (web)
45
57
  return "web";
46
- return "content";
58
+ return isPackage(repo) ? "package" : "content";
47
59
  }
48
60
  export function inferModules(repo, kind) {
49
61
  const modules = MODULES_BY_KIND[kind];
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- export declare const KINDS: readonly ["expo-app", "web", "content"];
2
+ /** What a repo ships: an app to the stores, a site to the web, both, a package to a registry, or no code at all. */
3
+ export declare const KINDS: readonly ["mobile", "web", "mobile-and-web", "package", "content"];
3
4
  export declare const MODULES: readonly ["docs", "design", "knowledge", "metrics", "code", "architecture", "listing", "refs"];
4
5
  /**
5
6
  * The wiki page types every repo has, and the folder each lives in. A repo adds more under `wiki.types`.
@@ -26,6 +27,13 @@ declare const command: z.ZodObject<{
26
27
  metrics: "metrics";
27
28
  refs: "refs";
28
29
  }>>;
30
+ kinds: z.ZodOptional<z.ZodArray<z.ZodEnum<{
31
+ content: "content";
32
+ mobile: "mobile";
33
+ "mobile-and-web": "mobile-and-web";
34
+ package: "package";
35
+ web: "web";
36
+ }>>>;
29
37
  }, z.core.$strict>;
30
38
  declare const wikiType: z.ZodObject<{
31
39
  type: z.ZodString;
@@ -60,7 +68,9 @@ export declare const layerSchema: z.ZodObject<{
60
68
  canon: z.ZodOptional<z.ZodString>;
61
69
  kind: z.ZodOptional<z.ZodEnum<{
62
70
  content: "content";
63
- "expo-app": "expo-app";
71
+ mobile: "mobile";
72
+ "mobile-and-web": "mobile-and-web";
73
+ package: "package";
64
74
  web: "web";
65
75
  }>>;
66
76
  modules: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -92,6 +102,13 @@ export declare const layerSchema: z.ZodObject<{
92
102
  metrics: "metrics";
93
103
  refs: "refs";
94
104
  }>>;
105
+ kinds: z.ZodOptional<z.ZodArray<z.ZodEnum<{
106
+ content: "content";
107
+ mobile: "mobile";
108
+ "mobile-and-web": "mobile-and-web";
109
+ package: "package";
110
+ web: "web";
111
+ }>>>;
95
112
  }, z.core.$strict>>>;
96
113
  doctor: z.ZodOptional<z.ZodObject<{
97
114
  tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -178,4 +195,6 @@ export interface Config {
178
195
  };
179
196
  }
180
197
  export declare const MODULES_BY_KIND: Record<Kind, Module[]>;
198
+ /** A repo that ships both counts as mobile and as web: it gets both kinds' packs. */
199
+ export declare function kindCovers(kind: Kind, wanted: Kind): boolean;
181
200
  export {};
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- export const KINDS = ["expo-app", "web", "content"];
2
+ /** What a repo ships: an app to the stores, a site to the web, both, a package to a registry, or no code at all. */
3
+ export const KINDS = ["mobile", "web", "mobile-and-web", "package", "content"];
3
4
  export const MODULES = ["docs", "design", "knowledge", "metrics", "code", "architecture", "listing", "refs"];
4
5
  /**
5
6
  * The wiki page types every repo has, and the folder each lives in. A repo adds more under `wiki.types`.
@@ -28,6 +29,8 @@ const command = z
28
29
  if: z.string().optional(),
29
30
  /** Only run in a repo that uses this module. */
30
31
  module: z.enum(MODULES).optional(),
32
+ /** Only run in a repo of one of these kinds. */
33
+ kinds: z.array(z.enum(KINDS)).optional(),
31
34
  })
32
35
  .strict();
33
36
  const kebab = z.string().regex(/^[a-z0-9-]+$/, "use lowercase-kebab");
@@ -100,7 +103,14 @@ export const layerSchema = z
100
103
  .partial()
101
104
  .strict();
102
105
  export const MODULES_BY_KIND = {
103
- "expo-app": ["docs", "knowledge", "code", "listing", "refs"],
106
+ mobile: ["docs", "knowledge", "code", "listing", "refs"],
104
107
  web: ["docs", "knowledge", "code", "refs"],
108
+ "mobile-and-web": ["docs", "knowledge", "code", "listing", "refs"],
109
+ // A package's docs describe other repos' .context/ paths, so refs would flag them.
110
+ package: ["docs", "knowledge", "code"],
105
111
  content: ["docs", "knowledge", "refs"],
106
112
  };
113
+ /** A repo that ships both counts as mobile and as web: it gets both kinds' packs. */
114
+ export function kindCovers(kind, wanted) {
115
+ return kind === wanted || (kind === "mobile-and-web" && (wanted === "mobile" || wanted === "web"));
116
+ }
@@ -1,4 +1,4 @@
1
- import type { Report } from "./run.ts";
1
+ import type { Report } from "./check.ts";
2
2
  /** Compact, rtk-style text: one header per product, one line per finding, one total line. */
3
3
  export declare function formatReports(reports: Report[], options?: {
4
4
  quiet?: boolean;
package/cli/dist/index.js CHANGED
@@ -16,8 +16,8 @@ import { registerSync } from "./commands/sync.js";
16
16
  import { registerUi } from "./commands/ui.js";
17
17
  import { registerWeigh } from "./commands/weigh.js";
18
18
  import { findEstate } from "./config/load.js";
19
- import { registerMcp } from "./search/mcp.js";
20
- import { registerSearch } from "./search/search.js";
19
+ import { registerMcp } from "./commands/mcp.js";
20
+ import { registerSearch } from "./commands/search.js";
21
21
  import { canonVersion } from "./status.js";
22
22
  const env = {
23
23
  canonRoot: resolve(import.meta.dirname, "..", ".."),
package/cli/dist/init.js CHANGED
@@ -4,7 +4,7 @@ import { stringify } from "yaml";
4
4
  import { RULES_FILES } from "./checks/architecture.js";
5
5
  import { requiredContext } from "./checks/context-layout.js";
6
6
  import { inferKind, inferModules } from "./config/load.js";
7
- import { MODULES_BY_KIND } from "./config/schema.js";
7
+ import { kindCovers, MODULES_BY_KIND } from "./config/schema.js";
8
8
  export const ARCHITECTURE_BASE = ".dependency-cruiser.base.cjs";
9
9
  const ARCHITECTURE_TEMPLATES = join("templates", "dependency-cruiser");
10
10
  const WORKSPACE_PARENTS = ["apps", "packages"];
@@ -86,7 +86,7 @@ export function planInit(repo, answers) {
86
86
  }
87
87
  const kind = answers.kind ?? inferKind(repo);
88
88
  const modules = answers.modules ?? (answers.kind ? MODULES_BY_KIND[kind] : inferModules(repo, kind));
89
- const sdk = kind === "expo-app" ? detectExpoSdk(repo) : undefined;
89
+ const sdk = kindCovers(kind, "mobile") ? detectExpoSdk(repo) : undefined;
90
90
  const packs = answers.packs?.length ? { packs: answers.packs } : {};
91
91
  const config = { canon: answers.canonVersion, kind, modules, ...(sdk ? { expo: { sdk } } : {}), ...packs };
92
92
  const create = requiredContext(modules).filter((name) => !existsSync(join(repo, ".context", name))).map((name) => FIRST_FILE[name]);
@@ -4,7 +4,9 @@ declare const packSchema: z.ZodObject<{
4
4
  description: z.ZodString;
5
5
  kinds: z.ZodArray<z.ZodEnum<{
6
6
  content: "content";
7
- "expo-app": "expo-app";
7
+ mobile: "mobile";
8
+ "mobile-and-web": "mobile-and-web";
9
+ package: "package";
8
10
  web: "web";
9
11
  }>>;
10
12
  skill: z.ZodOptional<z.ZodString>;
@@ -1,3 +1,3 @@
1
- import { type RunOptions } from "./run.ts";
1
+ import { type RunOptions } from "./check.ts";
2
2
  export declare function canonVersion(canonRoot: string): string;
3
3
  export declare function buildStatus(repo: string, options: RunOptions): string;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loadConfig } from "./config/load.js";
4
- import { runChecks } from "./run.js";
4
+ import { runChecks } from "./check.js";
5
5
  export function canonVersion(canonRoot) {
6
6
  const manifest = join(canonRoot, ".claude-plugin", "plugin.json");
7
7
  if (!existsSync(manifest))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freetesh/canon",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "canon CLI: keep your domain knowledge and AI rules in your repo, and enforce them.",
5
5
  "license": "MIT",
6
6
  "author": "Ritesh Firodiya",
@@ -35,8 +35,7 @@
35
35
  "test": "vitest run --root cli",
36
36
  "coverage": "vitest run --root cli --coverage",
37
37
  "typecheck": "tsc -p cli",
38
- "prepack": "pnpm build",
39
- "release": "bash scripts/publish.sh"
38
+ "prepack": "pnpm build"
40
39
  },
41
40
  "dependencies": {
42
41
  "@clack/prompts": "^1.8.1",
@@ -1,5 +1,5 @@
1
1
  name: expo
2
2
  description: Expo / React Native — versioned SDK docs
3
- kinds: [expo-app]
3
+ kinds: [mobile]
4
4
  docs:
5
5
  - https://docs.expo.dev/llms.txt
@@ -1,5 +1,5 @@
1
1
  name: firebase
2
2
  description: Firebase — auth, Firestore, rules, functions
3
- kinds: [expo-app, web]
3
+ kinds: [mobile, web]
4
4
  docs:
5
5
  - https://firebase.google.com/docs/llms.txt
@@ -1,5 +1,5 @@
1
1
  name: react
2
2
  description: React — components, hooks, rendering
3
- kinds: [expo-app, web]
3
+ kinds: [mobile, web]
4
4
  docs:
5
5
  - https://react.dev/llms.txt
@@ -1,5 +1,5 @@
1
1
  name: revenuecat
2
2
  description: RevenueCat — subscriptions, paywalls, entitlements
3
- kinds: [expo-app, web]
3
+ kinds: [mobile, web]
4
4
  docs:
5
5
  - https://www.revenuecat.com/docs/llms.txt
@@ -1,5 +1,5 @@
1
1
  name: uniwind
2
2
  description: UniWind — Tailwind classes for React Native
3
- kinds: [expo-app]
3
+ kinds: [mobile]
4
4
  docs:
5
5
  - https://docs.uniwind.dev/llms.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes