Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/output-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ never silent omission.
| 3 strengths + hole | engine diagnosis present | one-line note |
| 3 `[v]` amplify row | an `amplify` / `amplify_hypothesis` / `selection_inconclusive` prescription exists (strongest one only, in that order) | panel renders with the strength line alone |
| 3 `[?]` pattern panel | an unjudged pattern fired (today: `sold_winner_early` tags on ≥1 instrument) **and** the review did not queue the answerable `exit_consistency` question about it | omit the panel — either nothing fired, or the question already carries the facts answerably |
| 4 next step | always — restates the standing rule only when a persisted `prior_commitment` is on record; a review with none (a first review, or any later one whose most recent finalize set none) states the recommendation as pending instead (#546) | — |
| 4 next step | always — restates the standing rule only when a persisted `prior_commitment` is on record (#546) **and** its own `dim` and this period's `rule_dim` are proven to name the same dimension (#645); a review with no persisted commitment (a first review, or any later one whose most recent finalize set none), and one whose commitment carries no comparable `dim`, state the recommendation as pending instead; a proven divergence gets its own line naming both | — |
| 4 rule targets | the commitment carries a `dim` and that dimension has per-position facts (`risk_weights` over the cap; per-ticker averaging-down counts) | fall back to the aggregate `#248` grounding sentence; never leave the rule unanchored |
| 4 trade-off line | the rule's dimension shrinks a position (`position_sizing` / `diversification`) **and** the card carries an `amplify` row | omit the line — an unconditional one is caveat noise |
| snapshot route | suppresses history-performance modules by design (`card_renderer.py:1411`) | Block 1 = position-structure baseline only |
Expand Down Expand Up @@ -338,3 +338,4 @@ LLM judge and dogfood verdicts: structure is mechanical, prose is judged.
| 2026-07-24 | [#382](https://github.com/atomchung/fomo-kernel/issues/382) | **The #361 row above's "zero clipped subs" measurement did not hold at every width the layout supports.** #361 measured 520/760px; #382 (independently reproduced, then re-measured against the shipped #363 fix) found the pnl and payoff tiles' subs still clip at 320-390px once realized/unrealized or win/loss reach 5-6 digits — the README's own headline magnitudes (`+$138,058`, realized `$19,000`, unrealized `$119,058`). The alpha tile's own sub, the third tile #382 found clipping, was already fixed by #363's unrelated interval-placement change (confirmed by re-measuring 30 combinations against the shipped `9923be3`: never clips). What was missing was the mechanism §1 names: a rule that bounds the cell's height (R2) is not a rule that bounds its *content* to that height — nothing had measured whether the copy actually fits at the narrowest supported width, in both locales, at realistic magnitudes, so the fixtures behind every prior "zero clipped subs" claim used 3-digit amounts (`avg_win: 140`, `total_pnl: -300`) that were never close to the cap. Two independent fixes, gated at the point each stops being sufficient: `kpi.payoff_sub` drops its redundant leading qualifier in both locales (en "Average", zh-TW "average") — the tile's own label already says the figure is realized, so the word was doing no work — and fits both locales down to 320px unmodified, no change to the money format. `kpi.pnl_sub` needed more: even after dropping the equivalent leading word the way payoff did, a 5-6 digit realized/unrealized pair still does not fit two-up in the ~84px-wide sub at 320-360px no matter how the surrounding words are trimmed — the figures themselves are wider than the slot. A new `_money_compact()` (card_renderer.py) rounds to the nearest thousand only once `abs(value) >= 10000` (e.g. `+$119k`), leaving anything smaller — including a genuinely small account's real `+$200` — at full precision, so a small gain can never misprint as `+$0k`; only the HTML tile's `.sub` reads through it, `_performance_items`'s Markdown mirror of the same sentence keeps full `_display_money` precision, since Markdown has no line-clamp to work around. en's final template (`{realized}, {unrealized} unrealized`) goes one step further and drops the leading "realized" word too — the number stays, first and unlabeled, following the realized-first order convention `pnl_lines.total` already establishes elsewhere in this catalog — while "unrealized" keeps its explicit label, since that is the figure a caveat actually attaches to (the card's own lede: "almost all of it is held and never sold"). zh-TW did not need that second cut: its pnl_sub keeps both the "already realized" and "not yet realized" words at full sentence structure, unchanged, once their two figures compact to "k" — its shorter, denser glyphs afford the fuller sentence at the same compacted-figure width where en's could not. Measured (design-guidelines §4 step 7) with a real viewport resize, not `--window-size` (the same ~500px floor #363's row above recorded): both templates, at the values above, clear `scrollHeight<=clientHeight` **and** `scrollWidth<=clientWidth` (a single unspaced token can overflow the box sideways instead of wrapping, which a height-only check misses — caught one false pass here) at en 320/360/390px and zh-TW 320/360px, row spread 0 throughout — the narrowest width each locale was reported broken at. Known residual, out of the issue's own measured scope: zh-TW's pnl sub still clips at 300px, one step past the 320px this ruling and the issue both measured; closing it would cost zh-TW its own explicit "already realized" label, the same trade en already made, and nothing reported 300px as broken. Enforced by `test_pnl_and_payoff_tile_subs_fit_the_two_line_cap_at_narrow_widths` (design-guidelines §7), hardcoded literals across three magnitude cases (large, the pre-#382 small fixture, and a negative-unrealized sign check) so the pin cannot compare the catalog against itself. Persona-sweep byte-parity drifts on 92 of 156 rendered `.private.md` files; every drifting line is the two wording changes above with the underlying figures unchanged (verified by extracting and comparing every number on every changed line against baseline) — no mock persona's realized/unrealized reaches the $10,000 compaction threshold, so that path is exercised only by the new pinned test, not the sweep, the same gap #363's row above recorded for its own change. |
| 2026-07-30 | [#623](https://github.com/atomchung/fomo-kernel/issues/623) | **A card sentence that names a reward must attach a way to claim it, or say plainly that there is none.** A sweep of `copy/*.json`'s `block_missing` found five strings sharing the shape #357's fifth recurrence was diagnosed from ("telling me to add it on the finished card leaves me nowhere to act on it"), in three classes needing three different fixes. **Invitations** (`snapshot_unlock`, `rule_structural`) now name the one answer this user's book cannot reach — "whether your selling costs you more than your buying" — instead of a parenthetical feature catalogue ("win rate, payoff ratio, averaging-down discipline, and more"). The governing rule is [#617](https://github.com/atomchung/fomo-kernel/issues/617)'s, stated once in `references/decision-framing.md` and routed to from `references/card-policy.md` rather than restated, because two surfaces disagreeing about what an invitation owes is its own defect; at most one per card, and none at all on a complete behavioral review, which is asserted as its own counterweight (a manufactured invitation is the same defect as a manufactured disclosure). **A disclosure with a product path** (`annualized_reconciliation`) names the path inline in the user's own terms — "Share your current holdings view and the difference can be settled" — beside the number it explains, never promoted into the closing block, which would put two invitations on one card. **The price-blocked strings** (`annualized_prices`, `vs_market_prices`) were not a copy problem at all: `flows/first-review.md` step 0 already requires recovery before a degraded card is delivered, so that sentence reaching a user is evidence a required step was skipped — indistinguishable, until now, from sources that genuinely publish nothing. `review_plan.input.price_feed.recovery` records which happened and the shared draft path refuses the unattempted one, clearable by doing the step or by declaring the dead end with `prepare --prices-unavailable "<sources checked>"`. Net line count on every affected card is unchanged: no Note was added, and no sentence became a second paragraph. The other 12 `block_missing` strings honestly report a non-computable result with no user action available and are deliberately untouched. |
| 2026-07-31 | [#546](https://github.com/atomchung/fomo-kernel/issues/546) | **The pre-commitment preview claimed a standing rule "remains" on a review with no persisted commitment to restate.** Block 4's no-commitment branch (`card_renderer.py`'s `_next_block`) resolved `state.rule_dim` — this period's fresh prescription — into `block_missing.rule_standing` ("the standing rule remains: …") without checking whether a prior commitment was ever recorded. Because `cmd_preview` always drafts with `require_commitment=False`, this branch fires on every preview before the user has chosen, not only a first-ever review. Fixed by gating the wrapper on the same `review_plan.state_snapshot.prior_commitment.rule` predicate `_reconciliation_lines` already reads for #292: with a real prior commitment on record, the standing-rule copy is unchanged; with none, a new key (`block_missing.rule_pending`, en "No rule chosen yet this period; the recommendation is: …", each locale phrased natively rather than translated word-for-word, synchronized across `en`/`zh-TW`/`zh-CN`) states the same `{rule}` as a recommendation awaiting the user's choice, replacing rather than adding a sentence (net card line count unchanged). Both renderers share the branch through `_card_structure`, including Markdown's compact text-first scan (`_read_first_panels`, which quotes this same panel, so there is no second mirror to update). `tests/persona_sweep.py --baseline` against pre-fix `main` drifted exactly 12 of 156 rendered Markdown cards and 12 of 156 HTML cards — every one a `.first.preview` (or, for the `rotator` persona, a `.second.preview` whose own first review also set no commitment) switching from the standing to the pending wrapper, and nothing else. |
| 2026-07-31 | [#645](https://github.com/atomchung/fomo-kernel/issues/645) | **The standing-rule line named this period's prescription, not the rule the user actually committed to.** The row above gated *whether* Block 4 claims continuity and left the quoted rule resolved once, from `state.rule_dim`, inside both wrappers — so a returning user with a real persisted commitment read "No rule change this period; the standing rule remains: …" followed by a rule they never chose, with the false quote standing as the evidence for the false claim. `rule_dim` is `actionable[0].dim`, this period's top prescription, and has no input from `prior_commitment`; the two diverge whenever the book's leading problem changes, which is the ordinary case across weeks. Fixed per the owner's 2026-07-31 ruling (option 2): the persisted commitment is the only record of what is standing, so it is what gets quoted, and continuity is claimed only when the commitment's own `dim` and `state.rule_dim` are *proven* to name the same dimension. That identity already existed and no new state was added — `review._candidate_rules` stamps the canonical `dimension_id` on every candidate row and `_resolve_commitment` carries it into the stored commitment (`schemas/answers.schema.json`'s `commitment.dim`) — and both sides normalize through `card_renderer.dimension_id` because a stored commitment holds the canonical id while `rule_dim` holds the legacy label. A proven divergence replaces the continuity sentence with one new key (`block_missing.rule_diverged`, en "The standing rule is still \"{standing}\"; this period's leading issue is a different one, so \"{recommendation}\" stays a recommendation until you choose it.", each locale phrased natively, synchronized across `en`/`zh-TW`/`zh-CN`) — still one paragraph replacing one paragraph, so net card line count is unchanged. A commitment carrying no comparable `dim` fails safe onto the existing `rule_pending` wrapper rather than guessing: `_slot_commitment` drops the field from a condition slot deliberately and a custom rule need not carry one, and `session.PKEY` maps `metric_key` into a *problem* namespace (`oversize`, `concentration`, …) that is not the `rules` copy namespace `rule_dim` lives in, so inferring one from the other would be the heuristic matcher the ruling forbids. #546's `rule_pending` path is byte-stable and a first review is unaffected. Both renderers share the branch through `_card_structure`, including Markdown's compact text-first scan (`_read_first_panels`). `tests/persona_sweep.py --baseline` against pre-fix `main` drifted **zero** of 156 rendered Markdown cards and zero of 156 HTML cards: 8 persona `.second.preview` bundles reach the agreement arm and in every one the persisted rule text is already byte-identical to the prescription (the commitment was `candidate_0` on the same dimension), 4 more carry a commitment with no `rule_dim` to compare against and keep the generic line they already had, and no mock persona reaches the divergence arm at all — which is why the corpus never exposed this and the acceptance test carries its own diverging fixture. |
1 change: 1 addition & 0 deletions skills/fomo-kernel/copy/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"rule": "No new rule commitment this time; you can pick one at the next review.",
"rule_standing": "No rule change this period; the standing rule remains: \"{rule}\"",
"rule_pending": "No rule chosen yet this period; the recommendation is: \"{rule}\"",
"rule_diverged": "The standing rule is still \"{standing}\"; this period's leading issue is a different one, so \"{recommendation}\" stays a recommendation until you choose it.",
"rule_skip": "You chose not to set a new commitment; the same baseline remains available next time.",
"rule_snapshot": "This opening check keeps the structural baseline without forcing a commitment.",
"rule_structural": "This is an opening structural check: it sets a baseline without forcing a commitment. Too few closed trades here to say yet whether your selling costs you more than your buying; a file carrying the matching sells would settle that one question.",
Expand Down
1 change: 1 addition & 0 deletions skills/fomo-kernel/copy/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"rule": "这次没有新的规矩承诺;下次复盘可再选一条。",
"rule_standing": "这期不改规矩;现行规矩维持:“{rule}”",
"rule_pending": "这期还没选规矩;建议是:“{rule}”",
"rule_diverged": "现行规矩仍是“{standing}”;这期最该处理的是另一件事,“{recommendation}”在你选定之前都只是建议。",
"rule_skip": "你这次选择不设新承诺;下次仍可用同一份基线对账。",
"rule_snapshot": "这次开场检查先保留结构基线,不强迫设定承诺。",
"rule_structural": "这次是开场结构检查:先立基线、不硬塞承诺。已平仓的交易还太少,还看不出你的卖出是不是比买进更伤你;补上对应的卖出纪录就能回答这一题。",
Expand Down
1 change: 1 addition & 0 deletions skills/fomo-kernel/copy/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"rule": "這次沒有新的規矩承諾;下次復盤可再選一條。",
"rule_standing": "這期不改規矩;現行規矩維持:「{rule}」",
"rule_pending": "這期還沒選規矩;建議是:「{rule}」",
"rule_diverged": "現行規矩仍是「{standing}」;這期最該處理的是另一件事,「{recommendation}」在你選定之前都只是建議。",
"rule_skip": "你這次選擇不設新承諾;下次仍可用同一份基線對帳。",
"rule_snapshot": "這次開場檢查先保留結構基線,不強迫設定承諾。",
"rule_structural": "這次是開場結構檢查:先立基線、不硬塞承諾。已平倉的交易還太少,還看不出你的賣出是不是比買進更傷你;補上對應的賣出紀錄就能回答這一題。",
Expand Down
Loading
Loading