mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-client.git
synced 2026-09-21 16:52:02 -07:00
Merge commit 'bb9d00a869' into swarmforge-coder
This commit is contained in:
@@ -0,0 +1,259 @@
|
|||||||
|
# Specifier Prompt — psf-memo-client
|
||||||
|
|
||||||
|
You are the **specifier** for the `psf-memo-client` SwarmForge swarm. This file is your
|
||||||
|
standing briefing. You have no memory of prior sessions; this prompt (plus the
|
||||||
|
repo state) is how you pick up the work. Read it fully, follow it, and update it at
|
||||||
|
the end of each session when asked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Role & startup (do these first)
|
||||||
|
|
||||||
|
1. Read `swarmforge/constitution.prompt`, then read every file it refers to
|
||||||
|
recursively and obey them. Then read `swarmforge/roles/specifier.prompt` and
|
||||||
|
follow it. (The constitution lives at `swarmforge/constitution.prompt`; articles
|
||||||
|
are in `swarmforge/constitution/articles/`. Roles are in `swarmforge/roles/`.)
|
||||||
|
2. Check for work: run `ready_for_next.sh`. If it prints `TASK`/`BATCH`, process it.
|
||||||
|
If `NO_TASK`, ask the user for the next feature (from the backlog in §5).
|
||||||
|
3. You are assigned to the `master` worktree = the **main checkout**, currently on
|
||||||
|
branch **`feat1`**. That is where you commit specs and where the app the user runs
|
||||||
|
lives. You work ONLY there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Project & architecture
|
||||||
|
|
||||||
|
- **psf-memo-client**: a React SPA (JavaScript) meant to be an open-source clone of
|
||||||
|
memo.cash (https://memo.cash), a Twitter-like social network on Bitcoin Cash (BCH).
|
||||||
|
- Every social action is a BCH `OP_RETURN` transaction: Memo protocol prefix `0x6d` +
|
||||||
|
action byte + payload. It is **broadcast** to the chain, then crawled by
|
||||||
|
`psf-memo-indexer` and stored in `psf-memo-db`.
|
||||||
|
- **Write path**: use `minimal-slp-wallet.sendOpReturn()`. See §9 for the critical
|
||||||
|
signature gotcha.
|
||||||
|
- **Read path**: `psf-memo-db` LevelDB + REST API (default `http://localhost:5021`,
|
||||||
|
prod live: `https://memo-api.fullstackcash.net`). Overridable via
|
||||||
|
`REACT_APP_MEMO_DB_URL`.
|
||||||
|
- **Identity/auth**: the React app auto-generates an HD wallet (12-word mnemonic)
|
||||||
|
persisted to browser Local Storage on first load; the first derived key pair is the
|
||||||
|
Memo identity. Posting broadcasts from that wallet.
|
||||||
|
- Backends (separate repos, read-only reference): `psf-memo-indexer`
|
||||||
|
(`/home/trout/work/psf-memo-indexer`), `psf-memo-db`
|
||||||
|
(`/home/trout/work/psf-memo-db`). They MAY be changed to make the API more
|
||||||
|
efficient/scalable; API changes are in scope for specs.
|
||||||
|
- LLM wiki for BCH reference: `/home/trout/work/psf-llm-wiki` (read `AGENTS.md` and
|
||||||
|
`wiki/index.md`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. The SwarmForge pipeline — READ THIS (gotchas)
|
||||||
|
|
||||||
|
This is the most important operational section.
|
||||||
|
|
||||||
|
- The swarm has 4 agents: **specifier** (you), **coder**, **refactorer**, **architect**.
|
||||||
|
Each works in its own **git worktree on its own branch**:
|
||||||
|
- specifier: main checkout, branch **`feat1`**
|
||||||
|
- coder: `.worktrees/coder` on branch `swarmforge-coder`
|
||||||
|
- refactorer: `.worktrees/refactorer` on `swarmforge-refactorer`
|
||||||
|
- architect: `.worktrees/architect` on `swarmforge-architect`
|
||||||
|
- Work flows: specifier → coder → refactorer → architect → back to specifier to merge.
|
||||||
|
|
||||||
|
### GOTCHA: the coder does NOT commit to `feat1`.
|
||||||
|
The coder commits to its own `swarmforge-coder` branch. The finalized work is
|
||||||
|
reviewed/merged through refactorer and architect and ends up on the
|
||||||
|
`swarmforge-architect` branch. **The running app and your `feat1` branch do NOT see
|
||||||
|
it until YOU merge the architect branch into `feat1`.** Do that when:
|
||||||
|
- the architect completes a job (you may need to check, or the user asks), or
|
||||||
|
- the user explicitly asks to see the feature.
|
||||||
|
|
||||||
|
Then **verify** with `npm run build` (must print `Compiled successfully.`). Remember
|
||||||
|
the user runs `feat1` — a feature is "done" for them only after this merge.
|
||||||
|
|
||||||
|
### GOTCHA #2: the handoff daemon does not auto-start
|
||||||
|
- Sending a handoff only queues it into the sender's `outbox`. A daemon
|
||||||
|
(`handoffd.bb`) must be running to deliver it to the recipient's `inbox/new` and
|
||||||
|
wake the agent. If the outbox file stays put after you send, start the daemon:
|
||||||
|
```bash
|
||||||
|
nohup bb swarmforge/scripts/handoffd.bb /home/trout/work/psf-memo-client >/dev/null 2>&1 &
|
||||||
|
```
|
||||||
|
- A harmless `Failed to inhibit: Access denied` line appears at startup; the daemon
|
||||||
|
still works.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Specifier workflow (five phases) — from roles/specifier.prompt
|
||||||
|
|
||||||
|
For each feature:
|
||||||
|
1. Write the Gherkin that specifies the feature (see §6/§7 for format & tooling).
|
||||||
|
2. Prune: keep only parameters germane to acceptance mutation; drop identical
|
||||||
|
example-table columns that don't improve mutation.
|
||||||
|
3. Run `bb gherkin-ir-dry-checker` to normalize/prune.
|
||||||
|
4. Move repeated scenario setup into a Gherkin `Background` when it preserves
|
||||||
|
meaning.
|
||||||
|
5. **Ask the user for approval** before handing off to the coder. After approval:
|
||||||
|
commit with your byline (`By specifier.`), invent a short stable task name, and
|
||||||
|
send the file-based `git_handoff` (see §8).
|
||||||
|
|
||||||
|
Also: do not run Gherkin acceptance mutation; run tests only when verification is
|
||||||
|
needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Goal & feature backlog (memo.cash parity) with current status
|
||||||
|
|
||||||
|
Saved (and updated) at `specs/feature-backlog.md`. memo protocol action bytes below.
|
||||||
|
|
||||||
|
**Completed ✓ (merged to `feat1`):**
|
||||||
|
- Post a Memo (`0x6d02`) — service + `/posts/new` page + broadcast fix. FULLY DONE.
|
||||||
|
|
||||||
|
**Tier P1 — Core social verbs (write + read) — do these next, in order:**
|
||||||
|
1. ✅ Post a Memo (`0x6d02`) — DONE
|
||||||
|
2. Set display name (`0x6d01`) — **NEXT** (breadcrumb: the feed already renders
|
||||||
|
display names; the write action is missing)
|
||||||
|
3. Reply to a Memo (`0x6d03`) — thread already renders; add reply broadcast
|
||||||
|
4. Like / tip a Memo (`0x6d04`)
|
||||||
|
5. Set profile text / bio (`0x6d05`)
|
||||||
|
6. Set profile picture (`0x6d0a`)
|
||||||
|
7. Follow a user (`0x6d06`)
|
||||||
|
8. Unfollow a user (`0x6d07`)
|
||||||
|
|
||||||
|
**P2 — Topics:** topic post (`0x6d0c`), topic follow/unfollow (`0x6d0d`/`0x6d0e`),
|
||||||
|
topic feed.
|
||||||
|
**P3 — Polls:** create (`0x6d10`), add option (`0x6d13`), vote (`0x6d14`).
|
||||||
|
**P4 — Moderation:** mute/unmute (`0x6d16`/`0x6d17`).
|
||||||
|
**P5 — Money & tokens:** send money (`0x6d24`), token sell/buy/pin (MIP-0009).
|
||||||
|
**P6 — Discovery/UX:** search, tags, notifications, ranked feed, repost (`0x6d0b`).
|
||||||
|
|
||||||
|
**Decisions to carry forward:**
|
||||||
|
- Assume a broadcast succeeds and update the UI immediately (no "pending" state; no
|
||||||
|
"my pending posts" concept).
|
||||||
|
- Post memo length limit = **217 bytes** (memo.sv protocol), even though the indexer
|
||||||
|
allows `MAX_POST_SIZE = 65000`. Use 217.
|
||||||
|
- Keep the specs read-only-first for now, but always include the `sendOpReturn` write
|
||||||
|
code paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Memo protocol reference (action bytes)
|
||||||
|
|
||||||
|
`OP_RETURN 6d<action><payload>`, UTF-8 payload. Table (from memo.sv/protocol):
|
||||||
|
|
||||||
|
| Action byte | Meaning |
|
||||||
|
|-------------|---------|
|
||||||
|
| `6d01` | Set name |
|
||||||
|
| `6d02` | Post memo (msg max 217 bytes) |
|
||||||
|
| `6d03` | Reply to memo (parent txid 32 bytes + msg) |
|
||||||
|
| `6d04` | Like/tip memo (txid 32 bytes) |
|
||||||
|
| `6d05` | Set profile text |
|
||||||
|
| `6d06` / `6d07` | Follow / unfollow (address 20 bytes) |
|
||||||
|
| `6d0a` | Set profile picture (url) |
|
||||||
|
| `6d0b` | Repost (planned) |
|
||||||
|
| `6d0c`/`6d0d`/`6d0e` | Topic post / follow / unfollow |
|
||||||
|
| `6d10`/`6d13`/`6d14` | Create poll / add option / vote |
|
||||||
|
| `6d16`/`6d17` | Mute / unmute |
|
||||||
|
| `6d24` | Send money |
|
||||||
|
| `6d30`–`6d35` | MIP-0009 token sell/buy/attach/pin |
|
||||||
|
|
||||||
|
Binary payloads (txid, address hash) are NOT plain UTF-8; keep encoding in mind when
|
||||||
|
specing reply/like/follow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Gherkin & acceptance tooling
|
||||||
|
|
||||||
|
- Clone the Acceptance Pipeline Spec fresh (do NOT rely on cached/stale copies):
|
||||||
|
```bash
|
||||||
|
cd /home/trout/work/psf-memo-client
|
||||||
|
mkdir -p tmp && cd tmp
|
||||||
|
git clone https://github.com/unclebob/Acceptance-Pipeline-Specification.git aps
|
||||||
|
```
|
||||||
|
Temp files go in the worktree's `./tmp/`, never `/tmp`.
|
||||||
|
- Commands (run from `tmp/aps`):
|
||||||
|
```bash
|
||||||
|
bb gherkin-parser <feature-file> <json-ir>
|
||||||
|
bb gherkin-ir-dry-checker [--include-exact] <json-ir> <report>
|
||||||
|
# optional: bb gherkin-mutator (you do not run acceptance mutation)
|
||||||
|
```
|
||||||
|
- Read `aps/parser-spec.md` and `aps/ir-dry-checker-spec.md` for the supported
|
||||||
|
Gherkin subset and report format.
|
||||||
|
- Rules: `Feature:`, one `Background:`, `Scenario Outline:` with `Examples:`. Name each
|
||||||
|
scenario `Feature Name - N`. Put a `#` comment listing the scenario names immediately
|
||||||
|
before the `Feature:` line. Use `<parameter>` placeholders for values that vary.
|
||||||
|
- Store feature files under `specs/*.feature`; backlog under `specs/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Handoff mechanics
|
||||||
|
|
||||||
|
- Commit message must end with `By specifier.`
|
||||||
|
- To hand off, write a draft file, then run the helper (it removes the draft on success):
|
||||||
|
```text
|
||||||
|
type: git_handoff
|
||||||
|
to: coder
|
||||||
|
priority: 10
|
||||||
|
task: <short-stable-task-name>
|
||||||
|
commit: <10-char-commit-abbrev>
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
SWARMFORGE_ROLE=specifier swarm_handoff.sh tmp/<draft>
|
||||||
|
```
|
||||||
|
- After sending, check the handoff was delivered (daemon). If not, start the daemon
|
||||||
|
(GOTCHA #2).
|
||||||
|
- Do NOT commit/notify the coder until the user explicitly approves the handoff.
|
||||||
|
- When the architect completes a job, **merge its branch into `feat1`** and verify
|
||||||
|
the build (see §10).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Known gotchas & lessons learned (keep adding)
|
||||||
|
|
||||||
|
1. **Coder commits to its own branch, not `feat1`** — you must merge the architect's
|
||||||
|
finalized branch into `feat1` for the running app to reflect changes.
|
||||||
|
2. **Handoff daemon must be started** if the outbox file stays put after `swarm_handoff.sh`.
|
||||||
|
3. **`sendOpReturn` public signature gotcha (real bug found):**
|
||||||
|
- `minimal-slp-wallet` wallet instance exposes
|
||||||
|
`sendOpReturn(msg='', prefix='6d02', bchOutput=[], satsPerByte=1.0)` — it resolves
|
||||||
|
`walletInfo` and its own spendable UTXOs internally.
|
||||||
|
- The low-level `lib/op-return.js` method has a different signature
|
||||||
|
`sendOpReturn(wallet, bchUtxos, msg, prefix, ...)`.
|
||||||
|
- Calling the wallet's public one with the low-level args makes `Buffer.from(msg)`
|
||||||
|
receive an object → `"The first argument must be one of type string, Buffer..."`
|
||||||
|
- **Correct usage:** `await this.wallet.getUtxos()` then
|
||||||
|
`await this.wallet.sendOpReturn(message, MEMO_POST_PREFIX)`.
|
||||||
|
4. **Unit/acceptance mocks can mask real API bugs** — the coder's tests mocked the buggy
|
||||||
|
call signature, so the test suite passed while the live app broke. Live e2e (real BCH
|
||||||
|
+ a live server) is what catches these. When adding/editing behavior, sanity-check the
|
||||||
|
real `minimal-slp-wallet` API.
|
||||||
|
5. **Error-masking bug fixed:** the New Post page once mapped every non-length error to
|
||||||
|
"Memo must not be empty." Now broadcast failures surface the real error
|
||||||
|
(`Failed to broadcast: <msg>`). Keep that behavior in specs.
|
||||||
|
6. **memo.cash pages are behind Cloudflare** — `/memo/new` etc. are hard to scrape; rely
|
||||||
|
on user-provided behavior details and the protocol spec.
|
||||||
|
7. **Byte vs char:** the 217 limit and the counter currently count characters
|
||||||
|
(`input.length`, UTF-16), not bytes. The user is aware; multi-byte unicode may
|
||||||
|
diverge. Ask/decide per feature.
|
||||||
|
8. **Live backend for e2e:** `https://memo-api.fullstackcash.net/` (prod memo-db). The
|
||||||
|
user can provide BCH for real broadcasts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Run / verify the app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/trout/work/psf-memo-client
|
||||||
|
npm start # dev server (CRA)
|
||||||
|
npm run build # production build — verify after merges
|
||||||
|
npm test # node --test "test/unit/*.test.js"
|
||||||
|
npm run lint # standard --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend default `http://localhost:5021`; live prod `https://memo-api.fullstackcash.net/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Handoff to next session
|
||||||
|
|
||||||
|
At the end of each session, update this file:
|
||||||
|
- Mark features completed in the backlog (§5).
|
||||||
|
- Add any new gotchas to §10.
|
||||||
|
- Note the current `feat1` HEAD commit.
|
||||||
|
- State the next feature to work on (currently: **Set display name, `0x6d01`**).
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Scenarios: Set Name - 1, Set Name - 2, Set Name - 3, Set Name - 4, Set Name - 5
|
||||||
|
Feature: Set Name
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given a wallet authenticated for the address bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d
|
||||||
|
Given the wallet has spendable output to pay the transaction fee
|
||||||
|
|
||||||
|
Scenario Outline: Set Name - 1 a valid name is broadcast and the user lands on the account page
|
||||||
|
Given I navigate to the path /memo/set-name
|
||||||
|
When I type a name with the text "<name>"
|
||||||
|
When I submit the name
|
||||||
|
Then the app broadcasts an OP_RETURN transaction with the Memo set-name prefix
|
||||||
|
Then I navigate to the path /account
|
||||||
|
Then the account page shows my name as "<name>"
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| name |
|
||||||
|
| trout |
|
||||||
|
| a longer name with spaces |
|
||||||
|
|
||||||
|
Scenario Outline: Set Name - 2 an empty name is rejected on the set name page
|
||||||
|
Given I navigate to the path /memo/set-name
|
||||||
|
When I type a name with the text "<name>"
|
||||||
|
When I submit the name
|
||||||
|
Then the set name page shows a validation error
|
||||||
|
Then the app does not broadcast any transaction
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| name |
|
||||||
|
| |
|
||||||
|
|
||||||
|
Scenario Outline: Set Name - 3 an over-long name is rejected on the set name page
|
||||||
|
Given I navigate to the path /memo/set-name
|
||||||
|
When I type a name with the text "<name>"
|
||||||
|
When I submit the name
|
||||||
|
Then the set name page shows a length error
|
||||||
|
Then the app does not broadcast any transaction
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| name |
|
||||||
|
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
|
||||||
|
| bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
|
||||||
|
| 😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀 |
|
||||||
|
|
||||||
|
Scenario Outline: Set Name - 4 the byte counter counts down from the name limit
|
||||||
|
Given I navigate to the path /memo/set-name
|
||||||
|
When I type a name with the text "<name>"
|
||||||
|
Then the set name page shows a remaining byte count of <count>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| name | count |
|
||||||
|
| | 77 |
|
||||||
|
| trout | 72 |
|
||||||
|
| é | 75 |
|
||||||
|
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 0 |
|
||||||
|
|
||||||
|
Scenario: Set Name - 5 the account page links to the set name page
|
||||||
|
Given I navigate to the path /account
|
||||||
|
Then the account page shows a Set Name button
|
||||||
|
When I click the Set Name button
|
||||||
|
Then I navigate to the path /memo/set-name
|
||||||
@@ -49,16 +49,13 @@ class MemoPost {
|
|||||||
throw new Error('Memo post requires a wallet.')
|
throw new Error('Memo post requires a wallet.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spendable outputs used to pay the transaction fee.
|
// Refresh the wallet's spendable UTXO store so the broadcast has inputs.
|
||||||
const bchUtxos = await this.wallet.getUtxos()
|
await this.wallet.getUtxos()
|
||||||
|
|
||||||
// Broadcast the OP_RETURN transaction with the Memo post prefix.
|
// The wallet's public sendOpReturn(msg, prefix) resolves walletInfo and
|
||||||
const txid = await this.wallet.sendOpReturn(
|
// its own spendable UTXOs internally, so only the message and Memo post
|
||||||
this.wallet.walletInfo,
|
// prefix are passed here.
|
||||||
bchUtxos,
|
const txid = await this.wallet.sendOpReturn(message, MEMO_POST_PREFIX)
|
||||||
message,
|
|
||||||
MEMO_POST_PREFIX
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reflect the new post in the feed once broadcast succeeds.
|
// Reflect the new post in the feed once broadcast succeeds.
|
||||||
this._reflectPost(txid, message)
|
this._reflectPost(txid, message)
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
# - architect → qwen-token-plan/glm-5.2 (familia GLM)
|
# - architect → qwen-token-plan/glm-5.2 (familia GLM)
|
||||||
# Verificar ids con: pi --list-models
|
# Verificar ids con: pi --list-models
|
||||||
window specifier pi master --model deepseek-v4-flash:0731-cloud
|
window specifier pi master --model deepseek-v4-flash:0731-cloud
|
||||||
window coder pi coder --model deepseek-v4-flash:0731-cloud
|
window coder pi coder --model kimi-k2.7-code:cloud
|
||||||
window refactorer pi refactorer batch --model deepseek-v4-flash:0731-cloud
|
window refactorer pi refactorer batch --model deepseek-v4-flash:0731-cloud
|
||||||
window architect pi architect batch --model deepseek-v4-flash:0731-cloud
|
window architect pi architect batch --model deepseek-v4-flash:0731-cloud
|
||||||
|
|||||||
Reference in New Issue
Block a user