@caddis/cli 0.4.43 → 0.4.45
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/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_file_lock.py +48 -7
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +27 -0
- package/bundles/antigravity-plugin/scripts/caddis_todo.py +7 -5
- package/bundles/antigravity-plugin/scripts/claude-oss.ps1 +15 -3
- package/bundles/antigravity-plugin/scripts/claude-oss.sh +12 -1
- package/bundles/antigravity-plugin/skills/_registry.md +5 -4
- package/bundles/antigravity-plugin/skills/adopt-app/SKILL.md +6 -1
- package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +7 -1
- package/bundles/antigravity-plugin/skills/decide/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/flow-studio/README.md +53 -0
- package/bundles/antigravity-plugin/skills/flow-studio/SKILL.md +103 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/README.md +11 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/hostile.json +1783 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/lineage.json +1778 -0
- package/bundles/antigravity-plugin/skills/flow-studio/model.schema.json +123 -0
- package/bundles/antigravity-plugin/skills/flow-studio/package.json +11 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/patterns.md +79 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/build-engine.mjs +55 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/build.mjs +37 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/check.mjs +388 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/build-engine.test.mjs +26 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/check-selftest.test.mjs +75 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/cli.test.mjs +64 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.mjs +24 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.test.mjs +29 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/validate.mjs +38 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/chrome.ts +171 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/controls.ts +231 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/ctx.ts +118 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/engine.ts +142 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/inspector.ts +108 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/interact.ts +240 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/layout.test.ts +69 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/layout.ts +57 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/model.test.ts +107 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/model.ts +165 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/notes.ts +92 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/render.ts +155 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/routing.test.ts +13 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/routing.ts +11 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/rules.test.ts +88 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/rules.ts +35 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.test.ts +66 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.ts +44 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/styles.css +157 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/trace.test.ts +20 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/trace.ts +22 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.test.ts +55 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.ts +45 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.html +1744 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.shell.html +61 -0
- package/bundles/antigravity-plugin/skills/flow-studio/tsconfig.json +17 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +8 -0
- package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +6 -1
- package/bundles/antigravity-plugin/skills/port-to-github/SKILL.md +6 -1
- package/bundles/antigravity-plugin/skills/tech-explainer/SKILL.md +5 -6
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +2 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-rbac/SKILL.md +26 -0
- package/bundles/antigravity-plugin-extras/skills/case-management/SKILL.md +24 -0
- package/bundles/codex/.codex/skills/devops/adopt-app/SKILL.md +6 -1
- package/bundles/codex/.codex/skills/devops/port-to-github/SKILL.md +6 -1
- package/bundles/codex/.codex/skills/docs/tech-explainer/SKILL.md +5 -6
- package/bundles/codex/.codex/skills/frontend/flow-studio/README.md +53 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +103 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/README.md +11 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/hostile.json +1783 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/lineage.json +1778 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/model.schema.json +123 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/package.json +11 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/patterns.md +79 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build-engine.mjs +55 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build.mjs +37 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/check.mjs +388 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/build-engine.test.mjs +26 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/check-selftest.test.mjs +75 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/cli.test.mjs +64 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.mjs +24 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.test.mjs +29 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/validate.mjs +38 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/chrome.ts +171 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/controls.ts +231 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/ctx.ts +118 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/engine.ts +142 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/inspector.ts +108 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/interact.ts +240 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.test.ts +69 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.ts +57 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.test.ts +107 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.ts +165 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/notes.ts +92 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/render.ts +155 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.test.ts +13 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.ts +11 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.test.ts +88 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.ts +35 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.test.ts +66 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.ts +44 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/styles.css +157 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.test.ts +20 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.ts +22 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.test.ts +55 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.ts +45 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.html +1744 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.shell.html +61 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/tsconfig.json +17 -0
- package/bundles/codex/.codex/skills/media/particle-art/SKILL.md +6 -1
- package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +7 -1
- package/bundles/manifest.json +3 -3
- package/package.json +1 -1
|
@@ -14,7 +14,9 @@ from pathlib import Path
|
|
|
14
14
|
import stat
|
|
15
15
|
import tempfile
|
|
16
16
|
import time
|
|
17
|
-
from typing import BinaryIO, Iterator
|
|
17
|
+
from typing import BinaryIO, Callable, Iterator, TypeVar
|
|
18
|
+
|
|
19
|
+
_T = TypeVar("_T")
|
|
18
20
|
|
|
19
21
|
if os.name == "nt":
|
|
20
22
|
import msvcrt
|
|
@@ -22,14 +24,53 @@ else:
|
|
|
22
24
|
import fcntl
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
# Windows refuses os.replace, and even a stat or a read, on a file that another process has open at
|
|
28
|
+
# that instant, with PermissionError (WinError 5). The window is short. Measured 2026-10-03 with one
|
|
29
|
+
# process replacing a file in a loop: 0 of 759 replaces failed alone, and 250 of 908 failed while a second
|
|
30
|
+
# process called resolve() on it. POSIX has no such window, and there a PermissionError is a real,
|
|
31
|
+
# lasting refusal, so it is never retried.
|
|
32
|
+
_RETRY_DENIED = os.name == "nt"
|
|
33
|
+
_DENIED_RETRY_S = 5.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def retry_denied(operation: Callable[[], _T]) -> _T:
|
|
37
|
+
"""Run ``operation``; on Windows retry a transient PermissionError for up to ``_DENIED_RETRY_S`` seconds."""
|
|
38
|
+
deadline = time.monotonic() + _DENIED_RETRY_S
|
|
39
|
+
while True:
|
|
40
|
+
try:
|
|
41
|
+
return operation()
|
|
42
|
+
except PermissionError:
|
|
43
|
+
if not _RETRY_DENIED or time.monotonic() >= deadline:
|
|
44
|
+
raise
|
|
45
|
+
time.sleep(0.01)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _plain(path: Path) -> Path:
|
|
49
|
+
"""Drop the Windows extended-length prefix: ``\\\\?\\C:\\x`` and ``C:\\x`` name the same place.
|
|
50
|
+
|
|
51
|
+
While another process is creating or replacing a path, ``Path.resolve()`` can return it in the prefixed form
|
|
52
|
+
while the root resolves to the plain form, and ``is_relative_to`` then says the path escapes the root
|
|
53
|
+
(measured 2026-10-03: 2 of 60 trials of two writers starting on an empty folder; CI hit it on 2026-10-02).
|
|
54
|
+
"""
|
|
55
|
+
text = str(path)
|
|
56
|
+
if text.startswith("\\\\?\\UNC\\"):
|
|
57
|
+
return Path("\\\\" + text[8:])
|
|
58
|
+
if text.startswith("\\\\?\\"):
|
|
59
|
+
return Path(text[4:])
|
|
60
|
+
return path
|
|
61
|
+
|
|
62
|
+
|
|
25
63
|
def _destination(path: Path, root: Path | None) -> Path:
|
|
26
64
|
path = Path(path)
|
|
27
65
|
if ".." in path.parts:
|
|
28
66
|
raise ValueError("destination may not contain parent traversal")
|
|
29
|
-
|
|
30
|
-
|
|
67
|
+
# Resolve the parent directory, not the file: another writer may be replacing the file right now, and
|
|
68
|
+
# resolving the file opens it, which can make that writer's os.replace fail. A symlink as the last
|
|
69
|
+
# component is refused below.
|
|
70
|
+
resolved = _plain(retry_denied(path.parent.resolve)) / path.name
|
|
71
|
+
if root is not None and not resolved.is_relative_to(_plain(Path(root).resolve())):
|
|
31
72
|
raise ValueError("destination escapes artifact root")
|
|
32
|
-
if path.is_symlink
|
|
73
|
+
if retry_denied(path.is_symlink):
|
|
33
74
|
raise ValueError("destination may not be a symlink")
|
|
34
75
|
return resolved
|
|
35
76
|
|
|
@@ -37,7 +78,7 @@ def _destination(path: Path, root: Path | None) -> Path:
|
|
|
37
78
|
def _document_mode(target: Path) -> int:
|
|
38
79
|
"""Mode the replaced document should keep: the existing one, else what ``open()`` would give."""
|
|
39
80
|
try:
|
|
40
|
-
return stat.S_IMODE(target.stat
|
|
81
|
+
return stat.S_IMODE(retry_denied(target.stat).st_mode)
|
|
41
82
|
except FileNotFoundError:
|
|
42
83
|
# os.umask has no read-only form; the probe is process-wide for one line, so a file
|
|
43
84
|
# another thread creates in that window gets 0o666. caddis writers are single-threaded.
|
|
@@ -78,7 +119,7 @@ def locked_path(
|
|
|
78
119
|
target = _destination(path, root)
|
|
79
120
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
80
121
|
lock_path = target.with_name(target.name + ".lock")
|
|
81
|
-
if lock_path.is_symlink
|
|
122
|
+
if retry_denied(lock_path.is_symlink):
|
|
82
123
|
raise ValueError("lock file may not be a symlink")
|
|
83
124
|
# O_NOFOLLOW closes the gap between the check above and the open on POSIX.
|
|
84
125
|
flags = os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
|
|
@@ -114,7 +155,7 @@ def atomic_write(path: Path, text: str, *, root: Path | None = None) -> None:
|
|
|
114
155
|
os.fsync(handle.fileno())
|
|
115
156
|
# NamedTemporaryFile creates 0o600; without this every write would narrow the document.
|
|
116
157
|
os.chmod(temporary, _document_mode(target))
|
|
117
|
-
os.replace(temporary, target)
|
|
158
|
+
retry_denied(lambda: os.replace(temporary, target))
|
|
118
159
|
temporary = None # replaced: that name may now belong to another writer
|
|
119
160
|
finally:
|
|
120
161
|
if temporary is not None:
|
|
@@ -182,6 +182,7 @@ class GateResult:
|
|
|
182
182
|
returncode: int
|
|
183
183
|
passed: bool
|
|
184
184
|
tail: str
|
|
185
|
+
duration_s: float = 0.0
|
|
185
186
|
|
|
186
187
|
|
|
187
188
|
@dataclass
|
|
@@ -738,6 +739,7 @@ def run_gates(
|
|
|
738
739
|
with heavy_slot("gates"):
|
|
739
740
|
results = []
|
|
740
741
|
for command in cmds:
|
|
742
|
+
gate_started = time.monotonic()
|
|
741
743
|
try:
|
|
742
744
|
completed = subprocess.run(
|
|
743
745
|
governed_argv(command),
|
|
@@ -756,6 +758,7 @@ def run_gates(
|
|
|
756
758
|
returncode=-1,
|
|
757
759
|
passed=False,
|
|
758
760
|
tail=f"timed out after {timeout_s}s",
|
|
761
|
+
duration_s=time.monotonic() - gate_started,
|
|
759
762
|
)
|
|
760
763
|
)
|
|
761
764
|
continue
|
|
@@ -770,6 +773,7 @@ def run_gates(
|
|
|
770
773
|
returncode=completed.returncode,
|
|
771
774
|
passed=passed,
|
|
772
775
|
tail=output[-2000:],
|
|
776
|
+
duration_s=time.monotonic() - gate_started,
|
|
773
777
|
)
|
|
774
778
|
)
|
|
775
779
|
return results
|
|
@@ -1417,6 +1421,9 @@ def build_prompt(
|
|
|
1417
1421
|
"run anything against production.\n"
|
|
1418
1422
|
"- Do not implement any other phase.\n"
|
|
1419
1423
|
"- Run every command in the foreground and wait for it to finish. Start no background tasks, watchers or dev servers.\n"
|
|
1424
|
+
"- Test economy: after each step run only the tests for the file or module you changed. Run the full suite "
|
|
1425
|
+
"only for the baseline and once at the end. Do not run a review loop in this lane; the orchestrator reviews. "
|
|
1426
|
+
"Print a progress line every 20 minutes. Never start a second heavy test run while one is still running.\n"
|
|
1420
1427
|
"Resource budget (enforced by the machine, not by this text):\n"
|
|
1421
1428
|
f"- You run inside a Windows Job Object: {limits_line}. Exceeding it slows "
|
|
1422
1429
|
"you down; it does not stop the limit.\n"
|
|
@@ -1558,6 +1565,23 @@ def _gate_dict(result: GateResult) -> dict:
|
|
|
1558
1565
|
"returncode": result.returncode,
|
|
1559
1566
|
"passed": result.passed,
|
|
1560
1567
|
"tail": result.tail,
|
|
1568
|
+
"duration_s": round(result.duration_s, 1),
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
def _time_split(packet: dict, gates: list[GateResult], review_s: float) -> dict:
|
|
1573
|
+
"""Measured seconds the orchestrator can read: the lane run, the gate commands, the review.
|
|
1574
|
+
|
|
1575
|
+
What happens INSIDE the lane (edits versus the lane's own test runs) is not measured: the idle
|
|
1576
|
+
sampler sees process image names every IDLE_SAMPLE_S seconds, not command lines, so it cannot tell a
|
|
1577
|
+
pytest run from any other python process.
|
|
1578
|
+
"""
|
|
1579
|
+
return {
|
|
1580
|
+
"lane_s": round(sum(float(lane.get("duration_s") or 0) for lane in packet.get("lanes_tried", [])), 1),
|
|
1581
|
+
"gates_s": round(sum(gate.duration_s for gate in gates), 1),
|
|
1582
|
+
"gate_runs": len(gates),
|
|
1583
|
+
"review_s": round(review_s, 1),
|
|
1584
|
+
"in_lane": "not measured: only process names are visible inside a lane",
|
|
1561
1585
|
}
|
|
1562
1586
|
|
|
1563
1587
|
|
|
@@ -1597,7 +1621,9 @@ def _run_post_checks(
|
|
|
1597
1621
|
packet.pop("_freeze_before", None)
|
|
1598
1622
|
return "stop", EXIT_NOT_RUN
|
|
1599
1623
|
|
|
1624
|
+
review_started = time.monotonic()
|
|
1600
1625
|
review = run_review(worktree, dict(os.environ))
|
|
1626
|
+
review_s = time.monotonic() - review_started
|
|
1601
1627
|
pythons = list(before)
|
|
1602
1628
|
if pythons:
|
|
1603
1629
|
after = {python: freeze_snapshot(python) for python in pythons}
|
|
@@ -1626,6 +1652,7 @@ def _run_post_checks(
|
|
|
1626
1652
|
"gates": [_gate_dict(gate) for gate in gates],
|
|
1627
1653
|
"gates_passed": gates_passed,
|
|
1628
1654
|
"review": _review_dict(review),
|
|
1655
|
+
"time_split": _time_split(packet, gates, review_s),
|
|
1629
1656
|
"freeze_unchanged": freeze_unchanged,
|
|
1630
1657
|
"next": next_step,
|
|
1631
1658
|
}
|
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
14
|
import caddis_exit
|
|
15
|
-
from caddis_file_lock import atomic_write, locked_path
|
|
15
|
+
from caddis_file_lock import atomic_write, locked_path, retry_denied
|
|
16
16
|
from caddis_frontmatter import parse, split_document
|
|
17
17
|
from caddis_workstreams import _artifact, _name
|
|
18
18
|
except ModuleNotFoundError as exc:
|
|
@@ -21,7 +21,7 @@ except ModuleNotFoundError as exc:
|
|
|
21
21
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
22
22
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "claude-harness" / "scripts"))
|
|
23
23
|
import caddis_exit
|
|
24
|
-
from caddis_file_lock import atomic_write, locked_path
|
|
24
|
+
from caddis_file_lock import atomic_write, locked_path, retry_denied
|
|
25
25
|
from caddis_frontmatter import parse, split_document
|
|
26
26
|
from caddis_workstreams import _artifact, _name
|
|
27
27
|
|
|
@@ -41,15 +41,17 @@ def _path(root: Path, workstream: str | None) -> tuple[Path, Path]:
|
|
|
41
41
|
if folder.is_symlink() or not folder.resolve().is_relative_to(artifact.resolve()):
|
|
42
42
|
raise ValueError("todo-list path escapes artifact root")
|
|
43
43
|
path = folder / f"{name}.md"
|
|
44
|
-
|
|
44
|
+
# Reads are unlocked, so refuse here as well as in locked_path. On Windows a writer replacing the file
|
|
45
|
+
# makes this probe (and the read below) fail with PermissionError for an instant, so both retry.
|
|
46
|
+
if retry_denied(path.is_symlink):
|
|
45
47
|
raise ValueError("to-do file may not be a symlink")
|
|
46
48
|
return artifact, path
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
def _read(path: Path, workstream: str | None) -> list[dict]:
|
|
50
|
-
if not path.exists
|
|
52
|
+
if not retry_denied(path.exists):
|
|
51
53
|
return []
|
|
52
|
-
text = path.read_text(encoding="utf-8")
|
|
54
|
+
text = retry_denied(lambda: path.read_text(encoding="utf-8"))
|
|
53
55
|
header, body = split_document(text)
|
|
54
56
|
name = workstream or "inbox"
|
|
55
57
|
if header.get("type") != "todo" or header.get("workstream") != name or header.get("status") not in {"current", "done"}:
|
|
@@ -32,7 +32,7 @@ if (-not $provider) {
|
|
|
32
32
|
|
|
33
33
|
# Save the current ANTHROPIC_* so we can restore them — this launcher shares the session.
|
|
34
34
|
$restore = @{}
|
|
35
|
-
foreach ($name in "ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL", "ANTHROPIC_AUTH_TOKEN", "CADDIS_HEADLESS") {
|
|
35
|
+
foreach ($name in "ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL", "ANTHROPIC_AUTH_TOKEN", "CADDIS_HEADLESS", "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS") {
|
|
36
36
|
$restore[$name] = [Environment]::GetEnvironmentVariable($name)
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -42,7 +42,15 @@ foreach ($name in "ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL", "ANTHROPIC_AUTH_TOKEN
|
|
|
42
42
|
# EXECUTING the relay's leftover next step before its own prompt.
|
|
43
43
|
$headless = $false
|
|
44
44
|
foreach ($a in $rest) { if ($a -eq "-p" -or $a -eq "--print") { $headless = $true } }
|
|
45
|
-
if ($headless) {
|
|
45
|
+
if ($headless) {
|
|
46
|
+
$env:CADDIS_HEADLESS = "1"
|
|
47
|
+
if ($provider -eq "glm") {
|
|
48
|
+
# Print mode exits on the first end_turn without waiting for background tasks.
|
|
49
|
+
# GLM can background steps and exit with 'standing by', returning no review.
|
|
50
|
+
# Disable background tasks so GLM executes verification steps synchronously.
|
|
51
|
+
$env:CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
46
54
|
|
|
47
55
|
try {
|
|
48
56
|
# Resolve endpoint/model/key. The three stdout lines are captured into a variable and
|
|
@@ -54,7 +62,11 @@ try {
|
|
|
54
62
|
$env:ANTHROPIC_MODEL = $cfg[1]
|
|
55
63
|
$env:ANTHROPIC_AUTH_TOKEN = $cfg[2]
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
if ($MyInvocation.ExpectingInput) {
|
|
66
|
+
$input | & claude @rest
|
|
67
|
+
} else {
|
|
68
|
+
& claude @rest
|
|
69
|
+
}
|
|
58
70
|
exit $LASTEXITCODE
|
|
59
71
|
} finally {
|
|
60
72
|
foreach ($name in $restore.Keys) {
|
|
@@ -33,12 +33,23 @@ fi
|
|
|
33
33
|
# act on it — the SessionStart relay injection is skipped for headless runs, because a run
|
|
34
34
|
# handed its task on the command line does not need a resume pointer and was observed
|
|
35
35
|
# EXECUTING the relay's leftover next step before its own prompt.
|
|
36
|
+
headless=0
|
|
36
37
|
for _arg in "$@"; do
|
|
37
38
|
case "$_arg" in
|
|
38
|
-
-p|--print)
|
|
39
|
+
-p|--print)
|
|
40
|
+
export CADDIS_HEADLESS=1
|
|
41
|
+
headless=1
|
|
42
|
+
;;
|
|
39
43
|
esac
|
|
40
44
|
done
|
|
41
45
|
|
|
46
|
+
if [ "$headless" -eq 1 ] && [ "$provider" = "glm" ]; then
|
|
47
|
+
# Print mode exits on the first end_turn without waiting for background tasks.
|
|
48
|
+
# GLM can background steps and exit with 'standing by', returning no review.
|
|
49
|
+
# Disable background tasks so GLM executes verification steps synchronously.
|
|
50
|
+
export CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
|
|
51
|
+
fi
|
|
52
|
+
|
|
42
53
|
# Prefer python3, fall back to python.
|
|
43
54
|
PY="python3"; command -v "$PY" >/dev/null 2>&1 || PY="python"
|
|
44
55
|
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
|
|
37
37
|
| Skill | Path | When to Use |
|
|
38
38
|
|-------|------|-------------|
|
|
39
|
-
| Adopt App | `adopt-app/` |
|
|
39
|
+
| Adopt App | `adopt-app/` | >- |
|
|
40
40
|
| Ci Cd Pipeline | `ci-cd-pipeline/` | CI/CD pipelines for GitHub Actions and Azure DevOps: build and deploy workflows, quality gates, environment promotion and release automation. |
|
|
41
41
|
| Gh Cli | `gh-cli/` | GitHub CLI operations — issues, PRs, releases, and repo management |
|
|
42
42
|
| Git Commit | `git-commit/` | Create well-structured conventional commit messages following Conventional Commits standard. Use when committing changes, preparing PRs, or generating changelogs. |
|
|
43
|
-
| Port To Github | `port-to-github/` |
|
|
43
|
+
| Port To Github | `port-to-github/` | >- |
|
|
44
44
|
| Safe Cleanup | `safe-cleanup/` | Use BEFORE deleting a directory tree, scratchpad or git worktree, or "tidying up" generated folders, especially on Windows, where deleting through a junction or symlink destroys its target; and to verify a venv or node_modules survived. |
|
|
45
45
|
| Using Git Worktrees | `using-git-worktrees/` | Use when starting feature work that needs isolation from the current workspace, or before executing an implementation plan: creates an isolated git worktree safely. |
|
|
46
46
|
| Windows Deployment | `windows-deployment/` | Deploy FastAPI + React/Vite apps to Windows Server with NSSM services, IIS or nginx reverse proxy and git-pull. Use for Windows prod deploys, NSSM, reverse proxy setup, dev/prod config, or prod troubleshooting. |
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
| Skill | Path | When to Use |
|
|
65
65
|
|-------|------|-------------|
|
|
66
66
|
| Css Architecture | `css-architecture/` | CSS architecture: design tokens, custom properties, Tailwind config, CSS Modules, container queries, dark mode, fluid typography, animation. Covers how to implement styling; frontend-design covers what to achieve. |
|
|
67
|
+
| Flow Studio | `flow-studio/` | Design an interactive flow, lifecycle or journey page (nodes, lanes, links, motion, inspector, notes) from a JSON model as a self-contained demo, then build it in an app. Use for lineage, pipelines, ticket or agent journeys. |
|
|
67
68
|
| Frontend Design | `frontend-design/` | Create distinctive, production-grade frontend interfaces that avoid generic AI aesthetics. Use when the user asks to build web components, pages, artifacts, posters or applications. |
|
|
68
69
|
| Mockup | `mockup/` | Create framework-aware UI mockups with feasibility checks. Prevents wasted effort by validating that proposed designs work within the target framework's constraints. |
|
|
69
70
|
| React Best Practices | `react-best-practices/` | Modern React development guidelines covering hooks, component patterns, state management, performance optimization, and TypeScript integration. |
|
|
@@ -118,7 +119,7 @@
|
|
|
118
119
|
|-------|------|-------------|
|
|
119
120
|
| Draw Io | `draw-io/` | draw.io diagram creation, editing, and review. Use for .drawio XML editing, PNG conversion, layout adjustment, and AWS icon usage. |
|
|
120
121
|
| Mermaid Diagrams | `mermaid-diagrams/` | Software diagrams in Mermaid: class, sequence, flowchart, ERD, C4, state, git graph, gantt. Use for domain models, API flows, processes and database schemas. |
|
|
121
|
-
| Particle Art | `particle-art/` |
|
|
122
|
+
| Particle Art | `particle-art/` | >- |
|
|
122
123
|
|
|
123
124
|
### Testing
|
|
124
125
|
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
|
|
134
135
|
| Skill | Path | When to Use |
|
|
135
136
|
|-------|------|-------------|
|
|
136
|
-
| Advisory Hub | `advisory-hub/` |
|
|
137
|
+
| Advisory Hub | `advisory-hub/` | >- |
|
|
137
138
|
| Best Practices | `best-practices/` | >- |
|
|
138
139
|
| Brainstorming | `brainstorming/` | You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation. |
|
|
139
140
|
| Context Curator | `context-curator/` | Compress and prioritize codebase context before handing work to reasoning agents, minimizing token waste while preserving the required decision inputs. |
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: adopt-app
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
Bring an app that was never scaffolded (POC, server folder, checkout with no remote) up to standard:
|
|
5
|
+
inventory without running it, pass/gap/question verdicts, ordered plan. Use when the user says "this
|
|
6
|
+
app was never set up properly", "adopt this POC", "onboard this app", "it just runs from a folder on
|
|
7
|
+
the server", "what would it take to get this into CI", "nobody knows how this is deployed", or "it
|
|
8
|
+
started as a prototype and now it is production".
|
|
4
9
|
---
|
|
5
10
|
|
|
6
11
|
# adopt-app
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: advisory-hub
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
A long-lived "Hub" session re-derives each phase of a multi-session plan instead of trusting its
|
|
5
|
+
self-report. Use when the user says "advisory hub", "validate this phase", "phase report", "hub
|
|
6
|
+
verdict", "who checks the implementing session", "re-derive don't trust", "hand off the hub", or a
|
|
7
|
+
long plan with expensive-to-reverse work (migration, security change, production data correctness)
|
|
8
|
+
needs independent checks; not for short features. Hub artefacts go in
|
|
9
|
+
`.caddis/advisory-hub-reports/`.
|
|
4
10
|
---
|
|
5
11
|
|
|
6
12
|
# Advisory Hub — cross-session phase validation
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# flow-studio (engine source)
|
|
2
|
+
|
|
3
|
+
Stage S1a of the `flow-studio` skill: a model file (JSON) describes a flow, lifecycle or journey, and one
|
|
4
|
+
engine draws it as a self-contained HTML page with pan and zoom, lanes, linked nodes, a slide-in
|
|
5
|
+
inspector, motion that carries state, and numbered implementation notes. The design is in
|
|
6
|
+
`.caddis/plans/flow-studio-skill-design.md`; the plan for this stage is `.caddis/plans/flow-studio-s1a-plan.md`.
|
|
7
|
+
There is no `SKILL.md` yet: the playbook arrives in stage S2.
|
|
8
|
+
|
|
9
|
+
## Use it
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# from this folder (Node 22.6 or newer)
|
|
13
|
+
node --experimental-strip-types scripts/validate.mjs examples/lineage.json
|
|
14
|
+
node --experimental-strip-types scripts/build.mjs examples/lineage.json out/lineage.html
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open `out/lineage.html`. It has no network access and no libraries.
|
|
18
|
+
|
|
19
|
+
`build.mjs` embeds the model's own `notes` plus any extra notes from `--notes notes.json` (the model's
|
|
20
|
+
notes come first, then the file's). Note anchors are CSS selectors for parts of the page, for example
|
|
21
|
+
`#viewport`.
|
|
22
|
+
|
|
23
|
+
## Check a page
|
|
24
|
+
|
|
25
|
+
The harness drives a real browser. Run it from a folder whose `node_modules` has `playwright`
|
|
26
|
+
(it is looked up from the current folder, not from this one):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node /path/to/flow-studio/scripts/check.mjs out/lineage.html --out shots --hostile out/hostile.html --channel msedge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
It checks structure (no overlap, links touch their nodes, text stays inside cards, readable first view),
|
|
33
|
+
interaction (zoom, pan, select, what-if, view switch), accessibility (names, focus, contrast in both themes,
|
|
34
|
+
reduced motion), safety (no network, hostile text stays inert) and performance (200 nodes). Exit code 0 means
|
|
35
|
+
every check passed.
|
|
36
|
+
|
|
37
|
+
`scripts/lib/check-selftest.test.mjs` feeds the harness pages with one deliberate defect each and fails
|
|
38
|
+
if a defect is not caught. It is skipped when Playwright cannot be resolved, so run it from a folder that has
|
|
39
|
+
it: `FLOW_STUDIO_CHANNEL=msedge node --test /path/to/flow-studio/scripts/lib/check-selftest.test.mjs`.
|
|
40
|
+
|
|
41
|
+
## Develop it
|
|
42
|
+
|
|
43
|
+
- The source of truth is `src/*.ts`. The pure modules (`model`, `viewport`, `routing`, `trace`, `layout`,
|
|
44
|
+
`rules`, `stateCss`) have unit tests. The DOM modules (`render`, `controls`, `interact`, `inspector`,
|
|
45
|
+
`chrome`, `notes`, `engine`) are tested through the check harness.
|
|
46
|
+
- `npm test` runs the unit tests (`node --experimental-strip-types --test`).
|
|
47
|
+
- `templates/engine.html` is generated and committed. After you change `src/` or `templates/engine.shell.html`,
|
|
48
|
+
run `node --experimental-strip-types scripts/build-engine.mjs`. A test fails when the committed file is stale.
|
|
49
|
+
- `build-engine.mjs` needs `esbuild`. It looks in this folder, then in the caddis `cli/` folder, then in the
|
|
50
|
+
current folder. In a caddis checkout, `cli/node_modules` already has it. Elsewhere, run `npm i -D esbuild`. The stale-engine check compares bytes, so use the same esbuild version as the checkout that committed the file (the caddis `cli/` one).
|
|
51
|
+
- Type check with `tsc -p tsconfig.json` (TypeScript from `cli/node_modules` or your own install).
|
|
52
|
+
- Examples use fictional data only: this repository is public.
|
|
53
|
+
- Delete the `out/` folder before exporting bundles. It is ignored by git but not by a file copy.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-studio
|
|
3
|
+
description: Design an interactive flow, lifecycle or journey page (nodes, lanes, links, motion, inspector, notes) from a JSON model as a self-contained demo, then build it in an app. Use for lineage, pipelines, ticket or agent journeys.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# flow-studio
|
|
7
|
+
|
|
8
|
+
Turns a one-paragraph brief into an explorable page: stages across, lanes down, linked nodes, pan and zoom,
|
|
9
|
+
a slide-in inspector, motion that carries meaning, and numbered implementation notes. One JSON **model**
|
|
10
|
+
describes the scenario. One engine draws it. A check script proves it is sound.
|
|
11
|
+
|
|
12
|
+
Use it for: data lineage and dependency impact, a ticket or case lifecycle, an agent or customer journey, a CI/CD
|
|
13
|
+
or release pipeline, an approval flow, an incident timeline, a service map. See `references/patterns.md`.
|
|
14
|
+
Not for: a static screen or UI variants (use `mockup` or `sketch`), a chart, a graph with more than 300 nodes.
|
|
15
|
+
|
|
16
|
+
## What works today (version 0.1)
|
|
17
|
+
|
|
18
|
+
Left-to-right flows in **columns** (stages) and **lanes** (apps, systems or actors): states with their own look
|
|
19
|
+
and motion, portal chips for shared nodes, wide placeholders for what is not recorded, two or more views
|
|
20
|
+
(for example Today and Complete), search, a what-if action (a red ripple downstream), a table view, and notes.
|
|
21
|
+
|
|
22
|
+
**Not yet** (planned, do not promise them): links that go back to an earlier stage (loops), gateway diamonds,
|
|
23
|
+
automatic placement, token playback along a path, live data updates, free x and y placement. If the scenario
|
|
24
|
+
needs a loop, show the return as its own node in a later column (for example "Reopened") and say so.
|
|
25
|
+
|
|
26
|
+
## Process: demo mode
|
|
27
|
+
|
|
28
|
+
1. **Ask at most three questions**: what is the page for, who reads it, and what are the nouns (the stages, the
|
|
29
|
+
lanes, the states). If the brief already says, ask none.
|
|
30
|
+
2. **Map the scenario** to the model (`references/patterns.md` has a recipe per scenario):
|
|
31
|
+
columns = stages left to right, lanes = who or what owns the row, states = the vocabulary (for example
|
|
32
|
+
new, open, waiting, breached, resolved), nodes = the things, links = the flow.
|
|
33
|
+
3. **Write `model.json`.** Fields are in `model.schema.json`; `examples/lineage.json` is a complete model.
|
|
34
|
+
4. **Validate:** `node --experimental-strip-types scripts/validate.mjs model.json`. Fix every error. Read every
|
|
35
|
+
warning.
|
|
36
|
+
5. **Build:** `node --experimental-strip-types scripts/build.mjs model.json out/page.html`. It is one file with
|
|
37
|
+
no network use.
|
|
38
|
+
6. **Check:** from a folder that has `playwright`:
|
|
39
|
+
`node <skill folder>/scripts/check.mjs out/page.html --out shots [--channel msedge]`.
|
|
40
|
+
Every check must pass. Open the screenshots and look at them: the checks do not judge taste.
|
|
41
|
+
7. **Show** the page and the screenshots. Do not call it done until the checks pass and you have looked.
|
|
42
|
+
|
|
43
|
+
Paths above are relative to the folder that holds this file. Node 22.6 or newer is needed.
|
|
44
|
+
|
|
45
|
+
## Design rules (the engine and the checks enforce most of them)
|
|
46
|
+
|
|
47
|
+
1. **No orphan.** Every node sits in a lane and has a link. A missing neighbour is a dotted placeholder that says
|
|
48
|
+
what is missing. Never guess a link.
|
|
49
|
+
2. **The first view is readable**: fit to width at zoom 0.6 or more (up to 60 groups). Key `0` shows everything.
|
|
50
|
+
3. **Read left to right**, with pinned column headers and a phase ribbon.
|
|
51
|
+
4. **Motion carries state**: links flow by state, gaps breathe, the selected path draws in, a what-if ripples.
|
|
52
|
+
No decorative loops, no sweeping scan line, no bounce.
|
|
53
|
+
5. **No permanent side panel.** The inspector slides in on a click and closes with Esc.
|
|
54
|
+
6. **Standard controls**: select, hand, zoom in and out, fit, 100 percent, zoom to selection, fullscreen, keys.
|
|
55
|
+
7. **Every state has a word as well as a colour.** Contrast at least 4.5 in light and dark.
|
|
56
|
+
8. **Titles stay short** (one line at the column width). Long text goes in `subtitle` or `members`.
|
|
57
|
+
9. **Fixed node sizes per kind.** The engine never measures text, so layout does not depend on fonts.
|
|
58
|
+
10. **Reduced motion** removes all motion and keeps the glow.
|
|
59
|
+
|
|
60
|
+
## Model in one screen
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
version 1, meta {title, help, asOf, source, labels?}, layout "columns-lanes"
|
|
64
|
+
columns [{id, title, short?, width, phase?}] stages, left to right
|
|
65
|
+
lanes [{id, title, badge?, badgeTone?, note?, gap?}] rows; gap true = dashed amber "no document" lane
|
|
66
|
+
states {id: {word, tone, border, edge, motion, bucket}} the vocabulary; bucket good|neutral|gap
|
|
67
|
+
nodes [{id, kind, lane, col, colSpan?, row, state, title, subtitle?, members?, weight?, ref?, upgrade?,
|
|
68
|
+
stateIn?, visibleIn?}] kind: card group chip placeholder (others draw as cards)
|
|
69
|
+
links [{id, from, to, state, label?, stateIn?, visibleIn?}] from must be in an earlier column than to
|
|
70
|
+
views [{id, label, default?}] the first view is the baseline
|
|
71
|
+
inspector {relations [3 of {id,label,dir,filter?}], actions [{id,label,dir,staleText}]}
|
|
72
|
+
notes [{anchor, title, text}] anchor is a CSS selector for a part of the page
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Rows are numbers (1 row = 62 px). Keep nodes in one column at least 1 row apart. A lane needs about 1.75 rows of
|
|
76
|
+
space between single-row lanes. `validate.mjs` checks all of this and names the node or link at fault.
|
|
77
|
+
|
|
78
|
+
## Build mode: implement it in a real app
|
|
79
|
+
|
|
80
|
+
Use the demo page as the visual contract and `model.schema.json` as the data contract.
|
|
81
|
+
|
|
82
|
+
1. Copy the pure modules from `src/` into the app: `model.ts`, `layout.ts`, `viewport.ts`, `routing.ts`,
|
|
83
|
+
`trace.ts`, `rules.ts`, `stateCss.ts`, and their `*.test.ts` files. They have no DOM and no dependencies.
|
|
84
|
+
2. Work test first: run the copied tests, then write a failing test for each change.
|
|
85
|
+
3. Write the components in the app's own framework and design system (the demo's DOM and CSS are in
|
|
86
|
+
`src/render.ts`, `src/controls.ts`, `src/inspector.ts`, `src/chrome.ts`, `src/styles.css`). Feed them from the
|
|
87
|
+
app's real data through the app's hook and service layer. Make node and link ids stable (use business keys, not
|
|
88
|
+
array positions).
|
|
89
|
+
4. Keep typed loading, empty and error states. A failed refresh keeps the last good graph. Never draw a missing
|
|
90
|
+
value as zero.
|
|
91
|
+
5. Run `scripts/check.mjs` against the running app page for the structural, interaction and accessibility checks
|
|
92
|
+
(the page must expose the same element ids, or copy the harness and adapt it).
|
|
93
|
+
|
|
94
|
+
A packaged React kit and live-update API are planned; until then, build mode is "copy the pure modules, port the
|
|
95
|
+
DOM modules".
|
|
96
|
+
|
|
97
|
+
## Gotchas
|
|
98
|
+
|
|
99
|
+
- Examples and any committed data must be invented: a public repo must not carry real host, job or repo names.
|
|
100
|
+
- A model string is data, never markup. The engine renders text safely; keep it that way in any port.
|
|
101
|
+
- The caddis pool budgets all skill descriptions together. Keep this one short.
|
|
102
|
+
- `templates/engine.html` is generated. After changing `src/`, run `node --experimental-strip-types
|
|
103
|
+
scripts/build-engine.mjs` (it needs `esbuild`; see `README.md`).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Model files only. Build one with `node --experimental-strip-types scripts/build.mjs examples/<name>.json out/<name>.html`.
|
|
4
|
+
|
|
5
|
+
| File | What it shows |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `lineage.json` | A fictional data estate ("Northwind Analytics"): nine columns from source system to page, eight lanes (documented, infrastructure, gaps), four evidence states, two views (Today and Complete), portal chips, wide placeholders, the failure what-if. The proof that the generic engine reproduces a hand-built lineage page. |
|
|
8
|
+
| `hostile.json` | The same model with markup and script text in every free-text field (`</script>`, an image with an `onerror`, quotes, U+2028 and U+2029). The check harness loads it to prove nothing executes. Do not use it as a template. |
|
|
9
|
+
|
|
10
|
+
All data is fictional. The names are invented, and no real host, job, table or repository appears in them.
|
|
11
|
+
`lineage.json` and `hostile.json` are generated by a script that is not part of the skill; edit the JSON directly.
|