@earntori/cli 0.2.15 → 0.2.17

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 (2) hide show
  1. package/dist/index.js +83 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -163,7 +163,7 @@ function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true });
163
163
  // `install` writes that harness's files and returns their paths. Add a harness = add a row.
164
164
  // Pinned package versions written into agent config (B1b). CLI version = this package's own;
165
165
  // MCP version is a constant: bump it in the same commit as a @earntori/mcp release.
166
- const TORI_MCP_VERSION = "0.1.8";
166
+ const TORI_MCP_VERSION = "0.1.9";
167
167
  function cliVersion(cliBin) {
168
168
  try {
169
169
  return JSON.parse(readFileSync(resolve(dirname(realpathSync(cliBin)), "..", "package.json"), "utf8")).version ?? "latest";
@@ -305,11 +305,51 @@ function projectId() {
305
305
  catch { }
306
306
  return "local:" + createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12);
307
307
  }
308
+ /** Spec rev 86: paths never sent to Tori. `ignore` in .tori/project.json is a list of glob patterns
309
+ * (`fixtures/`, `seeds/**`, `*.snap`, `scratch`). A pattern with no slash matches at any depth. */
310
+ function ignorePatterns(root) {
311
+ try {
312
+ const j = JSON.parse(readFileSync(join(root, ".tori", "project.json"), "utf8"));
313
+ return Array.isArray(j?.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
314
+ }
315
+ catch {
316
+ return [];
317
+ }
318
+ }
319
+ function globToRe(g) {
320
+ let p = g.trim().replace(/^\.\//, "").replace(/\/+$/, "");
321
+ let re = "";
322
+ for (let i = 0; i < p.length; i++) {
323
+ const c = p[i];
324
+ if (c === "*") {
325
+ if (p[i + 1] === "*") {
326
+ re += ".*";
327
+ i++;
328
+ if (p[i + 1] === "/")
329
+ i++;
330
+ }
331
+ else
332
+ re += "[^/]*";
333
+ }
334
+ else if (c === "?")
335
+ re += "[^/]";
336
+ else
337
+ re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
338
+ }
339
+ return new RegExp((p.includes("/") ? "^" : "(^|/)") + re + "(/|$)");
340
+ }
341
+ function isIgnored(rel, pats) {
342
+ if (!pats.length)
343
+ return false;
344
+ const r = rel.replace(/\\/g, "/").replace(/^\.\//, "");
345
+ return pats.some((g) => globToRe(g).test(r));
346
+ }
308
347
  function gitChanged() {
309
348
  try {
310
349
  const o = execSync("git status --porcelain --untracked-files=all", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
311
350
  const SKIP = /(^|\/)(node_modules|dist|build|out|\.next|\.git|coverage|vendor|\.venv|venv|__pycache__|\.turbo|\.cache)(\/|$)/;
312
- return o.split("\n").map((l) => l.slice(3).trim()).filter(Boolean).map((p) => (p.includes(" -> ") ? p.split(" -> ")[1] : p)).filter((p) => !SKIP.test(p)).slice(0, 200);
351
+ const pats = ignorePatterns(process.cwd());
352
+ return o.split("\n").map((l) => l.slice(3).trim()).filter(Boolean).map((p) => (p.includes(" -> ") ? p.split(" -> ")[1] : p)).filter((p) => !SKIP.test(p) && !isIgnored(p, pats)).slice(0, 200);
313
353
  }
314
354
  catch {
315
355
  return [];
@@ -317,10 +357,11 @@ function gitChanged() {
317
357
  }
318
358
  function readCode(paths) {
319
359
  const files = [];
360
+ const pats = ignorePatterns(process.cwd());
320
361
  for (const p of paths) {
321
362
  const abs = isAbsolute(p) ? p : resolve(p);
322
363
  const rel = relative(process.cwd(), abs);
323
- if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs))
364
+ if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs) || isIgnored(rel, pats))
324
365
  continue;
325
366
  try {
326
367
  const c = readFileSync(abs, "utf8");
@@ -336,7 +377,7 @@ function formatRun(r, n) {
336
377
  return `\u2713 Tori checks clean \u2014 ${n} file(s), no open findings.`;
337
378
  const L = [`Tori checks: ${r.findings.length} finding(s) in ${n} file(s). Fix each one, then call the checks_resolve tool with the finding_id and file path so the fix is verified.`];
338
379
  for (const f of r.findings)
339
- L.push("", `[${f.severity.toUpperCase()}] ${f.path}${f.line ? ":" + f.line : ""} (${f.rule_id})`, `finding_id: ${f.id}`, f.message, "", "How to fix:", f.fix_hint);
380
+ L.push("", `[${f.severity.toUpperCase()}] ${f.path}${f.line ? ":" + f.line : ""} (${f.rule_id}${f.area ? " · " + f.area : ""})`, `finding_id: ${f.id}`, f.message, "", "How to fix:", f.fix_hint);
340
381
  if (r.waived?.length)
341
382
  L.push("", `Waived (${r.waived.length}, not blocking): ` + r.waived.map((w) => `${w.path}${w.line ? ":" + w.line : ""} ${w.rule_id} \u2014 ${w.reason ?? "no reason"}`).join("; "));
342
383
  if (r.reward?.issued)
@@ -539,7 +580,8 @@ async function standards() {
539
580
  return;
540
581
  }
541
582
  const r = await api(cfg, "GET", `/api/v1/standards?project=${encodeURIComponent(project)}`);
542
- const line = (s) => ` ${s.enabled ? "on " : "off"} ${s.id.padEnd(20)} ${s.name}${s.rules ? ` (${s.rules} checks)` : " (no checks yet)"}`;
583
+ const line = (s) => ` ${s.enabled ? "on " : "off"} ${s.id.padEnd(20)} ${s.name}${s.rules ? ` (${s.rules} checks)` : " (no checks yet)"}` +
584
+ (s.areas?.length ? `\n ${" ".repeat(20)} ${s.areas.map((a) => `${a.name} ${a.rules}`).join(" · ")}` : "");
543
585
  out(r, `${project.startsWith("connector:") ? `Project: ${project}\n\n` : ""}Coding standards \u2014 always on:\n${r.standards.filter((s) => s.tier === "coding").map(line).join("\n")}\n\nDomain standards \u2014 choose per project:\n${r.standards.filter((s) => s.tier === "domain").map(line).join("\n")}\n\nEnable: tori standards enable <id…> Disable: tori standards disable <id…>`);
544
586
  }
545
587
  async function rules() {
@@ -592,13 +634,48 @@ function logout() {
592
634
  unlinkSync(join(SESSIONS_DIR, f));
593
635
  out({ ok: true }, "\u2713 Signed out (all sessions removed)");
594
636
  }
595
- function help() { console.log(`tori \u2014 Tori CLI\n\n tori login [--base URL] sign in with GitHub (device flow)\n tori status [--json] balance + your AI keys\n tori key --limit 5 [--write env] [--file .env] [--json]\n tori stats [--json] your integration + developer program numbers\n tori init [--local] [--claude|--no-claude] [--cursor|--no-cursor] [--codex|--no-codex] wire Tori into this project (agents detected unless forced)\n tori check [paths...] [--json] run Tori's checks on changed files\n tori ledger [--project X|all] [--type a,b] [--since ISO] [--limit N] [--json]\n tori receipt <event_id|seq> fetch a receipt and verify it locally\n tori connector new [--label X] | list | revoke <id> Tori as a remote MCP connector (Claude Cowork etc.)\n tori standards [enable|disable <id…>] [--connector <label>] standards that apply to this project, or to a connector's\n tori rules [add "<rule>"|list|remove <id>] [--connector <label>] your own rules, in plain English\n tori init --base https://sandbox.earntori.com pin this repo to a Tori environment (.tori/project.json)\n tori logout\n`); }
637
+ function help() { console.log(`tori \u2014 Tori CLI\n\n tori login [--base URL] sign in with GitHub (device flow)\n tori status [--json] balance + your AI keys\n tori key --limit 5 [--write env] [--file .env] [--json]\n tori stats [--json] your integration + developer program numbers\n tori init [--local] [--claude|--no-claude] [--cursor|--no-cursor] [--codex|--no-codex] wire Tori into this project (agents detected unless forced)\n tori check [paths...] [--json] run Tori's checks on changed files\n tori ledger [--project X|all] [--type a,b] [--since ISO] [--limit N] [--json]\n tori receipt <event_id|seq> fetch a receipt and verify it locally\n tori connector new [--label X] | list | revoke <id> Tori as a remote MCP connector (Claude Cowork etc.)\n tori standards [enable|disable <id…>] [--connector <label>] standards that apply to this project, or to a connector's\n tori rules [add "<rule>"|list|remove <id>] [--connector <label>] your own rules, in plain English\n tori ignore add <glob…> | list | remove <glob> paths never sent to Tori (fixtures/, seeds/**, scratch); in .tori/project.json\n tori init --base https://sandbox.earntori.com pin this repo to a Tori environment (.tori/project.json)\n tori logout\n`); }
638
+ function ignoreCmd() {
639
+ const sub = args[1];
640
+ const pfile = join(".tori", "project.json");
641
+ let j = {};
642
+ try {
643
+ j = JSON.parse(readFileSync(pfile, "utf8"));
644
+ }
645
+ catch { }
646
+ const cur = Array.isArray(j.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
647
+ if (sub === "list" || !sub) {
648
+ if (has("json"))
649
+ return out({ ignore: cur }, cur.join("\n"));
650
+ console.log(cur.length ? cur.join("\n") : "No ignore patterns. Add one: tori ignore add fixtures/");
651
+ return;
652
+ }
653
+ const pats = args.slice(2).filter((a) => !a.startsWith("--")).map((p) => p.trim()).filter(Boolean);
654
+ if (sub === "add") {
655
+ if (!pats.length)
656
+ die("Usage: tori ignore add <glob> [<glob>…]");
657
+ const next = Array.from(new Set([...cur, ...pats]));
658
+ writeJson(pfile, { ...j, ignore: next });
659
+ console.log(`Ignoring ${next.length} pattern(s) in this project; never sent to Tori:\n` + next.join("\n"));
660
+ return;
661
+ }
662
+ if (sub === "remove") {
663
+ if (!pats.length)
664
+ die("Usage: tori ignore remove <glob>");
665
+ const next = cur.filter((p) => !pats.includes(p));
666
+ writeJson(pfile, { ...j, ignore: next });
667
+ console.log(next.length ? `Ignoring ${next.length} pattern(s):\n` + next.join("\n") : "No ignore patterns.");
668
+ return;
669
+ }
670
+ die("Usage: tori ignore add <glob…> | list | remove <glob>");
671
+ }
596
672
  (async () => {
597
673
  switch (cmd) {
598
674
  case "login": return login();
599
675
  case "status": return status();
600
676
  case "init": return init();
601
677
  case "check": return check();
678
+ case "ignore": return ignoreCmd();
602
679
  case "ledger": return ledger();
603
680
  case "receipt": return receipt();
604
681
  case "connector": return connector();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earntori/cli",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Tori CLI — holds your coding agent to a standard: checks before it calls a task done, each finding with its fix, on a signed record you can verify offline.",
5
5
  "license": "MIT",
6
6
  "type": "module",