@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.
- package/dist/index.js +47 -12
- 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.
|
|
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
|
|
308
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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.
|
|
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": "
|
|
21
|
-
"prepublishOnly": "node scripts/check-pins.mjs"
|
|
20
|
+
"test": "echo no tests"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
23
|
"@types/node": "^20.19.43",
|