@abdulmunimjemal/codescope 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -36,12 +36,12 @@ incumbent and shares codescope's architecture. In a **measured head-to-head**
36
36
  - **~4× faster indexing** (696 ms vs 2,855 ms on a 262-file repo; 5.2 s vs 20 s on 3,500 files).
37
37
  - **3–5× smaller index** on disk (2.5 MB vs 8.2 MB; 22.9 MB vs 112.8 MB).
38
38
  - **Fewer tokens per answer** for definition lookups on every repo tested; callers is ≈parity.
39
- - Feature parity on the core graph queries: `callers`, `callees`, `impact`, `context`.
39
+ - **Feature parity:** `callers`, `callees`, `impact`, `context`, **`affected`** (test-impact), and **`install`** (agent auto-wiring) — plus **20 languages**.
40
40
 
41
- codegraph still leads on **language breadth** (20+ vs 12), **extra tooling**
42
- (`affected` test-impact, agent auto-install), and **maturity/adoption**. Pick
43
- codescope when footprint, index speed, and token cost matter most; pick codegraph
44
- when you need the widest language coverage and the extra tooling.
41
+ codegraph still leads on **maturity & adoption** (35k★, a real user base) and
42
+ indexes a few extra node kinds (constants, properties, routes). Pick codescope
43
+ when footprint, index speed, and token cost matter; pick codegraph for the most
44
+ battle-tested option.
45
45
 
46
46
  ## Install
47
47
 
@@ -55,11 +55,20 @@ collides with an existing package; the installed command is still `codescope`.)
55
55
 
56
56
  ## Quick start
57
57
 
58
- Point your agent at codescope as an MCP server. It indexes the repo, starts
59
- watching for changes, and serves the graph over stdio:
58
+ The one-liner wire codescope into your agents automatically, from your repo:
60
59
 
61
60
  ```bash
62
- codescope mcp /path/to/your/repo
61
+ npx @abdulmunimjemal/codescope install # adds codescope to Claude Code + Cursor
62
+ ```
63
+
64
+ That writes the MCP server config (non-destructively) so your agent launches
65
+ codescope on the repo. Restart the agent and you're done. `--agent claude|cursor`
66
+ targets one; `--global` writes to your home dir instead of the project.
67
+
68
+ Prefer to wire it by hand? The server command is:
69
+
70
+ ```bash
71
+ codescope mcp /path/to/your/repo # index, watch, and serve over stdio
63
72
  ```
64
73
 
65
74
  **Claude Code** (`.mcp.json` or `claude mcp add`):
@@ -82,6 +91,10 @@ codescope index . # build the graph, print stats
82
91
  codescope search useState # fuzzy symbol search
83
92
  codescope get GraphStore # jump to a definition
84
93
  codescope callers parseSource # who calls this
94
+ codescope callees indexAll # what it calls
95
+ codescope impact GraphStore # blast radius before a change
96
+ codescope context "auth flow" # ranked relevance map for a task
97
+ codescope affected src/store.ts # which tests are affected by a change
85
98
  codescope neighborhood handleRequest --depth 3
86
99
  codescope watch . # keep the graph fresh, log updates
