@grifhinz/logics-manager 2.0.2 → 2.0.4

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/AlexAgo83/logics-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/AlexAgo83/logics-manager/actions/workflows/ci.yml)
4
4
  [![License](https://img.shields.io/github/license/AlexAgo83/logics-manager)](LICENSE)
5
- ![Version](https://img.shields.io/badge/version-v2.0.2-4C8BF5)
5
+ ![Version](https://img.shields.io/badge/version-v2.0.4-4C8BF5)
6
6
  ![VS Code](https://img.shields.io/badge/VS%20Code-1.86.0-007ACC?logo=visualstudiocode&logoColor=white)
7
7
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.3.3-3178C6?logo=typescript&logoColor=white)
8
8
  ![Vitest](https://img.shields.io/badge/Vitest-2.1.8-6E9F18?logo=vitest&logoColor=white)
@@ -35,6 +35,9 @@ Install the npm package with:
35
35
  npm install -g @grifhinz/logics-manager
36
36
  ```
37
37
 
38
+ To update that CLI later, run `logics-manager self-update`.
39
+ The command uses `pip` when the Python package is installed and falls back to `npm` for the global npm package.
40
+
38
41
  For the editor client, build and install the VSIX:
39
42
 
40
43
  ```bash
@@ -267,7 +270,7 @@ Contract:
267
270
  - `Environment` can also surface direct remediation actions when the plugin detects a stale runtime, an incomplete bootstrap, a missing global publication, or missing environment placeholders.
268
271
  - `Environment` now uses a clearer hierarchy with summary, recommended actions, current status, and technical details, plus hybrid assist runtime state, backend availability, degraded reasons, Claude-bridge presence, and the shared Windows-safe runtime entrypoint.
269
272
  - `Check Environment` can be promoted into `Recommended` when the current repo state actually warrants operator attention.
270
- - repo-local refresh now watches `logics/**/*`, `logics.yaml`, and supported `.claude/` bridge files; external global runtime state still requires an explicit refresh because it lives outside the workspace.
273
+ - repo-local refresh now watches `logics/**/*`, `logics.yaml`, and `.git/HEAD`; external global runtime state still requires an explicit refresh because it lives outside the workspace.
271
274
  - `Launch Codex` starts Codex using the globally published Logics runtime when the shared runtime is healthy.
272
275
  - `AI Runtime Status` probes the shared `logics.py flow assist runtime-status` surface and reports ready providers, flagged providers, cooldown or credential issues, and bounded backend provenance.
273
276
  - `AI Provider Insights` opens a dedicated plugin panel backed by `logics.py flow assist roi-report`, with provider mix, execution-path breakdowns, derived rates, estimated ROI proxies, and recent audit drill-down over the shared runtime output.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.2
1
+ 2.0.4
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import json
5
+ import os
5
6
  from collections import Counter
6
7
  from datetime import datetime, timedelta, timezone
7
8
  import re
@@ -1283,7 +1284,7 @@ def build_parser() -> argparse.ArgumentParser:
1283
1284
 
1284
1285
  claude_bridges = sub.add_parser(
1285
1286
  "claude-bridges",
1286
- help="Render the canonical Claude bridge files and prompts derived from the integrated runtime.",
1287
+ help="Render the canonical Claude runtime publication manifest and prompts derived from the integrated runtime.",
1287
1288
  )
1288
1289
  claude_bridges.add_argument("--format", choices=("text", "json"), default="text")
1289
1290
  claude_bridges.add_argument("--dry-run", action="store_true")
@@ -1346,10 +1347,17 @@ def build_parser() -> argparse.ArgumentParser:
1346
1347
  return parser
1347
1348
 
1348
1349
 
1349
- def _claude_bridge_status(repo_root: Path) -> dict[str, object]:
1350
+ def _get_global_claude_home() -> Path:
1351
+ return Path(os.environ.get("LOGICS_CLAUDE_GLOBAL_HOME") or (Path.home() / ".claude")).resolve()
1352
+
1353
+
1354
+ def _claude_bridge_status(_repo_root: Path) -> dict[str, object]:
1355
+ global_home = _get_global_claude_home()
1350
1356
  detected_variants: list[str] = []
1351
1357
  for variant in CLAUDE_BRIDGE_VARIANTS:
1352
- if (repo_root / variant["command_path"]).is_file() and (repo_root / variant["agent_path"]).is_file():
1358
+ command_path = global_home / str(variant["command_path"]).replace(".claude/", "")
1359
+ agent_path = global_home / str(variant["agent_path"]).replace(".claude/", "")
1360
+ if command_path.is_file() and agent_path.is_file():
1353
1361
  detected_variants.append(variant["id"])
1354
1362
  return {
1355
1363
  "available": bool(detected_variants),
@@ -1368,7 +1376,7 @@ def _render_claude_bridge_lines(variant: dict[str, object], prompt: str) -> tupl
1368
1376
  command_lines = [
1369
1377
  f"# {title}",
1370
1378
  "",
1371
- f"Use the repository-local {title.lower()} bridge for this project.",
1379
+ f"Use the published global {title.lower()} bridge for this project.",
1372
1380
  "",
1373
1381
  "Primary prompt:",
1374
1382
  prompt,
@@ -1377,7 +1385,7 @@ def _render_claude_bridge_lines(variant: dict[str, object], prompt: str) -> tupl
1377
1385
  agent_lines = [
1378
1386
  f"# {title} Agent",
1379
1387
  "",
1380
- f"Use the repository-local {title.lower()} agent for this project.",
1388
+ f"Use the published global {title.lower()} agent for this project.",
1381
1389
  "",
1382
1390
  "Default prompt:",
1383
1391
  prompt,
@@ -1433,8 +1441,8 @@ def _build_claude_instructions(repo_root: Path) -> dict[str, object]:
1433
1441
  "- `python3 -m logics_manager lint --require-status`",
1434
1442
  "- `python3 -m logics_manager audit --legacy-cutoff-version 1.1.0 --group-by-doc`",
1435
1443
  "",
1436
- "Repository-local Claude bridge files and assistant instructions are generated from the integrated runtime.",
1437
- "Do not edit `.claude/` bridge files by hand unless you are deliberately repairing a generated artifact.",
1444
+ "Claude runtime artifacts are generated outside the repository from the integrated runtime.",
1445
+ "Do not edit generated runtime artifacts by hand unless you are deliberately repairing a generated artifact.",
1438
1446
  "",
1439
1447
  "Do not edit indicator lines or workflow links by hand.",
1440
1448
  "",
@@ -1454,8 +1462,8 @@ def _select_backend(requested_backend: str | None, bridge_status: dict[str, obje
1454
1462
  if requested_backend and requested_backend != "auto":
1455
1463
  return requested_backend, []
1456
1464
  if bridge_status.get("available"):
1457
- return "codex", ["claude bridge files detected"]
1458
- return "deterministic", ["no bridge files detected"]
1465
+ return "codex", ["global Claude runtime published"]
1466
+ return "deterministic", ["no global Claude runtime published"]
1459
1467
 
1460
1468
 
1461
1469
  def cmd_claude_bridges(args: argparse.Namespace) -> dict[str, object]:
@@ -1953,9 +1961,9 @@ def cmd_runtime_status(args: argparse.Namespace) -> dict[str, object]:
1953
1961
  print(f"- selected backend: {selected_backend}")
1954
1962
  print(f"- model profile: {default_profile}")
1955
1963
  print(f"- model: {resolved_model}")
1956
- print(f"- bridge available: {'yes' if bridge_status['available'] else 'no'}")
1964
+ print(f"- global Claude runtime available: {'yes' if bridge_status['available'] else 'no'}")
1957
1965
  if bridge_status["preferred_variant"]:
1958
- print(f"- bridge variant: {bridge_status['preferred_variant']}")
1966
+ print(f"- runtime variant: {bridge_status['preferred_variant']}")
1959
1967
  return payload
1960
1968
 
1961
1969
 
@@ -2200,7 +2208,7 @@ def cmd_context(args: argparse.Namespace) -> dict[str, object]:
2200
2208
  print(f"- ref: {args.ref or '<flow-default>'}")
2201
2209
  print(f"- mode: {context_mode}")
2202
2210
  print(f"- profile: {profile}")
2203
- print(f"- bridge available: {'yes' if bridge_status['available'] else 'no'}")
2211
+ print(f"- global Claude runtime available: {'yes' if bridge_status['available'] else 'no'}")
2204
2212
  return payload
2205
2213
 
2206
2214
 
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import shutil
4
5
  from pathlib import Path
5
6
 
6
- from .assist import _build_claude_bridge_manifest, _build_claude_instructions
7
+ from .assist import _build_claude_instructions
7
8
 
8
9
 
9
10
  WORKFLOW_DIRS: tuple[str, ...] = ("request", "backlog", "tasks", "specs", "product", "architecture", "external", ".cache")
@@ -13,14 +14,34 @@ def _workflow_directories(repo_root: Path) -> list[Path]:
13
14
  return [repo_root / "logics" / name for name in WORKFLOW_DIRS]
14
15
 
15
16
 
17
+ def _legacy_runtime_paths(repo_root: Path) -> list[Path]:
18
+ return [repo_root / ".claude", repo_root / "logics" / "skills"]
19
+
20
+
21
+ def _remove_legacy_runtime_paths(repo_root: Path, *, check: bool) -> list[str]:
22
+ removed_paths: list[str] = []
23
+ for target in _legacy_runtime_paths(repo_root):
24
+ if not target.exists():
25
+ continue
26
+ removed_paths.append(target.relative_to(repo_root).as_posix() + ("/" if target.is_dir() else ""))
27
+ if not check:
28
+ if target.is_dir():
29
+ shutil.rmtree(target)
30
+ else:
31
+ target.unlink()
32
+ return removed_paths
33
+
34
+
16
35
  def bootstrap_payload(repo_root: Path, *, check: bool) -> dict[str, object]:
17
36
  logics_root = repo_root / "logics"
18
- bridge_manifest = _build_claude_bridge_manifest(repo_root)
19
37
  instructions_manifest = _build_claude_instructions(repo_root)
20
38
  directory_actions: list[dict[str, object]] = []
21
39
  created_paths: list[str] = []
40
+ removed_paths: list[str] = []
22
41
  missing_paths: list[str] = []
23
42
 
43
+ removed_paths.extend(_remove_legacy_runtime_paths(repo_root, check=check))
44
+
24
45
  if not logics_root.exists():
25
46
  missing_paths.append("logics/")
26
47
  elif not logics_root.is_dir():
@@ -71,24 +92,6 @@ def bootstrap_payload(repo_root: Path, *, check: bool) -> dict[str, object]:
71
92
  instructions_path.write_text(instructions_content, encoding="utf-8")
72
93
  created_paths.append("logics/instructions.md")
73
94
 
74
- for bridge in bridge_manifest["bridges"]:
75
- for rel_path, content in (
76
- (str(bridge["command_path"]), str(bridge["command_content"])),
77
- (str(bridge["agent_path"]), str(bridge["agent_content"])),
78
- ):
79
- bridge_path = repo_root / rel_path
80
- if bridge_path.exists():
81
- try:
82
- if bridge_path.read_text(encoding="utf-8") == content:
83
- continue
84
- except Exception:
85
- pass
86
- missing_paths.append(rel_path)
87
- if not check:
88
- bridge_path.parent.mkdir(parents=True, exist_ok=True)
89
- bridge_path.write_text(content, encoding="utf-8")
90
- created_paths.append(rel_path)
91
-
92
95
  ok = not missing_paths if check else True
93
96
  return {
94
97
  "command": "bootstrap",
@@ -97,8 +100,8 @@ def bootstrap_payload(repo_root: Path, *, check: bool) -> dict[str, object]:
97
100
  "ok": ok,
98
101
  "missing_paths": missing_paths,
99
102
  "created_paths": created_paths,
103
+ "removed_paths": removed_paths,
100
104
  "directory_actions": directory_actions,
101
- "claude_bridge_count": bridge_manifest["bridge_count"],
102
105
  "claude_instruction_line_count": instructions_manifest["line_count"],
103
106
  }
104
107
 
@@ -114,6 +117,10 @@ def render_bootstrap(payload: dict[str, object], *, output_format: str) -> str:
114
117
  lines.append(f"- missing: {path}")
115
118
  return "\n".join(lines)
116
119
  lines = ["Bootstrap: OK"]
120
+ if payload.get("removed_paths"):
121
+ lines.append("- removed:")
122
+ for path in payload["removed_paths"]:
123
+ lines.append(f" - {path}")
117
124
  if payload["created_paths"]:
118
125
  lines.append("- created:")
119
126
  for path in payload["created_paths"]:
@@ -2,7 +2,9 @@ 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
9
  from textwrap import dedent
8
10
 
@@ -16,6 +18,10 @@ from .lint import lint_payload, render_lint
16
18
  from .doctor import render_doctor
17
19
 
18
20
 
21
+ DEFAULT_SELF_UPDATE_PY_PACKAGE = "logics-manager"
22
+ DEFAULT_SELF_UPDATE_PACKAGE = "@grifhinz/logics-manager"
23
+
24
+
19
25
  def get_cli_version() -> str:
20
26
  version_file = Path(__file__).resolve().parents[1] / "VERSION"
21
27
  try:
@@ -52,7 +58,7 @@ def main(argv: list[str] | None = None) -> int:
52
58
  parser.add_argument(
53
59
  "command",
54
60
  nargs="?",
55
- choices=("bootstrap", "flow", "sync", "assist", "audit", "index", "lint", "config", "doctor"),
61
+ choices=("bootstrap", "flow", "sync", "assist", "audit", "index", "lint", "config", "doctor", "self-update"),
56
62
  )
57
63
  parser.add_argument("rest", nargs=argparse.REMAINDER)
58
64
  args = parser.parse_args(argv[:1])
@@ -100,6 +106,41 @@ def main(argv: list[str] | None = None) -> int:
100
106
  payload = bootstrap_payload(repo_root, check=parsed.check)
101
107
  print(render_bootstrap(payload, output_format=parsed.format))
102
108
  return 0 if payload["ok"] else 1
109
+ if args.command == "self-update":
110
+ parser = argparse.ArgumentParser(prog="logics-manager self-update", add_help=False)
111
+ parser.add_argument("--manager", choices=("auto", "pip", "npm"), default="auto")
112
+ parser.add_argument("--package", default=DEFAULT_SELF_UPDATE_PACKAGE)
113
+ parser.add_argument("--python-package", default=DEFAULT_SELF_UPDATE_PY_PACKAGE)
114
+ parser.add_argument("--dry-run", action="store_true")
115
+ parsed, _unknown = parser.parse_known_args(rest)
116
+
117
+ manager = parsed.manager
118
+ if manager == "auto":
119
+ try:
120
+ metadata.version(parsed.python_package)
121
+ except metadata.PackageNotFoundError:
122
+ manager = "npm" if which("npm") else "pip"
123
+ else:
124
+ manager = "pip"
125
+
126
+ if manager == "pip":
127
+ command = [sys.executable, "-m", "pip", "install", "--upgrade", parsed.python_package]
128
+ else:
129
+ npm = which("npm")
130
+ if not npm:
131
+ print("npm was not found on PATH. Install Node.js/npm or update the package manually.")
132
+ return 1
133
+ command = [npm, "install", "-g", f"{parsed.package}@latest"]
134
+
135
+ if parsed.dry_run:
136
+ print("Dry run: " + " ".join(command))
137
+ return 0
138
+
139
+ result = subprocess.run(command, check=False)
140
+ if result.returncode == 0:
141
+ target = parsed.python_package if manager == "pip" else parsed.package
142
+ print(f"Updated {target} via {manager}.")
143
+ return result.returncode
103
144
  if args.command == "flow" and rest[:1] in (["new"], ["companion"], ["promote"], ["split"], ["close"], ["finish"]):
104
145
  from .flow import main as flow_main
105
146
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@grifhinz/logics-manager",
3
3
  "displayName": "Logics Orchestrator",
4
4
  "description": "Visual orchestration for Logics workflows inside VS Code.",
5
- "version": "2.0.2",
5
+ "version": "2.0.4",
6
6
  "publisher": "cdx-logics",
7
7
  "icon": "media/icon.png",
8
8
  "repository": {
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "logics-manager"
7
- version = "2.0.2"
7
+ version = "2.0.4"
8
8
  description = "Canonical Logics CLI"
9
9
  requires-python = ">=3.10"
10
10