@grifhinz/logics-manager 2.1.2 → 2.2.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/README.md +47 -4
- package/VERSION +1 -1
- package/logics_manager/assist.py +185 -21
- package/logics_manager/cli.py +132 -12
- package/logics_manager/cli_output.py +18 -0
- package/logics_manager/flow.py +1257 -83
- package/logics_manager/index.py +3 -7
- package/logics_manager/insights.py +418 -0
- package/logics_manager/mcp.py +50 -0
- package/logics_manager/path_utils.py +31 -0
- package/logics_manager/sync.py +24 -12
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/logics_manager/cli.py
CHANGED
|
@@ -11,9 +11,13 @@ from .bootstrap import bootstrap_payload, render_bootstrap
|
|
|
11
11
|
from .assist import main as assist_main
|
|
12
12
|
from .audit import audit_payload, build_parser as build_audit_parser
|
|
13
13
|
from .audit import render_audit
|
|
14
|
+
from .cli_output import render_payload
|
|
14
15
|
from .config import ConfigError, find_repo_root, render_config_show
|
|
15
16
|
from .index import index_payload, render_index
|
|
17
|
+
from .insights import followups_payload, health_payload, render_followups, render_health, render_status, status_payload
|
|
18
|
+
from .insights import product_consistency_payload, render_product_consistency
|
|
16
19
|
from .lint import lint_payload, render_lint
|
|
20
|
+
from .sync import search_logics_docs_payload
|
|
17
21
|
from .doctor import render_doctor
|
|
18
22
|
from .termstyle import colorize_help
|
|
19
23
|
|
|
@@ -28,14 +32,29 @@ ROOT_COMMANDS = (
|
|
|
28
32
|
"assist",
|
|
29
33
|
"audit",
|
|
30
34
|
"index",
|
|
35
|
+
"health",
|
|
36
|
+
"followups",
|
|
37
|
+
"product-consistency",
|
|
38
|
+
"status",
|
|
31
39
|
"lint",
|
|
32
40
|
"config",
|
|
33
41
|
"doctor",
|
|
34
42
|
"mcp",
|
|
35
43
|
"self-update",
|
|
44
|
+
"search",
|
|
36
45
|
)
|
|
37
46
|
|
|
38
47
|
|
|
48
|
+
def _expand_json_alias(argv: list[str]) -> list[str]:
|
|
49
|
+
expanded: list[str] = []
|
|
50
|
+
for arg in argv:
|
|
51
|
+
if arg == "--json":
|
|
52
|
+
expanded.extend(["--format", "json"])
|
|
53
|
+
else:
|
|
54
|
+
expanded.append(arg)
|
|
55
|
+
return expanded
|
|
56
|
+
|
|
57
|
+
|
|
39
58
|
def _build_root_help() -> str:
|
|
40
59
|
sections = [
|
|
41
60
|
"Logics Manager CLI",
|
|
@@ -52,17 +71,23 @@ def _build_root_help() -> str:
|
|
|
52
71
|
"Common workflows:",
|
|
53
72
|
' logics-manager flow new request --title "My request"',
|
|
54
73
|
" logics-manager audit --group-by-doc",
|
|
74
|
+
" logics-manager status",
|
|
55
75
|
" logics-manager sync refresh-mermaid-signatures",
|
|
56
76
|
" logics-manager mcp tunnel --repo-root . --port 8765",
|
|
57
77
|
"",
|
|
58
78
|
"Workflow authoring:",
|
|
59
79
|
" flow Create, promote, split, close, and finish workflow docs.",
|
|
60
|
-
" Subcommands: new, list, companion, promote, split, close, finish",
|
|
80
|
+
" Subcommands: new, list, companion, deliver, validate-closeout, repair, closeout, promote, split, close, finish",
|
|
61
81
|
" sync Maintain generated workflow state and doc metadata.",
|
|
62
82
|
" Subcommands: close-eligible-requests, refresh-mermaid-signatures,",
|
|
63
83
|
" schema-status, read-doc, list-docs, search-docs,",
|
|
64
84
|
" update-indicators, append-note, context-pack, export-graph",
|
|
65
85
|
" index Generate logics/INDEX.md from the workflow corpus.",
|
|
86
|
+
" health Show workflow health counts and issue signals.",
|
|
87
|
+
" followups List follow-up areas with request creation commands.",
|
|
88
|
+
" product-consistency Check product brief lineage links.",
|
|
89
|
+
" status Summarize open workflow docs and next actions.",
|
|
90
|
+
" search Search workflow docs directly.",
|
|
66
91
|
"",
|
|
67
92
|
"Validation:",
|
|
68
93
|
" lint Check filenames, headings, indicators, and changed-doc hygiene.",
|
|
@@ -118,6 +143,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
118
143
|
print(f"logics-manager {get_cli_version()}")
|
|
119
144
|
return 0
|
|
120
145
|
|
|
146
|
+
argv = _expand_json_alias(argv)
|
|
121
147
|
command = argv[0]
|
|
122
148
|
if command not in ROOT_COMMANDS:
|
|
123
149
|
raise SystemExit(f"Unsupported command: {command}")
|
|
@@ -129,7 +155,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
129
155
|
config_args = rest[1:]
|
|
130
156
|
parser = argparse.ArgumentParser(prog="logics-manager config show", add_help=False)
|
|
131
157
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
132
|
-
parsed
|
|
158
|
+
parsed = parser.parse_args(config_args)
|
|
133
159
|
repo_root = find_repo_root(Path.cwd())
|
|
134
160
|
try:
|
|
135
161
|
output = render_config_show(repo_root, output_format=parsed.format)
|
|
@@ -141,7 +167,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
141
167
|
doctor_args = rest
|
|
142
168
|
parser = argparse.ArgumentParser(prog="logics-manager doctor", add_help=False)
|
|
143
169
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
144
|
-
parsed
|
|
170
|
+
parsed = parser.parse_args(doctor_args)
|
|
145
171
|
repo_root = find_repo_root(Path.cwd())
|
|
146
172
|
try:
|
|
147
173
|
output = render_doctor(repo_root, output_format=parsed.format)
|
|
@@ -153,7 +179,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
153
179
|
parser = argparse.ArgumentParser(prog="logics-manager bootstrap", add_help=False)
|
|
154
180
|
parser.add_argument("--check", action="store_true")
|
|
155
181
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
156
|
-
parsed
|
|
182
|
+
parsed = parser.parse_args(rest)
|
|
157
183
|
try:
|
|
158
184
|
repo_root = find_repo_root(Path.cwd())
|
|
159
185
|
except ConfigError:
|
|
@@ -167,7 +193,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
167
193
|
parser.add_argument("--package", default=DEFAULT_SELF_UPDATE_PACKAGE)
|
|
168
194
|
parser.add_argument("--python-package", default=DEFAULT_SELF_UPDATE_PY_PACKAGE)
|
|
169
195
|
parser.add_argument("--dry-run", action="store_true")
|
|
170
|
-
parsed
|
|
196
|
+
parsed = parser.parse_args(rest)
|
|
171
197
|
|
|
172
198
|
manager = parsed.manager
|
|
173
199
|
if manager == "auto":
|
|
@@ -196,7 +222,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
196
222
|
target = parsed.python_package if manager == "pip" else parsed.package
|
|
197
223
|
print(f"Updated {target} via {manager}.")
|
|
198
224
|
return result.returncode
|
|
199
|
-
if command == "flow" and (rest[:1] in (["new"], ["list"], ["companion"], ["promote"], ["split"], ["close"], ["finish"]) or rest[:1] in HELP_ARGV):
|
|
225
|
+
if command == "flow" and (rest[:1] in (["new"], ["list"], ["companion"], ["deliver"], ["validate-closeout"], ["repair"], ["closeout"], ["promote"], ["split"], ["close"], ["finish"]) or rest[:1] in HELP_ARGV):
|
|
200
226
|
from .flow import main as flow_main
|
|
201
227
|
|
|
202
228
|
return flow_main(rest)
|
|
@@ -207,7 +233,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
207
233
|
|
|
208
234
|
return sync_main(rest)
|
|
209
235
|
if command == "assist":
|
|
210
|
-
if rest[:1] not in (["runtime-status"], ["diff-risk"], ["commit-plan"], ["changed-surface-summary"], ["doc-consistency"], ["review-checklist"], ["validation-checklist"], ["validation-summary"], ["test-impact-summary"], ["roi-report"], ["next-step"], ["claude-bridges"], ["claude-instructions"], ["request-draft"], ["spec-first-pass"], ["backlog-groom"], ["closure-summary"], ["context"]) and rest[:1] not in HELP_ARGV:
|
|
236
|
+
if rest[:1] not in (["runtime-status"], ["diff-risk"], ["commit-plan"], ["changed-surface-summary"], ["doc-consistency"], ["review-checklist"], ["validation-checklist"], ["validation-summary"], ["test-impact-summary"], ["roi-report"], ["next-step"], ["claude-bridges"], ["claude-instructions"], ["request-draft"], ["spec-first-pass"], ["backlog-groom"], ["closure-summary"], ["handoff"], ["context"]) and rest[:1] not in HELP_ARGV:
|
|
211
237
|
raise SystemExit("Unsupported assist subcommand for the native CLI slice.")
|
|
212
238
|
return assist_main(rest)
|
|
213
239
|
if command == "mcp":
|
|
@@ -216,7 +242,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
216
242
|
return mcp_main(rest)
|
|
217
243
|
if command == "audit":
|
|
218
244
|
audit_parser = build_audit_parser()
|
|
219
|
-
parsed
|
|
245
|
+
parsed = audit_parser.parse_args(rest)
|
|
220
246
|
repo_root = find_repo_root(Path.cwd())
|
|
221
247
|
try:
|
|
222
248
|
payload = audit_payload(
|
|
@@ -253,25 +279,119 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
253
279
|
except ConfigError as exc:
|
|
254
280
|
raise SystemExit(str(exc)) from exc
|
|
255
281
|
print(output)
|
|
256
|
-
return 0
|
|
282
|
+
return 0
|
|
257
283
|
if command == "index":
|
|
258
284
|
parser = argparse.ArgumentParser(prog="logics-manager index", add_help=False)
|
|
259
285
|
parser.add_argument("--out", default="logics/INDEX.md")
|
|
260
286
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
261
|
-
parsed
|
|
287
|
+
parsed = parser.parse_args(rest)
|
|
262
288
|
repo_root = find_repo_root(Path.cwd())
|
|
263
289
|
try:
|
|
264
290
|
payload = index_payload(repo_root, out=parsed.out)
|
|
265
291
|
except ConfigError as exc:
|
|
266
292
|
raise SystemExit(str(exc)) from exc
|
|
267
|
-
output =
|
|
293
|
+
output = render_payload(payload, parsed.format, f"Wrote {payload['output_path']}")
|
|
294
|
+
print(output)
|
|
295
|
+
return 0 if payload["ok"] else 1
|
|
296
|
+
if command == "status":
|
|
297
|
+
parser = argparse.ArgumentParser(prog="logics-manager status", add_help=False)
|
|
298
|
+
parser.add_argument("--limit", type=int, default=10)
|
|
299
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
300
|
+
parsed = parser.parse_args(rest)
|
|
301
|
+
repo_root = find_repo_root(Path.cwd())
|
|
302
|
+
try:
|
|
303
|
+
payload = status_payload(repo_root, limit=parsed.limit)
|
|
304
|
+
output = render_status(repo_root, output_format=parsed.format, limit=parsed.limit)
|
|
305
|
+
except ConfigError as exc:
|
|
306
|
+
raise SystemExit(str(exc)) from exc
|
|
307
|
+
print(output)
|
|
308
|
+
return 0
|
|
309
|
+
if command == "health":
|
|
310
|
+
parser = argparse.ArgumentParser(prog="logics-manager health", add_help=False)
|
|
311
|
+
parser.add_argument("--limit", type=int, default=10)
|
|
312
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
313
|
+
parsed = parser.parse_args(rest)
|
|
314
|
+
repo_root = find_repo_root(Path.cwd())
|
|
315
|
+
try:
|
|
316
|
+
payload = health_payload(repo_root, limit=parsed.limit)
|
|
317
|
+
output = render_health(repo_root, output_format=parsed.format, limit=parsed.limit)
|
|
318
|
+
except ConfigError as exc:
|
|
319
|
+
raise SystemExit(str(exc)) from exc
|
|
320
|
+
print(output)
|
|
321
|
+
return 0
|
|
322
|
+
if command == "followups":
|
|
323
|
+
parser = argparse.ArgumentParser(prog="logics-manager followups", add_help=False)
|
|
324
|
+
parser.add_argument("--limit", type=int, default=50)
|
|
325
|
+
parser.add_argument("--source-kind", choices=("all", "request", "backlog", "task", "product", "architecture"), default="all")
|
|
326
|
+
parser.add_argument("--include-closed", action="store_true")
|
|
327
|
+
parser.add_argument("--closed-only", action="store_true")
|
|
328
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
329
|
+
parsed = parser.parse_args(rest)
|
|
330
|
+
if parsed.include_closed and parsed.closed_only:
|
|
331
|
+
raise SystemExit("--include-closed and --closed-only are mutually exclusive.")
|
|
332
|
+
repo_root = find_repo_root(Path.cwd())
|
|
333
|
+
try:
|
|
334
|
+
payload = followups_payload(
|
|
335
|
+
repo_root,
|
|
336
|
+
limit=parsed.limit,
|
|
337
|
+
source_kind=parsed.source_kind,
|
|
338
|
+
include_closed=parsed.include_closed,
|
|
339
|
+
closed_only=parsed.closed_only,
|
|
340
|
+
)
|
|
341
|
+
output = render_followups(
|
|
342
|
+
repo_root,
|
|
343
|
+
output_format=parsed.format,
|
|
344
|
+
limit=parsed.limit,
|
|
345
|
+
source_kind=parsed.source_kind,
|
|
346
|
+
include_closed=parsed.include_closed,
|
|
347
|
+
closed_only=parsed.closed_only,
|
|
348
|
+
)
|
|
349
|
+
except ConfigError as exc:
|
|
350
|
+
raise SystemExit(str(exc)) from exc
|
|
268
351
|
print(output)
|
|
269
352
|
return 0 if payload["ok"] else 1
|
|
353
|
+
if command == "search":
|
|
354
|
+
parser = argparse.ArgumentParser(prog="logics-manager search", add_help=False)
|
|
355
|
+
parser.add_argument("query")
|
|
356
|
+
parser.add_argument("--kind", choices=("all", "request", "backlog", "task"), default="all")
|
|
357
|
+
parser.add_argument("--status")
|
|
358
|
+
parser.add_argument("--limit", type=int, default=20)
|
|
359
|
+
parser.add_argument("--max-snippet-chars", type=int, default=240)
|
|
360
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
361
|
+
parsed = parser.parse_args(rest)
|
|
362
|
+
repo_root = find_repo_root(Path.cwd())
|
|
363
|
+
payload = search_logics_docs_payload(
|
|
364
|
+
repo_root,
|
|
365
|
+
parsed.query,
|
|
366
|
+
kind=parsed.kind,
|
|
367
|
+
status=parsed.status,
|
|
368
|
+
limit=parsed.limit,
|
|
369
|
+
max_snippet_chars=parsed.max_snippet_chars,
|
|
370
|
+
)
|
|
371
|
+
if parsed.format == "json":
|
|
372
|
+
output = render_payload(payload, "json")
|
|
373
|
+
else:
|
|
374
|
+
lines = [f"Search `{payload['query']}`: {payload['returned_count']} match(es)"]
|
|
375
|
+
for match in payload["matches"]:
|
|
376
|
+
lines.append(f"- {match['ref']}:{match['line']} {match['title']}")
|
|
377
|
+
output = "\n".join(lines)
|
|
378
|
+
print(output)
|
|
379
|
+
return 0
|
|
380
|
+
if command == "product-consistency":
|
|
381
|
+
parser = argparse.ArgumentParser(prog="logics-manager product-consistency", add_help=False)
|
|
382
|
+
parser.add_argument("--limit", type=int, default=50)
|
|
383
|
+
parser.add_argument("--strict", action="store_true")
|
|
384
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
385
|
+
parsed = parser.parse_args(rest)
|
|
386
|
+
repo_root = find_repo_root(Path.cwd())
|
|
387
|
+
payload = product_consistency_payload(repo_root, limit=parsed.limit)
|
|
388
|
+
print(render_product_consistency(repo_root, output_format=parsed.format, limit=parsed.limit))
|
|
389
|
+
return 1 if parsed.strict and not payload["ok"] else 0
|
|
270
390
|
if command == "lint":
|
|
271
391
|
parser = argparse.ArgumentParser(prog="logics-manager lint", add_help=False)
|
|
272
392
|
parser.add_argument("--require-status", action="store_true")
|
|
273
393
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
274
|
-
parsed
|
|
394
|
+
parsed = parser.parse_args(rest)
|
|
275
395
|
repo_root = find_repo_root(Path.cwd())
|
|
276
396
|
try:
|
|
277
397
|
payload = lint_payload(repo_root, require_status=parsed.require_status)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def render_payload(payload: dict[str, object], output_format: str, text: str | Callable[[], str] | None = None) -> str:
|
|
8
|
+
if output_format == "json":
|
|
9
|
+
return json.dumps(payload, indent=2, sort_keys=True)
|
|
10
|
+
if callable(text):
|
|
11
|
+
return text()
|
|
12
|
+
return text or ""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def print_payload(payload: dict[str, object], output_format: str, text: str | Callable[[], str] | None = None) -> None:
|
|
16
|
+
output = render_payload(payload, output_format, text)
|
|
17
|
+
if output:
|
|
18
|
+
print(output)
|