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.
Files changed (84) hide show
  1. whykit/__init__.py +21 -0
  2. whykit/adopt.py +776 -0
  3. whykit/backlinks.py +106 -0
  4. whykit/check.py +320 -0
  5. whykit/ci_formats.py +283 -0
  6. whykit/cli.py +1719 -0
  7. whykit/completion.py +193 -0
  8. whykit/config.py +233 -0
  9. whykit/console.py +217 -0
  10. whykit/context.py +204 -0
  11. whykit/contract.py +210 -0
  12. whykit/contract_schemas/backlinks-report.schema.json +61 -0
  13. whykit/contract_schemas/context-bundle.schema.json +27 -0
  14. whykit/contract_schemas/context-pack.schema.json +29 -0
  15. whykit/contract_schemas/impact-report.schema.json +122 -0
  16. whykit/contract_schemas/query-result.schema.json +105 -0
  17. whykit/contract_schemas/status-report.schema.json +176 -0
  18. whykit/contract_schemas/trace-report.schema.json +211 -0
  19. whykit/diff.py +823 -0
  20. whykit/evidence.py +178 -0
  21. whykit/explorer_index.py +554 -0
  22. whykit/graph.py +318 -0
  23. whykit/immutability.py +624 -0
  24. whykit/impact.py +272 -0
  25. whykit/init_layout.py +205 -0
  26. whykit/io.py +563 -0
  27. whykit/lint.py +2190 -0
  28. whykit/lsp.py +1241 -0
  29. whykit/markdown_code.py +107 -0
  30. whykit/mcp_server.py +2289 -0
  31. whykit/messages.py +46 -0
  32. whykit/pack.py +294 -0
  33. whykit/parse_cache.py +752 -0
  34. whykit/placeholders.py +84 -0
  35. whykit/promote.py +651 -0
  36. whykit/py.typed +0 -0
  37. whykit/query.py +174 -0
  38. whykit/review.py +527 -0
  39. whykit/rule_policy.py +1063 -0
  40. whykit/rules.py +274 -0
  41. whykit/scaffold.py +784 -0
  42. whykit/sensitivity.py +95 -0
  43. whykit/snapshot.py +319 -0
  44. whykit/status.py +167 -0
  45. whykit/tables.py +120 -0
  46. whykit/template/00-context/README.md +17 -0
  47. whykit/template/00-context/company.md +52 -0
  48. whykit/template/00-context/evidence-register.md +34 -0
  49. whykit/template/00-context/goals.md +38 -0
  50. whykit/template/00-context/review-log.md +22 -0
  51. whykit/template/00-context/terminology.md +34 -0
  52. whykit/template/01-strategy/README.md +7 -0
  53. whykit/template/02-discoverability/README.md +10 -0
  54. whykit/template/03-website/README.md +7 -0
  55. whykit/template/04-automation/README.md +10 -0
  56. whykit/template/05-operations/README.md +19 -0
  57. whykit/template/06-decisions/README.md +11 -0
  58. whykit/template/06-decisions/decision-log.md +38 -0
  59. whykit/template/07-research/README.md +10 -0
  60. whykit/template/AGENTS.md +163 -0
  61. whykit/template/Home.md +57 -0
  62. whykit/template/INTEROP.md +117 -0
  63. whykit/template/OBSIDIAN.md +92 -0
  64. whykit/template/README.md +51 -0
  65. whykit/template/assets/README.md +6 -0
  66. whykit/template/reports/README.md +7 -0
  67. whykit/template/templates/README.md +7 -0
  68. whykit/template/templates/automation-spec-template.md +70 -0
  69. whykit/template/templates/decision-record-template.md +65 -0
  70. whykit/template/templates/document-template.md +55 -0
  71. whykit/template/templates/experiment-template.md +62 -0
  72. whykit/template/templates/meeting-note-template.md +47 -0
  73. whykit/template/templates/page-brief-template.md +63 -0
  74. whykit/template/templates/research-note-template.md +53 -0
  75. whykit/template/templates/source-ingestion-template.md +43 -0
  76. whykit/template/whykit.toml +39 -0
  77. whykit/trace.py +272 -0
  78. whykit/vault_index.py +190 -0
  79. whykit-0.3.1.dist-info/METADATA +162 -0
  80. whykit-0.3.1.dist-info/RECORD +84 -0
  81. whykit-0.3.1.dist-info/WHEEL +4 -0
  82. whykit-0.3.1.dist-info/entry_points.txt +3 -0
  83. whykit-0.3.1.dist-info/licenses/LICENSE +201 -0
  84. whykit-0.3.1.dist-info/licenses/NOTICE +13 -0
@@ -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)