@loopover/miner 0.1.0

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.
Files changed (218) hide show
  1. package/DEPLOYMENT.md +156 -0
  2. package/Dockerfile +38 -0
  3. package/README.md +269 -0
  4. package/bin/gittensory-miner-mcp.d.ts +64 -0
  5. package/bin/gittensory-miner-mcp.js +279 -0
  6. package/bin/gittensory-miner.js +196 -0
  7. package/docs/coding-agent-driver.md +140 -0
  8. package/docs/config-precedence.md +96 -0
  9. package/docs/cross-repo-discovery-phase1.md +55 -0
  10. package/docs/discovery-index-contract.md +60 -0
  11. package/docs/discovery-plane-operator-guide.md +101 -0
  12. package/docs/env-reference.md +30 -0
  13. package/docs/fleet-run-manifest.md +50 -0
  14. package/docs/miner-goal-spec.md +76 -0
  15. package/docs/miner-selfimprove-calibration.md +105 -0
  16. package/docs/observability.md +60 -0
  17. package/docs/operations-runbook.md +209 -0
  18. package/docs/repo-agnostic-capability-audit.md +129 -0
  19. package/docs/unattended-scheduling.md +107 -0
  20. package/expected-engine.version +1 -0
  21. package/lib/ams-policy.d.ts +20 -0
  22. package/lib/ams-policy.js +68 -0
  23. package/lib/attempt-cli.d.ts +99 -0
  24. package/lib/attempt-cli.js +523 -0
  25. package/lib/attempt-input-builder.d.ts +24 -0
  26. package/lib/attempt-input-builder.js +91 -0
  27. package/lib/attempt-log.d.ts +37 -0
  28. package/lib/attempt-log.js +177 -0
  29. package/lib/attempt-runner.d.ts +59 -0
  30. package/lib/attempt-runner.js +164 -0
  31. package/lib/attempt-worktree.d.ts +31 -0
  32. package/lib/attempt-worktree.js +94 -0
  33. package/lib/calibration-cli.d.ts +1 -0
  34. package/lib/calibration-cli.js +92 -0
  35. package/lib/calibration-run.d.ts +161 -0
  36. package/lib/calibration-run.js +232 -0
  37. package/lib/calibration-types.d.ts +46 -0
  38. package/lib/calibration-types.js +74 -0
  39. package/lib/calibration.d.ts +24 -0
  40. package/lib/calibration.js +99 -0
  41. package/lib/ci-poller.d.ts +33 -0
  42. package/lib/ci-poller.js +234 -0
  43. package/lib/claim-adjudication.d.ts +21 -0
  44. package/lib/claim-adjudication.js +36 -0
  45. package/lib/claim-conflict-resolver.d.ts +29 -0
  46. package/lib/claim-conflict-resolver.js +101 -0
  47. package/lib/claim-ledger-cli.d.ts +59 -0
  48. package/lib/claim-ledger-cli.js +320 -0
  49. package/lib/claim-ledger-expiry.d.ts +20 -0
  50. package/lib/claim-ledger-expiry.js +41 -0
  51. package/lib/claim-ledger.d.ts +63 -0
  52. package/lib/claim-ledger.js +303 -0
  53. package/lib/cli-error.d.ts +3 -0
  54. package/lib/cli-error.js +27 -0
  55. package/lib/cli.d.ts +3 -0
  56. package/lib/cli.js +72 -0
  57. package/lib/coding-agent-construction.d.ts +17 -0
  58. package/lib/coding-agent-construction.js +103 -0
  59. package/lib/coding-agent-house-rules.d.ts +24 -0
  60. package/lib/coding-agent-house-rules.js +64 -0
  61. package/lib/coding-task-spec.d.ts +44 -0
  62. package/lib/coding-task-spec.js +183 -0
  63. package/lib/config-precedence.d.ts +25 -0
  64. package/lib/config-precedence.js +38 -0
  65. package/lib/deny-check.d.ts +11 -0
  66. package/lib/deny-check.js +76 -0
  67. package/lib/deny-hook-synthesis.d.ts +93 -0
  68. package/lib/deny-hook-synthesis.js +399 -0
  69. package/lib/deny-hooks.d.ts +28 -0
  70. package/lib/deny-hooks.js +155 -0
  71. package/lib/deployment-docs-audit.d.ts +41 -0
  72. package/lib/deployment-docs-audit.js +115 -0
  73. package/lib/discover-cli.d.ts +94 -0
  74. package/lib/discover-cli.js +284 -0
  75. package/lib/discovery-throttle.d.ts +8 -0
  76. package/lib/discovery-throttle.js +33 -0
  77. package/lib/env-file-indirection.d.ts +4 -0
  78. package/lib/env-file-indirection.js +45 -0
  79. package/lib/event-ledger-cli.d.ts +82 -0
  80. package/lib/event-ledger-cli.js +266 -0
  81. package/lib/event-ledger.d.ts +37 -0
  82. package/lib/event-ledger.js +210 -0
  83. package/lib/execute-local-write.d.ts +14 -0
  84. package/lib/execute-local-write.js +50 -0
  85. package/lib/feasibility-cli.d.ts +25 -0
  86. package/lib/feasibility-cli.js +80 -0
  87. package/lib/forge-config.d.ts +17 -0
  88. package/lib/forge-config.js +37 -0
  89. package/lib/governor-action-mode.d.ts +26 -0
  90. package/lib/governor-action-mode.js +47 -0
  91. package/lib/governor-chokepoint-persisted.d.ts +18 -0
  92. package/lib/governor-chokepoint-persisted.js +46 -0
  93. package/lib/governor-chokepoint.d.ts +14 -0
  94. package/lib/governor-chokepoint.js +52 -0
  95. package/lib/governor-kill-switch.d.ts +26 -0
  96. package/lib/governor-kill-switch.js +47 -0
  97. package/lib/governor-ledger-cli.d.ts +32 -0
  98. package/lib/governor-ledger-cli.js +155 -0
  99. package/lib/governor-ledger.d.ts +46 -0
  100. package/lib/governor-ledger.js +203 -0
  101. package/lib/governor-open-pr.d.ts +21 -0
  102. package/lib/governor-open-pr.js +27 -0
  103. package/lib/governor-pause-cli.d.ts +23 -0
  104. package/lib/governor-pause-cli.js +171 -0
  105. package/lib/governor-run-halt.d.ts +37 -0
  106. package/lib/governor-run-halt.js +58 -0
  107. package/lib/governor-state.d.ts +63 -0
  108. package/lib/governor-state.js +363 -0
  109. package/lib/governor-write-rate-limit.d.ts +30 -0
  110. package/lib/governor-write-rate-limit.js +64 -0
  111. package/lib/harness-submission-trigger.d.ts +69 -0
  112. package/lib/harness-submission-trigger.js +138 -0
  113. package/lib/http-retry.d.ts +12 -0
  114. package/lib/http-retry.js +52 -0
  115. package/lib/laptop-init.d.ts +53 -0
  116. package/lib/laptop-init.js +332 -0
  117. package/lib/live-issue-snapshot.d.ts +16 -0
  118. package/lib/live-issue-snapshot.js +117 -0
  119. package/lib/local-store.d.ts +18 -0
  120. package/lib/local-store.js +62 -0
  121. package/lib/logger.d.ts +58 -0
  122. package/lib/logger.js +167 -0
  123. package/lib/loop-cli.d.ts +66 -0
  124. package/lib/loop-cli.js +540 -0
  125. package/lib/loop-closure.d.ts +35 -0
  126. package/lib/loop-closure.js +66 -0
  127. package/lib/loop-reentry.d.ts +51 -0
  128. package/lib/loop-reentry.js +124 -0
  129. package/lib/manage-poll.d.ts +79 -0
  130. package/lib/manage-poll.js +254 -0
  131. package/lib/manage-status.d.ts +70 -0
  132. package/lib/manage-status.js +241 -0
  133. package/lib/metrics-cli.d.ts +9 -0
  134. package/lib/metrics-cli.js +50 -0
  135. package/lib/migrate-cli.d.ts +24 -0
  136. package/lib/migrate-cli.js +111 -0
  137. package/lib/miner-goal-spec.d.ts +12 -0
  138. package/lib/miner-goal-spec.js +67 -0
  139. package/lib/opportunity-fanout.d.ts +86 -0
  140. package/lib/opportunity-fanout.js +597 -0
  141. package/lib/opportunity-ranker.d.ts +36 -0
  142. package/lib/opportunity-ranker.js +118 -0
  143. package/lib/orb-export.d.ts +54 -0
  144. package/lib/orb-export.js +206 -0
  145. package/lib/plan-store-cli.d.ts +37 -0
  146. package/lib/plan-store-cli.js +151 -0
  147. package/lib/plan-store.d.ts +51 -0
  148. package/lib/plan-store.js +231 -0
  149. package/lib/policy-doc-cache.d.ts +25 -0
  150. package/lib/policy-doc-cache.js +79 -0
  151. package/lib/policy-verdict-cache.d.ts +35 -0
  152. package/lib/policy-verdict-cache.js +106 -0
  153. package/lib/portfolio-dashboard.d.ts +30 -0
  154. package/lib/portfolio-dashboard.js +105 -0
  155. package/lib/portfolio-discovery.d.ts +29 -0
  156. package/lib/portfolio-discovery.js +100 -0
  157. package/lib/portfolio-queue-cli.d.ts +87 -0
  158. package/lib/portfolio-queue-cli.js +509 -0
  159. package/lib/portfolio-queue-expiry.d.ts +20 -0
  160. package/lib/portfolio-queue-expiry.js +51 -0
  161. package/lib/portfolio-queue-manager.d.ts +50 -0
  162. package/lib/portfolio-queue-manager.js +134 -0
  163. package/lib/portfolio-queue.d.ts +62 -0
  164. package/lib/portfolio-queue.js +351 -0
  165. package/lib/pr-disposition-poller.d.ts +26 -0
  166. package/lib/pr-disposition-poller.js +163 -0
  167. package/lib/pr-number-parse.d.ts +4 -0
  168. package/lib/pr-number-parse.js +23 -0
  169. package/lib/pr-outcome.d.ts +41 -0
  170. package/lib/pr-outcome.js +90 -0
  171. package/lib/prediction-ledger.d.ts +47 -0
  172. package/lib/prediction-ledger.js +222 -0
  173. package/lib/pretooluse-hook.d.ts +31 -0
  174. package/lib/pretooluse-hook.js +93 -0
  175. package/lib/process-lifecycle.d.ts +31 -0
  176. package/lib/process-lifecycle.js +106 -0
  177. package/lib/purge-cli.d.ts +44 -0
  178. package/lib/purge-cli.js +198 -0
  179. package/lib/rejection-signal.d.ts +6 -0
  180. package/lib/rejection-signal.js +101 -0
  181. package/lib/rejection-state-machine.d.ts +37 -0
  182. package/lib/rejection-state-machine.js +81 -0
  183. package/lib/rejection-templates.d.ts +12 -0
  184. package/lib/rejection-templates.js +71 -0
  185. package/lib/replay-objective-anchor.d.ts +79 -0
  186. package/lib/replay-objective-anchor.js +179 -0
  187. package/lib/replay-snapshot.d.ts +41 -0
  188. package/lib/replay-snapshot.js +288 -0
  189. package/lib/replay-task-generation.d.ts +126 -0
  190. package/lib/replay-task-generation.js +223 -0
  191. package/lib/repo-clone.d.ts +19 -0
  192. package/lib/repo-clone.js +102 -0
  193. package/lib/run-state-cli.d.ts +27 -0
  194. package/lib/run-state-cli.js +154 -0
  195. package/lib/run-state.d.ts +37 -0
  196. package/lib/run-state.js +150 -0
  197. package/lib/schema-version.d.ts +17 -0
  198. package/lib/schema-version.js +71 -0
  199. package/lib/self-review-context.d.ts +28 -0
  200. package/lib/self-review-context.js +339 -0
  201. package/lib/slop-assessment.d.ts +3 -0
  202. package/lib/slop-assessment.js +20 -0
  203. package/lib/stack-detection.d.ts +41 -0
  204. package/lib/stack-detection.js +248 -0
  205. package/lib/status.d.ts +64 -0
  206. package/lib/status.js +441 -0
  207. package/lib/store-maintenance.d.ts +31 -0
  208. package/lib/store-maintenance.js +183 -0
  209. package/lib/submission-freshness-check.d.ts +32 -0
  210. package/lib/submission-freshness-check.js +93 -0
  211. package/lib/update-check.d.ts +36 -0
  212. package/lib/update-check.js +161 -0
  213. package/lib/version.d.ts +3 -0
  214. package/lib/version.js +10 -0
  215. package/lib/worktree-allocator.d.ts +39 -0
  216. package/lib/worktree-allocator.js +262 -0
  217. package/package.json +50 -0
  218. package/schema/miner-goal-spec.schema.json +111 -0
