Commit Graph
423 Commits
Author SHA1 Message Date
GMGN.AIandGitHub e742b4e830 Merge pull request #235 from Edwardchennnn/fix/holder-analysis-two-false-positives
fix(holder-analysis): two false positives rejecting tokens with sound chip structure
2026-09-23 17:30:36 +08:00
chenjunfengandClaude Opus 5 ff663aa3b9 fix(holder-analysis): a missing buy count was read as proof of zero-cost chips
`buy_tx_count_cur == 0` was taken to mean "these chips were transferred or
airdropped in, the holder paid nothing". Measured data refutes that inference.

On musebook (robinhood) 38 wallets read `buy_tx_count_cur: 0` and
`avg_cost: null`, but 34 of them carry a finite `unrealized_pnl` ratio — +2.7%,
+76.5%, +546% — and a chip with no cost cannot produce a finite PnL ratio; a
+2.7% ratio is only possible against a cost basis near the current price. 32 of
the 38 also carry a `fomo` frontend tag and 2 a `gmgn` tag, which is the trace of
active trading, the opposite of receiving an airdrop. What is actually missing is
buy-transaction indexing on that chain, while the PnL fields come from a separate
computation and arrive populated.

The error was systematic per chain rather than per token. Never-bought share of
supply across one batch: robinhood 27.0% and 41.2%, arc 26.3%, sol 4.8%. The 20%
airdrop warn gate therefore fired on essentially every robinhood and arc token and
essentially never on a sol token — a difference in index coverage, not in chip
structure. Reporting an unpopulated field as a risk conclusion is what this skill
explicitly forbids elsewhere.

So zero cost now requires positive evidence of no cost:

    def has_cost_basis(h):
        return (h.get('avg_cost') or 0) > 0 or h.get('unrealized_pnl') is not None

Six consumers of the old predicate move to it, all the same root cause:

  - the `airdrop` cohort and its >20% warn gate, plus the chip-quality buckets and
    the exit-signal line that read it
  - `diamond`, whose own stated rationale is "a wallet that never paid has no cost
    to hold through" — a proven cost basis satisfies that, and requiring an indexed
    buy count discarded most real diamond hands (JOLLY: 34 → 75 wallets,
    21.1% → 55.7% of float)
  - `idle_airdrop`, whose "zero cost" caption was a false statement for those rows
  - Top5 sell-risk classification, which labelled a wallet sitting at +6.6x as
    "zero-cost airdrop — can dump anytime" and simultaneously dropped it from the
    trapped and in-profit counts

Wallets with no indexed buy count but a real cost basis are not dropped from the
report — they get their own neutral line, `买入未记录 / Buy count unindexed`, with
their float share. That upstream did not index their buys is true and stays
visible; it just no longer drives a zero-cost gate.

Known limit, stated in SKILL.md: a genuine airdrop wallet whose `unrealized_pnl`
is a huge ratio computed against a dust-level cost is missed here. No second ratio
threshold is added — there is no measured sample to calibrate one on.

Also in the Advice section: a ⚠️ Caution rating needs two or more warns by
definition, but only `warns[0]` was printed, so a reader could never see why the
rating was what it was. Every triggered reason is now listed.

Ratings, up/main → this branch, measured end to end:

  musebook robinhood  🔴 Not Recommended → 🟡 Light Position
  JOLLY    robinhood  🟡 Light Position  → ✅ Normal
  ARGUS    arc        ⚠️ Caution         → 🟡 Light Position
  SI       sol        🟡 Light Position  → 🟡 Light Position   (control)

SI is the control: sol indexes buy counts normally, so it barely moves and its
🔴 risk-wallet reading of 45.36% is untouched. This is not a blanket loosening —
it only takes effect where the index is thin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 16:06:23 +08:00
chenjunfengandClaude Opus 5 338b0efdb9 fix(holder-analysis): the token's own contract address was scored as a holder wallet
Upstream returns the token's own CA in `token holders` with `addr_type: 0`, i.e.
as an ordinary wallet. Measured on musebook (robinhood,
0x91a2dae9699f0b82540b5886b0d8759c22820ba3): `addr_type: 0`, 0 buys, 0 sells,
0 transfer-out, 15.01% of total supply, $5.06M, carrying a `fresh_wallet` tag.

Left in the wallet cohort that one row polluted `biggest`, Top10, Top20, airdrop,
fresh wallets, risk wallets and the Top5 display at once, and on its own tripped
the danger gate as "largest wallet holds 16.30% — extreme concentration", rating
the token 🔴 Not Recommended. The largest genuine wallet there is 4.45% of float.

Controls on the same batch — JOLLY (robinhood), SI (sol), ARGUS (arc) — carry no
such row, so this is a per-token upstream classification gap, not a chain
convention, and cannot be handled by skipping a chain.

The address is partitioned out of `normal` and reported on its own line on a
total-supply basis, with its own warn gate at >10% of supply. It is not removed
from the judgement: contract-held supply reaches the market as soon as one release
transaction lands, so it stays visible and still counts toward the rating. Warn
rather than danger because that release is an observable prior step, unlike a
whale who can sell at will; no higher danger tier is added, because one measured
sample cannot calibrate a second threshold.

It is deliberately not deducted from the float denominator. Burn is permanent and
the DEX pool is the market itself, so neither can dump; self-held supply can.
Removing it from the denominator would raise every other wallet's percentage on
every token carrying this row, manufacturing new false positives while fixing one.

The same address is also excluded from `top100_map`, which feeds the dev
sock-puppet danger gate: chips sent back to the token contract, or gas paid to it,
is not "transferred to an internal wallet".

