@grifhinz/logics-manager 2.0.2 → 2.0.5

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.
@@ -2,9 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  from importlib import metadata
5
+ import subprocess
5
6
  import sys
7
+ from shutil import which
6
8
  from pathlib import Path
7
- from textwrap import dedent
8
9
 
9
10
  from .bootstrap import bootstrap_payload, render_bootstrap
10
11
  from .assist import main as assist_main
@@ -14,6 +15,91 @@ from .config import ConfigError, find_repo_root, render_config_show
14
15
  from .index import index_payload, render_index
15
16
  from .lint import lint_payload, render_lint
16
17
  from .doctor import render_doctor
18
+ from .termstyle import colorize_help
19
+
20
+
21
+ DEFAULT_SELF_UPDATE_PY_PACKAGE = "logics-manager"
22
+ DEFAULT_SELF_UPDATE_PACKAGE = "@grifhinz/logics-manager"
23
+ HELP_ARGV = (["-h"], ["--help"])
24
+ ROOT_COMMANDS = (
25
+ "bootstrap",
26
+ "flow",
27
+ "sync",
28
+ "assist",
29
+ "audit",
30
+ "index",
31
+ "lint",
32
+ "config",
33
+ "doctor",
34
+ "self-update",
35
+ )
36
+
37
+
38
+ def _build_root_help() -> str:
39
+ sections = [
40
+ "Logics Manager CLI",
41
+ "Canonical CLI for workflow, validation, and runtime ops.",
42
+ "",
43
+ "Usage:",
44
+ " logics-manager <command> [args...]",
45
+ " logics-manager config show [options]",
46
+ "",
47
+ "Top-level options:",
48
+ " -h, --help Show this help message and exit.",
49
+ " --version Print the installed version.",
50
+ "",
51
+ "Commands:",
52
+ " bootstrap",
53
+ " Prepare or check the workflow tree and generated instructions.",
54
+ " Options: --check, --format {text,json}",
55
+ "",
56
+ " flow",
57
+ " Create and manage workflow docs.",
58
+ " Subcommands: new, list, companion, promote, split, close, finish",
59
+ " Key flags: --title, --slug, --from-version, --understanding, --confidence, --status, --complexity, --theme, --progress, --format {text,json}, --dry-run",
60
+ "",
61
+ " sync",
62
+ " Synchronize workflow transitions and exports.",
63
+ " Subcommands: close-eligible-requests, refresh-mermaid-signatures, schema-status, context-pack, export-graph",
64
+ "",
65
+ " assist",
66
+ " Inspect runtime signals and build context bundles.",
67
+ " Subcommands: runtime-status, diff-risk, commit-plan, changed-surface-summary, doc-consistency, review-checklist, validation-checklist, validation-summary, test-impact-summary, roi-report, claude-bridges, context, claude-instructions, next-step, request-draft, spec-first-pass, backlog-groom, closure-summary",
68
+ "",
69
+ " audit",
70
+ " Audit request, backlog, and task consistency.",
71
+ " 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",
72
+ "",
73
+ " index",
74
+ " Generate `logics/INDEX.md` from the workflow corpus.",
75
+ " Options: --out, --format {text,json}",
76
+ "",
77
+ " lint",
78
+ " Lint workflow documents for filenames, headings, and indicators.",
79
+ " Options: --require-status, --format {text,json}",
80
+ "",
81
+ " config show",
82
+ " Render the merged runtime config.",
83
+ " Options: --format {text,json}",
84
+ "",
85
+ " doctor",
86
+ " Check required workflow directories and schema metadata.",
87
+ " Options: --format {text,json}",
88
+ "",
89
+ " self-update",
90
+ " Update the installed Python or npm package.",
91
+ " Options: --manager {auto,pip,npm}, --package, --python-package, --dry-run",
92
+ "",
93
+ "Examples:",
94
+ ' logics-manager flow new request --title "My request"',
95
+ " logics-manager audit",
96
+ " logics-manager config show --format json",
97
+ ]
98
+ return "\n".join(sections)
99
+
100
+
101
+ def _print_help(text: str) -> None:
102
+ print(colorize_help(text))
17
103
 
