nc1709 1.15.4__py3-none-any.whl
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.
- nc1709/__init__.py +13 -0
- nc1709/agent/__init__.py +36 -0
- nc1709/agent/core.py +505 -0
- nc1709/agent/mcp_bridge.py +245 -0
- nc1709/agent/permissions.py +298 -0
- nc1709/agent/tools/__init__.py +21 -0
- nc1709/agent/tools/base.py +440 -0
- nc1709/agent/tools/bash_tool.py +367 -0
- nc1709/agent/tools/file_tools.py +454 -0
- nc1709/agent/tools/notebook_tools.py +516 -0
- nc1709/agent/tools/search_tools.py +322 -0
- nc1709/agent/tools/task_tool.py +284 -0
- nc1709/agent/tools/web_tools.py +555 -0
- nc1709/agents/__init__.py +17 -0
- nc1709/agents/auto_fix.py +506 -0
- nc1709/agents/test_generator.py +507 -0
- nc1709/checkpoints.py +372 -0
- nc1709/cli.py +3380 -0
- nc1709/cli_ui.py +1080 -0
- nc1709/cognitive/__init__.py +149 -0
- nc1709/cognitive/anticipation.py +594 -0
- nc1709/cognitive/context_engine.py +1046 -0
- nc1709/cognitive/council.py +824 -0
- nc1709/cognitive/learning.py +761 -0
- nc1709/cognitive/router.py +583 -0
- nc1709/cognitive/system.py +519 -0
- nc1709/config.py +155 -0
- nc1709/custom_commands.py +300 -0
- nc1709/executor.py +333 -0
- nc1709/file_controller.py +354 -0
- nc1709/git_integration.py +308 -0
- nc1709/github_integration.py +477 -0
- nc1709/image_input.py +446 -0
- nc1709/linting.py +519 -0
- nc1709/llm_adapter.py +667 -0
- nc1709/logger.py +192 -0
- nc1709/mcp/__init__.py +18 -0
- nc1709/mcp/client.py +370 -0
- nc1709/mcp/manager.py +407 -0
- nc1709/mcp/protocol.py +210 -0
- nc1709/mcp/server.py +473 -0
- nc1709/memory/__init__.py +20 -0
- nc1709/memory/embeddings.py +325 -0
- nc1709/memory/indexer.py +474 -0
- nc1709/memory/sessions.py +432 -0
- nc1709/memory/vector_store.py +451 -0
- nc1709/models/__init__.py +86 -0
- nc1709/models/detector.py +377 -0
- nc1709/models/formats.py +315 -0
- nc1709/models/manager.py +438 -0
- nc1709/models/registry.py +497 -0
- nc1709/performance/__init__.py +343 -0
- nc1709/performance/cache.py +705 -0
- nc1709/performance/pipeline.py +611 -0
- nc1709/performance/tiering.py +543 -0
- nc1709/plan_mode.py +362 -0
- nc1709/plugins/__init__.py +17 -0
- nc1709/plugins/agents/__init__.py +18 -0
- nc1709/plugins/agents/django_agent.py +912 -0
- nc1709/plugins/agents/docker_agent.py +623 -0
- nc1709/plugins/agents/fastapi_agent.py +887 -0
- nc1709/plugins/agents/git_agent.py +731 -0
- nc1709/plugins/agents/nextjs_agent.py +867 -0
- nc1709/plugins/base.py +359 -0
- nc1709/plugins/manager.py +411 -0
- nc1709/plugins/registry.py +337 -0
- nc1709/progress.py +443 -0
- nc1709/prompts/__init__.py +22 -0
- nc1709/prompts/agent_system.py +180 -0
- nc1709/prompts/task_prompts.py +340 -0
- nc1709/prompts/unified_prompt.py +133 -0
- nc1709/reasoning_engine.py +541 -0
- nc1709/remote_client.py +266 -0
- nc1709/shell_completions.py +349 -0
- nc1709/slash_commands.py +649 -0
- nc1709/task_classifier.py +408 -0
- nc1709/version_check.py +177 -0
- nc1709/web/__init__.py +8 -0
- nc1709/web/server.py +950 -0
- nc1709/web/templates/index.html +1127 -0
- nc1709-1.15.4.dist-info/METADATA +858 -0
- nc1709-1.15.4.dist-info/RECORD +86 -0
- nc1709-1.15.4.dist-info/WHEEL +5 -0
- nc1709-1.15.4.dist-info/entry_points.txt +2 -0
- nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
- nc1709-1.15.4.dist-info/top_level.txt +1 -0
nc1709/checkpoints.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checkpoints System for NC1709
|
|
3
|
+
|
|
4
|
+
Saves file states before each change, allowing users to rewind to previous versions.
|
|
5
|
+
Similar to Claude Code's checkpoint system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import shutil
|
|
11
|
+
import hashlib
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, List, Optional, Any
|
|
15
|
+
from dataclasses import dataclass, asdict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class FileSnapshot:
|
|
20
|
+
"""Snapshot of a single file"""
|
|
21
|
+
path: str
|
|
22
|
+
content: str
|
|
23
|
+
hash: str
|
|
24
|
+
exists: bool
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_file(cls, path: str) -> "FileSnapshot":
|
|
28
|
+
"""Create a snapshot from an existing file"""
|
|
29
|
+
path_obj = Path(path)
|
|
30
|
+
if path_obj.exists():
|
|
31
|
+
try:
|
|
32
|
+
content = path_obj.read_text(encoding='utf-8')
|
|
33
|
+
file_hash = hashlib.md5(content.encode()).hexdigest()
|
|
34
|
+
return cls(path=str(path_obj.absolute()), content=content, hash=file_hash, exists=True)
|
|
35
|
+
except (UnicodeDecodeError, IOError):
|
|
36
|
+
# Binary file or read error - store empty
|
|
37
|
+
return cls(path=str(path_obj.absolute()), content="", hash="", exists=True)
|
|
38
|
+
else:
|
|
39
|
+
return cls(path=str(path_obj.absolute()), content="", hash="", exists=False)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class Checkpoint:
|
|
44
|
+
"""A checkpoint containing multiple file snapshots"""
|
|
45
|
+
id: str
|
|
46
|
+
timestamp: str
|
|
47
|
+
description: str
|
|
48
|
+
tool_name: str
|
|
49
|
+
files: Dict[str, FileSnapshot]
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert to dictionary for JSON serialization"""
|
|
53
|
+
return {
|
|
54
|
+
"id": self.id,
|
|
55
|
+
"timestamp": self.timestamp,
|
|
56
|
+
"description": self.description,
|
|
57
|
+
"tool_name": self.tool_name,
|
|
58
|
+
"files": {
|
|
59
|
+
path: asdict(snapshot)
|
|
60
|
+
for path, snapshot in self.files.items()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Checkpoint":
|
|
66
|
+
"""Create from dictionary"""
|
|
67
|
+
files = {
|
|
68
|
+
path: FileSnapshot(**snapshot_data)
|
|
69
|
+
for path, snapshot_data in data.get("files", {}).items()
|
|
70
|
+
}
|
|
71
|
+
return cls(
|
|
72
|
+
id=data["id"],
|
|
73
|
+
timestamp=data["timestamp"],
|
|
74
|
+
description=data["description"],
|
|
75
|
+
tool_name=data.get("tool_name", "unknown"),
|
|
76
|
+
files=files
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CheckpointManager:
|
|
81
|
+
"""
|
|
82
|
+
Manages checkpoints for file operations.
|
|
83
|
+
|
|
84
|
+
Creates a checkpoint before each file modification, allowing
|
|
85
|
+
users to rewind to any previous state.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, storage_dir: Optional[str] = None, max_checkpoints: int = 50):
|
|
89
|
+
"""
|
|
90
|
+
Initialize the checkpoint manager.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
storage_dir: Directory to store checkpoint data.
|
|
94
|
+
Defaults to ~/.nc1709/checkpoints/
|
|
95
|
+
max_checkpoints: Maximum number of checkpoints to keep
|
|
96
|
+
"""
|
|
97
|
+
if storage_dir:
|
|
98
|
+
self.storage_dir = Path(storage_dir)
|
|
99
|
+
else:
|
|
100
|
+
self.storage_dir = Path.home() / ".nc1709" / "checkpoints"
|
|
101
|
+
|
|
102
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
self.max_checkpoints = max_checkpoints
|
|
104
|
+
self.checkpoints: List[Checkpoint] = []
|
|
105
|
+
self.current_index: int = -1 # Points to current checkpoint
|
|
106
|
+
|
|
107
|
+
# Load existing checkpoints for current session
|
|
108
|
+
self._session_file = self.storage_dir / "current_session.json"
|
|
109
|
+
self._load_session()
|
|
110
|
+
|
|
111
|
+
def _load_session(self) -> None:
|
|
112
|
+
"""Load checkpoints from current session file"""
|
|
113
|
+
if self._session_file.exists():
|
|
114
|
+
try:
|
|
115
|
+
data = json.loads(self._session_file.read_text())
|
|
116
|
+
self.checkpoints = [
|
|
117
|
+
Checkpoint.from_dict(cp) for cp in data.get("checkpoints", [])
|
|
118
|
+
]
|
|
119
|
+
self.current_index = data.get("current_index", len(self.checkpoints) - 1)
|
|
120
|
+
except (json.JSONDecodeError, KeyError):
|
|
121
|
+
self.checkpoints = []
|
|
122
|
+
self.current_index = -1
|
|
123
|
+
|
|
124
|
+
def _save_session(self) -> None:
|
|
125
|
+
"""Save checkpoints to session file"""
|
|
126
|
+
data = {
|
|
127
|
+
"checkpoints": [cp.to_dict() for cp in self.checkpoints],
|
|
128
|
+
"current_index": self.current_index
|
|
129
|
+
}
|
|
130
|
+
self._session_file.write_text(json.dumps(data, indent=2))
|
|
131
|
+
|
|
132
|
+
def _generate_id(self) -> str:
|
|
133
|
+
"""Generate a unique checkpoint ID"""
|
|
134
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
|
135
|
+
return f"cp_{timestamp}"
|
|
136
|
+
|
|
137
|
+
def create_checkpoint(
|
|
138
|
+
self,
|
|
139
|
+
files: List[str],
|
|
140
|
+
tool_name: str = "unknown",
|
|
141
|
+
description: str = ""
|
|
142
|
+
) -> Checkpoint:
|
|
143
|
+
"""
|
|
144
|
+
Create a checkpoint before modifying files.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
files: List of file paths that will be modified
|
|
148
|
+
tool_name: Name of the tool making the modification
|
|
149
|
+
description: Human-readable description of the change
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The created Checkpoint
|
|
153
|
+
"""
|
|
154
|
+
# Snapshot all files
|
|
155
|
+
file_snapshots = {}
|
|
156
|
+
for file_path in files:
|
|
157
|
+
snapshot = FileSnapshot.from_file(file_path)
|
|
158
|
+
file_snapshots[snapshot.path] = snapshot
|
|
159
|
+
|
|
160
|
+
# Create checkpoint
|
|
161
|
+
checkpoint = Checkpoint(
|
|
162
|
+
id=self._generate_id(),
|
|
163
|
+
timestamp=datetime.now().isoformat(),
|
|
164
|
+
description=description or f"{tool_name} operation",
|
|
165
|
+
tool_name=tool_name,
|
|
166
|
+
files=file_snapshots
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# If we're not at the end (user rewound), truncate future checkpoints
|
|
170
|
+
if self.current_index < len(self.checkpoints) - 1:
|
|
171
|
+
self.checkpoints = self.checkpoints[:self.current_index + 1]
|
|
172
|
+
|
|
173
|
+
# Add checkpoint
|
|
174
|
+
self.checkpoints.append(checkpoint)
|
|
175
|
+
self.current_index = len(self.checkpoints) - 1
|
|
176
|
+
|
|
177
|
+
# Prune old checkpoints if needed
|
|
178
|
+
if len(self.checkpoints) > self.max_checkpoints:
|
|
179
|
+
self.checkpoints = self.checkpoints[-self.max_checkpoints:]
|
|
180
|
+
self.current_index = len(self.checkpoints) - 1
|
|
181
|
+
|
|
182
|
+
self._save_session()
|
|
183
|
+
return checkpoint
|
|
184
|
+
|
|
185
|
+
def rewind(self, steps: int = 1) -> Optional[Checkpoint]:
|
|
186
|
+
"""
|
|
187
|
+
Rewind to a previous checkpoint.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
steps: Number of checkpoints to go back (default: 1)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
The checkpoint that was restored, or None if can't rewind
|
|
194
|
+
"""
|
|
195
|
+
target_index = self.current_index - steps
|
|
196
|
+
|
|
197
|
+
if target_index < 0:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
return self.restore_to_index(target_index)
|
|
201
|
+
|
|
202
|
+
def restore_to_index(self, index: int) -> Optional[Checkpoint]:
|
|
203
|
+
"""
|
|
204
|
+
Restore files to state at specific checkpoint index.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
index: Checkpoint index to restore
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The checkpoint that was restored, or None if invalid
|
|
211
|
+
"""
|
|
212
|
+
if index < 0 or index >= len(self.checkpoints):
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
checkpoint = self.checkpoints[index]
|
|
216
|
+
|
|
217
|
+
# Restore all files
|
|
218
|
+
for path, snapshot in checkpoint.files.items():
|
|
219
|
+
self._restore_file(snapshot)
|
|
220
|
+
|
|
221
|
+
self.current_index = index
|
|
222
|
+
self._save_session()
|
|
223
|
+
|
|
224
|
+
return checkpoint
|
|
225
|
+
|
|
226
|
+
def restore_to_id(self, checkpoint_id: str) -> Optional[Checkpoint]:
|
|
227
|
+
"""
|
|
228
|
+
Restore files to state at specific checkpoint ID.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
checkpoint_id: ID of checkpoint to restore
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The checkpoint that was restored, or None if not found
|
|
235
|
+
"""
|
|
236
|
+
for i, cp in enumerate(self.checkpoints):
|
|
237
|
+
if cp.id == checkpoint_id:
|
|
238
|
+
return self.restore_to_index(i)
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def _restore_file(self, snapshot: FileSnapshot) -> None:
|
|
242
|
+
"""Restore a single file from snapshot"""
|
|
243
|
+
path = Path(snapshot.path)
|
|
244
|
+
|
|
245
|
+
if not snapshot.exists:
|
|
246
|
+
# File didn't exist at checkpoint - delete it
|
|
247
|
+
if path.exists():
|
|
248
|
+
path.unlink()
|
|
249
|
+
else:
|
|
250
|
+
# Restore file content
|
|
251
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
252
|
+
path.write_text(snapshot.content, encoding='utf-8')
|
|
253
|
+
|
|
254
|
+
def list_checkpoints(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
255
|
+
"""
|
|
256
|
+
List recent checkpoints.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
limit: Maximum number to return
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of checkpoint summaries
|
|
263
|
+
"""
|
|
264
|
+
result = []
|
|
265
|
+
start = max(0, len(self.checkpoints) - limit)
|
|
266
|
+
|
|
267
|
+
for i in range(start, len(self.checkpoints)):
|
|
268
|
+
cp = self.checkpoints[i]
|
|
269
|
+
result.append({
|
|
270
|
+
"index": i,
|
|
271
|
+
"id": cp.id,
|
|
272
|
+
"timestamp": cp.timestamp,
|
|
273
|
+
"description": cp.description,
|
|
274
|
+
"tool": cp.tool_name,
|
|
275
|
+
"files": list(cp.files.keys()),
|
|
276
|
+
"is_current": i == self.current_index
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
return result
|
|
280
|
+
|
|
281
|
+
def get_current_checkpoint(self) -> Optional[Checkpoint]:
|
|
282
|
+
"""Get the current checkpoint"""
|
|
283
|
+
if 0 <= self.current_index < len(self.checkpoints):
|
|
284
|
+
return self.checkpoints[self.current_index]
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
def can_rewind(self) -> bool:
|
|
288
|
+
"""Check if we can rewind"""
|
|
289
|
+
return self.current_index > 0
|
|
290
|
+
|
|
291
|
+
def can_forward(self) -> bool:
|
|
292
|
+
"""Check if we can go forward (after rewinding)"""
|
|
293
|
+
return self.current_index < len(self.checkpoints) - 1
|
|
294
|
+
|
|
295
|
+
def forward(self, steps: int = 1) -> Optional[Checkpoint]:
|
|
296
|
+
"""
|
|
297
|
+
Go forward after rewinding.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
steps: Number of checkpoints to go forward
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
The checkpoint that was restored
|
|
304
|
+
"""
|
|
305
|
+
target_index = self.current_index + steps
|
|
306
|
+
|
|
307
|
+
if target_index >= len(self.checkpoints):
|
|
308
|
+
target_index = len(self.checkpoints) - 1
|
|
309
|
+
|
|
310
|
+
return self.restore_to_index(target_index)
|
|
311
|
+
|
|
312
|
+
def clear_session(self) -> None:
|
|
313
|
+
"""Clear all checkpoints for current session"""
|
|
314
|
+
self.checkpoints = []
|
|
315
|
+
self.current_index = -1
|
|
316
|
+
if self._session_file.exists():
|
|
317
|
+
self._session_file.unlink()
|
|
318
|
+
|
|
319
|
+
def get_diff(self, checkpoint_id: str) -> Dict[str, Any]:
|
|
320
|
+
"""
|
|
321
|
+
Get diff between current state and a checkpoint.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
checkpoint_id: ID of checkpoint to compare with
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Dictionary with file changes
|
|
328
|
+
"""
|
|
329
|
+
for cp in self.checkpoints:
|
|
330
|
+
if cp.id == checkpoint_id:
|
|
331
|
+
changes = {}
|
|
332
|
+
for path, snapshot in cp.files.items():
|
|
333
|
+
current = FileSnapshot.from_file(path)
|
|
334
|
+
if current.hash != snapshot.hash:
|
|
335
|
+
changes[path] = {
|
|
336
|
+
"had_content": snapshot.exists,
|
|
337
|
+
"has_content": current.exists,
|
|
338
|
+
"changed": True
|
|
339
|
+
}
|
|
340
|
+
return {"checkpoint": checkpoint_id, "changes": changes}
|
|
341
|
+
|
|
342
|
+
return {"error": "Checkpoint not found"}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# Global checkpoint manager instance
|
|
346
|
+
_checkpoint_manager: Optional[CheckpointManager] = None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def get_checkpoint_manager() -> CheckpointManager:
|
|
350
|
+
"""Get or create the global checkpoint manager"""
|
|
351
|
+
global _checkpoint_manager
|
|
352
|
+
if _checkpoint_manager is None:
|
|
353
|
+
_checkpoint_manager = CheckpointManager()
|
|
354
|
+
return _checkpoint_manager
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def checkpoint_before_edit(file_path: str, tool_name: str = "Edit") -> Checkpoint:
|
|
358
|
+
"""Convenience function to create checkpoint before editing a file"""
|
|
359
|
+
return get_checkpoint_manager().create_checkpoint(
|
|
360
|
+
files=[file_path],
|
|
361
|
+
tool_name=tool_name,
|
|
362
|
+
description=f"Before editing {Path(file_path).name}"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def checkpoint_before_write(file_path: str, tool_name: str = "Write") -> Checkpoint:
|
|
367
|
+
"""Convenience function to create checkpoint before writing a file"""
|
|
368
|
+
return get_checkpoint_manager().create_checkpoint(
|
|
369
|
+
files=[file_path],
|
|
370
|
+
tool_name=tool_name,
|
|
371
|
+
description=f"Before writing {Path(file_path).name}"
|
|
372
|
+
)
|