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
whykit/placeholders.py ADDED
@@ -0,0 +1,84 @@
1
+ """Placeholder prose that WhyKit itself writes into new decision records.
2
+
3
+ `whykit new decision` renders its skeleton from the constants below, and the
4
+ `decision.placeholder` lint rule reads the same constants, so the two cannot
5
+ drift apart. The hand-copied template in ``template/templates/`` is parsed at
6
+ lint time for the same reason: whatever prose ships there is what an adopter
7
+ forgets to replace.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import functools
12
+ import re
13
+ from pathlib import Path
14
+
15
+ # Section heading -> the paragraph `whykit new decision` writes under it.
16
+ SCAFFOLD_SECTION_PROMPTS: dict[str, str] = {
17
+ "Context": "What is true now, and what forces a choice?",
18
+ "Decision": "State the choice in one sentence.",
19
+ "Rationale": "Why this option, given the evidence and constraints?",
20
+ }
21
+ SCAFFOLD_EVIDENCE_TODO = "- TODO — add E-NNN references or explain why none apply."
22
+ ALTERNATIVES_HEADER = "| Alternative | Upside | Risk | Why rejected |"
23
+ ALTERNATIVES_EMPTY_ROW = "| | | | |"
24
+
25
+ # Sections whose prose is the decision itself: a placeholder or nothing at all
26
+ # here means the record does not say what was decided or why.
27
+ PROSE_SECTIONS = ("Context", "Decision", "Rationale")
28
+
29
+ TEMPLATE_PATH = Path(__file__).resolve().parent / "template" / "templates" / "decision-record-template.md"
30
+
31
+
32
+ def normalize(text: str) -> str:
33
+ """Collapse whitespace so a re-wrapped placeholder still matches."""
34
+ return " ".join(text.split())
35
+
36
+
37
+ def split_sections(lines: list[str]) -> list[tuple[str, int, int]]:
38
+ """``(heading, first_body_line, end_line)`` for every ``## `` section.
39
+
40
+ Line indexes are 0-based and half-open; ``### `` subsections stay inside
41
+ their parent section.
42
+ """
43
+ heads = [(index, line[3:].strip()) for index, line in enumerate(lines) if line.startswith("## ")]
44
+ sections = []
45
+ for position, (index, heading) in enumerate(heads):
46
+ end = heads[position + 1][0] if position + 1 < len(heads) else len(lines)
47
+ sections.append((heading, index + 1, end))
48
+ return sections
49
+
50
+
51
+ def paragraphs(lines: list[str]) -> list[tuple[int, str]]:
52
+ """Plain prose paragraphs as ``(first_line_index, normalized_text)``.
53
+
54
+ Headings, list items, table rows and quotes end a paragraph and are not
55
+ part of one.
56
+ """
57
+ found: list[tuple[int, str]] = []
58
+ start: int | None = None
59
+ chunk: list[str] = []
60
+ for index, line in enumerate([*lines, ""]):
61
+ stripped = line.strip()
62
+ prose = bool(stripped) and not re.match(r"^(#|[-*+] |\d+[.)] |\||>)", stripped) and stripped not in {"-", "*", "+"}
63
+ if prose:
64
+ if start is None:
65
+ start = index
66
+ chunk.append(stripped)
67
+ elif chunk:
68
+ found.append((start or 0, normalize(" ".join(chunk))))
69
+ start, chunk = None, []
70
+ return found
71
+
72
+
73
+ @functools.lru_cache(maxsize=1)
74
+ def template_section_prompts() -> dict[str, frozenset[str]]:
75
+ """Placeholder paragraphs per section, from the scaffold and the shipped template."""
76
+ prompts: dict[str, set[str]] = {name: {normalize(text)} for name, text in SCAFFOLD_SECTION_PROMPTS.items()}
77
+ try:
78
+ lines = TEMPLATE_PATH.read_text(encoding="utf-8").splitlines()
79
+ except OSError:
80
+ lines = []
81
+ for heading, start, end in split_sections(lines):
82
+ for _, text in paragraphs(lines[start:end]):
83
+ prompts.setdefault(heading, set()).add(text)
84
+ return {name: frozenset(values) for name, values in prompts.items()}