18
104
 
19
105
  def get_cli_version() -> str:
@@ -35,34 +121,22 @@ def get_cli_version() -> str:
35
121
  def main(argv: list[str] | None = None) -> int:
36
122
  if argv is None:
37
123
  argv = sys.argv[1:]
38
- parser = argparse.ArgumentParser(
39
- prog="logics-manager",
40
- description="Canonical Logics CLI for workflow, validation, and runtime operations.",
41
- formatter_class=argparse.RawDescriptionHelpFormatter,
42
- epilog=dedent(
43
- """
44
- Examples:
45
- logics-manager flow new request --title "My request"
46
- logics-manager audit
47
- logics-manager config show --format json
48
- """
49
- ).strip(),
50
- )
51
- parser.add_argument("--version", action="version", version=f"logics-manager {get_cli_version()}")
52
- parser.add_argument(
53
- "command",
54
- nargs="?",
55
- choices=("bootstrap", "flow", "sync", "assist", "audit", "index", "lint", "config", "doctor"),
56
- )
57
- parser.add_argument("rest", nargs=argparse.REMAINDER)
58
- args = parser.parse_args(argv[:1])
59
-
60
- if args.command is None:
61
- parser.print_help()
124
+ if not argv:
125
+ _print_help(_build_root_help())
62
126
  return 1
127
+ if argv[0] in ("-h", "--help"):
128
+ _print_help(_build_root_help())
129
+ return 0
130
+ if argv[0] == "--version":
131
+ print(f"logics-manager {get_cli_version()}")
132
+ return 0
133
+
134
+ command = argv[0]
135
+ if command not in ROOT_COMMANDS:
136
+ raise SystemExit(f"Unsupported command: {command}")
63
137
 
64
138
  rest = argv[1:]
65
- if args.command == "config":
139
+ if command == "config":
66
140
  if not rest or rest[0] != "show":
67
141
  raise SystemExit("Usage: logics-manager config show [args...]")
68
142
  config_args = rest[1:]
@@ -76,7 +150,7 @@ def main(argv: list[str] | None = None) -> int:
76
150
  raise SystemExit(str(exc)) from exc
77
151
  print(output)
78
152
  return 0
79
- if args.command == "doctor":
153
+ if command == "doctor":
80
154
  doctor_args = rest
81
155
  parser = argparse.ArgumentParser(prog="logics-manager doctor", add_help=False)
82
156
  parser.add_argument("--format", choices=("text", "json"), default="text")
@@ -88,7 +162,7 @@ def main(argv: list[str] | None = None) -> int:
88
162
  raise SystemExit(str(exc)) from exc
89
163
  print(output)
90
164
  return 0
91
- if args.command == "bootstrap":
165
+ if command == "bootstrap":
92
166
  parser = argparse.ArgumentParser(prog="logics-manager bootstrap", add_help=False)
93
167
  parser.add_argument("--check", action="store_true")
94
168
  parser.add_argument("--format", choices=("text", "json"), default="text")
@@ -100,21 +174,56 @@ def main(argv: list[str] | None = None) -> int:
100
174
  payload = bootstrap_payload(repo_root, check=parsed.check)
101
175
  print(render_bootstrap(payload, output_format=parsed.format))
102
176
  return 0 if payload["ok"] else 1
