@jaggerxtrm/specialists 3.17.0 → 3.18.1
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/README.md +268 -134
- package/config/mandatory-rules/executor-delivery.md +4 -0
- package/config/mandatory-rules/json-only-final-output.md +13 -0
- package/config/mandatory-rules/service-skills-diff-scan-mandatory.md +26 -0
- package/config/mandatory-rules/service-skills-gitnexus-triage.md +5 -0
- package/config/mandatory-rules/test-runner-execution-scope.md +4 -0
- package/config/skills/setup-specialists/SKILL.md +556 -0
- package/config/skills/specialists-creator/SKILL.md +132 -4
- package/config/skills/specialists-creator/scripts/audit-spec-uniformity.mjs +1 -1
- package/config/skills/using-specialists-v3/SKILL.md +80 -20
- package/config/specialists/bare.specialist.json +3 -3
- package/config/specialists/changelog-drafter.specialist.json +5 -4
- package/config/specialists/changelog-keeper.specialist.json +7 -6
- package/config/specialists/debugger.specialist.json +2 -2
- package/config/specialists/executor.specialist.json +2 -2
- package/config/specialists/explorer.specialist.json +4 -4
- package/config/specialists/memory-processor.specialist.json +3 -3
- package/config/specialists/node-coordinator.specialist.json +3 -3
- package/config/specialists/obligations-scanner.specialist.json +67 -17
- package/config/specialists/overthinker.specialist.json +3 -3
- package/config/specialists/planner.specialist.json +4 -4
- package/config/specialists/quant-methodologist.specialist.json +145 -0
- package/config/specialists/quant-researcher.specialist.json +144 -0
- package/config/specialists/researcher.specialist.json +5 -5
- package/config/specialists/reviewer.specialist.json +1 -1
- package/config/specialists/seconder.specialist.json +2 -2
- package/config/specialists/security-auditor.specialist.json +2 -2
- package/config/specialists/service-skills-sync.specialist.json +90 -75
- package/config/specialists/specialists-creator.specialist.json +4 -4
- package/config/specialists/sync-docs.specialist.json +4 -4
- package/config/specialists/test-engineer.specialist.json +3 -3
- package/config/specialists/test-runner.specialist.json +7 -7
- package/config/specialists/transcriber.specialist.json +3 -3
- package/config/specialists/xt-merge.specialist.json +4 -4
- package/dist/asset-contract.json +22 -3
- package/dist/index.js +28711 -18517
- package/dist/lib.js +10020 -6150
- package/dist/types/cli/console/components.d.ts +83 -0
- package/dist/types/cli/console/components.d.ts.map +1 -0
- package/dist/types/cli/console/config-source.d.ts +58 -0
- package/dist/types/cli/console/config-source.d.ts.map +1 -0
- package/dist/types/cli/console/forensic.d.ts +11 -0
- package/dist/types/cli/console/forensic.d.ts.map +1 -0
- package/dist/types/cli/console/git.d.ts +28 -0
- package/dist/types/cli/console/git.d.ts.map +1 -0
- package/dist/types/cli/console/help.d.ts +2 -0
- package/dist/types/cli/console/help.d.ts.map +1 -0
- package/dist/types/cli/console/log.d.ts +13 -0
- package/dist/types/cli/console/log.d.ts.map +1 -0
- package/dist/types/cli/console/repo-config.d.ts +26 -0
- package/dist/types/cli/console/repo-config.d.ts.map +1 -0
- package/dist/types/cli/console/repo-discovery.d.ts +12 -0
- package/dist/types/cli/console/repo-discovery.d.ts.map +1 -0
- package/dist/types/cli/console/runtime.d.ts +12 -0
- package/dist/types/cli/console/runtime.d.ts.map +1 -0
- package/dist/types/cli/console/subscribe-prototype.d.ts +18 -0
- package/dist/types/cli/console/subscribe-prototype.d.ts.map +1 -0
- package/dist/types/cli/console/theme.d.ts +91 -0
- package/dist/types/cli/console/theme.d.ts.map +1 -0
- package/dist/types/cli/console/types.d.ts +231 -0
- package/dist/types/cli/console/types.d.ts.map +1 -0
- package/dist/types/cli/console/view-model.d.ts +252 -0
- package/dist/types/cli/console/view-model.d.ts.map +1 -0
- package/dist/types/cli/console.d.ts +2 -0
- package/dist/types/cli/console.d.ts.map +1 -0
- package/dist/types/cli/db.d.ts.map +1 -1
- package/dist/types/cli/doctor.d.ts.map +1 -1
- package/dist/types/cli/edit.d.ts.map +1 -1
- package/dist/types/cli/epic.d.ts.map +1 -1
- package/dist/types/cli/feed.d.ts.map +1 -1
- package/dist/types/cli/forensic.d.ts +2 -0
- package/dist/types/cli/forensic.d.ts.map +1 -0
- package/dist/types/cli/format-helpers.d.ts +4 -2
- package/dist/types/cli/format-helpers.d.ts.map +1 -1
- package/dist/types/cli/help.d.ts.map +1 -1
- package/dist/types/cli/init.d.ts +10 -0
- package/dist/types/cli/init.d.ts.map +1 -1
- package/dist/types/cli/list.d.ts.map +1 -1
- package/dist/types/cli/log.d.ts.map +1 -1
- package/dist/types/cli/merge.d.ts.map +1 -1
- package/dist/types/cli/metrics.d.ts +2 -0
- package/dist/types/cli/metrics.d.ts.map +1 -0
- package/dist/types/cli/ps.d.ts.map +1 -1
- package/dist/types/cli/result.d.ts.map +1 -1
- package/dist/types/cli/run.d.ts +20 -0
- package/dist/types/cli/run.d.ts.map +1 -1
- package/dist/types/cli/script.d.ts +3 -0
- package/dist/types/cli/script.d.ts.map +1 -1
- package/dist/types/cli/serve.d.ts.map +1 -1
- package/dist/types/cli/setup.d.ts +19 -1
- package/dist/types/cli/setup.d.ts.map +1 -1
- package/dist/types/cli/status.d.ts.map +1 -1
- package/dist/types/cli/version-check.d.ts +1 -0
- package/dist/types/cli/version-check.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/pi/session.d.ts +12 -1
- package/dist/types/pi/session.d.ts.map +1 -1
- package/dist/types/server.d.ts +15 -0
- package/dist/types/server.d.ts.map +1 -1
- package/dist/types/specialist/benchmarks.d.ts +37 -0
- package/dist/types/specialist/benchmarks.d.ts.map +1 -0
- package/dist/types/specialist/chain-identity.d.ts +7 -1
- package/dist/types/specialist/chain-identity.d.ts.map +1 -1
- package/dist/types/specialist/control.d.ts.map +1 -1
- package/dist/types/specialist/dead-job-audit.d.ts +20 -0
- package/dist/types/specialist/dead-job-audit.d.ts.map +1 -0
- package/dist/types/specialist/forensic-events.d.ts +138 -0
- package/dist/types/specialist/forensic-events.d.ts.map +1 -0
- package/dist/types/specialist/forensic-renderer.d.ts +34 -0
- package/dist/types/specialist/forensic-renderer.d.ts.map +1 -0
- package/dist/types/specialist/git-diff-evidence.d.ts +28 -0
- package/dist/types/specialist/git-diff-evidence.d.ts.map +1 -0
- package/dist/types/specialist/global-config.d.ts +389 -0
- package/dist/types/specialist/global-config.d.ts.map +1 -0
- package/dist/types/specialist/launch.d.ts +9 -0
- package/dist/types/specialist/launch.d.ts.map +1 -1
- package/dist/types/specialist/live-aggregates.d.ts +46 -0
- package/dist/types/specialist/live-aggregates.d.ts.map +1 -0
- package/dist/types/specialist/loader.d.ts +50 -1
- package/dist/types/specialist/loader.d.ts.map +1 -1
- package/dist/types/specialist/model-chain.d.ts +7 -0
- package/dist/types/specialist/model-chain.d.ts.map +1 -0
- package/dist/types/specialist/model-probes.d.ts +28 -0
- package/dist/types/specialist/model-probes.d.ts.map +1 -0
- package/dist/types/specialist/node-contract.d.ts +18 -18
- package/dist/types/specialist/node-supervisor.d.ts.map +1 -1
- package/dist/types/specialist/observability-db.d.ts +1 -1
- package/dist/types/specialist/observability-db.d.ts.map +1 -1
- package/dist/types/specialist/observability-sqlite.d.ts +101 -0
- package/dist/types/specialist/observability-sqlite.d.ts.map +1 -1
- package/dist/types/specialist/pr-drift-refresh.d.ts +17 -0
- package/dist/types/specialist/pr-drift-refresh.d.ts.map +1 -0
- package/dist/types/specialist/preset-resolver.d.ts +56 -0
- package/dist/types/specialist/preset-resolver.d.ts.map +1 -0
- package/dist/types/specialist/prometheus-projection.d.ts +25 -0
- package/dist/types/specialist/prometheus-projection.d.ts.map +1 -0
- package/dist/types/specialist/runner.d.ts +29 -1
- package/dist/types/specialist/runner.d.ts.map +1 -1
- package/dist/types/specialist/schema.d.ts +163 -54
- package/dist/types/specialist/schema.d.ts.map +1 -1
- package/dist/types/specialist/script-runner.d.ts +5 -1
- package/dist/types/specialist/script-runner.d.ts.map +1 -1
- package/dist/types/specialist/snapshot-diff.d.ts +8 -0
- package/dist/types/specialist/snapshot-diff.d.ts.map +1 -0
- package/dist/types/specialist/source-queue.d.ts +13 -0
- package/dist/types/specialist/source-queue.d.ts.map +1 -0
- package/dist/types/specialist/status-load.d.ts.map +1 -1
- package/dist/types/specialist/supervisor.d.ts +25 -1
- package/dist/types/specialist/supervisor.d.ts.map +1 -1
- package/dist/types/specialist/timeline-events.d.ts +70 -1
- package/dist/types/specialist/timeline-events.d.ts.map +1 -1
- package/dist/types/tools/specialist/resume_specialist.tool.d.ts +4 -4
- package/dist/types/tools/specialist/specialist_status.tool.d.ts +1 -1
- package/dist/types/tools/specialist/steer_specialist.tool.d.ts +4 -4
- package/dist/types/tools/specialist/use_specialist.tool.d.ts +16 -16
- package/docs/ARCHITECTURE.md +1176 -0
- package/docs/TODO.md +9 -0
- package/docs/architecture.md +11 -0
- package/docs/archive/2026-03-13-bd-decision-context-template-plan.md +96 -0
- package/docs/archive/AGENT-HANDOFF.md +351 -0
- package/docs/archive/PARITY-ANALYSIS.md +296 -0
- package/docs/archive/SPECIALISTS_REFACTOR.md.md +30 -0
- package/docs/archive/cc-programmatic.md +216 -0
- package/docs/archive/claude-agent-sdk.md +594 -0
- package/docs/archive/decision-specialist-directory.md +41 -0
- package/docs/archive/discoveries.md +148 -0
- package/docs/archive/executor-benchmark-protocol.md +198 -0
- package/docs/archive/future-features.md +66 -0
- package/docs/archive/gzrx-completion-critique.md +183 -0
- package/docs/archive/gzrx-research-notes.md +401 -0
- package/docs/archive/gzrx-tool-catalog.md +760 -0
- package/docs/archive/iron-review-hardening-qa-chain-substrate.md +322 -0
- package/docs/archive/iron-review-hardening.html +1004 -0
- package/docs/archive/issuetracking.md +312 -0
- package/docs/archive/qa-v3.0.2.md +220 -0
- package/docs/archive/restructure.md +231 -0
- package/docs/archive/script-specialists.md +1254 -0
- package/docs/archive/spec-v3.md +792 -0
- package/docs/archive/specialist-stats.md +127 -0
- package/docs/archive/specialists-friction-audit.md +1347 -0
- package/docs/archive/specialists-runtime-critique.md +170 -0
- package/docs/archive/specialists-service-evaluation.md +713 -0
- package/docs/archive/specialists-substrate-alignment.md +255 -0
- package/docs/archive/substrate-review.md +1288 -0
- package/docs/archive/test-writer-specialist.md +254 -0
- package/docs/archive/using-specialists-v3-improvements-2026-05-09.md +600 -0
- package/docs/archive/xtrm-specialists-analysis.md +314 -0
- package/docs/authoring.md +701 -0
- package/docs/background-jobs.md +203 -0
- package/docs/bare-specialists.md +83 -0
- package/docs/benchmarks/executor-benchmark-runner.md +66 -0
- package/docs/bootstrap.md +161 -0
- package/docs/cli-reference.md +1645 -0
- package/docs/deploying-alongside.md +155 -0
- package/docs/design/README.md +36 -0
- package/docs/design/darth-feedor-migration.md +290 -0
- package/docs/design/roadmap/README.md +32 -0
- package/docs/design/roadmap/chain-templates/README.md +146 -0
- package/docs/design/roadmap/chain-templates/code-quick.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/code-standard.formula.json +126 -0
- package/docs/design/roadmap/chain-templates/code-with-advisors.formula.json +128 -0
- package/docs/design/roadmap/chain-templates/code-with-tests.formula.json +76 -0
- package/docs/design/roadmap/chain-templates/debug.formula.json +128 -0
- package/docs/design/roadmap/chain-templates/doc-sync.formula.json +28 -0
- package/docs/design/roadmap/chain-templates/memory-hygiene.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/planning.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/premortem.formula.json +26 -0
- package/docs/design/roadmap/chain-templates/release-prep.formula.json +36 -0
- package/docs/design/roadmap/chain-templates/research-only.formula.json +28 -0
- package/docs/design/roadmap/chain-templates/restitch.formula.json +125 -0
- package/docs/design/roadmap/chain-templates/security-deep.formula.json +161 -0
- package/docs/design/roadmap/chain-templates/test-only.formula.json +52 -0
- package/docs/design/roadmap/chain-templates/triage.formula.json +35 -0
- package/docs/design/roadmap/history/handoff-from-substrate-design.md +87 -0
- package/docs/design/roadmap/history/substrate-reconciliation.md +105 -0
- package/docs/design/roadmap/specialists-roadmap.md +1261 -0
- package/docs/design/sp-console-subscribe-via-materializer.md +231 -0
- package/docs/design/sp-console-tui-mock-v2.html +293 -0
- package/docs/design/sp-console-tui-mock.html +120 -0
- package/docs/design/sp-console-tui.md +340 -0
- package/docs/design/specialist-agentops-suite.md +323 -0
- package/docs/design/substrate/channels.md +14 -0
- package/docs/design/substrate/devops-platform-engineering-prd.md +446 -0
- package/docs/design/substrate/html-design-example.md +339 -0
- package/docs/design/xtrm-tiers-architecture.svg +132 -0
- package/docs/devops/dependency-verdict-materialization.md +46 -0
- package/docs/epic-readiness.md +75 -0
- package/docs/examples/mercury-atomic-summarizer.specialist.json +26 -0
- package/docs/examples/smoke-echo-text-expected-keys.specialist.json +25 -0
- package/docs/examples/smoke-echo.specialist.json +25 -0
- package/docs/features.md +1577 -0
- package/docs/hooks.md +81 -0
- package/docs/installation.md +142 -0
- package/docs/manifest.md +184 -0
- package/docs/mcp-servers.md +73 -0
- package/docs/mcp-tools.md +71 -0
- package/docs/nodes.md +231 -0
- package/docs/observability-metrics.md +152 -0
- package/docs/operator/sp-console-v2-walkthrough.md +410 -0
- package/docs/overrides-guide.md +306 -0
- package/docs/pi-rpc-boundary.md +118 -0
- package/docs/pi-session.md +195 -0
- package/docs/release.md +22 -0
- package/docs/skills.md +132 -0
- package/docs/specialists/handoff-schema.md +181 -0
- package/docs/specialists-catalog.md +99 -0
- package/docs/specialists-service-install.md +226 -0
- package/docs/specialists-service.md +363 -0
- package/docs/surface-ownership.md +138 -0
- package/docs/upgrade-notes/kan-90-global-user-config.md +114 -0
- package/docs/upgrade-notes/kan-91-expanded-overrides.md +261 -0
- package/docs/workflow.md +114 -0
- package/docs/worktree.md +71 -0
- package/docs/worktrees.md +309 -0
- package/package.json +7 -4
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# sp console as materializer `sync_hint` subscriber — design proposal
|
|
2
|
+
|
|
3
|
+
> Status: **EXPLORATORY**. This document does NOT commit to a daemon
|
|
4
|
+
> architecture. It enumerates the trade-offs of swapping the 1500ms
|
|
5
|
+
> poll loop for a push-based subscription, and gives the operator
|
|
6
|
+
> enough to decide whether to file the Phase 5 build-out bead or close
|
|
7
|
+
> this thread as decided-not-to-build.
|
|
8
|
+
>
|
|
9
|
+
> Bead: `unitAI-ctb4u.22` (Phase 4 of materializer adoption).
|
|
10
|
+
|
|
11
|
+
## 1. Why this is worth considering
|
|
12
|
+
|
|
13
|
+
After Phase 1 (`snapshotDiff`) + Phase 2 (`SourceQueue`) + Phase 3
|
|
14
|
+
(per-row paint cache), the sp console poll loop is already at a steady
|
|
15
|
+
state of:
|
|
16
|
+
|
|
17
|
+
- One `git_numstat` + `git_status` (worktree DiffView only).
|
|
18
|
+
- One `listProcessSnapshot` per active repo, every 1500ms.
|
|
19
|
+
- `snapshotHash` short-circuits the dispatch on no-op snapshots, so most
|
|
20
|
+
polls produce zero render work.
|
|
21
|
+
|
|
22
|
+
In steady state the cost is dominated by the SQLite read in
|
|
23
|
+
`listProcessSnapshot`. That cost is per-repo and per-poll, so a 3-repo
|
|
24
|
+
session at 1500ms COALESCE makes 2 SQLite reads/sec just to learn that
|
|
25
|
+
nothing changed 90% of the time.
|
|
26
|
+
|
|
27
|
+
The gitboard materializer already publishes the right signal — it
|
|
28
|
+
fires a `specialists:sync_hint` event on `specialists:repo:<slug>` and
|
|
29
|
+
`specialists:activity` after every successful run
|
|
30
|
+
(`gitboard:packages/core/src/materializer/materializer.ts:104
|
|
31
|
+
publishHint`). If sp console subscribes to those channels, the poll
|
|
32
|
+
loop collapses to a fallback path used only when the daemon is
|
|
33
|
+
unreachable.
|
|
34
|
+
|
|
35
|
+
## 2. The five enumeration points
|
|
36
|
+
|
|
37
|
+
### 2.1 The daemon process model
|
|
38
|
+
|
|
39
|
+
The materializer must outlive a single sp console invocation, so a
|
|
40
|
+
long-running process is the load-bearing assumption of this whole
|
|
41
|
+
proposal.
|
|
42
|
+
|
|
43
|
+
Three candidate hosts:
|
|
44
|
+
|
|
45
|
+
1. **`sp serve` (already shipped)** — extend `sp serve` to mount the
|
|
46
|
+
materializer alongside the existing `/v1/generate` route. Pro:
|
|
47
|
+
single binary, no new daemon vocabulary. Con: `sp serve` is a
|
|
48
|
+
per-project HTTP service; materializer wants to span repos.
|
|
49
|
+
|
|
50
|
+
2. **New `sp materialize-daemon` subcommand** — explicit daemon
|
|
51
|
+
process. Pro: clean separation; binds a UNIX socket per `$HOME`,
|
|
52
|
+
not per repo. Con: another lifecycle the operator has to manage
|
|
53
|
+
(systemd unit / launchd plist / `xt` integration).
|
|
54
|
+
|
|
55
|
+
3. **Co-host inside `xt` itself** — `xt` already has a session
|
|
56
|
+
lifecycle (`xt claude`, `xt end`) and a global home at
|
|
57
|
+
`~/.xtrm/`. Materializer becomes an internal `xt materialize-daemon`
|
|
58
|
+
subprocess started lazily on first sp/xt invocation. Pro: leverages
|
|
59
|
+
the existing `xt` lifetime model. Con: cross-repo coupling between
|
|
60
|
+
specialists and xtrm-tools that didn't exist before.
|
|
61
|
+
|
|
62
|
+
**Recommendation:** option 2 (new dedicated subcommand) is the lowest
|
|
63
|
+
cognitive overhead — it stays inside the specialists repo, mounts at
|
|
64
|
+
`~/.local/share/specialists/materialize.sock`, and `xt` integration is
|
|
65
|
+
a follow-up if it turns out to be valuable. The other two options can
|
|
66
|
+
be reconsidered after the operator has lived with option 2.
|
|
67
|
+
|
|
68
|
+
### 2.2 Authentication / authorization for the channel
|
|
69
|
+
|
|
70
|
+
The sp console is local-only — operator runs it as themselves on
|
|
71
|
+
their own machine. Two transports are practical:
|
|
72
|
+
|
|
73
|
+
| Transport | Auth surface | Complexity | Cross-host? |
|
|
74
|
+
|-----------|--------------|------------|-------------|
|
|
75
|
+
| UNIX socket (line-delimited JSON) | File-mode 0600 | ~30 LoC | No |
|
|
76
|
+
| HTTP+WS (loopback only) | Bearer token via `~/.config/specialists/daemon-token` | ~80 LoC + bearer check | Yes (with care) |
|
|
77
|
+
|
|
78
|
+
**Recommendation:** UNIX socket. Zero new auth machinery — file
|
|
79
|
+
permissions are the auth surface, and sp console runs as the same uid
|
|
80
|
+
as the daemon by definition. A future cross-host need (remote sp
|
|
81
|
+
console hitting a remote daemon) is real but speculative; build for
|
|
82
|
+
the actual workflow.
|
|
83
|
+
|
|
84
|
+
The line-delimited JSON wire format mirrors the existing forensic
|
|
85
|
+
event shape (`src/specialist/forensic-events.ts` —
|
|
86
|
+
`xtrm.forensic.v1`). Each frame is one JSON object terminated by a
|
|
87
|
+
newline. Frames are small (~200 bytes per `sync_hint`) so coalescing
|
|
88
|
+
at the wire is unnecessary.
|
|
89
|
+
|
|
90
|
+
### 2.3 The fallback model: subscribe when available, poll otherwise
|
|
91
|
+
|
|
92
|
+
**Mandatory invariant:** sp console must work standalone. The daemon
|
|
93
|
+
is an optimization, not a dependency. The poll loop never dies; it
|
|
94
|
+
just changes cadence based on whether subscription is active.
|
|
95
|
+
|
|
96
|
+
Concrete shape:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const sub = await trySubscribe('unix:~/.local/share/specialists/materialize.sock');
|
|
100
|
+
if (sub.ok) {
|
|
101
|
+
sub.onSyncHint((repoSlug) => queues.get(repoSlug)?.enqueue(repoSlug, refresh));
|
|
102
|
+
// Poll cadence relaxes 6x: COALESCE_MS = 1500 → 9000 (fallback heartbeat).
|
|
103
|
+
RELAX_COALESCE();
|
|
104
|
+
} else {
|
|
105
|
+
// Standalone: keep COALESCE_MS = 1500 (existing behavior).
|
|
106
|
+
KEEP_COALESCE();
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The subscription only changes when the queue fires, not what it fires.
|
|
111
|
+
The SourceQueue port (unitAI-ctb4u.20) is the right hand-off seam —
|
|
112
|
+
the daemon decides _when_ to poke each repo's queue; the queue
|
|
113
|
+
decides _whether_ to drain (still coalesces against bursts).
|
|
114
|
+
|
|
115
|
+
If the socket closes mid-session, sp console drops back to the polling
|
|
116
|
+
cadence automatically. Reconnect logic is bounded-exponential (1s, 2s,
|
|
117
|
+
5s, 10s, 30s, cap) and a single line per reconnect attempt routes
|
|
118
|
+
through `logError` for visibility.
|
|
119
|
+
|
|
120
|
+
### 2.4 Materializer pieces — gitboard vs specialists
|
|
121
|
+
|
|
122
|
+
| Piece | Today | Should live |
|
|
123
|
+
|-------|-------|-------------|
|
|
124
|
+
| `snapshot-diff.ts` | Ported (Phase 1) | both repos (verbatim) |
|
|
125
|
+
| `queue.ts` (SourceQueue) | Ported (Phase 2) | both repos (verbatim) |
|
|
126
|
+
| `materializer.ts:Materializer` class | gitboard only | gitboard; specialists imports |
|
|
127
|
+
| `materializer.ts:realtimeHintFor` | gitboard only | gitboard (channel naming is gitboard's contract) |
|
|
128
|
+
| `publishHint` WS fan-out | gitboard only | gitboard (uses `WSRegistry`) |
|
|
129
|
+
| sp console subscriber | doesn't exist | specialists (new) |
|
|
130
|
+
|
|
131
|
+
**Recommendation:** keep the heavyweight pieces in gitboard. The
|
|
132
|
+
materializer-as-class is intricate (state cache, cursor management,
|
|
133
|
+
deps tracking) and gitboard already has the right tests. specialists
|
|
134
|
+
gets a thin subscriber that wraps the UNIX socket and translates the
|
|
135
|
+
`{ event: "specialists:sync_hint", data: { repoSlug } }` frames into
|
|
136
|
+
`SourceQueue.enqueue` calls.
|
|
137
|
+
|
|
138
|
+
For the daemon process, two options:
|
|
139
|
+
|
|
140
|
+
- **Vendor the materializer class into specialists** — copy the
|
|
141
|
+
~400 LoC of `materializer.ts` plus its deps (`snapshot-diff.ts`,
|
|
142
|
+
`queue.ts`). Big surface area to keep in sync.
|
|
143
|
+
|
|
144
|
+
- **Import gitboard as a dependency** — adds a 200kB+ workspace
|
|
145
|
+
package boundary. Operator currently uses gitboard as a separate
|
|
146
|
+
monorepo, not a published npm package.
|
|
147
|
+
|
|
148
|
+
Neither is great. A practical interim: extract the materializer core
|
|
149
|
+
to a small published package (`@xtrm/materializer-core`) that both
|
|
150
|
+
projects can consume. That's a follow-up bead, not part of this one.
|
|
151
|
+
|
|
152
|
+
### 2.5 `MaterializedSpecialistJob` vs `ConsoleJob`
|
|
153
|
+
|
|
154
|
+
Today `ConsoleJob extends SupervisorStatus`. The materializer projects
|
|
155
|
+
`SupervisorStatus[]` into `MaterializedSpecialistJob[]` with a
|
|
156
|
+
slightly different shape — denormalized fields for fast scans, no
|
|
157
|
+
deeply nested `metrics` block, label-keyed views for Prometheus.
|
|
158
|
+
|
|
159
|
+
**The real fork:** if sp console consumes `MaterializedSpecialistJob`,
|
|
160
|
+
the runtime client becomes a thin reader and the materializer is the
|
|
161
|
+
shared source of truth. If sp console stays on `ConsoleJob`, two
|
|
162
|
+
projections of the same data continue to live.
|
|
163
|
+
|
|
164
|
+
| Stay on `ConsoleJob` | Adopt `MaterializedSpecialistJob` |
|
|
165
|
+
|----------------------|-----------------------------------|
|
|
166
|
+
| Zero churn for existing renderers | Renderers refactor to the new shape (~50 sites) |
|
|
167
|
+
| ConsoleJob can carry sp-console-only enrichments (`payload_kb`, `next_action`) | Enrichments must live in the materializer or move to a console-local projection layer |
|
|
168
|
+
| Two shapes drift; data inconsistencies are possible | Single canonical shape across console + Prometheus + gitboard |
|
|
169
|
+
| Phase 5 bead is a thin sub task | Phase 5 bead is a multi-week refactor |
|
|
170
|
+
|
|
171
|
+
**Recommendation:** stay on `ConsoleJob`. The fork-in-the-road choice
|
|
172
|
+
is not the right call to bundle with "stop polling SQLite". Adopting
|
|
173
|
+
`MaterializedSpecialistJob` would force a console-wide rendering
|
|
174
|
+
refactor on top of the daemon work — too much risk in one step. File
|
|
175
|
+
the shape-unification question as a separate bead if/when it becomes
|
|
176
|
+
load-bearing for a real workflow.
|
|
177
|
+
|
|
178
|
+
## 3. Prototype
|
|
179
|
+
|
|
180
|
+
`src/cli/console/subscribe-prototype.ts` (gated behind
|
|
181
|
+
`SPECIALISTS_CONSOLE_SUBSCRIBE_PROTOTYPE=1`) demonstrates the UNIX
|
|
182
|
+
socket subscriber + fake materializer locally. It is NOT production
|
|
183
|
+
code. It:
|
|
184
|
+
|
|
185
|
+
1. Spawns a fake materializer that writes synthetic `specialists:sync_hint`
|
|
186
|
+
frames at 5Hz to a tmp UNIX socket.
|
|
187
|
+
2. Connects a tiny subscriber that consumes the frames and forwards
|
|
188
|
+
them to a stub `onHint(repoSlug)` callback.
|
|
189
|
+
3. Logs every frame + every reconnect attempt to stderr via
|
|
190
|
+
`console.error` (NOT through `logError` since the prototype is
|
|
191
|
+
not production wiring).
|
|
192
|
+
|
|
193
|
+
Running:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
SPECIALISTS_CONSOLE_SUBSCRIBE_PROTOTYPE=1 bun run src/cli/console/subscribe-prototype.ts
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
When the env var is unset, the module exports a no-op and importing
|
|
200
|
+
it has zero side effects. The gate is the only thing protecting
|
|
201
|
+
production from accidentally pulling the prototype in.
|
|
202
|
+
|
|
203
|
+
## 4. Operator decision points
|
|
204
|
+
|
|
205
|
+
Before filing Phase 5, the operator should answer:
|
|
206
|
+
|
|
207
|
+
1. **Daemon host:** option 2 (`sp materialize-daemon`) or push back
|
|
208
|
+
for option 1/3?
|
|
209
|
+
2. **Transport:** UNIX socket (default) or HTTP+WS (only if cross-host
|
|
210
|
+
becomes a real workflow)?
|
|
211
|
+
3. **Materializer source:** vendor into specialists, import gitboard as
|
|
212
|
+
workspace, or extract to `@xtrm/materializer-core`?
|
|
213
|
+
4. **Shape:** stay on `ConsoleJob` (default) or adopt
|
|
214
|
+
`MaterializedSpecialistJob` (separate bead)?
|
|
215
|
+
5. **Cadence:** when subscription is active, what's the right poll
|
|
216
|
+
fallback heartbeat? 9000ms? 30000ms? Never (only on socket close)?
|
|
217
|
+
|
|
218
|
+
## 5. Cross-references
|
|
219
|
+
|
|
220
|
+
- `substrate-review.md` §11 — channels work for the cross-repo
|
|
221
|
+
direction (substrate's `pulse` primitive is the conceptual cousin).
|
|
222
|
+
- `src/specialist/snapshot-diff.ts` + `src/specialist/source-queue.ts`
|
|
223
|
+
— the two pieces already in specialists ready to compose with a
|
|
224
|
+
subscriber.
|
|
225
|
+
- `gitboard:packages/core/src/materializer/materializer.ts:publishHint`
|
|
226
|
+
— the upstream emit site we'd subscribe to.
|
|
227
|
+
|
|
228
|
+
## 6. Revision history
|
|
229
|
+
|
|
230
|
+
- 2026-06-19 — initial proposal landed alongside the gated prototype
|
|
231
|
+
(`unitAI-ctb4u.22`).
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="it">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>sp console</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
:root{
|
|
12
|
+
--bg:#181818;
|
|
13
|
+
--txt:#c8c8c8;
|
|
14
|
+
--bright:#e4e4e4;
|
|
15
|
+
--dim:#7d7d7d;
|
|
16
|
+
--tree:#3c3c3c;
|
|
17
|
+
--rail:#3a3a3a;
|
|
18
|
+
--sel:#262626;
|
|
19
|
+
--run:#c39a5e; --done:#8caa6a; --rev:#9f88bf; --wait:#8095a8; --idle:#4a4a4a; --block:#c4806f;
|
|
20
|
+
--mono:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
|
|
21
|
+
--lh:1.2;
|
|
22
|
+
}
|
|
23
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
24
|
+
html,body{height:100%}
|
|
25
|
+
body{background:var(--bg);color:var(--txt);font-family:var(--mono);
|
|
26
|
+
font-size:12.5px;line-height:var(--lh);font-weight:400;
|
|
27
|
+
-webkit-font-smoothing:antialiased;letter-spacing:.2px;
|
|
28
|
+
height:100vh;display:flex;flex-direction:column;outline:none;overflow:hidden}
|
|
29
|
+
|
|
30
|
+
.top{flex:0 0 auto;padding:0 10px}
|
|
31
|
+
.top > div{line-height:var(--lh);white-space:pre}
|
|
32
|
+
.tabs{color:var(--dim)}
|
|
33
|
+
.tabs .on{color:var(--bright)}
|
|
34
|
+
.meters{color:var(--dim)}
|
|
35
|
+
.viewtag{color:var(--dim)}
|
|
36
|
+
.viewtag b{color:var(--txt);font-weight:500}
|
|
37
|
+
|
|
38
|
+
.main{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden}
|
|
39
|
+
.main::-webkit-scrollbar{width:6px}
|
|
40
|
+
.main::-webkit-scrollbar-thumb{background:#2c2c2c;border-radius:3px}
|
|
41
|
+
|
|
42
|
+
.ln{display:flex;align-items:stretch;line-height:var(--lh);padding:0 10px;white-space:pre}
|
|
43
|
+
.ln.sel{background:var(--sel)}
|
|
44
|
+
.g{flex:0 0 2ch}
|
|
45
|
+
.g.rail{border-left:1px solid var(--rail)}
|
|
46
|
+
.c{white-space:pre}
|
|
47
|
+
|
|
48
|
+
.tree{color:var(--tree)}
|
|
49
|
+
.id{color:var(--dim)}
|
|
50
|
+
.name{color:var(--txt)}
|
|
51
|
+
.hname{color:var(--bright)}
|
|
52
|
+
.dim{color:var(--dim)}
|
|
53
|
+
.type{color:var(--tree)}
|
|
54
|
+
.g-run,.s-run{color:var(--run)}
|
|
55
|
+
.g-done,.s-done{color:var(--done)}
|
|
56
|
+
.g-rev,.s-rev{color:var(--rev)}
|
|
57
|
+
.g-wait,.s-wait{color:var(--wait)}
|
|
58
|
+
.g-idle,.s-idle{color:var(--idle)}
|
|
59
|
+
.g-block,.s-block{color:var(--block)}
|
|
60
|
+
|
|
61
|
+
.ftime{color:var(--tree)} .ftag{color:var(--txt)} .fjob{color:var(--dim)} .farr{color:var(--tree)}
|
|
62
|
+
|
|
63
|
+
.ck{color:var(--dim)} .cv{color:var(--txt)} .ch{color:var(--tree)}
|
|
64
|
+
|
|
65
|
+
.bot{flex:0 0 auto;padding:0 10px;white-space:pre;color:var(--dim);font-size:12px;overflow:hidden;line-height:var(--lh)}
|
|
66
|
+
.bot .k{color:var(--txt)}
|
|
67
|
+
.filter{flex:0 0 auto;padding:0 10px;color:var(--txt);white-space:pre;line-height:var(--lh)}
|
|
68
|
+
.filter .p{color:var(--dim)}
|
|
69
|
+
.hidden{display:none}
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body tabindex="0">
|
|
73
|
+
<div class="top">
|
|
74
|
+
<div class="tabs" id="tabs"></div>
|
|
75
|
+
<div class="meters" id="meters"></div>
|
|
76
|
+
<div class="viewtag" id="viewtag"></div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="main" id="main"></div>
|
|
79
|
+
<div class="filter hidden" id="filter"></div>
|
|
80
|
+
<div class="bot" id="bot"></div>
|
|
81
|
+
|
|
82
|
+
<script>
|
|
83
|
+
const E = s => String(s).replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c]));
|
|
84
|
+
const padR=(s,n)=>{s=String(s);return s.length>=n?s:s+' '.repeat(n-s.length);};
|
|
85
|
+
const padL=(s,n)=>{s=String(s);return s.length>=n?s:' '.repeat(n-s.length)+s;};
|
|
86
|
+
const trunc=(s,n)=>{s=String(s);return s.length>n?s.slice(0,n-1)+'…':s;};
|
|
87
|
+
|
|
88
|
+
const GLY={running:'●',done:'✓',reviewing:'◉',waiting:'◔',idle:'○',blocked:'✕'};
|
|
89
|
+
const GC ={running:'g-run',done:'g-done',reviewing:'g-rev',waiting:'g-wait',idle:'g-idle',blocked:'g-block'};
|
|
90
|
+
const SC ={running:'s-run',done:'s-done',reviewing:'s-rev',waiting:'s-wait',idle:'s-idle',blocked:'s-block'};
|
|
91
|
+
const CGLY={epic:'◆',chain:'◇',wave:'▦'};
|
|
92
|
+
const MAXD=2;
|
|
93
|
+
|
|
94
|
+
function step(d,id,name,st,role,wt,clk,note){return {kind:'step',d,id,name,st,role,wt,clk,note,job:id};}
|
|
95
|
+
function cont(kind,d,id,name,st,counts){return {kind,d,id,name,st,counts};}
|
|
96
|
+
|
|
97
|
+
const REPOS=[
|
|
98
|
+
{name:'mercury-quant', rows:[
|
|
99
|
+
cont('epic' ,0,'ep-4f2a','rates-pipeline-refactor','running','3/7'),
|
|
100
|
+
cont('chain',1,'ch-91bd','event-study schema promotion','running','2/4'),
|
|
101
|
+
step(2,'iss-12','composition','done','executor','cmp-a','2m04',''),
|
|
102
|
+
step(2,'iss-13','formula-derivation','running','executor','fml-b','0:48','tether 1'),
|
|
103
|
+
step(2,'iss-14','numerical-test','reviewing','reviewer','fml-b','0:12','mbox 2'),
|
|
104
|
+
step(2,'iss-15','adversarial-review','idle','—','—','—',''),
|
|
105
|
+
cont('chain',1,'ch-77ce','convention yaml lookup','running','1/2'),
|
|
106
|
+
step(2,'iss-20','yaml-migrate','running','executor','cnv-c','1:31',''),
|
|
107
|
+
step(2,'iss-21','validate','blocked','debugger','cnv-c','0:05','collision conventions.yaml'),
|
|
108
|
+
cont('chain',1,'ch-12aa','no-lookahead static analysis','waiting','0/1'),
|
|
109
|
+
step(2,'iss-30','static-scan','waiting','explorer','sca-d','—','tether pending'),
|
|
110
|
+
cont('wave' ,0,'wv-08e1','nightly-regression','reviewing','1/1'),
|
|
111
|
+
step(1,'iss-40','judge-eval','reviewing','judge','—','0:22',''),
|
|
112
|
+
]},
|
|
113
|
+
{name:'substrate', rows:[
|
|
114
|
+
cont('epic' ,0,'ep-8c10','channel-as-substrate','running','1/3'),
|
|
115
|
+
cont('chain',1,'ch-44de','single-scheduler invariant','running','1/2'),
|
|
116
|
+
step(2,'iss-50','scheduler-lease','running','executor','sch-a','3:12',''),
|
|
117
|
+
step(2,'iss-51','split-brain-test','idle','—','—','—',''),
|
|
118
|
+
cont('chain',1,'ch-09a1','mailbox discriminated-union','reviewing','1/1'),
|
|
119
|
+
step(2,'iss-52','union-model','reviewing','reviewer','mbx-b','0:39','mbox 1'),
|
|
120
|
+
]},
|
|
121
|
+
{name:'rates-research', rows:[
|
|
122
|
+
cont('chain',0,'ch-71f0','SR3-ZN event study','running','1/2'),
|
|
123
|
+
step(1,'iss-60','build-windows','done','executor','evt-a','5m20',''),
|
|
124
|
+
step(1,'iss-61','fisher-z-ci','running','executor','evt-a','0:54','tether 1'),
|
|
125
|
+
]},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const FEEDS={
|
|
129
|
+
'iss-13':[['19:41:02','LIFE','iss-13','step.claimed executor acquired lease fml-b'],['19:41:02','LIFE','iss-13','step.running model=opus ctx=18k'],['19:41:18','MAIL','iss-13','msg.recv reviewer→ "watch lookahead on event window"'],['19:41:33','TETH','iss-13','hint.inject pending no-lookahead: window fixed pre-event'],['19:41:49','CTRC','iss-13','gate.check composition ok schema:event_study'],['19:42:04','LIFE','iss-13','tool.run mq.fisher_z_ci(n=42)'],['19:42:11','MAIL','iss-13','msg.send →reviewer "CI computed, n_min ok"'],['19:42:20','TETH','iss-13','hint.cleared no-lookahead (resolved)'],['19:42:31','LIFE','iss-13','step.progress derivation 3/5']],
|
|
130
|
+
'iss-14':[['19:40:55','LIFE','iss-14','step.running reviewer (read-only, no lease)'],['19:41:10','MAIL','iss-14','msg.recv executor→ "CI computed, n_min ok"'],['19:41:40','CTRC','iss-14','verdict.partial finding: outlier sensitivity untested'],['19:42:01','MAIL','iss-14','msg.send →executor "add jackknife on tail"']],
|
|
131
|
+
'iss-21':[['19:38:12','LIFE','iss-21','step.running debugger'],['19:39:00','COLL','iss-21','collision.detect conventions.yaml owner=iss-20'],['19:39:00','LIFE','iss-21','step.blocked awaiting lease release']],
|
|
132
|
+
'iss-50':[['19:36:00','LIFE','iss-50','step.running executor sch-a'],['19:37:30','CTRC','iss-50','gate.check scheduler invariant pending'],['19:39:11','LIFE','iss-50','tool.run build single-scheduler path']],
|
|
133
|
+
'iss-61':[['19:40:00','LIFE','iss-61','step.running executor evt-a'],['19:41:12','TETH','iss-61','hint.inject pending use act/360 day-count'],['19:41:50','LIFE','iss-61','tool.run mq.fisher_z_ci(n=58)']],
|
|
134
|
+
};
|
|
135
|
+
function feedFor(j){return FEEDS[j]||[['--:--:--','LIFE',j,'no events for this job']];}
|
|
136
|
+
|
|
137
|
+
// contract overrides; fallback generated
|
|
138
|
+
const CONTRACTS={
|
|
139
|
+
'iss-13':[
|
|
140
|
+
['kv','issue','iss-13 formula-derivation'],['kv','state','running claimed 19:41:02'],
|
|
141
|
+
['kv','role','executor model opus ctx 18k'],['kv','worktree','fml-b lease held'],
|
|
142
|
+
['kv','chain','ch-91bd event-study schema promotion'],['kv','schema','event_study KNOWN_TYPES: pending'],
|
|
143
|
+
['kv','deps','iss-12 composition done'],['kv','budget','turns 6/12 tokens 18k/40k'],
|
|
144
|
+
['h','contract gate'],
|
|
145
|
+
['kv','built-in val','pass'],['kv','agent val','pending .mercury/validators.py'],
|
|
146
|
+
['kv','adversarial','pending'],['kv','no-lookahead','pass static analysis'],
|
|
147
|
+
['kv','n_min','n=42 satisfied (n_min=30)'],['kv','schema reg','no -> requires human review'],
|
|
148
|
+
['h','memory pack'],
|
|
149
|
+
['kv','mem','act/360 day-count convention'],['kv','mem','Fisher-z CI for correlations'],
|
|
150
|
+
['h','acceptance'],
|
|
151
|
+
['li','derive CI for SR3~ZN beta with n_min guard'],['li','BH-FDR across event buckets'],['li','no lookahead in event window'],
|
|
152
|
+
],
|
|
153
|
+
'iss-21':[
|
|
154
|
+
['kv','issue','iss-21 validate'],['kv','state','blocked 0:05'],
|
|
155
|
+
['kv','role','debugger model haiku'],['kv','worktree','cnv-c lease contested'],
|
|
156
|
+
['kv','chain','ch-77ce convention yaml lookup'],['kv','deps','iss-20 yaml-migrate running'],
|
|
157
|
+
['h','block reason'],['kv','collision','conventions.yaml owner iss-20'],['kv','resolves','on lease release by iss-20'],
|
|
158
|
+
['h','contract gate'],['kv','built-in val','pass'],['kv','agent val','blocked'],['kv','adversarial','n/a'],
|
|
159
|
+
['h','acceptance'],['li','yaml schema validates against convention spec'],['li','versioned lookup keys unique'],
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
function buildContract(repoIdx,row){
|
|
163
|
+
if(CONTRACTS[row.id]) return CONTRACTS[row.id];
|
|
164
|
+
const rows=REPOS[repoIdx].rows; const idx=rows.indexOf(row);
|
|
165
|
+
let chain=''; for(let i=idx;i>=0;i--){ if(rows[i].kind==='chain'){chain=rows[i].id+' '+rows[i].name;break;} }
|
|
166
|
+
return [
|
|
167
|
+
['kv','issue',row.id+' '+row.name],['kv','state',row.st+' '+(row.clk!=='—'?row.clk:'')],
|
|
168
|
+
['kv','role',row.role!=='—'?row.role:'(unassigned)'],['kv','worktree',row.wt!=='—'?row.wt:'(none)'],
|
|
169
|
+
['kv','chain',chain],
|
|
170
|
+
['h','contract gate'],['kv','built-in val',row.st==='done'?'pass':'pending'],['kv','agent val','pending'],['kv','adversarial','pending'],
|
|
171
|
+
['h','acceptance'],['li','contract acceptance criteria not yet expanded'],
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let S={repo:0, view:'ps', sel:0, feedJob:null, feedSel:0, contractRow:null, follow:false, filtering:false, fstr:''};
|
|
176
|
+
|
|
177
|
+
function visibleRows(){
|
|
178
|
+
const rows=REPOS[S.repo].rows;
|
|
179
|
+
if(!S.fstr) return rows.map((r,i)=>({r,i}));
|
|
180
|
+
const q=S.fstr.toLowerCase();
|
|
181
|
+
return rows.map((r,i)=>({r,i})).filter(o=>(o.r.id+' '+o.r.name+' '+(o.r.st||'')).toLowerCase().includes(q));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderTop(){
|
|
185
|
+
let t='';
|
|
186
|
+
REPOS.forEach((r,i)=>{ t+=`<span class="${i===S.repo?'on':''}">${i===S.repo?'['+E(r.name)+']':' '+E(r.name)+' '}</span>`; if(i<REPOS.length-1) t+=' '; });
|
|
187
|
+
document.getElementById('tabs').innerHTML=t;
|
|
188
|
+
const rows=REPOS[S.repo].rows; const steps=rows.filter(r=>r.kind==='step');
|
|
189
|
+
const active=steps.filter(s=>['running','reviewing','waiting'].includes(s.st)).length; const total=steps.length;
|
|
190
|
+
const leases=new Set(steps.filter(s=>['running','reviewing'].includes(s.st)&&s.wt&&s.wt!=='—').map(s=>s.wt)).size;
|
|
191
|
+
document.getElementById('meters').textContent=`active ${active}/${total} leases ${leases}/4 budget 61%`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function gutter(d){
|
|
195
|
+
let g='';
|
|
196
|
+
for(let i=0;i<d;i++){ g+='<span class="g rail"></span>'; }
|
|
197
|
+
return g;
|
|
198
|
+
}
|
|
199
|
+
function psLine(r,selected){
|
|
200
|
+
const isStep=r.kind==='step';
|
|
201
|
+
const g=isStep?`<span class="${GC[r.st]}">${GLY[r.st]}</span>`:`<span class="${SC[r.st]}">${CGLY[r.kind]}</span>`;
|
|
202
|
+
const target = 46 - r.d*2;
|
|
203
|
+
const idp=padR(r.id,7), nm=trunc(r.name, Math.max(8,target-10));
|
|
204
|
+
const nameCls=isStep?'name':'hname';
|
|
205
|
+
let leftVis = 1+1+idp.length+1+nm.length;
|
|
206
|
+
let content = `${g} <span class="id">${E(idp)}</span> <span class="${nameCls}">${E(nm)}</span>`;
|
|
207
|
+
content += (leftVis<target?' '.repeat(target-leftVis):' ');
|
|
208
|
+
if(isStep){
|
|
209
|
+
content+=`<span class="${SC[r.st]}">${E(padR(r.st,10))}</span><span class="dim">${E(padR(r.role,9))}</span><span class="dim">${E(padR(r.wt,7))}</span><span class="dim">${E(padL(r.clk,6))}</span>`;
|
|
210
|
+
if(r.note) content+=` <span class="dim">${E(r.note)}</span>`;
|
|
211
|
+
}else{
|
|
212
|
+
content+=`<span class="type">${E(padR(r.kind,10))}</span><span class="dim">${E(padR('',16))}</span><span class="dim">${E(padL(r.counts,6))}</span>`;
|
|
213
|
+
}
|
|
214
|
+
return `<div class="ln${selected?' sel':''}">${gutter(r.d)}<span class="c">${content}</span></div>`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function feedLine(f,selected){
|
|
218
|
+
const txt=E(f[3]).replace(/→/g,'<span class="farr">→</span>');
|
|
219
|
+
return `<div class="ln${selected?' sel':''}"><span class="c"><span class="ftime">${E(f[0])}</span> <span class="ftag">${E(padR(f[1],5))}</span> <span class="fjob">${E(padR(f[2],8))}</span> ${txt}</span></div>`;
|
|
220
|
+
}
|
|
221
|
+
function contractLine(item){
|
|
222
|
+
if(item[0]==='h') return `<div class="ln"><span class="c"><span class="ch">── ${E(item[1])} ──</span></span></div>`;
|
|
223
|
+
if(item[0]==='li') return `<div class="ln"><span class="c"><span class="dim">• </span><span class="cv">${E(item[1])}</span></span></div>`;
|
|
224
|
+
return `<div class="ln"><span class="c"><span class="ck">${E(padR(item[1],13))}</span><span class="cv">${E(item[2])}</span></span></div>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function render(){
|
|
228
|
+
renderTop();
|
|
229
|
+
const main=document.getElementById('main'); const vt=document.getElementById('viewtag');
|
|
230
|
+
if(S.view==='ps'){
|
|
231
|
+
vt.innerHTML=`<b>ps</b> ${E(REPOS[S.repo].name)} · process tree`;
|
|
232
|
+
const vis=visibleRows(); if(S.sel>=vis.length) S.sel=Math.max(0,vis.length-1);
|
|
233
|
+
main.innerHTML = vis.map((o,vi)=>psLine(o.r, vi===S.sel)).join('') || '<div class="ln"><span class="c dim">no matches</span></div>';
|
|
234
|
+
}else if(S.view==='feed'){
|
|
235
|
+
const feed=feedFor(S.feedJob);
|
|
236
|
+
vt.innerHTML=`<b>feed</b> ${E(S.feedJob)} · event stream${S.follow?' · follow':''}`;
|
|
237
|
+
if(S.feedSel>=feed.length) S.feedSel=feed.length-1;
|
|
238
|
+
main.innerHTML = feed.map((f,i)=>feedLine(f, i===S.feedSel)).join('');
|
|
239
|
+
}else{
|
|
240
|
+
vt.innerHTML=`<b>contract</b> ${E(S.contractRow.id)} · issue contract`;
|
|
241
|
+
main.innerHTML = buildContract(S.repo,S.contractRow).map(contractLine).join('');
|
|
242
|
+
}
|
|
243
|
+
const fl=document.getElementById('filter');
|
|
244
|
+
if(S.filtering){fl.classList.remove('hidden'); fl.innerHTML=`<span class="p">/</span>${E(S.fstr)}_`;} else fl.classList.add('hidden');
|
|
245
|
+
renderBot();
|
|
246
|
+
const sel=main.querySelector('.sel'); if(sel) sel.scrollIntoView({block:'nearest'});
|
|
247
|
+
if(S.view==='feed'&&S.follow){main.scrollTop=main.scrollHeight;}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function key(label,desc){return `<span class="k">${label}</span> ${desc} `;}
|
|
251
|
+
function renderBot(){
|
|
252
|
+
const b=document.getElementById('bot');
|
|
253
|
+
if(S.view==='ps') b.innerHTML=key('↑↓','nav')+key('↵','feed')+key('⇧↵','contract')+key('⇥','repo')+key('/','filter')+key('g/G','top/end')+key('q','quit');
|
|
254
|
+
else if(S.view==='feed') b.innerHTML=key('↑↓','scroll')+key('⌫','back')+key('f','follow')+key('g/G','top/end')+key('q','quit');
|
|
255
|
+
else b.innerHTML=key('↑↓','scroll')+key('⌫','back')+key('q','quit');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function clampSel(d){
|
|
259
|
+
if(S.view==='ps'){const n=visibleRows().length; S.sel=Math.max(0,Math.min(n-1,S.sel+d));}
|
|
260
|
+
else if(S.view==='feed'){const n=feedFor(S.feedJob).length; S.feedSel=Math.max(0,Math.min(n-1,S.feedSel+d)); S.follow=false;}
|
|
261
|
+
else{document.getElementById('main').scrollBy(0, d*19);}
|
|
262
|
+
}
|
|
263
|
+
window.addEventListener('keydown',e=>{
|
|
264
|
+
if(S.filtering){
|
|
265
|
+
if(e.key==='Escape'){S.filtering=false;S.fstr='';S.sel=0;}
|
|
266
|
+
else if(e.key==='Enter'){S.filtering=false;}
|
|
267
|
+
else if(e.key==='Backspace'){S.fstr=S.fstr.slice(0,-1);S.sel=0;}
|
|
268
|
+
else if(e.key.length===1){S.fstr+=e.key;S.sel=0;}
|
|
269
|
+
else return;
|
|
270
|
+
e.preventDefault();render();return;
|
|
271
|
+
}
|
|
272
|
+
switch(e.key){
|
|
273
|
+
case 'ArrowDown': case 'j': clampSel(1); break;
|
|
274
|
+
case 'ArrowUp': case 'k': clampSel(-1); break;
|
|
275
|
+
case 'Enter':
|
|
276
|
+
if(S.view==='ps'){const o=visibleRows()[S.sel]; if(o&&o.r.kind==='step'){ if(e.shiftKey){S.view='contract';S.contractRow=o.r;} else {S.view='feed';S.feedJob=o.r.job;S.feedSel=0;S.follow=false;} }}
|
|
277
|
+
break;
|
|
278
|
+
case 'Escape': case 'Backspace': case 'ArrowLeft':
|
|
279
|
+
if(S.view!=='ps'){S.view='ps';} break;
|
|
280
|
+
case 'Tab': S.repo=(S.repo+1)%REPOS.length; S.sel=0; S.fstr=''; S.view='ps'; break;
|
|
281
|
+
case '1': case '2': case '3': {const i=+e.key-1; if(i<REPOS.length){S.repo=i;S.sel=0;S.fstr='';S.view='ps';}} break;
|
|
282
|
+
case '/': if(S.view==='ps'){S.filtering=true;} break;
|
|
283
|
+
case 'f': if(S.view==='feed'){S.follow=!S.follow;} break;
|
|
284
|
+
case 'g': if(S.view==='ps')S.sel=0; else if(S.view==='feed')S.feedSel=0; else document.getElementById('main').scrollTop=0; break;
|
|
285
|
+
case 'G': if(S.view==='ps')S.sel=visibleRows().length-1; else if(S.view==='feed')S.feedSel=feedFor(S.feedJob).length-1; else {const m=document.getElementById('main');m.scrollTop=m.scrollHeight;} break;
|
|
286
|
+
default: return;
|
|
287
|
+
}
|
|
288
|
+
e.preventDefault(); render();
|
|
289
|
+
});
|
|
290
|
+
document.body.focus(); window.addEventListener('click',()=>document.body.focus()); render();
|
|
291
|
+
</script>
|
|
292
|
+
</body>
|
|
293
|
+
</html>
|