@polycode-projects/seonix 0.10.6 → 0.10.9

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/bin/cli.mjs +68 -3
  2. package/package.json +2 -2
package/bin/cli.mjs CHANGED
@@ -22,6 +22,10 @@
22
22
  // seonix cli index_repository '{"multi_root":"<abs-dir>"}' → estate discovery: every immediate
23
23
  // child directory carrying a .git (dir or file) is indexed as a repo, merged into
24
24
  // <multi_root>/.seonix/ (discovered/skipped counts logged to stderr)
25
+ // seonix cli index_repository --multi-root [path] → same, flag form — no JSON
26
+ // quoting needed; a bare `--multi-root` (no path) defaults to cwd, e.g. `cd <estate> &&
27
+ // npx seonix cli index_repository --multi-root`. `--repo-path [path]` and `--out-root
28
+ // <path>` (always needs a value) are the flag forms of the other two path args.
25
29
  // seonix cli sync '{"multi_root":"<abs-dir>"}' → incrementally re-index the estate: fingerprint
26
30
  // every child repo (git/gitparent/plain), re-extract ONLY the changed ones (per-repo cache in
27
31
  // .seonix/cache/), one union rebuild = exact full-re-index. Unchanged-everything = byte-stable
@@ -54,7 +58,7 @@
54
58
 
55
59
  import { dirname, join } from "node:path";
56
60
  import { existsSync } from "node:fs";
57
- import { readFile, writeFile } from "node:fs/promises";
61
+ import { readFile, writeFile, readdir } from "node:fs/promises";
58
62
  import { fileURLToPath } from "node:url";
59
63
  import { startServer } from "../src/server.mjs";
60
64
  import { dispatchTool, buildContextBundle, TOOLS } from "../src/server.mjs";
@@ -80,6 +84,32 @@ function parsePayload(payload) {
80
84
  catch { return null; }
81
85
  }
82
86
 
87
+ const INDEX_FLAG_KEYS = { "--multi-root": "multi_root", "--repo-path": "repo_path", "--out-root": "out_root" };
88
+
89
+ /** Flag-style alternative to index_repository's JSON payload — `--multi-root [path]` /
90
+ * `--repo-path [path]` (also `--flag=path`), so `npx seonix cli index_repository
91
+ * --multi-root` works without JSON-quoting a path. A bare `--multi-root`/`--repo-path`
92
+ * (no path following, or the payload ends there) defaults to cwd, matching the JSON form's
93
+ * own cwd-default convention. `--out-root` always needs an explicit value — there's no
94
+ * sensible cwd default for it. Returns an args object shaped like the JSON payload, or
95
+ * null if the first token isn't a recognised flag (falls back to JSON parsing). */
96
+ function parseIndexFlags(argv) {
97
+ const args = {};
98
+ for (let i = 0; i < argv.length; i++) {
99
+ const tok = argv[i];
100
+ const eq = tok.indexOf("=");
101
+ const flag = eq === -1 ? tok : tok.slice(0, eq);
102
+ const key = INDEX_FLAG_KEYS[flag];
103
+ if (!key) return null;
104
+ if (eq !== -1) { args[key] = tok.slice(eq + 1); continue; }
105
+ const next = argv[i + 1];
106
+ if (next !== undefined && !next.startsWith("--")) { args[key] = next; i++; }
107
+ else if (key === "multi_root" || key === "repo_path") { args[key] = process.cwd(); }
108
+ else return null;
109
+ }
110
+ return Object.keys(args).length ? args : null;
111
+ }
112
+
83
113
  /** Nearest ancestor of `startDir` (itself first) containing a .seonix/ directory, or null. */