103
- if args.command == "flow" and rest[:1] in (["new"], ["companion"], ["promote"], ["split"], ["close"], ["finish"]):
177
+ if command == "self-update":
178
+ parser = argparse.ArgumentParser(prog="logics-manager self-update", add_help=False)
179
+ parser.add_argument("--manager", choices=("auto", "pip", "npm"), default="auto")
180
+ parser.add_argument("--package", default=DEFAULT_SELF_UPDATE_PACKAGE)
181
+ parser.add_argument("--python-package", default=DEFAULT_SELF_UPDATE_PY_PACKAGE)
182
+ parser.add_argument("--dry-run", action="store_true")
183
+ parsed, _unknown = parser.parse_known_args(rest)
184
+
185
+ manager = parsed.manager
186
+ if manager == "auto":
187
+ try:
188
+ metadata.version(parsed.python_package)
189
+ except metadata.PackageNotFoundError:
190
+ manager = "npm" if which("npm") else "pip"
191
+ else:
192
+ manager = "pip"
193
+
194
+ if manager == "pip":
195
+ command = [sys.executable, "-m", "pip", "install", "--upgrade", parsed.python_package]
196
+ else:
197
+ npm = which("npm")
198
+ if not npm:
199
+ print("npm was not found on PATH. Install Node.js/npm or update the package manually.")
200
+ return 1
201
+ command = [npm, "install", "-g", f"{parsed.package}@latest"]
202
+
203
+ if parsed.dry_run:
204
+ print("Dry run: " + " ".join(command))
205
+ return 0
206
+
207
+ result = subprocess.run(command, check=False)
208
+ if result.returncode == 0:
209
+ target = parsed.python_package if manager == "pip" else parsed.package
210
+ print(f"Updated {target} via {manager}.")
211
+ return result.returncode
212
+ if command == "flow" and (rest[:1] in (["new"], ["list"], ["companion"], ["promote"], ["split"], ["close"], ["finish"]) or rest[:1] in HELP_ARGV):
104
213
  from .flow import main as flow_main
105
214
 
106
215
  return flow_main(rest)
107
- if args.command == "sync":
108
- if rest[:1] not in (["close-eligible-requests"], ["refresh-mermaid-signatures"], ["schema-status"], ["context-pack"], ["export-graph"]):
216
+ if command == "sync":
217
+ if rest[:1] not in (["close-eligible-requests"], ["refresh-mermaid-signatures"], ["schema-status"], ["context-pack"], ["export-graph"]) and rest[:1] not in HELP_ARGV:
109
218
  raise SystemExit("Unsupported sync subcommand for the native CLI slice.")
110
219
  from .sync import main as sync_main
111
220
 
112
221
  return sync_main(rest)
113
- if args.command == "assist":
114
- 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"]):
222
+ if command == "assist":
223
+ 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:
115
224
  raise SystemExit("Unsupported assist subcommand for the native CLI slice.")
116
225
  return assist_main(rest)
117
- if args.command == "audit":
226
+ if command == "audit":
118
227
  audit_parser = build_audit_parser()
119
228
  parsed, _unknown = audit_parser.parse_known_args(rest)
120
229
  repo_root = find_repo_root(Path.cwd())
@@ -154,7 +263,7 @@ def main(argv: list[str] | None = None) -> int:
154
263
  raise SystemExit(str(exc)) from exc
155
264
  print(output)
156
265
  return 0 if payload["ok"] else 1
157
- if args.command == "index":
266
+ if command == "index":
158
267
  parser = argparse.ArgumentParser(prog="logics-manager index", add_help=False)
159
268
  parser.add_argument("--out", default="logics/INDEX.md")
160
269
  parser.add_argument("--format", choices=("text", "json"), default="text")
@@ -167,7 +276,7 @@ def main(argv: list[str] | None = None) -> int:
167
276
  output = render_index(repo_root, out=parsed.out, output_format=parsed.format) if parsed.format == "json" else f"Wrote {payload['output_path']}"
168
277
  print(output)
169
278
  return 0 if payload["ok"] else 1
170
- if args.command == "lint":
279
+ if command == "lint":
171
280
  parser = argparse.ArgumentParser(prog="logics-manager lint", add_help=False)
172
281
  parser.add_argument("--require-status", action="store_true")
173
282
  parser.add_argument("--format", choices=("text", "json"), default="text")
@@ -180,4 +289,4 @@ def main(argv: list[str] | None = None) -> int:
180
289
  raise SystemExit(str(exc)) from exc
181
290
  print(output)
182
291
  return 0 if payload["ok"] else 1
183
- raise SystemExit(f"Unsupported command: {args.command}")
292
+ raise SystemExit(f"Unsupported command: {command}")