@grifhinz/logics-manager 2.1.2 → 2.3.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 +106 -4
- package/VERSION +1 -1
- package/clients/README.md +9 -0
- package/clients/shared-web/media/css/board.css +658 -0
- package/clients/shared-web/media/css/details.css +457 -0
- package/clients/shared-web/media/css/layout.css +123 -0
- package/clients/shared-web/media/css/toolbar.css +576 -0
- package/clients/shared-web/media/harnessApi.js +324 -0
- package/clients/shared-web/media/hostApi.js +213 -0
- package/clients/shared-web/media/hostApiContract.js +55 -0
- package/clients/shared-web/media/icon.png +0 -0
- package/clients/shared-web/media/layoutController.js +246 -0
- package/clients/shared-web/media/logics.svg +7 -0
- package/clients/shared-web/media/logicsModel.js +910 -0
- package/clients/shared-web/media/main.css +112 -0
- package/clients/shared-web/media/main.js +3 -0
- package/clients/shared-web/media/mainApp.js +1005 -0
- package/clients/shared-web/media/mainCore.js +604 -0
- package/clients/shared-web/media/mainInteractionHandlers.js +324 -0
- package/clients/shared-web/media/mainInteractions.js +378 -0
- package/clients/shared-web/media/renderBoard.js +3 -0
- package/clients/shared-web/media/renderBoardApp.js +1339 -0
- package/clients/shared-web/media/renderDetails.js +685 -0
- package/clients/shared-web/media/renderMarkdown.js +449 -0
- package/clients/shared-web/media/toolsPanelLayout.js +172 -0
- package/clients/shared-web/media/uiStatus.js +54 -0
- package/clients/shared-web/media/webviewChrome.js +405 -0
- package/clients/shared-web/media/webviewPersistence.js +116 -0
- package/clients/shared-web/media/webviewSelectors.js +491 -0
- package/clients/viewer/README.md +5 -0
- package/clients/viewer/browser-host.js +847 -0
- package/clients/viewer/index.html +237 -0
- package/clients/viewer/viewer.css +433 -0
- package/logics_manager/assist.py +94 -63
- package/logics_manager/assist_handoff.py +132 -0
- package/logics_manager/assist_surface.py +38 -0
- package/logics_manager/cli.py +152 -12
- package/logics_manager/cli_output.py +18 -0
- package/logics_manager/flow.py +1360 -84
- package/logics_manager/flow_evidence.py +63 -0
- 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/logics_manager/update_check.py +138 -0
- package/logics_manager/viewer.py +533 -0
- package/package.json +12 -6
- package/pyproject.toml +1 -1
package/logics_manager/cli.py
CHANGED
|
@@ -11,11 +11,16 @@ 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
|
|
23
|
+
from .update_check import get_update_notice
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
DEFAULT_SELF_UPDATE_PY_PACKAGE = "logics-manager"
|
|
@@ -28,14 +33,30 @@ ROOT_COMMANDS = (
|
|
|
28
33
|
"assist",
|
|
29
34
|
"audit",
|
|
30
35
|
"index",
|
|
36
|
+
"health",
|
|
37
|
+
"followups",
|
|
38
|
+
"product-consistency",
|
|
39
|
+
"status",
|
|
31
40
|
"lint",
|
|
41
|
+
"view",
|
|
32
42
|
"config",
|
|
33
43
|
"doctor",
|
|
34
44
|
"mcp",
|
|
35
45
|
"self-update",
|
|
46
|
+
"search",
|
|
36
47
|
)
|
|
37
48
|
|
|
38
49
|
|
|
50
|
+
def _expand_json_alias(argv: list[str]) -> list[str]:
|
|
51
|
+
expanded: list[str] = []
|
|
52
|
+
for arg in argv:
|
|
53
|
+
if arg == "--json":
|
|
54
|
+
expanded.extend(["--format", "json"])
|
|
55
|
+
else:
|
|
56
|
+
expanded.append(arg)
|
|
57
|
+
return expanded
|
|
58
|
+
|
|
59
|
+
|
|
39
60
|
def _build_root_help() -> str:
|
|
40
61
|
sections = [
|
|
41
62
|
"Logics Manager CLI",
|
|
@@ -52,17 +73,24 @@ def _build_root_help() -> str:
|
|
|
52
73
|
"Common workflows:",
|
|
53
74
|
' logics-manager flow new request --title "My request"',
|
|
54
75
|
" logics-manager audit --group-by-doc",
|
|
76
|
+
" logics-manager status",
|
|
55
77
|
" logics-manager sync refresh-mermaid-signatures",
|
|
56
78
|
" logics-manager mcp tunnel --repo-root . --port 8765",
|
|
57
79
|
"",
|
|
58
80
|
"Workflow authoring:",
|
|
59
81
|
" flow Create, promote, split, close, and finish workflow docs.",
|
|
60
|
-
" Subcommands: new, list, companion, promote, split, close, finish",
|
|
82
|
+
" Subcommands: new, list, companion, deliver, validate-closeout, repair, closeout, promote, split, close, finish",
|
|
61
83
|
" sync Maintain generated workflow state and doc metadata.",
|
|
62
84
|
" Subcommands: close-eligible-requests, refresh-mermaid-signatures,",
|
|
63
85
|
" schema-status, read-doc, list-docs, search-docs,",
|
|
64
86
|
" update-indicators, append-note, context-pack, export-graph",
|
|
65
87
|
" index Generate logics/INDEX.md from the workflow corpus.",
|
|
88
|
+
" health Show workflow health counts and issue signals.",
|
|
89
|
+
" followups List follow-up areas with request creation commands.",
|
|
90
|
+
" product-consistency Check product brief lineage links.",
|
|
91
|
+
" status Summarize open workflow docs and next actions.",
|
|
92
|
+
" search Search workflow docs directly.",
|
|
93
|
+
" view Start a local read-only browser viewer for the Logics corpus.",
|
|
66
94
|
"",
|
|
67
95
|
"Validation:",
|
|
68
96
|
" lint Check filenames, headings, indicators, and changed-doc hygiene.",
|
|
@@ -105,6 +133,18 @@ def get_cli_version() -> str:
|
|
|
105
133
|
return "0.0.0"
|
|
106
134
|
|
|
107
135
|
|
|
136
|
+
def _is_json_mode(argv: list[str]) -> bool:
|
|
137
|
+
return "--json" in argv or any(argv[index] == "--format" and index + 1 < len(argv) and argv[index + 1] == "json" for index in range(len(argv)))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _maybe_print_update_notice(command: str, argv: list[str]) -> None:
|
|
141
|
+
if command in {"self-update", "mcp", "view"} or _is_json_mode(argv) or not sys.stdout.isatty():
|
|
142
|
+
return
|
|
143
|
+
notice = get_update_notice(get_cli_version())
|
|
144
|
+
if notice:
|
|
145
|
+
print(notice, file=sys.stderr)
|
|
146
|
+
|
|
147
|
+
|
|
108
148
|
def main(argv: list[str] | None = None) -> int:
|
|
109
149
|
if argv is None:
|
|
110
150
|
argv = sys.argv[1:]
|
|
@@ -118,9 +158,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
118
158
|
print(f"logics-manager {get_cli_version()}")
|
|
119
159
|
return 0
|
|
120
160
|
|
|
161
|
+
argv = _expand_json_alias(argv)
|
|
121
162
|
command = argv[0]
|
|
122
163
|
if command not in ROOT_COMMANDS:
|
|
123
164
|
raise SystemExit(f"Unsupported command: {command}")
|
|
165
|
+
_maybe_print_update_notice(command, argv)
|
|
124
166
|
|
|
125
167
|
rest = argv[1:]
|
|
126
168
|
if command == "config":
|
|
@@ -129,7 +171,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
129
171
|
config_args = rest[1:]
|
|
130
172
|
parser = argparse.ArgumentParser(prog="logics-manager config show", add_help=False)
|
|
131
173
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
132
|
-
parsed
|
|
174
|
+
parsed = parser.parse_args(config_args)
|
|
133
175
|
repo_root = find_repo_root(Path.cwd())
|
|
134
176
|
try:
|
|
135
177
|
output = render_config_show(repo_root, output_format=parsed.format)
|
|
@@ -141,7 +183,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
141
183
|
doctor_args = rest
|
|
142
184
|
parser = argparse.ArgumentParser(prog="logics-manager doctor", add_help=False)
|
|
143
185
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
144
|
-
parsed
|
|
186
|
+
parsed = parser.parse_args(doctor_args)
|
|
145
187
|
repo_root = find_repo_root(Path.cwd())
|
|
146
188
|
try:
|
|
147
189
|
output = render_doctor(repo_root, output_format=parsed.format)
|
|
@@ -153,7 +195,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
153
195
|
parser = argparse.ArgumentParser(prog="logics-manager bootstrap", add_help=False)
|
|
154
196
|
parser.add_argument("--check", action="store_true")
|
|
155
197
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
156
|
-
parsed
|
|
198
|
+
parsed = parser.parse_args(rest)
|
|
157
199
|
try:
|
|
158
200
|
repo_root = find_repo_root(Path.cwd())
|
|
159
201
|
except ConfigError:
|
|
@@ -167,7 +209,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
167
209
|
parser.add_argument("--package", default=DEFAULT_SELF_UPDATE_PACKAGE)
|
|
168
210
|
parser.add_argument("--python-package", default=DEFAULT_SELF_UPDATE_PY_PACKAGE)
|
|
169
211
|
parser.add_argument("--dry-run", action="store_true")
|
|
170
|
-
parsed
|
|
212
|
+
parsed = parser.parse_args(rest)
|
|
171
213
|
|
|
172
214
|
manager = parsed.manager
|
|
173
215
|
if manager == "auto":
|
|
@@ -196,7 +238,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
196
238
|
target = parsed.python_package if manager == "pip" else parsed.package
|
|
197
239
|
print(f"Updated {target} via {manager}.")
|
|
198
240
|
return result.returncode
|
|
199
|
-
if command == "flow" and (rest[:1] in (["new"], ["list"], ["companion"], ["promote"], ["split"], ["close"], ["finish"]) or rest[:1] in HELP_ARGV):
|
|
241
|
+
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
242
|
from .flow import main as flow_main
|
|
201
243
|
|
|
202
244
|
return flow_main(rest)
|
|
@@ -207,16 +249,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
207
249
|
|
|
208
250
|
return sync_main(rest)
|
|
209
251
|
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:
|
|
252
|
+
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
253
|
raise SystemExit("Unsupported assist subcommand for the native CLI slice.")
|
|
212
254
|
return assist_main(rest)
|
|
213
255
|
if command == "mcp":
|
|
214
256
|
from .mcp import main as mcp_main
|
|
215
257
|
|
|
216
258
|
return mcp_main(rest)
|
|
259
|
+
if command == "view":
|
|
260
|
+
from .viewer import main as viewer_main
|
|
261
|
+
|
|
262
|
+
return viewer_main(rest)
|
|
217
263
|
if command == "audit":
|
|
218
264
|
audit_parser = build_audit_parser()
|
|
219
|
-
parsed
|
|
265
|
+
parsed = audit_parser.parse_args(rest)
|
|
220
266
|
repo_root = find_repo_root(Path.cwd())
|
|
221
267
|
try:
|
|
222
268
|
payload = audit_payload(
|
|
@@ -253,25 +299,119 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
253
299
|
except ConfigError as exc:
|
|
254
300
|
raise SystemExit(str(exc)) from exc
|
|
255
301
|
print(output)
|
|
256
|
-
return 0
|
|
302
|
+
return 0
|
|
257
303
|
if command == "index":
|
|
258
304
|
parser = argparse.ArgumentParser(prog="logics-manager index", add_help=False)
|
|
259
305
|
parser.add_argument("--out", default="logics/INDEX.md")
|
|
260
306
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
261
|
-
parsed
|
|
307
|
+
parsed = parser.parse_args(rest)
|
|
262
308
|
repo_root = find_repo_root(Path.cwd())
|
|
263
309
|
try:
|
|
264
310
|
payload = index_payload(repo_root, out=parsed.out)
|
|
265
311
|
except ConfigError as exc:
|
|
266
312
|
raise SystemExit(str(exc)) from exc
|
|
267
|
-
output =
|
|
313
|
+
output = render_payload(payload, parsed.format, f"Wrote {payload['output_path']}")
|
|
268
314
|
print(output)
|
|
269
315
|
return 0 if payload["ok"] else 1
|
|
316
|
+
if command == "status":
|
|
317
|
+
parser = argparse.ArgumentParser(prog="logics-manager status", add_help=False)
|
|
318
|
+
parser.add_argument("--limit", type=int, default=10)
|
|
319
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
320
|
+
parsed = parser.parse_args(rest)
|
|
321
|
+
repo_root = find_repo_root(Path.cwd())
|
|
322
|
+
try:
|
|
323
|
+
payload = status_payload(repo_root, limit=parsed.limit)
|
|
324
|
+
output = render_status(repo_root, output_format=parsed.format, limit=parsed.limit)
|
|
325
|
+
except ConfigError as exc:
|
|
326
|
+
raise SystemExit(str(exc)) from exc
|
|
327
|
+
print(output)
|
|
328
|
+
return 0
|
|
329
|
+
if command == "health":
|
|
330
|
+
parser = argparse.ArgumentParser(prog="logics-manager health", add_help=False)
|
|
331
|
+
parser.add_argument("--limit", type=int, default=10)
|
|
332
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
333
|
+
parsed = parser.parse_args(rest)
|
|
334
|
+
repo_root = find_repo_root(Path.cwd())
|
|
335
|
+
try:
|
|
336
|
+
payload = health_payload(repo_root, limit=parsed.limit)
|
|
337
|
+
output = render_health(repo_root, output_format=parsed.format, limit=parsed.limit)
|
|
338
|
+
except ConfigError as exc:
|
|
339
|
+
raise SystemExit(str(exc)) from exc
|
|
340
|
+
print(output)
|
|
341
|
+
return 0
|
|
342
|
+
if command == "followups":
|
|
343
|
+
parser = argparse.ArgumentParser(prog="logics-manager followups", add_help=False)
|
|
344
|
+
parser.add_argument("--limit", type=int, default=50)
|
|
345
|
+
parser.add_argument("--source-kind", choices=("all", "request", "backlog", "task", "product", "architecture"), default="all")
|
|
346
|
+
parser.add_argument("--include-closed", action="store_true")
|
|
347
|
+
parser.add_argument("--closed-only", action="store_true")
|
|
348
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
349
|
+
parsed = parser.parse_args(rest)
|
|
350
|
+
if parsed.include_closed and parsed.closed_only:
|
|
351
|
+
raise SystemExit("--include-closed and --closed-only are mutually exclusive.")
|
|
352
|
+
repo_root = find_repo_root(Path.cwd())
|
|
353
|
+
try:
|
|
354
|
+
payload = followups_payload(
|
|
355
|
+
repo_root,
|
|
356
|
+
limit=parsed.limit,
|
|
357
|
+
source_kind=parsed.source_kind,
|
|
358
|
+
include_closed=parsed.include_closed,
|
|
359
|
+
closed_only=parsed.closed_only,
|
|
360
|
+
)
|
|
361
|
+
output = render_followups(
|
|
362
|
+
repo_root,
|
|
363
|
+
output_format=parsed.format,
|
|
364
|
+
limit=parsed.limit,
|
|
365
|
+
source_kind=parsed.source_kind,
|
|
366
|
+
include_closed=parsed.include_closed,
|
|
367
|
+
closed_only=parsed.closed_only,
|
|
368
|
+
)
|
|
369
|
+
except ConfigError as exc:
|
|
370
|
+
raise SystemExit(str(exc)) from exc
|
|
371
|
+
print(output)
|
|
372
|
+
return 0 if payload["ok"] else 1
|
|
373
|
+
if command == "search":
|
|
374
|
+
parser = argparse.ArgumentParser(prog="logics-manager search", add_help=False)
|
|
375
|
+
parser.add_argument("query")
|
|
376
|
+
parser.add_argument("--kind", choices=("all", "request", "backlog", "task"), default="all")
|
|
377
|
+
parser.add_argument("--status")
|
|
378
|
+
parser.add_argument("--limit", type=int, default=20)
|
|
379
|
+
parser.add_argument("--max-snippet-chars", type=int, default=240)
|
|
380
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
381
|
+
parsed = parser.parse_args(rest)
|
|
382
|
+
repo_root = find_repo_root(Path.cwd())
|
|
383
|
+
payload = search_logics_docs_payload(
|
|
384
|
+
repo_root,
|
|
385
|
+
parsed.query,
|
|
386
|
+
kind=parsed.kind,
|
|
387
|
+
status=parsed.status,
|
|
388
|
+
limit=parsed.limit,
|
|
389
|
+
max_snippet_chars=parsed.max_snippet_chars,
|
|
390
|
+
)
|
|
391
|
+
if parsed.format == "json":
|
|
392
|
+
output = render_payload(payload, "json")
|
|
393
|
+
else:
|
|
394
|
+
lines = [f"Search `{payload['query']}`: {payload['returned_count']} match(es)"]
|
|
395
|
+
for match in payload["matches"]:
|
|
396
|
+
lines.append(f"- {match['ref']}:{match['line']} {match['title']}")
|
|
397
|
+
output = "\n".join(lines)
|
|
398
|
+
print(output)
|
|
399
|
+
return 0
|
|
400
|
+
if command == "product-consistency":
|
|
401
|
+
parser = argparse.ArgumentParser(prog="logics-manager product-consistency", add_help=False)
|
|
402
|
+
parser.add_argument("--limit", type=int, default=50)
|
|
403
|
+
parser.add_argument("--strict", action="store_true")
|
|
404
|
+
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
405
|
+
parsed = parser.parse_args(rest)
|
|
406
|
+
repo_root = find_repo_root(Path.cwd())
|
|
407
|
+
payload = product_consistency_payload(repo_root, limit=parsed.limit)
|
|
408
|
+
print(render_product_consistency(repo_root, output_format=parsed.format, limit=parsed.limit))
|
|
409
|
+
return 1 if parsed.strict and not payload["ok"] else 0
|
|
270
410
|
if command == "lint":
|
|
271
411
|
parser = argparse.ArgumentParser(prog="logics-manager lint", add_help=False)
|
|
272
412
|
parser.add_argument("--require-status", action="store_true")
|
|
273
413
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
274
|
-
parsed
|
|
414
|
+
parsed = parser.parse_args(rest)
|
|
275
415
|
repo_root = find_repo_root(Path.cwd())
|
|
276
416
|
try:
|
|
277
417
|
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)
|