@xpufx/paseo-forges 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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/client/active-forge.ts +47 -0
  4. package/client/board-alert.tsx +236 -0
  5. package/client/foreign-link.tsx +33 -0
  6. package/client/hook-queue-panel.tsx +412 -0
  7. package/client/issues-pill.tsx +1976 -0
  8. package/client/label-chip.tsx +116 -0
  9. package/client/linkifier.tsx +280 -0
  10. package/client/pill-label.ts +183 -0
  11. package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
  12. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
  13. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
  14. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
  15. package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
  16. package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
  17. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
  18. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
  19. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
  20. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
  21. package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
  22. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
  23. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
  24. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
  25. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
  26. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
  27. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
  28. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
  29. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
  30. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
  31. package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
  32. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
  33. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
  34. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
  35. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
  36. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
  37. package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
  38. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
  39. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  40. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  41. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  42. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  43. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  44. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  45. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  46. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  47. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  48. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  49. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  50. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  51. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  52. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  53. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  54. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  55. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  56. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  57. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  58. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  59. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  60. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  61. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  62. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  63. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  64. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  65. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  66. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  67. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  68. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  69. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  70. package/client/webhook-card.tsx +198 -0
  71. package/docs/specs/forge-workflow-gui.md +820 -0
  72. package/docs/workflow.md +339 -0
  73. package/examples/README.md +108 -0
  74. package/examples/hook-service/README.md +66 -0
  75. package/examples/hook-service/forge-hook.service +28 -0
  76. package/examples/hook-service/hook-server.mjs +265 -0
  77. package/examples/hook-service/hook.env.example +21 -0
  78. package/examples/labels/README.md +64 -0
  79. package/examples/labels/label-base.yaml +117 -0
  80. package/examples/skills/coding-agent/SKILL.md +262 -0
  81. package/examples/skills/coding-agent-fgjx/SKILL.md +271 -0
  82. package/examples/skills/orchestrator/SKILL.md +133 -0
  83. package/examples/skills/orchestrator-fgjx/SKILL.md +139 -0
  84. package/examples/tools/README.md +68 -0
  85. package/examples/tools/fgjx +464 -0
  86. package/package.json +38 -0
  87. package/paseo-plugin.json +4 -0
  88. package/server/forge-client.ts +484 -0
  89. package/server/forge-guard.ts +70 -0
  90. package/server/git-origin.ts +70 -0
  91. package/server/hook-queue.ts +127 -0
  92. package/server/issues.ts +542 -0
  93. package/server/settings.ts +52 -0
  94. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  95. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  96. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  97. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  98. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  99. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  100. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  101. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  102. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  103. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  104. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  105. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  106. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  107. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  108. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  109. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  110. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  111. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  112. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  113. package/shared/hook-queue.ts +140 -0
  114. package/shared/issues.ts +1750 -0
  115. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  116. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  117. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  118. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  119. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  120. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  121. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  122. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  123. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  124. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  125. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  126. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  127. package/shared/version.ts +2 -0
  128. package/shared/webhook.ts +223 -0
