sin-code-bundle 0.9.2__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.
Files changed (41) hide show
  1. sin_code_bundle/__init__.py +6 -0
  2. sin_code_bundle/agents_md.py +245 -0
  3. sin_code_bundle/ast_edit.py +323 -0
  4. sin_code_bundle/bench.py +506 -0
  5. sin_code_bundle/budget.py +51 -0
  6. sin_code_bundle/cache.py +131 -0
  7. sin_code_bundle/checkpoint.py +230 -0
  8. sin_code_bundle/cli.py +1943 -0
  9. sin_code_bundle/codocs.py +328 -0
  10. sin_code_bundle/dap_bridge.py +135 -0
  11. sin_code_bundle/data/codocs/SKILL.md +280 -0
  12. sin_code_bundle/gitnexus.py +368 -0
  13. sin_code_bundle/hashline.py +216 -0
  14. sin_code_bundle/hooks.py +249 -0
  15. sin_code_bundle/immortal_commit.py +288 -0
  16. sin_code_bundle/interceptor.py +119 -0
  17. sin_code_bundle/lsp_backend.py +303 -0
  18. sin_code_bundle/lsp_bootstrap.py +85 -0
  19. sin_code_bundle/markitdown.py +254 -0
  20. sin_code_bundle/mcp_config.py +455 -0
  21. sin_code_bundle/mcp_server.py +963 -0
  22. sin_code_bundle/memory.py +208 -0
  23. sin_code_bundle/merge_safety.py +313 -0
  24. sin_code_bundle/orchestration_worktrees.py +102 -0
  25. sin_code_bundle/policy.py +224 -0
  26. sin_code_bundle/preflight.py +152 -0
  27. sin_code_bundle/programming_workflow.py +541 -0
  28. sin_code_bundle/rtk.py +154 -0
  29. sin_code_bundle/safety.py +52 -0
  30. sin_code_bundle/session_warmup.py +247 -0
  31. sin_code_bundle/skills.py +188 -0
  32. sin_code_bundle/symbol_resolve.py +166 -0
  33. sin_code_bundle/tools/__init__.py +4 -0
  34. sin_code_bundle/tools/pypi_setup.py +289 -0
  35. sin_code_bundle/vfs.py +264 -0
  36. sin_code_bundle-0.9.2.dist-info/METADATA +470 -0
  37. sin_code_bundle-0.9.2.dist-info/RECORD +41 -0
  38. sin_code_bundle-0.9.2.dist-info/WHEEL +5 -0
  39. sin_code_bundle-0.9.2.dist-info/entry_points.txt +4 -0
  40. sin_code_bundle-0.9.2.dist-info/licenses/LICENSE +21 -0
  41. sin_code_bundle-0.9.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,288 @@
