polymath-nexus 0.2.0__tar.gz

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 (32) hide show
  1. polymath_nexus-0.2.0/LICENSE +21 -0
  2. polymath_nexus-0.2.0/PKG-INFO +166 -0
  3. polymath_nexus-0.2.0/README.md +134 -0
  4. polymath_nexus-0.2.0/nexus/__init__.py +104 -0
  5. polymath_nexus-0.2.0/nexus/cli.py +223 -0
  6. polymath_nexus-0.2.0/nexus/config.py +45 -0
  7. polymath_nexus-0.2.0/nexus/discovery.py +77 -0
  8. polymath_nexus-0.2.0/nexus/index.py +689 -0
  9. polymath_nexus-0.2.0/nexus/memory/__init__.py +3 -0
  10. polymath_nexus-0.2.0/nexus/memory/chunker.py +151 -0
  11. polymath_nexus-0.2.0/nexus/memory/embedder.py +212 -0
  12. polymath_nexus-0.2.0/nexus/memory/migrate.py +128 -0
  13. polymath_nexus-0.2.0/nexus/memory/retriever.py +139 -0
  14. polymath_nexus-0.2.0/nexus/memory/store.py +779 -0
  15. polymath_nexus-0.2.0/nexus/memory/sync.py +79 -0
  16. polymath_nexus-0.2.0/nexus/memory/writer.py +295 -0
  17. polymath_nexus-0.2.0/nexus/mirrors.py +258 -0
  18. polymath_nexus-0.2.0/nexus/types.py +82 -0
  19. polymath_nexus-0.2.0/polymath_nexus.egg-info/PKG-INFO +166 -0
  20. polymath_nexus-0.2.0/polymath_nexus.egg-info/SOURCES.txt +30 -0
  21. polymath_nexus-0.2.0/polymath_nexus.egg-info/dependency_links.txt +1 -0
  22. polymath_nexus-0.2.0/polymath_nexus.egg-info/entry_points.txt +2 -0
  23. polymath_nexus-0.2.0/polymath_nexus.egg-info/requires.txt +10 -0
  24. polymath_nexus-0.2.0/polymath_nexus.egg-info/top_level.txt +1 -0
  25. polymath_nexus-0.2.0/pyproject.toml +68 -0
  26. polymath_nexus-0.2.0/setup.cfg +4 -0
  27. polymath_nexus-0.2.0/tests/test_brain_cli.py +197 -0
  28. polymath_nexus-0.2.0/tests/test_brain_decoupling.py +126 -0
  29. polymath_nexus-0.2.0/tests/test_longterm_brain.py +481 -0
  30. polymath_nexus-0.2.0/tests/test_memory_store.py +129 -0
  31. polymath_nexus-0.2.0/tests/test_mirrors.py +233 -0
  32. polymath_nexus-0.2.0/tests/test_timezone.py +94 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayushi Gupta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: polymath-nexus