87
100
  ```
@@ -127,7 +140,10 @@ The index lives in `.codescope/graph.db` (add `.codescope/` to your
127
140
 
128
141
  ## Languages
129
142
 
130
- TypeScript, JavaScript, TSX/JSX, Python, Go, Rust, Java, Ruby, C, C++, C#, PHP.
143
+ 20 languages: TypeScript, JavaScript, TSX/JSX, Python, Go, Rust, Java, Ruby, C,
144
+ C++, C#, PHP, Scala, Solidity, Zig, Kotlin, Objective-C, Lua, Bash, and OCaml.
145
+ Definition extraction (functions, classes, methods, …) works for all; call and
146
+ import edges are available for the languages whose grammars expose them.
131
147
 
132
148
  ## Programmatic API
133
149
 
package/dist/cli.js CHANGED
@@ -1,10 +1,62 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { mkdirSync } from "fs";
4
+ import { mkdirSync as mkdirSync2 } from "fs";
5
5
  import { resolve as resolve2 } from "path";
6
6
  import pc from "picocolors";
7
7
 
8
+ // src/affected.ts
9
+ var TEST_PATTERNS = [
10
+ /(^|\/)(tests?|spec|specs|__tests__|e2e|integration)\//i,
11
+ /\.(test|spec)\.[a-z]+$/i,
12
+ // foo.test.ts, foo.spec.js
13
+ /_test\.[a-z]+$/i,
14
+ // foo_test.go, foo_test.py, foo_test.rs
15
+ /(^|\/)test_[^/]+\.(py|rb)$/i,
16
+ // test_foo.py
17
+ /(Test|Tests|Spec)\.[a-z]+$/i,
18
+ // FooTest.java, FooTests.cs, FooSpec.scala
19
+ /_spec\.rb$/i
20
+ // foo_spec.rb
21
+ ];
22
+ function isTestFile(path) {
23
+ return TEST_PATTERNS.some((re) => re.test(path));
24
+ }
25
+ function moduleBasename(path) {
26
+ const base = path.slice(path.lastIndexOf("/") + 1);
27
+ const dot = base.indexOf(".");
28
+ return dot === -1 ? base : base.slice(0, dot);
29
+ }
30
+ function affected(store, changedPaths, opts = {}) {
31
+ const depth = opts.depth ?? 4;
32
+ const impactedFiles = new Set(changedPaths);
33
+ for (const path of changedPaths) {
34
+ for (const sym of store.fileOutline(path)) {
35
+ for (const caller of store.impact(sym.name, { depth })) {
36
+ impactedFiles.add(caller.file);
37
+ }
38
+ }
39
+ }
40
+ let frontier = [...changedPaths];
41
+ for (let d = 0; d < depth && frontier.length > 0; d++) {
42
+ const next = [];
43
+ for (const path of frontier) {
44
+ for (const importer of store.findImporters(moduleBasename(path))) {
45
+ if (!impactedFiles.has(importer)) {
46
+ impactedFiles.add(importer);
47
+ next.push(importer);
48
+ }
49
+ }
50
+ }
51
+ frontier = next;
52
+ }
53
+ return {
54
+ changed: changedPaths,
55
+ impactedFiles: [...impactedFiles].sort(),
56
+ tests: [...impactedFiles].filter(isTestFile).sort()
57
+ };
58
+ }
59
+
8
60
  // src/format.ts
9
61
  function shortSig(sig) {
10
62
  if (!sig) return "";
@@ -60,6 +112,14 @@ function formatContext(b) {
60
112
  }
61
113
  return lines.join("\n");
62
114
  }
115
+ function formatAffected(r) {
116
+ if (r.tests.length === 0) {
117
+ return `No test files appear affected by ${r.changed.length} changed file(s).`;
118
+ }
119
+ const lines = [`${r.tests.length} test file(s) affected by your changes:`, ""];
120
+ for (const t of r.tests) lines.push(` ${t}`);
121
+ return lines.join("\n");
122
+ }
63
123
  function formatStats(s) {
64
124
  const kinds = Object.entries(s.byKind).sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k}=${n}`).join(" ");
