whykit 0.3.1__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.
- whykit/__init__.py +21 -0
- whykit/adopt.py +776 -0
- whykit/backlinks.py +106 -0
- whykit/check.py +320 -0
- whykit/ci_formats.py +283 -0
- whykit/cli.py +1719 -0
- whykit/completion.py +193 -0
- whykit/config.py +233 -0
- whykit/console.py +217 -0
- whykit/context.py +204 -0
- whykit/contract.py +210 -0
- whykit/contract_schemas/backlinks-report.schema.json +61 -0
- whykit/contract_schemas/context-bundle.schema.json +27 -0
- whykit/contract_schemas/context-pack.schema.json +29 -0
- whykit/contract_schemas/impact-report.schema.json +122 -0
- whykit/contract_schemas/query-result.schema.json +105 -0
- whykit/contract_schemas/status-report.schema.json +176 -0
- whykit/contract_schemas/trace-report.schema.json +211 -0
- whykit/diff.py +823 -0
- whykit/evidence.py +178 -0
- whykit/explorer_index.py +554 -0
- whykit/graph.py +318 -0
- whykit/immutability.py +624 -0
- whykit/impact.py +272 -0
- whykit/init_layout.py +205 -0
- whykit/io.py +563 -0
- whykit/lint.py +2190 -0
- whykit/lsp.py +1241 -0
- whykit/markdown_code.py +107 -0
- whykit/mcp_server.py +2289 -0
- whykit/messages.py +46 -0
- whykit/pack.py +294 -0
- whykit/parse_cache.py +752 -0
- whykit/placeholders.py +84 -0
- whykit/promote.py +651 -0
- whykit/py.typed +0 -0
- whykit/query.py +174 -0
- whykit/review.py +527 -0
- whykit/rule_policy.py +1063 -0
- whykit/rules.py +274 -0
- whykit/scaffold.py +784 -0
- whykit/sensitivity.py +95 -0
- whykit/snapshot.py +319 -0
- whykit/status.py +167 -0
- whykit/tables.py +120 -0
- whykit/template/00-context/README.md +17 -0
- whykit/template/00-context/company.md +52 -0
- whykit/template/00-context/evidence-register.md +34 -0
- whykit/template/00-context/goals.md +38 -0
- whykit/template/00-context/review-log.md +22 -0
- whykit/template/00-context/terminology.md +34 -0
- whykit/template/01-strategy/README.md +7 -0
- whykit/template/02-discoverability/README.md +10 -0
- whykit/template/03-website/README.md +7 -0
- whykit/template/04-automation/README.md +10 -0
- whykit/template/05-operations/README.md +19 -0
- whykit/template/06-decisions/README.md +11 -0
- whykit/template/06-decisions/decision-log.md +38 -0
- whykit/template/07-research/README.md +10 -0
- whykit/template/AGENTS.md +163 -0
- whykit/template/Home.md +57 -0
- whykit/template/INTEROP.md +117 -0
- whykit/template/OBSIDIAN.md +92 -0
- whykit/template/README.md +51 -0
- whykit/template/assets/README.md +6 -0
- whykit/template/reports/README.md +7 -0
- whykit/template/templates/README.md +7 -0
- whykit/template/templates/automation-spec-template.md +70 -0
- whykit/template/templates/decision-record-template.md +65 -0
- whykit/template/templates/document-template.md +55 -0
- whykit/template/templates/experiment-template.md +62 -0
- whykit/template/templates/meeting-note-template.md +47 -0
- whykit/template/templates/page-brief-template.md +63 -0
- whykit/template/templates/research-note-template.md +53 -0
- whykit/template/templates/source-ingestion-template.md +43 -0
- whykit/template/whykit.toml +39 -0
- whykit/trace.py +272 -0
- whykit/vault_index.py +190 -0
- whykit-0.3.1.dist-info/METADATA +162 -0
- whykit-0.3.1.dist-info/RECORD +84 -0
- whykit-0.3.1.dist-info/WHEEL +4 -0
- whykit-0.3.1.dist-info/entry_points.txt +3 -0
- whykit-0.3.1.dist-info/licenses/LICENSE +201 -0
- whykit-0.3.1.dist-info/licenses/NOTICE +13 -0
whykit/markdown_code.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Position-preserving masking of code and comments for link extraction.
|
|
2
|
+
|
|
3
|
+
shortcut: this is not a full CommonMark parser (containers, indented code and
|
|
4
|
+
other HTML blocks are unsupported); use a full parser when those are required.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
_BT = chr(96)
|
|
11
|
+
_FENCE_OPEN = re.compile(r"^ {0,3}(" + _BT + r"{3,}|~{3,})(.*)$")
|
|
12
|
+
_FENCE_CLOSE = re.compile(r"^ {0,3}(" + _BT + r"+|~+)[ \t]*$")
|
|
13
|
+
_ATX_HEADING = re.compile(r"^ {0,3}#{1,6}(?:[ \t]|$)")
|
|
14
|
+
_THEMATIC_BREAK = re.compile(r"^ {0,3}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$")
|
|
15
|
+
_TOKENS = re.compile(_BT + r"+|<!--")
|
|
16
|
+
# Unicode separators are ordinary content, not Markdown physical line endings.
|
|
17
|
+
_MARKDOWN_LINE = re.compile(r"[^\r\n]*(?:\r\n|\n|\r)|[^\r\n]+$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def mask_code(text: str, *, keep_code: bool = False) -> str:
|
|
21
|
+
"""Mask comments and, unless keep_code, code; retain offsets in linear time."""
|
|
22
|
+
if _BT not in text and "~~~" not in text and "<!--" not in text:
|
|
23
|
+
return text
|
|
24
|
+
chars = list(text)
|
|
25
|
+
lines = list(_MARKDOWN_LINE.finditer(text))
|
|
26
|
+
tokens = list(_TOKENS.finditer(text))
|
|
27
|
+
fences = []
|
|
28
|
+
for line in lines:
|
|
29
|
+
match = _FENCE_OPEN.fullmatch(line.group().rstrip("\r\n"))
|
|
30
|
+
fences.append(match if match and (match[1][0] != _BT or _BT not in match[2]) else None)
|
|
31
|
+
|
|
32
|
+
# A heading owns inline spans on its line, isolated from both neighbours.
|
|
33
|
+
closing: list[int | None] = [None] * len(tokens)
|
|
34
|
+
next_of_size: dict[int, int] = {}
|
|
35
|
+
token_index = len(tokens) - 1
|
|
36
|
+
for line_index in range(len(lines) - 1, -1, -1):
|
|
37
|
+
line = lines[line_index]
|
|
38
|
+
body = line.group().rstrip("\r\n")
|
|
39
|
+
heading = _ATX_HEADING.match(body) is not None
|
|
40
|
+
boundary = fences[line_index] or not body.strip(" \t") or _THEMATIC_BREAK.fullmatch(body) is not None
|
|
41
|
+
if boundary or heading:
|
|
42
|
+
next_of_size.clear()
|
|
43
|
+
while token_index >= 0 and tokens[token_index].start() >= line.start():
|
|
44
|
+
token = tokens[token_index]
|
|
45
|
+
if not boundary and token[0][0] == _BT:
|
|
46
|
+
width = len(token[0])
|
|
47
|
+
closing[token_index] = next_of_size.get(width)
|
|
48
|
+
next_of_size[width] = token_index
|
|
49
|
+
token_index -= 1
|
|
50
|
+
if heading:
|
|
51
|
+
next_of_size.clear()
|
|
52
|
+
|
|
53
|
+
def blank(start: int, end: int) -> None:
|
|
54
|
+
for index in range(start, end):
|
|
55
|
+
if chars[index] not in "\r\n":
|
|
56
|
+
chars[index] = " "
|
|
57
|
+
|
|
58
|
+
opening: tuple[str, int] | None = None
|
|
59
|
+
cursor = 0
|
|
60
|
+
token_index = 0
|
|
61
|
+
for line_index, line in enumerate(lines):
|
|
62
|
+
if line.end() <= cursor:
|
|
63
|
+
continue
|
|
64
|
+
body = line.group().rstrip("\r\n")
|
|
65
|
+
if opening:
|
|
66
|
+
if not keep_code:
|
|
67
|
+
blank(line.start(), line.end())
|
|
68
|
+
match = _FENCE_CLOSE.fullmatch(body)
|
|
69
|
+
if match and match[1][0] == opening[0] and len(match[1]) >= opening[1]:
|
|
70
|
+
opening = None
|
|
71
|
+
cursor = line.end()
|
|
72
|
+
continue
|
|
73
|
+
fence = fences[line_index]
|
|
74
|
+
if cursor <= line.start() and fence:
|
|
75
|
+
opening = (fence[1][0], len(fence[1]))
|
|
76
|
+
if not keep_code:
|
|
77
|
+
blank(line.start(), line.end())
|
|
78
|
+
cursor = line.end()
|
|
79
|
+
continue
|
|
80
|
+
while token_index < len(tokens) and tokens[token_index].start() < line.end():
|
|
81
|
+
index = token_index
|
|
82
|
+
token_index += 1
|
|
83
|
+
token = tokens[index]
|
|
84
|
+
start = token.start()
|
|
85
|
+
if start < cursor:
|
|
86
|
+
continue
|
|
87
|
+
if token[0][0] == _BT:
|
|
88
|
+
end_index = closing[index]
|
|
89
|
+
cursor = token.end() if end_index is None else tokens[end_index].end()
|
|
90
|
+
if end_index is not None and not keep_code:
|
|
91
|
+
blank(start, cursor)
|
|
92
|
+
continue
|
|
93
|
+
escapes = 0
|
|
94
|
+
while start - escapes - 1 >= 0 and text[start - escapes - 1] == "\\":
|
|
95
|
+
escapes += 1
|
|
96
|
+
if escapes % 2:
|
|
97
|
+
cursor = token.end()
|
|
98
|
+
continue
|
|
99
|
+
if text.startswith("<!-->", start):
|
|
100
|
+
cursor = start + 5
|
|
101
|
+
elif text.startswith("<!--->", start):
|
|
102
|
+
cursor = start + 6
|
|
103
|
+
else:
|
|
104
|
+
closer = text.find("-->", token.end())
|
|
105
|
+
cursor = len(text) if closer < 0 else closer + 3
|
|
106
|
+
blank(start, cursor)
|
|
107
|
+
return "".join(chars)
|