@grifhinz/logics-manager 2.7.0 → 2.8.1

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.
@@ -15,6 +15,7 @@ from .flow_evidence import structured_validation_line as _structured_validation_
15
15
  from .index import index_payload
16
16
  from .lint import expected_workflow_mermaid_signature, lint_payload
17
17
  from .path_utils import ensure_relative_to
18
+ from .sync import read_logics_doc_payload
18
19
  from .termstyle import colorize_help
19
20
 
20
21
 
@@ -206,6 +207,10 @@ def _build_help() -> str:
206
207
  " List workflow docs that are still active.",
207
208
  " Flags: --kind {all,request,backlog,task}, --format {text,json}",
208
209
  "",
210
+ " show <ref>",
211
+ " Show a bounded workflow document view.",
212
+ " Flags: --max-chars, --section, --format {text,json}",
213
+ "",
209
214
  " companion <product|architecture>",
210
215
  " Create a companion doc from the integrated runtime.",
211
216
  " Flags: --title, --source-ref, --request-ref, --backlog-ref, --task-ref, --format {text,json}, --dry-run",
@@ -251,6 +256,7 @@ def _build_help() -> str:
251
256
  "Examples:",
252
257
  ' logics-manager flow new request --title "My request"',
253
258
  " logics-manager flow deliver --from-product prod_017_delivery_loop",
259
+ " logics-manager flow show req_001_my_request",
254
260
  " logics-manager flow validate-closeout task_003_fix_docs",
255
261
  " logics-manager flow repair gates task_003_fix_docs",
256
262
  " logics-manager flow closeout task_003_fix_docs --validation \"pytest passed\" --index --lint --audit",
@@ -338,6 +344,27 @@ def _build_list_help() -> str:
338
344
  )
339
345
 
340
346
 
347
+ def _build_show_help() -> str:
348
+ return "\n".join(
349
+ [
350
+ "Logics Flow Show",
351
+ "Show a bounded workflow document view.",
352
+ "",
353
+ "Usage:",
354
+ " logics-manager flow show <ref-or-path> [args...]",
355
+ "",
356
+ "Flags:",
357
+ " --max-chars",
358
+ " --section",
359
+ " --format {text,json}",
360
+ "",
361
+ "Examples:",
362
+ " logics-manager flow show req_001_my_request",
363
+ " logics-manager flow show task_003_fix_docs --section Validation",
364
+ ]
365
+ )
366
+
367
+
341
368
  def _build_companion_help() -> str:
