synap-git 2.1.5__tar.gz → 2.1.7__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.
- {synap_git-2.1.5 → synap_git-2.1.7}/.gitignore +4 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/CHANGELOG.md +22 -1
- {synap_git-2.1.5 → synap_git-2.1.7}/PKG-INFO +20 -18
- {synap_git-2.1.5 → synap_git-2.1.7}/README.md +15 -13
- {synap_git-2.1.5 → synap_git-2.1.7}/pyproject.toml +4 -4
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/__init__.py +1 -1
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/api/app.py +1 -1
- synap_git-2.1.7/src/synap_git/cli/checkpoint.py +125 -0
- synap_git-2.1.7/src/synap_git/cli/lessons.py +132 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/cli/main.py +126 -83
- synap_git-2.1.7/src/synap_git/cli/memory.py +114 -0
- synap_git-2.1.7/src/synap_git/cli/usage.py +88 -0
- synap_git-2.1.7/src/synap_git/cli/wiki.py +86 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/config.py +7 -6
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/diagnostics/logger.py +4 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/diagnostics/tracing.py +2 -4
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/git/state.py +1 -1
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/indexer/daemon.py +4 -61
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/indexer/engine.py +148 -120
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/indexer/scanner.py +31 -1
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/indexer/wiki.py +2 -2
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/mcp/server.py +2 -6
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/parser/registry.py +33 -36
- synap_git-2.1.7/src/synap_git/provider/__init__.py +1 -0
- synap_git-2.1.7/src/synap_git/provider/factory.py +165 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/retrieval/engine.py +12 -7
- synap_git-2.1.7/src/synap_git/security/__init__.py +5 -0
- synap_git-2.1.7/src/synap_git/security/redactor.py +56 -0
- synap_git-2.1.7/src/synap_git/security/sanitizer.py +29 -0
- synap_git-2.1.7/src/synap_git/security/validator.py +33 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/storage/sqlite.py +40 -4
- synap_git-2.1.7/website/README.md +36 -0
- synap_git-2.1.5/src/synap_git/provider/factory.py +0 -107
- {synap_git-2.1.5 → synap_git-2.1.7}/.synap.example/README.md +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/LICENSE.md +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/api/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/api/static/index.html +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/cli/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/cli/__main__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/diagnostics/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/embeddings/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/git/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/indexer/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/mcp/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/parser/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/anthropic.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/base.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/gemini.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/ollama.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/openai.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/provider/openrouter.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/py.typed +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/retrieval/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/retrieval/memory.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/storage/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/utils/__init__.py +0 -0
- {synap_git-2.1.5 → synap_git-2.1.7}/src/synap_git/utils/serialization.py +0 -0
|
@@ -5,7 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [2.1.
|
|
8
|
+
## [2.1.7] - 2026-07-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **AI Engineer Rules**: Created standardized agent rulesets across `AGENTS.md`, `.cursorrules`, `.windsurfrules`, and `.github/copilot-instructions.md`.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Daemon Branch Switching Test Stability**: Resolved timing race condition in daemon branch checkout verification.
|
|
15
|
+
- **CLI Setup Connectivity**: Fixed setup connectivity verification and non-TTY prompt fallback handling.
|
|
16
|
+
|
|
17
|
+
## [2.1.6] - 2026-06-29
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Universal Language Expansion**: Expanded tree-sitter AST parsing support from 4 hardcoded languages to dynamically supporting over ~50 languages using `tree-sitter-languages`. Also added dynamic shebang detection (`#!`) to parse extensionless scripts as python, ruby, node, and bash scripts.
|
|
21
|
+
- **API Rate Limiting**: Implemented a global `RateLimitedProvider` to throttle LLM and embedding calls to 3 requests per second to mitigate resource exhaustion on the diagnostic API.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Broad Exception Handling**: Eradicated remaining `except Exception:` blocks across diagnostics, daemon execution, indexer scanning, and module resolution, and replaced them with `OSError`, `sqlite3.Error`, and specific errors to prevent silent data corruption.
|
|
25
|
+
- **Async Sleep Compatibility**: Fixed thread-blocking `time.sleep()` in LLM retries by delegating to `anyio.from_thread.run(anyio.sleep)` if invoked inside FastMCP worker threads.
|
|
26
|
+
- **Edge Resolution & Source Paths**: Added support for resolving Javascript, Java, C++, Ruby, and more during import/edge resolution.
|
|
27
|
+
- **Module Path Trimming**: Improved module path mapping by trimming `internal/`, `source/`, `packages/`, `main/`, `java/`, `kotlin/`, `scala/` from module keys.
|
|
28
|
+
- **Wipe Confirmation**: Added a `--yes` confirmation bypass flag for `synap wipe` and enforced safety confirmation in non-interactive terminals.
|
|
29
|
+
- **Metadata Fixes**: Corrected repository URLs in `pyproject.toml` to correctly point to `synapse` instead of `synap-git`.
|
|
9
30
|
|
|
10
31
|
### Refactored
|
|
11
32
|
- **Daemon Teardown**: Refactored the forceful SIGTERM daemon teardown logic to safely toggle via `force_exit=True` rather than a blanket catch-all. Makes programmatic `daemon.stop()` safer without blocking terminal UI interactions.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synap-git
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.7
|
|
4
4
|
Summary: Persistent structural context infrastructure for AI coding agents.
|
|
5
|
-
Project-URL: Homepage, https://github.com/saahilpal/
|
|
6
|
-
Project-URL: Repository, https://github.com/saahilpal/
|
|
7
|
-
Project-URL: Issues, https://github.com/saahilpal/
|
|
8
|
-
Project-URL: Changelog, https://github.com/saahilpal/
|
|
5
|
+
Project-URL: Homepage, https://github.com/saahilpal/synapse
|
|
6
|
+
Project-URL: Repository, https://github.com/saahilpal/synapse
|
|
7
|
+
Project-URL: Issues, https://github.com/saahilpal/synapse/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/saahilpal/synapse/blob/main/CHANGELOG.md
|
|
9
9
|
Author: Synap contributors
|
|
10
10
|
License: Apache-2.0
|
|
11
11
|
License-File: LICENSE.md
|
|
@@ -339,8 +339,8 @@ Initialize the local SQLite database index:
|
|
|
339
339
|
synap init .
|
|
340
340
|
```
|
|
341
341
|
|
|
342
|
-
### 3.
|
|
343
|
-
|
|
342
|
+
### 3. Connect IDE / Start MCP
|
|
343
|
+
Launch the unified background daemon and MCP stdio server:
|
|
344
344
|
```bash
|
|
345
345
|
synap start .
|
|
346
346
|
```
|
|
@@ -372,7 +372,6 @@ All commands support target path arguments:
|
|
|
372
372
|
* `synap doctor [PATH]` — Verifies database integrity and parsers.
|
|
373
373
|
* `synap run [PATH]` — Runs daemon in foreground.
|
|
374
374
|
* `synap ui [PATH]` — Launches HTML dashboard.
|
|
375
|
-
* `synap mcp start [PATH]` — Hosts MCP stdio server.
|
|
376
375
|
* `synap mcp config [PATH]` — Outputs Cursor connection blocks.
|
|
377
376
|
* `synap mcp verify [PATH]` — Asserts MCP server stability.
|
|
378
377
|
* `synap memory status [PATH]` — Lists lesson counts.
|
|
@@ -432,8 +431,7 @@ synap mcp config .
|
|
|
432
431
|
"mcpServers": {
|
|
433
432
|
"synap": {
|
|
434
433
|
"command": "/usr/local/bin/python",
|
|
435
|
-
"args": ["-m", "synap_git.cli", "
|
|
436
|
-
"autoConnect": true
|
|
434
|
+
"args": ["-m", "synap_git.cli", "start", "/Users/username/repo"]
|
|
437
435
|
}
|
|
438
436
|
}
|
|
439
437
|
}
|
|
@@ -443,15 +441,19 @@ synap mcp config .
|
|
|
443
441
|
|
|
444
442
|
## Supported Languages
|
|
445
443
|
|
|
446
|
-
Synap processes syntax for
|
|
444
|
+
Synap now processes syntax dynamically for **over 50 programming languages** via `tree-sitter-languages`, including:
|
|
445
|
+
|
|
447
446
|
* **Python** (`.py`)
|
|
448
|
-
* **JavaScript /
|
|
449
|
-
* **
|
|
450
|
-
* **
|
|
451
|
-
* **Rust** (`.rs`)
|
|
452
|
-
* **
|
|
453
|
-
* **
|
|
454
|
-
* **
|
|
447
|
+
* **JavaScript / TypeScript** (`.js`, `.jsx`, `.ts`, `.tsx`)
|
|
448
|
+
* **C / C++ / C#** (`.c`, `.cpp`, `.h`, `.hpp`, `.cs`)
|
|
449
|
+
* **Java / Kotlin / Scala** (`.java`, `.kt`, `.kts`, `.scala`)
|
|
450
|
+
* **Go / Rust / Swift** (`.go`, `.rs`, `.swift`)
|
|
451
|
+
* **Ruby / PHP / Perl** (`.rb`, `.php`, `.pl`)
|
|
452
|
+
* **Shell Scripts** (`.sh`, `.bash`, or via `#!` shebang detection)
|
|
453
|
+
* **Data / Config** (`.sql`, `.json`, `.yaml`, `.toml`, `.xml`)
|
|
454
|
+
* **Web** (`.html`, `.css`, `.scss`)
|
|
455
|
+
|
|
456
|
+
Even extensionless scripts are gracefully detected via shebang parsing. Missing grammars automatically degrade gracefully to plaintext indexing.
|
|
455
457
|
|
|
456
458
|
---
|
|
457
459
|
|
|
@@ -289,8 +289,8 @@ Initialize the local SQLite database index:
|
|
|
289
289
|
synap init .
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
-
### 3.
|
|
293
|
-
|
|
292
|
+
### 3. Connect IDE / Start MCP
|
|
293
|
+
Launch the unified background daemon and MCP stdio server:
|
|
294
294
|
```bash
|
|
295
295
|
synap start .
|
|
296
296
|
```
|
|
@@ -322,7 +322,6 @@ All commands support target path arguments:
|
|
|
322
322
|
* `synap doctor [PATH]` — Verifies database integrity and parsers.
|
|
323
323
|
* `synap run [PATH]` — Runs daemon in foreground.
|
|
324
324
|
* `synap ui [PATH]` — Launches HTML dashboard.
|
|
325
|
-
* `synap mcp start [PATH]` — Hosts MCP stdio server.
|
|
326
325
|
* `synap mcp config [PATH]` — Outputs Cursor connection blocks.
|
|
327
326
|
* `synap mcp verify [PATH]` — Asserts MCP server stability.
|
|
328
327
|
* `synap memory status [PATH]` — Lists lesson counts.
|
|
@@ -382,8 +381,7 @@ synap mcp config .
|
|
|
382
381
|
"mcpServers": {
|
|
383
382
|
"synap": {
|
|
384
383
|
"command": "/usr/local/bin/python",
|
|
385
|
-
"args": ["-m", "synap_git.cli", "
|
|
386
|
-
"autoConnect": true
|
|
384
|
+
"args": ["-m", "synap_git.cli", "start", "/Users/username/repo"]
|
|
387
385
|
}
|
|
388
386
|
}
|
|
389
387
|
}
|
|
@@ -393,15 +391,19 @@ synap mcp config .
|
|
|
393
391
|
|
|
394
392
|
## Supported Languages
|
|
395
393
|
|
|
396
|
-
Synap processes syntax for
|
|
394
|
+
Synap now processes syntax dynamically for **over 50 programming languages** via `tree-sitter-languages`, including:
|
|
395
|
+
|
|
397
396
|
* **Python** (`.py`)
|
|
398
|
-
* **JavaScript /
|
|
399
|
-
* **
|
|
400
|
-
* **
|
|
401
|
-
* **Rust** (`.rs`)
|
|
402
|
-
* **
|
|
403
|
-
* **
|
|
404
|
-
* **
|
|
397
|
+
* **JavaScript / TypeScript** (`.js`, `.jsx`, `.ts`, `.tsx`)
|
|
398
|
+
* **C / C++ / C#** (`.c`, `.cpp`, `.h`, `.hpp`, `.cs`)
|
|
399
|
+
* **Java / Kotlin / Scala** (`.java`, `.kt`, `.kts`, `.scala`)
|
|
400
|
+
* **Go / Rust / Swift** (`.go`, `.rs`, `.swift`)
|
|
401
|
+
* **Ruby / PHP / Perl** (`.rb`, `.php`, `.pl`)
|
|
402
|
+
* **Shell Scripts** (`.sh`, `.bash`, or via `#!` shebang detection)
|
|
403
|
+
* **Data / Config** (`.sql`, `.json`, `.yaml`, `.toml`, `.xml`)
|
|
404
|
+
* **Web** (`.html`, `.css`, `.scss`)
|
|
405
|
+
|
|
406
|
+
Even extensionless scripts are gracefully detected via shebang parsing. Missing grammars automatically degrade gracefully to plaintext indexing.
|
|
405
407
|
|
|
406
408
|
---
|
|
407
409
|
|
|
@@ -40,10 +40,10 @@ dependencies = [
|
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[project.urls]
|
|
43
|
-
Homepage = "https://github.com/saahilpal/
|
|
44
|
-
Repository = "https://github.com/saahilpal/
|
|
45
|
-
Issues = "https://github.com/saahilpal/
|
|
46
|
-
Changelog = "https://github.com/saahilpal/
|
|
43
|
+
Homepage = "https://github.com/saahilpal/synapse"
|
|
44
|
+
Repository = "https://github.com/saahilpal/synapse"
|
|
45
|
+
Issues = "https://github.com/saahilpal/synapse/issues"
|
|
46
|
+
Changelog = "https://github.com/saahilpal/synapse/blob/main/CHANGELOG.md"
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
[project.optional-dependencies]
|
|
@@ -104,7 +104,7 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
104
104
|
except Exception as e:
|
|
105
105
|
import structlog
|
|
106
106
|
|
|
107
|
-
structlog.get_logger().error("suppressed_error_caught", error=str(e)
|
|
107
|
+
structlog.get_logger().error("suppressed_error_caught", error=str(e))
|
|
108
108
|
|
|
109
109
|
wiki_path = runtime.wiki.wiki_dir / f"{target}.md"
|
|
110
110
|
exists = await asyncio.to_thread(wiki_path.exists)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from synap_git.cli.main import (
|
|
12
|
+
_settings,
|
|
13
|
+
console,
|
|
14
|
+
)
|
|
15
|
+
from synap_git.indexer.engine import SynapRuntime
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(help="Checkpoint commands.", no_args_is_help=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command("create")
|
|
21
|
+
def checkpoint_create(
|
|
22
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
23
|
+
doing: Annotated[
|
|
24
|
+
str, typer.Option(help="What the agent is currently doing.")
|
|
25
|
+
] = "Manual snapshot",
|
|
26
|
+
files: Annotated[str, typer.Option(help="Comma-separated list of changed files.")] = "",
|
|
27
|
+
next_step: Annotated[str, typer.Option(help="The next step to be taken.")] = "",
|
|
28
|
+
blockers: Annotated[str, typer.Option(help="Current blockers or obstacles.")] = "",
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Create a new context checkpoint."""
|
|
31
|
+
runtime = SynapRuntime(_settings(path))
|
|
32
|
+
|
|
33
|
+
checkpoint_id = str(uuid.uuid4())
|
|
34
|
+
status = runtime.status()
|
|
35
|
+
branch = status.branch
|
|
36
|
+
commit = status.git_commit or "unknown"
|
|
37
|
+
|
|
38
|
+
file_list = [f.strip() for f in files.split(",") if f.strip()]
|
|
39
|
+
|
|
40
|
+
runtime.store.put_checkpoint(
|
|
41
|
+
checkpoint_id=checkpoint_id,
|
|
42
|
+
branch=branch,
|
|
43
|
+
commit_hash=commit,
|
|
44
|
+
doing=doing,
|
|
45
|
+
changed_files=json.dumps(file_list),
|
|
46
|
+
next_step=next_step,
|
|
47
|
+
blockers=blockers,
|
|
48
|
+
)
|
|
49
|
+
console.print(f"[green]✓ Checkpoint {checkpoint_id} created for branch '{branch}'.[/green]")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command("list")
|
|
53
|
+
def checkpoint_list(
|
|
54
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
55
|
+
) -> None:
|
|
56
|
+
"""List checkpoints for the active branch."""
|
|
57
|
+
runtime = SynapRuntime(_settings(path))
|
|
58
|
+
status = runtime.status()
|
|
59
|
+
branch = status.branch
|
|
60
|
+
cps = runtime.store.get_checkpoints(branch)
|
|
61
|
+
|
|
62
|
+
if not cps:
|
|
63
|
+
console.print(f"[yellow]No checkpoints found for branch '{branch}'.[/yellow]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
table = Table(
|
|
67
|
+
title=f"Checkpoints for branch '{branch}'", show_header=True, header_style="bold cyan"
|
|
68
|
+
)
|
|
69
|
+
table.add_column("Checkpoint ID")
|
|
70
|
+
table.add_column("Commit Hash")
|
|
71
|
+
table.add_column("Doing")
|
|
72
|
+
table.add_column("Changed Files")
|
|
73
|
+
table.add_column("Created At")
|
|
74
|
+
|
|
75
|
+
for cp in cps:
|
|
76
|
+
created = datetime.fromtimestamp(cp["created_at"]).isoformat()
|
|
77
|
+
try:
|
|
78
|
+
ch_files = ", ".join(json.loads(cp["changed_files"]))
|
|
79
|
+
except Exception:
|
|
80
|
+
ch_files = cp["changed_files"]
|
|
81
|
+
table.add_row(
|
|
82
|
+
cp["checkpoint_id"][:16] + "…",
|
|
83
|
+
cp["commit_hash"][:7],
|
|
84
|
+
cp["doing"],
|
|
85
|
+
ch_files if ch_files else "None",
|
|
86
|
+
created,
|
|
87
|
+
)
|
|
88
|
+
console.print(table)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command("restore")
|
|
92
|
+
def checkpoint_restore(
|
|
93
|
+
checkpoint_id: Annotated[
|
|
94
|
+
str, typer.Argument(help="The ID of the checkpoint to restore (defaults to latest).")
|
|
95
|
+
] = "latest",
|
|
96
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Restore and show details of a checkpoint."""
|
|
99
|
+
runtime = SynapRuntime(_settings(path))
|
|
100
|
+
status = runtime.status()
|
|
101
|
+
branch = status.branch
|
|
102
|
+
|
|
103
|
+
if checkpoint_id == "latest":
|
|
104
|
+
cp = runtime.store.get_latest_checkpoint(branch)
|
|
105
|
+
else:
|
|
106
|
+
cp = runtime.store.get_checkpoint(checkpoint_id)
|
|
107
|
+
|
|
108
|
+
if not cp:
|
|
109
|
+
console.print(f"[red]✗ Checkpoint '{checkpoint_id}' not found.[/red]")
|
|
110
|
+
raise typer.Exit(1)
|
|
111
|
+
|
|
112
|
+
console.print(f"[bold green]✓ Checkpoint '{cp['checkpoint_id']}' details:[/bold green]")
|
|
113
|
+
console.print(f" [bold]Branch:[/bold] {cp['branch']}")
|
|
114
|
+
console.print(f" [bold]Commit:[/bold] {cp['commit_hash']}")
|
|
115
|
+
console.print(f" [bold]Doing:[/bold] {cp['doing']}")
|
|
116
|
+
try:
|
|
117
|
+
ch_files = ", ".join(json.loads(cp["changed_files"]))
|
|
118
|
+
except Exception:
|
|
119
|
+
ch_files = cp["changed_files"]
|
|
120
|
+
console.print(f" [bold]Changed Files:[/bold] {ch_files if ch_files else 'None'}")
|
|
121
|
+
console.print(f" [bold]Next Step:[/bold] {cp['next_step'] or 'None'}")
|
|
122
|
+
console.print(f" [bold]Blockers:[/bold] {cp['blockers'] or 'None'}")
|
|
123
|
+
console.print(
|
|
124
|
+
f" [bold]Created At:[/bold] {datetime.fromtimestamp(cp['created_at']).isoformat()}"
|
|
125
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
|
|
9
|
+
from synap_git.cli.main import (
|
|
10
|
+
_settings,
|
|
11
|
+
console,
|
|
12
|
+
)
|
|
13
|
+
from synap_git.indexer.engine import SynapRuntime
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Lessons commands.", no_args_is_help=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command("approve")
|
|
19
|
+
def lessons_approve(
|
|
20
|
+
lesson_id: Annotated[str, typer.Argument(help="The ID of the lesson to approve.")],
|
|
21
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Approve a pending lesson."""
|
|
24
|
+
runtime = SynapRuntime(_settings(path))
|
|
25
|
+
# Fetch to ensure it exists and is pending
|
|
26
|
+
pending = runtime.store.get_lessons("pending")
|
|
27
|
+
target = next((lesson for lesson in pending if lesson["lesson_id"] == lesson_id), None)
|
|
28
|
+
|
|
29
|
+
if not target:
|
|
30
|
+
console.print(f"[red]✗ Pending lesson {lesson_id} not found.[/red]")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
runtime.store.update_lesson(lesson_id, target["why_failed"], "approved", actor="cli_user")
|
|
34
|
+
console.print(f"[green]✓ Lesson {lesson_id} approved. Memory updated.[/green]")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("review")
|
|
38
|
+
def lessons_review(
|
|
39
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Interactively review and manage pending lessons."""
|
|
42
|
+
|
|
43
|
+
runtime = SynapRuntime(_settings(path))
|
|
44
|
+
pending = runtime.store.get_lessons("pending")
|
|
45
|
+
|
|
46
|
+
if not pending:
|
|
47
|
+
console.print("[yellow]No pending lessons to review.[/yellow]")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
console.print(f"[bold cyan]Reviewing {len(pending)} pending lessons...[/bold cyan]\n")
|
|
51
|
+
|
|
52
|
+
approved_count = 0
|
|
53
|
+
rejected_count = 0
|
|
54
|
+
skipped_count = 0
|
|
55
|
+
|
|
56
|
+
for lesson in pending:
|
|
57
|
+
lesson_id = lesson["lesson_id"]
|
|
58
|
+
|
|
59
|
+
# Build detail view
|
|
60
|
+
details = (
|
|
61
|
+
f"[bold]Revert Commit:[/bold] {lesson['revert_commit'][:8]}\n"
|
|
62
|
+
f"[bold]Reverted From:[/bold] {lesson['reverted_from'][:8]}\n"
|
|
63
|
+
f"[bold]Files Affected:[/bold] {lesson['files_affected']}\n"
|
|
64
|
+
f"[bold]What Failed:[/bold] {lesson['what_failed']}\n"
|
|
65
|
+
f"[bold]Proposed Lesson:[/bold] {lesson['why_failed']}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
console.print(Panel(details, title=f"Lesson {lesson_id[:8]}", expand=False))
|
|
69
|
+
|
|
70
|
+
choice = questionary.select(
|
|
71
|
+
"Action:",
|
|
72
|
+
choices=[
|
|
73
|
+
questionary.Choice("Approve", value="approve"),
|
|
74
|
+
questionary.Choice("Edit & Approve", value="edit"),
|
|
75
|
+
questionary.Choice("Reject", value="reject"),
|
|
76
|
+
questionary.Choice("Skip", value="skip"),
|
|
77
|
+
],
|
|
78
|
+
).ask()
|
|
79
|
+
|
|
80
|
+
if choice == "approve":
|
|
81
|
+
runtime.store.update_lesson(
|
|
82
|
+
lesson_id, lesson["why_failed"], "approved", actor="cli_user"
|
|
83
|
+
)
|
|
84
|
+
console.print("[green]✓ Approved.[/green]\n")
|
|
85
|
+
approved_count += 1
|
|
86
|
+
elif choice == "edit":
|
|
87
|
+
edited = questionary.text("Edit lesson text:", default=lesson["why_failed"]).ask()
|
|
88
|
+
if edited:
|
|
89
|
+
runtime.store.update_lesson(lesson_id, edited, "approved", actor="cli_user")
|
|
90
|
+
console.print("[green]✓ Edited and Approved.[/green]\n")
|
|
91
|
+
approved_count += 1
|
|
92
|
+
else:
|
|
93
|
+
console.print("[yellow]Skipping edit.[/yellow]\n")
|
|
94
|
+
skipped_count += 1
|
|
95
|
+
elif choice == "reject":
|
|
96
|
+
runtime.store.update_lesson(
|
|
97
|
+
lesson_id, lesson["why_failed"], "rejected", actor="cli_user"
|
|
98
|
+
)
|
|
99
|
+
console.print("[red]✗ Rejected.[/red]\n")
|
|
100
|
+
rejected_count += 1
|
|
101
|
+
elif choice == "skip":
|
|
102
|
+
console.print("[yellow]Skipped.[/yellow]\n")
|
|
103
|
+
skipped_count += 1
|
|
104
|
+
else:
|
|
105
|
+
console.print("[yellow]Aborting review.[/yellow]")
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
console.print("[bold cyan]Review Summary:[/bold cyan]")
|
|
109
|
+
console.print(f" [green]Approved:[/green] {approved_count}")
|
|
110
|
+
console.print(f" [red]Rejected:[/red] {rejected_count}")
|
|
111
|
+
console.print(f" [yellow]Skipped:[/yellow] {skipped_count}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command("reject")
|
|
115
|
+
def lessons_reject(
|
|
116
|
+
lesson_id: Annotated[str, typer.Argument(help="The ID of the lesson to reject.")],
|
|
117
|
+
path: Annotated[str, typer.Argument(help="Repository path.")] = ".",
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Reject a pending or approved lesson."""
|
|
120
|
+
runtime = SynapRuntime(_settings(path))
|
|
121
|
+
pending = runtime.store.get_lessons("pending")
|
|
122
|
+
approved = runtime.store.get_lessons("approved")
|
|
123
|
+
target = next(
|
|
124
|
+
(lesson for lesson in pending + approved if lesson["lesson_id"] == lesson_id), None
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not target:
|
|
128
|
+
console.print(f"[red]✗ Lesson {lesson_id} not found in pending or approved queues.[/red]")
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
|
|
131
|
+
runtime.store.update_lesson(lesson_id, target["why_failed"], "rejected", actor="cli_user")
|
|
132
|
+
console.print(f"[yellow]✓ Lesson {lesson_id} rejected. Memory updated.[/yellow]")
|