@caddis/cli 0.4.15 → 0.4.18
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/bundles/antigravity-plugin/caddis_statusline.py +91 -14
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +91 -14
- package/bundles/antigravity-plugin/skills/confluence-hla/SKILL.md +8 -2
- package/bundles/antigravity-plugin/skills/confluence-hla/scripts/convert_hla_to_confluence.py +11 -5
- package/bundles/codex/.codex/skills/docs/confluence-hla/SKILL.md +8 -2
- package/bundles/codex/.codex/skills/docs/confluence-hla/scripts/convert_hla_to_confluence.py +11 -5
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -38,8 +38,22 @@ import shutil
|
|
|
38
38
|
import subprocess
|
|
39
39
|
import sys
|
|
40
40
|
import time
|
|
41
|
+
import datetime
|
|
42
|
+
import traceback
|
|
41
43
|
from pathlib import Path
|
|
42
44
|
|
|
45
|
+
_DEBUG_LOG = Path.home() / ".caddis" / "statusline_debug.log"
|
|
46
|
+
def _debug_log(msg: str):
|
|
47
|
+
if not os.environ.get("CADDIS_STATUSLINE_DEBUG"):
|
|
48
|
+
return
|
|
49
|
+
try:
|
|
50
|
+
with open(_DEBUG_LOG, "a", encoding="utf-8") as f:
|
|
51
|
+
f.write(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}] [PID {os.getpid()}] {msg}\n")
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
_debug_log(f"INVOKE: argv={sys.argv} cwd={os.getcwd()}")
|
|
56
|
+
|
|
43
57
|
# --------------------------------------------------------------------------------------
|
|
44
58
|
# Config
|
|
45
59
|
# --------------------------------------------------------------------------------------
|
|
@@ -186,17 +200,30 @@ def term_width() -> int:
|
|
|
186
200
|
# Host facts the payload does not carry
|
|
187
201
|
# --------------------------------------------------------------------------------------
|
|
188
202
|
|
|
203
|
+
_GIT_CACHE_FILE = Path.home() / ".caddis" / ".git_status_cache.json"
|
|
204
|
+
|
|
205
|
+
|
|
189
206
|
def git_facts(cwd: str) -> dict:
|
|
190
207
|
"""One `git status --porcelain=v2 --branch` call -> branch, staged/unstaged/untracked,
|
|
191
|
-
ahead/behind.
|
|
192
|
-
|
|
208
|
+
ahead/behind. Uses a 2-second cache file per cwd to eliminate git process thrashing on rapid
|
|
209
|
+
status updates."""
|
|
193
210
|
out = {"branch": "", "staged": 0, "unstaged": 0, "untracked": 0, "ahead": 0, "behind": 0}
|
|
194
211
|
if not cwd or not os.path.isdir(cwd):
|
|
195
212
|
return out
|
|
213
|
+
|
|
214
|
+
now = time.time()
|
|
215
|
+
try:
|
|
216
|
+
if _GIT_CACHE_FILE.exists():
|
|
217
|
+
cached = json.loads(_GIT_CACHE_FILE.read_text(encoding="utf-8"))
|
|
218
|
+
if cached.get("cwd") == cwd and (now - cached.get("time", 0)) < 2.0:
|
|
219
|
+
return cached.get("facts", out)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
|
|
196
223
|
try:
|
|
197
224
|
proc = subprocess.run(
|
|
198
225
|
["git", "-C", cwd, "--no-optional-locks", "status", "--porcelain=v2", "--branch"],
|
|
199
|
-
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=
|
|
226
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=0.8,
|
|
200
227
|
)
|
|
201
228
|
text = proc.stdout.decode("utf-8", "replace")
|
|
202
229
|
except Exception:
|
|
@@ -223,6 +250,12 @@ def git_facts(cwd: str) -> dict:
|
|
|
223
250
|
out["unstaged"] += 1
|
|
224
251
|
elif line.startswith("? "):
|
|
225
252
|
out["untracked"] += 1
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
_GIT_CACHE_FILE.write_text(json.dumps({"cwd": cwd, "time": now, "facts": out}), encoding="utf-8")
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
226
259
|
return out
|
|
227
260
|
|
|
228
261
|
|
|
@@ -722,7 +755,19 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
722
755
|
if host == "agy":
|
|
723
756
|
entry["enabled"] = True # agy ignores a statusLine block without this
|
|
724
757
|
|
|
725
|
-
|
|
758
|
+
changed = False
|
|
759
|
+
if data.get("statusLine") != entry:
|
|
760
|
+
data["statusLine"] = entry
|
|
761
|
+
changed = True
|
|
762
|
+
|
|
763
|
+
if host == "agy":
|
|
764
|
+
title_cmd = command.replace("--profile agy", "--title")
|
|
765
|
+
title_entry = {"type": "command", "command": title_cmd, "enabled": True}
|
|
766
|
+
if data.get("title") != title_entry:
|
|
767
|
+
data["title"] = title_entry
|
|
768
|
+
changed = True
|
|
769
|
+
|
|
770
|
+
if not changed:
|
|
726
771
|
print(" ok %-6s already wired" % host)
|
|
727
772
|
return 0
|
|
728
773
|
|
|
@@ -730,7 +775,6 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
730
775
|
if settings_path.exists():
|
|
731
776
|
backup = settings_path.with_suffix(settings_path.suffix + ".bak-caddis-statusline")
|
|
732
777
|
shutil.copyfile(str(settings_path), str(backup))
|
|
733
|
-
data["statusLine"] = entry
|
|
734
778
|
settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
735
779
|
print(" ok %-6s wired -> %s" % (host, settings_path))
|
|
736
780
|
return 0
|
|
@@ -761,6 +805,12 @@ def check() -> int:
|
|
|
761
805
|
else:
|
|
762
806
|
print(" !! %-6s points elsewhere: %s" % (host, cmd))
|
|
763
807
|
bad += 1
|
|
808
|
+
if host == "agy":
|
|
809
|
+
t_entry = data.get("title")
|
|
810
|
+
if isinstance(t_entry, dict) and "statusline.py" in str(t_entry.get("command", "")) and "--title" in str(t_entry.get("command", "")):
|
|
811
|
+
print(" ok agy-title %s" % t_entry.get("command"))
|
|
812
|
+
else:
|
|
813
|
+
print(" -- agy-title not wired to statusline.py (run --install to wire)")
|
|
764
814
|
return bad
|
|
765
815
|
|
|
766
816
|
|
|
@@ -815,6 +865,7 @@ def main(argv=None) -> int:
|
|
|
815
865
|
parser.add_argument("--sample", action="store_true", help="render a fake payload (preview / test)")
|
|
816
866
|
parser.add_argument("--dry-run", action="store_true", help="with --install: print, write nothing")
|
|
817
867
|
parser.add_argument("--no-color", action="store_true", help="render without ANSI colour")
|
|
868
|
+
parser.add_argument("--title", action="store_true", help="render terminal title (agy)")
|
|
818
869
|
args = parser.parse_args(argv)
|
|
819
870
|
|
|
820
871
|
# The line is Unicode. On Windows the default cp1252 stdout raises on ◆/⎇/█.
|
|
@@ -827,6 +878,21 @@ def main(argv=None) -> int:
|
|
|
827
878
|
except Exception:
|
|
828
879
|
pass
|
|
829
880
|
|
|
881
|
+
if args.title:
|
|
882
|
+
cwd = os.getcwd()
|
|
883
|
+
repo = os.path.basename(cwd) or cwd
|
|
884
|
+
facts = git_facts(cwd)
|
|
885
|
+
branch = facts.get("branch", "")
|
|
886
|
+
if branch:
|
|
887
|
+
print(f"agy: {repo} [{branch}]")
|
|
888
|
+
else:
|
|
889
|
+
print(f"agy: {repo}")
|
|
890
|
+
try:
|
|
891
|
+
sys.stdout.flush()
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
894
|
+
return 0
|
|
895
|
+
|
|
830
896
|
if args.install:
|
|
831
897
|
hosts = ["claude", "agy"] if args.host == "both" else [args.host]
|
|
832
898
|
return 1 if install(hosts, args.dry_run) else 0
|
|
@@ -838,31 +904,39 @@ def main(argv=None) -> int:
|
|
|
838
904
|
if args.no_color:
|
|
839
905
|
cfg["color"] = False
|
|
840
906
|
|
|
907
|
+
t0 = time.time()
|
|
841
908
|
if args.sample:
|
|
842
909
|
data = SAMPLES[args.profile]
|
|
843
910
|
else:
|
|
844
911
|
try:
|
|
845
912
|
raw = sys.stdin.read()
|
|
846
|
-
except Exception:
|
|
913
|
+
except Exception as e:
|
|
914
|
+
_debug_log(f"stdin.read exception: {e}")
|
|
847
915
|
raw = ""
|
|
916
|
+
_debug_log(f"stdin read: {len(raw)} bytes in {time.time()-t0:.3f}s")
|
|
848
917
|
start = raw.find("{") # strip a UTF-8 BOM or any leading noise
|
|
849
918
|
try:
|
|
850
919
|
data = json.loads(raw[start:]) if start >= 0 else {}
|
|
851
|
-
except Exception:
|
|
920
|
+
except Exception as e:
|
|
921
|
+
_debug_log(f"json.loads exception: {e}")
|
|
852
922
|
data = {}
|
|
853
923
|
if not isinstance(data, dict):
|
|
854
924
|
data = {}
|
|
855
925
|
|
|
856
926
|
try:
|
|
857
927
|
line = RENDERERS[args.profile](data, cfg)
|
|
858
|
-
|
|
928
|
+
_debug_log(f"Rendered in {time.time()-t0:.3f}s")
|
|
929
|
+
except Exception as e:
|
|
930
|
+
_debug_log(f"Renderer exception: {traceback.format_exc()}")
|
|
859
931
|
# A status line must never break the host. Degrade to the directory name.
|
|
860
932
|
line = os.path.basename(os.getcwd())
|
|
861
933
|
|
|
862
934
|
try:
|
|
863
935
|
print(line)
|
|
864
936
|
sys.stdout.flush()
|
|
865
|
-
|
|
937
|
+
_debug_log(f"Printed and flushed in {time.time()-t0:.3f}s")
|
|
938
|
+
except (BrokenPipeError, OSError) as e:
|
|
939
|
+
_debug_log(f"BrokenPipeError on flush: {e}")
|
|
866
940
|
# Parent process cancelled or closed stdout pipe (e.g. agy runner on user prompt/state switch).
|
|
867
941
|
# Redirecting to devnull prevents Windows Python from throwing unhandled TextIOWrapper errors on exit.
|
|
868
942
|
try:
|
|
@@ -871,7 +945,8 @@ def main(argv=None) -> int:
|
|
|
871
945
|
except Exception:
|
|
872
946
|
pass
|
|
873
947
|
return 0
|
|
874
|
-
except Exception:
|
|
948
|
+
except Exception as e:
|
|
949
|
+
_debug_log(f"Flush exception: {e}")
|
|
875
950
|
return 0
|
|
876
951
|
|
|
877
952
|
return 0
|
|
@@ -880,10 +955,12 @@ def main(argv=None) -> int:
|
|
|
880
955
|
if __name__ == "__main__":
|
|
881
956
|
try:
|
|
882
957
|
code = main()
|
|
883
|
-
except BaseException:
|
|
958
|
+
except BaseException as e:
|
|
959
|
+
_debug_log(f"Unhandled exception in main: {traceback.format_exc()}")
|
|
884
960
|
code = 0
|
|
885
961
|
try:
|
|
886
962
|
sys.stdout.flush()
|
|
887
|
-
except Exception:
|
|
888
|
-
|
|
889
|
-
|
|
963
|
+
except Exception as e:
|
|
964
|
+
_debug_log(f"Final flush exception: {e}")
|
|
965
|
+
_debug_log(f"EXIT with code {code}")
|
|
966
|
+
os._exit(code)
|
|
@@ -38,8 +38,22 @@ import shutil
|
|
|
38
38
|
import subprocess
|
|
39
39
|
import sys
|
|
40
40
|
import time
|
|
41
|
+
import datetime
|
|
42
|
+
import traceback
|
|
41
43
|
from pathlib import Path
|
|
42
44
|
|
|
45
|
+
_DEBUG_LOG = Path.home() / ".caddis" / "statusline_debug.log"
|
|
46
|
+
def _debug_log(msg: str):
|
|
47
|
+
if not os.environ.get("CADDIS_STATUSLINE_DEBUG"):
|
|
48
|
+
return
|
|
49
|
+
try:
|
|
50
|
+
with open(_DEBUG_LOG, "a", encoding="utf-8") as f:
|
|
51
|
+
f.write(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}] [PID {os.getpid()}] {msg}\n")
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
_debug_log(f"INVOKE: argv={sys.argv} cwd={os.getcwd()}")
|
|
56
|
+
|
|
43
57
|
# --------------------------------------------------------------------------------------
|
|
44
58
|
# Config
|
|
45
59
|
# --------------------------------------------------------------------------------------
|
|
@@ -186,17 +200,30 @@ def term_width() -> int:
|
|
|
186
200
|
# Host facts the payload does not carry
|
|
187
201
|
# --------------------------------------------------------------------------------------
|
|
188
202
|
|
|
203
|
+
_GIT_CACHE_FILE = Path.home() / ".caddis" / ".git_status_cache.json"
|
|
204
|
+
|
|
205
|
+
|
|
189
206
|
def git_facts(cwd: str) -> dict:
|
|
190
207
|
"""One `git status --porcelain=v2 --branch` call -> branch, staged/unstaged/untracked,
|
|
191
|
-
ahead/behind.
|
|
192
|
-
|
|
208
|
+
ahead/behind. Uses a 2-second cache file per cwd to eliminate git process thrashing on rapid
|
|
209
|
+
status updates."""
|
|
193
210
|
out = {"branch": "", "staged": 0, "unstaged": 0, "untracked": 0, "ahead": 0, "behind": 0}
|
|
194
211
|
if not cwd or not os.path.isdir(cwd):
|
|
195
212
|
return out
|
|
213
|
+
|
|
214
|
+
now = time.time()
|
|
215
|
+
try:
|
|
216
|
+
if _GIT_CACHE_FILE.exists():
|
|
217
|
+
cached = json.loads(_GIT_CACHE_FILE.read_text(encoding="utf-8"))
|
|
218
|
+
if cached.get("cwd") == cwd and (now - cached.get("time", 0)) < 2.0:
|
|
219
|
+
return cached.get("facts", out)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
|
|
196
223
|
try:
|
|
197
224
|
proc = subprocess.run(
|
|
198
225
|
["git", "-C", cwd, "--no-optional-locks", "status", "--porcelain=v2", "--branch"],
|
|
199
|
-
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=
|
|
226
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=0.8,
|
|
200
227
|
)
|
|
201
228
|
text = proc.stdout.decode("utf-8", "replace")
|
|
202
229
|
except Exception:
|
|
@@ -223,6 +250,12 @@ def git_facts(cwd: str) -> dict:
|
|
|
223
250
|
out["unstaged"] += 1
|
|
224
251
|
elif line.startswith("? "):
|
|
225
252
|
out["untracked"] += 1
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
_GIT_CACHE_FILE.write_text(json.dumps({"cwd": cwd, "time": now, "facts": out}), encoding="utf-8")
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
226
259
|
return out
|
|
227
260
|
|
|
228
261
|
|
|
@@ -722,7 +755,19 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
722
755
|
if host == "agy":
|
|
723
756
|
entry["enabled"] = True # agy ignores a statusLine block without this
|
|
724
757
|
|
|
725
|
-
|
|
758
|
+
changed = False
|
|
759
|
+
if data.get("statusLine") != entry:
|
|
760
|
+
data["statusLine"] = entry
|
|
761
|
+
changed = True
|
|
762
|
+
|
|
763
|
+
if host == "agy":
|
|
764
|
+
title_cmd = command.replace("--profile agy", "--title")
|
|
765
|
+
title_entry = {"type": "command", "command": title_cmd, "enabled": True}
|
|
766
|
+
if data.get("title") != title_entry:
|
|
767
|
+
data["title"] = title_entry
|
|
768
|
+
changed = True
|
|
769
|
+
|
|
770
|
+
if not changed:
|
|
726
771
|
print(" ok %-6s already wired" % host)
|
|
727
772
|
return 0
|
|
728
773
|
|
|
@@ -730,7 +775,6 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
730
775
|
if settings_path.exists():
|
|
731
776
|
backup = settings_path.with_suffix(settings_path.suffix + ".bak-caddis-statusline")
|
|
732
777
|
shutil.copyfile(str(settings_path), str(backup))
|
|
733
|
-
data["statusLine"] = entry
|
|
734
778
|
settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
735
779
|
print(" ok %-6s wired -> %s" % (host, settings_path))
|
|
736
780
|
return 0
|
|
@@ -761,6 +805,12 @@ def check() -> int:
|
|
|
761
805
|
else:
|
|
762
806
|
print(" !! %-6s points elsewhere: %s" % (host, cmd))
|
|
763
807
|
bad += 1
|
|
808
|
+
if host == "agy":
|
|
809
|
+
t_entry = data.get("title")
|
|
810
|
+
if isinstance(t_entry, dict) and "statusline.py" in str(t_entry.get("command", "")) and "--title" in str(t_entry.get("command", "")):
|
|
811
|
+
print(" ok agy-title %s" % t_entry.get("command"))
|
|
812
|
+
else:
|
|
813
|
+
print(" -- agy-title not wired to statusline.py (run --install to wire)")
|
|
764
814
|
return bad
|
|
765
815
|
|
|
766
816
|
|
|
@@ -815,6 +865,7 @@ def main(argv=None) -> int:
|
|
|
815
865
|
parser.add_argument("--sample", action="store_true", help="render a fake payload (preview / test)")
|
|
816
866
|
parser.add_argument("--dry-run", action="store_true", help="with --install: print, write nothing")
|
|
817
867
|
parser.add_argument("--no-color", action="store_true", help="render without ANSI colour")
|
|
868
|
+
parser.add_argument("--title", action="store_true", help="render terminal title (agy)")
|
|
818
869
|
args = parser.parse_args(argv)
|
|
819
870
|
|
|
820
871
|
# The line is Unicode. On Windows the default cp1252 stdout raises on ◆/⎇/█.
|
|
@@ -827,6 +878,21 @@ def main(argv=None) -> int:
|
|
|
827
878
|
except Exception:
|
|
828
879
|
pass
|
|
829
880
|
|
|
881
|
+
if args.title:
|
|
882
|
+
cwd = os.getcwd()
|
|
883
|
+
repo = os.path.basename(cwd) or cwd
|
|
884
|
+
facts = git_facts(cwd)
|
|
885
|
+
branch = facts.get("branch", "")
|
|
886
|
+
if branch:
|
|
887
|
+
print(f"agy: {repo} [{branch}]")
|
|
888
|
+
else:
|
|
889
|
+
print(f"agy: {repo}")
|
|
890
|
+
try:
|
|
891
|
+
sys.stdout.flush()
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
894
|
+
return 0
|
|
895
|
+
|
|
830
896
|
if args.install:
|
|
831
897
|
hosts = ["claude", "agy"] if args.host == "both" else [args.host]
|
|
832
898
|
return 1 if install(hosts, args.dry_run) else 0
|
|
@@ -838,31 +904,39 @@ def main(argv=None) -> int:
|
|
|
838
904
|
if args.no_color:
|
|
839
905
|
cfg["color"] = False
|
|
840
906
|
|
|
907
|
+
t0 = time.time()
|
|
841
908
|
if args.sample:
|
|
842
909
|
data = SAMPLES[args.profile]
|
|
843
910
|
else:
|
|
844
911
|
try:
|
|
845
912
|
raw = sys.stdin.read()
|
|
846
|
-
except Exception:
|
|
913
|
+
except Exception as e:
|
|
914
|
+
_debug_log(f"stdin.read exception: {e}")
|
|
847
915
|
raw = ""
|
|
916
|
+
_debug_log(f"stdin read: {len(raw)} bytes in {time.time()-t0:.3f}s")
|
|
848
917
|
start = raw.find("{") # strip a UTF-8 BOM or any leading noise
|
|
849
918
|
try:
|
|
850
919
|
data = json.loads(raw[start:]) if start >= 0 else {}
|
|
851
|
-
except Exception:
|
|
920
|
+
except Exception as e:
|
|
921
|
+
_debug_log(f"json.loads exception: {e}")
|
|
852
922
|
data = {}
|
|
853
923
|
if not isinstance(data, dict):
|
|
854
924
|
data = {}
|
|
855
925
|
|
|
856
926
|
try:
|
|
857
927
|
line = RENDERERS[args.profile](data, cfg)
|
|
858
|
-
|
|
928
|
+
_debug_log(f"Rendered in {time.time()-t0:.3f}s")
|
|
929
|
+
except Exception as e:
|
|
930
|
+
_debug_log(f"Renderer exception: {traceback.format_exc()}")
|
|
859
931
|
# A status line must never break the host. Degrade to the directory name.
|
|
860
932
|
line = os.path.basename(os.getcwd())
|
|
861
933
|
|
|
862
934
|
try:
|
|
863
935
|
print(line)
|
|
864
936
|
sys.stdout.flush()
|
|
865
|
-
|
|
937
|
+
_debug_log(f"Printed and flushed in {time.time()-t0:.3f}s")
|
|
938
|
+
except (BrokenPipeError, OSError) as e:
|
|
939
|
+
_debug_log(f"BrokenPipeError on flush: {e}")
|
|
866
940
|
# Parent process cancelled or closed stdout pipe (e.g. agy runner on user prompt/state switch).
|
|
867
941
|
# Redirecting to devnull prevents Windows Python from throwing unhandled TextIOWrapper errors on exit.
|
|
868
942
|
try:
|
|
@@ -871,7 +945,8 @@ def main(argv=None) -> int:
|
|
|
871
945
|
except Exception:
|
|
872
946
|
pass
|
|
873
947
|
return 0
|
|
874
|
-
except Exception:
|
|
948
|
+
except Exception as e:
|
|
949
|
+
_debug_log(f"Flush exception: {e}")
|
|
875
950
|
return 0
|
|
876
951
|
|
|
877
952
|
return 0
|
|
@@ -880,10 +955,12 @@ def main(argv=None) -> int:
|
|
|
880
955
|
if __name__ == "__main__":
|
|
881
956
|
try:
|
|
882
957
|
code = main()
|
|
883
|
-
except BaseException:
|
|
958
|
+
except BaseException as e:
|
|
959
|
+
_debug_log(f"Unhandled exception in main: {traceback.format_exc()}")
|
|
884
960
|
code = 0
|
|
885
961
|
try:
|
|
886
962
|
sys.stdout.flush()
|
|
887
|
-
except Exception:
|
|
888
|
-
|
|
889
|
-
|
|
963
|
+
except Exception as e:
|
|
964
|
+
_debug_log(f"Final flush exception: {e}")
|
|
965
|
+
_debug_log(f"EXIT with code {code}")
|
|
966
|
+
os._exit(code)
|
|
@@ -89,6 +89,12 @@ When authoring an HLA, strictly adhere to the following 7-section structure:
|
|
|
89
89
|
[Note: 7.2 References is kept in working Markdown but STRICTLY OMITTED from Confluence HTML]
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Key Section Authoring Guidelines:
|
|
93
|
+
- **Section 3.1 Operational Process Flow:** Document the flow using clean, continuous numbered text steps (`1. **Step Name:** ...`). Completely omit ASCII, unicode, or plain-text drawing boxes, which break formatting in Confluence. The accompanying sequence diagram in 3.2 provides the visual workflow.
|
|
94
|
+
- **Section 3.2 End-to-End Sequence Diagram:** Autonumber all interactions. Apply corporate palette classes (`clientStyle`, `serverStyle`, `appStyle`, `dataStyle`, `extStyle`). Wrap all participant names, notes, and message labels under 45 characters using `<br/>`.
|
|
95
|
+
- **Section 4.1 Solution Design and Architecture:** The standardized title is `4.1 Solution Design and Architecture` (do NOT use "System Topology and Network Boundaries"). Provides the compact component topology and network boundary diagram.
|
|
96
|
+
- **Tables:** All tables must be authored cleanly and converted into `<table class="wrapped">` with wrapped headers (`<th><p>Header</p></th>`) and wrapped table cells (`<td><p>Value</p></td>`).
|
|
97
|
+
|
|
92
98
|
---
|
|
93
99
|
|
|
94
100
|
## 3. High-Level Database and Data Processing Pipeline Discovery
|
|
@@ -272,7 +278,7 @@ Never use default bright or garish PlantUML colors. Use muted corporate Material
|
|
|
272
278
|
- **Stage 2 (Middle):** Language Model Synthesis Engine (`LLMEngine`), receiving 90-degree orthogonal connections from each track's summary node (`NSum -right-> LLMEngine`, etc.).
|
|
273
279
|
- **Stage 3 (Right):** Action Recommendation & Justification (`RecOutput`), receiving a horizontal connection from `LLMEngine`.
|
|
274
280
|
|
|
275
|
-
4. **Component-to-Component Connections in
|
|
281
|
+
4. **Component-to-Component Connections in Solution Design & Architecture:**
|
|
276
282
|
- Connect internal component nodes directly (e.g., `Worker -down-> SQL`, `Worker -down-> Apigee`, `Worker -right-> LLM`).
|
|
277
283
|
- Do NOT connect package boundaries (e.g., `HostLayer -down-> MidLayer`), which inflates diagram bounding boxes and distorts routing.
|
|
278
284
|
- Use `skinparam nodesep 60` and `skinparam ranksep 50` for comfortable whitespace.
|
|
@@ -337,7 +343,7 @@ Inspect the target codebase across five dimensions:
|
|
|
337
343
|
- Write using the 7-section canonical structure in Simplified Technical English.
|
|
338
344
|
- Author PlantUML (or Mermaid) diagram blocks for:
|
|
339
345
|
1. Sequence diagram
|
|
340
|
-
2.
|
|
346
|
+
2. Solution Design and Architecture diagram (compact topology)
|
|
341
347
|
3. Multi-track summarization pipeline
|
|
342
348
|
4. End-to-end database and processing pipeline (Section 4.5)
|
|
343
349
|
- Document the Screener Feedback Service and RLHF loop (Section 4.6).
|
package/bundles/antigravity-plugin/skills/confluence-hla/scripts/convert_hla_to_confluence.py
CHANGED
|
@@ -180,7 +180,13 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
180
180
|
|
|
181
181
|
subgraph_start_re = re.compile(r'^\s*subgraph\s+([A-Za-z0-9_]+)(?:\["([^"]+)"\]|\[([^\]]+)\])?', re.IGNORECASE)
|
|
182
182
|
direction_re = re.compile(r'^\s*direction\s+(LR|RL|TB|TD)', re.IGNORECASE)
|
|
183
|
-
node_def_re = re.compile(
|
|
183
|
+
node_def_re = re.compile(
|
|
184
|
+
r'([A-Za-z0-9_]+)(?:'
|
|
185
|
+
r'\["([^"]+)"\]|\[([^\]]+)\]|'
|
|
186
|
+
r'\{"([^"]+)"\}|\{([^\}]+)\}|'
|
|
187
|
+
r'\("([^"]+)"\)|\(([^\)]+)\)'
|
|
188
|
+
r')(?::::([A-Za-z0-9_]+))?'
|
|
189
|
+
)
|
|
184
190
|
|
|
185
191
|
has_flowchart = False
|
|
186
192
|
for line in lines:
|
|
@@ -222,9 +228,9 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
222
228
|
|
|
223
229
|
# 1. Extract node definitions
|
|
224
230
|
for match in node_def_re.finditer(stripped):
|
|
225
|
-
n_id = match.group(1)
|
|
226
|
-
n_lbl =
|
|
227
|
-
n_cls = match.group(
|
|
231
|
+
n_id = match.group(1)
|
|
232
|
+
n_lbl = next(g for g in match.groups()[1:7] if g is not None)
|
|
233
|
+
n_cls = match.group(8) or ""
|
|
228
234
|
node_info = {"id": n_id, "label": n_lbl, "class": n_cls}
|
|
229
235
|
if sg_stack:
|
|
230
236
|
sg_stack[-1]["nodes"][n_id] = node_info
|
|
@@ -233,7 +239,7 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
233
239
|
|
|
234
240
|
# 2. Clean line for edge extraction
|
|
235
241
|
def repl_node(m):
|
|
236
|
-
return m.group(1)
|
|
242
|
+
return m.group(1)
|
|
237
243
|
clean_edge_line = node_def_re.sub(repl_node, stripped)
|
|
238
244
|
|
|
239
245
|
# 3. Extract edges
|
|
@@ -89,6 +89,12 @@ When authoring an HLA, strictly adhere to the following 7-section structure:
|
|
|
89
89
|
[Note: 7.2 References is kept in working Markdown but STRICTLY OMITTED from Confluence HTML]
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Key Section Authoring Guidelines:
|
|
93
|
+
- **Section 3.1 Operational Process Flow:** Document the flow using clean, continuous numbered text steps (`1. **Step Name:** ...`). Completely omit ASCII, unicode, or plain-text drawing boxes, which break formatting in Confluence. The accompanying sequence diagram in 3.2 provides the visual workflow.
|
|
94
|
+
- **Section 3.2 End-to-End Sequence Diagram:** Autonumber all interactions. Apply corporate palette classes (`clientStyle`, `serverStyle`, `appStyle`, `dataStyle`, `extStyle`). Wrap all participant names, notes, and message labels under 45 characters using `<br/>`.
|
|
95
|
+
- **Section 4.1 Solution Design and Architecture:** The standardized title is `4.1 Solution Design and Architecture` (do NOT use "System Topology and Network Boundaries"). Provides the compact component topology and network boundary diagram.
|
|
96
|
+
- **Tables:** All tables must be authored cleanly and converted into `<table class="wrapped">` with wrapped headers (`<th><p>Header</p></th>`) and wrapped table cells (`<td><p>Value</p></td>`).
|
|
97
|
+
|
|
92
98
|
---
|
|
93
99
|
|
|
94
100
|
## 3. High-Level Database and Data Processing Pipeline Discovery
|
|
@@ -272,7 +278,7 @@ Never use default bright or garish PlantUML colors. Use muted corporate Material
|
|
|
272
278
|
- **Stage 2 (Middle):** Language Model Synthesis Engine (`LLMEngine`), receiving 90-degree orthogonal connections from each track's summary node (`NSum -right-> LLMEngine`, etc.).
|
|
273
279
|
- **Stage 3 (Right):** Action Recommendation & Justification (`RecOutput`), receiving a horizontal connection from `LLMEngine`.
|
|
274
280
|
|
|
275
|
-
4. **Component-to-Component Connections in
|
|
281
|
+
4. **Component-to-Component Connections in Solution Design & Architecture:**
|
|
276
282
|
- Connect internal component nodes directly (e.g., `Worker -down-> SQL`, `Worker -down-> Apigee`, `Worker -right-> LLM`).
|
|
277
283
|
- Do NOT connect package boundaries (e.g., `HostLayer -down-> MidLayer`), which inflates diagram bounding boxes and distorts routing.
|
|
278
284
|
- Use `skinparam nodesep 60` and `skinparam ranksep 50` for comfortable whitespace.
|
|
@@ -337,7 +343,7 @@ Inspect the target codebase across five dimensions:
|
|
|
337
343
|
- Write using the 7-section canonical structure in Simplified Technical English.
|
|
338
344
|
- Author PlantUML (or Mermaid) diagram blocks for:
|
|
339
345
|
1. Sequence diagram
|
|
340
|
-
2.
|
|
346
|
+
2. Solution Design and Architecture diagram (compact topology)
|
|
341
347
|
3. Multi-track summarization pipeline
|
|
342
348
|
4. End-to-end database and processing pipeline (Section 4.5)
|
|
343
349
|
- Document the Screener Feedback Service and RLHF loop (Section 4.6).
|
package/bundles/codex/.codex/skills/docs/confluence-hla/scripts/convert_hla_to_confluence.py
CHANGED
|
@@ -180,7 +180,13 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
180
180
|
|
|
181
181
|
subgraph_start_re = re.compile(r'^\s*subgraph\s+([A-Za-z0-9_]+)(?:\["([^"]+)"\]|\[([^\]]+)\])?', re.IGNORECASE)
|
|
182
182
|
direction_re = re.compile(r'^\s*direction\s+(LR|RL|TB|TD)', re.IGNORECASE)
|
|
183
|
-
node_def_re = re.compile(
|
|
183
|
+
node_def_re = re.compile(
|
|
184
|
+
r'([A-Za-z0-9_]+)(?:'
|
|
185
|
+
r'\["([^"]+)"\]|\[([^\]]+)\]|'
|
|
186
|
+
r'\{"([^"]+)"\}|\{([^\}]+)\}|'
|
|
187
|
+
r'\("([^"]+)"\)|\(([^\)]+)\)'
|
|
188
|
+
r')(?::::([A-Za-z0-9_]+))?'
|
|
189
|
+
)
|
|
184
190
|
|
|
185
191
|
has_flowchart = False
|
|
186
192
|
for line in lines:
|
|
@@ -222,9 +228,9 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
222
228
|
|
|
223
229
|
# 1. Extract node definitions
|
|
224
230
|
for match in node_def_re.finditer(stripped):
|
|
225
|
-
n_id = match.group(1)
|
|
226
|
-
n_lbl =
|
|
227
|
-
n_cls = match.group(
|
|
231
|
+
n_id = match.group(1)
|
|
232
|
+
n_lbl = next(g for g in match.groups()[1:7] if g is not None)
|
|
233
|
+
n_cls = match.group(8) or ""
|
|
228
234
|
node_info = {"id": n_id, "label": n_lbl, "class": n_cls}
|
|
229
235
|
if sg_stack:
|
|
230
236
|
sg_stack[-1]["nodes"][n_id] = node_info
|
|
@@ -233,7 +239,7 @@ def _parse_mermaid_flowchart(code: str) -> Optional[str]:
|
|
|
233
239
|
|
|
234
240
|
# 2. Clean line for edge extraction
|
|
235
241
|
def repl_node(m):
|
|
236
|
-
return m.group(1)
|
|
242
|
+
return m.group(1)
|
|
237
243
|
clean_edge_line = node_def_re.sub(repl_node, stripped)
|
|
238
244
|
|
|
239
245
|
# 3. Extract edges
|
package/bundles/manifest.json
CHANGED
package/package.json
CHANGED