@earntori/cli 0.2.20 → 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 +43 -5
  2. package/package.json +1 -1
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");
@@ -408,6 +408,35 @@ function formatRun(r, n) {
408
408
  }
409
409
  const dbg = (m) => { if (process.env.TORI_DEBUG)
410
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
+ }
411
440
  async function check() {
412
441
  dbg("check: start");
413
442
  if (has("reminder")) {
@@ -472,7 +501,12 @@ async function check() {
472
501
  die(e.message);
473
502
  }
474
503
  const text = formatRun(r, files.length);
475
- 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;
476
510
  if (hook) {
477
511
  if (r.clean)
478
512
  return;
@@ -480,9 +514,13 @@ async function check() {
480
514
  console.log(`Tori: ${r.findings.length} finding(s) still open \u2014 run \`tori check\` to see them.`);
481
515
  return;
482
516
  }
483
- if (highs === 0)
484
- return; // only high blocks; medium/low are recorded and shown in status and at session start
485
- 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}` : ""));
486
524
  process.exit(2); // exit 2 = block the stop; stderr goes to the agent
487
525
  }
488
526
  out(r, text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earntori/cli",
3
- "version": "0.2.20",
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",