@opencanon/canon 0.1.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/adapters/fs.d.ts +5 -0
  4. package/dist/adapters/fs.js +52 -0
  5. package/dist/adapters/fs.js.map +1 -0
  6. package/dist/adapters/github.d.ts +13 -0
  7. package/dist/adapters/github.js +63 -0
  8. package/dist/adapters/github.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +17 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/check.d.ts +2 -0
  13. package/dist/commands/check.js +43 -0
  14. package/dist/commands/check.js.map +1 -0
  15. package/dist/commands/init.d.ts +2 -0
  16. package/dist/commands/init.js +38 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/lock.d.ts +2 -0
  19. package/dist/commands/lock.js +74 -0
  20. package/dist/commands/lock.js.map +1 -0
  21. package/dist/commands/new.d.ts +2 -0
  22. package/dist/commands/new.js +66 -0
  23. package/dist/commands/new.js.map +1 -0
  24. package/dist/core/index.d.ts +2 -0
  25. package/dist/core/index.js +2 -0
  26. package/dist/core/index.js.map +1 -0
  27. package/dist/core/types.d.ts +104 -0
  28. package/dist/core/types.js +6 -0
  29. package/dist/core/types.js.map +1 -0
  30. package/dist/core/validate.d.ts +47 -0
  31. package/dist/core/validate.js +133 -0
  32. package/dist/core/validate.js.map +1 -0
  33. package/dist/templates/character.d.ts +1 -0
  34. package/dist/templates/character.js +8 -0
  35. package/dist/templates/character.js.map +1 -0
  36. package/dist/templates/conventions.d.ts +1 -0
  37. package/dist/templates/conventions.js +50 -0
  38. package/dist/templates/conventions.js.map +1 -0
  39. package/dist/templates/location.d.ts +1 -0
  40. package/dist/templates/location.js +8 -0
  41. package/dist/templates/location.js.map +1 -0
  42. package/dist/templates/metadata.d.ts +1 -0
  43. package/dist/templates/metadata.js +15 -0
  44. package/dist/templates/metadata.js.map +1 -0
  45. package/dist/test/hotfix.test.d.ts +1 -0
  46. package/dist/test/hotfix.test.js +64 -0
  47. package/dist/test/hotfix.test.js.map +1 -0
  48. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenCanon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @opencanon/canon
