648fea8dcb
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
251 lines
9.6 KiB
Python
251 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
expand-ts-module-docs.py — append rich TSDoc blocks to TypeScript modules (comments only).
|
|
|
|
Used to deepen in-file documentation for client, MCP, and desktop packages without
|
|
changing runtime behavior. Idempotent: skips files that already contain MODULE_GUIDE.
|
|
|
|
@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
AUTHOR = "@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>"
|
|
MARKER = "MODULE_GUIDE"
|
|
|
|
EXPORT_RE = re.compile(
|
|
r"^export\s+(?:async\s+)?(?:function|const|class|type|interface|enum)\s+(\w+)",
|
|
re.MULTILINE,
|
|
)
|
|
IMPORT_RE = re.compile(
|
|
r"""^import\s+(?:type\s+)?(?:\{[^}]+\}|\w+)\s+from\s+['"]([^'"]+)['"]""",
|
|
re.MULTILINE,
|
|
)
|
|
|
|
|
|
def topic_blurb(path: Path) -> str:
|
|
rel = path.as_posix()
|
|
name = path.stem
|
|
hints: list[str] = []
|
|
|
|
if "RemoteSources" in name or "remote" in rel.lower():
|
|
hints.append(
|
|
"Supports federated dashboards: register SSH-backed or file-synced remote "
|
|
"machines, health-check tunnels, and scope the entire UI to local vs all vs "
|
|
"selected sources."
|
|
)
|
|
if "prometheus" in rel.lower() or "metrics" in rel.lower():
|
|
hints.append(
|
|
"Relates to the `/api/metrics` Prometheus exposition endpoint and the optional "
|
|
"native/Docker Grafana stack under `monitoring/`."
|
|
)
|
|
if "ssh" in rel.lower():
|
|
hints.append(
|
|
"Covers SSH key management, jump-host tunnels, and secure remote ingestion "
|
|
"paths used by Remote Data Sources."
|
|
)
|
|
if path.parts[0:2] == ("client", "src") and path.parts[2:3] == ("pages",):
|
|
hints.append(
|
|
"Route-level screen mounted by `App.tsx`; fetches scoped REST data, subscribes "
|
|
"to WebSocket deltas via `eventBus`, and renders inside `Layout`."
|
|
)
|
|
if path.parts[0:2] == ("mcp", "src"):
|
|
hints.append(
|
|
"Part of the local MCP server (`npm run mcp:start`) that exposes dashboard "
|
|
"operations as MCP tools for Claude Code and other hosts."
|
|
)
|
|
if path.parts[0:2] == ("desktop", "src"):
|
|
hints.append(
|
|
"Electron main/preload process code for the packaged desktop app — embeds the "
|
|
"Express server, manages tray/window lifecycle, and writes discovery metadata."
|
|
)
|
|
if "workflow" in rel.lower():
|
|
hints.append(
|
|
"Workflow analytics visualization built on D3; consumes aggregated session/run "
|
|
"metrics from the workflows API."
|
|
)
|
|
if "conversation" in rel.lower():
|
|
hints.append(
|
|
"Renders Claude transcript rows (user, assistant, tool calls) inside Session "
|
|
"Detail with markdown, syntax highlighting, and TUI-style segments."
|
|
)
|
|
if "Tabby" in rel:
|
|
hints.append(
|
|
"Tabby is the optional on-screen cat assistant — quips, intents, and lightweight "
|
|
"event reactions layered above the dashboard chrome."
|
|
)
|
|
if "hook" in rel.lower() or name.startswith("use"):
|
|
hints.append(
|
|
"React hook: isolates side effects and subscription wiring so presentational "
|
|
"components stay declarative."
|
|
)
|
|
if rel.endswith("lib/api.ts"):
|
|
hints.append(
|
|
"Central typed HTTP client for every REST route; attaches auth token, data-scope "
|
|
"`sources` query params, and normalizes error payloads."
|
|
)
|
|
if rel.endswith("lib/types.ts"):
|
|
hints.append(
|
|
"Shared wire-format types for REST + WebSocket messages — keep in sync with "
|
|
"`server/` serializers and OpenAPI."
|
|
)
|
|
if rel.endswith("eventBus.ts"):
|
|
hints.append(
|
|
"In-memory pub/sub bus bridging `useWebSocket` to any page without prop drilling."
|
|
)
|
|
if not hints:
|
|
hints.append(
|
|
"Dashboard module consumed by the React client, MCP tools, or desktop shell "
|
|
"depending on deployment mode."
|
|
)
|
|
return " ".join(hints)
|
|
|
|
|
|
def list_exports(source: str) -> list[str]:
|
|
return EXPORT_RE.findall(source)
|
|
|
|
|
|
def list_imports(source: str) -> list[str]:
|
|
seen: list[str] = []
|
|
for m in IMPORT_RE.finditer(source):
|
|
mod = m.group(1)
|
|
if mod.startswith(".") and mod not in seen:
|
|
seen.append(mod)
|
|
return seen[:12]
|
|
|
|
|
|
def build_guide(path: Path, source: str) -> str:
|
|
exports = list_exports(source)
|
|
imports = list_imports(source)
|
|
rel = path.as_posix()
|
|
blurb = topic_blurb(path)
|
|
|
|
lines = [
|
|
"",
|
|
"/* =============================================================================",
|
|
f" * {MARKER} — extended in-file reference (comments only; safe to read, never executed)",
|
|
" * =============================================================================",
|
|
f" * **Path:** `{rel}`",
|
|
f" * **Purpose:** {blurb}",
|
|
" *",
|
|
" * ## Design constraints",
|
|
" * - Local-first: no telemetry leaves the machine unless the user configures webhooks.",
|
|
" * - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that",
|
|
" * philosophy by degrading gracefully (empty states, stale badges, reconnect loops).",
|
|
" * - Destructive flows stay behind explicit confirmation modals and server-side gates.",
|
|
" * - Internationalization: user-visible strings belong in i18n JSON, not literals here.",
|
|
" *",
|
|
" * ## Remote data & SSH",
|
|
" * Remote Data Sources let operators aggregate multiple machines. SSH entries describe",
|
|
" * how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every",
|
|
" * scoped GET via `?sources=`. Health checks and import history surface in Settings.",
|
|
" *",
|
|
" * ## Observability",
|
|
" * Prometheus scrapes `GET /api/metrics` (see `monitoring/`). Grafana ships four",
|
|
" * provisioned boards (overview, sessions, tools, alerts). Native npm scripts and",
|
|
" * Docker Compose profiles are documented in `monitoring/README.md`.",
|
|
" *",
|
|
]
|
|
|
|
if imports:
|
|
lines.append(" * ## Internal dependencies")
|
|
for imp in imports:
|
|
lines.append(f" * - `{imp}`")
|
|
lines.append(" *")
|
|
|
|
if exports:
|
|
lines.append(" * ## Public surface")
|
|
for name in exports[:40]:
|
|
lines.append(f" * - `{name}` — exported API; see TSDoc on the symbol for behavior.")
|
|
if len(exports) > 40:
|
|
lines.append(f" * - … plus {len(exports) - 40} additional exports")
|
|
lines.append(" *")
|
|
|
|
lines.extend(
|
|
[
|
|
" * ## Testing pointers",
|
|
" * - Prefer colocated `__tests__` with Vitest + Testing Library for UI.",
|
|
" * - Server contract changes require `npm run test:server` and OpenAPI sync.",
|
|
" * - MCP edits: `npm run mcp:typecheck` and `npm run mcp:build`.",
|
|
" *",
|
|
" * ## Related docs",
|
|
" * - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.",
|
|
" * - `docs/API.md` — REST reference.",
|
|
" * - `.claude/skills/file-headers/` — mandatory `@author` header policy.",
|
|
" * ============================================================================= */",
|
|
]
|
|
)
|
|
|
|
block = "\n".join(lines)
|
|
|
|
if exports:
|
|
catalog = [
|
|
"",
|
|
"/* -----------------------------------------------------------------------------",
|
|
" * EXPORT CATALOG — quick index of symbols defined below (documentation only).",
|
|
" * -----------------------------------------------------------------------------",
|
|
]
|
|
for name in exports:
|
|
catalog.extend(
|
|
[
|
|
f" * **{name}**",
|
|
" * Part of this module's public contract. Downstream imports should treat",
|
|
" * the signature and return type as stable unless release notes say otherwise.",
|
|
" * When behavior changes, update the `@file` overview and relevant tests.",
|
|
" *",
|
|
]
|
|
)
|
|
catalog.append(" * ----------------------------------------------------------------------------- */")
|
|
block += "\n".join(catalog) + "\n"
|
|
|
|
return block
|
|
|
|
|
|
def insert_guide(path: Path) -> bool:
|
|
source = path.read_text(encoding="utf-8")
|
|
if MARKER in source:
|
|
return False
|
|
if AUTHOR not in source:
|
|
print(f"SKIP (no author header): {path}")
|
|
return False
|
|
|
|
m = re.search(r"/\*\*[\s\S]*?\*/", source)
|
|
if not m:
|
|
print(f"SKIP (no file header block): {path}")
|
|
return False
|
|
|
|
guide = build_guide(path, source)
|
|
updated = source[: m.end()] + guide + source[m.end() :]
|
|
path.write_text(updated, encoding="utf-8")
|
|
return True
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
if len(argv) < 2:
|
|
print("Usage: expand-ts-module-docs.py <glob-root> [...]", file=sys.stderr)
|
|
return 1
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
changed = 0
|
|
for arg in argv[1:]:
|
|
for path in sorted(root.glob(arg)):
|
|
if not path.is_file():
|
|
continue
|
|
if path.suffix not in {".ts", ".tsx"}:
|
|
continue
|
|
if "__snapshots__" in path.parts or "__tests__" in path.parts:
|
|
continue
|
|
if insert_guide(path):
|
|
print(f"expanded: {path.relative_to(root)}")
|
|
changed += 1
|
|
print(f"Done — {changed} file(s) updated.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv))
|