@grifhinz/logics-manager 2.0.0

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.
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ from dataclasses import dataclass
7
+ from os import path as os_path
8
+ from pathlib import Path
9
+
10
+ from .config import find_repo_root
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Entry:
15
+ path: Path
16
+ doc_ref: str
17
+ title: str
18
+ status: str | None
19
+ progress: str | None
20
+
21
+
22
+ SECTION_DEFINITIONS = (
23
+ ("Architecture decisions", "logics/architecture", False),
24
+ ("Product briefs", "logics/product", False),
25
+ ("Requests", "logics/request", False),
26
+ ("Backlog", "logics/backlog", True),
27
+ ("Tasks", "logics/tasks", True),
28
+ )
29
+
30
+ SECTION_COUNT_KEYS = ("architecture", "product", "request", "backlog", "task")
31
+
32
+
33
+ def _parse_doc(path: Path) -> Entry:
34
+ lines = path.read_text(encoding="utf-8").splitlines()
35
+ doc_ref = path.stem
36
+ title = ""
37
+ status: str | None = None
38
+ progress: str | None = None
39
+
40
+ for line in lines:
41
+ if line.startswith("## "):
42
+ payload = line.removeprefix("## ").strip()
43
+ if " - " in payload:
44
+ maybe_ref, maybe_title = payload.split(" - ", 1)
45
+ doc_ref = maybe_ref.strip()
46
+ title = maybe_title.strip()
47
+ else:
48
+ title = payload
49
+ continue
50
+ if line.startswith("> Status:"):
51
+ status = line.split(":", 1)[1].strip()
52
+ continue
53
+ if line.startswith("> Progress:"):
54
+ progress = line.split(":", 1)[1].strip()
55
+ if not title:
56
+ title = "(missing title)"
57
+ return Entry(path=path, doc_ref=doc_ref, title=title, status=status, progress=progress)
58
+
59
+
60
+ def _collect_paths(repo_root: Path, rel_dir: str) -> list[Path]:
61
+ directory = repo_root / rel_dir
62
+ if not directory.is_dir():
63
+ return []
64
+ return sorted(directory.glob("*.md"))
65
+
66
+
67
+ def _collect_entries(repo_root: Path, rel_dir: str) -> list[Entry]:
68
+ return [_parse_doc(path) for path in _collect_paths(repo_root, rel_dir)]
69
+
70
+
71
+ def _render_section(title: str, entries: list[Entry], show_progress: bool, out_dir: Path) -> str:
72
+ lines: list[str] = [f"## {title}", ""]
73
+ if not entries:
74
+ lines.append("_None_")
75
+ lines.append("")
76
+ return "\n".join(lines)
77
+
78
+ lines.extend(["| Doc | Title | Status | Progress | Path |", "|---|---|---|---|---|"])
79
+
80
+ for entry in entries:
81
+ rel = os_path.relpath(entry.path, start=out_dir).replace(os.sep, "/")
82
+ doc_link = f"[{entry.doc_ref}]({rel})"
83
+ lines.append(f"| {doc_link} | {entry.title} | {entry.status or ''} | {entry.progress or ''} | {rel} |")
84
+ lines.append("")
85
+ return "\n".join(lines)
86
+
87
+
88
+ def index_payload(repo_root: Path, *, out: str = "logics/INDEX.md") -> dict[str, object]:
89
+ repo_root = repo_root.resolve()
90
+ sections: list[tuple[str, list[Entry], bool]] = []
91
+ for title, rel_dir, show_progress in SECTION_DEFINITIONS:
92
+ sections.append((title, _collect_entries(repo_root, rel_dir), show_progress))
93
+
94
+ out_path = (repo_root / out).resolve()
95
+ out_dir = out_path.parent
96
+ content = "\n".join(
97
+ [
98
+ "# Logics Index",
99
+ "",
100
+ *[_render_section(title, entries, show_progress, out_dir) for title, entries, show_progress in sections],
101
+ ]
102
+ ).rstrip() + "\n"
103
+
104
+ out_path.parent.mkdir(parents=True, exist_ok=True)
105
+ out_path.write_text(content, encoding="utf-8")
106
+
107
+ try:
108
+ printable = out_path.relative_to(repo_root)
109
+ except ValueError:
110
+ printable = out_path
111
+
112
+ counts = {key: len(entries) for key, (_, entries, _) in zip(SECTION_COUNT_KEYS, sections)}
113
+ return {
114
+ "ok": True,
115
+ "output_path": printable.as_posix(),
116
+ "counts": counts,
117
+ }
118
+
119
+
120
+ def render_index(repo_root: Path, *, out: str = "logics/INDEX.md", output_format: str = "text") -> str:
121
+ payload = index_payload(repo_root, out=out)
122
+ if output_format == "json":
123
+ return json.dumps(payload, indent=2, sort_keys=True)
124
+ return f"Wrote {payload['output_path']}"
125
+
126
+
127
+ def build_parser() -> argparse.ArgumentParser:
128
+ parser = argparse.ArgumentParser(
129
+ prog="logics-manager index",
130
+ description="Generate logics/INDEX.md from workflow docs.",
131
+ )
132
+ parser.add_argument("--out", default="logics/INDEX.md")
133
+ parser.add_argument("--format", choices=("text", "json"), default="text")
134
+ return parser
135
+
136
+
137
+ def main(argv: list[str]) -> int:
138
+ args = build_parser().parse_args(argv)
139
+ repo_root = find_repo_root(Path.cwd())
140
+ output = render_index(repo_root, out=args.out, output_format=args.format)
141
+ print(output)
142
+ return 0