2
+
3
+ Canon worldbuilding CLI — scaffold, validate, and manage shared fiction universes.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @opencanon/canon
9
+ ```
10
+
11
+ ## CLI Usage
12
+
13
+ ```bash
14
+ canon init # Scaffold a new canon repo
15
+ canon new story <id> # Create a new story from template
16
+ canon new character <id> # Create a new character
17
+ canon new location <id> # Create a new location
18
+ canon check [dir] # Run compliance checks (exit 1 on failure)
19
+ canon lock [dir] # Regenerate canon.lock.json
20
+ ```
21
+
22
+ ## Library Usage
23
+
24
+ ```ts
25
+ import { validateRepo } from "@opencanon/canon"
26
+ import { loadRepoFromFs } from "@opencanon/canon/adapters/fs"
27
+ import { buildRepoModel } from "@opencanon/canon/adapters/github"
28
+ ```
29
+
30
+ ---
31
+
32
+ - [English docs](docs/en/README.md)
33
+ - [한국어 문서](docs/ko/README.md)
34
+
35
+ ## License
36
+
37
+ MIT
@@ -0,0 +1,5 @@
1
+ import type { RepoModel } from "../core/types.js";
2
+ /**
3
+ * Load a local canon repo into an I/O-free RepoModel.
4
+ */
5
+ export declare function loadRepoFromFs(repoRoot: string): RepoModel;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Filesystem adapter — reads a local canon repo into a RepoModel.
3
+ */
4
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ function listSubDirs(dirPath) {
7
+ if (!existsSync(dirPath))
8
+ return [];
9
+ return readdirSync(dirPath, { withFileTypes: true })
10
+ .filter(d => d.isDirectory())
11
+ .map(d => d.name);
12
+ }
13
+ function listCanonEntries(dirPath) {
14
+ if (!existsSync(dirPath))
15
+ return [];
16
+ return readdirSync(dirPath, { withFileTypes: true })
17
+ .map(d => d.isDirectory() ? d.name : d.name.replace(/\.json$/, ""))
18
+ .filter(name => name !== "index");
19
+ }
20
+ function readCanonLock(repoRoot) {
21
+ const lockPath = join(repoRoot, "canon.lock.json");
22
+ if (!existsSync(lockPath))
23
+ return null;
24
+ return JSON.parse(readFileSync(lockPath, "utf-8"));
25
+ }
26
+ function readStoryMetadata(storyDir) {
27
+ const metaPath = join(storyDir, "metadata.json");
28
+ if (!existsSync(metaPath))
29
+ return null;
30
+ const raw = JSON.parse(readFileSync(metaPath, "utf-8"));
31
+ return { meta: raw, raw };
32
+ }
33
+ /**
34
+ * Load a local canon repo into an I/O-free RepoModel.
35
+ */
36
+ export function loadRepoFromFs(repoRoot) {
37
+ const canonLock = readCanonLock(repoRoot);
38
+ const characters = new Set(listCanonEntries(join(repoRoot, "canon", "characters")));
39
+ const locations = new Set(listCanonEntries(join(repoRoot, "canon", "worldbuilding", "locations")));
40
+ const storiesDir = join(repoRoot, "stories");
41
+ const storyDirs = listSubDirs(storiesDir);
42
+ const episodes = new Set(storyDirs);
43
+ const stories = new Map();
44
+ for (const slug of storyDirs) {
45
+ const result = readStoryMetadata(join(storiesDir, slug));
46
+ if (result) {
47
+ stories.set(slug, result);
48
+ }
49
+ }
50
+ return { canonLock, characters, locations, episodes, stories };
51
+ }
52
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/adapters/fs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,SAAS,WAAW,CAAC,OAAe;IAClC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,OAAO,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SACjD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AACrB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe;IACvC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,OAAO,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SACjD,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;SAClE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CAAA;AACrC,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAA;IAClD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;AACpD,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAA;IAChD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,OAAO,EAAE,IAAI,EAAE,GAAoB,EAAE,GAAG,EAAE,CAAA;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACzC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;IACnF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IAElG,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;IAEnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiE,CAAA;IACxF,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAA;QACxD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;AAChE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * GitHub adapter — pure conversion from parsed GitHub API data to RepoModel.
3
+ * No I/O, no Octokit, no HTTP. Runs anywhere.
4
+ */
5
+ import type { RepoModel, GitHubRepoInput } from "../core/types.js";
6
+ /**
7
+ * Build a RepoModel from pre-fetched GitHub API data.
8
+ *
9
+ * Expects:
10
+ * - tree: parsed Trees API response entries
11
+ * - files: Map of path → file content string (for metadata.json, canon.lock.json, etc.)
12
+ */
13
+ export declare function buildRepoModel(input: GitHubRepoInput): RepoModel;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Build a RepoModel from pre-fetched GitHub API data.
3
+ *
4
+ * Expects:
5
+ * - tree: parsed Trees API response entries
6
+ * - files: Map of path → file content string (for metadata.json, canon.lock.json, etc.)
7
+ */
8
+ export function buildRepoModel(input) {
9
+ const { tree, files } = input;
10
+ // Parse canon.lock.json
11
+ let canonLock = null;
12
+ const lockContent = files.get("canon.lock.json");
13
+ if (lockContent) {
14
+ canonLock = JSON.parse(lockContent);
15
+ }
16
+ // Extract character IDs from tree paths: canon/characters/<id>/ (dir) or canon/characters/<id>.json (blob)
17
+ const characters = new Set();
18
+ for (const entry of tree) {
19
+ if (entry.path.startsWith("canon/characters/")) {
20
+ const parts = entry.path.split("/");
21
+ if (parts.length === 3) {
22
+ const name = entry.type === "tree" ? parts[2] : parts[2].replace(/\.json$/, "");
23
+ if (name !== "index") {
24
+ characters.add(name);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ // Extract location IDs from tree paths: canon/worldbuilding/locations/<id>.json or <id>/
30
+ const locations = new Set();
31
+ for (const entry of tree) {
32
+ if (entry.path.startsWith("canon/worldbuilding/locations/")) {
33
+ const parts = entry.path.split("/");
34
+ if (parts.length === 4) {
35
+ const name = entry.type === "tree" ? parts[3] : parts[3].replace(/\.json$/, "");
36
+ if (name !== "index") {
37
+ locations.add(name);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ // Extract episode slugs from tree: stories/<slug>/
43
+ const episodes = new Set();
44
+ for (const entry of tree) {
45
+ if (entry.type === "tree" && entry.path.startsWith("stories/")) {
46
+ const parts = entry.path.split("/");
47
+ if (parts.length === 2) {
48
+ episodes.add(parts[1]);
49
+ }
50
+ }
51
+ }
52
+ // Parse story metadata from pre-fetched file contents
53
+ const stories = new Map();
54
+ for (const slug of episodes) {
55
+ const metaContent = files.get(`stories/${slug}/metadata.json`);
56
+ if (metaContent) {
57
+ const raw = JSON.parse(metaContent);
58
+ stories.set(slug, { meta: raw, raw });
59
+ }
60
+ }
61
+ return { canonLock, characters, locations, episodes, stories };
62
+ }
63
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/adapters/github.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { checkCommand } from "./commands/check.js";
4
+ import { lockCommand } from "./commands/lock.js";
5
+ import { initCommand } from "./commands/init.js";
6
+ import { newCommand } from "./commands/new.js";
7
+ const program = new Command();
8
+ program
9
+ .name("canon")
10
+ .description("Canon worldbuilding CLI — scaffold, validate, and manage shared fiction universes")
11
+ .version("0.1.0");
12
+ program.addCommand(checkCommand);
13
+ program.addCommand(lockCommand);
14
+ program.addCommand(initCommand);
15
+ program.addCommand(newCommand);
16
+ program.parse();
17
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,mFAAmF,CAAC;KAChG,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;AAChC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;AAE9B,OAAO,CAAC,KAAK,EAAE,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const checkCommand: Command;
@@ -0,0 +1,43 @@
1
+ import { Command } from "commander";
2
+ import { resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { loadRepoFromFs } from "../adapters/fs.js";
5
+ import { validateRepo } from "../core/validate.js";
6
+ export const checkCommand = new Command("check")
7
+ .description("Run canon compliance checks against a repo")
8
+ .argument("[dir]", "repo root directory", ".")
9
+ .action((dir) => {
10
+ const repoRoot = resolve(dir);
11
+ if (!existsSync(repoRoot)) {
12
+ console.error(`Error: directory not found: ${repoRoot}`);
13
+ process.exit(1);
14
+ }
15
+ let model;
16
+ try {
17
+ model = loadRepoFromFs(repoRoot);
18
+ }
19
+ catch (err) {
20
+ console.error(`Error: failed to read repo: ${err instanceof Error ? err.message : err}`);
21
+ process.exit(1);
22
+ }
23
+ const report = validateRepo(model);
24
+ if (report.totalStories === 0) {
25
+ console.error("Error: no stories found in stories/");
26
+ process.exit(1);
27
+ }
28
+ for (const story of report.stories) {
29
+ const icon = story.allPass ? "\u2713" : "\u2717";
30
+ console.log(`${icon} ${story.storyId}`);
31
+ for (const c of story.checks) {
32
+ const mark = c.pass ? " \u2713" : " \u2717";
33
+ const msg = c.message ? ` — ${c.message}` : "";
34
+ console.log(`${mark} ${c.id}${msg}`);
35
+ }
36
+ }
37
+ console.log();
38
+ console.log(`${report.passingStories}/${report.totalStories} stories passing`);
39
+ if (report.passingStories < report.totalStories) {
40
+ process.exit(1);
41
+ }
42
+ });
43
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,4CAA4C,CAAC;KACzD,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,GAAG,CAAC;KAC7C,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IAE7B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAA;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,KAAK,CAAA;IACT,IAAI,CAAC;QACH,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAA;QACxF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;IAElC,IAAI,MAAM,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;QAChD,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QACvC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;YAC7C,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,YAAY,kBAAkB,CAAC,CAAA;IAE9E,IAAI,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const initCommand: Command;
@@ -0,0 +1,38 @@
1
+ import { Command } from "commander";
2
+ import { resolve, join } from "node:path";
3
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
4
+ import { conventionsTemplate } from "../templates/conventions.js";
5
+ export const initCommand = new Command("init")
6
+ .description("Scaffold a new canon worldbuilding repo")
7
+ .argument("[dir]", "target directory", ".")
8
+ .action((dir) => {
9
+ const root = resolve(dir);
10
+ const dirs = [
11
+ join(root, "canon", "characters"),
12
+ join(root, "canon", "worldbuilding", "locations"),
13
+ join(root, "stories"),
14
+ ];
15
+ for (const d of dirs) {
16
+ mkdirSync(d, { recursive: true });
17
+ }
18
+ const conventions = join(root, "CONVENTIONS.md");
19
+ if (!existsSync(conventions)) {
20
+ writeFileSync(conventions, conventionsTemplate());
21
+ }
22
+ const rcPath = join(root, ".canonrc.json");
23
+ if (!existsSync(rcPath)) {
24
+ const config = {
25
+ schema_version: "canonrc.v1",
26
+ author: "",
27
+ default_lang: "ko",
28
+ };
29
+ writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
30
+ }
31
+ console.log("Canon repo initialized:");
32
+ console.log(" canon/characters/");
33
+ console.log(" canon/worldbuilding/locations/");
34
+ console.log(" stories/");
35
+ console.log(" CONVENTIONS.md");
36
+ console.log(" .canonrc.json");
37
+ });
38
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAGjE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,yCAAyC,CAAC;KACtD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,CAAC;KAC1C,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE;IACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IAEzB,MAAM,IAAI,GAAG;QACX,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC;QACjC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,CAAC;QACjD,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;KACtB,CAAA;IAED,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnC,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;IAChD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,aAAa,CAAC,WAAW,EAAE,mBAAmB,EAAE,CAAC,CAAA;IACnD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;IAC1C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,MAAM,GAAgB;YAC1B,cAAc,EAAE,YAAY;YAC5B,MAAM,EAAE,EAAE;YACV,YAAY,EAAE,IAAI;SACnB,CAAA;QACD,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;IACtC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;IAClC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACzB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAC/B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;AAChC,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const lockCommand: Command;
@@ -0,0 +1,74 @@
1
+ import { Command } from "commander";
2
+ import { resolve, join, relative, sep } from "node:path";
3
+ import { readFileSync, readdirSync, writeFileSync, existsSync } from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+ import { execSync } from "node:child_process";
6
+ function collectFiles(dir) {
7
+ const results = [];
8
+ if (!existsSync(dir))
9
+ return results;
10
+ const entries = readdirSync(dir, { withFileTypes: true });
11
+ for (const entry of entries) {
12
+ const full = join(dir, entry.name);
13
+ if (entry.isDirectory()) {
14
+ results.push(...collectFiles(full));
15
+ }
16
+ else {
17
+ results.push(full);
18
+ }
19
+ }
20
+ return results;
21
+ }
22
+ export const lockCommand = new Command("lock")
23
+ .description("Regenerate canon.lock.json from current canon/ contents")
24
+ .argument("[dir]", "repo root directory", ".")
25
+ .action((dir) => {
26
+ const repoRoot = resolve(dir);
27
+ const canonDir = join(repoRoot, "canon");
28
+ if (!existsSync(canonDir)) {
29
+ console.error("Error: canon/ directory not found");
30
+ process.exit(1);
31
+ }
32
+ // git rev-parse HEAD — hard error on failure
33
+ let canonCommit;
34
+ try {
35
+ canonCommit = execSync("git rev-parse HEAD", {
36
+ cwd: repoRoot,
37
+ encoding: "utf-8",
38
+ stdio: ["pipe", "pipe", "pipe"],
39
+ }).trim();
40
+ }
41
+ catch {
42
+ console.error("Error: git rev-parse HEAD failed. canon.lock.json requires a commit ref.");
43
+ process.exit(1);
44
+ }
45
+ // Collect all files under canon/, normalize to forward-slash relative paths, sort
46
+ const absolutePaths = collectFiles(canonDir);
47
+ const relativePaths = absolutePaths
48
+ .map(p => relative(canonDir, p).split(sep).join("/"))
49
+ .sort();
50
+ // Hash: for each file in sorted order, feed relativePath + NUL + fileBytes + NUL
51
+ const hash = createHash("sha256");
52
+ for (const relPath of relativePaths) {
53
+ const absPath = join(canonDir, ...relPath.split("/"));
54
+ const bytes = readFileSync(absPath);
55
+ hash.update(relPath);
56
+ hash.update("\0");
57
+ hash.update(bytes);
58
+ hash.update("\0");
59
+ }
60
+ const worldbuildingHash = hash.digest("hex");
61
+ const lock = {
62
+ schema_version: "canon.lock.v1",
63
+ canon_commit: canonCommit,
64
+ worldbuilding_hash: worldbuildingHash,
65
+ hash_algo: "sha256",
66
+ generated_at: new Date().toISOString(),
67
+ };
68
+ const lockPath = join(repoRoot, "canon.lock.json");
69
+ writeFileSync(lockPath, JSON.stringify(lock, null, 2) + "\n");
70
+ console.log(`canon.lock.json updated`);
71
+ console.log(` commit: ${canonCommit}`);
72
+ console.log(` hash: ${worldbuildingHash}`);
73
+ });
74
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/commands/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const newCommand: Command;
@@ -0,0 +1,66 @@
1
+ import { Command } from "commander";
2
+ import { resolve, join } from "node:path";
3
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
4
+ import { metadataTemplate } from "../templates/metadata.js";
5
+ import { characterTemplate } from "../templates/character.js";
6
+ import { locationTemplate } from "../templates/location.js";
7
+ const SLUG_RE = /^[a-z0-9][a-z0-9_-]*$/;
8
+ function validateSlug(value) {
9
+ if (!SLUG_RE.test(value)) {
10
+ console.error(`Error: invalid id "${value}". Must match ${SLUG_RE} (lowercase alphanumeric, hyphens, underscores, no leading special chars).`);
11
+ process.exit(1);
12
+ }
13
+ if (value.includes("..")) {
14
+ console.error(`Error: id must not contain ".."`);
15
+ process.exit(1);
16
+ }
17
+ return value;
18
+ }
19
+ export const newCommand = new Command("new")
20
+ .description("Create a new story, character, or location from template")
21
+ .argument("<type>", "entity type: story, character, or location")
22
+ .argument("<id>", "slug/id for the new entity")
23
+ .option("-d, --dir <dir>", "repo root directory", ".")
24
+ .action((type, id, opts) => {
25
+ const slug = validateSlug(id);
26
+ const root = resolve(opts.dir);
27
+ switch (type) {
28
+ case "story": {
29
+ const storyDir = join(root, "stories", slug);
30
+ if (existsSync(storyDir)) {
31
+ console.error(`Error: stories/${slug} already exists`);
32
+ process.exit(1);
33
+ }
34
+ mkdirSync(storyDir, { recursive: true });
35
+ writeFileSync(join(storyDir, "metadata.json"), metadataTemplate(slug));
36
+ console.log(`Created stories/${slug}/metadata.json`);
37
+ break;
38
+ }
39
+ case "character": {
40
+ const charDir = join(root, "canon", "characters", slug);
41
+ if (existsSync(charDir)) {
42
+ console.error(`Error: canon/characters/${slug} already exists`);
43
+ process.exit(1);
44
+ }
45
+ mkdirSync(charDir, { recursive: true });
46
+ writeFileSync(join(charDir, "definition.json"), characterTemplate(slug));
47
+ console.log(`Created canon/characters/${slug}/definition.json`);
48
+ break;
49
+ }
50
+ case "location": {
51
+ const locPath = join(root, "canon", "worldbuilding", "locations", `${slug}.json`);
52
+ if (existsSync(locPath)) {
53
+ console.error(`Error: canon/worldbuilding/locations/${slug}.json already exists`);
54
+ process.exit(1);
55
+ }
56
+ mkdirSync(join(root, "canon", "worldbuilding", "locations"), { recursive: true });
57
+ writeFileSync(locPath, locationTemplate(slug));
58
+ console.log(`Created canon/worldbuilding/locations/${slug}.json`);
59
+ break;
60
+ }
61
+ default:
62
+ console.error(`Error: unknown type "${type}". Use: story, character, or location`);
63
+ process.exit(1);
64
+ }
65
+ });
66
+ //# sourceMappingURL=new.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export type { CanonLock, StoryMetadata, CharacterDefinition, LocationDefinition, RepoModel, GitHubTreeEntry, GitHubRepoInput, CheckId, CheckResult, StoryCheckReport, RepoCheckReport, CanonConfig, } from "./types.js";
2
+ export { checkCharacters, checkLocations, checkTimeline, checkContinuity, checkCanonVersion, checkMetadataSchema, validateStory, validateRepo, } from "./validate.js";
@@ -0,0 +1,2 @@
1
+ export { checkCharacters, checkLocations, checkTimeline, checkContinuity, checkCanonVersion, checkMetadataSchema, validateStory, validateRepo, } from "./validate.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,YAAY,GACb,MAAM,eAAe,CAAA"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Core types for canon worldbuilding validation.
3
+ * No protocol internals — these are platform-facing types only.
4
+ */
5
+ export interface CanonLock {
6
+ schema_version: "canon.lock.v1";
7
+ canon_commit: string;
8
+ worldbuilding_hash: string;
9
+ hash_algo: "sha256";
10
+ generated_at: string;
11
+ }
12
+ export interface StoryMetadata {
13
+ schema_version: "1.1";
14
+ canon_ref: string;
15
+ id: string;
16
+ episode: number;
17
+ title: {
18
+ ko: string;
19
+ en: string;
20
+ };
21
+ timeline: string;
22
+ synopsis: {
23
+ ko: string;
24
+ en: string;
25
+ };
26
+ characters: string[];
27
+ locations: string[];
28
+ themes?: string[];
29
+ canon_events?: string[];
30
+ canon_status: "canonical" | "non-canonical";
31
+ word_count?: {
32
+ ko?: number;
33
+ en?: number;
34
+ };
35
+ temporal_context?: {
36
+ prev_episode: string | null;
37
+ next_episode: string | null;
38
+ thematic_echoes?: string[];
39
+ };
40
+ }
41
+ export interface CharacterDefinition {
42
+ id: string;
43
+ name: {
44
+ ko: string;
45
+ en: string;
46
+ };
47
+ description?: {
48
+ ko?: string;
49
+ en?: string;
50
+ };
51
+ }
52
+ export interface LocationDefinition {
53
+ id: string;
54
+ name: {
55
+ ko: string;
56
+ en: string;
57
+ };
58
+ description?: {
59
+ ko?: string;
60
+ en?: string;
61
+ };
62
+ }
63
+ export interface RepoModel {
64
+ canonLock: CanonLock | null;
65
+ characters: Set<string>;
66
+ locations: Set<string>;
67
+ episodes: Set<string>;
68
+ stories: Map<string, {
69
+ meta: StoryMetadata;
70
+ raw: Record<string, unknown>;
71
+ }>;
72
+ }
73
+ export interface GitHubTreeEntry {
74
+ path: string;
75
+ type: "blob" | "tree";
76
+ sha: string;
77
+ }
78
+ export interface GitHubRepoInput {
79
+ tree: GitHubTreeEntry[];
80
+ files: Map<string, string>;
81
+ }
82
+ export type CheckId = "metadata_schema_valid" | "characters_valid" | "locations_valid" | "timeline_consistent" | "continuity_valid" | "canon_version_match";
83
+ export interface CheckResult {
84
+ id: CheckId;
85
+ pass: boolean;
86
+ message?: string;
87
+ }
88
+ export interface StoryCheckReport {
89
+ storyId: string;
90
+ checks: CheckResult[];
91
+ allPass: boolean;
92
+ }
93
+ export interface RepoCheckReport {
94
+ schemaVersion: "check.v1";
95
+ stories: StoryCheckReport[];
96
+ totalStories: number;
97
+ passingStories: number;
98
+ }
99
+ export interface CanonConfig {
100
+ schema_version: "canonrc.v1";
101
+ author: string;
102
+ default_lang: "ko" | "en";
103
+ repo_url?: string;
104
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Core types for canon worldbuilding validation.
3
+ * No protocol internals — these are platform-facing types only.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Canon compliance validation logic.
3
+ *
4
+ * Pure functions — no I/O, no side effects.
5
+ * Shared between CLI (local check) and adapter (engine observation).
6
+ */
7
+ import type { StoryMetadata, CanonLock, CheckResult, StoryCheckReport, RepoCheckReport, RepoModel } from "./types.js";
8
+ /**
9
+ * Validate that all declared characters exist in the canon.
10
+ */
11
+ export declare function checkCharacters(meta: StoryMetadata, knownCharacters: ReadonlySet<string>): CheckResult;
12
+ /**
13
+ * Validate that all declared locations exist in the canon.
14
+ */
15
+ export declare function checkLocations(meta: StoryMetadata, knownLocations: ReadonlySet<string>): CheckResult;
16
+ /**
17
+ * Validate that the timeline field is a valid ISO date.
18
+ */
19
+ export declare function checkTimeline(meta: StoryMetadata): CheckResult;
20
+ /**
21
+ * Validate temporal_context references exist in known episode set.
22
+ */
23
+ export declare function checkContinuity(meta: StoryMetadata, knownEpisodes: ReadonlySet<string>): CheckResult;
24
+ /**
25
+ * Validate that metadata.canon_ref matches canon.lock.json.
26
+ */
27
+ export declare function checkCanonVersion(meta: StoryMetadata, canonLock: CanonLock | null): CheckResult;
28
+ /**
29
+ * Validate metadata.json schema conformance.
30
+ */
31
+ export declare function checkMetadataSchema(meta: Record<string, unknown>): CheckResult;
32
+ /**
33
+ * Run all checks for a single story.
34
+ */
35
+ export declare function validateStory(input: {
36
+ meta: StoryMetadata;
37
+ rawMeta: Record<string, unknown>;
38
+ knownCharacters: ReadonlySet<string>;
39
+ knownLocations: ReadonlySet<string>;
40
+ knownEpisodes: ReadonlySet<string>;
41
+ canonLock: CanonLock | null;
42
+ }): StoryCheckReport;
43
+ /**
44
+ * Run all checks for an entire repo model.
45
+ * Pure function — takes RepoModel, returns RepoCheckReport.
46
+ */
47
+ export declare function validateRepo(model: RepoModel): RepoCheckReport;
@@ -0,0 +1,133 @@
1
+ function check(id, pass, message) {
2
+ return { id, pass, ...(!pass && message ? { message } : {}) };
3
+ }
4
+ /**
5
+ * Validate that all declared characters exist in the canon.
6
+ */
7
+ export function checkCharacters(meta, knownCharacters) {
8
+ const missing = meta.characters.filter(c => !knownCharacters.has(c));
9
+ return check("characters_valid", missing.length === 0, missing.length > 0 ? `Unknown characters: ${missing.join(", ")}` : undefined);
10
+ }
11
+ /**
12
+ * Validate that all declared locations exist in the canon.
13
+ */
14
+ export function checkLocations(meta, knownLocations) {
15
+ const missing = meta.locations.filter(l => !knownLocations.has(l));
16
+ return check("locations_valid", missing.length === 0, missing.length > 0 ? `Unknown locations: ${missing.join(", ")}` : undefined);
17
+ }
18
+ /**
19
+ * Validate that the timeline field is a valid ISO date.
20
+ */
21
+ export function checkTimeline(meta) {
22
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(meta.timeline)) {
23
+ return check("timeline_consistent", false, `Invalid timeline date: "${meta.timeline}"`);
24
+ }
25
+ const [y, m, d] = meta.timeline.split("-").map(Number);
26
+ const date = new Date(Date.UTC(y, m - 1, d));
27
+ const roundTrip = date.toISOString().slice(0, 10);
28
+ const valid = roundTrip === meta.timeline;
29
+ return check("timeline_consistent", valid, valid ? undefined : `Invalid timeline date: "${meta.timeline}"`);
30
+ }
31
+ /**
32
+ * Validate temporal_context references exist in known episode set.
33
+ */
34
+ export function checkContinuity(meta, knownEpisodes) {
35
+ if (!meta.temporal_context) {
36
+ return check("continuity_valid", true);
37
+ }
38
+ const tc = meta.temporal_context;
39
+ const broken = [];
40
+ if (tc.prev_episode && !knownEpisodes.has(tc.prev_episode)) {
41
+ broken.push(`prev_episode "${tc.prev_episode}" not found`);
42
+ }
43
+ if (tc.next_episode && !knownEpisodes.has(tc.next_episode)) {
44
+ broken.push(`next_episode "${tc.next_episode}" not found`);
45
+ }
46
+ if (tc.thematic_echoes) {
47
+ for (const echo of tc.thematic_echoes) {
48
+ if (!knownEpisodes.has(echo)) {
49
+ broken.push(`thematic_echo "${echo}" not found`);
50
+ }
51
+ }
52
+ }
53
+ return check("continuity_valid", broken.length === 0, broken.length > 0 ? broken.join("; ") : undefined);
54
+ }
55
+ /**
56
+ * Validate that metadata.canon_ref matches canon.lock.json.
57
+ */
58
+ export function checkCanonVersion(meta, canonLock) {
59
+ if (!canonLock) {
60
+ return check("canon_version_match", false, "canon.lock.json not found");
61
+ }
62
+ const match = meta.canon_ref === canonLock.canon_commit;
63
+ return check("canon_version_match", match, match ? undefined : `canon_ref "${meta.canon_ref}" does not match lock "${canonLock.canon_commit}"`);
64
+ }
65
+ /**
66
+ * Validate metadata.json schema conformance.
67
+ */
68
+ export function checkMetadataSchema(meta) {
69
+ const required = ["schema_version", "canon_ref", "id", "episode", "title", "timeline", "synopsis", "characters", "locations", "canon_status"];
70
+ const missing = required.filter(f => !(f in meta));
71
+ if (missing.length > 0) {
72
+ return check("metadata_schema_valid", false, `Missing fields: ${missing.join(", ")}`);
73
+ }
74
+ if (meta.schema_version !== "1.1") {
75
+ return check("metadata_schema_valid", false, `Expected schema_version "1.1", got "${meta.schema_version}"`);
76
+ }
77
+ if (typeof meta.episode !== "number") {
78
+ return check("metadata_schema_valid", false, `episode must be a number`);
79
+ }
80
+ if (!Array.isArray(meta.characters)) {
81
+ return check("metadata_schema_valid", false, `characters must be an array`);
82
+ }
83
+ if (!Array.isArray(meta.locations)) {
84
+ return check("metadata_schema_valid", false, `locations must be an array`);
85
+ }
86
+ const validStatuses = ["canonical", "non-canonical"];
87
+ if (!validStatuses.includes(meta.canon_status)) {
88
+ return check("metadata_schema_valid", false, `canon_status must be "canonical" or "non-canonical"`);
89
+ }
90
+ return check("metadata_schema_valid", true);
91
+ }
92
+ /**
93
+ * Run all checks for a single story.
94
+ */
95
+ export function validateStory(input) {
96
+ const checks = [
97
+ checkMetadataSchema(input.rawMeta),
98
+ checkCharacters(input.meta, input.knownCharacters),
99
+ checkLocations(input.meta, input.knownLocations),
100
+ checkTimeline(input.meta),
101
+ checkContinuity(input.meta, input.knownEpisodes),
102
+ checkCanonVersion(input.meta, input.canonLock),
103
+ ];
104
+ return {
105
+ storyId: input.meta.id,
106
+ checks,
107
+ allPass: checks.every(c => c.pass),
108
+ };
109
+ }
110
+ /**
111
+ * Run all checks for an entire repo model.
112
+ * Pure function — takes RepoModel, returns RepoCheckReport.
113
+ */
114
+ export function validateRepo(model) {
115
+ const stories = [];
116
+ for (const [, { meta, raw }] of model.stories) {
117
+ stories.push(validateStory({
118
+ meta,
119
+ rawMeta: raw,
120
+ knownCharacters: model.characters,
121
+ knownLocations: model.locations,
122
+ knownEpisodes: model.episodes,
123
+ canonLock: model.canonLock,
124
+ }));
125
+ }
126
+ return {
127
+ schemaVersion: "check.v1",
128
+ stories,
129
+ totalStories: stories.length,
130
+ passingStories: stories.filter(s => s.allPass).length,
131
+ };
132
+ }
133
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"AAQA,SAAS,KAAK,CAAC,EAAW,EAAE,IAAa,EAAE,OAAgB;IACzD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AAC/D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,eAAoC;IAEpC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACpE,OAAO,KAAK,CACV,kBAAkB,EAClB,OAAO,CAAC,MAAM,KAAK,CAAC,EACpB,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAuB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAC7E,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAmB,EACnB,cAAmC;IAEnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,OAAO,KAAK,CACV,iBAAiB,EACjB,OAAO,CAAC,MAAM,KAAK,CAAC,EACpB,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAC5E,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAmB;IAC/C,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC,qBAAqB,EAAE,KAAK,EAAE,2BAA2B,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;IACzF,CAAC;IACD,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACtD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,SAAS,KAAK,IAAI,CAAC,QAAQ,CAAA;IACzC,OAAO,KAAK,CACV,qBAAqB,EACrB,KAAK,EACL,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,2BAA2B,IAAI,CAAC,QAAQ,GAAG,CAChE,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,aAAkC;IAElC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAA;IACxC,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAAA;IAChC,MAAM,MAAM,GAAa,EAAE,CAAA;IAE3B,IAAI,EAAE,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,YAAY,aAAa,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,EAAE,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,YAAY,aAAa,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,EAAE,CAAC,eAAe,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,kBAAkB,IAAI,aAAa,CAAC,CAAA;YAClD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CACV,kBAAkB,EAClB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAClD,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAmB,EACnB,SAA2B;IAE3B,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAC,qBAAqB,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAA;IACzE,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,YAAY,CAAA;IACvD,OAAO,KAAK,CACV,qBAAqB,EACrB,KAAK,EACL,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,SAAS,0BAA0B,SAAS,CAAC,YAAY,GAAG,CACpG,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA6B;IAC/D,MAAM,QAAQ,GAAG,CAAC,gBAAgB,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,CAAC,CAAA;IAC7I,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAA;IAClD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACvF,CAAC;IACD,IAAI,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,uCAAuC,IAAI,CAAC,cAAc,GAAG,CAAC,CAAA;IAC7G,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAA;IAC1E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,6BAA6B,CAAC,CAAA;IAC7E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,4BAA4B,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,aAAa,GAAG,CAAC,WAAW,EAAE,eAAe,CAAC,CAAA;IACpD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAsB,CAAC,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,qDAAqD,CAAC,CAAA;IACrG,CAAC;IACD,OAAO,KAAK,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAO7B;IACC,MAAM,MAAM,GAAG;QACb,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC;QAClC,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,eAAe,CAAC;QAClD,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC;QAChD,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC;QACzB,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QAChD,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;KAC/C,CAAA;IAED,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;QACtB,MAAM;QACN,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACnC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgB;IAC3C,MAAM,OAAO,GAAuB,EAAE,CAAA;IAEtC,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC;YACzB,IAAI;YACJ,OAAO,EAAE,GAAG;YACZ,eAAe,EAAE,KAAK,CAAC,UAAU;YACjC,cAAc,EAAE,KAAK,CAAC,SAAS;YAC/B,aAAa,EAAE,KAAK,CAAC,QAAQ;YAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC,CAAC,CAAA;IACL,CAAC;IAED,OAAO;QACL,aAAa,EAAE,UAAU;QACzB,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,MAAM;QAC5B,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM;KACtD,CAAA;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function characterTemplate(id: string): string;
@@ -0,0 +1,8 @@
1
+ export function characterTemplate(id) {
2
+ return JSON.stringify({
3
+ id,
4
+ name: { ko: "", en: "" },
5
+ description: { ko: "", en: "" },
6
+ }, null, 2) + "\n";
7
+ }
8
+ //# sourceMappingURL=character.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character.js","sourceRoot":"","sources":["../../src/templates/character.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,EAAE;QACF,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QACxB,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;KAChC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AACpB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function conventionsTemplate(): string;
@@ -0,0 +1,50 @@
1
+ export function conventionsTemplate() {
2
+ return `# Canon Conventions
3
+
4
+ ## Repository Structure
5
+
6
+ \`\`\`
7
+ canon/
8
+ characters/ # One directory per character
9
+ <id>/
10
+ definition.json
11
+ worldbuilding/
12
+ locations/ # One JSON file per location
13
+ <id>.json
14
+ stories/
15
+ <slug>/
16
+ metadata.json # Required — canon compliance metadata
17
+ chapter-01.md # Story content (markdown)
18
+ canon.lock.json # Generated — do not edit manually
19
+ CONVENTIONS.md # This file
20
+ \`\`\`
21
+
22
+ ## Metadata Schema (v1.1)
23
+
24
+ Every story directory must contain a \`metadata.json\` with these required fields:
25
+
26
+ | Field | Type | Description |
27
+ |------------------|----------|--------------------------------------|
28
+ | schema_version | "1.1" | Fixed schema version |
29
+ | canon_ref | string | Commit SHA from canon.lock.json |
30
+ | id | string | Unique story identifier (slug) |
31
+ | episode | number | Episode number |
32
+ | title | {ko, en} | Bilingual title |
33
+ | timeline | string | ISO date (YYYY-MM-DD) |
34
+ | synopsis | {ko, en} | Bilingual synopsis |
35
+ | characters | string[] | Character IDs used in this story |
36
+ | locations | string[] | Location IDs used in this story |
37
+ | canon_status | string | "canonical" or "non-canonical" |
38
+
39
+ ## Compliance Checks
40
+
41
+ Run \`canon check\` to validate:
42
+ - **metadata_schema_valid** — all required fields present and correct types
43
+ - **characters_valid** — all referenced characters exist in canon/characters/
44
+ - **locations_valid** — all referenced locations exist in canon/worldbuilding/locations/
45
+ - **timeline_consistent** — timeline is a valid ISO date
46
+ - **continuity_valid** — temporal_context references point to existing episodes
47
+ - **canon_version_match** — canon_ref matches canon.lock.json commit
48
+ `;
49
+ }
50
+ //# sourceMappingURL=conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/templates/conventions.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CR,CAAA;AACD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function locationTemplate(id: string): string;
@@ -0,0 +1,8 @@
1
+ export function locationTemplate(id) {
2
+ return JSON.stringify({
3
+ id,
4
+ name: { ko: "", en: "" },
5
+ description: { ko: "", en: "" },
6
+ }, null, 2) + "\n";
7
+ }
8
+ //# sourceMappingURL=location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location.js","sourceRoot":"","sources":["../../src/templates/location.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,EAAE;QACF,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QACxB,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;KAChC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AACpB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function metadataTemplate(id: string): string;
@@ -0,0 +1,15 @@
1
+ export function metadataTemplate(id) {
2
+ return JSON.stringify({
3
+ schema_version: "1.1",
4
+ canon_ref: "",
5
+ id,
6
+ episode: 0,
7
+ title: { ko: "", en: "" },
8
+ timeline: "2025-01-01",
9
+ synopsis: { ko: "", en: "" },
10
+ characters: [],
11
+ locations: [],
12
+ canon_status: "non-canonical",
13
+ }, null, 2) + "\n";
14
+ }
15
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/templates/metadata.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,cAAc,EAAE,KAAK;QACrB,SAAS,EAAE,EAAE;QACb,EAAE;QACF,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QACzB,QAAQ,EAAE,YAAY;QACtB,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QAC5B,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE;QACb,YAAY,EAAE,eAAe;KAC9B,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AACpB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execSync } from "node:child_process";
4
+ import { mkdtempSync, mkdirSync, readFileSync, existsSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { buildRepoModel } from "../adapters/github.js";
8
+ import { checkTimeline } from "../core/validate.js";
9
+ const CLI = join(import.meta.dirname, "..", "cli.js");
10
+ describe("hotfix regressions", () => {
11
+ it("check exits 1 when 0 stories found", () => {
12
+ const tmp = mkdtempSync(join(tmpdir(), "canon-empty-"));
13
+ mkdirSync(join(tmp, "stories"), { recursive: true });
14
+ const result = (() => {
15
+ try {
16
+ execSync(`node ${CLI} check ${tmp}`, { encoding: "utf-8", stdio: "pipe" });
17
+ return { code: 0 };
18
+ }
19
+ catch (e) {
20
+ return { code: e.status, stderr: e.stderr };
21
+ }
22
+ })();
23
+ assert.equal(result.code, 1);
24
+ });
25
+ it("github adapter parses canon/characters/alice.json blob", () => {
26
+ const input = {
27
+ tree: [
28
+ { path: "canon/characters/alice.json", type: "blob", sha: "aaa" },
29
+ { path: "canon/characters/bob", type: "tree", sha: "bbb" },
30
+ { path: "canon/characters/index.json", type: "blob", sha: "ccc" },
31
+ { path: "stories/ep01", type: "tree", sha: "ddd" },
32
+ ],
33
+ files: new Map([
34
+ ["stories/ep01/metadata.json", JSON.stringify({
35
+ schema_version: "1.1", canon_ref: "abc", id: "ep01", episode: 1,
36
+ title: { ko: "t", en: "t" }, timeline: "2025-01-01",
37
+ synopsis: { ko: "s", en: "s" }, characters: ["alice", "bob"],
38
+ locations: [], canon_status: "canonical",
39
+ })],
40
+ ]),
41
+ };
42
+ const model = buildRepoModel(input);
43
+ assert.ok(model.characters.has("alice"), "alice.json blob should be parsed");
44
+ assert.ok(model.characters.has("bob"), "bob/ dir should be parsed");
45
+ assert.ok(!model.characters.has("index"), "index should be excluded");
46
+ });
47
+ it("timeline rejects 2025-02-30 and 2025-01-01junk", () => {
48
+ const feb30 = checkTimeline({ timeline: "2025-02-30" });
49
+ assert.equal(feb30.pass, false, "2025-02-30 should fail");
50
+ const junk = checkTimeline({ timeline: "2025-01-01junk" });
51
+ assert.equal(junk.pass, false, "2025-01-01junk should fail");
52
+ const valid = checkTimeline({ timeline: "2025-03-15" });
53
+ assert.equal(valid.pass, true, "2025-03-15 should pass");
54
+ });
55
+ it("init creates .canonrc.json but not canon.lock.json", () => {
56
+ const tmp = mkdtempSync(join(tmpdir(), "canon-init-"));
57
+ execSync(`node ${CLI} init ${tmp}`, { encoding: "utf-8", stdio: "pipe" });
58
+ assert.ok(existsSync(join(tmp, ".canonrc.json")), ".canonrc.json should exist");
59
+ assert.ok(!existsSync(join(tmp, "canon.lock.json")), "canon.lock.json should NOT exist");
60
+ const rc = JSON.parse(readFileSync(join(tmp, ".canonrc.json"), "utf-8"));
61
+ assert.equal(rc.schema_version, "canonrc.v1");
62
+ });
63
+ });
64
+ //# sourceMappingURL=hotfix.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotfix.test.js","sourceRoot":"","sources":["../../src/test/hotfix.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@opencanon/canon",
3
+ "version": "0.1.0",
4
+ "description": "Canon worldbuilding CLI — scaffold, validate, and manage shared fiction universes",
5
+ "type": "module",
6
+ "bin": {
7
+ "canon": "./dist/cli.js"
8
+ },
9
+ "files": ["dist", "README.md", "LICENSE"],
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/core/index.js",
13
+ "types": "./dist/core/index.d.ts"
14
+ },
15
+ "./adapters/fs": {
16
+ "import": "./dist/adapters/fs.js",
17
+ "types": "./dist/adapters/fs.d.ts"
18
+ },
19
+ "./adapters/github": {
20
+ "import": "./dist/adapters/github.js",
21
+ "types": "./dist/adapters/github.d.ts"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "dev": "tsc --watch",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "ls dist/test/*.test.js >/dev/null 2>&1 && node --test dist/test/*.test.js || { echo 'No test files found in dist/test/'; exit 1; }",
29
+ "pretest": "tsc"
30
+ },
31
+ "keywords": ["worldbuilding", "canon", "fiction", "compliance", "opencanon"],
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "commander": "^12.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "typescript": "^5.7.0"
42
+ }
43
+ }