1
+ """Purpose: One-call immortal commit — conventional commit + tag + push.
2
+
3
+ Docs: immortal_commit.doc.md
4
+
5
+ Wraps the git-immortal-commit ritual into a single MCP tool call.
6
+ Validates Conventional Commits format, ensures we are on main (NEVER a
7
+ branch), creates the commit, optionally tags + pushes, and returns a
8
+ structured result.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ # Conventional Commit format: type(scope): subject (subject >= 5 chars).
22
+ # Permitted types mirror the git-immortal-commit skill (AGENTS.md) — adding
23
+ # a new type here means adding it to that skill as well.
24
+ _CC_PATTERN = re.compile(
25
+ r"^(feat|fix|docs|chore|style|test|refactor|perf|ci|build)"
26
+ r"(\([^)]+\))?"
27
+ r": .{5,}"
28
+ )
29
+
30
+ # Substrings that should NEVER appear in a commit message (would be a
31
+ # leaked secret in the public history).
32
+ _SECRET_HINTS = (
33
+ "BEGIN RSA PRIVATE KEY",
34
+ "BEGIN OPENSSH PRIVATE KEY",
35
+ "BEGIN PRIVATE KEY",
36
+ "sk-", # OpenAI / many SaaS keys
37
+ "ghp_", # GitHub PAT
38
+ "github_pat_", # GitHub fine-grained PAT
39
+ "xoxb-",
40
+ "xoxp-", # Slack tokens
41
+ "AIza", # Google API keys
42
+ "AKIA", # AWS access key
43
+ "ASIA",
44
+ )
45
+
46
+ # Main-branch names. Repository-specific overrides (e.g. "master") are
47
+ # detected at runtime when the user passes a custom name.
48
+ _DEFAULT_MAIN = "main"
49
+
50
+ # Hard-coded fallback for the dev-machine layout (AGENTS.md). The MCP
51
+ # stdio process inherits a stripped PATH in some envs, so we look up
52
+ # the rollback CLI at well-known locations first.
53
+ _ROLLBACK_FALLBACK = "/Users/jeremy/Library/Python/3.14/bin/sin-honcho-rollback"
54
+
55
+
56
+ class ImmortalCommitter:
57
+ """One-call commit/tag/push with all safety checks applied."""
58
+
59
+ def __init__(self, repo_root: Optional[Path] = None) -> None:
60
+ self.repo_root = Path(repo_root) if repo_root else Path.cwd()
61
+
62
+ def commit(
63
+ self,
64
+ message: str,
65
+ tag: str = "",
66
+ push: bool = True,
67
+ force_main: bool = True,
68
+ main_branch: str = _DEFAULT_MAIN,
69
+ snapshot_first: bool = True,
70
+ ) -> Dict[str, Any]:
71
+ """Run the full immortal-commit ritual.
72
+
73
+ Args:
74
+ message: Conventional Commits message (``feat(scope): subject``).
75
+ tag: optional annotated tag name (e.g. ``v0.8.0``).
76
+ push: if True, push commit (and tag) to ``origin/<main_branch>``.
77
+ force_main: if True, refuse to run on any branch other than main.
78
+ main_branch: which branch counts as ``main`` (default ``main``).
79
+
80
+ Returns:
81
+ Dict with ``success``, ``sha``, ``tag``, ``pushed``, ``branch``,
82
+ ``warnings`` and ``steps`` (per-step status) — always returns
83
+ (never raises) so the caller can surface a useful error.
84
+ """
85
+ result: Dict[str, Any] = {
86
+ "success": False,
87
+ "message": message,
88
+ "tag": tag or None,
89
+ "pushed": False,
90
+ "branch": None,
91
+ "sha": None,
92
+ "warnings": [],
93
+ "steps": [],
94
+ "snapshot": None,
95
+ "timestamp": datetime.now(timezone.utc).isoformat(),
96
+ }
97
+
98
+ # ── Step 0 (optional): Pre-commit snapshot ─────────────────────
99
+ # Lets the user roll back to the pre-commit state if the change
100
+ # later turns out to be broken. Independent of the commit itself.
101
+ if snapshot_first:
102
+ snap_name = (
103
+ f"pre-commit-{(message[:24].replace(' ', '-').replace(':', '').lower() or 'auto')}"
104
+ )
105
+ snap = self._create_snapshot(snap_name, message)
106
+ result["snapshot"] = snap
107
+ if snap.get("ok"):
108
+ result["steps"].append(
109
+ {"step": "snapshot", "ok": True, "id": snap.get("snapshot_id")}
110
+ )
111
+ else:
112
+ # Snapshot is best-effort; log but don't fail the commit.
113
+ result["steps"].append(
114
+ {"step": "snapshot", "ok": False, "warning": snap.get("error")}
115
+ )
116
+
117
+ # ── Step 1: Validate Conventional Commits format ─────────────
118
+ if not _CC_PATTERN.match(message):
119
+ result["error"] = (
120
+ "Not a Conventional Commits message. "
121
+ "Required: type(scope): subject (subject >= 5 chars). "
122
+ "Valid types: feat, fix, docs, chore, style, test, refactor, perf, ci, build."
123
+ )
124
+ result["steps"].append({"step": "validate_format", "ok": False})
125
+ return result
126
+ result["steps"].append({"step": "validate_format", "ok": True})
127
+
128
+ # ── Step 2: Scan message for secrets (cheap, not exhaustive) ──
129
+ secret_hits = [s for s in _SECRET_HINTS if s in message]
130
+ if secret_hits:
131
+ result["error"] = f"Possible secret material in commit message: {secret_hits}"
132
+ result["steps"].append({"step": "secret_scan", "ok": False})
133
+ return result
134
+ result["steps"].append({"step": "secret_scan", "ok": True})
135
+
136
+ # ── Step 3: Detect current branch ─────────────────────────────
137
+ branch = self._git(["branch", "--show-current"], default="").strip()
138
+ result["branch"] = branch
139
+
140
+ if force_main and branch != main_branch:
141
+ result["error"] = (
142
+ f"Refusing to commit: on branch '{branch}', expected '{main_branch}'. "
143
+ "Per the NEVER-BRANCHES mandate, switch to main first: "
144
+ f"`git checkout {main_branch} && git pull origin {main_branch}`."
145
+ )
146
+ result["steps"].append({"step": "branch_check", "ok": False})
147
+ return result
148
+ result["steps"].append({"step": "branch_check", "ok": True, "branch": branch})
149
+
150
+ # ── Step 4: Working-tree dirty? Warn if so, but still proceed ─
151
+ # Per skill: "agents are autonomous, stop blocking on dirty tree,
152
+ # but flag it so the user can see what is being committed together".
153
+ status = self._git(["status", "--porcelain"], default="")
154
+ if status.strip():
155
+ result["warnings"].append(
156
+ f"Working tree is dirty ({len(status.splitlines())} entries) — committing all"
157
+ )
158
+
159
+ # ── Step 5: git add -A + commit ────────────────────────────────
160
+ add_proc = self._run(["git", "add", "-A"])
161
+ if not add_proc["ok"]:
162
+ result["error"] = f"git add failed: {add_proc['stderr']}"
163
+ result["steps"].append({"step": "git_add", "ok": False})
164
+ return result
165
+ result["steps"].append({"step": "git_add", "ok": True})
166
+
167
+ commit_proc = self._run(["git", "commit", "-m", message])
168
+ if not commit_proc["ok"]:
169
+ # No changes staged is a soft error: surface it but don't
170
+ # treat it as fatal — the user may have wanted a no-op.
171
+ if "nothing to commit" in (commit_proc["stdout"] + commit_proc["stderr"]).lower():
172
+ result["error"] = "Nothing to commit — working tree clean after git add"
173
+ result["steps"].append({"step": "git_commit", "ok": False, "soft": True})
174
+ return result
175
+ result["error"] = f"git commit failed: {commit_proc['stderr']}"
176
+ result["steps"].append({"step": "git_commit", "ok": False})
177
+ return result
178
+ result["steps"].append({"step": "git_commit", "ok": True})
179
+
180
+ # ── Step 6: Capture the new SHA ───────────────────────────────
181
+ sha = self._git(["rev-parse", "HEAD"], default="").strip()
182
+ result["sha"] = sha
183
+
184
+ # ── Step 7: Optional annotated tag ────────────────────────────
185
+ if tag:
186
+ tag_proc = self._run(["git", "tag", "-a", tag, "-m", f"Release {tag}"])
187
+ if not tag_proc["ok"]:
188
+ # If tag already exists, that's a soft error (idempotent).
189
+ if "already exists" in (tag_proc["stderr"]).lower():
190
+ result["warnings"].append(f"Tag '{tag}' already exists locally — keeping")
191
+ else:
192
+ result["error"] = f"git tag failed: {tag_proc['stderr']}"
193
+ result["steps"].append({"step": "git_tag", "ok": False})
194
+ return result
195
+ result["steps"].append({"step": "git_tag", "ok": True})
196
+
197
+ # ── Step 8: Optional push to origin ───────────────────────────
198
+ if push:
199
+ push_proc = self._run(["git", "push", "origin", branch or main_branch])
200
+ if not push_proc["ok"]:
201
+ result["error"] = f"git push failed: {push_proc['stderr']}"
202
+ result["steps"].append({"step": "git_push", "ok": False})
203
+ return result
204
+ result["pushed"] = True
205
+ result["steps"].append({"step": "git_push", "ok": True})
206
+
207
+ if tag:
208
+ # Tags are pushed separately (unless --follow-tags was used).
209
+ tag_push = self._run(["git", "push", "origin", tag])
210
+ if not tag_push["ok"]:
211
+ result["warnings"].append(
212
+ f"Tag '{tag}' was not pushed: {tag_push['stderr'][-200:]}"
213
+ )
214
+ else:
215
+ result["steps"].append({"step": "git_push_tag", "ok": True})
216
+
217
+ result["success"] = True
218
+ return result
219
+
220
+ # ── helpers ─────────────────────────────────────────────────────
221
+ def _git(self, args: List[str], default: str = "") -> str:
222
+ """Run a git command, return stdout (or ``default`` on failure)."""
223
+ proc = subprocess.run(
224
+ ["git", *args],
225
+ cwd=self.repo_root,
226
+ capture_output=True,
227
+ text=True,
228
+ timeout=30,
229
+ )
230
+ return proc.stdout if proc.returncode == 0 else default
231
+
232
+ def _run(self, args: List[str]) -> Dict[str, Any]:
233
+ """Run a subprocess, return dict with ok/stdout/stderr/returncode."""
234
+ try:
235
+ proc = subprocess.run(
236
+ args,
237
+ cwd=self.repo_root,
238
+ capture_output=True,
239
+ text=True,
240
+ timeout=30,
241
+ )
242
+ return {
243
+ "ok": proc.returncode == 0,
244
+ "stdout": proc.stdout,
245
+ "stderr": proc.stderr,
246
+ "returncode": proc.returncode,
247
+ }
248
+ except subprocess.TimeoutExpired:
249
+ return {"ok": False, "stdout": "", "stderr": "timeout after 30s", "returncode": -1}
250
+ except Exception as exc:
251
+ return {"ok": False, "stdout": "", "stderr": str(exc), "returncode": -1}
252
+
253
+ def _create_snapshot(self, name: str, description: str) -> Dict[str, Any]:
254
+ """Best-effort pre-commit snapshot via sin-honcho-rollback.
255
+
256
+ Never raises. Returns ``{ok, snapshot_id}`` on success, ``{ok: False,
257
+ error}`` on any failure (missing CLI, non-zero exit, JSON parse
258
+ error, timeout).
259
+ """
260
+ try:
261
+ rb_bin = shutil.which("sin-honcho-rollback") or _ROLLBACK_FALLBACK
262
+ if not Path(rb_bin).exists():
263
+ return {"ok": False, "error": "sin-honcho-rollback not installed"}
264
+ proc = subprocess.run(
265
+ [
266
+ rb_bin,
267
+ "snapshot",
268
+ name,
269
+ "--description",
270
+ description or f"pre-commit checkpoint: {name}",
271
+ "--db",
272
+ str(self.repo_root / ".sin" / "rollback.db"),
273
+ ],
274
+ capture_output=True,
275
+ text=True,
276
+ timeout=15,
277
+ )
278
+ if proc.returncode == 0 and proc.stdout.strip():
279
+ data = json.loads(proc.stdout)
280
+ return {
281
+ "ok": True,
282
+ "snapshot_id": data.get("snapshot", {}).get("id") or data.get("id"),
283
+ }
284
+ return {"ok": False, "error": proc.stderr[-300:]}
285
+ except (subprocess.TimeoutExpired, json.JSONDecodeError) as exc:
286
+ return {"ok": False, "error": str(exc)}
287
+ except Exception as exc:
288
+ return {"ok": False, "error": str(exc)}
@@ -0,0 +1,119 @@
1
+ """Purpose: ADW interceptor — pre-flight architectural rule enforcement.
2
+
3
+ Docs: interceptor.doc.md
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+
13
+ # ── InterceptorRule: Single Pattern Matcher ────────────────────────────────
14
+ class InterceptorRule:
15
+ def __init__(self, name: str, pattern: str, message: str, severity: str = "error"):
16
+ self.name = name
17
+ self.pattern = re.compile(pattern, re.IGNORECASE)
18
+ self.message = message
19
+ self.severity = severity
20
+
21
+ def matches(self, content: str) -> bool:
22
+ return bool(self.pattern.search(content))
23
+
24
+
25
+ # ── SINInterceptor: Tool-Call Rule Engine ─────────────────────────────────
26
+ class SINInterceptor:
27
+ """Intercepts tool calls and validates against architectural rules."""
28
+
29
+ DEFAULT_RULES = [
30
+ (
31
+ "no_frontend_db_direct",
32
+ r"(import|require).*(database|db|sql).*from.*(frontend|ui|component)",
33
+ "Frontend components must not import database/SQL modules directly. Use an API layer.",
34
+ "error",
35
+ ),
36
+ (
37
+ "no_hardcoded_secrets",
38
+ r"(password|secret|api_key|token)\s*=\s*['\"][^'\"]+['\"]",
39
+ "Hardcoded secrets detected. Use environment variables or a secret manager.",
40
+ "error",
41
+ ),
42
+ (
43
+ "no_eval_exec",
44
+ r"\b(eval|exec|subprocess\.shell=True)\b",
45
+ "Dangerous execution pattern (eval/exec/shell=True) detected. Review for injection risks.",
46
+ "warning",
47
+ ),
48
+ ]
49
+
50
+ def __init__(self, repo_root: Optional[Path] = None):
51
+ self.repo_root = repo_root or Path.cwd()
52
+ self.rules: list[InterceptorRule] = []
53
+ self._load_default_rules()
54
+ self._load_adw_rules()
55
+
56
+ def _load_default_rules(self) -> None:
57
+ for name, pattern, message, severity in self.DEFAULT_RULES:
58
+ self.rules.append(InterceptorRule(name, pattern, message, severity))
59
+
60
+ def _load_adw_rules(self) -> None:
61
+ try:
62
+ from sin_code_adw import ADW # type: ignore
63
+
64
+ adw = ADW(repo_root=self.repo_root)
65
+ for rule in adw.get_active_rules():
66
+ self.rules.append(
67
+ InterceptorRule(
68
+ name=rule.get("name", "adw_rule"),
69
+ pattern=rule.get("pattern", ".*"),
70
+ message=rule.get("message", "ADW violation"),
71
+ severity=rule.get("severity", "warning"),
72
+ )
73
+ )
74
+ except Exception:
75
+ pass
76
+
77
+ def add_rule(self, name: str, pattern: str, message: str, severity: str = "error") -> None:
78
+ self.rules.append(InterceptorRule(name, pattern, message, severity))
79
+
80
+ def preflight(self, tool_name: str, tool_input: dict) -> dict:
81
+ content = self._extract_content(tool_name, tool_input)
82
+ if not content:
83
+ return {"allowed": True, "violations": []}
84
+ violations = []
85
+ for rule in self.rules:
86
+ if rule.matches(content):
87
+ violations.append(
88
+ {
89
+ "rule": rule.name,
90
+ "message": rule.message,
91
+ "severity": rule.severity,
92
+ "tool": tool_name,
93
+ }
94
+ )
95
+ if violations:
96
+ has_error = any(v["severity"] == "error" for v in violations)
97
+ return {
98
+ "allowed": not has_error,
99
+ "violations": violations,
100
+ "system_reminder": self._format_reminder(violations) if has_error else None,
101
+ }
102
+ return {"allowed": True, "violations": []}
103
+
104
+ @staticmethod
105
+ def _extract_content(tool_name: str, tool_input: dict) -> Optional[str]:
106
+ if tool_name in ("sin_write", "sin_edit", "sin_ast_edit"):
107
+ return tool_input.get("content") or tool_input.get("new_content") or ""
108
+ if tool_name == "sin_bash":
109
+ return tool_input.get("command", "")
110
+ return None
111
+
112
+ @staticmethod
113
+ def _format_reminder(violations: list) -> str:
114
+ lines = ["⚠️ **ARCHITECTURAL VIOLATION DETECTED** ⚠️"]
115
+ for v in violations:
116
+ if v["severity"] == "error":
117
+ lines.append(f"- 🚫 [{v['rule'].upper()}] {v['message']}")
118
+ lines.append("\nPlease revise your approach before proceeding.")
119
+ return "\n".join(lines)