65
125
  const langs = Object.entries(s.byLang).sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k}=${n}`).join(" ");
@@ -290,6 +350,118 @@ var php = {
290
350
  importRules: [{ type: "namespace_use_declaration", childTypes: ["namespace_use_clause"] }],
291
351
  exportTypes: /* @__PURE__ */ new Set()
292
352
  };
353
+ var scala = {
354
+ id: "scala",
355
+ wasm: "scala",
356
+ defs: {
357
+ class_definition: { kind: "class" },
358
+ object_definition: { kind: "class" },
359
+ trait_definition: { kind: "interface" },
360
+ function_definition: { kind: "method" },
361
+ type_definition: { kind: "type" }
362
+ },
363
+ functionBindings: /* @__PURE__ */ new Set(),
364
+ nestedFunctionsAreMethods: false,
365
+ callRules: [
366
+ { type: "call_expression", fnField: "function", memberTypes: ["field_expression"], memberField: "field" }
367
+ ],
368
+ importRules: [{ type: "import_declaration", childTypes: ["stable_identifier", "identifier"] }],
369
+ exportTypes: /* @__PURE__ */ new Set()
370
+ };
371
+ var solidity = {
372
+ id: "solidity",
373
+ wasm: "solidity",
374
+ defs: {
375
+ contract_declaration: { kind: "class" },
376
+ interface_declaration: { kind: "interface" },
377
+ library_declaration: { kind: "class" },
378
+ function_definition: { kind: "method" },
379
+ modifier_definition: { kind: "function" },
380
+ struct_declaration: { kind: "class" },
381
+ enum_declaration: { kind: "enum" }
382
+ },
383
+ functionBindings: /* @__PURE__ */ new Set(),
384
+ nestedFunctionsAreMethods: false,
385
+ callRules: [{ type: "call_expression", fnField: "function" }],
386
+ importRules: [{ type: "import_directive", field: "source" }],
387
+ exportTypes: /* @__PURE__ */ new Set()
388
+ };
389
+ var zig = {
390
+ id: "zig",
391
+ wasm: "zig",
392
+ defs: { function_declaration: { kind: "function" } },
393
+ functionBindings: /* @__PURE__ */ new Set(),
394
+ nestedFunctionsAreMethods: false,
395
+ callRules: [{ type: "call_expression", fnField: "function" }],
396
+ importRules: [],
397
+ exportTypes: /* @__PURE__ */ new Set()
398
+ };
399
+ var kotlin = {
400
+ id: "kotlin",
401
+ wasm: "kotlin",
402
+ defs: {
403
+ class_declaration: { kind: "class", name: "first_typed", nameTypes: ["type_identifier"] },
404
+ object_declaration: { kind: "class", name: "first_typed", nameTypes: ["type_identifier"] },
405
+ function_declaration: { kind: "function", name: "first_typed", nameTypes: ["simple_identifier"] }
406
+ },
407
+ functionBindings: /* @__PURE__ */ new Set(),
408
+ nestedFunctionsAreMethods: true,
409
+ callRules: [],
410
+ importRules: [{ type: "import_header", childTypes: ["identifier"] }],
411
+ exportTypes: /* @__PURE__ */ new Set()
412
+ };
413
+ var objc = {
414
+ id: "objc",
415
+ wasm: "objc",
416
+ defs: {
417
+ class_interface: { kind: "class", name: "first_typed", nameTypes: ["identifier"] },
418
+ class_implementation: { kind: "class", name: "first_typed", nameTypes: ["identifier"] },
419
+ method_declaration: { kind: "method", name: "first_typed", nameTypes: ["identifier"] },
420
+ method_definition: { kind: "method", name: "first_typed", nameTypes: ["identifier"] }
421
+ },
422
+ functionBindings: /* @__PURE__ */ new Set(),
423
+ nestedFunctionsAreMethods: false,
424
+ callRules: [{ type: "call_expression", fnField: "function" }],
425
+ importRules: [{ type: "preproc_include", field: "path" }],
426
+ exportTypes: /* @__PURE__ */ new Set()
427
+ };
428
+ var lua = {
429
+ id: "lua",
430
+ wasm: "lua",
431
+ defs: {
432
+ function_definition_statement: { kind: "function" },
433
+ local_function_definition_statement: { kind: "function" }
434
+ },
435
+ functionBindings: /* @__PURE__ */ new Set(),
436
+ nestedFunctionsAreMethods: false,
437
+ callRules: [],
438
+ importRules: [],
439
+ exportTypes: /* @__PURE__ */ new Set()
440
+ };
441
+ var bash = {
442
+ id: "bash",
443
+ wasm: "bash",
444
+ defs: { function_definition: { kind: "function" } },
445
+ functionBindings: /* @__PURE__ */ new Set(),
446
+ nestedFunctionsAreMethods: false,
447
+ callRules: [],
448
+ importRules: [],
449
+ exportTypes: /* @__PURE__ */ new Set()
450
+ };
451
+ var ocaml = {
452
+ id: "ocaml",
453
+ wasm: "ocaml",
454
+ defs: {
455
+ let_binding: { kind: "function", name: "field", nameField: "pattern" },
456
+ module_definition: { kind: "class" },
457
+ type_definition: { kind: "type" }
458
+ },
459
+ functionBindings: /* @__PURE__ */ new Set(),
460
+ nestedFunctionsAreMethods: false,
461
+ callRules: [],
462
+ importRules: [],
463
+ exportTypes: /* @__PURE__ */ new Set()
464
+ };
293
465
  var LANGUAGES = {
294
466
  typescript,
295
467
  tsx,
@@ -302,7 +474,15 @@ var LANGUAGES = {
302
474
  c,
303
475
  cpp,
304
476
  csharp,
305
- php
477
+ php,
478
+ scala,
479
+ solidity,
480
+ zig,
481
+ kotlin,
482
+ objc,
483
+ lua,
484
+ bash,
485
+ ocaml
306
486
  };
307
487
  var EXT_TO_LANG = {
308
488
  ".ts": "typescript",
@@ -327,7 +507,19 @@ var EXT_TO_LANG = {
327
507
  ".hpp": "cpp",
328
508
  ".hh": "cpp",
329
509
  ".cs": "csharp",
330
- ".php": "php"
510
+ ".php": "php",
511
+ ".scala": "scala",
512
+ ".sc": "scala",
513
+ ".sol": "solidity",
514
+ ".zig": "zig",
515
+ ".kt": "kotlin",
516
+ ".kts": "kotlin",
517
+ ".m": "objc",
518
+ ".lua": "lua",
519
+ ".sh": "bash",
520
+ ".bash": "bash",
521
+ ".ml": "ocaml",
522
+ ".mli": "ocaml"
331
523
  };
332
524
  var SUPPORTED_EXTENSIONS = Object.keys(EXT_TO_LANG);
333
525
  function languageForPath(path) {
@@ -413,7 +605,7 @@ function classify(node, lang) {
413
605
  return null;
414
606
  }
415
607
  function buildSymbol(node, rule, container, containerKind, lang) {
416
- const name = symbolName(node, rule.name ?? "field");
608
+ const name = symbolName(node, rule);
417
609
  if (!name) return null;
418
610
  let kind = rule.kind;
419
611
  if (kind === "function" && lang.nestedFunctionsAreMethods && containerKind === "class") {
@@ -433,9 +625,20 @@ function buildSymbol(node, rule, container, containerKind, lang) {
433
625
  endByte: node.endIndex
434
626
  };
435
627
  }
436
- function symbolName(node, strategy) {
437
- if (strategy === "c_declarator") return cDeclaratorName(node);
438
- return node.childForFieldName("name")?.text ?? null;
628
+ function symbolName(node, rule) {
629
+ switch (rule.name ?? "field") {
630
+ case "c_declarator":
631
+ return cDeclaratorName(node);
632
+ case "first_typed": {
633
+ const types = rule.nameTypes ?? [];
634
+ for (const child of node.namedChildren) {
635
+ if (types.includes(child.type)) return child.text;
636
+ }
637
+ return null;
638
+ }
639
+ default:
640
+ return node.childForFieldName(rule.nameField ?? "name")?.text ?? null;
641
+ }
439
642
  }
440
643
  function cDeclaratorName(node) {
441
644
  let decl = node.childForFieldName("declarator");
@@ -651,13 +854,65 @@ function errorMessage(err) {
651
854
  return err instanceof Error ? err.message : String(err);
652
855
  }
653
856
 
857
+ // src/install.ts
858
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
859
+ import { homedir } from "os";
860
+ import { dirname, join } from "path";
861
+ var PACKAGE = "@abdulmunimjemal/codescope";
862
+ var MCP_SERVER_NAME = "codescope";
863
+ var SUPPORTED_AGENTS = ["claude", "cursor"];
864
+ function serverEntry(serveTarget = ".") {
865
+ return { command: "npx", args: ["-y", PACKAGE, "mcp", serveTarget] };
866
+ }
867
+ function configPath(agent, root, global = false) {
868
+ switch (agent) {
869
+ case "claude":
870
+ return global ? join(homedir(), ".mcp.json") : join(root, ".mcp.json");
871
+ case "cursor":
872
+ return global ? join(homedir(), ".cursor", "mcp.json") : join(root, ".cursor", "mcp.json");
873
+ }
874
+ }
875
+ function readJson(path) {
876
+ if (!existsSync(path)) return {};
877
+ try {
878
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
879
+ return parsed && typeof parsed === "object" ? parsed : {};
880
+ } catch {
881
+ return {};
882
+ }
883
+ }
884
+ function installInto(agent, root, opts = {}) {
885
+ const path = configPath(agent, root, opts.global ?? false);
886
+ const config = readJson(path);
887
+ const existing = config.mcpServers;
888
+ const servers = existing && typeof existing === "object" ? existing : {};
889
+ const existed = Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_NAME);
890
+ servers[MCP_SERVER_NAME] = serverEntry(opts.serveTarget);
891
+ config.mcpServers = servers;
892
+ mkdirSync(dirname(path), { recursive: true });
893
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
894
+ `);
895
+ return { agent, path, action: existed ? "updated" : "added" };
896
+ }
897
+ function install(root, opts = {}) {
898
+ const agents = opts.agents ?? [...SUPPORTED_AGENTS];
899
+ return agents.map((agent) => installInto(agent, root, opts));
900
+ }
901
+ function codexSnippet(serveTarget = ".") {
902
+ return [
903
+ "[mcp_servers.codescope]",
904
+ 'command = "npx"',
905
+ `args = ["-y", "${PACKAGE}", "mcp", "${serveTarget}"]`
906
+ ].join("\n");
907
+ }
908
+
654
909
  // src/mcp.ts
