@chit-run/cli 0.5.0 → 0.6.0

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/chit.js +204 -111
  2. package/package.json +1 -1
package/dist/chit.js CHANGED
@@ -970,6 +970,7 @@ function validateLoopRecord(raw) {
970
970
  scope: str2(o, "scope", ctx),
971
971
  task: str2(o, "task", ctx),
972
972
  repo: str2(o, "repo", ctx),
973
+ repoKey: str2(o, "repoKey", ctx),
973
974
  startedAt: str2(o, "startedAt", ctx),
974
975
  maxIterations: int2(o, "maxIterations", ctx, 1)
975
976
  };
@@ -988,8 +989,11 @@ function validateLoopRecord(raw) {
988
989
  checkDurationMs: int2(o, "checkDurationMs", ctx, 0),
989
990
  at: str2(o, "at", ctx)
990
991
  };
991
- if (o.detailsRef !== undefined)
992
- rec.detailsRef = str2(o, "detailsRef", ctx);
992
+ if (o.workspaceWarnings !== undefined) {
993
+ rec.workspaceWarnings = stringArray2(o, "workspaceWarnings", ctx);
994
+ }
995
+ if (o.auditRef !== undefined)
996
+ rec.auditRef = str2(o, "auditRef", ctx);
993
997
  const usage = optUsage2(o, ctx);
994
998
  if (usage !== undefined)
995
999
  rec.usage = usage;