musebook: 🔴 Not Recommended → ⚠️ Caution. Top10 34.7% → 19.4%, Top20 43.4% →
27.8%, risk wallets 21.01% → 4.71%, fresh wallets 18.25% → 1.95%.
Regression on the three control tokens: JOLLY and SI byte-identical, ARGUS differs
only on the market-cap header line (live price drift between runs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 16:05:19 +08:00
GMGN.AIandGitHub 5c61823fad Merge pull request #234 from Edwardchennnn/fix/heat-rank-age-split
fix(gmgn-heat-rank): read the track and freshness axis off creation time, not pool-open time
2026-09-22 19:22:30 +08:00
chenjunfengandClaude Opus 5 117884db9c fix(gmgn-heat-rank): growth rates off creation time, and stop one bad created.json entry discarding the rest
Three fixes found by testing the age split, not by reading it.

1. holders/day and KOLs/day still divided by the pool-open age. Those counts
   accumulate from the moment the token exists, so a token that spent 18 days on
   a bonding curve and migrated yesterday reported 10,836 holders/day and 22.0
   KOLs/day against a true 993 and 2.0 -- a wrong number printed on the address
   line and fed into an axis carrying weight 0.15. They now divide by the same
   creation age the track and the freshness axis read, which falls back to the
   open-time age when Step 3b has no answer and is clamped never to read younger,
   so a lookup can only lower a row's own rate. `c['age']` had no other reader
   and is retired rather than left beside `cage` as a second age.

2. The created.json loader judged every entry under one try block, and comparing
   a non-number against the plausibility window raises. So a single unreadable
   entry threw away every entry after it, and those rows returned in the next
   NEEDS-CREATED block -- a loop with no exit if the feed keeps answering the same
   value. Each entry is now judged on its own and no comparison can raise.
   Measured: ten bad values x first and last position, 20 runs, every one exits 0,
   the readable entries survive, and each unreadable one falls back to open time.

3. Pass 1 stopped with SystemExit(0), so "stopped to ask for Step 3b" and "ran to
   completion" were the same status. It now exits 3, leaving 0 = the list is the
   answer, 3 = incomplete, 1 = could not read its inputs.

Net effect on the same saved sweep: the same four names in the same order, ARGUS
72.1 -> 69.5 as its growth rate corrects. Two claims the first commits left
imprecise are amended: the second pass is not purely subtractive, since the
growth axis is a within-chain percentile, and only gate-surviving rows are looked
up, so a listed migrated token is measured on its true rate against neighbours
still on their inflated one -- an error that costs listed names points rather
than granting them, now stated under `## Known limits`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:19:47 +08:00
EdwardchennnnandClaude Opus 5 5eecc8574b fix(gmgn-heat-rank): close four holes found reviewing the age split
Re-audit of the change in this PR, all four in code or claims the PR itself
introduced.

1. A creation time that was not a readable number crashed the whole run. A
   string-typed timestamp in `created.json` raised `TypeError: unsupported
   operand type(s) for -: 'float' and 'str'` out of `creage()` and took the
   entire scoring pass with it, instead of dropping one row. The loader now
   coerces once, up front.

2. Worse than the crash: the values that did NOT crash were accepted and
   silently moved the list. `NaN` compares False against everything, so it
   passed the track test as "not young" AND came out of the freshness clamp
   at 1.0 — the easier mature track and a full newborn bonus at the same
   time, which is precisely the bug this PR exists to remove. A negative or
   absurdly small number read as "very old" and also bought the mature
   track. Unreadable is now stored as None (asked, no answer), which falls
   back to open time and keeps the row on the stricter track. Verified
   against string / list / NaN / negative / float / null / bool / zero /
   future-dated: nine cases, no crash, and every unreadable one now resolves
   in the strict direction instead of the generous one.

3. A lookup that never landed was indistinguishable from one that came back
   empty, and both were written as null. Exit status cannot separate them —
   `gmgn-cli token info` answers 0 with a fully zeroed body for an address it
   does not know (measured: `creation_timestamp: 0`, empty stderr). So the
   body itself decides: one that parses and echoes the address back is a real
   answer and a zero creation time is written as null, while one that does not
   parse or does not echo it back is a call that never landed, is left out of
   `created.json` entirely, and is named on stdout so Step 3 lists it again
   rather than scoring the row off an age nobody read. Step 3b also keeps its
   stderr now, in an `.err` file beside the response, the way Step 1 does,
   instead of sending it to /dev/null.

4. The report could state an age that was off by weeks. The table's age column
   is the pool-open age, which is right for the 7-day ceiling, but after this
   PR a token can be listed whose real age is far older — and nothing printed
   that number, so the writer could not have known. arc/ARGUS would have been
   reported as "1.9d" on a list whose headline promise is recency, when the
   token is 19.5 days old. The address line now prints `created=N.Nd ago`
   whenever the two differ by more than a day, and stating it is required in
   `## What the answer has to contain`.

Two claims elsewhere in the file that this PR had made false are corrected
rather than left to contradict it: the ceiling bullet said `MAX_AGE_D` stops a
months-old token reaching the list, which is now true only of months of
trading and not of the token's age, and the `YOUNG_D` cliff note did not say
which of the two ages the cliff is measured on.

No threshold, weight, gate constant or routing description changes. Against
the same saved 7-chain sweep, pass 1 names the same 5 rows and pass 2 returns
the same 4 names in the same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:09:13 +08:00
EdwardchennnnandClaude Opus 5 5c4841342a fix(gmgn-heat-rank): read the track and the freshness axis off creation time, not pool-open time
`open_timestamp` was doing all three of the age's jobs: the 7-day recency
ceiling, the new-launch/mature track choice, and the freshness axis. For a
token that sat on a bonding curve for weeks before it migrated, that one
number pulls in two opposite directions at once -- the token reads as hours
old, so it is held to the new-launch run-rate floors (thresholds set for
tokens that have no history to measure) while simultaneously being paid a
newborn's freshness bonus.

Measured on a live sweep this cost a correct listing: arc/ARGUS, real age
18.4d, was dropped for missing the $150,000 1h floor by $824 while carrying
a 21.8h freshness score.

The jobs are now split onto the timestamps that actually mean them:

- the 7-day ceiling (`--max-created` / `MAX_AGE_D`) still reads
  `open_timestamp`, because "recently hot" is a claim about the tape and
  migration is that event;
- the track choice (`YOUNG_D`) and the freshness axis read
  `creation_timestamp` from `token info`, because those are claims about the
  token's own history.

Cost is bounded rather than per-candidate. Step 3 now runs twice: pass 1
emits a `NEEDS-CREATED` block naming only the rows whose outcome can still
turn on their age -- a row already dead on liquidity, rug score or a
one-sided tape gets no call -- new Step 3b fetches exactly those, pass 2
prints the list. On the verification sweep that was 5 lookups against 58
candidates, so a whole run is 21-27 calls instead of 21. The block warns
itself if it ever asks for more than 20.

Guards on the new code:

- a creation time that reads newer than the pool open time is clamped, so a
  lookup can never move a row onto the easier track or buy it a freshness
  bonus it did not already have;
- a key present with a null value means "the lookup ran and there is no
  answer" and falls back to open time, which keeps the row on the stricter
  track instead of re-asking forever;
- Step 3b refuses to run with `DATA` unset, validates each chain name
  against the literal seven and rejects any address that is not
  alphanumeric, and refuses to write an empty `created.json` that would send
  pass 1 back to the same `NEEDS-CREATED` block in a loop.

No thresholds, weights, gate constants or report rules change, and the
frontmatter is untouched. Verified against a saved 7-chain sweep: pass 1
names the same 5 rows, pass 2 returns the same ranking, and the only list
difference the rule itself makes is the one row it was written for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 14:25:22 +08:00
GMGN.AI a9e7036ea2 1.6.6 v1.6.6 2026-09-16 17:08:11 +08:00
GMGN.AIandGitHub debd453a44 Merge pull request #233 from dylanzhong662-max/docs-openapi-contract-sync
docs(cli): synchronize OpenAPI contracts
2026-09-16 17:07:26 +08:00
zhongsongzhi-cmyk 0ffec3f73a docs(cli): synchronize OpenAPI contracts 2026-09-16 16:41:01 +08:00
GMGN.AI 685c46b324 1.6.4 v1.6.4 2026-09-14 20:17:01 +08:00
GMGN.AIandGitHub c300178097 Merge pull request #231 from dylanzhong662-max/feat/support-arbitrum-hyperevm
feat: document Pro-only 1s K-line support
2026-09-14 20:14:39 +08:00
zhongsongzhi-cmyk af73701a8d chore: bump gmgn-cli to 1.6.3 2026-09-14 19:46:20 +08:00
zhongsongzhi-cmyk a8f40a4be3 feat(cli): document Pro-only 1s kline 2026-09-14 19:45:58 +08:00
zhongsongzhi-cmyk d2e4e96865 feat: document Pro-only 1s kline support 2026-09-14 19:45:58 +08:00
GMGN.AIandGitHub 4a48b8a31e Merge pull request #229 from Edwardchennnn/feat/gmgn-narrative-skill
feat(gmgn-narrative): add independent token narrative/due-diligence card skill
2026-09-11 19:25:37 +08:00
GMGN.AIandGitHub 5870e68558 Merge pull request #223 from Edwardchennnn/docs/contract-dd-display-templates
gmgn-contract-dd: reshape the report, and put the verdict on the buyability axis
2026-09-11 19:25:07 +08:00
GMGN.AIandGitHub d210d94bf8 Merge pull request #228 from Edwardchennnn/token-buy/direction-execution-gate
fix(token-buy): direction and volatility decide how to place the order, not whether to place it
2026-09-11 19:24:51 +08:00
chenjunfengandClaude Sonnet 5 be98adaeaa fix(gmgn-narrative): final self-audit — stale test-status claim, DNS-rebinding gap
Two findings from a final read-through before requesting merge:

- SKILL.md's Supported Chains section still said arbitrum/hyperevm were
  "untested" — stale as of this skill's own development, where 4 real
  tokens (2 per chain) were actually run through the full workflow with
  no chain-specific issue found. Corrected to describe that spot-check
  honestly (a signal, not a validation pass) instead of leaving a claim
  that had already been overtaken by the skill's own test history.
- interfaces.md's web-fetch address-range control named the private/
  loopback/link-local ranges to block but didn't say to pin the resolved
  address between the check and the connection — a hostname re-resolved
  a second time at connect can return a different (and unblocked)
  address, which is exactly what a DNS-rebinding attack targets. Added
  the one line closing that gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:45:45 +08:00
chenjunfengandClaude Sonnet 5 35a0d812ee fix(gmgn-narrative): declare the web-fetch interface and fix rule/workflow contradiction
Review caught a real internal contradiction: step 4 requires opening a
linked source (a post's URL, a project's site) to verify a claim, but hard
rule 6 said the skill may only read from the two declared interfaces —
neither of which is a general web fetch. Declares a third interface
(web content fetch) with explicit controls: http/https only, private/
loopback/link-local address ranges rejected (closes the SSRF vector a
token's own attacker-controlled link.website field would otherwise open),
a timeout, a response-size cap, and re-validation on redirect. Rule 6 now
scopes "no browser automation" against open-ended browsing rather than
against fetching one already-known URL, which is what it was actually
contradicting.

Also, per the same review:
- New hard rule 7: a hard ~30-tool-call ceiling per card, so an
  adversarially ambiguous token can't turn one card into unbounded tool
  use — write the card with what's verified so far and say the cap was
  hit, same "neutral over fabricated" principle applied to cost/time.
- New hard rule 8: a product-facing note that a hedged single-sourced
  negative claim about a named/identifiable party still carries
  reputational and legal exposure, even when the hedge itself is correct.
- interfaces.md's turn-budget row and "what this skill must never do"
  section updated to match (three interfaces, not two; the 30-call cap
  is this skill's own stop condition, not one for the runtime to
  second-guess with a lower limit of its own).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:38:49 +08:00
chenjunfengandClaude Sonnet 5 130711ec2f feat(gmgn-narrative): add independent token narrative/due-diligence card skill
Produces a two-field narrative card (叙事背景 + 传播观察) for a token from
on-chain data (gmgn-cli) and independently-verified social posts, built to
catch the failure modes automated narrative cards on the market commonly
have: under-sampling social search, uncited/misattributed claims, launchpad
vs issuer confusion, and fabrication when evidence is thin.

Provider-agnostic by design: the social evidence step is a pluggable
interface (X API v2 or xAI Grok API), never browser automation or a
personal login — see references/interfaces.md for the configuration
contract and a validation-pass checklist for testing on a new model/runtime.
references/patterns.md is the red-flag checklist accumulated during
development (ticker collisions, similarly-named token mixups, creator
wallet history, paired-token mechanics, citation failures).

Explicitly out of scope for gmgn-contract-dd (safety score),
gmgn-heat-rank (screened hot list), and gmgn-token-buy/gmgn-swap (buy
decisions) — this only answers "what story is this token telling and
what's verifiable about its spread."

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:25:13 +08:00
chenjunfengandClaude Sonnet 5 dd6413c88f fix(token-buy): sync stale direction-gate wording in thresholds.md and pitfalls.md
Two leftover passages still described direction as a hard veto gate
that "must pass" alongside volume, contradicting the new rule in
section 三 where only the 5m ≥10% drawdown hard-stops and everything
else downgrades execution instead of vetoing the order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 16:37:43 +08:00
chenjunfengandClaude Opus 5 48d61990cb gmgn-contract-dd: scope the EVM lock tightening to the six meme-native chains
Upstream added `arbitrum` and `hyperevm` (#227), making the file's chain counts
nine / eight rather than seven / six. Three things follow.

1. Chain counts in the passages this branch added still said six EVM chains and
   seven supported chains. Corrected, and the `burn_status: ""` coverage claim is
   extended with a 2026-09-11 measurement on the two new chains: ARB, WBTC, ODYS,
   SIGNAL, CPU and CHAMELEON all return `""`, so the burn half is missing there too.

2. The Step 3 tightening — deduct −12 and cap 79 on `lock_summary.is_locked ===
   false` alone — was calibrated on the six chains that existed then, where 12 of
   12 sampled tokens returned `is_locked: true`. It does not hold on the two new
   ones: measured 2026-09-11 on the top three of each chain's 24h trending list,
   ARB and WBTC on `arbitrum` and SIGNAL and CPU on `hyperevm` all return
   `is_locked: false` — 4 of 6, including the chain's own governance token and
   wrapped Bitcoin. Deep DEX liquidity from real market makers is not locked and
   is not meant to be, so there the deduction would fire hardest on the tokens it
   exists to clear. The row is now skipped on `arbitrum` and `hyperevm` and the
   tightening applies only to `bsc`, `base`, `eth`, `robinhood`, `arc`, `stable`.
   Cross-references in the cap summary, the cap-cause list and the Step 3 table
   row name the scope.

3. The two dated bsc trenches passages contradicted each other on the same
   180-row sample — 130 flap / 50 four.meme against flap 143 / fourmeme 37. The
   per-platform split is not a stable quantity: two captures of the same 180 rows
   hours apart on 2026-09-11 gave 150 / 30 and 137 / 43. The load-bearing claim is
   the mapping, not the mix, so the suffix passage now states the mapping with
   today's figures and says outright that a platform's share must not be read off
   one capture, and the non-load-bearing counts are dropped from the launchpad
   passage, whose actual claim is that `market trenches` never emits `openfour`.

No band boundary, weight or cap value changes. Inventory totals are unchanged at
23 on `sol` and 25 on the EVM chains — the skipped lock row stays in the
denominator as a skipped check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:35:29 +08:00
chenjunfengandClaude Opus 5 7b51b191d4 Merge up/main into contract-dd display-templates branch
Brings in the arbitrum + hyperevm chain support merged upstream in #227, so the
chain counts in this file are the nine-chain / eight-EVM ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:32:30 +08:00
chenjunfengandClaude Opus 5 91088a8cf5 fix(token-buy): direction and volatility decide how to place the order, not whether to place it
The four gates were one flat "any single failure means no order" list, but they
answer two different questions. Volume, depth and security answer "can I get
back out once I am in" — structural facts that do not flip within hours.
Direction and volatility answer "where will a market order fill this second" —
which changes minute by minute. Merging them makes the same token get opposite
verdicts on the same day.

Measured: VC on BSC (0x36f01746a86639d0c3b6a850d328be1b634a4444) scored 91.4
"buyable" from gmgn-contract-dd, one-sided depth $91,442, 95% of LP burned to
the black hole, $6.6M 24h volume, 2,560 holders, zero taxes, owner renounced —
and was still reported as "not recommended" purely because 5m was -8.2% and all
three net-flow windows were negative. Two separate rules produced that: the
compound hard rule (three windows negative AND 5m drawdown >= 5%), and the
sentence that merged this section's warnings into the security section's
">= 2 warnings fails" count.

Section 三 now has three exits instead of two: 5m drawdown >= 10% remains the
one hard stop (a market order genuinely cannot execute there), everything else
downgrades execution — the order card is still produced, but it must name the
degraded execution path. Added a table of the three downgrade actions so none
of them can be given by feel: the limit trigger price is derived as
price.price * (1 - |5m drawdown|) and handed to gmgn-swap as
`order strategy create --order-type limit_order --sub-order-type buy_low
--check-price <price>`; waiting names the condition that has to converge (5m
drawdown back inside 5%, or buy_volume_5m - sell_volume_5m turning positive);
reducing size reuses the existing price-impact wording. The downgrade needs its
own separate confirmation — nodding yes to buying a token is not the same as
knowing you are no longer getting a market order.

Direction now produces no veto count at all, and missing direction data
downgrades to the strictest tier rather than failing the order: not being able
to read direction is not the same as a quiet market.

Nothing about the original intent is relaxed. The slippage formula is
untouched (its |5m change| * 0.5 term already prices volatility), the
>= 100x-with-negative-flow warning still refuses to widen slippage, and the
security gate's ten warnings and red flags are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 14:48:08 +08:00
GMGN.AI 4e48e25328 1.6.2 v1.6.2 2026-09-11 13:56:27 +08:00
GMGN.AIandGitHub 5fd50e552c Merge pull request #227 from dylanzhong662-max/feat/support-arbitrum-hyperevm
feat: support Arbitrum and HyperEVM
2026-09-11 13:55:10 +08:00
zhongsongzhi-cmyk 263e699397 feat: support arbitrum and hyperevm 2026-09-11 11:31:49 +08:00
chenjunfengandClaude Opus 5 de9ce030db gmgn-contract-dd: reconcile four live rules the reword left inconsistent
Step 6 still told the reader to print the section weights while Step 8 and
Display Templates both forbid printing coverage and weights, so the same
response could ship two different report shapes depending on which sentence
was read last. Step 5B's rug_ratio cap table still named the pre-reword band
("high risk") in a live rule, which also falsified the neighbouring claim that
every live occurrence had been corrected — that count is now three, with the
grep procedure that found the last one written down. Step 8's cap list named
Step 2, 5B and 7 but not Step 3's EVM lock cap, which the assembly list in
Step 6 already carries.

No threshold, weight or band boundary moves; every measured score in the file
is unchanged. Dated measurement passages keep the original audit vocabulary by
the policy stated in the file — all 32 remaining occurrences were classified
and every one of them is a record of a run, not a rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:19:02 +08:00
GMGN.AIandGitHub ec95135eca Merge pull request #225 from dylanzhong662-max/docs-clarify-cli-trenches
docs: correct market trenches --limit / --launchpad-platform description
2026-09-10 17:17:27 +08:00
GMGN.AIandGitHub 965b492fa1 Merge pull request #222 from Edwardchennnn/feat/gmgn-heat-rank
feat(skills): add gmgn-heat-rank — screened cross-chain hot list
2026-09-10 17:13:16 +08:00
chenjunfengandClaude Opus 5 176eb126ac gmgn-heat-rank: print contract addresses without a gmgn.ai link
The CA block was headed `CA (verify at https://gmgn.ai/<chain>/token/<CA>)`.
That path shape is the one thing in this file nobody was allowed to check: the
skill's own rules forbid reaching gmgn.ai by web search, WebFetch, curl or a
browser, so the URL was printed on the strength of an assumption. If the shape
is wrong the reader lands on a 404 and doubts the list rather than the link.

The addresses themselves were already complete and one per line, so dropping
the link costs nothing -- an address pastes into whatever front-end the reader
actually uses. The heading now says exactly that and names no host.

Behaviour is otherwise unchanged: replayed against five stored sweeps, the
scorer's output differs on that single heading line and nowhere else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 17:02:18 +08:00
chenjunfengandClaude Opus 5 685ce2177b gmgn-contract-dd: label the stat percentages by their real denominator
Five problems found by rendering the report against a live bsc token
(4Stock, 2026-09-10) and checking every printed line back against the
response. All five are output-wording problems; no scoring table and no
composite in this file moves.

- The stat percentages were rendered as shares of the supply. They are
  not: bot_degen_count 1068 / bot_degen_rate 0.5416 gives a denominator
  of 1971.9 on a token with 20,930 holders, and top_bot_degen_percentage
  returned exactly 0.5416 alongside it, which ties the whole
  top_*_trader_percentage family to that same analysed-trader cohort.
  The worked example in Display Templates carried the wrong wording and
  a live report inherited it, telling the reader that 61.1% of the float
  sat in entrapment wallets — a claim the response never made.
- The Limits of this read skeleton asked for off-chain presence
  unconditionally, so an all-empty link block was written up as "no
  off-chain presence at all". An empty link block cannot be told apart
  from GMGN not having collected the socials, so the bullet is now
  dropped unless a social field or the verification status has a value.
- An isolated stat zero was being printed as a passing check. The report
  now needs a second field from the same response to corroborate it:
  creator_hold_rate 0 qualifies (creator_token_balance "0" plus
  creator_token_status "creator_close"), top70_sniper_hold_rate 0
  qualifies (wallet_tags_stat.sniper_wallets 0), private_vault_hold_rate
  and dev_team_hold_rate do not and are dropped. Scoring is untouched —
  the zero is still an executed check in coverage.
- Market cap was being printed with no rule saying where it comes from,
  and the response carries no market_cap key at all. It is now fixed as
  price * circulating_supply, verified against a figure GMGN does
  publish: ath_price * circulating_supply = 82,633,663 matches
  dev.ath_token_info.ath_mc exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:53:27 +08:00
chenjunfengandClaude Opus 5 1ab16cb76e fix(heat-rank): iterate the chain list as an array, and match whole names
Step 1 held the chain list in a plain string and iterated it as
`for ch in $CHAINS`. That works under bash and silently does not under zsh,
which performs no word splitting on an unquoted parameter expansion: the loop
runs once with all seven names in one variable. The sweep then collapses to a
single call with `--chain "sol bsc base eth robinhood arc stable"`, which the
CLI refuses -- no data, no list, on every run in a zsh shell.

The whitelist did not catch it either. `case " sol bsc ... " in *" $ch "*` is a
substring test, so the whole concatenated string matched its own haystack and
was waved through; it accepts any run of adjacent names. It is now an exact
alternation over whole names, which also refuses `sol bsc` and anything
carrying shell metacharacters. `$ch` and the two output redirections are
quoted.

Introduced by b4b54ad, whose own verification ran the extracted block under
bash and so could not see it.

Verified: `-n` clean under zsh, bash and bash --posix; seven iterations with
the correct per-chain filters in all three; and a live run under zsh now makes
15 calls across all seven chains (eth/arc/stable short-circuiting on an empty
24h window as designed), 56 candidates -> 10 passed -> 3 listed. The scorer
block is byte-identical, so scoring is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:37:34 +08:00
chenjunfengandClaude Opus 5 dfbbb7c688 fix(gmgn-heat-rank): an unreadable age is unknown, not zero
`rage` fell back to `now` when a row carried neither `open_timestamp` nor
`creation_timestamp`, so a row with no age read as "launched this instant":
it walked through the `MAX_AGE_D` ceiling -- the one gate the whole list
rests on -- was judged on the wrong (new-launch) track, and would have taken
full freshness credit had it cleared that track's gates. That is the same
mistake as reading a missing risk field as clean, which this skill refuses
to make anywhere else.

An unreadable age is now placed past the ceiling and refused as
`no timestamp (age unknown)`, reported as the data fault it is rather than
as a token that failed a check. Both timestamp fields are normalised as
counts, so an unparseable value arrives as 0 and is caught by the same test
as an absent one.

Verified: on a row with both timestamps stripped, before/after is
rage=0.000 age=0.500 young=True (age gate silent) -> rage=8.000 young=False
fail=['no timestamp (age unknown)'], with no duplicate age-backstop reason.
Output is byte-identical on four unmodified fixtures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:19:19 +08:00
chenjunfengandClaude Opus 5 17d312bd1b fix(heat-rank): drop --filter is_out_market, which the API does not recognise
`is_out_market` is not a filter tag the API knows, and an unrecognised tag is not
refused -- it is silently ignored. Worse, a filters list made only of unrecognised
tags does not fall back to the server's defaults: it replaces them with nothing.

Measured on sol with the skill's own pool parameters: `--filter is_out_market` and
`--filter zzz_fake_tag_qqq` returned the identical 22 rows, both a superset of the
18 returned with no --filter at all.

On sol / bsc / base / eth the tag sat alongside real tags and was inert -- dropping
it returns the identical address set on both chains tested. On robinhood / arc /
stable it was the *only* tag, so those three chains were fetched with server-side
screening switched off. On one robinhood sweep that admitted 6 rows the defaults
exclude: 4 with is_honeypot=1, plus CLUBY and BRIAN, neither renounced nor open
source and neither caught by any local gate in this file.

So the fourth branch now sends no --filter and lets the server's defaults apply,
with a comment saying not to invent a tag to fill it, and `## Known limits` records
the measurement.

Verified end to end on a live sweep with the fix in place: 15 calls, the three
empty chains short-circuited, robinhood 20 rows instead of 26, and zero
is_honeypot=1 rows anywhere in the candidate pool. 52 candidates, 10 passed,
3 listed. Step 1 passes bash -n; an empty F array expands to no arguments; the
scorer is byte-identical to the previous commit's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:56:53 +08:00
chenjunfengandClaude Opus 5 7fe841b9fb gmgn-contract-dd: put the verdict on the buyability axis, drop the launchpad claim
The verdict band words were audit vocabulary — `relatively clean · mixed,
needs manual review · high risk · very high risk`. "Mixed" names the average
of the three sections, which is the one reading that hides a one-rotten
two-clean profile, and "needs manual review" hands the work back without
naming what to review. A reader who pastes an address is asking whether they
can buy it, so the bands now answer that: `buyable` / `buyable with
conditions` / `not advised` / `do not buy`, each paired with a clause derived
by rule from which sections fell under 60, so the pair carries how buyable
from the word and what is in the way from the clause.

The 80 / 60 / 40 boundaries did not move, so no composite in this file
changes grade. Dated measurement passages keep the original words as records
and a decoder maps them one-for-one; two live rules that still named a band
in the old vocabulary are corrected.

Three guards come with the new axis, because `buyable` is easier to
over-read than the audit words were: it means the token can be bought and
sold back, never that it will appreciate; `Buy or not` may not contradict the
band word nor merely repeat it; and the band word is not permission to size a
position, which stays gmgn-token-buy's job.

Also drops the launchpad line from the report. `info.launchpad` is not
reliable enough to name: the vocabulary is unstable across endpoints (4Stock
returns `openfour` from `token info` while all 180 `market trenches` bsc rows
use only `flap` and `fourmeme`), and 9 of 100 bsc trending rows return an
empty string with no way to tell "no launchpad" from "unknown launchpad".
Measured coverage is complete on sol and unmeasured on base/eth/robinhood/
arc/stable, so the claim goes rather than ships hedged. The address-suffix
rule stays — it rests on its own 180-row tally.

And corrects an over-absolute claim: `pool.initial_liquidity` is *usually* 0
on a migrated pool, not always. Measured on 4Stock, GMGN had switched the
reported main pool to a pancake_v3 pool created about half an hour after
launch, which returns 19893.57. A non-zero value is that pool's own opening
depth and must never be reported as the token's launch depth; a shrink ratio
above 1 means the pool deepened and is never a deduction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:42:23 +08:00
zhongsongzhi-cmykandClaude Opus 4.8 6572db0e2d docs: correct market trenches --limit / --launchpad-platform description
The CLI docs promised behavior the API does not deliver:

- --limit: was "max 80 (default 80)". In reality it is best-effort only —
  the new_creation category is served from a fixed live window (~60), so
  larger and smaller --limit values both tend to return ~60. Documented as
  best-effort.
- --launchpad-platform: was "default: all platforms for the chain". In
  reality, when omitted the server injects a fixed per-chain default
  allow-list (not literally all platforms), and passing values REPLACES that
  allow-list (fewer platforms => less data). Platforms outside the list
  (incl. some newer RWA / tokenized-stock launchpads) are filtered out.
- Added the previously-missing robinhood platform slug list, and noted that
  arc/stable apply no launchpad filter (all platforms).

Docs only; no code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 15:36:35 +08:00
chenjunfengandClaude Opus 5 dd485face1 perf(heat-rank): fetch 24h first and skip a chain's other two windows when it is empty
A candidate has to be present in the 24h window to count at all -- the scorer
only uses a chain when a 24h file loaded, and keys candidates off it. So for a
chain whose 24h window comes back empty, the 1h and 6h calls cannot produce a
candidate no matter what they contain. The sweep was spending them anyway.

Reorder the window loop to 24h first and break out when the 24h response holds
an empty rank array. On a measured sweep eth, arc and stable were empty in all
three windows, so this drops 21 calls to 15 and ~30s to ~21s. Worst case is
unchanged: when all seven chains are alive the sweep still costs its full 21.
This cuts waste, not coverage -- no window that could hold a candidate is skipped.

Teach the loader to tell a deliberate skip from a failure, so the saving does not
print as two failed calls. `missing (excluded)` keeps meaning one thing: a call
that failed or returned unparseable JSON. A 24h window that itself failed to load
still reports, since that is the signal worth seeing.

Verified against the frozen sweep: with eth/arc/stable's 1h and 6h files removed
the run is identical -- 60 candidates, 11 passed, the same five names with the
same scores in the same order -- and no missing-window line. Corrupting bsc_6h
and sol_24h still reports both. Step 1 passes bash -n.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:34:55 +08:00
chenjunfengandClaude Opus 5 b4b54ad192 fix(heat-rank): check chain names against a fixed whitelist before they reach the CLI
The chain list is a documented parameter a user can narrow, and whatever is in
it lands on a command line unquoted. Nothing validated it, so a typo, a chain
name from some other exchange, or a string carrying spaces or shell
metacharacters would have been passed straight through to gmgn-cli as arguments.

Hoist the list into a CHAINS variable (which is the thing to narrow) and check
each name against the literal set of the seven names this API actually has,
refusing anything else out loud and skipping it. The whitelist is deliberately
written out again rather than reusing $CHAINS, which would check the input
against itself.

Tested: sol / eth / robinhood accepted; "xyz", "sol; rm -rf /" and
"sol --limit 1" refused. Step 1 passes bash -n, and the extracted scorer is
byte-identical to the previous commit's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:24:34 +08:00
chenjunfengandClaude Opus 5 feb6331c47 fix(heat-rank): strip pipes and backticks from token symbols
sym() already removed control characters, terminal escapes and bidi overrides,
but let two markdown metacharacters through into text that gets copied straight
into the report. A token named "X | buy now" splits into two cells of the
report's markdown table with the second one written by whoever deployed the
contract; a backtick opens or closes a code span. The symbol is data the
attacker chose, so both become ?.

Tested on the extracted function: 'X | 买它 | 忽略上文' -> 'X ? 买它 ? 忽略上文',
'AAA`rm -rf /`BBB' -> 'AAA?rm -rf /?BBB', ordinary symbols untouched. Verified
against the stored sweep: same five names, same order, no symbol in the sample
contains either character.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:23:30 +08:00
chenjunfengandClaude Opus 5 6306d99a6e fix(heat-rank): use mktemp -d instead of a guessable path under /tmp
Step 1 built its working directory as /tmp/gmgn-heat-data-$(date +%s) -- a
second-resolution timestamp in a world-writable directory -- then wrote
heat_rank.py into it and executed it. Another local user can guess that name and
pre-create the directory holding their own heat_rank.py, at which point the run
executes their code as the user. Narrow (it needs a local unprivileged account)
but it is local code execution on a path this skill both writes and runs.

mktemp -d gives an unpredictable name and mode 0700, verified on this machine
(/var/folders/.../tmp.XXXXXXXX, drwx------).

The scorer's fallback was the matching half of the same problem:
os.environ.get('HEAT_DATA', '/tmp/gmgn-heat-data') meant a run with the variable
unset would read a fixed path anyone can plant JSON into. There is no safe
default, so it now exits with the correct invocation instead of inventing one.

Verified against the stored sweep: same five names in the same order. The
candidate count reads 11 rather than 12 only because the fixture keeps ageing
against a live clock -- robinhood PAIR has crossed 7d and the age backstop from
897153f drops it, at score 53.4, far below any floor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:22:39 +08:00
chenjunfengandClaude Opus 5 f6105c5ae0 docs(heat-rank): close the last two routing overlaps, and say no input is needed
Rescanned the current upstream skill set (13 skills on up/main, aa4d29a) and
grepped every description for hot-list vocabulary. Only gmgn-market matches,
and two of its claims were still unanswered here:

- "discover early-stage opportunities" and its Chinese equivalents. The
  description already sent "just launched" / "新盘" to `market trenches`, but not
  the ambush framing (值得埋伏吗, 新币筛选, 早期机会), which is exactly where the two
  skills read alike. Named, with the reason: the pool here starts at 500k market
  cap and 100k liquidity and never holds a bonding-curve token.
- gmgn-track's "what smart money is buying" / "on-chain alpha" /
  "copy-trade signals". The body table routed it; the description, which is what
  a router actually reads, did not.

Also added what the description never said about using it: no input is needed at
all -- the trigger phrase alone runs it, no address, chain or amount -- and the
four values a user can narrow. Runtime and pacing stay out of the description,
where no skill in this repo puts them; they are already in ## Pacing.

Documentation only: the extracted scorer is byte-identical to the previous
commit's and the stored sweep returns the same five names in the same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:10:01 +08:00
chenjunfengandClaude Opus 5 bc53c7d34c docs(heat-rank): stop advertising four flags the CLI does not have
argument-hint offered "--chains sol,bsc,... --max-age 7d --top 10
--min-score 60". Measured against `gmgn-cli market trending --help`, none of
the four exists: the CLI takes one `--chain` at a time (the seven-chain sweep
is the Step 1 loop, not a list argument), the age flag is spelled
`--max-created`, and TOP_N / MIN_SCORE are Python constants no flag reaches.
So a request like "this time only sol and bsc" or "raise the floor to 70" led
straight to an invalid command line, even though all four values are in fact
tunable and already listed in Parameters under their real names.

Rename the hint to the names used in the body, and add a paragraph under
Parameters saying these are spoken options rather than flags and mapping each
one to the single place it changes -- including that max-created now moves
together with MAX_AGE_D.

Documentation only: the extracted scorer is byte-identical to the previous
commit's and the stored sweep produces the same five names in the same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:04:36 +08:00
chenjunfengandClaude Opus 5 897153fddd fix(heat-rank): re-check age locally instead of trusting the server alone
"Recently hot" is the premise of this list, and ## Rules calls age a hard gate
with no compensation logic. Its only enforcer was --max-created 7d in Step 1 --
a server-side filter. The script computes each row's true age from
open_timestamp and used it for the young/mature track split and the 24h-change
blanking, but never compared it to the ceiling, so an endpoint that ignored or
re-interpreted the parameter would put a months-old token on the list under the
word "recent" with nothing local to stop it.

Add MAX_AGE_D = 7.0 and reject any row above it alongside the other
unconditional gates, with its own rejection reason. Document it in Parameters as
tied to --max-created, and say in ## Rules that the gate is enforced in both
places and the two values move together.

Verified against the stored seven-chain sweep: no row is affected -- the oldest
candidate is 6.99d, so the gate is silent, which is the intended steady state.
Re-running the same data with MAX_AGE_D lowered to 5.0 drops robinhood ZZZ
(5.3d) and MEME (6.4d) with reason "age>5d(local backstop)", 5 listed -> 3,
confirming the gate fires and reports itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:03:44 +08:00
chenjunfengandClaude Opus 5 dc69d8fc08 fix(heat-rank): print both score floors instead of only the lower one
The floor is two-valued -- MIN_SCORE for a row every gate could judge,
MIN_SCORE + U_SCORE_ADD for one no manipulation gate could -- and floor_for()
applies it correctly. The diagnostic line printed only MIN_SCORE, so the one
number carried into the report was wrong for half the rows.

The visible damage was in the near-miss block, which exists so the boundary can
be inspected and instead made the skill look broken: on a measured sweep bsc
BREW at 63.4 was dropped while sol LOOM at 63.3 was listed. Correct -- BREW
sits on a chain with no rug model and needed 68 -- but a report claiming a
floor of 60 alongside those two lines reads as padding or a bug.

Three changes, none touching selection:
- the diagnostic line names both floors and how many rows fall in the higher
  tier ("floor 60, or 68 for the 6 of 12 rows no manipulation gate could judge")
- each near miss prints the bar it actually had to clear
- the reporting checklist forbids writing the floor as a single number, and
  still forbids explaining the two tiers, which would disclose what ## Rules
  keeps out of the report

Verified against the stored seven-chain sweep: same five names, same order,
same three near misses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:02:54 +08:00
chenjunfengandClaude Opus 5 9ddda22279 fix(heat-rank): blank the 24h change cell for tokens under a day old
The 24h% column is only meaningful once the token has 24h of history. Below
that the window opens before the token existed, so price_change_percent is
measured from the launch price and prints figures like +128168.0% -- correct
arithmetic, no information about momentum, and wide enough to break the
column's alignment.

This was latent until the previous commit: the reference row used to be
whichever window loaded last, so a young token showed its 1h change under the
24h heading and happened to look plausible. Reading the right window exposed
the real number.

Print n/a instead when age < 1 day, matching how an unknown ATH position is
already rendered. The age column sits immediately left of the cell, so the
reason the cell is empty is visible without a footnote. Rows a day or older
are untouched.

Verified against the stored seven-chain sweep: same five names in the same
order, the four rows aged 2.2d-6.4d keep their 24h figures unchanged
(-21.2 / -0.4 / -18.7 / +39.7), and the 8.2h-old row now reads n/a.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:53:46 +08:00
chenjunfengandClaude Opus 5 ad9ff57741 fix(heat-rank): take the reference row from the 24h window, not whichever loaded last
`U.setdefault(key,{})['ref']=t` only guaranteed the key existed -- the assignment
that followed ran unconditionally, so the reference row ended up being whichever
window was loaded last. Iteration order is 24h, 6h, 1h, so for any token that
appeared in the 1h list the reference row was the 1h row.

Most fields read off that row are a current snapshot and are unaffected, but
`price_change_percent` is the change over that row's own window, so the column
headed `24h%` printed a 1h move -- and printed a 24h move for tokens that only
appeared in the 24h list, so the column was not even consistent between rows.
Measured on a stored 7-chain sweep, all five listed tokens were showing their 1h
change: 4Stock printed +29.7% where its 24h move was -21.2%, and MEME printed
+0.1% against a real -18.7%. A reader comparing the list against the exchange
would have found two of five names moving the opposite way.

setdefault now keeps the first window that carried the token, which is 24h for
every listed name (a token absent from the 24h list is already rejected).
Re-scored against the same sweep: same five names, same order, and all five
percentages now match the 24h files exactly. Scores shift by up to 0.5 because a
few snapshot fields differ slightly between the 24h and 1h responses, which were
fetched seconds apart.

One thing this exposes rather than fixes: a token younger than a day now shows
its true 24h change, and for an 8-hour-old token that is +128168%. The number is
correct and useless; a separate change should blank the cell below a day of age.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:46:39 +08:00
chenjunfengandClaude Opus 5 3c49980398 fix(heat-rank): defend the boundary against gmgn-market, and drop three dead symbols
The routing collision was one-sided. This skill's description told the reader
where a raw ranking dump belongs, but `gmgn-market` still claims "trending
tokens, what's pumping, hot coins" and cannot point back here, so an English
"hot coins" or a bare Chinese 热门币 could land on either. Rather than edit that
skill, the deciding test now lives here: what shape of answer is being asked
for. An untouched ranking of N rows in the exchange's own order is
`gmgn-market trending`; a short list that survived risk gates and carries a
score is this skill. A bare ambiguous ask defaults here, because this skill can
point at the raw ranking while the raw ranking cannot screen itself. The
"what this skill is not" table now names the colliding wording too.

Also removed three symbols that never did anything:

- `MAX_BUNDLER = 0.40`, defined and never read. The ceiling that actually fires
  is each chain's leave-one-out p90, and a flat 40% is precisely the mistake
  that deleted whole chains before -- leaving the constant in suggests
  otherwise to the next reader.
- `RANK`, a dict of per-window ranks that nothing consumes.
- `import glob`, unused.

And `near` compared whole candidate dicts by value to decide what was already
listed; it now compares (chain, address), which is what identity means here.

No threshold, weight or gate changed. Re-scored against a stored 7-chain sweep:
the same five names in the same order, the same three near-misses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:45:46 +08:00
chenjunfengandClaude Opus 5 153b72320c gmgn-contract-dd: reshape the report to the dev-score output template
The Display Templates section shipped as a shape table — which section holds
what — and Step 8 asked for `field path → measured value → points → reason`
plus a `executed N / skipped M / held-out K of T` coverage line. Together they
produced a report written for whoever audits the scorer rather than for the
person deciding whether to buy: two dozen field paths, the section weights, and
a full list of every field that could not be read, on a token whose actual
answer was "the contract is fine, the holders are bots".

Replace it with the shape `gmgn-dev-score` already uses for its own report: a
header block carrying the number, narrative sections that each answer one
question a buyer has, and a fixed four-question decision block at the end. The
section is now a fill-in skeleton rather than a description of one, so the same
JSON lays out the same way every run.

Three rules the new section adds:

- No field paths and no internal accounting in the output. Coverage is still
  computed exactly as Step 6 specifies, because it sets the confidence label —
  it just is not printed, and neither are the weights.
- Numbers stay, jargon goes. Every measured value is still printed; what
  changes is the label beside it.
- Exactly one table in the report (`Key numbers`). Everything else is prose.

Step 8 items 2 through 6 are rewritten to match, since as written they mandated
the output the new section forbids. The `stat` disclosure stays mandatory and
moves to plain wording; absent fields are described in words instead of listed
with a reason column; fields that are not applicable on the chain are no longer
listed at all, so a Solana report stops padding itself with a honeypot
non-finding. A `[filtered]` string or a `neutralized … suspicious metadata`
stderr notice remains impossible to drop.

No scoring table, threshold, weight or cap changes. The composite, the grade
bands, the coverage arithmetic and every measurement in "Where the thresholds
came from" are untouched — this is the presentation layer only. Verified on
4Stock (bsc, 0xd270d4e1ec6e6e0d28c0ecb8be966ec75997ffff): 85.7, contract 90.0 /
holders 72.0 / price 100.0, identical before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 19:20:26 +08:00