@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.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/VERSION +1 -0
- package/logics_manager/__init__.py +5 -0
- package/logics_manager/__main__.py +9 -0
- package/logics_manager/assist.py +2211 -0
- package/logics_manager/audit.py +990 -0
- package/logics_manager/bootstrap.py +123 -0
- package/logics_manager/cli.py +183 -0
- package/logics_manager/config.py +251 -0
- package/logics_manager/doctor.py +127 -0
- package/logics_manager/flow.py +1449 -0
- package/logics_manager/index.py +142 -0
- package/logics_manager/lint.py +622 -0
- package/logics_manager/sync.py +604 -0
- package/package.json +162 -0
- package/pyproject.toml +15 -0
- package/scripts/logics-manager.py +15 -0
- package/scripts/npm/logics-manager.mjs +96 -0
|
@@ -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
|