rm-contextos 0.1.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 (124) hide show
  1. rm_contextos-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  2. rm_contextos-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. rm_contextos-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. rm_contextos-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  5. rm_contextos-0.1.0/.github/workflows/ci.yml +42 -0
  6. rm_contextos-0.1.0/.github/workflows/publish.yml +45 -0
  7. rm_contextos-0.1.0/.gitignore +15 -0
  8. rm_contextos-0.1.0/ARCHITECTURE.md +257 -0
  9. rm_contextos-0.1.0/AUDIT_REPORT.md +200 -0
  10. rm_contextos-0.1.0/CHANGELOG.md +33 -0
  11. rm_contextos-0.1.0/CLAUDE.md +105 -0
  12. rm_contextos-0.1.0/CODEX.md +175 -0
  13. rm_contextos-0.1.0/CODE_OF_CONDUCT.md +32 -0
  14. rm_contextos-0.1.0/CONTRIBUTING.md +51 -0
  15. rm_contextos-0.1.0/LICENSE +183 -0
  16. rm_contextos-0.1.0/PKG-INFO +683 -0
  17. rm_contextos-0.1.0/README.md +452 -0
  18. rm_contextos-0.1.0/RISK_REGISTER.md +189 -0
  19. rm_contextos-0.1.0/ROADMAP.md +77 -0
  20. rm_contextos-0.1.0/SECURITY.md +37 -0
  21. rm_contextos-0.1.0/TASKS.md +123 -0
  22. rm_contextos-0.1.0/TEST_PLAN.md +269 -0
  23. rm_contextos-0.1.0/contextos/__init__.py +6 -0
  24. rm_contextos-0.1.0/contextos/cli/__init__.py +3 -0
  25. rm_contextos-0.1.0/contextos/cli/commands/__init__.py +3 -0
  26. rm_contextos-0.1.0/contextos/cli/commands/export.py +270 -0
  27. rm_contextos-0.1.0/contextos/cli/commands/init.py +96 -0
  28. rm_contextos-0.1.0/contextos/cli/commands/memory.py +262 -0
  29. rm_contextos-0.1.0/contextos/cli/commands/pack.py +173 -0
  30. rm_contextos-0.1.0/contextos/cli/commands/scan.py +98 -0
  31. rm_contextos-0.1.0/contextos/cli/commands/task.py +153 -0
  32. rm_contextos-0.1.0/contextos/cli/main.py +49 -0
  33. rm_contextos-0.1.0/contextos/core/__init__.py +3 -0
  34. rm_contextos-0.1.0/contextos/core/ast_extractor.py +234 -0
  35. rm_contextos-0.1.0/contextos/core/compression.py +74 -0
  36. rm_contextos-0.1.0/contextos/core/context_selector.py +641 -0
  37. rm_contextos-0.1.0/contextos/core/dependency_graph.py +339 -0
  38. rm_contextos-0.1.0/contextos/core/headroom_adapter.py +116 -0
  39. rm_contextos-0.1.0/contextos/core/initializer.py +285 -0
  40. rm_contextos-0.1.0/contextos/core/pack_builder.py +372 -0
  41. rm_contextos-0.1.0/contextos/core/repo_index.py +528 -0
  42. rm_contextos-0.1.0/contextos/core/safety.py +174 -0
  43. rm_contextos-0.1.0/contextos/core/scanner.py +178 -0
  44. rm_contextos-0.1.0/contextos/core/secret_detector.py +253 -0
  45. rm_contextos-0.1.0/contextos/core/summarizer.py +587 -0
  46. rm_contextos-0.1.0/contextos/core/token_counter.py +52 -0
  47. rm_contextos-0.1.0/contextos/exporters/__init__.py +1 -0
  48. rm_contextos-0.1.0/contextos/exporters/aider.py +62 -0
  49. rm_contextos-0.1.0/contextos/exporters/base.py +206 -0
  50. rm_contextos-0.1.0/contextos/exporters/claude.py +64 -0
  51. rm_contextos-0.1.0/contextos/exporters/codex.py +62 -0
  52. rm_contextos-0.1.0/contextos/exporters/cursor.py +62 -0
  53. rm_contextos-0.1.0/demo/demo.gif +0 -0
  54. rm_contextos-0.1.0/demo.tape +45 -0
  55. rm_contextos-0.1.0/docs/ARCHITECTURE.md +232 -0
  56. rm_contextos-0.1.0/docs/CONTRIBUTING.md +172 -0
  57. rm_contextos-0.1.0/docs/HEADROOM.md +128 -0
  58. rm_contextos-0.1.0/docs/INSTALL.md +111 -0
  59. rm_contextos-0.1.0/docs/MCP_SETUP.md +180 -0
  60. rm_contextos-0.1.0/docs/ROADMAP.md +100 -0
  61. rm_contextos-0.1.0/docs/SAFETY.md +145 -0
  62. rm_contextos-0.1.0/docs/USAGE.md +259 -0
  63. rm_contextos-0.1.0/examples/README.md +174 -0
  64. rm_contextos-0.1.0/examples/monorepo/README.md +38 -0
  65. rm_contextos-0.1.0/examples/monorepo/package.json +14 -0
  66. rm_contextos-0.1.0/examples/monorepo/packages/api/package.json +17 -0
  67. rm_contextos-0.1.0/examples/monorepo/packages/api/src/auth.ts +48 -0
  68. rm_contextos-0.1.0/examples/monorepo/packages/api/src/index.ts +15 -0
  69. rm_contextos-0.1.0/examples/monorepo/packages/api/src/routes.ts +40 -0
  70. rm_contextos-0.1.0/examples/monorepo/packages/shared/package.json +8 -0
  71. rm_contextos-0.1.0/examples/monorepo/packages/shared/src/types.ts +25 -0
  72. rm_contextos-0.1.0/examples/monorepo/packages/shared/src/validators.ts +30 -0
  73. rm_contextos-0.1.0/examples/monorepo/packages/web/package.json +19 -0
  74. rm_contextos-0.1.0/examples/monorepo/packages/web/src/App.tsx +40 -0
  75. rm_contextos-0.1.0/examples/monorepo/packages/web/src/components/ProjectCard.tsx +25 -0
  76. rm_contextos-0.1.0/examples/monorepo/turbo.json +8 -0
  77. rm_contextos-0.1.0/examples/python_fastapi/README.md +34 -0
  78. rm_contextos-0.1.0/examples/python_fastapi/app/__init__.py +0 -0
  79. rm_contextos-0.1.0/examples/python_fastapi/app/auth.py +79 -0
  80. rm_contextos-0.1.0/examples/python_fastapi/app/database.py +23 -0
  81. rm_contextos-0.1.0/examples/python_fastapi/app/main.py +24 -0
  82. rm_contextos-0.1.0/examples/python_fastapi/app/models.py +45 -0
  83. rm_contextos-0.1.0/examples/python_fastapi/app/routes/__init__.py +0 -0
  84. rm_contextos-0.1.0/examples/python_fastapi/app/routes/items.py +48 -0
  85. rm_contextos-0.1.0/examples/python_fastapi/app/routes/users.py +52 -0
  86. rm_contextos-0.1.0/examples/python_fastapi/requirements.txt +8 -0
  87. rm_contextos-0.1.0/examples/python_fastapi/tests/conftest.py +12 -0
  88. rm_contextos-0.1.0/examples/python_fastapi/tests/test_auth.py +29 -0
  89. rm_contextos-0.1.0/examples/python_fastapi/tests/test_users.py +45 -0
  90. rm_contextos-0.1.0/examples/react_typescript/README.md +33 -0
  91. rm_contextos-0.1.0/examples/react_typescript/package.json +25 -0
  92. rm_contextos-0.1.0/examples/react_typescript/src/App.tsx +18 -0
  93. rm_contextos-0.1.0/examples/react_typescript/src/api/client.ts +41 -0
  94. rm_contextos-0.1.0/examples/react_typescript/src/components/Auth/LoginForm.test.tsx +31 -0
  95. rm_contextos-0.1.0/examples/react_typescript/src/components/Auth/LoginForm.tsx +49 -0
  96. rm_contextos-0.1.0/examples/react_typescript/src/components/Dashboard/Dashboard.tsx +58 -0
  97. rm_contextos-0.1.0/examples/react_typescript/src/hooks/useApi.ts +47 -0
  98. rm_contextos-0.1.0/examples/react_typescript/src/hooks/useAuth.ts +53 -0
  99. rm_contextos-0.1.0/examples/react_typescript/src/types/index.ts +23 -0
  100. rm_contextos-0.1.0/examples/react_typescript/tsconfig.json +15 -0
  101. rm_contextos-0.1.0/pyproject.toml +98 -0
  102. rm_contextos-0.1.0/tests/__init__.py +0 -0
  103. rm_contextos-0.1.0/tests/cli/__init__.py +0 -0
  104. rm_contextos-0.1.0/tests/cli/test_commands.py +153 -0
  105. rm_contextos-0.1.0/tests/cli/test_imports.py +46 -0
  106. rm_contextos-0.1.0/tests/cli/test_init.py +221 -0
  107. rm_contextos-0.1.0/tests/cli/test_memory.py +552 -0
  108. rm_contextos-0.1.0/tests/cli/test_pack.py +590 -0
  109. rm_contextos-0.1.0/tests/cli/test_task.py +338 -0
  110. rm_contextos-0.1.0/tests/conftest.py +29 -0
  111. rm_contextos-0.1.0/tests/core/__init__.py +0 -0
  112. rm_contextos-0.1.0/tests/core/test_ast_extractor.py +215 -0
  113. rm_contextos-0.1.0/tests/core/test_compression.py +422 -0
  114. rm_contextos-0.1.0/tests/core/test_context_selector.py +802 -0
  115. rm_contextos-0.1.0/tests/core/test_dependency_graph.py +598 -0
  116. rm_contextos-0.1.0/tests/core/test_repo_index.py +635 -0
  117. rm_contextos-0.1.0/tests/core/test_scanner.py +476 -0
  118. rm_contextos-0.1.0/tests/core/test_secret_detector.py +749 -0
  119. rm_contextos-0.1.0/tests/core/test_summarizer.py +770 -0
  120. rm_contextos-0.1.0/tests/core/test_token_counter.py +265 -0
  121. rm_contextos-0.1.0/tests/examples/__init__.py +0 -0
  122. rm_contextos-0.1.0/tests/examples/test_examples.py +454 -0
  123. rm_contextos-0.1.0/tests/exporters/__init__.py +0 -0
  124. rm_contextos-0.1.0/tests/exporters/test_exporters.py +765 -0
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a reproducible problem in ContextOS
4
+ title: "[Bug]: "
5
+ labels: bug
6
+ assignees: ""
7
+ ---
8
+
9
+ ## What happened?
10
+
11
+
12
+ ## Steps to reproduce
13
+
14
+ 1.
15
+ 2.
16
+ 3.
17
+
18
+ ## Expected behavior
19
+
20
+
21
+ ## Environment
22
+
23
+ - ContextOS version:
24
+ - Python version:
25
+ - OS:
26
+ - Install method:
27
+
28
+ ## Logs or output
29
+
30
+ ```text
31
+
32
+ ```
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an improvement for ContextOS
4
+ title: "[Feature]: "
5
+ labels: enhancement
6
+ assignees: ""
7
+ ---
8
+
9
+ ## Problem
10
+
11
+
12
+ ## Proposed solution
13
+
14
+
15
+ ## Alternatives considered
16
+
17
+
18
+ ## Additional context
19
+
@@ -0,0 +1,17 @@
1
+ ## Summary
2
+
3
+ -
4
+
5
+ ## Validation
6
+
7
+ - [ ] `pytest`
8
+ - [ ] `ruff check .`
9
+ - [ ] `mypy .`
10
+ - [ ] `ruff format --check contextos/ tests/`
11
+
12
+ ## Safety Checklist
13
+
14
+ - [ ] No unrelated behavior changes.
15
+ - [ ] Secret handling remains safe by default.
16
+ - [ ] Filesystem reads/writes are scoped and intentional.
17
+ - [ ] Documentation or changelog updated when user-visible behavior changes.
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.11", "3.12", "3.13"]
17
+ os: [ubuntu-latest, macos-latest]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Lint (ruff)
33
+ run: ruff check contextos/ tests/
34
+
35
+ - name: Format check (ruff)
36
+ run: ruff format --check contextos/ tests/
37
+
38
+ - name: Type check (mypy)
39
+ run: mypy contextos/
40
+
41
+ - name: Run tests
42
+ run: pytest --cov=contextos --cov-report=term-missing --cov-fail-under=80
@@ -0,0 +1,45 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install build
20
+ run: pip install build
21
+
22
+ - name: Build package
23
+ run: python -m build
24
+
25
+ - name: Upload dist artifacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ name: Publish to PyPI
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Download dist artifacts
37
+ uses: actions/download-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+
42
+ - name: Publish to PyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
44
+ with:
45
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ venv/
8
+ .coverage
9
+ .coverage.*
10
+ htmlcov/
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ .mypy_cache/
14
+ .contextos/
15
+ *.md.bak
@@ -0,0 +1,257 @@
1
+ # ContextOS Architecture
2
+
3
+ ## Overview
4
+
5
+ ContextOS is a pipeline: a repository enters one end, a context pack exits the other. Each stage is a pure transformation — no side effects, no network, no mutation of the source repo.
6
+
7
+ ```
8
+ repo path
9
+
10
+
11
+ ┌──────────┐
12
+ │ Scanner │ walks files, respects .gitignore, classifies by language
13
+ └──────────┘
14
+ │ FileIndex
15
+
16
+ ┌─────────────┐
17
+ │ Intelligence│ extracts symbols, imports, call edges → RepoGraph
18
+ └─────────────┘
19
+ │ RepoGraph
20
+
21
+ ┌──────────┐
22
+ │ Selector │ takes task + budget → ranks and clips chunks
23
+ └──────────┘
24
+ │ ContextBundle
25
+
26
+ ┌────────────┐
27
+ │ Compressor │ optional Headroom pass — shrinks tokens further
28
+ └────────────┘
29
+ │ CompressedBundle
30
+
31
+ ┌──────────┐
32
+ │ Exporter │ formats for target agent
33
+ └──────────┘
34
+
35
+
36
+ context pack (file on disk)
37
+ ```
38
+
39
+ ## Data Models (`models.py`)
40
+
41
+ ```python
42
+ @dataclass
43
+ class FileNode:
44
+ path: Path # absolute path
45
+ rel_path: Path # relative to repo root
46
+ language: str # "python", "typescript", etc.
47
+ size_bytes: int
48
+ lines: int
49
+ content: str
50
+ tokens: int # estimated at scan time
51
+
52
+ @dataclass
53
+ class ChunkNode:
54
+ file: FileNode
55
+ start_line: int
56
+ end_line: int
57
+ chunk_type: str # "function", "class", "module", "block"
58
+ name: str | None # symbol name if extractable
59
+ content: str
60
+ tokens: int
61
+ score: float # set by Selector; 0.0 at creation
62
+
63
+ @dataclass
64
+ class RepoGraph:
65
+ files: list[FileNode]
66
+ chunks: list[ChunkNode]
67
+ imports: dict[Path, list[Path]] # file → files it imports
68
+ symbols: dict[str, list[ChunkNode]] # symbol name → defining chunks
69
+ call_edges: list[tuple[str, str]] # (caller_symbol, callee_symbol)
70
+
71
+ @dataclass
72
+ class ContextBundle:
73
+ task: str
74
+ repo_root: Path
75
+ chunks: list[ChunkNode] # selected, ordered
76
+ total_tokens: int
77
+ budget: int
78
+ strategy: str
79
+ created_at: str # ISO 8601, deterministic
80
+
81
+ @dataclass
82
+ class CompressedBundle:
83
+ bundle: ContextBundle
84
+ compressed_content: str
85
+ original_tokens: int
86
+ compressed_tokens: int
87
+ compression_ratio: float
88
+ ```
89
+
90
+ ## Module Breakdown
91
+
92
+ ### `scanner/`
93
+
94
+ **`walker.py`** — `RepoWalker`
95
+ - Walks a directory tree using `pathlib.Path.rglob`
96
+ - Reads `.gitignore` via `gitignore-parser` (or built-in simple parser)
97
+ - Skips: binary files, files over a size limit (default 512 KB), lock files, build artifacts
98
+ - Returns an ordered list of `FileNode` (ordered by `rel_path` for determinism)
99
+
100
+ **`classifier.py`** — `LanguageClassifier`
101
+ - Maps file extensions to language names
102
+ - Detects shebangs for extensionless scripts
103
+ - Returns language string or `"unknown"`
104
+
105
+ **`index.py`** — `FileIndex`
106
+ - Thin wrapper over `list[FileNode]`
107
+ - Provides lookup by path, by language, by glob pattern
108
+ - Computes aggregate stats (total files, total tokens, language breakdown)
109
+
110
+ ### `intelligence/`
111
+
112
+ **`chunker.py`** — `Chunker`
113
+ - Splits files into `ChunkNode` objects
114
+ - Two modes:
115
+ - **AST mode** (tree-sitter, optional): function/class/method-level chunks
116
+ - **Line mode** (default): fixed-size overlapping windows (e.g. 50 lines, 10-line overlap)
117
+ - Chunk size is bounded: minimum 5 lines, maximum 200 lines
118
+ - Always produces at least one chunk per file (the whole file if small)
119
+
120
+ **`extractor.py`** — `SymbolExtractor`
121
+ - Runs in AST mode only
122
+ - Extracts: function names, class names, import statements, exported symbols
123
+ - Falls back to regex-based extraction for unsupported languages
124
+
125
+ **`graph.py`** — `GraphBuilder`
126
+ - Builds `RepoGraph` from `FileIndex` + extracted symbols
127
+ - Import resolution: converts `from foo.bar import baz` → `Path` edges
128
+ - Does not execute code; static analysis only
129
+ - Cycle detection: records import cycles as metadata, does not fail on them
130
+
131
+ ### `selector/`
132
+
133
+ **`ranker.py`** — `Ranker`
134
+ - Assigns a relevance score to each `ChunkNode` given a task string
135
+ - Scoring is additive; no single signal dominates:
136
+
137
+ | Signal | Weight | Description |
138
+ |--------|--------|-------------|
139
+ | Keyword overlap | 0.40 | Task tokens that appear in chunk content |
140
+ | Symbol match | 0.25 | Task tokens matching extracted symbol names |
141
+ | Import centrality | 0.20 | Files imported by many others score higher |
142
+ | Recency proxy | 0.15 | Files modified recently score slightly higher |
143
+
144
+ - Scores are normalized to [0.0, 1.0]
145
+ - Scoring is purely local: no embeddings, no LLM calls
146
+
147
+ **`budget.py`** — `BudgetEnforcer`
148
+ - Takes ranked `ChunkNode` list and a token budget
149
+ - Greedy selection: add highest-scoring chunks until budget is exhausted
150
+ - Two modes:
151
+ - `greedy`: pure score-descending fill
152
+ - `balanced`: ensures at least one chunk from each file that appears in the top N
153
+
154
+ **`strategy.py`** — `SelectionStrategy`
155
+ - Named presets that combine Ranker weights + BudgetEnforcer mode
156
+ - Built-in: `default`, `deep-file` (fewer files, deeper), `wide-repo` (more files, shallower)
157
+ - Custom strategies via TOML config
158
+
159
+ ### `compressor/`
160
+
161
+ **`headroom.py`** — `HeadroomCompressor`
162
+ - Optional; only active when `headroom-ai` is installed and proxy is reachable
163
+ - Sends `ContextBundle` content to local Headroom proxy at `http://127.0.0.1:8787`
164
+ - Returns `CompressedBundle` with compressed text and token delta
165
+ - Fails gracefully: if proxy is unreachable, returns original bundle unchanged
166
+ - No data leaves the machine; proxy is local
167
+
168
+ ### `exporter/`
169
+
170
+ Each exporter takes a `ContextBundle | CompressedBundle` and returns a `str`.
171
+
172
+ **`base.py`** — `BaseExporter` (abstract)
173
+ - `render(bundle) -> str`
174
+ - `filename() -> str`
175
+
176
+ **`markdown.py`** — renders as fenced code blocks with file headers
177
+
178
+ **`xml.py`** — renders as `<file path="...">` XML tags (Claude-style)
179
+
180
+ **`claude.py`** — XML blocks + a task preamble formatted for Claude Code's system prompt
181
+
182
+ **`codex.py`** — plain text blocks formatted for `AGENTS.md` injection
183
+
184
+ **`cursor.py`** — splits into `.cursorrules` (repo summary) + `context.md` (file blocks)
185
+
186
+ **`aider.py`** — tab-separated file list + content blocks for Aider's `--read` input
187
+
188
+ **`json_.py`** — machine-readable JSON: metadata + chunks as structured objects
189
+
190
+ ### `cli.py`
191
+
192
+ Built with Typer. Three top-level commands:
193
+
194
+ ```
195
+ contextos scan <repo> # index and summarize
196
+ contextos pack <repo> --task --budget --format # select and export
197
+ contextos graph <repo> # print import graph (optional)
198
+ ```
199
+
200
+ All commands are read-only on the source repo. Output goes to stdout or `--out <path>`.
201
+
202
+ ## Token Counting
203
+
204
+ Token counting uses a priority chain:
205
+
206
+ 1. `tiktoken` with the model's actual tokenizer (if installed and model known)
207
+ 2. `tiktoken` with `cl100k_base` as a universal approximation
208
+ 3. `len(content) // 4` as a last-resort fallback
209
+
210
+ The counting method is reported in bundle metadata so consumers know which approximation was used.
211
+
212
+ ## Determinism Contract
213
+
214
+ Given identical inputs (repo state, task string, budget, strategy), ContextOS always produces byte-for-byte identical output. This is guaranteed by:
215
+
216
+ - File iteration order: `sorted(path.rglob(...))` — lexicographic, OS-independent
217
+ - Chunk ordering: deterministic by `(file.rel_path, start_line)`
218
+ - Score tie-breaking: ties broken by `(rel_path, start_line)` not by dict insertion order
219
+ - Timestamps: `created_at` in output can be suppressed with `--no-timestamp` for reproducible diffs
220
+
221
+ ## Extension Points
222
+
223
+ | Extension | Mechanism |
224
+ |-----------|-----------|
225
+ | New language chunker | Register in `chunker.py` dispatch table |
226
+ | New ranking signal | Add signal function to `ranker.py`, assign weight |
227
+ | New exporter | Subclass `BaseExporter`, register in `cli.py` |
228
+ | Custom selection strategy | TOML config file, loaded by `strategy.py` |
229
+ | Alternative compressor | Implement `Compressor` protocol in `compressor/` |
230
+
231
+ ## Security Model
232
+
233
+ - **Read-only**: ContextOS never writes to the source repo
234
+ - **No execution**: no `eval`, no subprocess of repo code, no dynamic imports from repo
235
+ - **No network by default**: all network features gated behind explicit flags
236
+ - **Path traversal**: all paths validated to stay within the declared repo root
237
+ - **Output only to declared destinations**: `--out` flag or stdout; no implicit file writes
238
+
239
+ ## Dependencies
240
+
241
+ ### Required
242
+ - `typer` — CLI
243
+ - `pathlib` (stdlib) — path handling
244
+ - `dataclasses` (stdlib) — models
245
+ - `tomllib` (stdlib, Python 3.11+) — config parsing
246
+
247
+ ### Optional
248
+ - `tiktoken` — accurate token counting
249
+ - `tree-sitter` + language grammars — AST chunking
250
+ - `gitignore-parser` — full `.gitignore` spec support
251
+ - `headroom-ai` — compression
252
+
253
+ ### Dev only
254
+ - `pytest` — tests
255
+ - `pytest-cov` — coverage
256
+ - `ruff` — lint + format
257
+ - `mypy` — type checking
@@ -0,0 +1,200 @@
1
+ # ContextOS Maintainer Audit Report
2
+
3
+ Date: 2026-06-29
4
+
5
+ Scope: strict open-source maintainer review of the Python package, Typer CLI, scanner,
6
+ context pack builder, secret redaction, tests, packaging metadata, and README.
7
+
8
+ ## Executive Summary
9
+
10
+ ContextOS is small, well-tested, and generally conservative about filesystem access. The
11
+ main high-priority issue found during this audit was that the repository advertises a
12
+ strict mypy configuration, but `mypy .` did not pass. That is a release/CI quality gate
13
+ failure, especially for a typed CLI package with dynamic exporter and compression
14
+ boundaries.
15
+
16
+ I fixed only that high-priority issue. Lower-priority documentation and packaging gaps
17
+ are recorded below but intentionally left unfixed.
18
+
19
+ ## High-Priority Findings Fixed
20
+
21
+ ### HP-1: Configured mypy check failed
22
+
23
+ Severity: High
24
+
25
+ Area: typing, CI/release confidence, dynamic plugin boundaries
26
+
27
+ Evidence:
28
+
29
+ - `pyproject.toml` enables `[tool.mypy] strict = true`.
30
+ - Baseline `mypy .` failed with 27 errors across source and tests.
31
+ - Source-level failures included untyped dynamic provider/exporter calls:
32
+ - `contextos/core/compression.py`
33
+ - `contextos/core/headroom_adapter.py`
34
+ - `contextos/cli/commands/export.py`
35
+ - `contextos/core/secret_detector.py`
36
+
37
+ Impact:
38
+
39
+ - Contributors cannot run the configured quality gate successfully.
40
+ - Dynamic compression/exporter boundaries were opaque to mypy, which weakens confidence
41
+ in CLI code paths that are meant to be extensible.
42
+
43
+ Fix:
44
+
45
+ - Added narrow protocols/casts around dynamic exporter and Headroom imports.
46
+ - Removed a dead helper in secret redaction.
47
+ - Tightened test fixture/type annotations and corrected test imports that referenced a
48
+ helper through the wrong module.
49
+
50
+ Status: Fixed. `mypy .` now passes.
51
+
52
+ ## Other Findings Not Fixed In This Pass
53
+
54
+ ### README project structure is stale
55
+
56
+ Severity: Medium
57
+
58
+ Area: README clarity
59
+
60
+ Details:
61
+
62
+ The README's `Project Structure` section lists an older layout such as `cli.py`,
63
+ `scanner/`, `intelligence/`, `selector/`, `compressor/`, and `models.py`. The current
64
+ repository uses `contextos/cli/commands/`, `contextos/core/`, and `contextos/exporters/`.
65
+
66
+ Risk:
67
+
68
+ New contributors will look for files that do not exist.
69
+
70
+ Status: Not fixed. Documentation-only and not high priority for this pass.
71
+
72
+ ### MIT license declared but no LICENSE file present
73
+
74
+ Severity: Medium
75
+
76
+ Area: packaging, open-source hygiene
77
+
78
+ Details:
79
+
80
+ `pyproject.toml` declares `license = { text = "MIT" }`, but the repository does not
81
+ include a `LICENSE` file.
82
+
83
+ Risk:
84
+
85
+ Package metadata is installable, but GitHub/open-source consumers do not get the full
86
+ license text in the repository.
87
+
88
+ Status: Not fixed. Adding license text should be done by the project owner with the
89
+ correct copyright holder.
90
+
91
+ ### Package metadata is minimal
92
+
93
+ Severity: Low
94
+
95
+ Area: packaging
96
+
97
+ Details:
98
+
99
+ The package has name, version, description, readme, Python requirement, dependencies,
100
+ and console script configured. It does not include classifiers, project URLs, authors,
101
+ or keywords.
102
+
103
+ Risk:
104
+
105
+ Lower discoverability and weaker PyPI presentation, but no runtime breakage.
106
+
107
+ Status: Not fixed. Not high priority.
108
+
109
+ ## Hardening Follow-Up
110
+
111
+ Additional real-user hardening completed after the initial audit:
112
+
113
+ - `.contextos/` is now always excluded from scans so generated packs and indexes do not
114
+ feed back into later scans or packs.
115
+ - `contextos scan --max-files` now enforces the documented indexing limit instead of
116
+ only changing the displayed count.
117
+ - Scanner relative paths are normalized to POSIX separators for stable output across
118
+ Windows, macOS, and Linux.
119
+ - Generated/noisy notebook and minified asset files are skipped before content indexing.
120
+ - `contextos pack` now regenerates scan data when `.contextos/file_summaries.json` or
121
+ `.contextos/dependency_graph.json` is corrupted or missing.
122
+ - Test-file detection now handles Windows-style separators.
123
+
124
+ Regression coverage added for empty repos, repos without README files, monorepo-shaped
125
+ trees, binary files, large lockfiles, minified JS, notebooks, symlinks, permission errors,
126
+ invalid Unicode, corrupted `.contextos`, repeated init/scan/pack workflows, Windows path
127
+ separators, nested git metadata, and missing optional dependencies.
128
+
129
+ ### `--out` writes exactly the user-provided path
130
+
131
+ Severity: Low
132
+
133
+ Area: file handling
134
+
135
+ Details:
136
+
137
+ `contextos pack --out` and `contextos export ... --out` write to the provided path
138
+ without creating parent directories and without restricting the path to the repository.
139
+
140
+ Risk:
141
+
142
+ This is normal CLI behavior for an explicit output flag, but errors may be less friendly
143
+ when a parent directory is missing. It is not an unsafe implicit write because the user
144
+ must provide `--out`.
145
+
146
+ Status: Not fixed. Not high priority.
147
+
148
+ ## Areas Reviewed
149
+
150
+ ### CLI behavior
151
+
152
+ - Typer command registration is straightforward.
153
+ - Invalid repo and invalid budget paths return controlled `typer.Exit(code=1)`.
154
+ - Tests cover command help, init, scan, task, memory, pack, export, invalid formats,
155
+ missing repos, and secret-related flags.
156
+
157
+ ### Unsafe file handling
158
+
159
+ - Scanner uses `os.walk(..., followlinks=False)`.
160
+ - Symlinks escaping the repo root are rejected.
161
+ - Binary files, oversized files, permission errors, encoding errors, `.git`, virtualenvs,
162
+ build artifacts, and cache directories are skipped.
163
+ - `init --force` overwrites `.contextos` templates, but that behavior is explicit and
164
+ documented.
165
+
166
+ ### Secret leakage risks
167
+
168
+ - Secret-looking filenames are excluded from context selection.
169
+ - Raw content is redacted before pack/export output unless `--allow-sensitive` is used.
170
+ - `--allow-sensitive` is visibly marked dangerous.
171
+ - Memory and decision commands reject note text that looks like embedded credentials.
172
+
173
+ ### Platform issues
174
+
175
+ - The code primarily uses `pathlib`, UTF-8 text IO, and Typer/Rich.
176
+ - Windows-specific chmod semantics are skipped in tests.
177
+ - No high-priority Mac/Linux/Windows runtime issue was found in the reviewed code.
178
+
179
+ ### Abstractions and complexity
180
+
181
+ - The separation between scanner, summarizer, selector, pack builder, exporters, and CLI
182
+ commands is reasonable for the package size.
183
+ - Dynamic exporter/provider loading needed clearer typing; fixed.
184
+ - No large unnecessary abstraction was introduced in this pass.
185
+
186
+ ## Validation
187
+
188
+ Commands run after fixes:
189
+
190
+ ```bash
191
+ .venv/bin/pytest
192
+ .venv/bin/ruff check .
193
+ .venv/bin/mypy .
194
+ ```
195
+
196
+ Final status after hardening:
197
+
198
+ - `pytest`: 907 passed, 1 skipped
199
+ - `ruff`: passed
200
+ - `mypy`: passed, 49 source files checked
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to ContextOS are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-06-29
9
+
10
+ ### Added
11
+
12
+ - Initial ContextOS CLI with `init`, `scan`, `task`, `memory`, `pack`, and `export` commands.
13
+ - Deterministic repository scanning, summarization, dependency graphing, and task-aware context selection.
14
+ - Context pack rendering for Markdown/JSON plus Claude Code, Codex, Cursor, and Aider exporters.
15
+ - Secret filename exclusion and content redaction, with explicit `--allow-sensitive` override.
16
+ - Optional Headroom compression provider integration.
17
+ - Project memory and decision-log helpers under `.contextos/`.
18
+ - CI, release documentation, community health files, and issue/PR templates.
19
+
20
+ ### Hardened
21
+
22
+ - Exclude generated `.contextos/` outputs from future scans.
23
+ - Enforce `scan --max-files` in written scan output.
24
+ - Skip notebooks and minified generated assets by default.
25
+ - Recover from corrupted `.contextos` scan data during pack generation.
26
+ - Normalize internal scan paths for cross-platform stability.
27
+
28
+ ### Verified
29
+
30
+ - `pytest`: 907 passed, 1 skipped.
31
+ - `ruff check .`: passing.
32
+ - `mypy .`: passing.
33
+ - Local wheel build, install, and installed CLI smoke test.