@fakto/core 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,22 @@ npx fakto canvas # the live map on http://localhost:4848 (--backgrou
69
69
 
70
70
  As a library: `import { loadRepo, checkRepo, staleList } from '@fakto/core'`.
71
71
 
72
+ ## Claude Code skills
73
+
74
+ The package ships the two skills Claude uses in a Fakto repo (`fakto`: the change workflow;
75
+ `fakto-canvas`: starts the live map) and the hooks that run the check after every edit and
76
+ start the canvas at session start. Claude Code loads skills from the project, not from
77
+ `node_modules`, so install them into the repo:
78
+
79
+ ```bash
80
+ npx fakto skills # write .claude/skills/* and merge the hooks into .claude/settings.json
81
+ npx fakto skills --check # exit 1 when they are missing, edited, or from another version
82
+ ```
83
+
84
+ Put `"postinstall": "fakto skills"` in the repo's package.json so every `npm install` refreshes
85
+ them; the session-start hook runs it too, and `fakto check` warns when they are not current.
86
+ Commit them: they are readable on GitHub and work before the first install.
87
+
72
88
  ## Canvas
73
89
 
74
90
  A local web app that draws the whole graph, lets you edit or add a fact, and runs your
package/bin/fakto.mjs CHANGED
@@ -11,6 +11,7 @@
11
11
  * fakto touch <file | fact-id>… stamp reviewed: today and the source hashes (after a human reviewed it)
12
12
  * fakto sync [path | fact-id …] bring facts and sections in line with the files they draw on (--all)
13
13
  * fakto canvas open the canvas on localhost (--port 4848; --background to detach, --stop)
14
+ * fakto skills install or refresh the Claude Code skills and hooks in .claude/ (--check, --quiet)
14
15
  *
