feat: Claude Code Monitor — lanes, pipelines and a merged workspace
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.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: backend-reviewer
|
||||
description: Review backend route and hook logic for regressions, data integrity risks, and missing tests.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are a backend reviewer for this repository.
|
||||
|
||||
Focus on:
|
||||
- Hook event lifecycle correctness.
|
||||
- Session/agent state-machine regressions.
|
||||
- API contract compatibility.
|
||||
- Transaction and persistence correctness.
|
||||
- Missing or weak verification coverage.
|
||||
|
||||
Output:
|
||||
- Prioritized findings.
|
||||
- File references.
|
||||
- Reproduction or validation notes.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: frontend-reviewer
|
||||
description: Review React UI changes for behavior regressions, state consistency, and UX breakage.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a frontend reviewer for this repository.
|
||||
|
||||
Focus on:
|
||||
- Routing and navigation consistency.
|
||||
- State updates from websocket and API responses.
|
||||
- Loading/empty/error state correctness.
|
||||
- Breaking visual or interaction regressions.
|
||||
- Missing tests for changed UI behavior.
|
||||
|
||||
Output:
|
||||
- Prioritized findings.
|
||||
- File references.
|
||||
- Suggested verification steps.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: mcp-reviewer
|
||||
description: Review MCP server changes for tool safety, schema quality, and host integration correctness.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an MCP-focused reviewer for this repository.
|
||||
|
||||
Focus on:
|
||||
- Tool naming and schema strictness.
|
||||
- Safety gate enforcement for mutating/destructive operations.
|
||||
- API client timeout/retry/error handling.
|
||||
- Stdio protocol safety (stderr-only logs).
|
||||
- Host configuration and runbook documentation accuracy.
|
||||
|
||||
Output:
|
||||
- Prioritized findings.
|
||||
- File references.
|
||||
- Verification commands to run.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
paths:
|
||||
- "server/**/*.js"
|
||||
- "scripts/**/*.js"
|
||||
---
|
||||
|
||||
# Backend and Hook Rules
|
||||
|
||||
- Keep API responses backward-compatible unless a breaking change is explicitly requested.
|
||||
- Maintain deterministic, non-blocking hook ingestion behavior.
|
||||
- Preserve transaction boundaries and data integrity in event processing logic.
|
||||
- For route changes, validate input thoroughly and return structured errors.
|
||||
- Prefer prepared-statement usage patterns already established in `server/db.js`.
|
||||
- If touching status transitions, verify session and agent lifecycle state machines still make sense.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.md"
|
||||
---
|
||||
|
||||
# Documentation Rules
|
||||
|
||||
- Keep command examples executable and aligned with actual scripts.
|
||||
- Use absolute or clearly rooted paths when discussing project files.
|
||||
- When adding architecture claims, reflect current code behavior.
|
||||
- Update all affected docs together (`README`, `ARCHITECTURE`, `SETUP`, `INSTALL`, `mcp/README`) when workflows change.
|
||||
- Prefer concise sections and concrete troubleshooting steps.
|
||||
@@ -0,0 +1,6 @@
|
||||
# File Header Rules (binding for every coding agent)
|
||||
|
||||
- Every applicable source file (`.js/.ts/.tsx/.cjs/.mjs/.py/.sh/.css` — excluding `node_modules/`, `dist/`, `data/`, minified/vendored, snapshots) MUST start with the header comment: a truthful file overview plus the exact line `@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>`.
|
||||
- Creating a new applicable file → write the header before any code (after the shebang in scripts).
|
||||
- Editing a file that lacks the header → add it in the same change; if the edit changes the file's purpose, update the overview.
|
||||
- Formats and the repo-wide audit script live in `.claude/skills/file-headers/` (`bash .claude/skills/file-headers/scripts/check-headers.sh` must exit 0).
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
paths:
|
||||
- "client/src/**/*.{ts,tsx,css}"
|
||||
- "client/public/**/*.tsx"
|
||||
---
|
||||
|
||||
# Frontend Rules
|
||||
|
||||
- Preserve existing UI information hierarchy unless redesign is requested.
|
||||
- Keep component props and API typing explicit; avoid implicit `any`.
|
||||
- Match existing page/component patterns for loading, empty, and error states.
|
||||
- When adding UI behavior, ensure it degrades safely if websocket updates are delayed.
|
||||
- Keep routes consistent with current navigation model.
|
||||
- Prefer focused UI diffs over broad stylistic rewrites.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
paths:
|
||||
- "mcp/src/**/*.ts"
|
||||
- "mcp/package.json"
|
||||
- "mcp/tsconfig.json"
|
||||
- "mcp/.env.example"
|
||||
- "mcp/README.md"
|
||||
---
|
||||
|
||||
# MCP Server Rules
|
||||
|
||||
- Keep MCP tool names stable and descriptive.
|
||||
- Keep destructive operations behind explicit guardrails.
|
||||
- Route all logs to stderr only; never write protocol logs to stdout.
|
||||
- Keep tool input schemas strict and bounded.
|
||||
- Preserve loopback-only API target enforcement unless security posture changes by request.
|
||||
- Verify MCP with `npm run mcp:typecheck` and `npm run mcp:build` after code edits.
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run:*)",
|
||||
"Bash(cd client:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git:*)",
|
||||
"Bash(node --test server/lib/__tests__/transcript-cache.test.js)",
|
||||
"Bash(node -e \"const r = require\\(''''./server/routes/hooks''''\\); console.log\\(''''router type:'''', typeof r\\); console.log\\(''''transcriptCache exists:'''', !!r.transcriptCache\\); console.log\\(''''transcriptCache has extract:'''', typeof r.transcriptCache?.extract\\);\")",
|
||||
"Bash(node -e \"const r = require\\(''''./server/routes/hooks''''\\); console.log\\(''''router type:'''', typeof r\\); console.log\\(''''transcriptCache exists:'''', Boolean\\(r.transcriptCache\\)\\); console.log\\(''''transcriptCache has extract:'''', typeof \\(r.transcriptCache && r.transcriptCache.extract\\)\\);\")",
|
||||
"Bash(node -e \"const { transcriptCache } = require\\(''./server/routes/hooks''\\); console.log\\(''transcriptCache exists:'', Boolean\\(transcriptCache\\)\\); console.log\\(''has extract:'', typeof \\(transcriptCache && transcriptCache.extract\\)\\); console.log\\(''has extractCompactions:'', typeof \\(transcriptCache && transcriptCache.extractCompactions\\)\\); console.log\\(''has invalidate:'', typeof \\(transcriptCache && transcriptCache.invalidate\\)\\); console.log\\(''has stats:'', typeof \\(transcriptCache && transcriptCache.stats\\)\\);\")",
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(npx --prefix client tsc --noEmit -p client/tsconfig.json)",
|
||||
"Bash(node -e \":*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(grep -E \"\\\\.js$\")",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(./node_modules/.bin/tsc --noEmit)",
|
||||
"Bash(npx vite:*)",
|
||||
"Bash(node -e \"const p = require\\(''./package.json''\\); console.log\\(''deps:'', Object.keys\\(p.dependencies || {}\\).length\\); console.log\\(Object.keys\\(p.dependencies || {}\\).join\\('', ''\\)\\)\")",
|
||||
"Bash(npx prettier:*)",
|
||||
"Bash(node scripts/seed.js)",
|
||||
"Bash(lsof -ti:4820)",
|
||||
"Bash(xargs kill:*)",
|
||||
"Bash(lsof -ti:5173)",
|
||||
"Bash(lsof -ti:5174)",
|
||||
"Bash(curl -s http://localhost:4820/api/health)",
|
||||
"Bash(node -e \"require\\('./server/lib/transcript-cache'\\); console.log\\('transcript-cache OK'\\)\")",
|
||||
"Bash(python3:*)",
|
||||
"Bash(node -c scripts/import-history.js)",
|
||||
"Bash(node -e \"const m = require\\('./scripts/import-history'\\); console.log\\(Object.keys\\(m\\).sort\\(\\).join\\(', '\\)\\)\")",
|
||||
"Bash(node -e \"const TC = require\\('./server/lib/transcript-cache'\\); const tc = new TC\\(\\); console.log\\('transcript-cache loaded OK, methods:', Object.getOwnPropertyNames\\(TC.prototype\\).filter\\(n => n !== 'constructor'\\).join\\(', '\\)\\)\")",
|
||||
"Bash(node -e \"try { require\\('./server/routes/hooks'\\); } catch\\(e\\) { console.log\\('Expected error \\(db not available in isolation\\):', e.message.slice\\(0, 100\\)\\); }\")",
|
||||
"Bash(node --check server/routes/hooks.js)",
|
||||
"Bash(node --check server/lib/transcript-cache.js)",
|
||||
"Bash(node -e ':*)",
|
||||
"Bash(node -e \"require\\('./server/lib/transcript-cache'\\); require\\('./scripts/import-history'\\); console.log\\('All modules load OK'\\)\")",
|
||||
"Bash(claude --version)",
|
||||
"Bash(gh pr *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: debug-live-issue
|
||||
description: Debug production-like issues in this repository with disciplined evidence gathering. Use when fixing failing workflows, regressions, flaky behavior, or data inconsistencies across hooks, API, DB, websocket, and UI.
|
||||
---
|
||||
|
||||
# Debug Live Issue
|
||||
|
||||
Use this workflow for incident-style debugging.
|
||||
|
||||
## Steps
|
||||
- Capture symptom, expected behavior, and reproducible path.
|
||||
- Isolate subsystem first: hook ingestion, API route, DB state, websocket, or UI rendering.
|
||||
- Reproduce with minimal surface area.
|
||||
- Prove root cause before changing code.
|
||||
- Apply minimal fix and re-verify.
|
||||
|
||||
## Evidence standards
|
||||
- Prefer direct logs, API responses, DB state checks, and deterministic repro steps.
|
||||
- Avoid speculative fixes without root-cause evidence.
|
||||
- If not fully reproducible, state uncertainty and strongest hypothesis.
|
||||
|
||||
## References
|
||||
- Investigation template: `references/investigation-template.md`
|
||||
@@ -0,0 +1,32 @@
|
||||
# Investigation Template
|
||||
|
||||
## Problem statement
|
||||
- Symptom:
|
||||
- Expected behavior:
|
||||
- First observed:
|
||||
- Scope:
|
||||
|
||||
## Reproduction
|
||||
- Preconditions:
|
||||
- Exact steps:
|
||||
- Actual result:
|
||||
|
||||
## Evidence
|
||||
- Logs:
|
||||
- API responses:
|
||||
- Database observations:
|
||||
- Websocket behavior:
|
||||
- UI behavior:
|
||||
|
||||
## Root cause
|
||||
- Confirmed cause:
|
||||
- Why it happens:
|
||||
|
||||
## Fix
|
||||
- Change summary:
|
||||
- Why this fix is minimal and safe:
|
||||
|
||||
## Verification
|
||||
- Commands/tests run:
|
||||
- Manual verification:
|
||||
- Residual risk:
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: file-headers
|
||||
description: MANDATORY for every coding agent (Claude Code, Codex, or any other) on every change-set — every applicable source file the agent creates or updates MUST start with the project's copyright/authorship header (file overview + exact author line). Use automatically whenever writing a new file or editing an existing one; do not wait to be asked. Covers JS/TS/TSX/CJS/MJS, Python, shell, and CSS. Includes the audit script to verify repo-wide compliance.
|
||||
---
|
||||
|
||||
# File Headers — Copyright Comment + File Overview
|
||||
|
||||
Every applicable source file in this repository starts with a header comment
|
||||
containing a **file overview** and the **exact author line**:
|
||||
|
||||
```
|
||||
@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
```
|
||||
|
||||
The name and email must be exactly as above — no variations, no substitutions,
|
||||
no other names. This applies to **every coding agent** working in this repo
|
||||
(Claude Code, Codex, or any other tool): when you **create** a new applicable
|
||||
file, write the header first; when you **update** an existing applicable file
|
||||
that is missing the header, add it as part of the same change.
|
||||
|
||||
## Applicable files
|
||||
|
||||
| Included | Excluded |
|
||||
| -------- | -------- |
|
||||
| `*.js`, `*.ts`, `*.tsx`, `*.cjs`, `*.mjs` | anything under `node_modules/`, `dist/`, `build/`, `data/` |
|
||||
| `*.py`, `*.sh` | vendored/minified files (`*.min.js`) |
|
||||
| `*.css` | generated files (they carry their own AUTO-GENERATED banner) |
|
||||
| | snapshots (`__snapshots__/`), lockfiles, JSON/YAML/Markdown |
|
||||
|
||||
## Header formats by file type
|
||||
|
||||
**JS / TS / TSX — server & scripts style** (overview inline in `@file`):
|
||||
|
||||
```js
|
||||
/**
|
||||
* @file One-to-few-sentence overview of what this file does and why it
|
||||
* exists. Mention the key contracts or invariants the file owns.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
```
|
||||
|
||||
**JS / TS / TSX — client style** (`@file` name + `@description` overview), used
|
||||
under `client/src/`:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* @file ComponentName.tsx
|
||||
* @description What the component/module renders or provides and how it fits
|
||||
* into the app.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
```
|
||||
|
||||
**CSS** (same block-comment shape as `client/src/index.css`):
|
||||
|
||||
```css
|
||||
/**
|
||||
* @file file.css
|
||||
* @description What these styles cover.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
```
|
||||
|
||||
**Shell** (`#` block right after the shebang; existing overview comments count —
|
||||
just make sure the `@author` line is in the block):
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# script-name.sh — what the script does, one to few lines.
|
||||
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
```
|
||||
|
||||
**Python** (inside the module docstring):
|
||||
|
||||
```python
|
||||
"""
|
||||
module.py — what the module does.
|
||||
|
||||
@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
"""
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. **New file → header first.** Any applicable file you create starts with the
|
||||
header before any code (after the shebang for scripts).
|
||||
2. **Touched file missing header → add it.** If you edit a file that lacks the
|
||||
header, add one in the same commit. Write a real overview — describe what
|
||||
the file actually does; never a placeholder like "TODO" or "utility file".
|
||||
3. **Exact author line.** `@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>` —
|
||||
byte-exact, in every file type (shell and Python use it inside `#` / docstring
|
||||
comments).
|
||||
4. **Don't churn existing headers.** If a file already has a compliant header,
|
||||
leave it alone unless the file's purpose changed (then update the overview).
|
||||
5. **Overviews must stay truthful.** When an edit changes what a file does,
|
||||
update its `@file`/`@description` overview in the same change.
|
||||
|
||||
## Audit
|
||||
|
||||
Run the bundled checker to list any applicable file missing the header:
|
||||
|
||||
```bash
|
||||
bash .claude/skills/file-headers/scripts/check-headers.sh
|
||||
```
|
||||
|
||||
Exit code `0` = fully compliant; `1` = the printed files are missing headers.
|
||||
Run it before finishing any change-set that adds files, and during reviews.
|
||||
|
||||
On every pull request, GitHub Actions runs
|
||||
`.claude/skills/file-headers/scripts/check-headers-pr.sh` against only the
|
||||
files changed in the PR diff (added, copied, renamed, or modified). Test locally
|
||||
before pushing:
|
||||
|
||||
```bash
|
||||
bash .claude/skills/file-headers/scripts/check-headers-pr.sh origin/master HEAD
|
||||
```
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-headers-pr.sh — verify that applicable files touched in a git diff carry
|
||||
# the mandatory copyright/authorship header. Used locally before opening a PR and
|
||||
# by the file-headers GitHub Actions workflow on every pull request.
|
||||
#
|
||||
# Usage:
|
||||
# check-headers-pr.sh [<base-sha> <head-sha>]
|
||||
#
|
||||
# When omitted, compares the current branch against origin/master (or master).
|
||||
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
|
||||
AUTHOR_MARK="@author Nguyễn Ngọc Trí Vĩ"
|
||||
AUTHOR_EMAIL="vinnt@smartgift.vn"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: check-headers-pr.sh [<base-sha> <head-sha>]
|
||||
|
||||
Checks only added/copied/renamed/modified files in the diff between base and
|
||||
head. Applicable extensions: .js .ts .tsx .cjs .mjs .py .sh .css
|
||||
|
||||
The author line must appear in the file header using the syntax for that type:
|
||||
JS/TS/CSS — block comment (/** ... @author ... */)
|
||||
Shell — # comment after the shebang
|
||||
Python — module docstring (""" ... @author ... """)
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BASE_SHA="${1:-}"
|
||||
HEAD_SHA="${2:-}"
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
if [[ -z "$BASE_SHA" || -z "$HEAD_SHA" ]]; then
|
||||
if git show-ref --verify --quiet refs/remotes/origin/master; then
|
||||
BASE_SHA="$(git merge-base HEAD origin/master)"
|
||||
elif git show-ref --verify --quiet refs/heads/master; then
|
||||
BASE_SHA="$(git merge-base HEAD master)"
|
||||
else
|
||||
echo "error: could not resolve base ref; pass <base-sha> <head-sha>" >&2
|
||||
exit 1
|
||||
fi
|
||||
HEAD_SHA="HEAD"
|
||||
fi
|
||||
|
||||
# Return 0 when the path is subject to the header policy (keep in sync with
|
||||
# check-headers.sh exclusions).
|
||||
is_applicable_file() {
|
||||
local f="$1"
|
||||
|
||||
case "$f" in
|
||||
*/node_modules/*|*/dist/*|*/build/*|*/.git/*|*/data/*)
|
||||
return 1
|
||||
;;
|
||||
*/monitoring/.bin/*|*/monitoring/.data/*|*/__snapshots__/*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$f" in
|
||||
esac
|
||||
|
||||
case "$f" in
|
||||
*.js|*.ts|*.tsx|*.cjs|*.mjs|*.py|*.sh|*.css)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Best-effort hint for contributors when a file fails.
|
||||
header_hint_for() {
|
||||
local f="$1"
|
||||
case "$f" in
|
||||
*.py)
|
||||
echo ' expected: module docstring with @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>'
|
||||
;;
|
||||
*.sh)
|
||||
echo ' expected: # block after shebang with @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>'
|
||||
;;
|
||||
*.css)
|
||||
echo ' expected: /** @file ... @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn> */'
|
||||
;;
|
||||
*)
|
||||
echo ' expected: /** @file ... @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn> */'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Require the exact author mark anywhere in the file (same rule as check-headers.sh).
|
||||
has_author_header() {
|
||||
local f="$1"
|
||||
grep -q "$AUTHOR_MARK" "$f" && grep -q "$AUTHOR_EMAIL" "$f"
|
||||
}
|
||||
|
||||
BASE_SHORT="$(git rev-parse --short "${BASE_SHA}" 2>/dev/null || echo "${BASE_SHA}")"
|
||||
HEAD_SHORT="$(git rev-parse --short "${HEAD_SHA}" 2>/dev/null || echo "${HEAD_SHA}")"
|
||||
|
||||
checked=0
|
||||
missing=0
|
||||
skipped=0
|
||||
|
||||
echo "Checking authorship headers for files changed between ${BASE_SHORT}..${HEAD_SHORT}"
|
||||
|
||||
while IFS= read -r f; do
|
||||
[[ -z "$f" ]] && continue
|
||||
|
||||
if ! is_applicable_file "$f"; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "$f" ]]; then
|
||||
echo "SKIP (missing on disk): $f"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
checked=$((checked + 1))
|
||||
|
||||
if ! has_author_header "$f"; then
|
||||
echo "MISSING HEADER: $f"
|
||||
header_hint_for "$f"
|
||||
missing=1
|
||||
fi
|
||||
done < <(git diff --name-only --diff-filter=ACMR "${BASE_SHA}" "${HEAD_SHA}")
|
||||
|
||||
if [[ "$checked" -eq 0 ]]; then
|
||||
echo "✔ No applicable source files changed in this diff (skipped ${skipped} path(s))."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$missing" -eq 0 ]]; then
|
||||
echo "✔ All ${checked} applicable changed file(s) carry the authorship header."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Add the project header to each file listed above."
|
||||
echo "See .claude/skills/file-headers/SKILL.md for per-type examples."
|
||||
exit 1
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-headers.sh — audit the repo for applicable source files missing the
|
||||
# mandatory copyright/authorship header (see .claude/skills/file-headers).
|
||||
# Prints each non-compliant file; exits 0 when fully compliant, 1 otherwise.
|
||||
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
|
||||
AUTHOR_MARK="@author Nguyễn Ngọc Trí Vĩ"
|
||||
|
||||
missing=0
|
||||
while IFS= read -r f; do
|
||||
if ! grep -q "$AUTHOR_MARK" "$f"; then
|
||||
echo "MISSING HEADER: ${f#"$ROOT"/}"
|
||||
missing=1
|
||||
fi
|
||||
done < <(
|
||||
find "$ROOT" \
|
||||
\( -name node_modules -o -name dist -o -name build -o -name .git \
|
||||
-o -path "$ROOT/data" -o -path "$ROOT/monitoring/.bin" \
|
||||
-o -path "$ROOT/monitoring/.data" -o -name "__snapshots__" \) -prune -o \
|
||||
-type f \( -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.cjs" \
|
||||
-o -name "*.mjs" -o -name "*.py" -o -name "*.sh" -o -name "*.css" \) \
|
||||
! -name "*.min.js" -print
|
||||
)
|
||||
|
||||
if [ "$missing" -eq 0 ]; then
|
||||
echo "✔ All applicable files carry the authorship header."
|
||||
fi
|
||||
exit "$missing"
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: mcp-operations
|
||||
description: Operate and maintain the local MCP server for this project. Use when creating MCP host config, troubleshooting tool connectivity, modifying tool domains, or adjusting safety policy flags.
|
||||
---
|
||||
|
||||
# MCP Operations
|
||||
|
||||
Use this skill whenever work touches `mcp/` behavior or MCP host integration.
|
||||
|
||||
## Core workflow
|
||||
- Confirm dashboard API is running (`/api/health`).
|
||||
- Confirm MCP server build status.
|
||||
- Validate env flags for mutation/destructive modes.
|
||||
- Verify host configuration path and command.
|
||||
|
||||
## Safe operations policy
|
||||
- Default to read-only mode (`MCP_DASHBOARD_ALLOW_MUTATIONS=false`).
|
||||
- Enable mutations only for explicit admin tasks.
|
||||
- Enable destructive mode only transiently and require explicit confirmation token.
|
||||
|
||||
## Required verification for code changes
|
||||
- `npm run mcp:typecheck`
|
||||
- `npm run mcp:build`
|
||||
|
||||
## References
|
||||
- Host config examples: `references/host-config.md`
|
||||
- Operations runbook: `references/runbook.md`
|
||||
@@ -0,0 +1,18 @@
|
||||
# MCP Host Config
|
||||
|
||||
## Command
|
||||
- `node`
|
||||
|
||||
## Args
|
||||
- Absolute path to `mcp/build/index.js`
|
||||
|
||||
## Example env
|
||||
- `MCP_DASHBOARD_BASE_URL=http://127.0.0.1:4820`
|
||||
- `MCP_DASHBOARD_ALLOW_MUTATIONS=false`
|
||||
- `MCP_DASHBOARD_ALLOW_DESTRUCTIVE=false`
|
||||
- `MCP_LOG_LEVEL=info`
|
||||
|
||||
## Common mistakes
|
||||
- Relative path to MCP build entry.
|
||||
- Dashboard not running while MCP starts.
|
||||
- Mutating tools used while mutation flag is false.
|
||||
@@ -0,0 +1,19 @@
|
||||
# MCP Runbook
|
||||
|
||||
## Read-only daily mode
|
||||
- Keep both mutation flags false.
|
||||
- Use read tools for observability and reporting.
|
||||
|
||||
## Admin mode
|
||||
- Set `MCP_DASHBOARD_ALLOW_MUTATIONS=true`.
|
||||
- Run maintenance/pricing operations.
|
||||
- Reset mutation flag to false after completion.
|
||||
|
||||
## Destructive mode
|
||||
- Set both mutation and destructive flags true.
|
||||
- Execute destructive command only with explicit confirmation token.
|
||||
- Immediately disable destructive flag after operation.
|
||||
|
||||
## Verification commands
|
||||
- `npm run mcp:typecheck`
|
||||
- `npm run mcp:build`
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: repo-onboarding
|
||||
description: Onboard quickly to this repository. Use when asked to understand architecture, locate ownership, choose the right module, or identify the correct commands and verification strategy before coding.
|
||||
---
|
||||
|
||||
# Repo Onboarding
|
||||
|
||||
Use this workflow when a task begins with discovery.
|
||||
|
||||
## Steps
|
||||
- Read `README.md` and `ARCHITECTURE.md` for system-level context.
|
||||
- Identify target layer:
|
||||
- `server/` for API, hooks, DB, websocket
|
||||
- `client/` for UI and routing
|
||||
- `mcp/` for local MCP tools and policy gates
|
||||
- Select the smallest set of files required to answer the task.
|
||||
- Confirm verification commands before implementation.
|
||||
|
||||
## Verification defaults
|
||||
- Backend: `npm run test:server`
|
||||
- Frontend: `npm run test:client`
|
||||
- MCP: `npm run mcp:typecheck` and `npm run mcp:build`
|
||||
|
||||
## References
|
||||
- Module map: `references/module-map.md`
|
||||
- Command map: `references/command-map.md`
|
||||
@@ -0,0 +1,25 @@
|
||||
# Command Map
|
||||
|
||||
## Root commands
|
||||
- `npm run setup`
|
||||
- `npm run dev`
|
||||
- `npm run build`
|
||||
- `npm start`
|
||||
- `npm run test:server`
|
||||
- `npm run test:client`
|
||||
- `npm run install-hooks`
|
||||
- `npm run import-history`
|
||||
- `npm run clear-data`
|
||||
|
||||
## MCP helper commands (root)
|
||||
- `npm run mcp:install`
|
||||
- `npm run mcp:build`
|
||||
- `npm run mcp:start`
|
||||
- `npm run mcp:dev`
|
||||
- `npm run mcp:typecheck`
|
||||
|
||||
## Direct MCP package commands
|
||||
- `npm --prefix mcp install`
|
||||
- `npm --prefix mcp run build`
|
||||
- `npm --prefix mcp run start`
|
||||
- `npm --prefix mcp run typecheck`
|
||||
@@ -0,0 +1,20 @@
|
||||
# Module Map
|
||||
|
||||
## Backend
|
||||
- `server/index.js`: app composition, startup behavior, periodic maintenance.
|
||||
- `server/db.js`: schema and prepared statement ownership.
|
||||
- `server/routes/*.js`: endpoint contracts by domain.
|
||||
- `server/websocket.js`: WS lifecycle and broadcast behavior.
|
||||
|
||||
## Frontend
|
||||
- `client/src/pages/`: route-level screens.
|
||||
- `client/src/components/`: reusable UI building blocks.
|
||||
- `client/src/lib/api.ts`: client API access patterns.
|
||||
- `client/src/hooks/useWebSocket.ts`: live update pipeline.
|
||||
|
||||
## MCP
|
||||
- `mcp/src/index.ts`: runtime entrypoint.
|
||||
- `mcp/src/server.ts`: MCP assembly.
|
||||
- `mcp/src/tools/domains/`: domain tool registration.
|
||||
- `mcp/src/clients/dashboard-api-client.ts`: resilient API bridge.
|
||||
- `mcp/src/policy/tool-guards.ts`: mutation/destructive gates.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: ship-feature
|
||||
description: Implement a feature safely end-to-end in this repository. Use when adding or changing functionality across backend, frontend, or MCP with required verification and documentation updates.
|
||||
---
|
||||
|
||||
# Ship Feature
|
||||
|
||||
Use this workflow for medium or large implementation tasks.
|
||||
|
||||
## Steps
|
||||
- Explore impacted modules first.
|
||||
- Write a short implementation plan before editing.
|
||||
- Implement smallest coherent diff that satisfies requirements.
|
||||
- Run relevant verification commands.
|
||||
- Update docs when commands, paths, architecture, or behavior changed.
|
||||
|
||||
## Required quality checks
|
||||
- Keep API and websocket contracts stable unless intentionally changed.
|
||||
- Keep destructive operations behind explicit guardrails.
|
||||
- Avoid broad refactors in feature tickets unless requested.
|
||||
|
||||
## Finish checklist
|
||||
- Tests/build/typecheck completed or explicitly reported as not run.
|
||||
- Changed file set is scoped and intentional.
|
||||
- User-facing docs updated if behavior changed.
|
||||
|
||||
## References
|
||||
- Checklist template: `references/feature-checklist.md`
|
||||
@@ -0,0 +1,22 @@
|
||||
# Feature Checklist
|
||||
|
||||
- Scope
|
||||
- Problem and success criteria are explicit.
|
||||
- Impacted layers identified (server/client/mcp/docs/scripts).
|
||||
|
||||
- Implementation
|
||||
- Input validation and error handling are explicit.
|
||||
- Existing behavior preserved where not in scope.
|
||||
- Safety controls preserved.
|
||||
|
||||
- Verification
|
||||
- Backend: `npm run test:server` when backend changed.
|
||||
- Frontend: `npm run test:client` when UI changed.
|
||||
- MCP: `npm run mcp:typecheck` + `npm run mcp:build` when MCP changed.
|
||||
|
||||
- Documentation
|
||||
- `README.md`, `ARCHITECTURE.md`, `SETUP.md`, `INSTALL.md`, `mcp/README.md` updated as needed.
|
||||
- Commands in docs match `package.json`.
|
||||
|
||||
- Delivery
|
||||
- Known risks and unrun checks are clearly stated.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: update-project-docs
|
||||
description: MANDATORY for every coding agent (Claude Code, Codex, or any other) — keep this repository's documentation in sync after any change to behavior, configuration, interfaces, events, schema, or features. Use automatically (without being asked) at the end of ANY change-set that adds or alters an env var, event type, hook behavior, session/agent state transition, API route or response shape, DB schema, WebSocket message, MCP tool, CLI command, or user-facing feature — and whenever the user asks to "update the docs / README / architecture". Knows the full doc surface (README, ARCHITECTURE, server/client READMEs, docs/*) and which docs each kind of change touches.
|
||||
---
|
||||
|
||||
# Update Project Docs
|
||||
|
||||
This repository keeps a large doc set and docs drift silently, because one change often belongs in several files at once. This skill encodes **which docs exist, which change-types touch which docs, and how to propagate consistently**. This build ships English only — the translated READMEs, the wiki and the root landing page were removed; do not recreate them.
|
||||
|
||||
Authoritative inventory with exact section anchors lives in [`references/doc-map.md`](references/doc-map.md) — read it when deciding where a specific change lands. The repo rule [`.claude/rules/docs-markdown.md`](../../rules/docs-markdown.md) ("update all affected docs together") is binding.
|
||||
|
||||
## When to update (including without being asked)
|
||||
|
||||
Update docs **in the same change-set (PR/commit) as the code**, before claiming done — do not wait for the user to ask — whenever the change is observable from outside the module:
|
||||
|
||||
- **New/changed env var** → every env-var table + `.env.example`.
|
||||
- **New event type** (e.g. an `events.event_type` value) → every event-type list/table.
|
||||
- **New/changed hook behavior or session/agent state transition** → hook docs + every state-machine diagram.
|
||||
- **New/changed API route or response shape** → API docs + route tables + OpenAPI.
|
||||
- **DB schema change** (table/column/index) → database docs + ERD.
|
||||
- **New WebSocket message type** → client/server WS docs.
|
||||
- **New MCP tool** → MCP docs.
|
||||
- **New CLI command / script / renamed file referenced in docs** → command lists + onboarding guides.
|
||||
- **New user-facing feature / page / background service** → feature tables + architecture.
|
||||
|
||||
**Do NOT** auto-update for: pure internal refactors with no observable/interface/config change, test-only changes, comment/typo fixes, or work the user explicitly scoped as "no docs". When unsure whether a change is observable, check the mapping below; if it touches any row, update.
|
||||
|
||||
## Change → docs mapping
|
||||
|
||||
| Change type | Docs to update |
|
||||
|---|---|
|
||||
| **Env var** | `README.md` (env table), `ARCHITECTURE.md` (inline), `server/README.md`, `.env.example` |
|
||||
| **Event type** | `README.md`+VN+CN (hook-event table), `ARCHITECTURE.md` (Event types line), `docs/PLUGINS.md`, + i18n, `docs/DATABASE.md` (if it enumerates types) |
|
||||
| **Hook behavior / state transition** | `docs/HOOKS.md`, state-machine **mermaid** diagrams in `README.md`+VN+CN + `server/README.md` + `docs/DATABASE.md` + , `ARCHITECTURE.md` (hooks.js row) |
|
||||
| **API route / response** | `docs/API.md`, `server/README.md` (routes), `ARCHITECTURE.md` (routes row), `server/openapi*.js` (code) |
|
||||
| **DB schema** | `docs/DATABASE.md`, `ARCHITECTURE.md` (ERD/schema) |
|
||||
| **WebSocket message** | `client/README.md` (Event Types), `server/README.md`, |
|
||||
| **MCP tool** | `mcp/README.md`, `docs/MCP.md` |
|
||||
| **Feature / page / background service** | `README.md` (feature table + data-flow list), `ARCHITECTURE.md` (module table), `server/README.md` or `client/README.md` |
|
||||
| **CLI command / script** | `README.md` commands, `CLAUDE.md` / `AGENTS.md`, `INSTALL.md` / `SETUP.md` |
|
||||
| **New language** | `docs/I18N.md`, `client/src/i18n/locales/<xx>/*`, `client/src/i18n/index.ts` (add to `supportedLngs` AND the `resources` map) |
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Classify** the change against the table above. A change can hit multiple rows (a new feature with a new env var hits both).
|
||||
2. **Write the canonical English version first** — usually `README.md` and/or `ARCHITECTURE.md`. Get the wording right there; it anchors everything else.
|
||||
6. **Area READMEs / docs/**: update `server/README.md`, `client/README.md`, and the relevant `docs/*.md` per the mapping.
|
||||
7. **Diagrams**: when a state transition changes, edit every mermaid `stateDiagram-v2` block that models it (they are duplicated across README, server/README and docs/DATABASE). Keep transition labels consistent.
|
||||
|
||||
## Verify (do not skip)
|
||||
|
||||
- **Coverage**: run `scripts/doc-coverage.sh <new-term> [...]` (e.g. the new env var / event type / identifier) and confirm every doc the mapping flags shows a HIT. The matrix is advisory — not every term belongs in every file — but a flagged doc reading `0` is a miss to fix.
|
||||
- **Tables**: markdown tables stay pipe-balanced (header column count == every row).
|
||||
- **Mermaid**: each edited block still parses (valid `source --> target: label`).
|
||||
- **i18n**: every new English string has a `vi` entry in `client/src/i18n/locales/vi/`.
|
||||
- **Format/tests**: run `npm run format` (or `prettier --check` on touched files); for any code touched, run the verification from `CLAUDE.md` (`npm run test:server` / `test:client` / `mcp:typecheck`).
|
||||
- State exactly which docs were updated and which were intentionally skipped (with reason), mirroring the repo's verification policy.
|
||||
|
||||
## Tips
|
||||
|
||||
- The fastest way to find where something already lives: `grep -n "<existing-neighbor-term>" <doc>` (e.g. grep an adjacent env var to find the env table). `references/doc-map.md` lists the stable anchors per file.
|
||||
- Parallelize translations + HTML across subagents when the change is large, but write the canonical English edit yourself first so the translations have a faithful source.
|
||||
- One language/area per subagent keeps edits reviewable and tables un-corrupted.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Documentation Map
|
||||
|
||||
Authoritative inventory of this repository's documentation surface: every doc that must be kept in sync, what each contains, and the stable anchors to grep for when placing an edit. Section line numbers drift — grep the anchor strings, don't trust line numbers.
|
||||
|
||||
## Tier 1 — primary, always consider
|
||||
|
||||
### `README.md` (English, canonical)
|
||||
The source of truth most other docs mirror. Key sections:
|
||||
- **Feature table** — rows like `**Kanban Board**`, `**Transcript Cache**`, `**Pre-Existing Session Detection**`, `**Continuous Project Sync**`. Grep a neighboring row label.
|
||||
- **Data-flow numbered list** — bullets describing hook ingestion, the watchdog, periodic sweep, continuous sync. Grep `Error detection watchdog` / `periodic server sweep`.
|
||||
- **Agent State Machine** + **Session State Machine** — two `mermaid stateDiagram-v2` blocks. Grep `stateDiagram-v2`.
|
||||
- **Hook Events table** — `| Hook Type | Trigger | Dashboard Action |`. Lists `SessionStart`…`SessionEnd`, plus synthetic `Compaction`, `APIError`, `TurnDuration`, `ToolError`, `Interrupted`. Grep `## Hook Events`.
|
||||
- **Configuration / Environment Variables table** — `| Environment Variable | Default | Description |`. Grep `DASHBOARD_PORT` or `DASHBOARD_HOST`.
|
||||
|
||||
### Translations
|
||||
|
||||
This build ships English only. `README-VN.md`, `README-CN.md` and `README-KO.md` were removed, as were the `zh` and `ko` UI locales — do not recreate them.
|
||||
Standalone full translations of `README.md`. **Every** README change must be mirrored here at the corresponding section. Conventions:
|
||||
- Keep in English/code: identifiers, env-var names, event-type names, `awaiting_input_since`, `pendingInterrupt`, "watchdog", `fs.watch`, model IDs, mermaid transition labels.
|
||||
- Translate prose. "Waiting" → **Đang chờ** (vi) / **等待中** (zh) / **대기 중** (ko). "watchdog" often kept; in zh sometimes 看门狗.
|
||||
|
||||
### `ARCHITECTURE.md`
|
||||
- **Module responsibility table** — one row per source file (`scripts/import-history.js`, `lib/transcript-cache.js`, `routes/hooks.js`, `server/index.js`, …). Update the row whose file you changed. Grep the file path.
|
||||
- **Data-flow + sequence diagrams**, **state machines**, **Continuous background sync** prose block (grep `Continuous background sync`).
|
||||
- **Event types line** — grep `| Event types |`.
|
||||
- **ERD / schema** mermaid + `event_type "PreToolUse|PostToolUse|Stop|etc"`.
|
||||
|
||||
### `server/README.md`
|
||||
Backend reference: routes table, **Error Detection Watchdog** / **User-Interrupt (Esc) Recovery** / **Continuous Project Sync** sections, Agent/Session lifecycle mermaid diagrams, Environment Variables bash block under `## Deployment`. Update for any backend behavior, route, state, env var, or background service.
|
||||
|
||||
### `client/README.md`
|
||||
Frontend reference: component list, **Event Types** table (WebSocket broadcast message types like `session_created`, `agent_updated`), session/agent status TypeScript unions. Update for new WS message types or client-facing behavior. NOT needed for server-only changes the UI already renders generically.
|
||||
|
||||
### `docs/HOOKS.md`
|
||||
Per-hook deep reference (`### 1. SessionStart` … `### 8. SessionEnd`), the `awaiting_input_since` overlay rules, the "User interrupts (Esc) — no hook fires" section, transcript-derived sync. Update for any hook semantics or state behavior.
|
||||
|
||||
### `docs/DATABASE.md`
|
||||
Schema reference: `sessions` / `agents` / `events` tables, column docs, status CHECK constraints, lifecycle mermaid diagrams. Update for schema or state-machine changes.
|
||||
|
||||
### `docs/API.md`
|
||||
REST API reference (endpoints, params, example responses). Update for route/response changes. Pair with `server/openapi*.js` (code, not docs).
|
||||
|
||||
### `docs/PLUGINS.md`
|
||||
Plugin/marketplace docs incl. an **Event Types** enumeration line — keep it in sync with the canonical event-type list.
|
||||
|
||||
### `docs/MCP.md` + `mcp/README.md`
|
||||
MCP server + tool reference. Update for new/changed MCP tools.
|
||||
|
||||
### `docs/I18N.md`
|
||||
i18n architecture: **Supported languages** list, `supportedLngs`, the 15 namespaces. Update when adding a language or namespace. Client UI strings live in `client/src/i18n/locales/{en,zh,vi}/*.json` (code).
|
||||
|
||||
## Tier 3 — situational
|
||||
|
||||
- `.env.example` — every env var belongs here with a sane default + comment.
|
||||
- `INSTALL.md`, `SETUP.md`, `DEPLOYMENT.md`, `docs/DEPLOYMENT.md` — install/run/deploy commands.
|
||||
- `CLAUDE.md`, `AGENTS.md` — agent working guides; update when commands, file locations, or workflows change.
|
||||
- `docs/README.md` — docs index; add a link when a new `docs/*.md` is created.
|
||||
- `desktop/README.md`, `vscode-extension/README.md`, `statusline/README.md` — surface-specific; update only when that surface changes.
|
||||
|
||||
## Consistency invariants
|
||||
|
||||
- The **event-type set** must match across: `README` hook table, `ARCHITECTURE` Event types line, `docs/PLUGINS.md`. When adding one, grep the existing set (e.g. `TurnDuration`) across all and add everywhere it appears.
|
||||
- **Env-var set** must match across: README tables, `server/README.md`, `.env.example`, and any inline `ARCHITECTURE` mention.
|
||||
- **State-machine diagrams** are duplicated across README, `server/README.md` and `docs/DATABASE.md`. A transition change touches all of them.
|
||||
- Run `scripts/doc-coverage.sh <term>` to confirm a new identifier/var/event reached every doc that should mention it.
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# doc-coverage.sh — verify that one or more terms (a new env var, event type,
|
||||
# route, identifier, feature name, …) are documented across this repo's
|
||||
# canonical doc surface. Prints a HIT/miss matrix so a docs update can be
|
||||
# checked for "full coverage" before finishing.
|
||||
#
|
||||
# Usage:
|
||||
# .claude/skills/update-project-docs/scripts/doc-coverage.sh DASHBOARD_SESSION_SYNC_MS
|
||||
# .claude/skills/update-project-docs/scripts/doc-coverage.sh Interrupted pendingInterrupt
|
||||
#
|
||||
# Run from the repo root. Exit code is non-zero if any term is missing from a
|
||||
# doc that the change-type mapping (see references/doc-map.md) says it belongs
|
||||
# in — but treat the matrix as advisory: not every term belongs in every file.
|
||||
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
|
||||
set -u
|
||||
|
||||
# The canonical doc set kept in sync. Translations + HTML + per-area READMEs.
|
||||
DOCS=(
|
||||
"README.md"
|
||||
"ARCHITECTURE.md"
|
||||
"server/README.md"
|
||||
"client/README.md"
|
||||
"docs/HOOKS.md"
|
||||
"docs/DATABASE.md"
|
||||
"docs/API.md"
|
||||
"docs/PLUGINS.md"
|
||||
"docs/MCP.md"
|
||||
"mcp/README.md"
|
||||
"docs/I18N.md"
|
||||
".env.example"
|
||||
)
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "usage: $0 <term> [term2 ...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
missing_any=0
|
||||
for term in "$@"; do
|
||||
echo "── coverage for: $term ──────────────────────────────"
|
||||
for doc in "${DOCS[@]}"; do
|
||||
if [ ! -f "$doc" ]; then
|
||||
printf " %-26s (absent)\n" "$doc"
|
||||
continue
|
||||
fi
|
||||
n=$(grep -Fc -- "$term" "$doc" 2>/dev/null || true)
|
||||
n=${n:-0}
|
||||
if [ "$n" -gt 0 ]; then
|
||||
printf " ✅ %-26s %s\n" "$doc" "$n"
|
||||
else
|
||||
printf " · %-26s 0\n" "$doc"
|
||||
fi
|
||||
done
|
||||
echo
|
||||
done
|
||||
|
||||
exit $missing_any
|
||||
Reference in New Issue
Block a user