raidlock 0.1.0__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.
- raidlock/__init__.py +11 -0
- raidlock/__main__.py +5 -0
- raidlock/_python.py +58 -0
- raidlock/achievements.py +156 -0
- raidlock/approvals.py +237 -0
- raidlock/bench.py +286 -0
- raidlock/bridge/__init__.py +186 -0
- raidlock/budgets.py +178 -0
- raidlock/cli.py +2153 -0
- raidlock/commands_install.py +114 -0
- raidlock/core/__init__.py +26 -0
- raidlock/core/advisories.py +16 -0
- raidlock/core/billing.py +54 -0
- raidlock/core/board.py +2251 -0
- raidlock/core/brief.py +111 -0
- raidlock/core/clock.py +40 -0
- raidlock/core/cost.py +198 -0
- raidlock/core/db.py +608 -0
- raidlock/core/errors.py +52 -0
- raidlock/core/exe.py +6 -0
- raidlock/core/hints.py +32 -0
- raidlock/core/identity.py +285 -0
- raidlock/core/lead.py +758 -0
- raidlock/core/lead_env.py +23 -0
- raidlock/core/lead_hook.py +42 -0
- raidlock/core/leases.py +19 -0
- raidlock/core/lock.py +99 -0
- raidlock/core/messages.py +98 -0
- raidlock/core/migrate.py +579 -0
- raidlock/core/model.py +126 -0
- raidlock/core/models.py +109 -0
- raidlock/core/paths.py +61 -0
- raidlock/core/portable.py +53 -0
- raidlock/core/receipts.py +73 -0
- raidlock/core/registry.py +39 -0
- raidlock/core/repos.py +250 -0
- raidlock/core/safeio.py +201 -0
- raidlock/core/status.py +52 -0
- raidlock/core/store.py +107 -0
- raidlock/core/tokens.py +77 -0
- raidlock/core/transcript.py +208 -0
- raidlock/duel.py +371 -0
- raidlock/foresight.py +191 -0
- raidlock/frames.py +162 -0
- raidlock/gitguard.py +753 -0
- raidlock/guard.py +239 -0
- raidlock/hooks.py +1526 -0
- raidlock/infer.py +205 -0
- raidlock/limits.py +685 -0
- raidlock/lore/__init__.py +22 -0
- raidlock/lore/approval.py +145 -0
- raidlock/lore/capture.py +146 -0
- raidlock/lore/cli.py +109 -0
- raidlock/lore/distill.py +17 -0
- raidlock/lore/frontmatter.py +56 -0
- raidlock/lore/inject.py +127 -0
- raidlock/lore/vault.py +264 -0
- raidlock/mcp/__init__.py +34 -0
- raidlock/mcp/resources.py +72 -0
- raidlock/mcp/server.py +184 -0
- raidlock/mcp/tools.py +178 -0
- raidlock/notify.py +144 -0
- raidlock/party.py +239 -0
- raidlock/pr.py +575 -0
- raidlock/prsync.py +318 -0
- raidlock/recipes/README.md +34 -0
- raidlock/recipes/__init__.py +105 -0
- raidlock/recipes/docs-refresh.md +15 -0
- raidlock/recipes/multi-repo-refactor.md +15 -0
- raidlock/recipes/security-audit.md +27 -0
- raidlock/recipes/upgrade-deps.md +15 -0
- raidlock/reputation.py +194 -0
- raidlock/resume.py +329 -0
- raidlock/resurrect.py +419 -0
- raidlock/routing.py +419 -0
- raidlock/runners/__init__.py +16 -0
- raidlock/runners/base.py +341 -0
- raidlock/runners/brief.py +176 -0
- raidlock/runners/claude_code.py +121 -0
- raidlock/runners/cli.py +110 -0
- raidlock/runners/codex.py +114 -0
- raidlock/runners/gemini.py +64 -0
- raidlock/runners/isolation.py +391 -0
- raidlock/runners/policy.py +123 -0
- raidlock/runners/runner.py +921 -0
- raidlock/runners/shell.py +35 -0
- raidlock/runners/verifycache.py +93 -0
- raidlock/runners/workspace.py +376 -0
- raidlock/scan.py +357 -0
- raidlock/snapshot.py +151 -0
- raidlock/treasury.py +210 -0
- raidlock/tui.py +223 -0
- raidlock/web/__init__.py +5 -0
- raidlock/web/achievements/view.css +19 -0
- raidlock/web/achievements/view.js +58 -0
- raidlock/web/bosses/view.css +19 -0
- raidlock/web/bosses/view.js +46 -0
- raidlock/web/cli.py +90 -0
- raidlock/web/data.py +687 -0
- raidlock/web/export.py +153 -0
- raidlock/web/frames/view.css +25 -0
- raidlock/web/frames/view.js +63 -0
- raidlock/web/graph/__init__.py +142 -0
- raidlock/web/graph/view.css +33 -0
- raidlock/web/graph/view.js +136 -0
- raidlock/web/raidmap/__init__.py +205 -0
- raidlock/web/raidmap/view.css +15 -0
- raidlock/web/raidmap/view.js +47 -0
- raidlock/web/replay/__init__.py +258 -0
- raidlock/web/replay/view.css +13 -0
- raidlock/web/replay/view.js +127 -0
- raidlock/web/server.py +292 -0
- raidlock/web/static/app.css +851 -0
- raidlock/web/static/app.js +2102 -0
- raidlock/web/static/fonts/OFL.txt +93 -0
- raidlock/web/static/fonts/cinzel-600.woff2 +0 -0
- raidlock/web/static/fonts/cinzel-700.woff2 +0 -0
- raidlock/web/static/icon-32.png +0 -0
- raidlock/web/static/icon-raid.svg +1 -0
- raidlock/web/static/icon.svg +1 -0
- raidlock/web/static/index.html +122 -0
- raidlock-0.1.0.dist-info/METADATA +186 -0
- raidlock-0.1.0.dist-info/RECORD +126 -0
- raidlock-0.1.0.dist-info/WHEEL +4 -0
- raidlock-0.1.0.dist-info/entry_points.txt +34 -0
- raidlock-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from ..core import paths as P
|
|
5
|
+
from ..core import repos
|
|
6
|
+
from ..core.errors import Precondition
|
|
7
|
+
|
|
8
|
+
DENY_PREFIXES = (".github/workflows", ".claude", ".board")
|
|
9
|
+
DENY_NAMES = ("*.pem", ".env*", "secrets", "secrets.*")
|
|
10
|
+
DENY_SEGMENTS = ("secrets",)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def denied(path, allow_protected=()):
|
|
14
|
+
"""True when a repo-relative path matches a deny rule, whatever the card's locks say."""
|
|
15
|
+
text = path.replace("\\", "/").strip("/").lower()
|
|
16
|
+
parts = P.split(text)
|
|
17
|
+
if not parts:
|
|
18
|
+
return False
|
|
19
|
+
for prefix in DENY_PREFIXES:
|
|
20
|
+
if (text == prefix or text.startswith(prefix + "/")) and (prefix == ".board" or prefix not in allow_protected):
|
|
21
|
+
return True
|
|
22
|
+
if any(seg in DENY_SEGMENTS for seg in parts[:-1]):
|
|
23
|
+
return True
|
|
24
|
+
return any(fnmatch.fnmatchcase(parts[-1], pattern) for pattern in DENY_NAMES)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def target(workspace, board_dir, locks):
|
|
28
|
+
"""The single repo a card's locks live in: {repo, path, rels, ci}. Raises when locks span repos or none."""
|
|
29
|
+
if not locks:
|
|
30
|
+
raise Precondition("the card has no locks; add some with `raidlock lock <card> <repo>/<path>`")
|
|
31
|
+
resolver = repos.Resolver(workspace, board_dir)
|
|
32
|
+
found = resolver.repos
|
|
33
|
+
ci = repos.case_insensitive(os.path.realpath(workspace))
|
|
34
|
+
hits = {}
|
|
35
|
+
for raw in locks:
|
|
36
|
+
parts = P.split(P.canonical(raw))
|
|
37
|
+
name, used = resolver._match(parts)
|
|
38
|
+
if name is None:
|
|
39
|
+
raise Precondition(
|
|
40
|
+
f"lock {raw} matches no unambiguous repo in workspace {workspace}",
|
|
41
|
+
lock=raw,
|
|
42
|
+
hint="prefix the lock with a workspace repo: "
|
|
43
|
+
+ (", ".join(sorted(found)) or "initialize or declare a git repo in this workspace"),
|
|
44
|
+
)
|
|
45
|
+
hits.setdefault(name, []).append("/".join(parts[used:]))
|
|
46
|
+
if len(hits) > 1:
|
|
47
|
+
raise Precondition(
|
|
48
|
+
"the card's locks span several repos (" + ", ".join(sorted(hits)) + "); split the quest per repo",
|
|
49
|
+
repos=sorted(hits),
|
|
50
|
+
)
|
|
51
|
+
name, rels = next(iter(hits.items()))
|
|
52
|
+
return {"repo": name, "path": found[name], "rels": sorted(set(rels)), "ci": ci}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _prefix(want, have, ci):
|
|
56
|
+
if len(want) > len(have):
|
|
57
|
+
return False
|
|
58
|
+
a, b = want, have[: len(want)]
|
|
59
|
+
if ci:
|
|
60
|
+
a, b = [x.casefold() for x in a], [x.casefold() for x in b]
|
|
61
|
+
return a == b
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def contained(path, rels, ci=False):
|
|
65
|
+
return any(not rel or P.contains(rel, path, ci) for rel in rels)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def check(paths, rels, ci=False, allow_protected=()):
|
|
69
|
+
"""{outside: [...], denied: [...]} for repo-relative changed paths."""
|
|
70
|
+
return {
|
|
71
|
+
"outside": sorted(p for p in paths if not contained(p, rels, ci)),
|
|
72
|
+
"denied": sorted(p for p in paths if denied(p, allow_protected)),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def link_target(path, target, links=None):
|
|
77
|
+
"""Resolve components in filesystem order: expand links before processing '..'."""
|
|
78
|
+
import posixpath
|
|
79
|
+
from collections import deque
|
|
80
|
+
|
|
81
|
+
def absolute(value):
|
|
82
|
+
return "\\" in value or value.startswith(("/", "~")) or (len(value) > 1 and value[1] == ":")
|
|
83
|
+
|
|
84
|
+
if absolute(target):
|
|
85
|
+
return None
|
|
86
|
+
links = links or {}
|
|
87
|
+
pending = deque((posixpath.dirname(path) + "/" + target).split("/"))
|
|
88
|
+
resolved, expansions = [], 0
|
|
89
|
+
while pending:
|
|
90
|
+
part = pending.popleft()
|
|
91
|
+
if part in ("", "."):
|
|
92
|
+
continue
|
|
93
|
+
if part == "..":
|
|
94
|
+
if not resolved:
|
|
95
|
+
return None
|
|
96
|
+
resolved.pop()
|
|
97
|
+
continue
|
|
98
|
+
resolved.append(part)
|
|
99
|
+
prefix = "/".join(resolved)
|
|
100
|
+
if prefix in links:
|
|
101
|
+
expansions += 1
|
|
102
|
+
value = links[prefix]
|
|
103
|
+
if expansions > 64 or absolute(value):
|
|
104
|
+
return None
|
|
105
|
+
resolved.pop()
|
|
106
|
+
pending.extendleft(reversed(value.split("/")))
|
|
107
|
+
return "/".join(resolved) or "."
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def check_links(links, rels=None, ci=False, paths=None, allow_protected=(), excluded=()):
|
|
111
|
+
bad = []
|
|
112
|
+
for path, target in links.items():
|
|
113
|
+
if paths is not None and path not in paths:
|
|
114
|
+
continue
|
|
115
|
+
resolved = link_target(path, target, links)
|
|
116
|
+
if (
|
|
117
|
+
resolved is None
|
|
118
|
+
or contained(resolved, excluded, ci)
|
|
119
|
+
or denied(resolved, allow_protected)
|
|
120
|
+
or (rels is not None and not contained(resolved, rels, ci))
|
|
121
|
+
):
|
|
122
|
+
bad.append(path)
|
|
123
|
+
return sorted(bad)
|