docs(major2): invariantraden - eksperten svarer paa forslaget paa bordet, og svaret brukes
CLAUDE.md: en invariantrad etter verdict-gate-raden, i husets skjelett - nevneren (grep "prior_" finner EN soem, maskinens egen), D6 og hvorfor alternativet ble forkastet, den ledger-bevisste attempts_remaining, honoured = "hentet faktisk", kanalvalget begrunnet av tre maalinger, den kaller-eide sinken, skriv-iff-reviewer, de fem nektene ved navn og den hostede pre-whitelist-sjekken. Load-bearing-blokka lister M1-M40 med roedtall, M29 staar som et FUNN (uvitnet baerer), og de to armene som var groenne av feil grunn i steg 1-8 er skrevet ned som repoets vakuoes-gate-klasse, sekstende og syttende gang. Aerlighets-grensene til slutt. README.md: --proposal-review i enkeltprosjekt-flagglista og en prosablokk etter --plan-review/--checkpoint-dir-paret, i samme form - hva operatoeren ser og skriver, at en revise KJOEPER ett forsoek til under de eksisterende takene, at approve ikke er en ekspertdom, og alle fem nektene navngitt (--checkpoint-dir-en peker paa --resume, doera som VIRKER). Kundevendt vokabular. Ny arm: test_the_readme_block_names_every_flag_the_cli_refuses_the_door_with, Fase-3-formen (raa tekst, uttrukket blokk, kontroll paa at uttrekkeren finner noe som finnes). Intet M-nummer - lista lukket ved M40 - saa den ble drevet ROED TO ganger: mot README-en foer blokka fantes, og med blokka paa plass men --checkpoint-dir omskrevet til aa beskrive nekten uten aa navngi flagget. 1364 passed / 5 skipped. Golden demo-transcript.stdout BYTE-UENDRET (shasum -a 1 av INNHOLDET = ea8c534773acdbe41ae68f2c55724d69aaf8be4f). Node-ID-ene er et strengt supersett: 1319 -> 1369, 0 fjernet. mypy og ruff rene. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
74ea1dcf02
commit
4a3655fc07
3 changed files with 211 additions and 1 deletions
119
CLAUDE.md
119
CLAUDE.md
|
|
@ -1839,6 +1839,125 @@ Python ≥3.10. MAF (`agent-framework-core` 1.16.0, `-orchestrations` 1.1.1 —
|
|||
nekten er nøklet på den ENE erklærte typen ExpeL-folden eier, aldri på et bredere
|
||||
«ikke i `context_files`»-komplement, som M4 viser ville rammet `index.md` — navigasjon, ikke en
|
||||
dom. Måling: `docs/2026-09-04-s2c-debatt-k2.md` § 8.
|
||||
- **Eksperten kan svare på forslaget som ligger på BORDET — og svaret BRUKES, det registreres ikke
|
||||
bare (MAJOR-2, økt 88–89):** før dette fantes ÉN menneske-søm inne i løkka, `plan_reviewer`, og
|
||||
den fyrer FØR noen hypotese finnes. **Nevneren er målt** (`docs/2026-09-02-misjonsreview-v2.md`
|
||||
§ 3, re-målt mot HEAD): `grep -n "prior_" generate.py` finner nøyaktig ÉN tilbakemeldings-søm,
|
||||
`prior_rejection` — MASKINENS egen falsifisering matet tilbake i samme kjøring. Et menneskes ord
|
||||
landet først i NESTE kjøring (Steg-7-innboksen), så «improved proposals given feedback» var, for
|
||||
en person, en to-kjørings løkke med dager imellom. `--proposal-review` viser kandidaten
|
||||
validatoren nettopp AKSEPTERTE og leser `approve` eller `revise <hva>`; en `revise` **kjøper ETT
|
||||
forsøk til under de EKSISTERENDE takene** (ingen ny løkke — `max_attempts` + `meter.tick_round`,
|
||||
som Steg 5), og ordene går ORDRETT inn i det forsøkets prompt som en tredje komponerbar blokk
|
||||
ved siden av `prior_rejection`.
|
||||
**D6: validatorens SISTE dom vinner, aldri revieweren sin.** Alternativet (falle tilbake til det
|
||||
sist VALIDERTE forslaget når oppfølgingen blir avvist) ble forkastet fordi det ville latt et
|
||||
menneskes ønske om ett forsøk til FORBEDRE utfallet uten at noen falsifiserer sa ja — mennesket
|
||||
er en tredje stemme som gater INGENTING utover å be om ett forsøk til. `M27` er dét vitnet.
|
||||
**Ekspertens ord er KLEBRIGE, maskinens grunn er PER FORSØK:** `feedback` står til mennesket
|
||||
svarer neste gang, `prior_rejection` overskrives hver runde (M4/M5, hver sin ene røde arm).
|
||||
**`attempts_remaining` er LEDGER-BEVISST, ikke `max_attempts`-avledet:**
|
||||
`min(max_attempts - i - 1, meter.budget.max_rounds - meter.rounds)` — den DELTE rundeboka
|
||||
(`max_rounds*4` på run-nivå) spenner over hver tilnærming i en kommisjon og er ofte dét som
|
||||
binder, så et tall regnet fra forsøkstaket alene ville vært en påstand terminalen gjør om seg
|
||||
selv som måleren straks gjendriver (M38 → 2 røde, BEGGE navngir M38 i sin docstring).
|
||||
**`honoured` betyr «forsøket som ble kjøpt HENTET faktisk et svar», ikke «ble kjøpt»:** posten
|
||||
føres `False` og forfremmes først når `_fetch_parsed` har RETURNERT, så en revise rundeboka kappet
|
||||
før den står `false` ved siden av `rounds limit=2 observed=3` (M40 → 2 røde).
|
||||
**Kanalen er valgt av TRE målinger, ikke av smak:** `ProposalReviewInputError` er en
|
||||
`RuntimeError` fanget VED NAVN på CLI-ens fullkjørings-dispatch og printet på en egen
|
||||
`run stopped:`-linje med rc 1. En `ValueError` ville (a) landet på `run refused:`-tuppelen, som
|
||||
MISLABLER en kjøring der argv var riktig og tokens allerede var brukt, og (b) — verre — blitt
|
||||
fanget av `_fetch_parsed` ETT frame unna som en PARSE-feil, altså føyd til `parse_failures`
|
||||
ordrett og modellen re-kalt til rundeboka fyrte. EOF er ALDRI en signatur (M15/M16).
|
||||
**Sinken er KALLER-EID** (funn-1-formen, `parse_failures`-presedensen): `run_project` eier lista,
|
||||
nøkler hver post på `(approach_id, attempt)` — `OWN_PROPOSAL_ID` for systemets eget forslag, så
|
||||
to tilnærminger aldri kollapser på én rad — og skriver `{run_id}-proposal-reviews.json` fra en
|
||||
**`finally`**. En returverdi ville vært tapt på nøyaktig den kjøringen som trenger beviset: et
|
||||
budsjett-stopp inne i genereringen konstruerer aldri et `GenerationResult` (M35 → T9 alene).
|
||||
**Skrive-regelen er «iff en reviewer ble gitt, OGSÅ når lista er tom»** (D4) — `write_debate_tools`'
|
||||
regel, av samme grunn: en reviewer ingen rakk å spørre er dét som må kunne LESES, ikke utledes av
|
||||
en fil som ikke er der (M12/M13). Uten reviewer er utboksen BYTE-IDENTISK (M13 → 3 røde, hvorav
|
||||
to i tester eldre enn dette arbeidet).
|
||||
**Fem nekter, alle VED NAVN**, og plasseringen er MÅLT: blokka ligger på FUNKSJONS-nivå rett etter
|
||||
mode-dispatchen, aldri nøstet under `if args.scripted_replies` eller `if args.explore` — under
|
||||
hver av dem ville et bart `--live-dry-run --proposal-review` falt rett gjennom til dry-run-
|
||||
dispatchen og droppet flagget. `--portfolio` (bølgene deler ÉN terminal), `--report`
|
||||
(returnerer OVER hver dispatch, så en utelatelse er et stille DROPP — F4-gapet),
|
||||
`--live-dry-run`, `--proposals-from-mandate` og `--checkpoint-dir` («send `--proposal-review` ved
|
||||
`--resume` i stedet» — en nekt som bare forbyr etterlater operatøren uten døra som VIRKER).
|
||||
Den hostede flaten nekter ved navn som en **pre-whitelist rå-payload-sjekk**, ikke en fjerde
|
||||
liste-oppføring: `_CONSUMED_FIELDS` må være disjunkt fra `run_project`s parametre (Fase 4es
|
||||
negative halvdel) mens `proposal_reviewer` ER en av dem, og den generiske
|
||||
`unknown field(s)`-nekten fyrer FØRST — et navn overlatt til den ville gitt samme 400 med en
|
||||
melding som ikke sier hvor døra faktisk er. **F2 står: en `approve` mynter INGEN `Verdict`**
|
||||
(M14), og `provenance.validator_decision` speiler fortsatt KUN validatoren.
|
||||
**Load-bearing MÅLT** (`tests/test_proposal_review_loop_loadbearing.py`, 49 armer),
|
||||
**M1–M40 i ÉN liste, 41 kjøringer (M26 er to lengder), 40 RØDE mot HELE suiten** + grønn kontroll
|
||||
**1364/5** og golden `demo-transcript.stdout` BYTE-UENDRET (`shasum -a 1` av INNHOLDET =
|
||||
`ea8c534773acdbe41ae68f2c55724d69aaf8be4f`, ALDRI git-blob-id-en): M1 detach reviewer-kallet (16) ·
|
||||
M2 alltid approve (13) · M3 print-and-discard av feedback (6) · M4 ikke-klebrig feedback (1) ·
|
||||
M5 akkumuler avvisninger (1) · M6 vis også en AVVIST kandidat (4) · M7 revise tikker ikke
|
||||
rundeboka (1) · M8 revisjoner løkker UTENFOR `max_attempts` (36, 18 eldre testfiler) ·
|
||||
M9 `honoured` konstant `True` (6) · M10 `approach_id` alltid `None` (3) · M11 skriv etter returen
|
||||
i stedet for fra `finally` (2) · M12 skriv kun ved ikke-tom liste (2) · M13 skriv også uten
|
||||
reviewer (3) · M14 la `approve` mynte en `Verdict` (1) · M15 EOF er approve (2) · M16 fang ikke
|
||||
feilen ved navn (1) · M17 alt som ikke er revise er approve (1) · M18 drop `single_only`-raden (1)
|
||||
· M19 drop `report_forbidden`-raden (1) · M20 bibliotekssømmen wiret, argparse-flagget borte (9) ·
|
||||
M21 strømmene bundet ved konstruksjon (1) · M22 slett den hostede nekten (1) · M23 videresend det
|
||||
hostede feltet (4) · M24 fersk reviewer per base (1) · M25 render kandidaten som repr (2) ·
|
||||
M26a trunker feedback til 20 tegn (5) · M26b til 40 tegn, sentinelen OVERLEVER (3) · M27 D6-
|
||||
alternativ b (1) · **M29 reverter exit-kontrakten (0 — se under)** · M30 drop
|
||||
`--live-dry-run`-nekten (1) · M31 drop `--proposals-from-mandate`-nekten (1) · M32 rendereren
|
||||
skriver linja uten reviewer (2) · M33 rendereren returnerer `None` på null (3) · M28 drop
|
||||
`verdict_key` fra posten (2) · M34 feedback løftet ut av per-approach-kallet (5) · M35 returnert
|
||||
liste i stedet for kaller-eid sink (1) · M36 dispatcheren slutter å tråde revieweren (1) ·
|
||||
M37 detach CLI-utskriften mens rendereren returnerer linja (2) · M38 `attempts_remaining` fra
|
||||
`max_attempts` alene (2) · M39 drop `--checkpoint-dir`-nekten (1) · M40 `honoured=True` ved
|
||||
revise-tid (2).
|
||||
**ÉN MUTASJON FORBLIR GRØNN, OG DET ER ET FUNN — IKKE EN GATE:** M29 reverterer `last_ruling` til
|
||||
`assert last is not None`, og HELE suiten står grønn. Planens «Critical risk #1» — at
|
||||
«validert → revise» på hvert forsøk ville nå halen med `last is None` — er dermed **FALSIFISERT AV
|
||||
MÅLINGEN**: D1(a) RETURNERER inne i løkka når `remaining == 0`, og på siste forsøk er
|
||||
`max_attempts - i - 1` alltid 0, så halen er nåbar KUN etter en validator-AVVISNING, som setter
|
||||
`last` også. Bæreren er derfor **UVITNET** (`budget_stop`-presedensen) — den står fordi den gjør
|
||||
D6 eksplisitt og fjerner en assert som hviler på en ikke-lokal invariant, ikke fordi en test
|
||||
holder den. Kommentaren i `generate.py` som PÅSTO at asserten ville fyrt er rettet i samme
|
||||
slengen (Fase-3-klassen: en påstand flaten gjør om seg selv); commit-teksten for `bbf4d3b` bærer
|
||||
fortsatt den gamle påstanden, og historikken skrives ikke om.
|
||||
**TO ARMER VAR GRØNNE AV FEIL GRUNN (repoets vakuøs-gate-klasse, SEKSTENDE og SYTTENDE gang),
|
||||
begge funnet i steg 1–8:** (i) `report_forbidden`-armens ledger-fikstur var et JSON-OBJEKT, som
|
||||
`SavingsLedger.load` uansett nekter — rc 1 kom fra fiksturen, ikke fra raden; den er nå en JSON-
|
||||
ARRAY med en rc-0-kontroll på en argv report-modus ellers ville AKSEPTERT. (ii) «offered, never
|
||||
consulted»-armen kjørte et TO-stegs manus mot `max_attempts=3`, så det tredje forsøket falt
|
||||
gjennom til selectorens default-svar, som aldri parser — rundeboka fyrte og armen var grønn av en
|
||||
helt annen mekanisme; manuset har nå like mange oppføringer som `max_attempts`.
|
||||
**README-armen har intet M-nummer** (lista lukket ved M40) og ble drevet RØD TO ganger, fordi
|
||||
«blokka mangler» og «blokka er ufullstendig» er ulike feil og bare den andre er dét gaten finnes
|
||||
for: først mot README-en slik den STO (uttrekket fant ingenting), så med blokka på plass men
|
||||
`--checkpoint-dir`-setningen omskrevet til å beskrive nekten uten å NAVNGI flagget. Kontrollen er
|
||||
`--plan-review`-blokka, som har eksistert siden F4.
|
||||
**K2-omkjøringen (kriterium 8), samme base og samme syntetiske prisfikstur som S7b/S2c**,
|
||||
instrumentet validert mot en KJENT POSITIV FØR bruk (rotnivå-listingen på levert K2: 3 954 tegn /
|
||||
1 495 o200k-tokens over 629 konsepter, S7a-3s publiserte tall reprodusert eksakt):
|
||||
utforskningen **12 prompter / 18 355 tokens UENDRET til tokenet**, debatt-promptene **UENDRET**
|
||||
(proposer 2 → 2 à 314 tokens, checker 1 → 1 à 199), genererings-promptene **2 → 4** (én ekstra per
|
||||
tilnærming), totalt **17 → 19 prompter og 19 274 → 19 776 tokens (+2,6 %)**. Ekspertens ord står
|
||||
ORDRETT i nøyaktig de to andre-forsøks-promptene (2 av 6), utfallet flytter seg **200 000 →
|
||||
150 000 NOK** og dom-nøkkelen `be8535e204cdc4c6` → `f23ecff85f4188b5`, mens
|
||||
`{run_id}-proposal-reviews.json` bærer feedbacken ordrett med `honoured: true`.
|
||||
**Ærlighets-grenser, uttalt:** Steg 5 UNDER-rapporterer på en revidert kjøring (en revise bruker
|
||||
ett av de SAMME `max_attempts`-forsøkene, så `refinements` kan mangle en falsifisering som aldri
|
||||
rakk å skje); `coverage`-radene navngir ALDRI en revise som årsak til en `not_evaluated`
|
||||
(rundeboka er delt — sann, og `coverage` er et non-goal); feedback-teksten er UBEGRENSET som
|
||||
plan-review-feedbacken, belastet det bundne `max_tokens`; checker-gaten og dimensjons-gaten
|
||||
overstyrer ETTER at genereringen har returnert, så en `approve` registrert ordrett kan sitte på en
|
||||
kjøring hvis utfall er en checker-kildet `Rejection` — posten sier hva mennesket SÅ og SVARTE,
|
||||
aldri hva kjøringen konkluderte, og å blande dem er nøyaktig dét to-falsifiserer-raden forbyr;
|
||||
F4s traceback-asymmetri er URØRT (`PlanReviewInputError` forlater fortsatt CLI-en som traceback);
|
||||
`run_portfolio` tar INGEN reviewer, og fraværet er ASSERTERT; K2-prisene er SYNTETISKE og ingen
|
||||
LEVENDE modell har svart på en review (structured-output-grensens klasse). Måling:
|
||||
`docs/2026-09-04-major2-proposal-review-k2.md`.
|
||||
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
|
||||
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.
|
||||
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -416,7 +416,8 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
cannot exercise every flag:
|
||||
- **Single-project** — `PROJECT_ID --docs-dir <dir>`, plus optional `--bundle-dir`,
|
||||
`--verdict-dir`, `--outbox-dir` (which requires `--run-id`), `--dimension-config`,
|
||||
`--semantic-retrieval`, `--decision`/`--rationale`, `--live-dry-run`, and
|
||||
`--semantic-retrieval`, `--decision`/`--rationale`, `--live-dry-run`, `--proposal-review`
|
||||
(opt-in: answer the proposal the validator just accepted, at your terminal — see below), and
|
||||
`--scripted-replies <file>` (the offline whole-loop door — see
|
||||
[Walk the whole chain offline](#walk-the-whole-chain-offline); mutually exclusive with
|
||||
`--live-dry-run`, which stops before the first model call rather than answering it), and
|
||||
|
|
@ -511,6 +512,44 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
`--checkpoint-dir` are refused together (two doors onto one review), as are `--resume` and
|
||||
`--explore` (two sources of one exploration).
|
||||
|
||||
**Answering the proposal review (`--proposal-review`).** The plan review asks you about the
|
||||
*plan*, before any candidate exists. This one asks you about the **candidate on the table**:
|
||||
every time the deterministic validator accepts a proposal, the run stops and shows you the
|
||||
measure, the cost lines it touches, the claimed saving, the validator's percentiles, the
|
||||
checker's verdict and how many further attempts are still affordable. You type `approve` to
|
||||
take it as it stands, or `revise <what to change>`.
|
||||
|
||||
A `revise` is not a note filed for later. It **buys one more generation attempt** under the
|
||||
caps the run already had — no new loop — and your words go into that attempt's prompt verbatim,
|
||||
where they stay standing until you next answer. The run then carries whatever the validator
|
||||
ruled on that later attempt: the expert asks for another try, the machine still decides. Every
|
||||
round trip is written to `{run_id}-proposal-reviews.json` with your words, which candidate you
|
||||
were shown, and whether the attempt your `revise` bought actually ran. Input that ends without
|
||||
an answer stops the run rather than counting as approval.
|
||||
|
||||
```bash
|
||||
uv run python -m portfolio_optimiser.run BYGG-KONTOR-NORD --docs-dir <docs> \
|
||||
--bundle-dir <bundle> --proposal-review --outbox-dir out --run-id r1
|
||||
```
|
||||
|
||||
`approve` is **not** an expert verdict, and does not become one: it says the candidate may
|
||||
stand, not that the measure was reviewed and judged. The verdict still arrives the way it
|
||||
always did — `--decision`/`--rationale` on the run, or a verdict file dropped into
|
||||
`--verdict-dir` for a later run to read.
|
||||
|
||||
The door belongs to single-project mode, and every other combination is refused by name rather
|
||||
than quietly ignored: `--portfolio` (its waves share one terminal, so several projects'
|
||||
candidates would arrive at the same prompt with nothing to tell them apart), `--report` (a
|
||||
read-only roll-up generates nothing to review), `--live-dry-run` (it stops before the first
|
||||
model call, so no candidate ever reaches you), `--proposals-from-mandate` (that mode settles the
|
||||
commission deterministically and never generates a candidate), and `--checkpoint-dir` (a parked
|
||||
exploration returns before any candidate exists — pass `--proposal-review` at `--resume`
|
||||
instead, which is where the two doors do compose).
|
||||
|
||||
Like the plan review it is **synchronous**, so the hosted surface refuses it by name and points
|
||||
at this CLI. With `--explore --plan-review --proposal-review` you answer two different doors
|
||||
from the same terminal in sequence: first the plan, then each candidate the validator accepts.
|
||||
|
||||
- **Deriving the cost baseline from the knowledge base** — `--derive-cost-baseline` (opt-in,
|
||||
requires `--bundle-dir`). The deterministic validator anchors a proposal's `affected_items` to
|
||||
the project's actual cost lines. Normally those come from a hand-written `cost-baseline.json` in
|
||||
|
|
|
|||
|
|
@ -1501,3 +1501,55 @@ def test_run_portfolio_takes_no_reviewer() -> None:
|
|||
is the ``--portfolio`` partition's own reason — so a later "symmetry" edit must be a RED test,
|
||||
not a silent widening."""
|
||||
assert "proposal_reviewer" not in inspect.signature(run.run_portfolio).parameters
|
||||
|
||||
|
||||
def test_the_readme_block_names_every_flag_the_cli_refuses_the_door_with() -> None:
|
||||
"""The README-claim arm, in the Fase-3 form (``test_public_surface_claims_loadbearing``):
|
||||
a claim the PUBLISHED surface makes about itself, read as RAW TEXT, because prose is the one
|
||||
thing no behavioural test can see.
|
||||
|
||||
The door has five partner refusals. A README block that documents the door but omits one of
|
||||
them sends a stranger into a refusal the page said nothing about — the same drift the wheel
|
||||
filename gate exists for, and the reason the handover gate matches names rather than prose.
|
||||
|
||||
The block is EXTRACTED (the ``--proposal-review`` prose block, from its bold heading to the
|
||||
next bold heading) rather than substring-matched over the whole file, because ``--portfolio``,
|
||||
``--report`` and ``--checkpoint-dir`` all appear elsewhere in the README for their own reasons
|
||||
— a whole-file check would be green on exactly the prose it is supposed to protect.
|
||||
|
||||
This arm carries no M-number: the M-list closed at M40. It was driven RED TWICE, because
|
||||
"the block is missing" and "the block is incomplete" are different failures and only the
|
||||
second is what this gate is for. (i) Written and measured against the README as it stood
|
||||
BEFORE the block was added — extraction found nothing, and ``assert block`` failed. (ii) With
|
||||
the block in place, the ``--checkpoint-dir`` sentence was rewritten to describe the refusal
|
||||
without NAMING the flag; the partner loop went red on its own. The control that the extractor
|
||||
is not silently finding nothing is the ``--plan-review`` block at the end, which has existed
|
||||
since F4.
|
||||
"""
|
||||
readme = (_REPO / "README.md").read_text(encoding="utf-8")
|
||||
block = _readme_block(readme, "Answering the proposal review (`--proposal-review`)")
|
||||
assert block, "the README must carry a --proposal-review prose block"
|
||||
for partner in (
|
||||
"--portfolio",
|
||||
"--report",
|
||||
"--live-dry-run",
|
||||
"--proposals-from-mandate",
|
||||
"--checkpoint-dir",
|
||||
):
|
||||
assert partner in block, f"the README block never names the refusal with {partner}"
|
||||
assert "--resume" in block, "the --checkpoint-dir refusal must name the door that DOES work"
|
||||
assert "OKF bundle" not in block, "customer-facing prose says knowledge base, never OKF bundle"
|
||||
|
||||
# CONTROL: the extractor finds a block that has existed since F4, so an empty result above is
|
||||
# a missing block and not a broken extractor.
|
||||
plan_block = _readme_block(readme, "Answering the plan review (`--plan-review`)")
|
||||
assert plan_block and "enable_plan_review" in plan_block
|
||||
|
||||
|
||||
def _readme_block(readme: str, heading: str) -> str:
|
||||
marker = f"**{heading}.**"
|
||||
start = readme.find(marker)
|
||||
if start == -1:
|
||||
return ""
|
||||
nxt = readme.find("\n **", start + len(marker))
|
||||
return readme[start : nxt if nxt != -1 else len(readme)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue