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.
Files changed (126) hide show
  1. raidlock/__init__.py +11 -0
  2. raidlock/__main__.py +5 -0
  3. raidlock/_python.py +58 -0
  4. raidlock/achievements.py +156 -0
  5. raidlock/approvals.py +237 -0
  6. raidlock/bench.py +286 -0
  7. raidlock/bridge/__init__.py +186 -0
  8. raidlock/budgets.py +178 -0
  9. raidlock/cli.py +2153 -0
  10. raidlock/commands_install.py +114 -0
  11. raidlock/core/__init__.py +26 -0
  12. raidlock/core/advisories.py +16 -0
  13. raidlock/core/billing.py +54 -0
  14. raidlock/core/board.py +2251 -0
  15. raidlock/core/brief.py +111 -0
  16. raidlock/core/clock.py +40 -0
  17. raidlock/core/cost.py +198 -0
  18. raidlock/core/db.py +608 -0
  19. raidlock/core/errors.py +52 -0
  20. raidlock/core/exe.py +6 -0
  21. raidlock/core/hints.py +32 -0
  22. raidlock/core/identity.py +285 -0
  23. raidlock/core/lead.py +758 -0
  24. raidlock/core/lead_env.py +23 -0
  25. raidlock/core/lead_hook.py +42 -0
  26. raidlock/core/leases.py +19 -0
  27. raidlock/core/lock.py +99 -0
  28. raidlock/core/messages.py +98 -0
  29. raidlock/core/migrate.py +579 -0
  30. raidlock/core/model.py +126 -0
  31. raidlock/core/models.py +109 -0
  32. raidlock/core/paths.py +61 -0
  33. raidlock/core/portable.py +53 -0
  34. raidlock/core/receipts.py +73 -0
  35. raidlock/core/registry.py +39 -0
  36. raidlock/core/repos.py +250 -0
  37. raidlock/core/safeio.py +201 -0
  38. raidlock/core/status.py +52 -0
  39. raidlock/core/store.py +107 -0
  40. raidlock/core/tokens.py +77 -0
  41. raidlock/core/transcript.py +208 -0
  42. raidlock/duel.py +371 -0
  43. raidlock/foresight.py +191 -0
  44. raidlock/frames.py +162 -0
  45. raidlock/gitguard.py +753 -0
  46. raidlock/guard.py +239 -0
  47. raidlock/hooks.py +1526 -0
  48. raidlock/infer.py +205 -0
  49. raidlock/limits.py +685 -0
  50. raidlock/lore/__init__.py +22 -0
  51. raidlock/lore/approval.py +145 -0
  52. raidlock/lore/capture.py +146 -0
  53. raidlock/lore/cli.py +109 -0
  54. raidlock/lore/distill.py +17 -0
  55. raidlock/lore/frontmatter.py +56 -0
  56. raidlock/lore/inject.py +127 -0
  57. raidlock/lore/vault.py +264 -0
  58. raidlock/mcp/__init__.py +34 -0
  59. raidlock/mcp/resources.py +72 -0
  60. raidlock/mcp/server.py +184 -0
  61. raidlock/mcp/tools.py +178 -0
  62. raidlock/notify.py +144 -0
  63. raidlock/party.py +239 -0
  64. raidlock/pr.py +575 -0
  65. raidlock/prsync.py +318 -0
  66. raidlock/recipes/README.md +34 -0
  67. raidlock/recipes/__init__.py +105 -0
  68. raidlock/recipes/docs-refresh.md +15 -0
  69. raidlock/recipes/multi-repo-refactor.md +15 -0
  70. raidlock/recipes/security-audit.md +27 -0
  71. raidlock/recipes/upgrade-deps.md +15 -0
  72. raidlock/reputation.py +194 -0
  73. raidlock/resume.py +329 -0
  74. raidlock/resurrect.py +419 -0
  75. raidlock/routing.py +419 -0
  76. raidlock/runners/__init__.py +16 -0
  77. raidlock/runners/base.py +341 -0
  78. raidlock/runners/brief.py +176 -0
  79. raidlock/runners/claude_code.py +121 -0
  80. raidlock/runners/cli.py +110 -0
  81. raidlock/runners/codex.py +114 -0
  82. raidlock/runners/gemini.py +64 -0
  83. raidlock/runners/isolation.py +391 -0
  84. raidlock/runners/policy.py +123 -0
  85. raidlock/runners/runner.py +921 -0
  86. raidlock/runners/shell.py +35 -0
  87. raidlock/runners/verifycache.py +93 -0
  88. raidlock/runners/workspace.py +376 -0
  89. raidlock/scan.py +357 -0
  90. raidlock/snapshot.py +151 -0
  91. raidlock/treasury.py +210 -0
  92. raidlock/tui.py +223 -0
  93. raidlock/web/__init__.py +5 -0
  94. raidlock/web/achievements/view.css +19 -0
  95. raidlock/web/achievements/view.js +58 -0
  96. raidlock/web/bosses/view.css +19 -0
  97. raidlock/web/bosses/view.js +46 -0
  98. raidlock/web/cli.py +90 -0
  99. raidlock/web/data.py +687 -0
  100. raidlock/web/export.py +153 -0
  101. raidlock/web/frames/view.css +25 -0
  102. raidlock/web/frames/view.js +63 -0
  103. raidlock/web/graph/__init__.py +142 -0
  104. raidlock/web/graph/view.css +33 -0
  105. raidlock/web/graph/view.js +136 -0
  106. raidlock/web/raidmap/__init__.py +205 -0
  107. raidlock/web/raidmap/view.css +15 -0
  108. raidlock/web/raidmap/view.js +47 -0
  109. raidlock/web/replay/__init__.py +258 -0
  110. raidlock/web/replay/view.css +13 -0
  111. raidlock/web/replay/view.js +127 -0
  112. raidlock/web/server.py +292 -0
  113. raidlock/web/static/app.css +851 -0
  114. raidlock/web/static/app.js +2102 -0
  115. raidlock/web/static/fonts/OFL.txt +93 -0
  116. raidlock/web/static/fonts/cinzel-600.woff2 +0 -0
  117. raidlock/web/static/fonts/cinzel-700.woff2 +0 -0
  118. raidlock/web/static/icon-32.png +0 -0
  119. raidlock/web/static/icon-raid.svg +1 -0
  120. raidlock/web/static/icon.svg +1 -0
  121. raidlock/web/static/index.html +122 -0
  122. raidlock-0.1.0.dist-info/METADATA +186 -0
  123. raidlock-0.1.0.dist-info/RECORD +126 -0
  124. raidlock-0.1.0.dist-info/WHEEL +4 -0
  125. raidlock-0.1.0.dist-info/entry_points.txt +34 -0
  126. 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)