15
16
  * Conventions (full spec in this package's README):
16
17
  * facts <facts>/*.yaml, flat records: - id / value / statement / status / updated
@@ -26,7 +27,7 @@
26
27
  *
27
28
  * All logic lives in ../lib; this file only formats and dispatches.
28
29
  */
29
- import { findRoot, loadRepo, staleList, checkRepo, graphJson, touch as touchFiles, renderOutputs, renderStatus, syncPlan, syncExamples, feedsIndex, previousSelection, lineDiff } from '../lib/core.mjs';
30
+ import { installSkills, skillsStatus, findRoot, loadRepo, staleList, checkRepo, graphJson, touch as touchFiles, renderOutputs, renderStatus, syncPlan, syncExamples, feedsIndex, previousSelection, lineDiff } from '../lib/core.mjs';
30
31
 
31
32
  const ROOT = findRoot(process.cwd());
32
33
  const cmd = process.argv[2];
@@ -164,6 +165,27 @@ function render(repo, only) {
164
165
  if (done.some((r) => !r.ok)) process.exit(1);
165
166
  }
166
167
 
168
+ /**
169
+ * fakto skills [--check | --quiet]
170
+ * Installs this package's Claude Code skills into .claude/skills (stamped with the version) and
171
+ * merges its hooks and permissions into .claude/settings.json. Runs on postinstall and at session
172
+ * start. --check exits 1 when anything is missing or out of date; --quiet prints only changes.
173
+ */
174
+ function skills() {
175
+ if (args.includes('--check')) {
176
+ const s = skillsStatus(ROOT);
177
+ for (const k of s.skills) console.log(`${k.status === 'ok' ? grn('ok ') : red(k.status.padEnd(8))} .claude/skills/${k.name}${k.installed ? dim(` (installed ${k.installed}, package ${s.version})`) : ''}`);
178
+ console.log(`${s.settings === 'ok' ? grn('ok ') : red(s.settings.padEnd(8))} .claude/settings.json`);
179
+ if (!s.ok) { console.log(`\nRun ${'npx fakto skills'} to install the skills for ${s.version}.`); process.exit(1); }
180
+ return;
181
+ }
182
+ const { changed, version } = installSkills(ROOT);
183
+ const quiet = args.includes('--quiet');
184
+ if (!changed.length) { if (!quiet) console.log(grn(`Fakto skills are up to date (${version}).`)); return; }
185
+ if (quiet) console.log(`The Fakto skills were just updated to ${version} (${changed.join(', ')}). Read .claude/skills/fakto/SKILL.md and .claude/skills/fakto-canvas/SKILL.md again before you use them.`);
186
+ else for (const c of changed) console.log(`${c}: updated to ${version}`);
187
+ }
188
+
167
189
  /** What answers on a port: { root } for a fakto canvas, 'other' for anything else, null when free. */
168
190
  async function probe(port) {
169
191
  try {
@@ -234,7 +256,7 @@ async function canvas() {
234
256
  await start({ root: ROOT, port });
235
257
  }
236
258
 
237
- const repo = cmd && cmd !== 'canvas' ? loadRepo(ROOT) : null;
259
+ const repo = cmd && cmd !== 'canvas' && cmd !== 'skills' ? loadRepo(ROOT) : null;
238
260
  switch (cmd) {
239
261
  case 'graph': graph(repo); break;
240
262
  case 'check': check(repo); break;
@@ -243,8 +265,9 @@ switch (cmd) {
243
265
  case 'touch': touch(repo, args); break;
244
266
  case 'render': render(repo, args); break;
245
267
  case 'sync': sync(repo, args); break;
268
+ case 'skills': skills(); break;
246
269
  case 'canvas': await canvas(); break;
247
270
  default:
248
- console.log('usage: fakto graph | check | stale | show <fact-id | file> | touch <file | fact-id>… | render [path…] | sync [path | fact-id …] | canvas');
271
+ console.log('usage: fakto graph | check | stale | show <fact-id | file> | touch <file | fact-id>… | render [path…] | sync [path | fact-id …] | skills | canvas');
249
272
  process.exit(cmd ? 1 : 0);
250
273
  }
package/lib/core.mjs CHANGED
@@ -18,8 +18,10 @@
18
18
  import fs from 'node:fs';
19
19
  import path from 'node:path';
20
20
  import { renderStatus } from './render.mjs';
21
+ import { skillsStatus } from './skills.mjs';
21
22
  import { parseSourceAnchors, factSourceStatus, drawStatus, exampleStatus, syncPlan, sourceCycles, restampFile, restampedSource, parseRef } from './sources.mjs';
22
23
 
24
+ export { installSkills, skillsStatus, PACKAGE } from './skills.mjs';
23
25
  export { renderStatus, renderOutputs, parseSlides, stripAnchors, sourceHash, RENDERERS } from './render.mjs';
24
26
  export { parseRef, refString, resolveRef, blobHash, factSourceStatus, drawStatus, exampleStatus, syncPlan, sourceCycles, syncExamples, previousSelection, lineDiff, FROM_RE, EXAMPLE_RE } from './sources.mjs';
25
27
 
@@ -162,7 +164,7 @@ export function anchorOk(repo, artifact, anchor) {
162
164
  * Every problem, in the order the CLI prints them.
163
165
  * level: error | warning | info. kind: orphan | no-stamp | unknown | unknown-support | retired | draft | drift | stale |
164
166
  * render-stale | render-missing | render-config | source-changed | source-missing | example-drift | source-cycle |
165
- * region-unused.
167
+ * region-unused | skills.
166
168
  * `message` is the CLI's text (without colour); line-level problems also carry
167
169
  * path, line, text, fact for the canvas.
168
170
  */
@@ -221,6 +223,12 @@ export function checkRepo(repo) {
221
223
  for (const a of repo.artifacts) for (const x of [...a.draws, ...a.examples]) if (x.ref.selector) used.add(`${x.ref.path}#${x.ref.selector}`);
222
224
  for (const a of repo.artifacts) for (const r of a.regions) if (!used.has(`${a.file}#${r.name}`)) out.push({ level: 'info', kind: 'region-unused', path: a.file, line: r.line, message: `${a.file}:${r.line}: region "${r.name}" is marked but nothing draws on it` });
223
225
  }
226
+ // The Claude Code skills and hooks this package ships must be installed, and current.
227
+ const sk = skillsStatus(repo.root);
228
+ if (!sk.ok) {
229
+ const bad = [...sk.skills.filter((s) => s.status !== 'ok').map((s) => `${s.name} ${s.status}${s.installed ? ` (${s.installed})` : ''}`), ...(sk.settings === 'ok' ? [] : [`settings ${sk.settings}`])];
230
+ out.push({ level: 'warning', kind: 'skills', path: '.claude', message: `.claude: the Fakto skills for Claude Code are not current for ${sk.version} (${bad.join(', ')}) — run npx fakto skills` });
231
+ }
224
232
  for (const r of renderStatus(repo)) {
225
233
  if (r.status === 'stale') out.push({ level: 'warning', kind: 'render-stale', path: r.to, message: `${r.to}: out of date with ${r.from} — run fakto render` });
226
234
  if (r.status === 'missing') out.push({ level: 'warning', kind: 'render-missing', path: r.to, message: `${r.to}: not rendered yet from ${r.from} — run fakto render` });
package/lib/skills.mjs ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The Claude Code side of a Fakto repo ships in this package, so it can never drift from the
3
+ * CLI it calls: the skills in ../skills and the hooks and permissions below.
4
+ *
5
+ * installSkills(root) write .claude/skills/<name>/ (stamped with this package's version) and
6
+ * merge the hooks and permissions into .claude/settings.json
7
+ * skillsStatus(root) what `fakto skills --check` and `fakto check` report
8
+ *
9
+ * Claude Code loads skills from the project's .claude/skills, never from node_modules, so a
10
+ * repo runs `fakto skills` on postinstall and the session-start hook runs it again.
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const PKG = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const SOURCE = path.join(PKG, 'skills');
18
+ export const PACKAGE = JSON.parse(fs.readFileSync(path.join(PKG, 'package.json'), 'utf8'));
19
+ const STAMP_RE = /<!-- Installed by `fakto skills` from (\S+)@(\d+\.\d+\.\d+[^\s.]*)\./;
20
+ const stamp = () => `\n<!-- Installed by \`fakto skills\` from ${PACKAGE.name}@${PACKAGE.version}. Do not edit: \`fakto skills\` overwrites this file; change the package's skills instead. -->\n`;
21
+
22
+ export const SESSION_HOOK = [
23
+ '[ -f fakto.config.json ] && [ -z "$FAKTO_CANVAS_RUN" ] && {',
24
+ 'if [ -x node_modules/.bin/fakto ]; then node_modules/.bin/fakto skills --quiet;',
25
+ "echo 'This is a Fakto repo. Before anything else, use the fakto-canvas skill: start the canvas (npx fakto canvas --background) and tell the user where it runs.';",
26
+ "else echo 'This is a Fakto repo whose dependencies are not installed yet. Before anything else run npm install: it installs Fakto and refreshes its Claude skills in .claude/skills. Then use the fakto-canvas skill to start the canvas and tell the user where it runs.';",
27
+ 'fi; } || true',
28
+ ].join(' ');
29
+ export const CHECK_HOOK = 'npx --no fakto check 2>&1 | tail -n 8';
30
+ export const PERMISSIONS = ['Bash(npx fakto:*)', 'Bash(npm run:*)', 'Bash(npm install)'];
31
+
32
+ const ours = (entry) => (entry.hooks || []).some((h) => typeof h.command === 'string' && /\bfakto\b/.test(h.command));
33
+ const WANT_HOOKS = {
34
+ SessionStart: { hooks: [{ type: 'command', command: SESSION_HOOK }] },
35
+ PostToolUse: { matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: CHECK_HOOK }] },
36
+ };
37
+
38
+ /** The settings with Fakto's hooks and permissions in place; everything else is kept as it was. */
39
+ function mergedSettings(current) {
40
+ const s = JSON.parse(JSON.stringify(current || {}));
41
+ s.permissions = s.permissions || {};
42
+ s.permissions.allow = [...new Set([...(s.permissions.allow || []), ...PERMISSIONS])];
43
+ s.hooks = s.hooks || {};
44
+ for (const [event, entry] of Object.entries(WANT_HOOKS)) {
45
+ s.hooks[event] = [...(s.hooks[event] || []).filter((e) => !ours(e)), entry];
46
+ }
47
+ return s;
48
+ }
49
+
50
+ const skillNames = () => fs.readdirSync(SOURCE, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
51
+ const wanted = (name) => fs.readFileSync(path.join(SOURCE, name, 'SKILL.md'), 'utf8').replace(/\n*$/, '\n') + stamp();
52
+ const settingsPath = (root) => path.join(root, '.claude', 'settings.json');
53
+ const readJson = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };
54
+
55
+ /** { ok, version, skills: [{ name, status: ok | missing | outdated | modified, installed? }], settings: ok | missing | outdated } */
56
+ export function skillsStatus(root) {
57
+ const skills = skillNames().map((name) => {
58
+ const p = path.join(root, '.claude', 'skills', name, 'SKILL.md');
59
+ if (!fs.existsSync(p)) return { name, status: 'missing' };
60
+ const have = fs.readFileSync(p, 'utf8');
61
+ const m = have.match(STAMP_RE);
62
+ if (have === wanted(name)) return { name, status: 'ok' };
63
+ if (m && m[2] !== PACKAGE.version) return { name, status: 'outdated', installed: m[2] };
64
+ return { name, status: 'modified' }; // same version (or no stamp at all) but different text: edited by hand
65
+ });
66
+ const cur = readJson(settingsPath(root));
67
+ const settings = !cur ? 'missing' : JSON.stringify(mergedSettings(cur)) === JSON.stringify(cur) ? 'ok' : 'outdated';
68
+ return { ok: skills.every((s) => s.status === 'ok') && settings === 'ok', version: PACKAGE.version, skills, settings };
69
+ }
70
+
71
+ /** Write what is not ok. Returns { changed: [paths], version }. */
72
+ export function installSkills(root) {
73
+ const changed = [];
74
+ for (const name of skillNames()) {
75
+ const dir = path.join(root, '.claude', 'skills', name);
76
+ const p = path.join(dir, 'SKILL.md');
77
+ const want = wanted(name);
78
+ // a symlink (an older setup) is replaced by a real file, so the repo works on its own
79
+ try { if (fs.lstatSync(dir).isSymbolicLink()) fs.rmSync(dir); } catch { /* not there yet */ }
80
+ if (fs.existsSync(p) && fs.readFileSync(p, 'utf8') === want) continue;
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ fs.writeFileSync(p, want);
83
+ changed.push(path.relative(root, p));
84
+ }
85
+ const sp = settingsPath(root);
86
+ const cur = readJson(sp);
87
+ if (fs.existsSync(sp) && !cur) throw new Error(`${path.relative(root, sp)} is not valid JSON; fix it, then run fakto skills again`);
88
+ const next = mergedSettings(cur);
89
+ if (!cur || JSON.stringify(next) !== JSON.stringify(cur)) {
90
+ fs.mkdirSync(path.dirname(sp), { recursive: true });
91
+ fs.writeFileSync(sp, JSON.stringify(next, null, 2) + '\n');
92
+ changed.push(path.relative(root, sp));
93
+ }
94
+ return { changed, version: PACKAGE.version };
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakto/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Keep every file that states a fact in line with the fact: the CLI, the library and the canvas in one package.",
6
6
  "type": "module",
@@ -17,6 +17,8 @@
17
17
  "lib/core.mjs",
18
18
  "lib/render.mjs",
19
19
  "lib/sources.mjs",
20
+ "lib/skills.mjs",
21
+ "skills",
20
22
  "canvas/server",
21
23
  "canvas/dist",
22
24
  "canvas/README.md",
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: fakto
3
+ description: >
4
+ Keep every artifact (docs, code, website, decks, box copy) in sync with the facts
5
+ it depends on. Use whenever a fact changes ("the price is now X", "we have 9 levels
6
+ now", "retire feature Y"), whenever `fakto check` or `fakto stale` reports something,
7
+ when adding a new fact or a new artifact, or when the user says "propagate",
8
+ "update everything that mentions X", or "what depends on X". Also when a source file
9
+ changed ("I changed the code, sync the docs", `fakto check` says "behind its source",
10
+ `fakto sync`). Facts live in the facts directory named in fakto.config.json; artifacts
11
+ carry `fact:<id>` anchors, and may draw on other files with `from:` / `example:`.
12
+ ---
13
+
14
+ # fakto — keep dependents in sync
15
+
16
+ Facts are the source of truth. Artifacts cite them with an inline anchor
17
+ `fact:<id>` on the line that states the fact. The CLI finds what depends on
18
+ what; this skill does the part that needs judgment: rewriting the dependents.
19
+
20
+ Run the CLI as `npx fakto <cmd>` (the repo installs the `fakto` package).
21
+ The conventions are in the package README (`node_modules/@fakto/core/README.md`).
22
+ If `fakto.config.json` names `houseRules`, read that file before writing any
23
+ artifact and follow it: it is the company's voice and wording rules.
24
+
25
+ ## Which case are you in?
26
+
27
+ ### A. A fact changed (the common case)
28
+
29
+ 1. Edit the fact record only: `value`, `statement`, and bump `updated` to today.
30
+ Never touch an artifact first.
31
+ 2. Run `npx fakto stale`. It lists every artifact whose `reviewed` date is older than
32
+ the fact's `updated` date, with the exact lines (anchors) that cite it.
33
+ 3. For each stale artifact, draft the minimal edit that makes the anchored line
34
+ true again. Rules:
35
+ - Change the sentence, keep the voice of that artifact (a manual is not a deck).
36
+ - Keep the anchor comment on the line. The drift check needs the new `value`
37
+ to appear on that line.
38
+ - Code: change the constant, not the comment. Derived values (enums, ranges,
39
+ schemas) move with it.
40
+ - `lifecycle: snapshot` artifacts are frozen. Never edit them; they are history.
41
+ - A changelog is living: add a dated entry on top, never rewrite old entries.
42
+ When an old entry's fact changes, turn its anchor into an as-of citation
43
+ (`fact:id@<that entry's date>`) so the line keeps its link and stops being
44
+ checked for the current value. Same for any "was X, now Y" aside.
45
+ - If an edit needs a decision (new copy angle, a number that no longer fits the
46
+ story), stop and ask; do not invent.
47
+ 4. Present the edit list (file, line, before → after) and apply it. If the user
48
+ asked for autonomous propagation, apply without asking.
49
+ 5. Stamp the reviewed artifacts: `npx fakto touch <file>…`. If `fakto.config.json`
50
+ declares `renders`, run `npx fakto render` next: generated outputs (a slide deck
51
+ built from a Markdown deck) are rebuilt from their source, never edited by hand.
52
+ 6. Run `npx fakto check`. It must print `0 errors` with no stale warnings. Fix anything
53
+ it reports before you call the job done.
54
+
55
+ ### B. Retiring a fact
56
+
57
+ 1. Set `status: retired` on the record, bump `updated`, add a `note:` saying why.
58
+ 2. Run `npx fakto check`. Every living artifact that cites it is now an error.
59
+ 3. Remove or rewrite each citing line. Where the artifact should record that the
60
+ thing existed (a changelog, a history section), keep the anchor as an as-of
61
+ citation (`fact:id@date`) or put `RETIRED` on the line.
62
+ 4. `npx fakto touch` the edited files, `npx fakto check` must be green.
63
+
64
+ ### C. Adding a new fact
65
+
66
+ 1. One record per fact, kebab-case id with a topic prefix, `status: live` only
67
+ when the value is confirmed; otherwise `draft`.
68
+ 2. `value` is the shortest string every artifact line must contain. Pick it so
69
+ it matches prose and code alike (`"7"`, `"$4.99"`, `"client.send("`).
70
+ 3. Anchor the artifacts that already state it. Run `npx fakto graph` to confirm it has
71
+ dependents; a fact with none is either new copy to write or not a fact.
72
+
73
+ ### D. Tagging a new artifact
74
+
75
+ 1. Add `reviewed: YYYY-MM-DD` to the file head (frontmatter or a comment).
76
+ 2. Anchor every line that states a fact: `<!-- fact:id -->`, `// fact:id`,
77
+ `# fact:id`, whatever comment syntax the file uses.
78
+ 3. A published, dated thing (a post, a press release, an old deck) gets
79
+ `lifecycle: snapshot` and is never edited again.
80
+ 4. `npx fakto check` must be green.
81
+
82
+ ### E. Narratives (facts without a `value`)
83
+
84
+ A narrative is a story every channel tells in its own words ("a platform, not an
85
+ appliance"). Its record has a `statement` and no `value`, and usually `supports: [ids]`,
86
+ the facts it rests on. The check cannot compare wording, so you are the check:
87
+
88
+ 1. When a narrative's statement changes, `fakto stale` lists every section anchored to
89
+ it. Rewrite each so it tells the new story in that file's voice: a website headline,
90
+ a slide, a sentence in a deck. Do not paste the statement verbatim unless the file
91
+ is meant to quote it.
92
+ 2. Consistency pass: whenever you rewrite a file that cites a narrative (for any
93
+ reason), read the anchored section and confirm it still tells that narrative. If it
94
+ no longer does, rewrite it or report it; never leave a section that contradicts its
95
+ story.
96
+ 3. Numbers inside a narrative section are still facts: anchor them separately.
97
+ 4. `supports` must list existing fact ids; the check fails otherwise.
98
+
99
+ ### F. A source file changed (sync)
100
+
101
+ Some facts and sections follow a file instead of the other way round: a fact with
102
+ `source:`, a section with `<!-- from:path#selector -->`, a fence after
103
+ `<!-- example:path#selector -->`. When that file changes:
104
+
105
+ 1. Run `npx fakto sync`. It copies every fenced example from its source (never edit those
106
+ by hand) and lists the facts and sections that need judgment, with the diff since their
107
+ last sync where git has the old version.
108
+ 2. **Facts.** For each listed fact, read the source (the anchored line, or the selection).
109
+ Change `value` and `statement` only if the meaning changed, and bump `updated` when you
110
+ do. For a source with a selector, then run `npx fakto touch <fact-id>` to restamp it,
111
+ also when nothing changed. A derived fact is never edited for any other reason.
112
+ 3. **Sections.** For each listed section, reread it against the source. Rewrite it only
113
+ where it no longer describes the source, in that file's own voice, keeping its anchors.
114
+ Then `npx fakto touch <file>`: it stamps `reviewed` and moves the `from:` hashes.
115
+ 4. **Propagate.** If step 2 changed a fact, follow case A from step 2 (`npx fakto stale`,
116
+ rewrite, touch, render).
117
+ 5. `npx fakto check` must be green. A rewrite can change a file another section draws on;
118
+ if `fakto check` still reports something behind its source, go again (at most three
119
+ passes, then report what is left).
120
+
121
+ ## Invariants
122
+
123
+ - No number, name or claim in an artifact without a fact record and an anchor.
124
+ - Forward links only: artifacts point at facts. The reverse direction is derived
125
+ by `npx fakto graph`, never stored.
126
+ - The `updated` date on a fact moves on every value change. That date is the
127
+ whole propagation mechanism; forget it and nothing goes stale.
128
+ - Snapshots are never edited. Living artifacts are never left stale.
129
+ - Render outputs are build output: edit the source, then `npx fakto render`.
130
+ - A derived fact (one with `source:`) follows its file: change the file, never the record.
131
+ - Fenced examples are copies: `npx fakto sync` writes them, nobody else.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: fakto-canvas
3
+ description: >
4
+ Start the Fakto canvas, the live map of this repo's facts and the files that state them,
5
+ and tell the user where it runs. Use it first, at the start of every session in a repo
6
+ that has a fakto.config.json, before any other work. Also use it whenever the user wants
7
+ to see the map, the canvas, or the changes as they happen, or says "start", "run it",
8
+ "open the canvas" or "show me".
9
+ ---
10
+
11
+ # fakto-canvas — start the live map and say where it is
12
+
13
+ The canvas is a local web page that draws every fact and every file that cites it, and
14
+ updates live as files change. The user watches it while you work in the terminal, so
15
+ start it before anything else.
16
+
17
+ 1. Run `npx fakto canvas --background`. It starts the canvas detached (or finds the one already
18
+ running for this repo) and returns within a few seconds with one line:
19
+ - `fakto canvas: http://localhost:<port> (started in the background …)`: it started.
20
+ - `fakto canvas: http://localhost:<port> (already running for …)`: one was already up.
21
+ - `The canvas is not installed` or `Cannot find package`: the dependencies are missing.
22
+ Run `npm install`, then try again.
23
+ Never run plain `npx fakto canvas` yourself: it stays in the foreground and blocks you.
24
+ 2. If it timed out, read the log file it names and tell the user what it says.
25
+ 3. Run `npx fakto check` and keep its last line (the error and warning count).
26
+ 4. Tell the user, in two short lines, with the real URL and counts:
27
+ > The Fakto canvas is running at http://localhost:4848. Open it to watch facts and files
28
+ > update as we work.
29
+ > Right now: 31 facts, 15 files, check green (or: what the check reports).
30
+
31
+ Get the counts from `npx fakto graph --json` if you need them (`facts.length`,
32
+ `artifacts.length`).
33
+
34
+ ## While it runs
35
+
36
+ - Every edit you make shows up on the canvas within a second; mention it when the user should
37
+ look ("watch the marketing column").
38
+ - The canvas has its own buttons (Save on a fact, Sync, Sync all). They run the user's Claude
39
+ Code CLI in this repo; their results land in the same files you see.
40
+ - Leave it running when the session ends. If the user asks to stop it: `npx fakto canvas --stop`.