@@ -9964,11 +9968,11 @@ var init_dist = __esm(() => {
9964
9968
 
9965
9969
  // ../studio/src/server/audit.ts
9966
9970
  import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
9967
- import { homedir as homedir5 } from "os";
9968
- import { join as join8 } from "path";
9971
+ import { homedir as homedir6 } from "os";
9972
+ import { join as join9 } from "path";
9969
9973
  function defaultAuditDir2() {
9970
- const xdg = process.env.XDG_STATE_HOME || join8(homedir5(), ".local", "state");
9971
- return join8(xdg, "chit", "audit");
9974
+ const xdg = process.env.XDG_STATE_HOME || join9(homedir6(), ".local", "state");
9975
+ return join9(xdg, "chit", "audit");
9972
9976
  }
9973
9977
  function blobRefs(e) {
9974
9978
  switch (e.type) {
@@ -9987,8 +9991,8 @@ function blobRefs(e) {
9987
9991
  function readAuditRun(auditDir, runId, includeBlobs) {
9988
9992
  if (!SAFE_RUN_ID2.test(runId))
9989
9993
  return { kind: "invalid-id" };
9990
- const runDir = join8(auditDir, "runs", runId);
9991
- const eventsPath = join8(runDir, "events.jsonl");
9994
+ const runDir = join9(auditDir, "runs", runId);
9995
+ const eventsPath = join9(runDir, "events.jsonl");
9992
9996
  if (!existsSync8(eventsPath))
9993
9997
  return { kind: "not-found" };
9994
9998
  let events2;
@@ -10001,13 +10005,13 @@ function readAuditRun(auditDir, runId, includeBlobs) {
10001
10005
  }
10002
10006
  if (!includeBlobs)
10003
10007
  return { kind: "ok", events: events2 };
10004
- const blobsDir = join8(runDir, "blobs");
10008
+ const blobsDir = join9(runDir, "blobs");
10005
10009
  const blobs = {};
10006
10010
  for (const e of events2) {
10007
10011
  for (const ref of blobRefs(e)) {
10008
10012
  if (!SHA256_HEX2.test(ref) || ref in blobs)
10009
10013
  continue;
10010
- const blobPath = join8(blobsDir, ref);
10014
+ const blobPath = join9(blobsDir, ref);
10011
10015
  if (existsSync8(blobPath))
10012
10016
  blobs[ref] = readFileSync9(blobPath, "utf-8");
10013
10017
  }
@@ -10094,7 +10098,7 @@ var init_paths = __esm(() => {
10094
10098
 
10095
10099
  // ../studio/src/server/discovery.ts
10096
10100
  import { readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
10097
- import { basename, join as join9, relative } from "path";
10101
+ import { basename, join as join10, relative } from "path";
10098
10102
  function safeParseChit(absolutePath) {
10099
10103
  try {
10100
10104
  const raw2 = JSON.parse(readFileSync10(absolutePath, "utf-8"));
@@ -10124,7 +10128,7 @@ function discover(opts) {
10124
10128
  continue;
10125
10129
  if (!entry.name.endsWith(".json"))
10126
10130
  continue;
10127
- const absolutePath = join9(opts.cwd, entry.name);
10131
+ const absolutePath = join10(opts.cwd, entry.name);
10128
10132
  if (!safeParseChit(absolutePath))
10129
10133
  continue;
10130
10134
  candidates.push({
@@ -10149,14 +10153,14 @@ var init_discovery = __esm(() => {
10149
10153
  });
10150
10154
 
10151
10155
  // ../studio/src/server/docs.ts
10152
- import { createHash as createHash5 } from "crypto";
10156
+ import { createHash as createHash6 } from "crypto";
10153
10157
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
10154
10158
  import { basename as basename2, relative as relative2 } from "path";
10155
10159
  function canonicalize(draft) {
10156
10160
  return JSON.stringify(draft, null, "\t");
10157
10161
  }
10158
10162
  function hashRaw(raw2) {
10159
- return createHash5("sha256").update(raw2, "utf-8").digest("hex");
10163
+ return createHash6("sha256").update(raw2, "utf-8").digest("hex");
10160
10164
  }
10161
10165
 
10162
10166
  class DocStore {
@@ -10363,10 +10367,7 @@ var init_docs = __esm(() => {
10363
10367
 
10364
10368
  // ../studio/src/server/loops.ts
10365
10369
  import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
10366
- import { join as join10 } from "path";
10367
- function loopsDir2(cwd) {
10368
- return join10(cwd, ".chit", "loops");
10369
- }
10370
+ import { join as join11 } from "path";
10370
10371
  function summarize(loopId, records) {
10371
10372
  const header = records[0];
10372
10373
  if (header?.type !== "loop")
@@ -10383,26 +10384,8 @@ function summarize(loopId, records) {
10383
10384
  startedAt: header.startedAt
10384
10385
  };
10385
10386
  }
10386
- function listLoops(cwd) {
10387
- const dir = loopsDir2(cwd);
10388
- if (!existsSync10(dir))
10389
- return [];
10390
- const summaries = [];
10391
- for (const name of readdirSync4(dir)) {
10392
- if (!name.endsWith(".jsonl"))
10393
- continue;
10394
- const loopId = name.slice(0, -".jsonl".length);
10395
- const result = readLoop2(cwd, loopId);
10396
- if (result.kind === "ok")
10397
- summaries.push(summarize(loopId, result.records));
10398
- }
10399
- summaries.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
10400
- return summaries;
10401
- }
10402
- function readLoop2(cwd, loopId) {
10403
- if (!SAFE_LOOP_ID2.test(loopId))
10404
- return { kind: "invalid-id" };
10405
- const path = join10(loopsDir2(cwd), `${loopId}.jsonl`);
10387
+ function readLoopFrom(dir, loopId) {
10388
+ const path = join11(dir, `${loopId}.jsonl`);
10406
10389
  if (!existsSync10(path))
10407
10390
  return { kind: "not-found" };
10408
10391
  try {
@@ -10418,6 +10401,30 @@ function readLoop2(cwd, loopId) {
10418
10401
  throw e;
10419
10402
  }
10420
10403
  }
10404
+ function listLoops(loopsDir) {
10405
+ if (!loopsDir || !existsSync10(loopsDir))
10406
+ return [];
10407
+ const summaries = [];
10408
+ for (const name of readdirSync4(loopsDir)) {
10409
+ if (!name.endsWith(".jsonl"))
10410
+ continue;
10411
+ const loopId = name.slice(0, -".jsonl".length);
10412
+ if (!SAFE_LOOP_ID2.test(loopId))
10413
+ continue;
10414
+ const result = readLoopFrom(loopsDir, loopId);
10415
+ if (result.kind === "ok")
10416
+ summaries.push(summarize(loopId, result.records));
10417
+ }
10418
+ summaries.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
10419
+ return summaries;
10420
+ }
10421
+ function readLoop2(loopsDir, loopId) {
10422
+ if (!SAFE_LOOP_ID2.test(loopId))
10423
+ return { kind: "invalid-id" };
10424
+ if (!loopsDir)
10425
+ return { kind: "not-found" };
10426
+ return readLoopFrom(loopsDir, loopId);
10427
+ }
10421
10428
  var SAFE_LOOP_ID2;
10422
10429
  var init_loops = __esm(() => {
10423
10430
  init_src();
@@ -10456,7 +10463,7 @@ __export(exports_server, {
10456
10463
  PathError: () => PathError
10457
10464
  });
10458
10465
  import { existsSync as existsSync11 } from "fs";
10459
- import { join as join11 } from "path";
10466
+ import { join as join12 } from "path";
10460
10467
  async function startStudio(opts) {
10461
10468
  const hostname3 = opts.hostname ?? "127.0.0.1";
10462
10469
  const requestedPort = opts.port ?? 0;
@@ -10474,7 +10481,8 @@ async function startStudio(opts) {
10474
10481
  store,
10475
10482
  allowedHosts,
10476
10483
  clientDistDir,
10477
- lifecycle: opts.lifecycle
10484
+ lifecycle: opts.lifecycle,
10485
+ loopsDir: opts.loopsDir
10478
10486
  });
10479
10487
  const server2 = Bun.serve({
10480
10488
  port: requestedPort,
@@ -10508,7 +10516,7 @@ function buildApp(opts) {
10508
10516
  const asset = c.req.param("asset");
10509
10517
  if (!CLIENT_ASSETS.has(asset))
10510
10518
  return c.text("not found", 404);
10511
- const path = join11(opts.clientDistDir, asset);
10519
+ const path = join12(opts.clientDistDir, asset);
10512
10520
  if (!existsSync11(path)) {
10513
10521
  return c.text(`client bundle missing at ${path}. Run: bun run studio:build`, 503);
10514
10522
  }
@@ -10589,10 +10597,10 @@ function buildApp(opts) {
10589
10597
  return c.json(result);
10590
10598
  });
10591
10599
  app.get("/api/loops", (c) => {
10592
- return c.json(listLoops(opts.cwd));
10600
+ return c.json(listLoops(opts.loopsDir));
10593
10601
  });
10594
10602
  app.get("/api/loops/:loopId", (c) => {
10595
- const result = readLoop2(opts.cwd, c.req.param("loopId"));
10603
+ const result = readLoop2(opts.loopsDir, c.req.param("loopId"));
10596
10604
  if (result.kind === "not-found")
10597
10605
  return c.text("not found", 404);
10598
10606
  if (result.kind === "invalid-id")
@@ -10684,15 +10692,15 @@ var init_server = __esm(() => {
10684
10692
  init_loops();
10685
10693
  init_token();
10686
10694
  init_paths();
10687
- CLIENT_DIST = join11(import.meta.dir, "..", "..", "dist", "client");
10695
+ CLIENT_DIST = join12(import.meta.dir, "..", "..", "dist", "client");
10688
10696
  CLIENT_ASSETS = new Set(["index.js", "index.css"]);
10689
10697
  });
10690
10698
 
10691
10699
  // src/cli/run.ts
10692
10700
  init_src();
10693
10701
  import { readFileSync as readFileSync13 } from "fs";
10694
- import { homedir as homedir6 } from "os";
10695
- import { basename as basename3, dirname as dirname2, join as join12 } from "path";
10702
+ import { homedir as homedir7 } from "os";
10703
+ import { basename as basename3, dirname as dirname2, join as join13 } from "path";
10696
10704
 
10697
10705
  // src/adapters/sanitize.ts
10698
10706
  var SENSITIVE_KEY = /key|token|secret|password|auth/i;
@@ -11596,6 +11604,45 @@ function wrapAdaptersWithAudit(adapters, recorder) {
11596
11604
  return out;
11597
11605
  }
11598
11606
 
11607
+ // src/loops/location.ts
11608
+ import { spawnSync } from "child_process";
11609
+ import { createHash as createHash2 } from "crypto";
11610
+ import { realpathSync } from "fs";
11611
+ import { homedir as homedir3 } from "os";
11612
+ import { join as join3 } from "path";
11613
+ function gitTopLevel(cwd) {
11614
+ try {
11615
+ const out = spawnSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
11616
+ encoding: "utf-8",
11617
+ stdio: ["ignore", "pipe", "ignore"]
11618
+ });
11619
+ if (out.status !== 0)
11620
+ return;
11621
+ const top = out.stdout.trim();
11622
+ return top || undefined;
11623
+ } catch {
11624
+ return;
11625
+ }
11626
+ }
11627
+ function repoRoot(cwd) {
11628
+ const base = gitTopLevel(cwd) ?? cwd;
11629
+ try {
11630
+ return realpathSync(base);
11631
+ } catch {
11632
+ return base;
11633
+ }
11634
+ }
11635
+ function repoKey(cwd) {
11636
+ return createHash2("sha256").update(repoRoot(cwd)).digest("hex").slice(0, 16);
11637
+ }
11638
+ function loopStateDir() {
11639
+ const xdg = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
11640
+ return join3(xdg, "chit", "loops");
11641
+ }
11642
+ function loopLogDir(cwd) {
11643
+ return join3(loopStateDir(), repoKey(cwd));
11644
+ }
11645
+
11599
11646
  // src/runtime/render.ts
11600
11647
  import { existsSync as existsSync3 } from "fs";
11601
11648
  import { isAbsolute, resolve } from "path";
@@ -11818,7 +11865,7 @@ async function executeManifest(manifest, options) {
11818
11865
  }
11819
11866
 
11820
11867
  // src/sessions/fingerprint.ts
11821
- import { createHash as createHash2 } from "crypto";
11868
+ import { createHash as createHash3 } from "crypto";
11822
11869
  function computeFingerprint(input) {
11823
11870
  const { agent, participant } = input;
11824
11871
  const baseUrl = agent.env?.ANTHROPIC_BASE_URL ?? agent.env?.OPENAI_BASE_URL ?? agent.env?.OLLAMA_HOST ?? "";
@@ -11834,7 +11881,7 @@ function computeFingerprint(input) {
11834
11881
  session: participant.session,
11835
11882
  permissions: participant.permissions
11836
11883
  });
11837
- return createHash2("sha256").update(material).digest("hex").slice(0, 16);
11884
+ return createHash3("sha256").update(material).digest("hex").slice(0, 16);
11838
11885
  }
11839
11886
 
11840
11887
  // src/sessions/coordinator.ts
@@ -11886,7 +11933,7 @@ function buildSessionAdapter(inner, manifestId, scope, trackedFingerprints, stor
11886
11933
  }
11887
11934
 
11888
11935
  // src/sessions/store.ts
11889
- import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
11936
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
11890
11937
  import {
11891
11938
  closeSync,
11892
11939
  existsSync as existsSync4,
@@ -11898,8 +11945,8 @@ import {
11898
11945
  writeFileSync as writeFileSync2,
11899
11946
  writeSync
11900
11947
  } from "fs";
11901
- import { homedir as homedir3 } from "os";
11902
- import { dirname, join as join3 } from "path";
11948
+ import { homedir as homedir4 } from "os";
11949
+ import { dirname, join as join4 } from "path";
11903
11950
  function isObject4(v) {
11904
11951
  return v !== null && typeof v === "object" && !Array.isArray(v);
11905
11952
  }
@@ -11916,8 +11963,8 @@ function sleepSync(ms) {
11916
11963
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
11917
11964
  }
11918
11965
  function defaultSessionDir() {
11919
- const xdg = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
11920
- return join3(xdg, "chit", "sessions");
11966
+ const xdg = process.env.XDG_STATE_HOME || join4(homedir4(), ".local", "state");
11967
+ return join4(xdg, "chit", "sessions");
11921
11968
  }
11922
11969
 
11923
11970
  class FileSessionStore {
@@ -12012,16 +12059,16 @@ class FileSessionStore {
12012
12059
  }
12013
12060
  filePath(key) {
12014
12061
  const readable = `${safeSegment(key.scope)}--${safeSegment(key.manifestId)}`;
12015
- const hash = createHash3("sha256").update(`${key.scope}${HASH_SEP}${key.manifestId}`).digest("hex").slice(0, 12);
12016
- return join3(this.baseDir, `${readable}--${hash}.json`);
12062
+ const hash = createHash4("sha256").update(`${key.scope}${HASH_SEP}${key.manifestId}`).digest("hex").slice(0, 12);
12063
+ return join4(this.baseDir, `${readable}--${hash}.json`);
12017
12064
  }
12018
12065
  }
12019
12066
 
12020
12067
  // src/surfaces/claude-skill.ts
12021
12068
  init_src();
12022
- import { createHash as createHash4, randomBytes } from "crypto";
12069
+ import { createHash as createHash5, randomBytes } from "crypto";
12023
12070
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
12024
- import { join as join4, resolve as resolvePath } from "path";
12071
+ import { join as join5, resolve as resolvePath } from "path";
12025
12072
  class SurfaceInstallError extends Error {
12026
12073
  constructor(message) {
12027
12074
  super(message);
@@ -12083,7 +12130,7 @@ ${formatEnforcementGaps(gaps)}
12083
12130
  Pass allowUnenforcedPermissions=true to install anyway; the generated skill will warn on every run.`);
12084
12131
  }
12085
12132
  const installName = opts.overrideName ?? manifest.id;
12086
- const skillDir = join4(outputDir, installName);
12133
+ const skillDir = join5(outputDir, installName);
12087
12134
  if (existsSync5(skillDir)) {
12088
12135
  if (!opts.force) {
12089
12136
  throw new SurfaceInstallError(`skill directory already exists: ${skillDir}
@@ -12093,9 +12140,9 @@ Pass force=true to remove and replace it, or use overrideName="<id>" to install
12093
12140
  rmSync3(skillDir, { recursive: true, force: true });
12094
12141
  }
12095
12142
  mkdirSync3(skillDir, { recursive: true });
12096
- const skillMdPath = join4(skillDir, "SKILL.md");
12097
- const manifestPath = join4(skillDir, "manifest.json");
12098
- const markerPath = join4(skillDir, INSTALL_MARKER_FILENAME);
12143
+ const skillMdPath = join5(skillDir, "SKILL.md");
12144
+ const manifestPath = join5(skillDir, "manifest.json");
12145
+ const markerPath = join5(skillDir, INSTALL_MARKER_FILENAME);
12099
12146
  const manifestJson = `${JSON.stringify(rawJson, null, 2)}
12100
12147
  `;
12101
12148
  writeFileSync3(manifestPath, manifestJson);
@@ -12115,7 +12162,7 @@ Pass force=true to remove and replace it, or use overrideName="<id>" to install
12115
12162
  manifestId: manifest.id,
12116
12163
  runtimePath,
12117
12164
  installedAt: new Date().toISOString(),
12118
- manifestHash: createHash4("sha256").update(manifestJson).digest("hex")
12165
+ manifestHash: createHash5("sha256").update(manifestJson).digest("hex")
12119
12166
  };
12120
12167
  writeFileSync3(markerPath, `${JSON.stringify(marker, null, 2)}
12121
12168
  `);
@@ -12182,10 +12229,10 @@ function escapeFrontmatter(s) {
12182
12229
  // src/surfaces/lifecycle.ts
12183
12230
  init_src();
12184
12231
  import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, statSync as statSync2 } from "fs";
12185
- import { homedir as homedir4 } from "os";
12186
- import { join as join5 } from "path";
12232
+ import { homedir as homedir5 } from "os";
12233
+ import { join as join6 } from "path";
12187
12234
  function defaultSkillsDir() {
12188
- return join5(homedir4(), ".claude", "skills");
12235
+ return join6(homedir5(), ".claude", "skills");
12189
12236
  }
12190
12237
 
12191
12238
  class LifecycleError extends Error {
@@ -12200,7 +12247,7 @@ function listInstalled(parentDir) {
12200
12247
  const out = [];
12201
12248
  const entries = readdirSync2(parentDir);
12202
12249
  for (const name of entries) {
12203
- const skillDir = join5(parentDir, name);
12250
+ const skillDir = join6(parentDir, name);
12204
12251
  let stat;
12205
12252
  try {
12206
12253
  stat = statSync2(skillDir);
@@ -12209,7 +12256,7 @@ function listInstalled(parentDir) {
12209
12256
  }
12210
12257
  if (!stat.isDirectory())
12211
12258
  continue;
12212
- const markerPath = join5(skillDir, INSTALL_MARKER_FILENAME);
12259
+ const markerPath = join6(skillDir, INSTALL_MARKER_FILENAME);
12213
12260
  if (!existsSync6(markerPath))
12214
12261
  continue;
12215
12262
  let raw;
@@ -12229,14 +12276,14 @@ function uninstall(parentDir, name) {
12229
12276
  if (!VALID_INSTALL_NAME_RE.test(name)) {
12230
12277
  throw new LifecycleError(`install name "${name}" is invalid: must be kebab-case (lowercase letters, digits, hyphens; must start with a letter). Path-traversal sequences like ".." or "/" are rejected.`);
12231
12278
  }
12232
- const skillDir = join5(parentDir, name);
12279
+ const skillDir = join6(parentDir, name);
12233
12280
  if (!existsSync6(skillDir)) {
12234
12281
  throw new LifecycleError(`no install at ${skillDir}`);
12235
12282
  }
12236
12283
  if (!statSync2(skillDir).isDirectory()) {
12237
12284
  throw new LifecycleError(`${skillDir} is not a directory`);
12238
12285
  }
12239
- const markerPath = join5(skillDir, INSTALL_MARKER_FILENAME);
12286
+ const markerPath = join6(skillDir, INSTALL_MARKER_FILENAME);
12240
12287
  if (!existsSync6(markerPath)) {
12241
12288
  throw new LifecycleError(`refusing to uninstall ${skillDir}: no install marker (${INSTALL_MARKER_FILENAME}) present. ` + `This directory was not created by chit (or was created by a pre-marker version). ` + `If you're sure this is yours, remove it manually with rm -rf.`);
12242
12289
  }
@@ -35061,8 +35108,7 @@ import { resolve as resolve2 } from "path";
35061
35108
  // src/loops/log-store.ts
35062
35109
  init_src();
35063
35110
  import { appendFileSync as appendFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
35064
- import { join as join6 } from "path";
35065
-
35111
+ import { join as join7 } from "path";
35066
35112
  class LoopStoreError extends Error {
35067
35113
  }
35068
35114
  var SAFE_LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
@@ -35070,14 +35116,14 @@ var realClock2 = () => Date.now();
35070
35116
  function iso(ms) {
35071
35117
  return new Date(ms).toISOString();
35072
35118
  }
35073
- function loopsDir(cwd) {
35074
- return join6(cwd, ".chit", "loops");
35075
- }
35076
- function loopPath(cwd, loopId) {
35119
+ function safeId(loopId) {
35077
35120
  if (!SAFE_LOOP_ID.test(loopId)) {
35078
35121
  throw new LoopStoreError(`invalid loop id ${JSON.stringify(loopId)}`);
35079
35122
  }
35080
- return join6(loopsDir(cwd), `${loopId}.jsonl`);
35123
+ return loopId;
35124
+ }
35125
+ function loopPath(cwd, loopId) {
35126
+ return join7(loopLogDir(cwd), `${safeId(loopId)}.jsonl`);
35081
35127
  }
35082
35128
  function readRecords(path, loopId) {
35083
35129
  if (!existsSync7(path)) {
@@ -35096,14 +35142,15 @@ function startLoop(cwd, opts) {
35096
35142
  if (existsSync7(path) && !opts.force) {
35097
35143
  throw new LoopStoreError(`loop log already exists at ${path} (pass force to overwrite)`);
35098
35144
  }
35099
- mkdirSync4(loopsDir(cwd), { recursive: true });
35145
+ mkdirSync4(loopLogDir(cwd), { recursive: true });
35100
35146
  const header = {
35101
35147
  type: "loop",
35102
35148
  schema: 1,
35103
35149
  loopId,
35104
35150
  scope: opts.scope,
35105
35151
  task: opts.task,
35106
- repo: cwd,
35152
+ repo: repoRoot(cwd),
35153
+ repoKey: repoKey(cwd),
35107
35154
  startedAt: iso((opts.clock ?? realClock2)()),
35108
35155
  maxIterations: opts.maxIterations
35109
35156
  };
@@ -35134,8 +35181,11 @@ function appendIteration(cwd, loopId, opts) {
35134
35181
  checkDurationMs: opts.checkDurationMs,
35135
35182
  at: iso((opts.clock ?? realClock2)())
35136
35183
  };
35137
- if (opts.detailsRef !== undefined)
35138
- rec.detailsRef = opts.detailsRef;
35184
+ if (opts.workspaceWarnings !== undefined && opts.workspaceWarnings.length > 0) {
35185
+ rec.workspaceWarnings = opts.workspaceWarnings;
35186
+ }
35187
+ if (opts.auditRef !== undefined)
35188
+ rec.auditRef = opts.auditRef;
35139
35189
  if (opts.usage !== undefined)
35140
35190
  rec.usage = opts.usage;
35141
35191
  appendFileSync2(path, `${serializeLoopRecord(rec)}
@@ -35172,7 +35222,7 @@ function readLoop(cwd, loopId) {
35172
35222
  var DEFAULT_CONVERGE_MANIFEST = {
35173
35223
  schema: 1,
35174
35224
  id: "converge",
35175
- description: "Autonomous convergence: a write-capable Claude implements a slice, then a read-only Codex reviews the diff and returns proceed/revise/block. Drive it in a loop with the `chit converge` CLI driver, or stepwise from the MCP - one chit_start per iteration, same scope so both agents keep their thread, feeding the prior review back in via inputs.prior_review. The human sequences and checkpoints (inspect the diff each round, stop if it goes sideways); chit runs the agents. Run against an isolated worktree, not the main checkout.",
35225
+ description: "Autonomous convergence: a write-capable Claude implements a slice, then a read-only Codex reviews the diff and returns proceed/revise/block. Drive it in a loop with the `chit converge` CLI driver, or stepwise from the MCP - one chit_run_start per iteration, same scope so both agents keep their thread, feeding the prior review back in via inputs.prior_review. The human sequences and checkpoints (inspect the diff each round, stop if it goes sideways); chit runs the agents. Run against an isolated worktree, not the main checkout.",
35176
35226
  inputs: {
35177
35227
  task: { type: "string" },
35178
35228
  prior_review: { type: "string", optional: true }
@@ -35189,7 +35239,7 @@ var DEFAULT_CONVERGE_MANIFEST = {
35189
35239
  },
35190
35240
  reviewer: {
35191
35241
  agent: "codex",
35192
- role: "You are a skeptical implementation reviewer for a convergence loop. Claude just edited the repository at your cwd. Inspect the current git diff and the changed files, verify the work against the task, and run non-mutating checks if useful. Do not edit. Do not agree for the sake of agreeing. Use prior context from this scope. Cite file:line and command results.",
35242
+ role: "You are a skeptical implementation reviewer for a convergence loop. Claude just edited the repository at your cwd. Inspect the current git diff and the changed files, and verify the work against the task. Base your verdict on the TASK changes. Untracked generated build artifacts (e.g. __pycache__, *.pyc) are workspace hygiene, not task changes: note them at most as a minor aside and do NOT revise solely because of them. chit keeps its own control-plane state outside the repo, so it never appears in the diff. Run non-mutating checks if useful. Do not edit. Do not agree for the sake of agreeing. Use prior context from this scope. Cite file:line and command results.",
35193
35243
  session: "per_scope",
35194
35244
  permissions: { filesystem: "read_only" }
35195
35245
  }
@@ -35238,6 +35288,32 @@ findingCount is the integer number of findings; checksRun is a short human strin
35238
35288
  output: "out"
35239
35289
  };
35240
35290
 
35291
+ // src/cli/workspace.ts
35292
+ function isChitOwned(path) {
35293
+ return path === ".chit" || path.startsWith(".chit/");
35294
+ }
35295
+ function isGeneratedArtifact(path) {
35296
+ const base = path.slice(path.lastIndexOf("/") + 1);
35297
+ return path === "__pycache__" || path.startsWith("__pycache__/") || path.includes("/__pycache__/") || path.endsWith(".pyc") || path.endsWith(".pyo") || base === ".DS_Store";
35298
+ }
35299
+ function classifyWorkspace(snap) {
35300
+ const changed = new Set;
35301
+ for (const f of snap.tracked) {
35302
+ if (!isChitOwned(f))
35303
+ changed.add(f);
35304
+ }
35305
+ const workspaceWarnings = [];
35306
+ for (const f of snap.untracked) {
35307
+ if (isChitOwned(f))
35308
+ continue;
35309
+ if (isGeneratedArtifact(f))
35310
+ workspaceWarnings.push(`untracked generated artifact: ${f}`);
35311
+ else
35312
+ changed.add(f);
35313
+ }
35314
+ return { changedFiles: [...changed], workspaceWarnings };
35315
+ }
35316
+
35241
35317
  // src/cli/converge.ts
35242
35318
  var defaultIO = {
35243
35319
  out: (s) => process.stdout.write(s),
@@ -35330,13 +35406,14 @@ function gitLines(cwd, args) {
35330
35406
  return [];
35331
35407
  }
35332
35408
  }
35333
- function gitChangedFiles(cwd) {
35334
- const all = [
35335
- ...gitLines(cwd, ["diff", "--name-only"]),
35336
- ...gitLines(cwd, ["diff", "--cached", "--name-only"]),
35337
- ...gitLines(cwd, ["ls-files", "--others", "--exclude-standard"])
35338
- ];
35339
- return [...new Set(all)];
35409
+ function gitWorkspace(cwd) {
35410
+ return classifyWorkspace({
35411
+ tracked: [
35412
+ ...gitLines(cwd, ["diff", "--name-only"]),
35413
+ ...gitLines(cwd, ["diff", "--cached", "--name-only"])
35414
+ ],
35415
+ untracked: gitLines(cwd, ["ls-files", "--others", "--exclude-standard"])
35416
+ });
35340
35417
  }
35341
35418
 
35342
35419
  class ConvergeExecuteError extends Error {
@@ -35366,17 +35443,18 @@ async function runConvergeIteration(ctx) {
35366
35443
  const reviewText = result.outputs.review ?? "";
35367
35444
  const review = parseReview(reviewText);
35368
35445
  const usage = sumTraceUsage(result.trace);
35369
- const changedFiles = gitChangedFiles(ctx.cwd);
35446
+ const { changedFiles, workspaceWarnings } = gitWorkspace(ctx.cwd);
35370
35447
  appendIteration(ctx.cwd, ctx.loopId, {
35371
35448
  implementSummary: capSummary(result.outputs.implement ?? ""),
35372
35449
  changedFiles,
35450
+ workspaceWarnings,
35373
35451
  checksRun: review.checksRun,
35374
35452
  verdict: review.verdict,
35375
35453
  findingCount: review.findingCount,
35376
35454
  decision: review.verdict,
35377
35455
  checkDurationMs: reviewDurationMs(result.trace),
35378
35456
  ...usage && { usage },
35379
- ...result.auditRunId && { detailsRef: `audit:${result.auditRunId}` }
35457
+ ...result.auditRunId && { auditRef: result.auditRunId }
35380
35458
  });
35381
35459
  const stopStatus = review.verdict === "proceed" ? "converged" : review.verdict === "block" ? "blocked" : undefined;
35382
35460
  return {
@@ -35386,6 +35464,7 @@ async function runConvergeIteration(ctx) {
35386
35464
  checksRun: review.checksRun,
35387
35465
  decision: review.verdict,
35388
35466
  changedFiles,
35467
+ workspaceWarnings,
35389
35468
  ...usage && { usage },
35390
35469
  ...result.auditRunId && { auditRunId: result.auditRunId },
35391
35470
  reviewText,
@@ -35512,8 +35591,8 @@ var CONVERGE_HELP = `chit converge --task <text> --scope <id> [options]
35512
35591
  adapter cannot enforce (emits a warning each run).
35513
35592
  Default off: such a manifest is refused before running.
35514
35593
 
35515
- Runs the implement/check loop to convergence and records it under
35516
- .chit/loops/<loopId>.jsonl. Stops at the reviewer's verdict: proceed ->
35594
+ Runs the implement/check loop to convergence and records it under chit's
35595
+ state dir (keyed by repo, not in the worktree). Stops at the reviewer's verdict: proceed ->
35517
35596
  converged, block -> blocked, else revise and retry up to the budget. An
35518
35597
  unparseable verdict is treated as block (never an implicit proceed).
35519
35598
  `;
@@ -35764,6 +35843,7 @@ async function runNextIteration(session, signal) {
35764
35843
  findingCount: iter.findingCount,
35765
35844
  checksRun: iter.checksRun,
35766
35845
  changedFiles: iter.changedFiles,
35846
+ workspaceWarnings: iter.workspaceWarnings,
35767
35847
  ...iter.usage !== undefined && { usage: iter.usage },
35768
35848
  ...iter.auditRunId !== undefined && { auditRunId: iter.auditRunId },
35769
35849
  ...session.terminalStatus !== undefined && { stopStatus: session.terminalStatus }
@@ -35878,7 +35958,7 @@ function startRun(runId, opts) {
35878
35958
  }
35879
35959
  const needsScope = Object.values(manifest.participants).some((p) => p.session === "per_scope");
35880
35960
  if (needsScope && opts.scope === undefined) {
35881
- throw new RuntimeError(`manifest "${manifest.id}" has per_scope participant(s); a scope is required (pass scope to chit_start)`);
35961
+ throw new RuntimeError(`manifest "${manifest.id}" has per_scope participant(s); a scope is required (pass scope to chit_run_start)`);
35882
35962
  }
35883
35963
  const baseAdapters = {};
35884
35964
  for (const p of Object.values(manifest.participants)) {
@@ -36179,7 +36259,7 @@ function describeRun(run) {
36179
36259
  audit: run.recorder && run.recorder.lastError === undefined ? { runId: run.runId } : undefined
36180
36260
  };
36181
36261
  }
36182
- server.registerTool("chit_start", {
36262
+ server.registerTool("chit_run_start", {
36183
36263
  description: "Start a stepwise run of a chit manifest. Returns a run_id and the steps ready to run. chit owns the declared order; only ready steps can be run. Then call chit_run_step for each ready step.",
36184
36264
  inputSchema: {
36185
36265
  manifest_path: exports_external.string().describe("Path to the manifest .json (absolute, or relative to cwd)"),
@@ -36215,7 +36295,7 @@ server.registerTool("chit_start", {
36215
36295
  runs.add(run, Date.now());
36216
36296
  return jsonResult(describeRun(run));
36217
36297
  });
36218
- server.registerTool("chit_next", {
36298
+ server.registerTool("chit_run_next", {
36219
36299
  description: "List the steps ready to run next for a run, or report that the run is complete.",
36220
36300
  inputSchema: { run_id: exports_external.string() }
36221
36301
  }, async ({ run_id }) => {
@@ -36274,7 +36354,7 @@ server.registerTool("chit_run_step", {
36274
36354
  runs.touch(run_id, Date.now());
36275
36355
  }
36276
36356
  });
36277
- server.registerTool("chit_cancel", {
36357
+ server.registerTool("chit_run_cancel", {
36278
36358
  description: "Cancel a step that is currently running: aborts its controller, which kills the agent's child process and settles the step as cancelled (terminal, blocks dependents). Returns cancelled:true if it stopped a running step, or a reason (already_done | not_running) otherwise. Use after interrupting a long step.",
36279
36359
  inputSchema: { run_id: exports_external.string(), step_id: exports_external.string() }
36280
36360
  }, async ({ run_id, step_id }) => {
@@ -36291,7 +36371,7 @@ server.registerTool("chit_cancel", {
36291
36371
  ...describeRun(run)
36292
36372
  });
36293
36373
  });
36294
- server.registerTool("chit_trace", {
36374
+ server.registerTool("chit_run_trace", {
36295
36375
  description: "Return the transcript of a run so far: each step's status, participant, agent, elapsed, and output.",
36296
36376
  inputSchema: { run_id: exports_external.string() }
36297
36377
  }, async ({ run_id }) => {
@@ -36349,7 +36429,7 @@ Pass allow_unenforced_permissions=true to run anyway.`
36349
36429
  return { ok: true, execute: buildExecute(manifest, registry3, scope, cwd), warnings };
36350
36430
  }
36351
36431
  server.registerTool("chit_converge_start", {
36352
- description: "Start an autonomous converge loop (a write-capable implementer slices the task, a read-only reviewer checks the diff) driven one iteration at a time. Returns a loop_id and the next action. Then call chit_converge_next per iteration. Records to .chit/loops/<loop_id>.jsonl, identical to `chit converge`.",
36432
+ description: "Start an autonomous converge loop (a write-capable implementer slices the task, a read-only reviewer checks the diff) driven one iteration at a time. Returns a loop_id and the next action. Then call chit_converge_next per iteration. Records the loop under chit's state dir (keyed by repo), identical to `chit converge`.",
36353
36433
  inputSchema: {
36354
36434
  task: exports_external.string().describe("The slice to converge on"),
36355
36435
  scope: exports_external.string().describe("Session scope id; both agents keep their thread across iterations"),
@@ -36454,6 +36534,7 @@ server.registerTool("chit_converge_next", {
36454
36534
  findingCount: result.findingCount,
36455
36535
  checksRun: result.checksRun,
36456
36536
  changedFiles: result.changedFiles,
36537
+ workspaceWarnings: result.workspaceWarnings,
36457
36538
  ...result.usage && { usage: result.usage },
36458
36539
  ...result.auditRunId && { auditRunId: result.auditRunId },
36459
36540
  ...result.stopStatus && { stopStatus: result.stopStatus },
@@ -36520,7 +36601,7 @@ server.registerTool("chit_audit_show", {
36520
36601
  }
36521
36602
  });
36522
36603
  server.registerTool("chit_status", {
36523
- description: "Operator overview: the stepwise runs and converge loops live in THIS server right now (each loop with its status and next action), plus a compact list of recently audited runs (newest first). Read-only; answers 'what is active and what should I do next?'. Active state is per-session (a new session starts empty, and idle runs are evicted); recent state is durable. Drill into one item with chit_converge_status/chit_trace, or chit_audit_show for a run's receipt.",
36604
+ description: "Operator overview: the stepwise runs and converge loops live in THIS server right now (each loop with its status and next action), plus a compact list of recently audited runs (newest first). Read-only; answers 'what is active and what should I do next?'. Active state is per-session (a new session starts empty, and idle runs are evicted); recent state is durable. Drill into one item with chit_converge_status/chit_run_trace, or chit_audit_show for a run's receipt.",
36524
36605
  inputSchema: {
36525
36606
  recent_limit: exports_external.number().int().min(0).default(5).describe("How many recently audited runs to include (newest first). Default 5; 0 for none.")
36526
36607
  }
@@ -36757,7 +36838,7 @@ ${AUDIT_HELP}`);
36757
36838
  // src/cli/doctor.ts
36758
36839
  import { randomUUID as randomUUID3 } from "crypto";
36759
36840
  import { mkdirSync as mkdirSync5, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
36760
- import { join as join7 } from "path";
36841
+ import { join as join8 } from "path";
36761
36842
  var defaultIO3 = {
36762
36843
  out: (s) => process.stdout.write(s),
36763
36844
  err: (s) => process.stderr.write(s)
@@ -36826,7 +36907,7 @@ function checkGitRepo(deps) {
36826
36907
  function checkAuditDir(deps) {
36827
36908
  try {
36828
36909
  mkdirSync5(deps.auditDir, { recursive: true });
36829
- const probeFile = join7(deps.auditDir, `.doctor-${randomUUID3()}`);
36910
+ const probeFile = join8(deps.auditDir, `.doctor-${randomUUID3()}`);
36830
36911
  writeFileSync5(probeFile, "ok");
36831
36912
  rmSync5(probeFile);
36832
36913
  return { name: "audit dir", status: "pass", detail: `writable (${deps.auditDir})` };
@@ -36959,12 +37040,13 @@ var ALLOWED = {
36959
37040
  "loop-id",
36960
37041
  "summary",
36961
37042
  "changed-files",
37043
+ "workspace-warnings",
36962
37044
  "checks-run",
36963
37045
  "verdict",
36964
37046
  "finding-count",
36965
37047
  "decision",
36966
37048
  "duration-ms",
36967
- "details-ref"
37049
+ "audit-ref"
36968
37050
  ],
36969
37051
  bools: []
36970
37052
  },
@@ -36976,7 +37058,8 @@ var LOOP_LOG_HELP = `chit loop-log <start|append|stop|show> [flags]
36976
37058
  start --scope <s> --task <t> --max-iterations <n> [--cwd <dir>] [--loop-id <id>] [--force]
36977
37059
  append --loop-id <id> --summary <t> --changed-files <json> --checks-run <t>
36978
37060
  --verdict <proceed|revise|block> --finding-count <n>
36979
- --decision <proceed|revise|block> --duration-ms <n> [--details-ref <r>] [--cwd <dir>]
37061
+ --decision <proceed|revise|block> --duration-ms <n>
37062
+ [--workspace-warnings <json>] [--audit-ref <r>] [--cwd <dir>]
36980
37063
  stop --loop-id <id> --status <converged|blocked|max-iterations|needs-decision> --reason <t> [--cwd <dir>]
36981
37064
  show --loop-id <id> [--json] [--cwd <dir>]
36982
37065
 
@@ -37026,15 +37109,15 @@ function intFlag(p, key, verb) {
37026
37109
  }
37027
37110
  return n;
37028
37111
  }
37029
- function parseChangedFiles(raw) {
37112
+ function parseStringArray(raw, flag) {
37030
37113
  let parsed;
37031
37114
  try {
37032
37115
  parsed = JSON.parse(raw);
37033
37116
  } catch {
37034
- throw new UsageError3("--changed-files must be a JSON array of strings");
37117
+ throw new UsageError3(`--${flag} must be a JSON array of strings`);
37035
37118
  }
37036
37119
  if (!Array.isArray(parsed) || parsed.some((e) => typeof e !== "string")) {
37037
- throw new UsageError3("--changed-files must be a JSON array of strings");
37120
+ throw new UsageError3(`--${flag} must be a JSON array of strings`);
37038
37121
  }
37039
37122
  return parsed;
37040
37123
  }
@@ -37053,6 +37136,11 @@ function renderLoop(records) {
37053
37136
  lines.push(` ${r.n}. ${r.implementSummary}`);
37054
37137
  lines.push(` ${r.changedFiles.length} files \xB7 check ${r.verdict.toUpperCase()} \xB7 ` + `${Math.round(r.checkDurationMs / 1000)}s \xB7 ${r.findingCount} findings`);
37055
37138
  lines.push(` decide ${r.decision}`);
37139
+ if (r.workspaceWarnings && r.workspaceWarnings.length > 0) {
37140
+ lines.push(` workspace: ${r.workspaceWarnings.length} warning(s)`);
37141
+ for (const w of r.workspaceWarnings)
37142
+ lines.push(` - ${w}`);
37143
+ }
37056
37144
  } else if (r.type === "stop") {
37057
37145
  lines.push("");
37058
37146
  lines.push(` stopped: ${r.status} (${r.reason}) \xB7 ${r.iterations} iters \xB7 ` + `${Math.round(r.totalElapsedMs / 1000)}s`);
@@ -37084,15 +37172,19 @@ function runLoopLog(argv, io = defaultIO4) {
37084
37172
  return 0;
37085
37173
  }
37086
37174
  if (verb === "append") {
37175
+ const warningsRaw = p.flags["workspace-warnings"];
37087
37176
  const res = appendIteration(cwd, req(p, "loop-id", verb), {
37088
37177
  implementSummary: req(p, "summary", verb),
37089
- changedFiles: parseChangedFiles(req(p, "changed-files", verb)),
37178
+ changedFiles: parseStringArray(req(p, "changed-files", verb), "changed-files"),
37179
+ ...warningsRaw !== undefined && {
37180
+ workspaceWarnings: parseStringArray(warningsRaw, "workspace-warnings")
37181
+ },
37090
37182
  checksRun: req(p, "checks-run", verb),
37091
37183
  verdict: req(p, "verdict", verb),
37092
37184
  findingCount: intFlag(p, "finding-count", verb),
37093
37185
  decision: req(p, "decision", verb),
37094
37186
  checkDurationMs: intFlag(p, "duration-ms", verb),
37095
- detailsRef: p.flags["details-ref"]
37187
+ auditRef: p.flags["audit-ref"]
37096
37188
  });
37097
37189
  io.out(`${JSON.stringify(res)}
37098
37190
  `);
@@ -37690,7 +37782,7 @@ ${HELP}`);
37690
37782
  `);
37691
37783
  return 2;
37692
37784
  }
37693
- const outputDir = args.outputDir ?? join12(homedir6(), ".claude", "skills");
37785
+ const outputDir = args.outputDir ?? join13(homedir7(), ".claude", "skills");
37694
37786
  const runtimePath = args.runtimePath ?? defaultRuntimePath();
37695
37787
  try {
37696
37788
  const result = installClaudeSkill({
@@ -37857,7 +37949,8 @@ async function runStudio(args) {
37857
37949
  cwd: process.cwd(),
37858
37950
  explicitPath: args.manifestPath,
37859
37951
  registry: registry3,
37860
- lifecycle: buildStudioLifecycle()
37952
+ lifecycle: buildStudioLifecycle(),
37953
+ loopsDir: loopLogDir(process.cwd())
37861
37954
  });
37862
37955
  } catch (e) {
37863
37956
  if (e instanceof PathError2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chit-run/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Versioned, cross-vendor agent routines with an audit trail. Stop being the glue between your agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",