342
369
  return "\n".join(
343
370
  [
@@ -2214,6 +2241,13 @@ def build_parser() -> argparse.ArgumentParser:
2214
2241
  list_parser.add_argument("--format", choices=("text", "json"), default="text")
2215
2242
  list_parser.set_defaults(func=cmd_list)
2216
2243
 
2244
+ show_parser = sub.add_parser("show", help="Show a bounded workflow document view.")
2245
+ show_parser.add_argument("source")
2246
+ show_parser.add_argument("--max-chars", type=int, default=4000)
2247
+ show_parser.add_argument("--section", action="append", default=[])
2248
+ show_parser.add_argument("--format", choices=("text", "json"), default="text")
2249
+ show_parser.set_defaults(func=cmd_show)
2250
+
2217
2251
  companion_parser = sub.add_parser("companion", help="Create a companion doc from the integrated runtime.")
2218
2252
  companion_sub = companion_parser.add_subparsers(dest="kind", required=True)
2219
2253
  for kind in ("product", "architecture"):
@@ -2429,6 +2463,22 @@ def cmd_list(args: argparse.Namespace) -> dict[str, object]:
2429
2463
  return payload
2430
2464
 
2431
2465
 
2466
+ def cmd_show(args: argparse.Namespace) -> dict[str, object]:
2467
+ repo_root = _find_repo_root(Path.cwd())
2468
+ max_chars = args.max_chars if args.max_chars > 0 else 4000
2469
+ payload = read_logics_doc_payload(repo_root, args.source, max_chars=min(max_chars, 12000), sections=args.section or None)
2470
+ if args.format == "json":
2471
+ print_payload({"command": "show", **payload}, args.format)
2472
+ else:
2473
+ print(f"{payload['ref']} ({payload['kind']}): {payload['title']}")
2474
+ print(f"- path: {payload['path']}")
2475
+ print(f"- status: {payload['status']}")
2476
+ print(f"- truncated: {payload['truncated']}")
2477
+ print("")
2478
+ print(str(payload["content"]).rstrip())
2479
+ return payload
2480
+
2481
+
2432
2482
  def cmd_companion(args: argparse.Namespace) -> dict[str, object]:
2433
2483
  repo_root = _find_repo_root(Path.cwd())
2434
2484
  request_ref, backlog_ref, task_ref = _resolve_workflow_refs_for_companion(
@@ -2793,6 +2843,9 @@ def closeout_payload(
2793
2843
  finish_payload: dict[str, object] | None = None
2794
2844
  if not dry_run:
2795
2845
  _close_chain_for_kind(repo_root, task_path, DOC_KINDS["task"], dry_run=False, quiet=True)
2846
+ for ref in mermaid_refs:
2847
+ if ref.startswith(f"{DOC_KINDS['request'].prefix}_"):
2848
+ _maybe_close_request_chain(repo_root, ref, dry_run=False, quiet=True)
2796
2849
  finish_issues = _verify_finished_task_chain(repo_root, task_path)
2797
2850
  if finish_issues:
2798
2851
  raise SystemExit("Finish verification failed:\n" + "\n".join(f"- {issue}" for issue in finish_issues))
@@ -3244,6 +3297,9 @@ def main(argv: list[str]) -> int:
3244
3297
  if argv[0] == "list" and _help_requested(argv, 1):
3245
3298
  _print_help(_build_list_help())
3246
3299
  return 0
3300
+ if argv[0] == "show" and _help_requested(argv, 1):
3301
+ _print_help(_build_show_help())
3302
+ return 0
3247
3303
  if argv[0] == "companion" and _help_requested(argv, 1):
3248
3304
  _print_help(_build_companion_help())
3249
3305
  return 0
@@ -3289,9 +3345,13 @@ def main(argv: list[str]) -> int:
3289
3345
  if argv[0] == "finish" and len(argv) > 1 and argv[1] == "task" and _help_requested(argv, 2):
3290
3346
  _print_help(_build_finish_kind_help(argv[1]))
3291
3347
  return 0
3348
+ valid_commands = {"new", "list", "show", "companion", "deliver", "validate-closeout", "repair", "closeout", "promote", "split", "close", "finish"}
3349
+ if argv[0] not in valid_commands:
3350
+ hint = " Use `logics-manager flow show <ref>` to inspect a workflow doc." if argv[0] in {"read", "view", "cat"} else " Run `logics-manager flow --help` for valid commands."
3351
+ raise SystemExit(f"Unsupported flow subcommand: {argv[0]}.{hint}")
3292
3352
  parser = build_parser()
3293
3353
  args = parser.parse_args(argv)
3294
- if args.command not in {"new", "list", "companion", "deliver", "validate-closeout", "repair", "closeout", "promote", "split", "close", "finish"}:
3354
+ if args.command not in valid_commands:
3295
3355
  raise SystemExit("Unsupported flow subcommand for the native CLI slice.")
3296
3356
  payload = args.func(args)
3297
3357
  if args.command == "validate-closeout" and isinstance(payload, dict) and not payload.get("ok", False):
@@ -259,11 +259,23 @@ def _build_context_pack(
259
259
  profile: str,
260
260
  config: dict[str, object] | None = None,
261
261
  ) -> dict[str, object]:
262
+ seed_refs = [ref for ref in seed_ref.split(",") if ref]
262
263
  docs = _load_workflow_docs(repo_root)
263
- seed = docs.get(seed_ref)
264
- if seed is None:
265
- raise SystemExit(f"Unknown workflow ref `{seed_ref}`.")
266
- ordered = _workflow_neighborhood(seed, docs)[: _context_profile_limit(profile)]
264
+ seeds = [docs.get(ref) for ref in seed_refs]
265
+ missing = [ref for ref, doc in zip(seed_refs, seeds) if doc is None]
266
+ if missing:
267
+ raise SystemExit(f"Unknown workflow ref(s): {', '.join(f'`{ref}`' for ref in missing)}.")
268
+ ordered: list[WorkflowDocModel] = []
269
+ seen: set[str] = set()
270
+ per_seed_limit = _context_profile_limit(profile)
271
+ for seed in seeds:
272
+ if seed is None:
273
+ continue
274
+ for doc in _workflow_neighborhood(seed, docs)[:per_seed_limit]:
275
+ if doc.ref in seen:
276
+ continue
277
+ ordered.append(doc)
278
+ seen.add(doc.ref)
267
279
  changed_paths = _git_changed_paths(repo_root) if mode == "diff-first" else []
268
280
  cache_key = _context_pack_cache_key(
269
281
  repo_root,
@@ -281,7 +293,8 @@ def _build_context_pack(
281
293
  "ref": seed_ref,
282
294
  "mode": mode,
283
295
  "profile": profile,
284
- "budgets": {"max_docs": _context_profile_limit(profile)},
296
+ "refs": seed_refs,
297
+ "budgets": {"max_docs": per_seed_limit * max(1, len(seed_refs)), "max_docs_per_ref": per_seed_limit},
285
298
  "changed_paths": changed_paths,
286
299
  "docs": pack_docs,
287
300
  "estimates": {
@@ -725,6 +738,8 @@ def build_parser() -> argparse.ArgumentParser:
725
738
  close_eligible.set_defaults(func=cmd_close_eligible_requests)
726
739
 
727
740
  refresh_mermaid = sub.add_parser("refresh-mermaid-signatures", help="Refresh stale workflow Mermaid signatures without rewriting the full diagram body.")
741
+ refresh_mermaid.add_argument("sources", nargs="*", help="Optional workflow refs or paths to scope the refresh.")
742
+ refresh_mermaid.add_argument("--changed-only", action="store_true", help="Refresh only changed workflow docs.")
728
743
  refresh_mermaid.add_argument("--format", choices=("text", "json"), default="text")
729
744
  refresh_mermaid.add_argument("--dry-run", action="store_true")
730
745
  refresh_mermaid.set_defaults(func=cmd_refresh_mermaid_signatures)
@@ -779,7 +794,7 @@ def build_parser() -> argparse.ArgumentParser:
779
794
  append_note.set_defaults(func=cmd_append_note)
780
795
 
781
796
  context_pack = sub.add_parser("context-pack", help="Build a compact context pack from workflow docs.")
782
- context_pack.add_argument("ref", help="Seed workflow ref for the context pack.")
797
+ context_pack.add_argument("refs", nargs="+", help="Seed workflow ref(s) for the context pack.")
783
798
  context_pack.add_argument("--mode", choices=("summary-only", "diff-first", "full"), default="summary-only")
784
799
  context_pack.add_argument("--profile", choices=("tiny", "normal", "deep"), default="normal")
785
800
  context_pack.add_argument("--out", help="Write the JSON artifact to this relative path.")
@@ -812,13 +827,14 @@ def _build_help() -> str:
812
827
  "",
813
828
  " refresh-mermaid-signatures",
814
829
  " Refresh stale Mermaid signatures without rewriting diagram bodies.",
815
- " Flags: --format {text,json}, --dry-run",
830
+ " Args: [refs-or-paths...]",
831
+ " Flags: --changed-only, --format {text,json}, --dry-run",
816
832
  "",
817
833
  " schema-status [sources...]",
818
834
  " Report schema-version coverage for selected workflow docs.",
819
835
  " Flags: --format {text,json}",
820
836
  "",
821
- " context-pack <ref>",
837
+ " context-pack <refs...>",
822
838
  " Build a compact JSON context pack from workflow docs.",
823
839
  " Flags: --mode {summary-only,diff-first,full}, --profile {tiny,normal,deep}, --out, --format {text,json}, --dry-run",
824
840
  "",
@@ -848,7 +864,7 @@ def _build_help() -> str:
848
864
  "",
849
865
  "Examples:",
850
866
  " logics-manager sync schema-status",
851
- " logics-manager sync context-pack req_001_my_request --out logics/context-pack.json",
867
+ " logics-manager sync context-pack req_001_my_request task_002_fix_bug --out logics/context-pack.json",
852
868
  " logics-manager sync export-graph --format json",
853
869
  ]
854
870
  )
@@ -879,9 +895,10 @@ def _build_subcommand_help(command: str) -> str:
879
895
  "Refresh stale workflow Mermaid signatures without rewriting diagram bodies.",
880
896
  "",
881
897
  "Usage:",
882
- " logics-manager sync refresh-mermaid-signatures [args...]",
898
+ " logics-manager sync refresh-mermaid-signatures [refs-or-paths...] [args...]",
883
899
  "",
884
900
  "Flags:",
901
+ " --changed-only",
885
902
  " --format {text,json}",
886
903
  " --dry-run",
887
904
  ]
@@ -909,7 +926,7 @@ def _build_subcommand_help(command: str) -> str:
909
926
  "Build a compact JSON context pack from workflow docs.",
910
927
  "",
911
928
  "Usage:",
912
- " logics-manager sync context-pack <ref> [args...]",
929
+ " logics-manager sync context-pack <refs...> [args...]",
913
930
  "",
914
931
  "Flags:",
915
932
  " --mode {summary-only,diff-first,full}",
@@ -919,7 +936,7 @@ def _build_subcommand_help(command: str) -> str:
919
936
  " --dry-run",
920
937
  "",
921
938
  "Example:",
922
- " logics-manager sync context-pack req_001_my_request --out logics/context-pack.json",
939
+ " logics-manager sync context-pack req_001_my_request task_002_fix_bug --out logics/context-pack.json",
923
940
  ]
924
941
  )
925
942
  if command == "read-doc":
@@ -1053,17 +1070,26 @@ def cmd_close_eligible_requests(args: argparse.Namespace) -> dict[str, object]:
1053
1070
  def cmd_refresh_mermaid_signatures(args: argparse.Namespace) -> dict[str, object]:
1054
1071
  repo_root = _find_repo_root(Path.cwd())
1055
1072
  modified: list[str] = []
1056
- for kind in ("request", "backlog", "task"):
1057
- directory = repo_root / DOC_KINDS[kind]["directory"]
1058
- for path in sorted(directory.glob("*.md")):
1059
- if refresh_workflow_mermaid_signature_file(path, kind, args.dry_run, repo_root=repo_root):
1060
- modified.append(path.relative_to(repo_root).as_posix())
1073
+ if args.changed_only:
1074
+ changed_sources = [
1075
+ path
1076
+ for path in _git_changed_paths(repo_root)
1077
+ if path.startswith(("logics/request/", "logics/backlog/", "logics/tasks/")) and path.endswith(".md")
1078
+ ]
1079
+ targets = _resolve_target_docs(repo_root, changed_sources)
1080
+ else:
1081
+ targets = _resolve_target_docs(repo_root, args.sources)
1082
+ for kind, path in targets:
1083
+ if refresh_workflow_mermaid_signature_file(path, kind, args.dry_run, repo_root=repo_root):
1084
+ modified.append(path.relative_to(repo_root).as_posix())
1061
1085
 
1062
1086
  payload = {
1063
1087
  "command": "sync",
1064
1088
  "kind": "refresh-mermaid-signatures",
1065
1089
  "repo_root": repo_root.as_posix(),
1066
1090
  "modified_files": modified,
1091
+ "scanned_files": [path.relative_to(repo_root).as_posix() for _kind, path in targets],
1092
+ "changed_only": args.changed_only,
1067
1093
  "dry_run": args.dry_run,
1068
1094
  }
1069
1095
  if args.format == "json":
@@ -1106,6 +1132,8 @@ def cmd_read_doc(args: argparse.Namespace) -> dict[str, object]:
1106
1132
  print(f"- path: {payload['path']}")
1107
1133
  print(f"- status: {payload['status']}")
1108
1134
  print(f"- truncated: {payload['truncated']}")
1135
+ print("")
1136
+ print(str(payload["content"]).rstrip())
1109
1137
  return {"command": "sync", "kind": "read-doc", "repo_root": repo_root.as_posix(), **payload}
1110
1138
 
1111
1139
 
@@ -1172,7 +1200,7 @@ def cmd_append_note(args: argparse.Namespace) -> dict[str, object]:
1172
1200
 
1173
1201
  def cmd_context_pack(args: argparse.Namespace) -> dict[str, object]:
1174
1202
  repo_root = _find_repo_root(Path.cwd())
1175
- payload = _build_context_pack(repo_root, args.ref, mode=args.mode, profile=args.profile, config=None)
1203
+ payload = _build_context_pack(repo_root, ",".join(args.refs), mode=args.mode, profile=args.profile, config=None)
1176
1204
  if args.out:
1177
1205
  out_path, output_path = resolve_repo_output_path(repo_root, args.out)
1178
1206
  serialized = json.dumps(payload, indent=2, sort_keys=True) + "\n"
@@ -1188,7 +1216,7 @@ def cmd_context_pack(args: argparse.Namespace) -> dict[str, object]:
1188
1216
  if args.format == "json":
1189
1217
  print(json.dumps(payload, indent=2, sort_keys=True))
1190
1218
  else:
1191
- print(f"Context pack: {payload['ref']} ({payload['mode']}, {payload['profile']})")
1219
+ print(f"Context pack: {', '.join(args.refs)} ({payload['mode']}, {payload['profile']})")
1192
1220
  print(f"- docs: {payload['estimates']['doc_count']}")
1193
1221
  return {"command": "sync", "kind": "context-pack", "repo_root": repo_root.as_posix(), **payload}
1194
1222