84
114
  function findIndexRoot(startDir = process.cwd()) {
85
115
  let dir = startDir;
@@ -113,6 +143,36 @@ function defaultRepoPath(args, { mustExist = true } = {}) {
113
143
  return args;
114
144
  }
115
145
 
146
+ /** Single-mode index_repository against a directory that (a) isn't itself a git repo and
147
+ * (b) has ≥2 immediate child directories that ARE — the exact signature of an estate root
148
+ * mistaken for one flat repo (the `multi_root` docs' own warning: "repo_path would index it
149
+ * as one flat repo"). Silent consequence: git history extraction fails against the non-repo
150
+ * root (0 commits, no error the caller necessarily notices among the rest of the index
151
+ * output), module ids aren't per-repo-prefixed, and every sub-repo gets flattened together.
152
+ * Best-effort (readdir failure just means no warning, never a crash) — this only ADDS a
153
+ * warning in a case that previously silently mis-indexed, so it can't change any existing
154
+ * single-repo index's output. */
155
+ async function warnIfLooksLikeEstateRoot(repoPath) {
156
+ if (existsSync(join(repoPath, ".git"))) return;
157
+ let entries;
158
+ try { entries = await readdir(repoPath, { withFileTypes: true }); }
159
+ catch { return; }
160
+ let childRepoCount = 0;
161
+ for (const e of entries) {
162
+ if (e.isDirectory() && existsSync(join(repoPath, e.name, ".git"))) childRepoCount++;
163
+ if (childRepoCount >= 2) break;
164
+ }
165
+ if (childRepoCount >= 2) {
166
+ process.stderr.write(
167
+ `seonix: WARNING — ${repoPath} has no .git of its own, but multiple child directories do. ` +
168
+ "This looks like a multi-repo estate root, not a single repo — indexing it this way flattens " +
169
+ "every sub-repo together with NO git history (git log fails against a non-repo root) and no " +
170
+ "per-repo module-id prefixing. If that's not what you want, re-run with " +
171
+ `'{"multi_root":"${repoPath}"}' instead, which discovers each child repo and indexes it properly.\n`,
172
+ );
173
+ }
174
+ }
175
+
116
176
  // Hot tools (server.mjs's TOOLS) carry an inputSchema but no example value, so their
117
177
  // example-args + primary-arg-key aren't derivable the way COLD_TOOLS' are — hand-maintained
118
178
  // here, right next to COLD_TOOLS' import, so a new hot tool is a one-line addition in both places.
@@ -482,9 +542,13 @@ async function main() {
482
542
  // '{"repo_paths":[…],"out_root":"<abs>"}' (explicit n-repo merge) or
483
543
  // '{"multi_root":"<abs>"}' (discover child repos, merge into <multi_root>/.seonix/).
484
544
  if (sub === "index_repository") {
485
- const args = parsePayload(payload);
545
+ const isFlagForm = payload !== undefined && payload.startsWith("--");
546
+ const args = isFlagForm ? parseIndexFlags(process.argv.slice(4)) : parsePayload(payload);
486
547
  if (args === null) {
487
- process.stderr.write("seonix: index_repository expects a JSON arg, e.g. '{\"repo_path\":\"/abs\"}'\n");
548
+ process.stderr.write(
549
+ "seonix: index_repository expects a JSON arg, e.g. '{\"repo_path\":\"/abs\"}', " +
550
+ "or a flag: --multi-root [path], --repo-path [path], --out-root <path>\n",
551
+ );
488
552
  process.exit(2);
489
553
  }
490
554
  const given = ["repo_path", "repo_paths", "multi_root"].filter((k) => args[k] !== undefined);
@@ -557,6 +621,7 @@ async function main() {
557
621
  };
558
622
 
559
623
  if (args.repo_path) {
624
+ await warnIfLooksLikeEstateRoot(args.repo_path);
560
625
  const { graphFile, counts, summary } = await indexRepository(args.repo_path, { ignores: args.ignores !== false, historyDepth, extraIgnores, languages: tomlLanguages, interfaces: tomlInterfaces });
561
626
  emitSummary(graphFile, counts, "");
562
627
  // A1: the human summary MD goes to STDOUT (bench ignores index stdout — safe;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/seonix",
3
- "version": "0.10.6",
3
+ "version": "0.10.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SEONIX — a deterministic, offline, $0 code-graph tool that makes a cheap coding agent edit like an expensive one. Indexes a repo with Python ast + git (zero model calls) and renders a bounded edit-digest.",
@@ -87,7 +87,7 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@modelcontextprotocol/sdk": "^1.29.0",
90
- "@polycode-projects/the-mechanical-code-talker": "^0.9.4",
90
+ "@polycode-projects/the-mechanical-code-talker": "^0.9.8",
91
91
  "cytoscape": "^3.30.0",
92
92
  "smol-toml": "^1.7.0",
93
93
  "tree-sitter": "^0.21.1",