3
+ Version: 0.2.0
4
+ Summary: Long-term team brain — markdown + JSONL, date-stamped, owner-attributed, append-only. Auto-generates CLAUDE.md / AGENTS.md / .cursor / Copilot mirrors so every AI tool reads it for free.
5
+ Author: Ayushi Gupta
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ayushigupta-29/nexus
8
+ Project-URL: Repository, https://github.com/ayushigupta-29/nexus
9
+ Project-URL: Format spec, https://github.com/ayushigupta-29/nexus/blob/main/SPEC.md
10
+ Keywords: ai,memory,context,team-brain,claude,cursor,codex,copilot
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: httpx>=0.27.0
24
+ Provides-Extra: embed
25
+ Requires-Dist: google-genai>=1.0.0; extra == "embed"
26
+ Requires-Dist: openai>=1.50.0; extra == "embed"
27
+ Requires-Dist: sqlite-vec>=0.1.6; extra == "embed"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # nexus — long-term team brain
34
+
35
+ > *nexus* (Greek: νοῦς) — mind, intellect, intuition.
36
+ > The faculty that sees things as they are.
37
+
38
+ `nexus` is a long-lived, team-shareable project memory. It is:
39
+
40
+ - **Just markdown files in a folder.** No DB to run, no server to host.
41
+ - **Date-stamped and owner-attributed.** Every entry carries
42
+ `[YYYY-MM-DD owner]`. Grep works forever.
43
+ - **Append-only.** Nothing is ever auto-deleted. Old engineering rules
44
+ are load-bearing, not stale.
45
+ - **Tool-agnostic.** Auto-generates `CLAUDE.md`, `AGENTS.md`,
46
+ `.cursor/rules/brain.mdc`, `.github/copilot-instructions.md` —
47
+ every AI tool you use reads the brain through its native discovery,
48
+ no extra config.
49
+ - **Forward-compatible.** Schema versioned. A `nexus` from 2046 will
50
+ still read a brain written in 2026, and vice versa.
51
+
52
+ ## Why
53
+
54
+ Teams accumulate knowledge faster than they document it. The brain is
55
+ where that knowledge goes to live for decades — surviving turnover,
56
+ tool churn, and reorgs.
57
+
58
+ When someone leaves: their `brain/rules/<them>.md` stays. Their
59
+ decisions, their patterns, their gotchas — all dated, all attributed.
60
+ The next person reads `brain/` on day one and inherits the team's
61
+ mental model.
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ pip install nexus
67
+ cd ~/my-repo
68
+ nexus init # creates brain/ + AI tool mirrors
69
+ nexus learn-from "always use httpx, never requests — async-first codebase"
70
+ ```
71
+
72
+ That's it. Open Claude Code / Cursor / Codex / Copilot in this repo —
73
+ all four now read this rule automatically via their native discovery
74
+ files.
75
+
76
+ ## Folder structure
77
+
78
+ ```
79
+ brain/
80
+ ├── README.md intro for humans
81
+ ├── rules/<owner>.md hard constraints, never break these
82
+ ├── logic/<owner>.md business/domain logic
83
+ ├── code/<owner>.md patterns, conventions, architecture
84
+ ├── stack/<owner>.md tech stack, versions
85
+ ├── data/<owner>.md schemas, field definitions
86
+ ├── goals/<owner>.md project objectives, scope
87
+ ├── decisions/<owner>.md ADR-style: chosen approach + why
88
+ ├── glossary/<owner>.md domain term definitions
89
+ └── personas/<owner>.md tone, behavior, communication style
90
+ ```
91
+
92
+ Per-owner sub-files mean two teammates writing simultaneously **never
93
+ conflict at the file level**. You can also use single-file mode
94
+ (`brain/rules.md`); the reader handles both.
95
+
96
+ ## CLI
97
+
98
+ ```bash
99
+ nexus init # create brain/ in cwd
100
+ nexus learn-from "<rule>" # append to rules/<you>.md, dated
101
+ nexus show [bucket] # grep-first search
102
+ --since 2026-01-01
103
+ --until 2026-05-31
104
+ --by ayushi
105
+ -q pattern
106
+ --json
107
+ nexus usage # counts per bucket + owner
108
+ nexus own # print your owner slug
109
+ nexus sync # regenerate AI-tool mirrors
110
+ nexus unmirror # remove mirrors (opt-out)
111
+ nexus spec # print SPEC.md
112
+ ```
113
+
114
+ ## Auto-generated mirrors
115
+
116
+ After every `nexus` write, four files at repo root are regenerated:
117
+
118
+ | Mirror | Tool | Auto-discovered |
119
+ |---|---|---|
120
+ | `CLAUDE.md` | Claude Code | ✓ |
121
+ | `AGENTS.md` | Codex CLI | ✓ |
122
+ | `.cursor/rules/brain.mdc` | Cursor | ✓ |
123
+ | `.github/copilot-instructions.md` | GitHub Copilot | ✓ |
124
+
125
+ Each carries `<!-- GENERATED — DO NOT EDIT -->`. Edit `brain/`; mirrors
126
+ follow. To opt out: `nexus unmirror`.
127
+
128
+ ## Per-team use cases
129
+
130
+ **Engineering** — Drop your rules in `brain/rules/<you>.md`. Cursor /
131
+ Claude Code / Codex see them all automatically.
132
+
133
+ **Data** — `brain/glossary/<you>.md` is the canonical home for metric
134
+ definitions. "What does 'active user' mean?" gets answered once,
135
+ forever.
136
+
137
+ **Analytics** — `brain/decisions/<you>.md` for revenue recognition,
138
+ attribution choices. Date stamps make the rationale auditable.
139
+
140
+ **Business / PMs** — Edit any `.md` via GitHub web UI; commits become
141
+ PRs through your normal review flow. No CLI required.
142
+
143
+ ## Long-term contracts
144
+
145
+ - **No auto-delete.** Ever. The brain only grows. Old rules are
146
+ load-bearing.
147
+ - **Append-only history.** When a rule changes, write a new chunk that
148
+ `supersedes` the old one; the old one stays on disk forever.
149
+ - **Forward-compat.** Unknown fields in chunk JSONL are dropped on
150
+ read, not crashed. Your 2026 install reads 2046 brains and vice
151
+ versa.
152
+ - **No required services.** A brain is markdown files. Anything that
153
+ reads files reads the brain.
154
+
155
+ Full format contract: see [`SPEC.md`](./SPEC.md).
156
+
157
+ ## License
158
+
159
+ MIT.
160
+
161
+ ## Related
162
+
163
+ Built originally as the substrate layer of
164
+ [polymath](https://github.com/ayushigupta-29/polymath), a multi-model
165
+ AI orchestrator. Extracted into its own repo so any AI tool, not just
166
+ Polymath, can use the format.
@@ -0,0 +1,134 @@
1
+ # nexus — long-term team brain
2
+
3
+ > *nexus* (Greek: νοῦς) — mind, intellect, intuition.
4
+ > The faculty that sees things as they are.
5
+
6
+ `nexus` is a long-lived, team-shareable project memory. It is:
7
+
8
+ - **Just markdown files in a folder.** No DB to run, no server to host.
9
+ - **Date-stamped and owner-attributed.** Every entry carries
10
+ `[YYYY-MM-DD owner]`. Grep works forever.
11
+ - **Append-only.** Nothing is ever auto-deleted. Old engineering rules
12
+ are load-bearing, not stale.
13
+ - **Tool-agnostic.** Auto-generates `CLAUDE.md`, `AGENTS.md`,
14
+ `.cursor/rules/brain.mdc`, `.github/copilot-instructions.md` —
15
+ every AI tool you use reads the brain through its native discovery,
16
+ no extra config.
17
+ - **Forward-compatible.** Schema versioned. A `nexus` from 2046 will
18
+ still read a brain written in 2026, and vice versa.
19
+
20
+ ## Why
21
+
22
+ Teams accumulate knowledge faster than they document it. The brain is
23
+ where that knowledge goes to live for decades — surviving turnover,
24
+ tool churn, and reorgs.
25
+
26
+ When someone leaves: their `brain/rules/<them>.md` stays. Their
27
+ decisions, their patterns, their gotchas — all dated, all attributed.
28
+ The next person reads `brain/` on day one and inherits the team's
29
+ mental model.
30
+
31
+ ## Quickstart
32
+
33
+ ```bash
34
+ pip install nexus
35
+ cd ~/my-repo
36
+ nexus init # creates brain/ + AI tool mirrors
37
+ nexus learn-from "always use httpx, never requests — async-first codebase"
38
+ ```
39
+
40
+ That's it. Open Claude Code / Cursor / Codex / Copilot in this repo —
41
+ all four now read this rule automatically via their native discovery
42
+ files.
43
+
44
+ ## Folder structure
45
+
46
+ ```
47
+ brain/
48
+ ├── README.md intro for humans
49
+ ├── rules/<owner>.md hard constraints, never break these
50
+ ├── logic/<owner>.md business/domain logic
51
+ ├── code/<owner>.md patterns, conventions, architecture
52
+ ├── stack/<owner>.md tech stack, versions
53
+ ├── data/<owner>.md schemas, field definitions
54
+ ├── goals/<owner>.md project objectives, scope
55
+ ├── decisions/<owner>.md ADR-style: chosen approach + why
56
+ ├── glossary/<owner>.md domain term definitions
57
+ └── personas/<owner>.md tone, behavior, communication style
58
+ ```
59
+
60
+ Per-owner sub-files mean two teammates writing simultaneously **never
61
+ conflict at the file level**. You can also use single-file mode
62
+ (`brain/rules.md`); the reader handles both.
63
+
64
+ ## CLI
65
+
66
+ ```bash
67
+ nexus init # create brain/ in cwd
68
+ nexus learn-from "<rule>" # append to rules/<you>.md, dated
69
+ nexus show [bucket] # grep-first search
70
+ --since 2026-01-01
71
+ --until 2026-05-31
72
+ --by ayushi
73
+ -q pattern
74
+ --json
75
+ nexus usage # counts per bucket + owner
76
+ nexus own # print your owner slug
77
+ nexus sync # regenerate AI-tool mirrors
78
+ nexus unmirror # remove mirrors (opt-out)
79
+ nexus spec # print SPEC.md
80
+ ```
81
+
82
+ ## Auto-generated mirrors
83
+
84
+ After every `nexus` write, four files at repo root are regenerated:
85
+
86
+ | Mirror | Tool | Auto-discovered |
87
+ |---|---|---|
88
+ | `CLAUDE.md` | Claude Code | ✓ |
89
+ | `AGENTS.md` | Codex CLI | ✓ |
90
+ | `.cursor/rules/brain.mdc` | Cursor | ✓ |
91
+ | `.github/copilot-instructions.md` | GitHub Copilot | ✓ |
92
+
93
+ Each carries `<!-- GENERATED — DO NOT EDIT -->`. Edit `brain/`; mirrors
94
+ follow. To opt out: `nexus unmirror`.
95
+
96
+ ## Per-team use cases
97
+
98
+ **Engineering** — Drop your rules in `brain/rules/<you>.md`. Cursor /
99
+ Claude Code / Codex see them all automatically.
100
+
101
+ **Data** — `brain/glossary/<you>.md` is the canonical home for metric
102
+ definitions. "What does 'active user' mean?" gets answered once,
103
+ forever.
104
+
105
+ **Analytics** — `brain/decisions/<you>.md` for revenue recognition,
106
+ attribution choices. Date stamps make the rationale auditable.
107
+
108
+ **Business / PMs** — Edit any `.md` via GitHub web UI; commits become
109
+ PRs through your normal review flow. No CLI required.
110
+
111
+ ## Long-term contracts
112
+
113
+ - **No auto-delete.** Ever. The brain only grows. Old rules are
114
+ load-bearing.
115
+ - **Append-only history.** When a rule changes, write a new chunk that
116
+ `supersedes` the old one; the old one stays on disk forever.
117
+ - **Forward-compat.** Unknown fields in chunk JSONL are dropped on
118
+ read, not crashed. Your 2026 install reads 2046 brains and vice
119
+ versa.
120
+ - **No required services.** A brain is markdown files. Anything that
121
+ reads files reads the brain.
122
+
123
+ Full format contract: see [`SPEC.md`](./SPEC.md).
124
+
125
+ ## License
126
+
127
+ MIT.
128
+
129
+ ## Related
130
+
131
+ Built originally as the substrate layer of
132
+ [polymath](https://github.com/ayushigupta-29/polymath), a multi-model
133
+ AI orchestrator. Extracted into its own repo so any AI tool, not just
134
+ Polymath, can use the format.
@@ -0,0 +1,104 @@
1
+ """
2
+ Brain — long-term project memory.
3
+
4
+ This is the public API surface of the brain. Anything not exported here is
5
+ internal to brain and may change without notice. The surface is intentionally
6
+ small — the brain's value is in the *file format* (markdown + JSONL + date
7
+ stamps + owner attribution), not in any specific Python API.
8
+
9
+ Long-term contract: nothing inside `nexus.*` imports anything from
10
+ `polymath.*` outside the brain. Enforced by tests/test_brain_decoupling.py.
11
+
12
+ This boundary is what makes the brain extractable into its own package
13
+ later — when that happens, this exact `__init__.py` becomes the top-level
14
+ of the new `brain` distribution.
15
+
16
+ ────────────────────────────────────────────────────────────────────────
17
+ Quick reference
18
+
19
+ # Discovery
20
+ from nexus import find_brain_root, find_repo_root, current_owner_slug
21
+
22
+ # Init
23
+ from nexus import init_brain_root
24
+
25
+ # Reading
26
+ from nexus import read_context, build_context_injection
27
+ from nexus import brain_grep, brain_usage
28
+
29
+ # Writing
30
+ from nexus import append_context, write_context
31
+
32
+ # Types
33
+ from nexus import TaskType, CONTEXT_FILES, AuthExpiredError
34
+
35
+ # Memory (chunked, embedded — optional, opt-in)
36
+ from nexus import Chunk, MemoryStore, harvest_session
37
+ """
38
+ from __future__ import annotations
39
+
40
+ # ── Discovery ────────────────────────────────────────────────────────────────
41
+ from nexus.discovery import (
42
+ current_owner_slug,
43
+ find_brain_root,
44
+ find_repo_root,
45
+ )
46
+
47
+ # ── Types (canonical home) ───────────────────────────────────────────────────
48
+ from nexus.types import (
49
+ ALWAYS_INJECT,
50
+ CONTEXT_DESCRIPTIONS,
51
+ CONTEXT_FILES,
52
+ TASK_CONTEXT_MAP,
53
+ AuthExpiredError,
54
+ TaskType,
55
+ )
56
+
57
+ # ── Config ───────────────────────────────────────────────────────────────────
58
+ from nexus.config import (
59
+ BRAIN_CACHE_DIR,
60
+ BRAIN_DIR_NAME,
61
+ BRAIN_HOME,
62
+ BRAIN_PROJECTS_DIR,
63
+ MIRROR_FILES,
64
+ )
65
+
66
+ # ── Index (read/write) ───────────────────────────────────────────────────────
67
+ from nexus.index import (
68
+ append_context,
69
+ brain_grep,
70
+ brain_usage,
71
+ build_context_injection,
72
+ context_dir,
73
+ create_project,
74
+ init_brain_root,
75
+ list_context_files,
76
+ list_projects,
77
+ read_context,
78
+ write_context,
79
+ )
80
+
81
+ # ── Memory (chunked store, optional) ─────────────────────────────────────────
82
+ from nexus.memory.store import Chunk, MemoryStore
83
+ from nexus.memory.writer import (
84
+ HarvestResult,
85
+ harvest_session,
86
+ )
87
+
88
+ __all__ = [
89
+ # Discovery
90
+ "current_owner_slug", "find_brain_root", "find_repo_root",
91
+ # Types
92
+ "TaskType", "AuthExpiredError",
93
+ "CONTEXT_FILES", "CONTEXT_DESCRIPTIONS", "TASK_CONTEXT_MAP", "ALWAYS_INJECT",
94
+ # Config
95
+ "BRAIN_HOME", "BRAIN_PROJECTS_DIR", "BRAIN_CACHE_DIR", "BRAIN_DIR_NAME",
96
+ "MIRROR_FILES",
97
+ # Index
98
+ "init_brain_root", "create_project", "list_projects",
99
+ "context_dir", "list_context_files",
100
+ "read_context", "write_context", "append_context",
101
+ "build_context_injection", "brain_grep", "brain_usage",
102
+ # Memory
103
+ "Chunk", "MemoryStore", "HarvestResult", "harvest_session",
104
+ ]
@@ -0,0 +1,223 @@
1
+ """
2
+ `nexus` — standalone CLI for the long-term brain.
3
+
4
+ No LLM dependencies. Pure stdlib + nexus. Works with `pip install nexus`
5
+ (brain-only) or as a dependency of polymath.
6
+
7
+ Subcommands:
8
+ nexus init Create brain/ in cwd
9
+ nexus show [bucket] [--since DATE] [--until DATE] [--by OWNER] [-q PATTERN]
10
+ Search the brain. Grep-first.
11
+ nexus usage Counts per bucket + per owner
12
+ nexus own Print which slug you'll write as
13
+ nexus learn-from "<correction>" Append to rules/<you>.md (deliberate)
14
+ nexus sync Regenerate the AI-tool mirror files
15
+ nexus unmirror Remove the AI-tool mirror files
16
+ nexus spec Print the SPEC.md format contract
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import json
22
+ import sys
23
+ from pathlib import Path
24
+
25
+ from nexus.discovery import current_owner_slug, find_brain_root, find_repo_root
26
+ from nexus.index import (
27
+ append_context, brain_grep, brain_usage, context_dir, create_project,
28
+ init_brain_root,
29
+ )
30
+ from nexus.mirrors import regenerate_mirrors, remove_mirrors
31
+
32
+
33
+ def _cmd_init(args) -> int:
34
+ base = init_brain_root(Path(args.cwd) if args.cwd else None)
35
+ repo = find_repo_root() or base.parent
36
+ report = regenerate_mirrors(repo_root=repo, force=getattr(args, "force", False))
37
+ print(f"✓ Brain initialised at {base}")
38
+ if report.written:
39
+ for tool, path in report.written.items():
40
+ print(f" ↳ mirrored to {path.relative_to(repo)} (for {tool})")
41
+ elif not report.skipped:
42
+ print(" (brain is empty; mirrors will be generated after first write)")
43
+ _print_skipped(report, repo)
44
+ print(f"\nYour owner slug: {current_owner_slug()}")
45
+ print("\nNext: `nexus learn-from \"your first rule\"`")
46
+ return 0
47
+
48
+
49
+ def _cmd_show(args) -> int:
50
+ project = args.project or ""
51
+ hits = brain_grep(
52
+ project,
53
+ pattern=args.pattern or "",
54
+ since=args.since or "",
55
+ until=args.until or "",
56
+ by=args.by or "",
57
+ ctx_name=args.bucket or "",
58
+ )
59
+ if not hits:
60
+ print("(no matches)")
61
+ return 0
62
+ if args.json:
63
+ print(json.dumps(hits, indent=2))
64
+ return 0
65
+ for h in hits:
66
+ print(f"{h['file']:40s} {h['text']}")
67
+ return 0
68
+
69
+
70
+ def _cmd_usage(args) -> int:
71
+ project = args.project or ""
72
+ stats = brain_usage(project)
73
+ if args.json:
74
+ print(json.dumps(stats, indent=2))
75
+ return 0
76
+ print(f"Total entries: {stats['total_entries']}\n")
77
+ if stats["buckets"]:
78
+ print("By bucket:")
79
+ for k, v in sorted(stats["buckets"].items(), key=lambda kv: -kv[1]):
80
+ print(f" {k:12s} {v}")
81
+ if stats["owners"]:
82
+ print("\nBy owner:")
83
+ for k, v in sorted(stats["owners"].items(), key=lambda kv: -kv[1]):
84
+ print(f" {k:12s} {v}")
85
+ return 0
86
+
87
+
88
+ def _cmd_own(args) -> int:
89
+ """Print owner slug and the timezone we'll record on writes."""
90
+ from nexus.memory.store import _local_tz_name
91
+ print(f"owner: {current_owner_slug()}")
92
+ print(f"tz: {_local_tz_name()}")
93
+ return 0
94
+
95
+
96
+ def _cmd_learn_from(args) -> int:
97
+ text = args.text or ""
98
+ if not text:
99
+ print("usage: nexus learn-from \"<rule>\"", file=sys.stderr)
100
+ return 2
101
+ project = args.project or ""
102
+ brain = find_brain_root()
103
+ if brain is None and not project:
104
+ # Need either a brain/ in cwd or a project name for personal scope.
105
+ print("error: no brain/ found in this repo. Run `nexus init` first,\n"
106
+ "or use --project <name> for personal-scope brains.", file=sys.stderr)
107
+ return 1
108
+ if not brain and project:
109
+ create_project(project)
110
+ owner = current_owner_slug()
111
+ written = append_context(project, "rules", text, by_owner=True, author=owner)
112
+ print(f"✓ Logged to {written.relative_to(written.parents[2]) if len(written.parents) >= 3 else written}")
113
+ _dt = __import__("datetime").datetime
114
+ today = _dt.now().astimezone().strftime("%Y-%m-%d")
115
+ print(f" - [{today} {owner}] {text}")
116
+ return 0
117
+
118
+
119
+ def _print_skipped(report, repo: Path) -> None:
120
+ """Tell the user about mirror files we refused to overwrite. Silence
121
+ here would look like the mirror simply worked."""
122
+ for tool, path in report.backed_up.items():
123
+ print(f" ↳ saved your old {tool} file to {path.relative_to(repo)}")
124
+ if not report.skipped:
125
+ return
126
+ for tool, path in report.skipped.items():
127
+ print(f" skipped {path.relative_to(repo)} — you wrote this, not nexus")
128
+ print(" Move its content into brain/, or run with --force to hand it "
129
+ "over (your version is saved as a .bak first).")
130
+
131
+
132
+ def _cmd_sync(args) -> int:
133
+ repo = find_repo_root() or (find_brain_root() or Path.cwd()).parent
134
+ report = regenerate_mirrors(args.project or "", repo_root=repo, force=args.force)
135
+ if not report.written and not report.skipped:
136
+ print("(brain is empty; nothing to mirror)")
137
+ return 0
138
+ for tool, path in report.written.items():
139
+ print(f"✓ {tool}: {path.relative_to(repo)}")
140
+ _print_skipped(report, repo)
141
+ return 0
142
+
143
+
144
+ def _cmd_unmirror(args) -> int:
145
+ report = remove_mirrors(force=args.force)
146
+ for p in report.removed:
147
+ print(f"removed {p}")
148
+ for p in report.skipped:
149
+ print(f"kept {p} — you wrote this, not nexus")
150
+ if report.skipped:
151
+ print("Pass --force to delete it anyway.")
152
+ if not report.removed and not report.skipped:
153
+ print("(no mirror files present)")
154
+ return 0
155
+
156
+
157
+ def _cmd_spec(args) -> int:
158
+ spec = Path(__file__).resolve().parent / "SPEC.md"
159
+ if spec.exists():
160
+ print(spec.read_text())
161
+ return 0
162
+ print(f"SPEC.md not found at {spec}", file=sys.stderr)
163
+ return 1
164
+
165
+
166
+ def build_parser() -> argparse.ArgumentParser:
167
+ p = argparse.ArgumentParser(
168
+ prog="nexus",
169
+ description="Long-term, team-shareable project brain.",
170
+ )
171
+ p.add_argument("--project", default="", help="project name (personal scope)")
172
+ sub = p.add_subparsers(dest="cmd", required=True)
173
+
174
+ p_init = sub.add_parser("init", help="create brain/ in cwd")
175
+ p_init.add_argument("--cwd", default="", help="target directory (default: cwd)")
176
+ p_init.add_argument("--force", action="store_true",
177
+ help="take over mirror files you wrote by hand (saves a .bak)")
178
+ p_init.set_defaults(func=_cmd_init)
179
+
180
+ p_show = sub.add_parser("show", help="search the brain (grep-first)")
181
+ p_show.add_argument("bucket", nargs="?", default="", help="one of: rules logic code stack data goals decisions glossary personas")
182
+ p_show.add_argument("--since", default="", help="YYYY-MM-DD lower bound")
183
+ p_show.add_argument("--until", default="", help="YYYY-MM-DD upper bound")
184
+ p_show.add_argument("--by", default="", help="owner slug")
185
+ p_show.add_argument("-q", "--pattern", default="", help="substring filter (case-insensitive)")
186
+ p_show.add_argument("--json", action="store_true", help="emit JSON")
187
+ p_show.set_defaults(func=_cmd_show)
188
+
189
+ p_usage = sub.add_parser("usage", help="counts per bucket and per owner (read-only)")
190
+ p_usage.add_argument("--json", action="store_true")
191
+ p_usage.set_defaults(func=_cmd_usage)
192
+
193
+ p_own = sub.add_parser("own", help="print which owner slug you'll write as")
194
+ p_own.set_defaults(func=_cmd_own)
195
+
196
+ p_learn = sub.add_parser("learn-from", help="log a correction to rules/<you>.md")
197
+ p_learn.add_argument("text", nargs="?", default="", help="the rule to log")
198
+ p_learn.set_defaults(func=_cmd_learn_from)
199
+
200
+ p_sync = sub.add_parser("sync", help="regenerate AI-tool mirror files")
201
+ p_sync.add_argument("--force", action="store_true",
202
+ help="take over mirror files you wrote by hand (saves a .bak)")
203
+ p_sync.set_defaults(func=_cmd_sync)
204
+
205
+ p_unmirror = sub.add_parser("unmirror", help="remove AI-tool mirror files")
206
+ p_unmirror.add_argument("--force", action="store_true",
207
+ help="also delete mirror files nexus did not write")
208
+ p_unmirror.set_defaults(func=_cmd_unmirror)
209
+
210
+ p_spec = sub.add_parser("spec", help="print the brain SPEC.md")
211
+ p_spec.set_defaults(func=_cmd_spec)
212
+
213
+ return p
214
+
215
+
216
+ def main(argv: list[str] | None = None) -> int:
217
+ parser = build_parser()
218
+ args = parser.parse_args(argv)
219
+ return args.func(args)
220
+
221
+
222
+ if __name__ == "__main__":
223
+ sys.exit(main())