@grifhinz/logics-manager 2.1.1 → 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 +80 -5
- package/VERSION +1 -1
- package/logics_manager/assist.py +185 -21
- package/logics_manager/audit.py +72 -18
- package/logics_manager/cli.py +162 -61
- 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/lint.py +21 -7
- package/logics_manager/mcp.py +385 -27
- package/logics_manager/path_utils.py +31 -0
- package/logics_manager/sync.py +24 -12
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/scripts/npm/logics-manager.mjs +13 -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,78 +32,80 @@ 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",
|
|
42
|
-
"Canonical CLI for workflow, validation, and runtime ops.",
|
|
61
|
+
"Canonical CLI for Logics workflow, validation, MCP, and runtime ops.",
|
|
43
62
|
"",
|
|
44
63
|
"Usage:",
|
|
45
64
|
" logics-manager <command> [args...]",
|
|
46
|
-
" logics-manager
|
|
65
|
+
" logics-manager <command> --help",
|
|
47
66
|
"",
|
|
48
67
|
"Top-level options:",
|
|
49
68
|
" -h, --help Show this help message and exit.",
|
|
50
|
-
" -v, --version Print the installed version.",
|
|
51
|
-
" --version Print the installed version.",
|
|
52
|
-
"",
|
|
53
|
-
"Commands:",
|
|
54
|
-
" bootstrap",
|
|
55
|
-
" Prepare or check the workflow tree and generated instructions.",
|
|
56
|
-
" Options: --check, --format {text,json}",
|
|
57
|
-
"",
|
|
58
|
-
" flow",
|
|
59
|
-
" Create and manage workflow docs.",
|
|
60
|
-
" Subcommands: new, list, companion, promote, split, close, finish",
|
|
61
|
-
" Key flags: --title, --slug, --from-version, --understanding, --confidence, --status, --complexity, --theme, --progress, --format {text,json}, --dry-run",
|
|
62
|
-
"",
|
|
63
|
-
" sync",
|
|
64
|
-
" Synchronize workflow transitions and exports.",
|
|
65
|
-
" Subcommands: close-eligible-requests, refresh-mermaid-signatures, schema-status, read-doc, list-docs, search-docs, update-indicators, append-note, context-pack, export-graph",
|
|
69
|
+
" -v, --version Print the installed version and exit.",
|
|
66
70
|
"",
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
" Options: --stale-days, --skip-ac-traceability, --skip-gates, --legacy-cutoff-version, --format {text,json}, --group-by-doc, --autofix-ac-traceability, --paths, --refs, --since-version, --token-hygiene, --autofix-structure, --governance-profile",
|
|
74
|
-
"",
|
|
75
|
-
" index",
|
|
76
|
-
" Generate `logics/INDEX.md` from the workflow corpus.",
|
|
77
|
-
" Options: --out, --format {text,json}",
|
|
78
|
-
"",
|
|
79
|
-
" lint",
|
|
80
|
-
" Lint workflow documents for filenames, headings, and indicators.",
|
|
81
|
-
" Options: --require-status, --format {text,json}",
|
|
82
|
-
"",
|
|
83
|
-
" config show",
|
|
84
|
-
" Render the merged runtime config.",
|
|
85
|
-
" Options: --format {text,json}",
|
|
71
|
+
"Common workflows:",
|
|
72
|
+
' logics-manager flow new request --title "My request"',
|
|
73
|
+
" logics-manager audit --group-by-doc",
|
|
74
|
+
" logics-manager status",
|
|
75
|
+
" logics-manager sync refresh-mermaid-signatures",
|
|
76
|
+
" logics-manager mcp tunnel --repo-root . --port 8765",
|
|
86
77
|
"",
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
78
|
+
"Workflow authoring:",
|
|
79
|
+
" flow Create, promote, split, close, and finish workflow docs.",
|
|
80
|
+
" Subcommands: new, list, companion, deliver, validate-closeout, repair, closeout, promote, split, close, finish",
|
|
81
|
+
" sync Maintain generated workflow state and doc metadata.",
|
|
82
|
+
" Subcommands: close-eligible-requests, refresh-mermaid-signatures,",
|
|
83
|
+
" schema-status, read-doc, list-docs, search-docs,",
|
|
84
|
+
" update-indicators, append-note, context-pack, export-graph",
|
|
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.",
|
|
90
91
|
"",
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
92
|
+
"Validation:",
|
|
93
|
+
" lint Check filenames, headings, indicators, and changed-doc hygiene.",
|
|
94
|
+
" audit Check workflow consistency and traceability.",
|
|
95
|
+
" Use --governance-profile {relaxed,standard,strict}.",
|
|
96
|
+
" JSON output includes issue_count, warning_count, can_continue,",
|
|
97
|
+
" and release_ready for agent workflows.",
|
|
98
|
+
" doctor Check required workflow directories and schema metadata.",
|
|
94
99
|
"",
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
100
|
+
"Agent and integration surfaces:",
|
|
101
|
+
" assist Inspect runtime signals and build bounded context bundles.",
|
|
102
|
+
" mcp Expose bounded Logics tools for MCP clients.",
|
|
103
|
+
" Subcommands: serve, serve-http, connect, tunnel, tools, call",
|
|
104
|
+
" config Render merged runtime config. Example: config show --format json",
|
|
98
105
|
"",
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
" logics-manager config show --format json",
|
|
106
|
+
"Maintenance:",
|
|
107
|
+
" bootstrap Prepare or check the workflow tree and generated instructions.",
|
|
108
|
+
" self-update Update the installed Python or npm package.",
|
|
103
109
|
]
|
|
104
110
|
return "\n".join(sections)
|
|
105
111
|
|
|
@@ -137,6 +143,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
137
143
|
print(f"logics-manager {get_cli_version()}")
|
|
138
144
|
return 0
|
|
139
145
|
|
|
146
|
+
argv = _expand_json_alias(argv)
|
|
140
147
|
command = argv[0]
|
|
141
148
|
if command not in ROOT_COMMANDS:
|
|
142
149
|
raise SystemExit(f"Unsupported command: {command}")
|
|
@@ -148,7 +155,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
148
155
|
config_args = rest[1:]
|
|
149
156
|
parser = argparse.ArgumentParser(prog="logics-manager config show", add_help=False)
|
|
150
157
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
151
|
-
parsed
|
|
158
|
+
parsed = parser.parse_args(config_args)
|
|
152
159
|
repo_root = find_repo_root(Path.cwd())
|
|
153
160
|
try:
|
|
154
161
|
output = render_config_show(repo_root, output_format=parsed.format)
|
|
@@ -160,7 +167,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
160
167
|
doctor_args = rest
|
|
161
168
|
parser = argparse.ArgumentParser(prog="logics-manager doctor", add_help=False)
|
|
162
169
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
163
|
-
parsed
|
|
170
|
+
parsed = parser.parse_args(doctor_args)
|
|
164
171
|
repo_root = find_repo_root(Path.cwd())
|
|
165
172
|
try:
|
|
166
173
|
output = render_doctor(repo_root, output_format=parsed.format)
|
|
@@ -172,7 +179,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
172
179
|
parser = argparse.ArgumentParser(prog="logics-manager bootstrap", add_help=False)
|
|
173
180
|
parser.add_argument("--check", action="store_true")
|
|
174
181
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
175
|
-
parsed
|
|
182
|
+
parsed = parser.parse_args(rest)
|
|
176
183
|
try:
|
|
177
184
|
repo_root = find_repo_root(Path.cwd())
|
|
178
185
|
except ConfigError:
|
|
@@ -186,7 +193,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
186
193
|
parser.add_argument("--package", default=DEFAULT_SELF_UPDATE_PACKAGE)
|
|
187
194
|
parser.add_argument("--python-package", default=DEFAULT_SELF_UPDATE_PY_PACKAGE)
|
|
188
195
|
parser.add_argument("--dry-run", action="store_true")
|
|
189
|
-
parsed
|
|
196
|
+
parsed = parser.parse_args(rest)
|
|
190
197
|
|
|
191
198
|
manager = parsed.manager
|
|
192
199
|
if manager == "auto":
|
|
@@ -215,7 +222,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
215
222
|
target = parsed.python_package if manager == "pip" else parsed.package
|
|
216
223
|
print(f"Updated {target} via {manager}.")
|
|
217
224
|
return result.returncode
|
|
218
|
-
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):
|
|
219
226
|
from .flow import main as flow_main
|
|
220
227
|
|
|
221
228
|
return flow_main(rest)
|
|
@@ -226,7 +233,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
226
233
|
|
|
227
234
|
return sync_main(rest)
|
|
228
235
|
if command == "assist":
|
|
229
|
-
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:
|
|
230
237
|
raise SystemExit("Unsupported assist subcommand for the native CLI slice.")
|
|
231
238
|
return assist_main(rest)
|
|
232
239
|
if command == "mcp":
|
|
@@ -235,7 +242,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
235
242
|
return mcp_main(rest)
|
|
236
243
|
if command == "audit":
|
|
237
244
|
audit_parser = build_audit_parser()
|
|
238
|
-
parsed
|
|
245
|
+
parsed = audit_parser.parse_args(rest)
|
|
239
246
|
repo_root = find_repo_root(Path.cwd())
|
|
240
247
|
try:
|
|
241
248
|
payload = audit_payload(
|
|
@@ -272,25 +279,119 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
272
279
|
except ConfigError as exc:
|
|
273
280
|
raise SystemExit(str(exc)) from exc
|
|
274
281
|
print(output)
|
|
275
|
-
return 0
|
|
282
|
+
return 0
|
|
276
283
|
if command == "index":
|
|
277
284
|
parser = argparse.ArgumentParser(prog="logics-manager index", add_help=False)
|
|
278
285
|
parser.add_argument("--out", default="logics/INDEX.md")
|
|
279
286
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
280
|
-
parsed
|
|
287
|
+
parsed = parser.parse_args(rest)
|
|
281
288
|
repo_root = find_repo_root(Path.cwd())
|
|
282
289
|
try:
|
|
283
290
|
payload = index_payload(repo_root, out=parsed.out)
|
|
284
291
|
except ConfigError as exc:
|
|
285
292
|
raise SystemExit(str(exc)) from exc
|
|
286
|
-
output =
|
|
293
|
+
output = render_payload(payload, parsed.format, f"Wrote {payload['output_path']}")
|
|
287
294
|
print(output)
|
|
288
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
|
|
351
|
+
print(output)
|
|
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
|
|
289
390
|
if command == "lint":
|
|
290
391
|
parser = argparse.ArgumentParser(prog="logics-manager lint", add_help=False)
|
|
291
392
|
parser.add_argument("--require-status", action="store_true")
|
|
292
393
|
parser.add_argument("--format", choices=("text", "json"), default="text")
|
|
293
|
-
parsed
|
|
394
|
+
parsed = parser.parse_args(rest)
|
|
294
395
|
repo_root = find_repo_root(Path.cwd())
|
|
295
396
|
try:
|
|
296
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)
|