@@ -0,0 +1,339 @@
1
+ # The agent workflow around `forges`
2
+
3
+ This document describes the end-to-end loop that turns board activity in a
4
+ Forgejo/Gitea-family repo into work on an agent fleet, and back into comments,
5
+ commits, and label state on that board. It is written for an **adopter**: it
6
+ names the parts the `forges` plugin itself ships, the parts you must supply,
7
+ and the exact seams between them.
8
+
9
+ Everything here is generic. Hosts, repos, paths, unit names, tokens, and agent
10
+ identities are placeholders (`forge.example.com`, `your-org/your-repo`,
11
+ `/opt/forge-hook`, ...). Replace them with your own. The running copy of one
12
+ team's board is the worked example, not a supported configuration.
13
+
14
+ > New here? Start with [`../README.md`](../README.md) for install and settings,
15
+ > then [`../examples/README.md`](../examples/README.md) for the copy-and-adapt
16
+ > Skills. This file is the connective tissue between them.
17
+
18
+ ## 1. The loop at a glance
19
+
20
+ ```
21
+ forge.example.com Paseo daemon
22
+ ┌───────────────┐ webhook ┌──────────────────┐ message ┌──────────────┐
23
+ │ issues, PRs, │ ───────────▶ │ hook service │ ──────────▶ │ orchestrator │
24
+ │ comments, │ │ (systemd or │ `paseo │ agent │
25
+ │ label writes │ │ workspace svc) │ send` └──────┬───────┘
26
+ └───────▲───────┘ └──────────────────┘ │ dispatch
27
+ │ ▼
28
+ │ plugin RPCs (forge.set-label / forge.add-comment) ┌──────────┐
29
+ └────────────────────────────────────────────────────────── │ coding │
30
+ labels, comments, commits, envelope reports │ agents │
31
+ └──────────┘
32
+ ```
33
+
34
+ Four moving parts:
35
+
36
+ 1. **The forge** emits events (issue opened/labeled, comment posted, push, PR)
37
+ as webhooks. It also stores all durable state: issues, comments, labels,
38
+ commits.
39
+ 2. **The hook service** receives those events, authenticates them, and forwards
40
+ a human-readable summary to the Paseo daemon's agent transport.
41
+ 3. **The Paseo daemon + agents** do the work. The `forges` plugin gives the
42
+ agent and the operator in-client views of the board and RPCs to write back
43
+ (`forge.set-label`, `forge.add-comment`).
44
+ 4. **The board** is the source of truth for what is done, claimed, blocked, or
45
+ waiting on a human — expressed through scoped labels and envelope-stamped
46
+ comments.
47
+
48
+ The plugin covers step 3's *reading and writing* of the board. Steps 1, 2, and
49
+ 4 are adopter-supplied, with example material in [`../examples/`](../examples/).
50
+
51
+ ## 2. The hook service
52
+
53
+ The plugin never talks to the forge's webhook side. It only fetches and writes
54
+ issues through the Gitea-family `/api/v1`. Something must bridge the forge's
55
+ *events* into the daemon, because a new comment or label change is what wakes
56
+ an idle fleet.
57
+
58
+ A minimal bridge is one HTTP listener with two endpoints. The shipped example
59
+ skeleton is [`../examples/hook-service/hook-server.mjs`](../examples/hook-service/hook-server.mjs);
60
+ its contract is:
61
+
62
+ ### `POST /hook` — event delivery
63
+
64
+ - Authenticated with a **shared secret** supplied by the forge webhook config.
65
+ Accept any of: `Authorization: Bearer <secret>`, `x-webhook-secret: <secret>`,
66
+ or `X-Forgejo-Signature: <hmac-sha256-hex>` over the raw body. The secret is
67
+ server-side only and is never handed to agents.
68
+ - The body is the forge's webhook JSON. The service derives the routing key
69
+ from the payload's repository (`host/owner/repo`) — never from the working
70
+ directory, so one process can serve many repos.
71
+ - It renders a short human line and hands it to the daemon, e.g.:
72
+
73
+ ```
74
+ 🔔 Forgejo webhook incoming [issues:labeled] your-org/your-repo#42 Fix the thing (by alice) https://forge.example.com/your-org/your-repo/issues/42
75
+ ```
76
+
77
+ The `forges` plugin parses exactly this summary line (and an optional
78
+ `[forgejo-hook] {json}` envelope prefix) into a timeline card — see
79
+ `plugins/forges/shared/webhook.ts`. Keep the shape if you want the card.
80
+
81
+ ### `POST /orchestrate` — who owns this repo
82
+
83
+ A repo has at most one orchestrator at a time. The service keeps a small
84
+ per-repo state file recording the current orchestrator's agent id; the body is
85
+ `{ "agentId": "...", "repo": "host/owner/repo" }` (repo optional — derived from
86
+ the agent's checkout `origin` when omitted). Loopback callers need no secret so
87
+ a local agent can claim the role with a plain `curl`; off-box callers must
88
+ present the secret.
89
+
90
+ Agent **name/role labels are a projection for the UI** — the state file is the
91
+ authority. Treat the projection as observability, not as a lock.
92
+
93
+ ### Delivery and coalescing
94
+
95
+ - Messages queue **per repo** and are delivered one at a time, because a daemon
96
+ rejects a second send while an agent already has an active run.
97
+ - A short debounce coalesces a burst on the same `(repo, issue)` into one
98
+ digest. **Bypass the debounce** for slash-commands in comments and for
99
+ urgent labels (`priority/0-SOS`, `flag/stop-work`) so those never lag.
100
+ - With no orchestrator registered for a repo, the queue holds rather than
101
+ drops. Your service should log that state loudly; it is the most common
102
+ "nothing happened" cause.
103
+
104
+ ### Option A — systemd unit (host-wide, many repos)
105
+
106
+ Use this when the service runs once for the whole machine. The example unit is
107
+ [`../examples/hook-service/forge-hook.service`](../examples/hook-service/forge-hook.service)
108
+ with an environment file at
109
+ [`../examples/hook-service/hook.env.example`](../examples/hook-service/hook.env.example).
110
+ Install, edit the placeholders, then:
111
+
112
+ ```sh
113
+ sudo cp forge-hook.service /etc/systemd/system/
114
+ sudo cp hook.env.example /etc/forge-hook/hook.env # then edit
115
+ sudo systemctl daemon-reload
116
+ sudo systemctl enable --now forge-hook.service
117
+ journalctl -u forge-hook.service -f
118
+ ```
119
+
120
+ Point your forge repo's webhook at `http://127.0.0.1:8099/hook` with the
121
+ matching secret and the events you care about (`issues`, `issue_comment`,
122
+ `push`, `pull_request`, and a `ping` for setup). Keep the listener on loopback
123
+ unless you terminate TLS and authenticate in front of it.
124
+
125
+ ### Option B — workspace-scoped Paseo service (one checkout, one repo)
126
+
127
+ If you use Paseo workspaces, you can run the same script as a managed workspace
128
+ service instead of a system unit. Paseo's workspace config supports service
129
+ scripts, so a repo can declare:
130
+
131
+ ```jsonc
132
+ // paseo.json (workspace-scoped; paths are relative to the checkout)
133
+ {
134
+ "scripts": {
135
+ "hook": { "type": "service", "command": "node scripts/hook-server.mjs" }
136
+ }
137
+ }
138
+ ```
139
+
140
+ Paseo starts/stops it with the workspace and gives you its logs in-client. The
141
+ trade-off: it only runs while that workspace is up, and it is scoped to that
142
+ checkout. The host-wide systemd unit keeps running regardless of the client.
143
+ Pick one; running both against the same port and secret double-delivers.
144
+
145
+ ## 3. Label usage
146
+
147
+ The board is driven by **scoped labels**: a name containing `/` where the last
148
+ `/` separates a *scope* from a *value* (`state/1-wip`, `priority/2-normal`).
149
+ Labels created with `exclusive: true` are **mutually exclusive per scope**:
150
+ applying one evicts any existing label with the same scope, at the forge DB
151
+ level. No explicit remove is required for the happy path.
152
+
153
+ ### The scopes
154
+
155
+ | Scope | Values | Meaning |
156
+ | ------------ | ----------------------------------------------------- | ----------------------------------------- |
157
+ | `state/` | `0-triage` `1-wip` `2-review` `3-verify` `4-done` | Execution lifecycle |
158
+ | `priority/` | `0-SOS` `1-high` `2-normal` `3-low` `4-backburner` | Queue ordering / urgency |
159
+ | `attention/` | `0-orchestrator` `1-agent` `2-user` `3-ignore` | Who acts next (action token) |
160
+ | `spec/` | `0-needed` `1-checklist` `2-approved` | Pre-code shaping and approval |
161
+
162
+ A generic, apply-able seed for all four scopes lives in
163
+ [`../examples/labels/label-base.yaml`](../examples/labels/label-base.yaml).
164
+ Apply it as a Forgejo label template, or create the labels in the UI/API; the
165
+ plugin does not install labels for you.
166
+
167
+ ### How the plugin uses them
168
+
169
+ - **Live vocabulary, not a hardcoded list.** The plugin derives the scopes
170
+ present on the board (`liveScopesFromIssues` in
171
+ `plugins/forges/shared/issues.ts`) and only falls back to the canonical names
172
+ above. A foreign board degrades gracefully; an unknown scope shows up rather
173
+ than erroring.
174
+ - **Chips are the vocabulary made visible.** Every label renders through
175
+ `LabelChip` as two segments — scope + value — colored from the forge label
176
+ color. The chips and the sort tuple
177
+ (`priorityRank`, then `stateRank`, then recency) are only meaningful if the
178
+ labels are.
179
+ - **Writes add, and also remove a same-scope mate.** The plugin sends the new
180
+ label plus an explicit removal of any existing label in the same scope, so
181
+ the result is correct even on boards whose scope names differ from the
182
+ canonical set (where DB-level exclusivity alone would not evict).
183
+
184
+ ### The cold-start gap (read this)
185
+
186
+ **The operator may apply no labels at all.** A ticket can arrive with an empty
187
+ label set, and that is neither an error nor a signal that the ticket is out of
188
+ scope. The plugin then shows the issue with no scope chips, and the board sort
189
+ falls back to defaults (`priority/2-normal`, unranked state).
190
+
191
+ Downstream automation must not assume a populated taxonomy:
192
+
193
+ - Missing labels are **advisory**, not a gate. Never skip a ticket solely
194
+ because it lacks `state/` or `spec/`.
195
+ - The first agent to touch a ticket should **read it, infer the state, and set
196
+ the labels itself** — that is how the taxonomy gets bootstrapped.
197
+ - Deterministic board queries (rank/filter) treat unlabeled issues as normal
198
+ priority with no state rank; surface them, don't hide them.
199
+
200
+ ## 4. Agent responsibilities
201
+
202
+ The workflow assumes two roles. One agent can hold both, but keeping them
203
+ separate is what stops a fleet from colliding.
204
+
205
+ ### Orchestrator
206
+
207
+ - **Triage.** Watch the `attention/` signal and incoming comments. Turn vague
208
+ operator input ("build's failing, fix it") into a concrete ticket with a
209
+ `- [ ]` checklist and boundary constraints.
210
+ - **Dispatch.** One ticket, one worker, isolated by package directory. Never
211
+ let two workers edit the same checkout at once; queue instead of colliding.
212
+ Instruct the worker to claim with a stamped comment and `state/1-wip`.
213
+ - **Pre-flight.** Before presenting anything to the operator: work committed and
214
+ pushed, tests/typechecks green, and the running daemon actually executing the
215
+ new commit (helper build fresh, plugin version stamp matching HEAD). Never
216
+ present unverified work.
217
+ - **Verify (non-binding).** `state/3-verify` is "needs a human look", not "block
218
+ forever". If the operator doesn't test, resolve with narration (requeue,
219
+ close as superseded, or verify by proxy) instead of deadlocking.
220
+
221
+ ### Coding agent
222
+
223
+ - **Discover and claim.** Read the *entire* ticket and *entire* comment thread
224
+ first — scope is often amended in comments. Verify no peer already claimed it,
225
+ then post a claim comment and set `state/1-wip`.
226
+ - **Page every list read.** Issue lists, search results, label lists, and
227
+ comment lists are collections: each call returns one page, and the default
228
+ page size is **server-defined and can change**. Pass `limit`/`page` — or follow
229
+ `Link`/`X-Total-Count` — until a short page comes back before concluding "no
230
+ results" or claiming you have read the whole thread. One unpaged call is never
231
+ the full set; a plugin surface that lists results should page internally
232
+ rather than render a truncated set (#189). See the worked example in the
233
+ [examples README](../examples/README.md#paged-reads-issue--search--label--comment-lists)
234
+ (unpaged `fgj label list` returned 30 of 59 labels, #197).
235
+ - **Implement.** Work quietly in your own checkout. Stage explicit paths only;
236
+ never `git add -A` in a shared tree.
237
+ - **Hand off.** Run tests/typecheck, commit, push to the forge, then post a
238
+ structured report (what changed, commit SHA, test result) and advance state —
239
+ `state/2-review` for an internal review, or `state/3-verify` for operator
240
+ testing.
241
+ - **Stamp comments.** Because many agents may share one forge account, every
242
+ comment should carry a self-identification footer (an "agent envelope") so
243
+ attribution survives. That tooling is external to the plugin — see §6.
244
+
245
+ Missing labels in no way block either role; see the cold-start gap above.
246
+
247
+ ## 5. Skills
248
+
249
+ `plugins/forges/examples/skills/` ships the workflow above as **two skill
250
+ sets**, adapted for publication. Each set has a coding-agent and an orchestrator
251
+ counterpart, and they differ only in how they touch the board:
252
+
253
+ | Zero-dependency (plugin + embedded `/api/v1`) | CLI (`fgjx` over `fgj`) |
254
+ | --------------------------------------------- | ---------------------------------------- |
255
+ | [`examples/skills/coding-agent/SKILL.md`](../examples/skills/coding-agent/SKILL.md) | [`examples/skills/coding-agent-fgjx/SKILL.md`](../examples/skills/coding-agent-fgjx/SKILL.md) |
256
+ | [`examples/skills/orchestrator/SKILL.md`](../examples/skills/orchestrator/SKILL.md) | [`examples/skills/orchestrator-fgjx/SKILL.md`](../examples/skills/orchestrator-fgjx/SKILL.md) |
257
+
258
+ - The **zero-dependency** set drives the board through the plugin's own
259
+ surfaces and its embedded Gitea-family `/api/v1` client. Nothing needs to be
260
+ installed; no forge CLI is present.
261
+ - The **CLI** set uses the vendored example wrapper
262
+ [`examples/tools/fgjx`](../examples/tools/fgjx), which is a thin passthrough
263
+ shim over **your** `fgj`: `fgj` owns the host URL + token and performs the raw
264
+ `/api/v1` calls, while `fgjx` adds label resolution, table/view niceties, and
265
+ optional envelope stamping. See
266
+ [`examples/tools/README.md`](../examples/tools/README.md). The envelope tool
267
+ is optional — without one, `fgjx` emits a generic machine-authored footer.
268
+
269
+ They are **examples, not drop-ins**. Both carry a warning banner and use
270
+ placeholders (`forge.example.com`, `your-org/your-repo`). Before adopting one:
271
+
272
+ 1. Copy it into your skills directory (for Paseo: `.agents/skills/<name>/`).
273
+ 2. Rewrite the host, repo, and command/tool names to match your setup. If you
274
+ choose the CLI set, supply `fgj` and (optionally) an envelope tool; if you
275
+ can't, use the zero-dependency set and the plugin surfaces / direct forge API
276
+ calls.
277
+ 3. Align the label and slash-command vocabulary with your seed YAML. If you
278
+ only seed the four scopes in §3, drop or define any extra labels a Skill
279
+ mentions (the Orchestrator examples refer to a `flag/stop-work` circuit
280
+ breaker).
281
+ 4. Cross-link the adapted Skills back to your board conventions so the next
282
+ agent inherits them.
283
+
284
+ ## 6. Includes vs does-not-include
285
+
286
+ What the `forges` plugin provides versus what you must supply:
287
+
288
+ | Provided by the plugin | Supplied by the adopter |
289
+ | ------------------------------------------------------------- | ---------------------------------------------------------- |
290
+ | Issue list/detail, comments, scoped label chips | Hook service (systemd unit **or** workspace-scoped service)|
291
+ | Embedded `/api/v1` fetch client; daemon-side per-host tokens | Label base (seed YAML applied to the repo) |
292
+ | Live label vocabulary derived from the board | Skills (adapt the examples to your tooling) |
293
+ | Two example skill sets (plugin/`/api/v1` and `fgjx`) + README | `fgj`, only if you adopt the CLI skill set |
294
+ | Bundled, sanitized `examples/tools/fgjx` wrapper | Envelope tool for comment stamps (optional, CLI set only) |
295
+ | `forge.set-label` / `forge.add-comment` write RPCs | The forge itself (Forgejo/Gitea-family host + repo) |
296
+ | Webhook timeline card parser (`shared/webhook.ts`) | Webhook secret/config on the forge repo |
297
+
298
+ Explicit non-goals (do not expect the plugin to do these):
299
+
300
+ - **No webhook receiver.** The plugin is not an HTTP endpoint and does not
301
+ register webhooks for you.
302
+ - **No label install.** The optional label-set install is operator-gated and
303
+ excluded from the release surface; seed labels yourself.
304
+ - **No envelope tooling shipped.** Comment attribution/self-stamping is
305
+ external. The bundled `examples/tools/fgjx` can call an adopter-supplied
306
+ envelope tool for `--envelope`, but the plugin ships none and the core
307
+ workflow never needs one.
308
+ - **No orchestration.** Dispatch, pre-flight, and verify are agent behaviour
309
+ encoded in the example Skills, not plugin features.
310
+ - **No CLI dependency in the plugin runtime.** The plugin works on a machine
311
+ that has never had a forge CLI installed. The `fgjx` wrapper is a bundled
312
+ *example* under `examples/tools/` for adopters who already run `fgj`; it is
313
+ never loaded by the plugin.
314
+
315
+ ### 6.1 Minimum token scopes (write-enabled forges)
316
+
317
+ A token the host accepts is not automatically write-capable. Edit surfaces
318
+ (labels, comments, label-set install) require the repo to report write
319
+ permission; the plugin reads that from the repo response
320
+ (`permissions.push`/`admin` on Forgejo/GitHub, `access_level >= 30` on GitLab)
321
+ and otherwise falls back to token validity. A valid but under-scoped token is
322
+ reported as **"token lacks write scope"**, not rejected.
323
+
324
+ Forgejo/Gitea PAT: `read:user` (identity probe), `read:repository` (repo
325
+ metadata), and `write:issue` (issues, comments, labels). Add
326
+ `write:repository` if label management still 403s. GitHub needs `read:user`
327
+ plus `repo`; GitLab needs `read_user`, `read_api`, and `api`.
328
+
329
+ ## 7. Adopter checklist
330
+
331
+ - [ ] Create the board labels from `examples/labels/label-base.yaml`.
332
+ - [ ] Stand up the hook service (systemd unit or workspace service) and point a
333
+ forge webhook at it with a shared secret.
334
+ - [ ] Confirm deliveries arrive (send a `ping` event) and an orchestrator is
335
+ registered for the repo.
336
+ - [ ] Copy and adapt `examples/skills/` into your skills directory.
337
+ - [ ] Add an envelope/attribution step if multiple agents share one account.
338
+ - [ ] Install the plugin, set the per-host token in Settings, and verify the
339
+ issue pill renders your board.
@@ -0,0 +1,108 @@
1
+ # Examples — adapt, do not drop in
2
+
3
+ This directory ships the maintainers' own agent workflow as **examples** so a
4
+ team adopting the `forges` plugin has a working starting point. They are not a
5
+ supported configuration and they are not drop-in: every host, repo, CLI tool,
6
+ label, and convention below reflects one team's setup.
7
+
8
+ **You must adjust them to fit your own workflow.** The end-to-end picture — how
9
+ webhooks reach the daemon, how labels drive the board, and what each agent role
10
+ owns — is in [`../docs/workflow.md`](../docs/workflow.md).
11
+
12
+ ## What is here
13
+
14
+ - `hook-service/` — a sanitized systemd unit, a generic webhook bridge script
15
+ (`hook-server.mjs`), and an environment-file example. One listener with
16
+ `POST /hook` and `POST /orchestrate`; placeholders only. See its `README.md`.
17
+ - `labels/label-base.yaml` — a generic, apply-able seed for the scoped labels
18
+ the workflow uses (`state/`, `priority/`, `attention/`, `spec/`, plus an
19
+ optional `flag/stop-work`). Apply it as a Forgejo label template or create
20
+ the labels via the API/UI.
21
+ - `tools/fgjx` — a sanitized, vendored copy of one team's `fgjx` wrapper. It is
22
+ a passthrough shim over the `fgj` CLI (label resolution, table/view niceties,
23
+ optional envelope stamping) and **requires the adopter's own `fgj`**, pointed
24
+ at their forge; it fails loudly without it. `tools/README.md` explains the
25
+ fgj-vs-fgjx split and the optional envelope tool.
26
+ - `skills/coding-agent/SKILL.md` — the **zero-dependency** variant: workflow,
27
+ issue conventions, and reporting standards, driving the board through the
28
+ plugin's own surfaces and embedded Gitea-family `/api/v1` client. No forge CLI.
29
+ - `skills/coding-agent-fgjx/SKILL.md` — the **CLI** variant of the same
30
+ workflow, using the vendored `fgjx` (hence an adopter-supplied `fgj`) and an
31
+ optional envelope self-stamp so comment attribution survives across many
32
+ agents sharing one account.
33
+ - `skills/orchestrator/SKILL.md` and `skills/orchestrator-fgjx/SKILL.md` —
34
+ dispatch, pre-flight, and human-in-the-loop signoff rules for the agent
35
+ coordinating the others, split the same way (plugin + `/api/v1` vs `fgjx`).
36
+
37
+ Every file carries an `EXAMPLE` warning at the top. To use the Skills, copy a
38
+ `SKILL.md` into your own skills directory (for Paseo: `.agents/skills/<name>/`)
39
+ and rewrite the placeholders (`forge.example.com`, `your-org/your-repo`,
40
+ `fgj`/`fgjx`, `envelope-tool`) to match your tooling. If you have no forge CLI,
41
+ start from the `coding-agent`/`orchestrator` variants — they need none.
42
+
43
+ ## The label taxonomy the skills assume
44
+
45
+ The scoped label set below is what the skills and the plugin's label chips use,
46
+ and what `labels/label-base.yaml` seeds. The plugin no longer ships a label-set
47
+ install in the UI, so create these labels on your repo yourself (with the seed
48
+ file, by hand, or via the Forgejo API):
49
+
50
+ | Scope | Labels |
51
+ | ----------- | ------------------------------------------------------------- |
52
+ | `state/` | `0-triage`, `1-wip`, `2-review`, `3-verify`, `4-done` |
53
+ | `priority/` | `0-SOS`, `1-high`, `2-normal`, `3-low`, `4-backburner` |
54
+ | `attention/`| `0-orchestrator`, `1-agent`, `2-user`, `3-ignore` |
55
+ | `spec/` | `0-needed`, `1-checklist`, `2-approved` |
56
+
57
+ Gitea-family scoped labels are exclusive per scope, so applying one evicts the
58
+ previous mate in the same scope. The plugin's label chips rely on this
59
+ vocabulary to advance `state/`, `priority/`, `attention/`, and `spec/`.
60
+
61
+ The operator may apply **no labels at all**; that is the normal starting state,
62
+ not an error. Automation must treat missing labels as advisory and bootstrap the
63
+ taxonomy on first touch (see `docs/workflow.md` §3).
64
+
65
+ ## Paged reads (issue / search / label / comment lists)
66
+
67
+ Every list either skill set reads is **paged**. A Gitea-family collection
68
+ endpoint returns one page per call, and the default page size is
69
+ **server-defined and can change** — on both the plugin's embedded `/api/v1`
70
+ client and the `fgj`/`fgjx` CLI. This applies to the **issue list, search
71
+ results, label list, and comment list**.
72
+
73
+ - **Always page**, then aggregate: pass `limit`/`page` and keep going while a
74
+ full page comes back, or follow the `Link` header / `X-Total-Count` when the
75
+ server sends them.
76
+ - **Never treat page 1 as complete**, and never conclude "no results" — or
77
+ "labels not found" — from one unpaged call.
78
+ - If a forges UI surface lists results, it should **page internally** rather
79
+ than render a truncated set (#189).
80
+
81
+ Worked example: an unpaged `fgj label list` returned **30 of 59** labels, which
82
+ surfaced as false "labels not found" errors (#197). Both skill sets carry the
83
+ same caveat at their list paths.
84
+
85
+ ## Two skill sets, and what they need
86
+
87
+ The plugin itself assumes no CLI. The Skills come in two variants so you can
88
+ pick the one that fits your host:
89
+
90
+ - **Plugin + embedded `/api/v1`** — `skills/coding-agent` and
91
+ `skills/orchestrator`. Zero external dependencies: list and read issues
92
+ through the issues pill/modal, change scoped labels through the Labels tab /
93
+ label chips, post through the quick-comment composer, all backed by the
94
+ daemon-side `/api/v1` client. Scripting can call the forge API directly with
95
+ your own token.
96
+ - **`fgjx` CLI** — `skills/coding-agent-fgjx` and `skills/orchestrator-fgjx`.
97
+ The richer path, using the bundled `tools/fgjx`. That wrapper needs **your**
98
+ `fgj` (the authenticated transport: it owns the host URL + token and does the
99
+ raw `/api/v1` calls) and, **optionally**, an envelope tool for comment
100
+ stamps. Neither ships with the plugin.
101
+
102
+ Both variants expose the same board: the plugin's equivalents for a CLI step
103
+ are the issues pill/modal (list/detail), the Labels tab (scoped label changes),
104
+ and the quick-comment composer (steering). Map the CLI steps onto those (or the
105
+ plugin's `forge.board-overview` / `forge.issue-detail` / `forge.set-label` /
106
+ `forge.add-comment` RPCs) when you have no CLI. See
107
+ [`../docs/workflow.md`](../docs/workflow.md) §6 for the full includes-vs-excludes
108
+ table.
@@ -0,0 +1,66 @@
1
+ # Hook service (example)
2
+
3
+ The `forges` plugin reads and writes the forge API, but it does not receive
4
+ webhooks. Something has to turn "a label changed on `your-org/your-repo`" into a
5
+ message the Paseo daemon delivers to an agent. This directory is a **sanitized
6
+ skeleton** for that bridge: systemd unit, environment file, and a generic
7
+ script. See [`../../docs/workflow.md`](../../docs/workflow.md) §2 for the full
8
+ contract.
9
+
10
+ > **Not a supported configuration.** Replace every placeholder and review the
11
+ > TODOs in `hook-server.mjs` (secret rotation, durable queueing, coalescing,
12
+ > payload validation) before relying on it.
13
+
14
+ ## Files
15
+
16
+ | File | Purpose |
17
+ | ------------------- | -------------------------------------------------------------- |
18
+ | `hook-server.mjs` | Generic listener: `POST /hook` + `POST /orchestrate` |
19
+ | `forge-hook.service`| systemd unit for a host-wide deploy (many repos) |
20
+ | `hook.env.example` | Environment placeholders consumed by the script |
21
+
22
+ ## Option A — systemd (host-wide)
23
+
24
+ ```sh
25
+ sudo install -d -o forge-hook -g forge-hook /opt/forge-hook /etc/forge-hook /var/lib/forge-hook
26
+ sudo cp hook-server.mjs /opt/forge-hook/
27
+ sudo cp hook.env.example /etc/forge-hook/hook.env # then edit
28
+ sudo chmod 600 /etc/forge-hook/hook.env
29
+ sudo cp forge-hook.service /etc/systemd/system/
30
+ sudo systemctl daemon-reload
31
+ sudo systemctl enable --now forge-hook.service
32
+ systemctl status forge-hook.service
33
+ ```
34
+
35
+ Then point the repo's webhook at `http://127.0.0.1:8099/hook`, set the same
36
+ secret, and send a `ping` event to verify delivery.
37
+
38
+ ## Option B — Paseo workspace service
39
+
40
+ Run the same script as a managed workspace service instead of a host unit:
41
+
42
+ ```jsonc
43
+ // paseo.json in the workspace/checkout
44
+ {
45
+ "scripts": {
46
+ "hook": { "type": "service", "command": "node plugins/forges/examples/hook-service/hook-server.mjs" }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Paseo starts/stops it with the workspace and surfaces its logs. It only runs
52
+ while that workspace is up; a host-wide unit does not. Run one, not both.
53
+
54
+ ## Registering an orchestrator
55
+
56
+ Until a repo has an orchestrator, deliveries for it are held. Register one from
57
+ the agent's own checkout over loopback (no secret needed):
58
+
59
+ ```sh
60
+ curl -s -X POST http://127.0.0.1:8099/orchestrate \
61
+ -H 'content-type: application/json' \
62
+ -d '{"agentId":"<your-agent-id>","repo":"forge.example.com/your-org/your-repo"}'
63
+ ```
64
+
65
+ Off-box callers must send the shared secret. The `repo` field is a
66
+ forge-qualified `host/owner/repo` key.
@@ -0,0 +1,28 @@
1
+ [Unit]
2
+ Description=Forge webhook receiver (forges plugin bridge)
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=simple
8
+ # Replace with the unprivileged account that owns the secret and state dir.
9
+ User=forge-hook
10
+ Group=forge-hook
11
+ WorkingDirectory=/opt/forge-hook
12
+ # Copy hook.env.example to /etc/forge-hook/hook.env and edit the placeholders.
13
+ EnvironmentFile=/etc/forge-hook/hook.env
14
+ ExecStart=/usr/bin/node /opt/forge-hook/hook-server.mjs
15
+ Restart=on-failure
16
+ RestartSec=5
17
+
18
+ # Hardening. Keep this after you have confirmed the paths exist.
19
+ NoNewPrivileges=true
20
+ PrivateTmp=true
21
+ ProtectSystem=strict
22
+ ProtectHome=true
23
+ # The state dir must be writable; match HOOK_STATE_DIR in the environment file.
24
+ ReadWritePaths=/var/lib/forge-hook
25
+ StateDirectory=forge-hook
26
+
27
+ [Install]
28
+ WantedBy=multi-user.target