package/DEPLOYMENT.md ADDED
@@ -0,0 +1,156 @@
1
+ # Gittensory miner deployment
2
+
3
+ Two form factors for running `@loopover/miner`: **laptop mode** (single machine, zero Docker) and **fleet mode** (containerized workers with a shared data volume). Both are 100% client-side for core operation — the miner never uploads source and never requires a hosted Gittensory callback to boot. Credentials (GitHub tokens, etc.) stay on the operator's machine or in their own secret store; nothing is baked into images.
4
+
5
+ | | Laptop mode | Fleet mode |
6
+ | ---------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
7
+ | **Best for** | One contributor machine, local experimentation | Many parallel miner attempts on a host or small cluster |
8
+ | **Dependencies** | Node.js `>=22.13.0` only | Docker (or compatible runtime) + Node image or custom image |
9
+ | **State** | SQLite files under `~/.config/gittensory-miner/` (override with `GITTENSORY_MINER_CONFIG_DIR`) | Same SQLite layout on a mounted `/data` (or `GITTENSORY_MINER_CONFIG_DIR`) volume |
10
+ | **Setup** | `npm install -g @loopover/miner` or workspace build | `docker build` + `docker run` with env + volume (see below) |
11
+ | **Footprint** | One Node process, local disk for ledgers/queues | One container per worker; scale horizontally by adding containers |
12
+
13
+ ## Coding-agent provider configuration
14
+
15
+ For provider selection and the CLI-specific model/timeout overrides, see
16
+ [`README.md`](README.md) and the interface-level contract in
17
+ [`docs/coding-agent-driver.md`](docs/coding-agent-driver.md).
18
+
19
+ ## Laptop mode walkthrough
20
+
21
+ 1. Install Node.js 22.13+ and the package:
22
+
23
+ ```sh
24
+ npm install -g @loopover/miner@latest
25
+ # or from a checkout:
26
+ npm install && npm --workspace @loopover/miner run build
27
+ ```
28
+
29
+ 2. Inspect what is installed and where local state will live. `status` and `doctor` stay offline; `init --verify-token` is optional and makes one authenticated GitHub call up front:
30
+
31
+ ```sh
32
+ gittensory-miner status
33
+ gittensory-miner doctor
34
+ gittensory-miner init --verify-token # optional: validate GITHUB_TOKEN once before attempts
35
+ ```
36
+
37
+ 3. Expected layout after first use (default paths):
38
+
39
+ ```text
40
+ ~/.config/gittensory-miner/
41
+ laptop-state.sqlite3 # laptop-mode setup state, created by `init`
42
+ portfolio-queue.sqlite3 # prioritized work backlog across tracked repos (#2292)
43
+ claim-ledger.sqlite3 # soft issue claims (#2314)
44
+ plan-store.sqlite3 # persisted MCP plan DAGs (#2318)
45
+ run-state.sqlite3 # per-repo run state (idle/discovering/planning/preparing)
46
+ event-ledger.sqlite3 # append-only miner-loop event audit trail (#2290)
47
+ governor-ledger.sqlite3 # append-only governor allow/deny/throttle decisions (#2328)
48
+ governor-state.sqlite3 # governor cross-attempt counters/state (#5134)
49
+ attempt-log.sqlite3 # per-attempt coding-agent driver event trace (#4294)
50
+ worktree-allocator.sqlite3 # git-worktree-per-attempt allocation bookkeeping (#4297)
51
+ prediction-ledger.sqlite3 # predicted-gate verdicts, for later self-improve scoring (#4263)
52
+ replay-snapshot.sqlite3 # frozen historical-replay target snapshots (#3010)
53
+ policy-doc-cache.sqlite3 # ETag cache for discovery's policy-doc fetches (#4842)
54
+ policy-verdict-cache.sqlite3 # cache of resolved AI-usage-policy verdicts (#4843)
55
+ deny-hook-synthesis.sqlite3 # synthesized PreToolUse deny-hook proposals (#4522)
56
+ orb-export.sqlite3 # opt-in anonymized Orb telemetry export state (#4277)
57
+ ```
58
+
59
+ Not every file appears immediately: `laptop-state` is written by `init`, and each of the others is created
60
+ the first time its subsystem actually runs (an attempt, a discovery pass, a replay, an Orb export, …), so a
61
+ fresh install that has only run `status`/`doctor` will show a subset. All sixteen default into this one
62
+ directory. Override the directory for every store at once with `GITTENSORY_MINER_CONFIG_DIR` or
63
+ `XDG_CONFIG_HOME` (same resolution chain as `@loopover/mcp`); every store except `laptop-state.sqlite3`
64
+ (directory only) also honors its own `GITTENSORY_MINER_<NAME>_DB` path override — e.g.
65
+ `GITTENSORY_MINER_PORTFOLIO_QUEUE_DB` — to relocate an individual file. `doctor`'s `store-integrity:*` checks
66
+ report the persistent stores, so it is the quickest way to confirm what exists and is readable on disk.
67
+
68
+ 4. Optional per-repo miner goals: copy [`.gittensory-miner.yml.example`](../../.gittensory-miner.yml.example) to a target repo as `.gittensory-miner.yml`. See [`docs/miner-goal-spec.md`](docs/miner-goal-spec.md).
69
+
70
+ ## Fleet mode walkthrough
71
+
72
+ Build the fleet image from the **monorepo root** (the Dockerfile needs the full workspace on disk before `npm ci` — see comments in [`Dockerfile`](Dockerfile)):
73
+
74
+ ```sh
75
+ docker build -f packages/gittensory-miner/Dockerfile -t gittensory-miner:latest .
76
+ ```
77
+
78
+ Run a disposable worker with persistent SQLite state on a mounted volume. Inject secrets at runtime (never bake them into the image):
79
+
80
+ ```sh
81
+ docker run --rm -it \
82
+ -e GITTENSORY_MINER_CONFIG_DIR=/data/miner \
83
+ -e GITHUB_TOKEN \
84
+ -v miner-data:/data/miner \
85
+ gittensory-miner:latest \
86
+ doctor
87
+ ```
88
+
89
+ The image entrypoint is `gittensory-miner`; pass subcommands after the image name (`status`, `doctor`, `claim`, …).
90
+
91
+ - **`/data/miner` volume** — holds all SQLite state (`claim-ledger.sqlite3`, `plan-store.sqlite3`, etc.) so containers are disposable. Defaults to `GITTENSORY_MINER_CONFIG_DIR=/data/miner` in the image.
92
+ - **`GITHUB_TOKEN`** — supplied by the operator at run time; the image contains no credentials.
93
+ - **Scale** — launch additional containers with the same volume (or partitioned config dirs) for parallel attempts.
94
+
95
+ **Secret-file alternative (`GITHUB_TOKEN_FILE`).** A plain `-e GITHUB_TOKEN` value is visible in plaintext
96
+ via `docker inspect`/`docker compose config` and any full-env dump of the running container. For Docker
97
+ Swarm/Kubernetes-managed secrets (mounted as a file, e.g. at `/run/secrets/github_token`), set
98
+ `GITHUB_TOKEN_FILE` to that mount path instead — the miner reads and trims the file's contents at startup and
99
+ uses it exactly as if `GITHUB_TOKEN` had been set directly:
100
+
101
+ ```sh
102
+ docker run --rm -it \
103
+ -e GITTENSORY_MINER_CONFIG_DIR=/data/miner \
104
+ -e GITHUB_TOKEN_FILE=/run/secrets/github_token \
105
+ -v miner-data:/data/miner \
106
+ -v /path/to/your/secret:/run/secrets/github_token:ro \
107
+ gittensory-miner:latest \
108
+ doctor
109
+ ```
110
+
111
+ If both `GITHUB_TOKEN` and `GITHUB_TOKEN_FILE` are set, the plain `GITHUB_TOKEN` value always wins (same
112
+ precedence rule as ORB's own `src/selfhost/load-file-secrets.ts`). A missing or unreadable `GITHUB_TOKEN_FILE`
113
+ fails the container fast with a clear error naming the file path, rather than silently proceeding with no
114
+ credential. The same `<NAME>_FILE` convention works for any credential the miner reads from a plain env var —
115
+ not only `GITHUB_TOKEN`.
116
+
117
+ The repo-root [`docker-compose.yml`](../../docker-compose.yml) documents the **self-hosted review stack** (the `gittensory` API/orb), not the miner CLI. Miners are clients of that stack (or of github.com directly) and do not require it to run locally.
118
+
119
+ ### Docker Compose (fleet mode)
120
+
121
+ Instead of a hand-assembled `docker run`, [`docker-compose.miner.yml`](docker-compose.miner.yml) defines a long-lived `miner` service (built from this package's Dockerfile, `restart: unless-stopped`, state on a named `miner-data` volume). Credentials come from an env file, never inlined:
122
+
123
+ ```sh
124
+ cp .gittensory-miner.env.example .gittensory-miner.env # fill in GITHUB_TOKEN (+ optional provider keys)
125
+ docker compose -f docker-compose.miner.yml up -d --build
126
+ ```
127
+
128
+ **Scaling to N parallel workers.** `docker compose -f docker-compose.miner.yml up -d --scale miner=N` gives every replica the **same** `miner-data` volume — and the miner's SQLite ledgers are **not** safe for concurrent access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give each its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2 …` — `-p` namespaces the volume) or point each at a distinct `GITTENSORY_MINER_CONFIG_DIR` on its own mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in [`k8s/`](../../k8s/) (per-pod volumes).
129
+
130
+ ## Bare-host (systemd, no Docker)
131
+
132
+ To run the miner continuously on a plain Linux host without Docker, supervise `gittensory-miner loop` — the autonomous discover → attempt → manage daemon (#5135) — with systemd. [`systemd/gittensory-miner.service.example`](../../systemd/gittensory-miner.service.example) is a ready-to-adapt persistent unit; its header carries the full install steps:
133
+
134
+ ```sh
135
+ npm install -g @loopover/miner
136
+ gittensory-miner init --verify-token # optional: validate GITHUB_TOKEN before discovery/attempt runs
137
+ sudo cp systemd/gittensory-miner.service.example /etc/systemd/system/gittensory-miner.service
138
+ sudo $EDITOR /etc/systemd/system/gittensory-miner.service # set User / WorkingDirectory / ExecStart / secrets
139
+ sudo systemctl daemon-reload
140
+ sudo systemctl enable --now gittensory-miner.service
141
+ ```
142
+
143
+ Because `loop` is a **long-running daemon that schedules its own cycles**, it is a persistent `Type=simple` service (with `Restart=on-failure`) — **not** a oneshot unit driven by a `.timer`, unlike the periodic `loopover-docker-prune.*.example` hygiene job in [`systemd/`](../../systemd/). Keep `GITHUB_TOKEN` (and any coding-agent credentials) in a root-owned `0600` `EnvironmentFile`, never in the unit file. Follow the loop with `journalctl -u gittensory-miner -f`; `systemctl stop` sends SIGTERM, which the loop handles cleanly at its next kill-switch check.
144
+
145
+ ## Invariants
146
+
147
+ - Core miner bookkeeping (claims, plans, queues, ledgers) works offline after install.
148
+ - `gittensory-miner status` and `gittensory-miner doctor` make **no network calls**.
149
+ - Discovery/ranking primitives that touch GitHub only run when explicitly invoked and only perform documented GETs unless a future command says otherwise.
150
+ - Operators own secret injection; images and packages ship without embedded tokens.
151
+
152
+ See [`docs/operations-runbook.md`](docs/operations-runbook.md) for operational scenarios: ledger corruption, two miners on one state dir, and post-upgrade schema migration ([#4875](https://github.com/JSONbored/gittensory/issues/4875)).
153
+
154
+ ## Optional hosted discovery plane (opt-in)
155
+
156
+ The Phase 6 **hosted discovery-index** is **off by default** — unlike Orb fleet export (`ORB_AIR_GAP` is the only opt-out). Operators who want cross-fleet metadata queries or soft-claim coordination must opt in explicitly. See [`docs/discovery-plane-operator-guide.md`](docs/discovery-plane-operator-guide.md) ([#4309](https://github.com/JSONbored/gittensory/issues/4309), placeholder until [#4300](https://github.com/JSONbored/gittensory/issues/4300) / [#4301](https://github.com/JSONbored/gittensory/issues/4301) / [#4302](https://github.com/JSONbored/gittensory/issues/4302) ship).
package/Dockerfile ADDED
@@ -0,0 +1,38 @@
1
+ # Fleet-mode image for @loopover/miner (#4295). Build context = monorepo root:
2
+ # docker build -f packages/gittensory-miner/Dockerfile -t gittensory-miner:latest .
3
+ # SECRETS ARE NEVER BAKED: supply operator credentials at `docker run` time only.
4
+ # Persistent SQLite ledgers live on a mounted volume (default GITTENSORY_MINER_CONFIG_DIR=/data/miner).
5
+
6
+ ARG GITTENSORY_VERSION=
7
+
8
+ # --- build: workspace install + engine compile + miner syntax check ----------------------------
9
+ FROM public.ecr.aws/docker/library/node:24-slim AS build
10
+ WORKDIR /app
11
+ # Full source BEFORE `npm ci`: npm workspaces only symlinks packages that already exist on disk.
12
+ # Same ordering fix as the root gittensory-api Dockerfile — @loopover/engine must be
13
+ # present when `npm ci` runs or gittensory-miner's workspace dependency cannot resolve.
14
+ COPY . .
15
+ RUN npm ci --ignore-scripts
16
+ RUN npm --workspace @loopover/engine run build
17
+ RUN npm --workspace @loopover/miner run build
18
+ RUN npm prune --omit=dev --ignore-scripts
19
+
20
+ # --- runtime: non-root CLI image with a mounted config volume -----------------------------------
21
+ FROM public.ecr.aws/docker/library/node:24-slim AS runtime
22
+ WORKDIR /app
23
+ ARG GITTENSORY_VERSION=
24
+ ENV NODE_ENV=production \
25
+ GITTENSORY_MINER_CONFIG_DIR=/data/miner \
26
+ GITTENSORY_MINER_VERSION=${GITTENSORY_VERSION} \
27
+ PATH=/app/node_modules/.bin:$PATH
28
+ COPY --from=build --chown=node:node /app/node_modules ./node_modules
29
+ COPY --from=build --chown=node:node /app/packages/gittensory-miner ./packages/gittensory-miner
30
+ COPY --from=build --chown=node:node /app/packages/gittensory-engine ./packages/gittensory-engine
31
+ RUN mkdir -p /data/miner && chown -R node:node /data
32
+ USER node
33
+ VOLUME ["/data/miner"]
34
+ # No HEALTHCHECK: the miner is a batch/CLI workload (`docker run … gittensory-miner <cmd>`), not a
35
+ # long-running HTTP service — there is no steady-state endpoint to probe unless an operator wraps
36
+ # the container in their own supervising loop.
37
+ ENTRYPOINT ["gittensory-miner"]
38
+ CMD ["doctor"]
package/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # @loopover/miner
2
+
3
+ Foundation CLI for the local Gittensory miner runtime.
4
+
5
+ This package is the future home of the autonomous discover → analyze → plan → prepare → create → manage miner workflow. In this foundation phase it provides the package scaffold, a minimal CLI surface for `--help` and `--version`, and a non-blocking npm registry version nudge on startup.
6
+
7
+ ## Status
8
+
9
+ Current scope is intentionally small:
10
+
11
+ - workspace package wiring
12
+ - CLI entry point
13
+ - `--help` and `version` commands
14
+ - startup npm version nudge (override with `--no-update-check` or `GITTENSORY_MINER_NO_UPDATE_CHECK=1`)
15
+
16
+ Environment variables read by the miner are documented in [`docs/env-reference.md`](docs/env-reference.md).
17
+ Regenerate that file with `npm run miner:env-reference` from the repo root after adding or removing env reads.
18
+
19
+ Config precedence (`.gittensory-miner.yml` vs operator env vs CLI flags) is documented in
20
+ [`docs/config-precedence.md`](docs/config-precedence.md).
21
+
22
+ Real miner commands land in follow-up issues.
23
+
24
+ The package also includes the first metadata-only discovery primitive: `fetchCandidateIssues` lists open issue
25
+ metadata across target repos, and `searchCandidateIssues` does the same from a GitHub issue-search query. Both
26
+ paths hard-skip repos whose `AI-USAGE.md` or `CONTRIBUTING.md` explicitly bans AI-generated PRs. They perform
27
+ GitHub GET requests only, never clone source, never upload source, and never write to GitHub.
28
+
29
+ The package also includes a metadata-only ranker: `rankCandidateIssues` composes deterministic engine signals
30
+ (potential, feasibility, lane fit, freshness, dup risk) and returns fan-out candidates sorted by `rankScore`.
31
+ It never clones source and never writes to GitHub.
32
+
33
+ Discovery is per-tenant, not github.com-specific (#4784): `lib/forge-config.js` (`resolveForgeConfig`) holds the
34
+ forge base URL, API version, request headers, repo path, search endpoint/qualifiers, user-agent, and credential env
35
+ var behind one resolver with gittensory's github.com values as the only defaults, so the fan-out targets another
36
+ forge unchanged. `gittensory-miner discover` surfaces `--api-base-url <url>` and `--token-env <VAR>` and forwards a
37
+ tenant goal spec to the ranker, printing `usedDefaultGoalSpec` so a fall-back to the built-in rubric is explicit
38
+ rather than silent. See [`docs/repo-agnostic-capability-audit.md`](docs/repo-agnostic-capability-audit.md) for the
39
+ #4780 audit this executes.
40
+
41
+ The package also includes repo stack auto-detection: `detectRepoStack` (`lib/stack-detection.js`) inspects an
42
+ already-cloned target repo's manifest / lockfile / config files and returns a structured description — language,
43
+ package manager, and the build / test / lint / format commands — for Node (npm/yarn/pnpm/bun), Python
44
+ (pip/poetry/pipenv/uv), Rust, Go, Maven, and Gradle. It is pure (injectable `existsSync` / `readFileSync`), never
45
+ throws, and per its acceptance criteria **fails closed** — a repo with no recognized manifest returns
46
+ `{ detected: false, reason }` and a command that can't be inferred without guessing stays `null`, rather than being
47
+ assumed. Detection only; wiring the description into the attempt prompt is the follow-up ([#4786](https://github.com/JSONbored/gittensory/issues/4786)). (#4785)
48
+
49
+ The package also includes an append-only governor decision ledger: `initGovernorLedger` / `appendGovernorEvent`
50
+ persist structured allow/deny/throttle/kill-switch outcomes in local SQLite for contributor audit. Insert-only —
51
+ no enforcement wiring yet. (#2328)
52
+
53
+ The package also includes a real, persisted governor pause/resume control surface: `gittensory-miner governor
54
+ pause [--reason <text>]` / `governor resume` / `governor status` toggle a `paused`/`reason`/`pausedAt` flag on
55
+ governor-state.js's existing scalar-state row, and `loop-cli.js`'s iteration loop checks it at the same two
56
+ boundaries as the kill switch (before the first cycle and at the top of every subsequent one) — pausing mid-run
57
+ stops the loop before its next cycle claims anything, and resuming (clear the flag, re-invoke `loop`) continues
58
+ from the already-persisted queue/run state exactly where it left off. Distinct from the read-only kill switch
59
+ (env/YAML inputs this package never writes) and the one-way run-halt breaker (no resume path at all) — this is
60
+ the first genuinely operator/governor-writable stop/go control; the autonomous logic that decides *when* to pause
61
+ is separate, later-wave scope. (#4851)
62
+
63
+ The package also includes a local soft-claim ledger: `openClaimLedger` / `claimIssue` / `releaseClaim` /
64
+ `listActiveClaims` persist which issues this miner instance has claimed on this machine. The table is local
65
+ bookkeeping only — duplicate winners are adjudicated elsewhere via `@loopover/engine`. (#2291)
66
+
67
+ The package also includes an append-only event ledger: `initEventLedger` / `appendEvent` / `readEvents` persist
68
+ immutable miner-loop events in local SQLite for contributor audit. Insert-only — rows are never updated or
69
+ deleted. (#2322)
70
+
71
+ The package also records local PR outcomes: `recordPrOutcomeSnapshot` / `readPrOutcomes` write and reduce the
72
+ miner's OWN record of the outcomes of its OWN PRs (merged / closed, with an optional rejection-reason bucket) over
73
+ the append-only event ledger above. This is DISTINCT from the gittensory server's `recordPrOutcome`
74
+ (`src/review/outcomes-wire.ts`), which writes hosted-backend audit rows from the GitHub App's webhook stream — same
75
+ concept name, different codebase layer, no shared code (a laptop-mode miner may have no webhook relay at all). (#4274)
76
+
77
+ The package also includes an append-only prediction ledger: `initPredictionLedger` / `appendPrediction` /
78
+ `readPredictions` persist each predicted-gate verdict (conclusion / pack / readiness score + blocker/warning
79
+ codes, plus the producing `ENGINE_VERSION`) in local SQLite, so a later self-improve pass can score predictions
80
+ against realized outcomes. Insert-only. (#4263)
81
+
82
+ The package also includes the Phase 7 calibration runner: `runHistoricalReplayCalibrationCycle`
83
+ (`lib/calibration-run.js`) scores a completed historical-replay run with the deterministic objective-anchor scorer,
84
+ folds the composite into the engine's `computePhase7CalibrationLoop` combine alongside the existing `pr_outcome`
85
+ signal, and persists the combined snapshot as a `calibration_snapshot` event — queryable with
86
+ `gittensory-miner ledger list --type calibration_snapshot` or `readCalibrationSnapshots` /
87
+ `latestCalibrationSnapshot`. It measures and records only; acting on the metric (autonomy bumps, threshold tuning)
88
+ stays maintainer-only. See [`docs/miner-selfimprove-calibration.md`](docs/miner-selfimprove-calibration.md). (#4248)
89
+
90
+ `gittensory-miner manage status` now also folds each tracked repo's current discover/plan/prepare run state
91
+ (`run-state.js`) alongside its managed PR rows into a "run portfolio" view — `collectRunPortfolio` /
92
+ `renderRunPortfolioTable` — so a repo actively being discovered or planned shows up even with zero PRs yet.
93
+ Additive only: the existing `rows` JSON key and PR table are unchanged; `runPortfolio` is a new key printed
94
+ after the existing table. A real GUI dashboard surface is out of scope here — `apps/gittensory-miner-ui/` is
95
+ Phase 6 of the same roadmap tracker and hasn't been scaffolded yet. (#4279)
96
+
97
+ ## Local storage
98
+
99
+ Independent local SQLite stores back the commands above. Each keeps its own file, its own table(s), and (for most stores) its own env-var override — this is a DRY pass over their shared path-resolution/open boilerplate (`local-store.js`), not a merge into one database. (#4272)
100
+
101
+ See [`docs/env-reference.md`](docs/env-reference.md) for the full `GITTENSORY_MINER_*` / `MINER_*` env-var list.
102
+
103
+ | Store | File | Primary table(s) | Module | Env var override |
104
+ | --- | --- | --- | --- | --- |
105
+ | Laptop bootstrap | `laptop-state.sqlite3` | `laptop_meta` | `laptop-init.js` | `GITTENSORY_MINER_CONFIG_DIR` (path only) |
106
+ | Run state | `run-state.sqlite3` | `miner_run_state` | `run-state.js` | `GITTENSORY_MINER_RUN_STATE_DB` |
107
+ | Claim ledger | `claim-ledger.sqlite3` | `miner_claims` | `claim-ledger.js` | `GITTENSORY_MINER_CLAIM_LEDGER_DB` |
108
+ | Portfolio queue | `portfolio-queue.sqlite3` | `miner_portfolio_queue` | `portfolio-queue.js` | `GITTENSORY_MINER_PORTFOLIO_QUEUE_DB` |
109
+ | Event ledger | `event-ledger.sqlite3` | `miner_event_ledger` | `event-ledger.js` | `GITTENSORY_MINER_EVENT_LEDGER_DB` |
110
+ | Plan store | `plan-store.sqlite3` | `miner_plans` | `plan-store.js` | `GITTENSORY_MINER_PLAN_STORE_DB` |
111
+ | Governor ledger | `governor-ledger.sqlite3` | `governor_events` | `governor-ledger.js` | `GITTENSORY_MINER_GOVERNOR_LEDGER_DB` |
112
+ | Governor state | `governor-state.sqlite3` | `governor_scalar_state`, `governor_reputation_history`, `governor_own_submissions` | `governor-state.js` | `GITTENSORY_MINER_GOVERNOR_STATE_DB` |
113
+ | Attempt log | `attempt-log.sqlite3` | `attempt_log_events` | `attempt-log.js` | `GITTENSORY_MINER_ATTEMPT_LOG_DB` |
114
+ | Prediction ledger | `prediction-ledger.sqlite3` | `predictions` | `prediction-ledger.js` | `GITTENSORY_MINER_PREDICTION_LEDGER_DB` |
115
+ | Replay snapshot | `replay-snapshot.sqlite3` | `replay_snapshots` | `replay-snapshot.js` | `GITTENSORY_MINER_REPLAY_SNAPSHOT_DB` |
116
+ | Deny-hook synthesis | `deny-hook-synthesis.sqlite3` | `deny_rule_proposals` | `deny-hook-synthesis.js` | `GITTENSORY_MINER_DENY_HOOK_SYNTHESIS_DB` |
117
+ | Worktree allocator | `worktree-allocator.sqlite3` | `worktree_slots` | `worktree-allocator.js` | `GITTENSORY_MINER_WORKTREE_ALLOCATOR_DB` |
118
+ | Orb export | `orb-export.sqlite3` | `orb_export_meta` | `orb-export.js` | `GITTENSORY_MINER_ORB_EXPORT_DB` |
119
+ | Policy-doc cache | `policy-doc-cache.sqlite3` | `policy_doc_cache` | `policy-doc-cache.js` | `GITTENSORY_MINER_POLICY_DOC_CACHE_DB` |
120
+ | Policy-verdict cache | `policy-verdict-cache.sqlite3` | `policy_verdict_cache` | `policy-verdict-cache.js` | `GITTENSORY_MINER_POLICY_VERDICT_CACHE_DB` |
121
+
122
+ The policy-doc and policy-verdict caches are the only stores above that hold no miner state of their own — both are
123
+ pure optimization, and deleting either file only forces the next run to redo the work it would have skipped. The
124
+ policy-doc cache caches the last-known ETag + body of each target repo's fetched policy docs
125
+ (AI-USAGE.md/CONTRIBUTING.md) so a repeated `discover` revalidates them with a conditional GET (`If-None-Match`)
126
+ instead of re-downloading static content, spending no extra rate-limit budget when GitHub answers
127
+ `304 Not Modified` (#4842). The policy-verdict cache goes one step further: once a repo's deciding doc's ETag is
128
+ confirmed unchanged, it reuses the already-resolved AI-usage-policy verdict instead of re-resolving it from the
129
+ (identical) doc text (#4843).
130
+
131
+ Every store resolves its file the same way: the store-specific env var above, else `GITTENSORY_MINER_CONFIG_DIR`,
132
+ else `XDG_CONFIG_HOME` (falling back to `~/.config`), joined with `gittensory-miner/<file>`. Every store also opens
133
+ its file with `0700`/`0600` permissions and a shared `PRAGMA busy_timeout` so two instances on the same file
134
+ serialize writes instead of racing.
135
+
136
+ Opening a store through `local-store.js` also registers it with the CLI's crash-safety chokepoint
137
+ (`process-lifecycle.js`): the entrypoint calls `installCliSignalHandlers()` once at startup, so a `SIGINT`/`SIGTERM`
138
+ mid-run — or an uncaught exception / unhandled rejection — closes every still-open ledger cleanly and exits with a
139
+ conventional code (130/143 for signals, non-zero for a crash) instead of dying mid-write. A store's normal `close()`
140
+ unregisters itself first, so the happy path never double-closes and a long-running `loop` never accumulates stale
141
+ handles. Cleanup only — no command business logic is affected. (#4826)
142
+
143
+ The "PR portfolio" `manage status` renders is currently a **read-time join**, not a dedicated table:
144
+ `collectManageStatus` reads `portfolio-queue.js` rows (via the `pr:{number}` identifier convention) and joins them
145
+ against `event-ledger.js`'s free-form `manage_pr_update` JSON events at query time, on every read. Decision: keep
146
+ this as a read-time join for now; revisit a dedicated indexed table only if/when PR-portfolio reads become frequent
147
+ enough (e.g. a live-polling dashboard) that the per-read linear event-ledger scan becomes a measured bottleneck.
148
+
149
+ ## Install
150
+
151
+ See [`docs/miner-goal-spec.md`](docs/miner-goal-spec.md) for the `.gittensory-miner.yml` field reference and [`.gittensory-miner.yml.example`](../../.gittensory-miner.yml.example) at the repo root.
152
+
153
+ See [`docs/cross-repo-discovery-phase1.md`](docs/cross-repo-discovery-phase1.md) for the Phase 1 cross-repo discovery scope (re-scoped from [#1060](https://github.com/JSONbored/gittensory/issues/1060), paper trail for [#2299](https://github.com/JSONbored/gittensory/issues/2299)).
154
+
155
+ See [`docs/discovery-plane-operator-guide.md`](docs/discovery-plane-operator-guide.md) for the optional hosted discovery-index plane (opt-in default OFF; contrasts with Orb's opt-out-only export — [#4309](https://github.com/JSONbored/gittensory/issues/4309)).
156
+
157
+ See [`DEPLOYMENT.md`](DEPLOYMENT.md) for laptop vs fleet deployment.
158
+
159
+ See [`docs/operations-runbook.md`](docs/operations-runbook.md) for SQLite concurrency guarantees, corruption recovery, multi-process collision response, and post-upgrade ledger migration ([#4875](https://github.com/JSONbored/gittensory/issues/4875)).
160
+
161
+ ### Laptop-mode quickstart
162
+
163
+ Zero-infra local install — no Docker, Redis, or Postgres required:
164
+
165
+ ```sh
166
+ npm install -g @loopover/miner
167
+ gittensory-miner init
168
+ gittensory-miner doctor
169
+ gittensory-miner status
170
+ ```
171
+
172
+ `init` creates `~/.config/gittensory-miner/` (or `GITTENSORY_MINER_CONFIG_DIR` / `XDG_CONFIG_HOME` overrides) and a local `laptop-state.sqlite3` bootstrap file. Re-running `init` is idempotent. Pass `--verify-token` to make one authenticated GitHub API call up front and fail fast if `GITHUB_TOKEN` is invalid or missing repository access scopes. `doctor` reports Node, the state directory, SQLite readiness, and whether Docker is installed (informational only). Every local store already applies its own pending schema migrations automatically the moment some other command first opens it, but `migrate` lets an operator proactively bring every EXISTING store file up to date in one pass (e.g. right after upgrading) instead of relying on whichever command happens to touch a given store first; a store file that hasn't been created yet is reported as skipped, not created.
173
+
174
+ From a local checkout:
175
+
176
+ ```sh
177
+ npm install
178
+ npm --workspace @loopover/miner run build
179
+ npm link --workspace @loopover/miner
180
+ ```
181
+
182
+ ### Coding-agent driver configuration
183
+
184
+ The production driver is selected by `MINER_CODING_AGENT_PROVIDER`. The value is a comma-separated preference list:
185
+ the first configured name wins, unknown names are skipped, and an empty/unset list leaves production driver
186
+ construction fail-closed. See [`docs/coding-agent-driver.md`](docs/coding-agent-driver.md) for the interface-level
187
+ contract and provider behavior.
188
+
189
+ | Env var | Accepted values | Default / behavior |
190
+ | --- | --- | --- |
191
+ | `MINER_CODING_AGENT_PROVIDER` | `noop`, `claude-cli`, `codex-cli`, `agent-sdk` | Unset / empty means no provider is configured; the miner fails closed until a valid provider name is supplied. |
192
+ | `MINER_CODING_AGENT_CLAUDE_MODEL` | Any Claude model string accepted by the local `claude` CLI | Unset means `claude-cli` uses the CLI's own default model. Ignored by `noop`, `codex-cli`, and `agent-sdk`. |
193
+ | `MINER_CODING_AGENT_CODEX_MODEL` | Any Codex model string accepted by the local `codex` CLI | Unset means `codex-cli` uses the CLI's own default model. Ignored by `noop`, `claude-cli`, and `agent-sdk`. |
194
+ | `MINER_CODING_AGENT_TIMEOUT_MS` | Positive integer milliseconds | Unset or invalid falls back to the CLI driver's default wall-clock timeout of `120000` ms. Ignored by `noop` and `agent-sdk`. |
195
+
196
+ ### Recognizing a stale or missing coding-agent credential
197
+
198
+ When an attempt fails on a `claude-cli` / `codex-cli` provider, the CLI-subprocess driver folds the CLI's own output into a machine-readable `error` string on the attempt result. The credential/auth failure modes below map that exact string to a symptom and a concrete remediation — mirroring ORB's hosted-side [Recognizing a stale or missing credential](../../apps/gittensory-ui/src/routes/docs.self-hosting-ai-providers.tsx) table. Every string is emitted by [`cli-subprocess-driver.ts`](../gittensory-engine/src/miner/cli-subprocess-driver.ts); nothing here is speculative.
199
+
200
+ | Error string / pattern | Symptom | Remediation |
201
+ | --- | --- | --- |
202
+ | `claude_code_error_<status>` | The `claude` CLI ran but its `--output-format json` envelope reported `is_error: true` (e.g. `claude_code_error_invalid_api_key`) — the OAuth token is missing, rejected, or expired. `gittensory-miner doctor` reports the same condition up front as `not authenticated: set CLAUDE_CODE_OAUTH_TOKEN`. | Regenerate a long-lived token with `claude setup-token` and set `CLAUDE_CODE_OAUTH_TOKEN`, then retry. |
203
+ | `codex_no_auth` | `codex exec` exited non-zero with no structured error in its JSONL stdout and only the `Reading prompt from stdin...` banner on stderr — its `auth.json` credential is missing or expired. The driver appends its own remediation hint (`... auth.json missing or expired -- run codex auth to authenticate`). | Run `codex auth` to re-authenticate; the next attempt reads the refreshed `auth.json` with no further restart. |
204
+ | `<command>_exit_<code>` | Generic non-zero-exit fallback when neither structured parser matched (e.g. `codex_exit_1: ...`, `claude_exit_1: ...`). The driver appends the redacted stderr slice — an auth failure that neither parser recognized still surfaces here. | Read the appended detail; if it points at authentication, re-run `claude setup-token` or `codex auth` for the failing provider, otherwise address the reported error directly. |
205
+
206
+ ## Commands
207
+
208
+ ```sh
209
+ gittensory-miner --help
210
+ gittensory-miner help
211
+ gittensory-miner --version
212
+ gittensory-miner version
213
+ gittensory-miner init [--json] [--verify-token]
214
+ gittensory-miner status [--json]
215
+ gittensory-miner doctor [--json]
216
+ gittensory-miner migrate [--json]
217
+ gittensory-miner manage status [--json]
218
+ gittensory-miner manage poll <owner/repo> <pr#> [--branch <name>] [--json]
219
+ ```
220
+
221
+ ## MCP server
222
+
223
+ The package ships a second bin entry, `gittensory-miner-mcp`, a minimal [Model Context Protocol](https://modelcontextprotocol.io) stdio server that any MCP-compatible client can connect to:
224
+
225
+ ```sh
226
+ gittensory-miner-mcp
227
+ ```
228
+
229
+ It exposes these read-only tools:
230
+
231
+ - `gittensory_miner_ping` (#5153) — a health check returning a static `{ "status": "ok", "tool": "gittensory_miner_ping" }` object. Reads no AMS state, takes no arguments.
232
+ - `gittensory_miner_get_portfolio_dashboard` (#5155) — the per-repo portfolio-queue backlog dashboard: status counts (queued / in_progress / done), totals, and the oldest-queued age. Wraps `collectPortfolioDashboard()` (no new logic) — the same data `gittensory-miner queue dashboard --json` prints locally. Read-only, takes no arguments.
233
+ - `gittensory_miner_list_claims` (#5156) — lists the local claim ledger (repo, issue number, status, claimed-at, note) via `listClaims()`. Optional `repoFullName` / `status` filters pass through to the query. Read-only — exposes no claim/release mutation.
234
+ - `gittensory_miner_get_audit_feed` (#5158) — read-only, metadata-only event-ledger audit feed (`eventType`, `repoFullName`, `outcome`, `actor`, `detail`, `createdAt`). Wraps `collectEventLedgerAuditFeed()` with the same filters as `gittensory-miner ledger list` (`--repo`, `--since`, `--type`). Never returns `payload_json` or other raw ledger columns.
235
+
236
+ - `gittensory_miner_get_run_state` (#5160) — read-only per-repo run-state (`idle` / `discovering` / `planning` / `preparing`) via `getRunState` / `listRunStates`. Pass `repoFullName` for one repo (a null state means none recorded yet), or omit it to list all. The read-only analog of ORB's `gittensory_get_automation_state`; adds no state-set mutation.
237
+
238
+ - `gittensory_miner_list_plans` / `gittensory_miner_get_plan` (#5161) — read-only access to the persisted plan store (`planId`, plan DAG, status, `updatedAt`) via `listPlans` / `loadPlan`; `list_plans` takes an optional `status` filter, `get_plan` takes a `planId` and returns an explicit `{ planId, found: false }` for an unknown id. These read the store-backed AMS plan store — distinct from ORB's stateless `gittensory_plan_status` tool.
239
+
240
+ - `gittensory_miner_get_governor_decisions` (#5159) — read-only projection of the governor decision log (`id`, `ts`, `eventType`, `repoFullName`, `actionClass`, `decision`, `reason`), optionally filtered by `repoFullName`. The projection **excludes the sensitive `payload_json` column by construction** — `governor-ledger.js` reads it with an explicit named-column SELECT, never `SELECT *`.
241
+
242
+ - `gittensory_miner_status` (#5154) — read-only status + doctor diagnostics, returning `{ status, doctor }`: `status` = package/engine versions (and skew), node version, state-dir + config-file paths, and the resolved coding-agent driver (provider name, the model **env-var NAME** never its value, CLI-present boolean); `doctor` = the checks `gittensory-miner doctor` runs (Docker/CLI presence, config validity, …) as `{ name, ok, detail }`. Reuses `collectStatus` / `runDoctorChecks` so it can't drift from the CLI, and returns only names / booleans / paths — never any env-var value, token, or credential.
243
+
244
+ This completes the read-only AMS MCP tool surface (status, portfolio, claims, event-ledger, governor-ledger, run-state, plan-store).
245
+
246
+ ### Client config
247
+
248
+ `gittensory-mcp` (ORB's hosted contributor-workflow tools) and `gittensory-miner-mcp` (AMS's own local state-visibility tools above) can run as two separate stdio servers in the same MCP client session — useful for a dual-role operator running both ORB and AMS on the same box. Generate ORB's half with `gittensory-mcp init-client --print claude` (see the [`@loopover/mcp` README](../gittensory-mcp/README.md#client-config)); `gittensory-miner-mcp` takes no flags, so its entry is just the bin name. Combined, a Claude Desktop / Claude Code style config looks like:
249
+
250
+ ```json
251
+ {
252
+ "mcpServers": {
253
+ "gittensory": {
254
+ "command": "gittensory-mcp",
255
+ "args": ["--stdio"]
256
+ },
257
+ "gittensory-miner": {
258
+ "command": "gittensory-miner-mcp",
259
+ "args": []
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
265
+ `gittensory` exposes ORB's hosted contributor-workflow tools (issue ranking, PR packet prep, decision packs). `gittensory-miner` exposes AMS's own local state-visibility tools listed above (portfolio dashboard, claims, audit feed, run state, plans) — a fully separate, 100% local tool surface with no shared code or network calls between the two. Both follow the same `gittensory_*` tool-naming convention (`gittensory_...` vs. `gittensory_miner_...`), but back onto different stores: ORB's tools read the hosted gittensory backend, AMS's tools read this machine's own local SQLite files (see [Local storage](#local-storage)) — a handful of AMS tools even name the ORB tool they mirror (e.g. `gittensory_miner_get_run_state` is the read-only analog of `gittensory_get_automation_state`) so the relationship is explicit at the point of use, not just here.
266
+
267
+ ## Version check
268
+
269
+ On every invocation the CLI starts an async npm registry lookup (5s timeout). When the installed package is behind `@loopover/miner@latest`, it prints a one-line upgrade command to stderr without blocking or failing the requested command. Set `GITTENSORY_NPM_REGISTRY_URL` to point at a mirror, same as `@loopover/mcp`.
@@ -0,0 +1,64 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { EventLedger } from "../lib/event-ledger.js";
3
+
4
+ /** The static, non-secret payload the gittensory_miner_ping tool always returns, independent of input. */
5
+ export const MINER_PING_STATUS: { status: "ok"; tool: "gittensory_miner_ping" };
6
+
7
+ export interface MinerMcpServerOptions {
8
+ /**
9
+ * Override the portfolio-queue store opener (defaults to the real on-disk store); injection seam for tests.
10
+ * Typed to the minimal read surface the dashboard tool uses, mirroring runPortfolioDashboard's own seam.
11
+ */
12
+ initPortfolioQueue?: () => { listQueue(repoFullName?: string | null): unknown[]; close(): void };
13
+ /**
14
+ * Override the claim-ledger opener (defaults to the real on-disk ledger); injection seam for tests. Typed to
15
+ * the minimal read surface the list-claims tool uses.
16
+ */
17
+ openClaimLedger?: () => {
18
+ listClaims(filter?: { repoFullName?: string | null; status?: string | null }): unknown[];
19
+ close(): void;
20
+ };
21
+ /** Override the clock used for the oldest-queued age (defaults to Date.now()); injection seam for tests. */
22
+ nowMs?: number;
23
+ /** Override the event-ledger opener (defaults to initEventLedger); injection seam for tests. */
24
+ initEventLedger?: () => EventLedger;
25
+ /**
26
+ * Override the run-state store opener (defaults to the real on-disk store); injection seam for tests. Typed to
27
+ * the minimal read surface the run-state tool uses (never setRunState).
28
+ */
29
+ initRunStateStore?: () => {
30
+ getRunState(repoFullName: string): unknown;
31
+ listRunStates(): unknown[];
32
+ close(): void;
33
+ };
34
+ /**
35
+ * Override the plan-store opener (defaults to the real on-disk store); injection seam for tests. Typed to the
36
+ * minimal read surface the plan tools use (never savePlan).
37
+ */
38
+ openPlanStore?: () => {
39
+ loadPlan(planId: string): unknown;
40
+ listPlans(filter?: { status?: string | null }): unknown[];
41
+ close(): void;
42
+ };
43
+ /**
44
+ * Override the governor-ledger opener (defaults to the real on-disk ledger); injection seam for tests. Typed
45
+ * to the minimal read surface the decisions tool uses (the payload-excluding readGovernorDecisions).
46
+ */
47
+ initGovernorLedger?: () => {
48
+ readGovernorDecisions(filter?: { repoFullName?: string | null }): unknown[];
49
+ close(): void;
50
+ };
51
+ /** Override the status reader (defaults to status.js's collectStatus); injection seam for tests. */
52
+ collectStatus?: () => unknown;
53
+ /** Override the doctor-checks reader (defaults to status.js's runDoctorChecks); injection seam for tests. */
54
+ runDoctorChecks?: () => unknown[];
55
+ }
56
+
57
+ /**
58
+ * Build the miner MCP server with its tools registered (gittensory_miner_ping,
59
+ * gittensory_miner_get_portfolio_dashboard, gittensory_miner_list_claims, gittensory_miner_get_audit_feed,
60
+ * gittensory_miner_get_run_state, gittensory_miner_list_plans, gittensory_miner_get_plan,
61
+ * gittensory_miner_get_governor_decisions, gittensory_miner_status). `options` supplies test injection seams;
62
+ * production callers pass nothing.
63
+ */
64
+ export function createMinerMcpServer(options?: MinerMcpServerOptions): McpServer;