dsh-maestro-harness
DSH meta-plugin: automated GitLab MR review + performance audit, and an interactive pair-programming preset.
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 18, 2026
- Updated
- Aug 26, 2026
Introduction
dsh-maestro-harness
DSH meta-plugin: automated GitLab MR review + performance audit, an interactive
maestro-coder pair-programming preset, and full remote access to the DSH web
UI from LAN or the public internet through a Cloudflare Tunnel.
See docs/architecture.md for the current architecture and remote-access security model.
What you get
| Capability | How it works |
|---|---|
| Intent-driven MR review | A GitLab assignment or @maestro note hits this machine; Quick requests run the reviewer, Deep requests add the auditor, and inline mentions reply in the originating discussion |
| Remote access (LAN) | The plugin serves a loopback-rewriting reverse proxy on :3081; any device on your network gets the full DSH web UI — sessions, chat, Settings. Open by default, or gated behind a separate LAN PIN |
| Remote access (public) | A Cloudflare Tunnel exposes the same proxy under your own hostname; visitors enter an 8-digit PIN first. cloudflared is resolved automatically (PATH → cache → multi-mirror download) and runs over --protocol http2 so QUIC-blocking networks still work |
| QR pairing | The Settings → Maestro card shows scannable QR codes for the LAN and public URLs; the LAN URL is the scored best interface (private ranges first, VPN adapters skipped) |
| Fast remote responses | Large JSON/text replies are streamed through brotli/gzip, cutting multi-megabyte session histories roughly 10x on slow links |
| Mobile layout | On phones the UI becomes a drawer layout with touch-sized targets and safe-area insets (dsh-web-mobile port, MIT) |
| Tunnel auto-restore | If DSH restarts while a tunnel was running, it comes back automatically |
| Telegram PIN updates | Optionally sends protected messages with the current public PIN and initial access status whenever DSH web starts, and with the new PIN after a user explicitly rotates it |
| Review history & signals | Every run is recorded in a local history (visible in Settings → Maestro), acknowledged on the MR with award emoji (👀 running → ✅/⚠️ finished), and can send an opt-in Telegram digest when a review finishes |
| Push re-review (opt-in) | After a completed review, new commits pushed to the same MR trigger an automatic Quick re-review — enable it in Settings → Maestro |
Requirements
- Node.js ≥ 22, pnpm
- A running DeepSeek Harness with the
webprofile (pnpm dsh web) DEEPSEEK_API_KEYfor the review/audit sessions themselves- A GitLab account with permission to create project webhooks and personal access tokens
- maestro-skills installed for DSH when reviewing Magento projects (the installer checks the exact required set; as a plugin,
dsh plugin --profile web add github:ddtcorex/maestro-skillsserves the skills itself) cloudflaredonly if you want public access (LAN access works without it)
Companion repository: maestro-skills
The Magento review pipeline is tightly coupled to
ddtcorex/maestro-skills — the
magento2 review profile loads an exact skill set served by that package:
Required by the magento2 review profile | Role |
|---|---|
govard-toolbox | Govard environment base commands (govard up/sh/db/audit) |
govard-magento | Magento-specific container/CLI shortcuts (govard tool magerun, cache, indexers) |
magento2-dev-core | Architecture, DI, security patterns — foundation all findings are fixed with |
magento2-code-review | Review orchestration: scopes + merges QA trio output into one report |
magento2-linter | PHPCS/PHPStan/PHPMD quality gate (govard audit run --checks lint) |
magento2-security-scan | Static vulnerability scanning |
magento2-performance-audit | 9-step performance/infrastructure audit |
The generic profile needs none of these. If a required skill is missing at
review time, the reviewer fails closed and prints a one-line install command.
Install everything as one plugin (no loose files needed):
dsh plugin --profile web add github:ddtcorex/maestro-skills
Install
# from a checkout of this repository
pnpm install
pnpm build # emits lib/ (the node-half plugins)
pnpm run build:client # emits client/client.js (the Settings card bundle)
# register the plugins into the web profile (live link: edit + rebuild just works)
dsh plugin --profile web add /path/to/dsh-maestro-harness -w
# copy the three agent presets (not covered by `dsh plugin add`)
./install.sh
Magento review requests load a complete, fixed profile: Govard toolbox and
Magento guidance plus code-review, lint, security, and performance checks. If
./install.sh reports a missing skill, install exactly the required DSH skills
with the command it prints before enabling review webhooks.
Restart dsh web afterwards. Open Settings → Maestro — if the card
appears, the client wiring is working.
Remote Settings compatibility
The plugin works with unmodified DeepSeek Harness releases. For remote access,
it marks only PIN-authorized proxy HTML as trusted so DSH can expose its
Settings data to that browser. DSH versions that do not recognize
__DSH_TRUSTED_PROXY__ remain safe and usable through the proxy, but their
Models screen can show settings are unavailable in this browser.
Apply the matching DSH trusted-proxy patch when remote Models/Settings access is required. The marker is ignored by older DSH versions; it does not grant access to the login page or unauthenticated API and WebSocket requests.
Fallback: manual cordis.yml editing
If you're not using the dsh plugin CLI, add these rows to your cordis.yml
(paths relative to this package's install location):
- id: maestro-gitlab-webhook
name: '@ddtcorex/dsh-maestro-harness/lib/gitlab-webhook.js'
config:
port: 3000
secret: !!js process.env.MAESTRO_GITLAB_WEBHOOK_SECRET
- id: maestro-orchestrator
name: '@ddtcorex/dsh-maestro-harness/lib/orchestrator.js'
config:
gitlabBaseUrl: https://gitlab.example.com
gitlabToken: !!js process.env.MAESTRO_GITLAB_TOKEN
botUsername: maestro-bot
projectMappings: []
- id: maestro-tunnel
name: '@ddtcorex/dsh-maestro-harness/lib/tunnel.js'
- id: maestro-settings-rpc
name: '@ddtcorex/dsh-maestro-harness/lib/settings-rpc.js'
- id: maestro-client
name: '@ddtcorex/dsh-maestro-harness'
The last row (package root) is required — the client-modules registry only scans loader entries whose name resolves to a package root, so without it the Settings card never mounts.
Set up on a new machine
All values below are placeholders — replace gitlab.example.com,
group/project, dsh.example.com, and every token with your own. Nothing real
belongs in this repo or its config files.
1. GitLab personal access token
Create a personal access token with the api scope
(GitLab → Preferences → Access Tokens). You will paste it into the Settings UI
in step 3 — it is stored owner-only (0600) under $DSH_HOME/dsh-maestro-harness/config.json.
2. Boot environment
The webhook's shared secret can come from the environment or from the Settings UI (Settings wins, effective immediately):
export MAESTRO_GITLAB_WEBHOOK_SECRET='generate-a-long-random-string' # optional if set in Settings
export MAESTRO_GITLAB_TOKEN='glpat-not-a-real-token' # optional fallback; Settings UI wins
pnpm dsh web --no-open
Put both lines in whatever you use to persist env (shell profile, systemd unit,
.env consumed by your launcher). Never commit them.
3. Configure via Settings → Maestro
- GitLab: base URL (
https://gitlab.example.com), the token from step 1, and the GitLab service-account username (for example,maestro-bot). This username is matched when GitLab assigns a reviewer. Use that account's PAT too if comments should be authored asmaestro. - Projects: map each GitLab path to its local checkout, e.g.
group/project→/home/you/work/project. The automated pipeline only reacts to mapped projects. Each row can also override the review model. - Review model (optional): pick the provider/model (and reasoningEffort) for automated reviews from the dropdown catalog. Leave empty to use the DSH default. A per-project override beats the global choice.
Values saved here override the cordis.yml row config at runtime and take effect on the next webhook delivery without a restart.
4. Register the webhook in GitLab
One endpoint serves every repository: each delivery names its project in
project.path_with_namespace, and the orchestrator reacts only to projects
mapped in Settings → Maestro. Register the same URL and secret on each project
you want covered, or once at group level to cover all of them.
Per project (or at group level): Settings → Webhooks →
- URL:
https://<your-public-hostname>/hooks/gitlab-mr - Secret token: the effective webhook secret —
webhookSecretfrom Settings → Maestro, orMAESTRO_GITLAB_WEBHOOK_SECRETif Settings has none (step 2) - Trigger: Merge request events and Comment/Note events
- SSL verification: enabled (Cloudflare certificates validate fine)
Create a GitLab service account (or use an existing bot account), configure
its exact username in Settings, grant it permission to review and comment in
the project, and assign it as a reviewer. A new assignment starts a Quick
whole-MR review. An MR-level @<botUsername> mention (for example,
@maestro-bot) also starts Quick; add /maestro deep to request Deep review.
An inline @<botUsername> mention reviews
only that discussion's changed line and replies in the same thread. The
literal token remains supported before the service account exists, but it is
not a real GitLab mention and cannot be assigned as a reviewer.
For an unmapped project, only an explicit @<botUsername> mention starts a
Quick diff-only review. It reads the GitLab diff and labels the result as
having no local checkout, Magento environment, static analysis, or tests. A
reviewer assignment remains a no-op, and /maestro deep posts a request to add
a project mapping rather than pretending to run a full review.
Until step 5 is done, use a temporary URL of http://<machine-LAN-IP>:3000/hooks/gitlab-mr
if GitLab can reach your machine directly.
5. Public access via Cloudflare Tunnel (one-time)
Skip entirely if LAN-only access is enough.
cloudflared tunnel login # opens a browser; pick your domain
cloudflared tunnel create dsh-home # prints the tunnel ID
cloudflared tunnel route dns dsh-home dsh.example.com
Then in Settings → Maestro: switch Tunnel mode to Named, paste the
Tunnel ID, the credentials file path (~/.cloudflared/<tunnel-id>.json), and
the hostname (dsh.example.com). Press Start tunnel — the plugin generates
the ingress config itself:
<hostname>/hooks/*→ the webhook server (:3000)- everything else on
<hostname>→ the remote-access proxy (:3081, which forwards to the DSH web UI on127.0.0.1:3080)
The public address demands the access PIN shown in the card (8 digits,
stored at $DSH_HOME/dsh-maestro-harness/token, mode 0600). It survives
tunnel and DSH restarts; use Rotate in Settings → Maestro when you need to
invalidate it. Update the
GitLab webhook URL to https://dsh.example.com/hooks/gitlab-mr now that DNS
exists.
Do not run a second manual cloudflared alongside the plugin-managed one —
two connectors on one tunnel route inconsistently.
6. Verify end-to-end
- Assign the configured
maestrouser as reviewer, or write@maestroin an MR comment in a mapped project. Add/maestro deepfor the full audit. For an unmapped project, use a mention only to request the explicitly labelled diff-only Quick review. - Within seconds the orchestrator creates a worktree under the mapped clone; Quick runs the reviewer and Deep runs reviewer + auditor. An inline command replies in the original discussion.
- From a phone on the same network, scan the LAN QR in Settings → Maestro — the full DSH UI should load without any PIN.
- From an external network, open
https://dsh.example.com, enter the PIN, and confirm the session list appears.
If something doesn't work, see Troubleshooting.
Manually reviewing an already-existing MR
The automated pipeline only reacts to live webhook deliveries. To re-review an older MR:
MAESTRO_GITLAB_BASE_URL=https://gitlab.example.com \
MAESTRO_GITLAB_TOKEN=glpat-not-a-real-token \
MAESTRO_GITLAB_WEBHOOK_SECRET=the-same-secret-as-step-2 \
./bin/maestro-review.mjs --project group/project --mr 42
This looks the MR up via the GitLab API and POSTs the same payload shape the
real webhook produces to the running server's /hooks/gitlab-mr/trigger
route — the pipeline that runs is identical.
Agent presets
./install.sh copies three presets into ~/.dsh/.agent-presets/:
| Preset | Role |
|---|---|
dsh-maestro-reviewer | Reviews the MR diff and posts findings |
dsh-maestro-auditor | Performance audit pass over the changed code |
dsh-maestro-coder | Interactive pair-programming agent (available in the Web GUI agent picker immediately) |
Re-run ./install.sh after pulling preset changes.
Configuration reference
Runtime-configurable from Settings → Maestro (stored in
$DSH_HOME/dsh-maestro-harness/config.json, overrides cordis.yml):
| Field | Meaning |
|---|---|
gitlabBaseUrl / gitlabToken / botUsername | GitLab API coordinates for the review pipeline. Secrets saved from Settings are stored server-side and never echoed back — the card shows only whether one is saved |
projectMappings | projectPath → localRepoPath → reviewProfile → reviewModel mappings used for full local reviews; profiles are magento2 (full Magento skill set) or generic (no skills required). A mapping's localRepoPath must be an absolute path to an existing git checkout, validated on save. reviewModel is an optional per-project override ({provider, model, reasoningEffort?}) that takes precedence over the global review model. An unmapped explicit mention receives a limited diff-only Quick review |
reviewModel | Global model override for automated reviews ({provider, model, reasoningEffort?}) — e.g. deepseek-official / deepseek-chat. When absent the DSH default model (ctx.agentDefaultModel) is used; per-project reviewModel in projectMappings overrides this value. Dropdown catalog is served from maestro.models.list; reasoningEffort is low/medium/high when the model supports it |
webhookSecret | Webhook auth (X-Gitlab-Token), re-read per request; overrides MAESTRO_GITLAB_WEBHOOK_SECRET without a restart. Must match the GitLab webhook's Secret token |
autoRereviewOnPush | Opt-in: after a completed review, further pushes to the same MR trigger an automatic quick re-review (default false) |
agentTimeoutMs | Hard ceiling on one automated review agent's turn, minimum 1000 ms (default 20 minutes). A hung agent is disposed and the review recorded as failed |
reviewSessionRetentionDays | Prune Maestro's own review history (reviews.json) and failed-report files older than this many days; 0 keeps everything forever (default). DSH session transcripts are never touched |
tunnelMode | quick (ephemeral trycloudflare URL) or named (your hostname) |
quickTarget | What a quick tunnel exposes: dsh-web or webhook |
tunnelId / tunnelCredentialsFile / tunnelHostname | Named-tunnel coordinates from the one-time setup |
proxyPort / proxyHost | Remote-access proxy bind, default 3081 on 0.0.0.0 (set proxyHost: 127.0.0.1 to disable LAN exposure); when the port is busy the proxy walks up to ten ports instead of failing |
lanPinEnabled | Gate LAN access behind a second PIN (default false — LAN stays open). The LAN PIN lives in its own token-lan file, so rotating the public PIN never invalidates LAN links |
telegramBotToken / telegramChatId | Optional Telegram Bot API credentials for one fixed chat. Enter both in Settings → Maestro; leave either blank to disable it. Telegram sends the public PIN, proxy/LAN status, and tunnel URL/status on boot, then the new PIN after a user presses Rotate |
telegramReviewNotifications | Opt-in digest: one protected message per finished review with its outcome and a short summary (default false) |
lastTunnelRunning | Managed by the plugin; drives auto-restore on boot |
Environment variables:
| Variable | Consumed by |
|---|---|
MAESTRO_GITLAB_WEBHOOK_SECRET | Webhook auth fallback (X-Gitlab-Token) and the manual-review CLI; optional if webhookSecret is set in Settings, which wins per request |
MAESTRO_GITLAB_TOKEN, MAESTRO_GITLAB_BASE_URL | Orchestrator fallback when Maestro Settings has no value, and the manual-review CLI; the token is optional at boot |
MAESTRO_TRIGGER_URL | Manual-review CLI target, default http://localhost:3000/hooks/gitlab-mr/trigger |
Mobile layout
Phone/tablet adaptation of the DSH web shell is owned by the external
dsh-web-mobile plugin,
installed in the web profile (~/.dsh/profiles/web/package.json, tracking
upstream main), not by this repository. After any profile update or
restart: verify once in a mobile-emulating browser (Chrome
/opt/google/chrome/chrome, isMobile + hasTouch, viewport 390×844):
drawer open/close, composer, market page, Maestro settings stacking.
Only the Maestro-rendered settings card keeps local adaptation
(client/mobile-maestro.css.ts); problems elsewhere go upstream as PRs to
dsh-web-mobile, not patches here.
Security notes
- The remote-access proxy rewrites Host/Origin to loopback, which unlocks DSH's privileged RPCs remotely. The PIN gate is therefore the only authentication on the public path — treat the PIN like a password and rotate it if a link leaks (the Rotate button does this without restarting).
- PIN checks and webhook-secret comparison run in constant time (sha256 digest
timingSafeEqual), and failed public-PIN logins are rate limited per source address: five failures per ten minutes earn a429with aRetry-Afterheader; a successful login resets the counter.
- LAN access is unauthenticated by default (same trust model as your network).
On shared networks either set
proxyHost: 127.0.0.1or enable Require a PIN on the LAN in Settings → Maestro, which gates LAN hosts behind a separate rotating PIN. - GitLab tokens and the webhook secret never appear in this repository; they
live in
0600files under$DSH_HOMEor your boot environment. - Settings saves are validated server-side: unknown keys are rejected, mapping paths must be existing git checkouts, and stored secrets are never returned to the browser — the card shows only a "saved" marker plus a Clear action.
- Review history (
reviews.json) and failed reports live under$DSH_HOMEwith owner-only permissions; they record project/MR/outcome metadata only, never tokens or MR content. - Telegram bot tokens are also stored owner-only in the Maestro config and are never written to logs. Telegram startup messages contain the public access PIN; send them only to a private chat you control. The bot API request uses Telegram's
protect_contentoption, but it is not a substitute for choosing a trusted destination.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| No Maestro card in Settings | The package-root row (maestro-client) is missing from your composition, or the client bundle wasn't built (pnpm run build:client) |
Models says settings are unavailable in this browser through the tunnel | The installed DSH does not recognize the authenticated-proxy marker. The plugin and tunnel still work; apply the matching DSH trusted-proxy patch to enable remote Models/Settings |
| Session list empty via the tunnel | You are hitting dsh web directly instead of the proxy; the tunnel's non-hook ingress must point at :3081 |
| Public URL asks for a PIN you don't know | Read the current one via Show in Settings → Maestro (from localhost or LAN), or use Rotate if you need to replace it |
| Tunnel fails with "download failed on all sources" | cloudflared isn't on PATH and every mirror failed — install it manually (npm i -g cloudflared, brew install cloudflared, or winget install cloudflared) or place the binary at $DSH_HOME/dsh-maestro-harness/bin/cloudflared |
| Tunnel URL unreachable from a phone but the process runs | Many networks block cloudflared's default QUIC transport; the plugin already forces --protocol http2 (TCP 443). If it still fails, the network likely blocks that too — try a different network or a named tunnel |
| Tunnel was running, now shows an error | The cloudflared process exited; the status card shows the exit code. Start the tunnel again — a quick tunnel gets a fresh URL |
| Proxy port busy at boot | The proxy automatically moves to the next free port (up to ten tries); the actual port appears in the LAN URL on the card |
| The LAN URL on the card opens nothing from a phone | The advertised address is the scored best interface (private ranges preferred, VPN/virtual adapters penalized) — if your phone is on a guest/VLAN network it cannot reach that address; use the tunnel instead |
| Large session history loads slowly over the tunnel | Responses are compressed (brotli/gzip) automatically; if a client shows garbled JSON, it sent an accept-encoding it cannot actually decode — that is a client bug, not the proxy |
| Webhook returns 401 | X-Gitlab-Token doesn't match the effective secret — webhookSecret from Settings if set, otherwise MAESTRO_GITLAB_WEBHOOK_SECRET; with neither configured, every request is rejected |
| PIN page answers 429 | Five failed PIN attempts from your address in ten minutes; wait for the Retry-After window (a successful login resets the counter) |
| MR changed but nothing happens | This is expected unless the configured bot is newly assigned as reviewer or a note contains @<botUsername>; assignments still require a project mapping, while an unmapped mention runs only the labelled diff-only Quick review. Pushes re-review only when Re-review when new commits are pushed is enabled and a review of that MR already completed. Confirm MR + Comment/Note events are enabled. |
| Settings rejects a save | The payload failed server-side validation — the error names the key or path: unknown keys, non-absolute or non-git localRepoPath, out-of-range webhookPort/agentTimeoutMs |
| Port 3000 already in use | Another service owns the webhook port; change the port config on the maestro-gitlab-webhook row and the mapping accordingly |
Contributing: Superpowers workflow (required)
All changes to this repository follow the Superpowers skill workflow. Agents and humans contributing here are required to comply with it:
- brainstorming — explore intent, requirements, and design before touching code; write the outcome to
docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md. - writing-plans — turn the approved spec into a numbered, task-by-task plan (each task lists its failing test first) at
docs/superpowers/plans/YYYY-MM-DD-<topic>.md. - executing-plans — implement one task at a time in strict TDD: failing test → RED → implement → GREEN → commit that task as its own commit.
Rules of thumb enforced by AGENTS.md: never batch multiple tasks into one commit; never commit while a task's tests are red; keep specs and plans under docs/superpowers/ as the durable design record; tunnel/proxy tests must bind hermetic servers on port 0 because a live DSH web often occupies :3080.
Test
pnpm test