655
910
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
656
911
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
657
912
  import { z } from "zod";
658
913
 
659
914
  // src/version.ts
660
- var VERSION = "0.2.0";
915
+ var VERSION = "0.3.0";
661
916
 
662
917
  // src/mcp.ts
663
918
  var KIND = z.enum(["function", "method", "class", "interface", "type", "enum", "variable"]);
@@ -777,6 +1032,18 @@ function createServer(store) {
777
1032
  },
778
1033
  async ({ name, depth, limit }) => textResult(formatNeighborhood(store.neighborhood(name, { depth, limit })))
779
1034
  );
1035
+ server.registerTool(
1036
+ "affected",
1037
+ {
1038
+ title: "Affected tests",
1039
+ description: "Given a list of changed files, return the test files likely affected \u2014 the symbols those files define, walked through their transitive callers, filtered to tests. Use before running a suite to know what's worth re-running.",
1040
+ inputSchema: {
1041
+ files: z.array(z.string()).describe("repo-relative paths of the changed files"),
1042
+ depth: z.number().int().min(1).max(6).optional()
1043
+ }
1044
+ },
1045
+ async ({ files, depth }) => textResult(formatAffected(affected(store, files, { depth })))
1046
+ );
780
1047
  server.registerTool(
781
1048
  "stats",
782
1049
  {
@@ -1097,6 +1364,18 @@ var GraphStore = class {
1097
1364
  ).all(name, limit);
1098
1365
  return rows.map(toRefRow);
1099
1366
  }
1367
+ /**
1368
+ * Files whose imports reference a module basename (e.g. `store` for
1369
+ * `src/store.ts`). Matches `import … from "./store"`, `"../src/store.js"`,
1370
+ * etc. Used by affected-test analysis to follow import edges, which — unlike
1371
+ * call edges — reliably reach test files (tests import the module under test).
1372
+ */
1373
+ findImporters(moduleBasename2) {
1374
+ return this.db.prepare(
1375
+ `SELECT DISTINCT f.path FROM refs r JOIN files f ON f.id = r.file_id
1376
+ WHERE r.kind = 'import' AND (r.name = ? OR r.name LIKE ? OR r.name LIKE ?)`
1377
+ ).all(moduleBasename2, `%/${moduleBasename2}`, `%/${moduleBasename2}.%`).map((r) => r.path);
1378
+ }
1100
1379
  /** The symbols defined in a file, in source order. */
1101
1380
  fileOutline(path) {
1102
1381
  return this.db.prepare(
@@ -1285,6 +1564,7 @@ Usage:
1285
1564
  codescope <command> [path] [options]
1286
1565
 
1287
1566
  Commands:
1567
+ install [path] Wire codescope into your agents (Claude Code, Cursor) automatically.
1288
1568
  mcp [path] Index, watch for changes, and serve the graph over MCP (stdio).
1289
1569
  This is what you wire into Claude Code / Cursor / Codex.
1290
1570
  index [path] Build (or refresh) the on-disk graph and print stats.
@@ -1296,9 +1576,12 @@ Commands:
1296
1576
  callees <name> [path] List what a function/method calls.
1297
1577
  impact <name> [path] Transitive callers (blast radius) of a symbol.
1298
1578
  context <query> [path] Ranked relevance map for a task (matches + neighbours).
1579
+ affected <files...> Test files affected by a set of changed files.
1299
1580
  neighborhood <name> Show the call neighbourhood around a symbol.
1300
1581
 
1301
1582
  Options:
1583
+ --agent <name> install target: claude | cursor | all (default all).
1584
+ --global install: write to your home dir instead of the project.
1302
1585
  --path <dir> Repository root (default: current directory or the positional path).
1303
1586
  --db <file> SQLite graph location (default: <root>/.codescope/graph.db).
1304
1587
  --memory Use an in-memory graph (not persisted).
@@ -1315,13 +1598,19 @@ Examples:
1315
1598
  codescope mcp . # add this command to your agent's MCP config
1316
1599
  `;
1317
1600
  function parseArgs(argv) {
1318
- const flags = { positional: [], memory: false };
1601
+ const flags = { positional: [], memory: false, global: false };
1319
1602
  for (let i = 0; i < argv.length; i++) {
1320
1603
  const arg = argv[i];
1321
1604
  switch (arg) {
1322
1605
  case "--memory":
1323
1606
  flags.memory = true;
1324
1607
  break;
1608
+ case "--global":
1609
+ flags.global = true;
1610
+ break;
1611
+ case "--agent":
1612
+ flags.agent = argv[++i];
1613
+ break;
1325
1614
  case "--path":
1326
1615
  flags.path = argv[++i];
1327
1616
  break;
@@ -1349,7 +1638,7 @@ function rootDir(flags, positionalRootIndex = 0) {
1349
1638
  function openStore(root, flags) {
1350
1639
  if (flags.memory) return new GraphStore(":memory:");
1351
1640
  const dir = resolve2(root, ".codescope");
1352
- mkdirSync(dir, { recursive: true });
1641
+ mkdirSync2(dir, { recursive: true });
1353
1642
  return new GraphStore(flags.db ?? resolve2(dir, "graph.db"));
1354
1643
  }
1355
1644
  async function ensureIndexed(indexer, store) {
@@ -1421,6 +1710,32 @@ async function cmdQuery(command, root, flags) {
1421
1710
  `);
1422
1711
  store.close();
1423
1712
  }
1713
+ async function cmdAffected(root, flags) {
1714
+ const files = flags.positional;
1715
+ if (files.length === 0) fail("'affected' needs one or more changed file paths. See --help.");
1716
+ const store = openStore(root, flags);
1717
+ const indexer = new Indexer(store, root);
1718
+ await ensureIndexed(indexer, store);
1719
+ const rels = files.map((f) => indexer.rel(resolve2(root, f)));
1720
+ process.stdout.write(`${formatAffected(affected(store, rels, { depth: flags.depth }))}
1721
+ `);
1722
+ store.close();
1723
+ }
1724
+ function cmdInstall(root, flags) {
1725
+ const agents = !flags.agent || flags.agent === "all" ? [...SUPPORTED_AGENTS] : SUPPORTED_AGENTS.includes(flags.agent) ? [flags.agent] : fail(`unknown --agent '${flags.agent}'. Use: ${SUPPORTED_AGENTS.join(", ")}, or all.`);
1726
+ const results = install(root, { agents, global: flags.global });
1727
+ for (const r of results) {
1728
+ process.stdout.write(`${pc.green("\u2713")} ${r.action} codescope for ${pc.bold(r.agent)} \u2192 ${r.path}
1729
+ `);
1730
+ }
1731
+ process.stdout.write(
1732
+ `
1733
+ ${pc.dim("Restart your agent to pick up the change.")}
1734
+ ${pc.dim("Codex (TOML config) \u2014 add this to ~/.codex/config.toml:")}
1735
+ ${codexSnippet()}
1736
+ `
1737
+ );
1738
+ }
1424
1739
  async function cmdWatch(root, flags) {
1425
1740
  const store = openStore(root, flags);
1426
1741
  const indexer = new Indexer(store, root);
@@ -1487,6 +1802,10 @@ async function main() {
1487
1802
  case "context":
1488
1803
  case "neighborhood":
1489
1804
  return cmdQuery(command, resolve2(flags.path ?? flags.positional[1] ?? "."), flags);
1805
+ case "affected":
1806
+ return cmdAffected(resolve2(flags.path ?? "."), flags);
1807
+ case "install":
1808
+ return cmdInstall(rootDir(flags), flags);
1490
1809
  case "watch":
1491
1810
  return cmdWatch(rootDir(flags), flags);
1492
1811
  case "mcp":