@earntori/cli 0.2.19 → 0.2.21

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 +47 -12
  2. package/package.json +2 -3
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { readdirSync, realpathSync } from "node:fs";
4
4
  import { readFileSync, writeFileSync, mkdirSync, existsSync, appendFileSync, unlinkSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { join, resolve, relative, isAbsolute, dirname } from "node:path";
7
- import { execSync } from "node:child_process";
7
+ import { execSync, execFileSync } from "node:child_process";
8
8
  import { createHash, createPublicKey, verify as edVerify } from "node:crypto";
9
9
  const CONFIG_DIR = join(homedir(), ".tori");
10
10
  const CONFIG = join(CONFIG_DIR, "config.json");
@@ -162,8 +162,7 @@ function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true });
162
162
  // (Claude Code and Cursor: always; Codex: when it is installed or present in the repo, or --codex);
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
- // MCP version is a constant: bump it in the same commit as a @earntori/mcp release. Spec rev 97:
166
- // scripts/check-pins.mjs fails `test` and `prepublishOnly` when it differs from packages/mcp's version.
165
+ // MCP version is a constant: bump it in the same commit as a @earntori/mcp release.
167
166
  const TORI_MCP_VERSION = "0.1.12";
168
167
  function cliVersion(cliBin) {
169
168
  try {
@@ -304,15 +303,13 @@ function repoIndex() { try {
304
303
  catch {
305
304
  return undefined;
306
305
  } }
307
- /** Spec rev 97 (amends rev 86): `.env.example` is skipped when it matches an ignore pattern, and when it is
308
- * read only variable names leave the machine, never values. Names are taken with the same pattern the one
309
- * rule that uses them applies, so the check is unchanged. */
306
+ /** Spec rev 97, corrected by rev 99 (amends rev 86): `.env.example` is skipped when it matches an ignore
307
+ * pattern; otherwise it is sent as before (up to 20,000 characters). */
310
308
  function envExample() {
311
309
  try {
312
310
  if (!existsSync(".env.example") || isIgnored(".env.example", ignorePatterns(process.cwd())))
313
311
  return undefined;
314
- const names = readFileSync(".env.example", "utf8").match(/^\s*[A-Z][A-Z0-9_]+\s*=/gm) ?? [];
315
- return names.map((l) => l.replace(/\s*=.*$/, "").trim() + "=").join("\n").slice(0, 20000);
312
+ return readFileSync(".env.example", "utf8").slice(0, 20000);
316
313
  }
317
314
  catch {
318
315
  return undefined;
@@ -411,6 +408,35 @@ function formatRun(r, n) {
411
408
  }
412
409
  const dbg = (m) => { if (process.env.TORI_DEBUG)
413
410
  console.error(`[tori ${new Date().toISOString().slice(11, 23)}] ${m}`); };
411
+ /** Spec rev 101 (fits in phase A3; extends rev 90): the lines of `path` that differ from the last commit, as
412
+ * line numbers in the working copy. "all" when the file has no committed version, and when git cannot answer
413
+ * (the behaviour before rev 101). */
414
+ const changedLinesCache = new Map();
415
+ function changedLines(path) {
416
+ const hit = changedLinesCache.get(path);
417
+ if (hit)
418
+ return hit;
419
+ let v = "all";
420
+ try {
421
+ const git = (a) => execFileSync("git", a, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 16 * 1024 * 1024 });
422
+ if (git(["ls-tree", "--name-only", "HEAD", "--", path]).trim()) {
423
+ const diff = git(["diff", "--no-color", "--no-ext-diff", "--no-textconv", "-U0", "HEAD", "--", path]);
424
+ const s = new Set();
425
+ let hunks = 0;
426
+ for (const m of diff.matchAll(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gm)) {
427
+ hunks++;
428
+ const start = Number(m[1]), n = m[2] === undefined ? 1 : Number(m[2]);
429
+ for (let i = 0; i < n; i++)
430
+ s.add(start + i);
431
+ }
432
+ if (hunks > 0 || !diff.trim())
433
+ v = s; // a diff with no readable hunks stays "all"
434
+ }
435
+ }
436
+ catch { }
437
+ changedLinesCache.set(path, v);
438
+ return v;
439
+ }
414
440
  async function check() {
415
441
  dbg("check: start");
416
442
  if (has("reminder")) {
@@ -475,7 +501,12 @@ async function check() {
475
501
  die(e.message);
476
502
  }
477
503
  const text = formatRun(r, files.length);
478
- const highs = r.findings.filter((f) => f.severity === "high").length, meds = r.findings.filter((f) => f.severity === "medium").length, lows = r.findings.length - highs - meds;
504
+ // Spec rev 101: in hook mode a turn is held only by findings on lines it changed. A finding with no line number
505
+ // holds, since it cannot be placed. Findings on other lines stay open on the record and are counted as older.
506
+ const held = hook ? r.findings.filter((f) => { if (!f.line)
507
+ return true; const c = changedLines(f.path); return c === "all" || c.has(f.line); }) : r.findings;
508
+ const older = r.findings.length - held.length;
509
+ const highs = held.filter((f) => f.severity === "high").length, meds = held.filter((f) => f.severity === "medium").length, lows = held.length - highs - meds;
479
510
  if (hook) {
480
511
  if (r.clean)
481
512
  return;
@@ -483,9 +514,13 @@ async function check() {
483
514
  console.log(`Tori: ${r.findings.length} finding(s) still open \u2014 run \`tori check\` to see them.`);
484
515
  return;
485
516
  }
486
- if (highs === 0)
487
- return; // only high blocks; medium/low are recorded and shown in status and at session start
488
- console.error(text + `\n\n${highs} high (blocking)${meds ? `, ${meds} medium` : ""}${lows ? `, ${lows} low` : ""} — medium and low are recorded and do not block.`);
517
+ const olderNote = older ? `Tori: ${older} older finding(s) open on lines this turn did not change \u2014 recorded, not blocking. Run \`tori check\` to see them.` : "";
518
+ if (highs === 0) {
519
+ if (olderNote)
520
+ console.log(olderNote);
521
+ return;
522
+ } // only high on a changed line blocks; medium/low are recorded and shown in status and at session start
523
+ console.error(formatRun({ ...r, findings: held }, files.length) + `\n\n${highs} high (blocking)${meds ? `, ${meds} medium` : ""}${lows ? `, ${lows} low` : ""} — medium and low are recorded and do not block.` + (olderNote ? `\n${olderNote}` : ""));
489
524
  process.exit(2); // exit 2 = block the stop; stderr goes to the agent
490
525
  }
491
526
  out(r, text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earntori/cli",
3
- "version": "0.2.19",
3
+ "version": "0.2.21",
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",
@@ -17,8 +17,7 @@
17
17
  "scripts": {
18
18
  "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
19
19
  "typecheck": "tsc --noEmit -p tsconfig.json",
20
- "test": "node scripts/check-pins.mjs",
21
- "prepublishOnly": "node scripts/check-pins.mjs"
20
+ "test": "echo no tests"
22
21
  },
23
22
  "devDependencies": {
24
23
  "@types/node": "^20.19.43",