goldenreq 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
goldenreq/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """goldenreq — the ACCEPTANCE GATE for goldenization.
2
+
3
+ THE LAW (gauge ≠ handler). A *gauge* is an internal self-assessment: the
4
+ bandit's 2×🏆 on a chain, a rulecatcher candidate, a forged template, a
5
+ personapack grade claim. A gauge may detect quality but it may NEVER certify
6
+ itself. Promotion to golden/adopted/canon happens only through a *handler* — an
7
+ external acceptance authority. This package is that gate.
8
+
9
+ - `queue.file_request(...)` — a gauge FILES a request carrying its receipts
10
+ (evidence). That is the only move a gauge gets. The request sits pending.
11
+ - `verdict.accept / verdict.reject` — a handler rules. Only `accept` fires the
12
+ promotion effect (`effects.dispatch`). `reject` carries a reason.
13
+
14
+ Requests carry receipts; verdicts are the seed corpus for the learned acceptance
15
+ model that will one day staff this gate; rejections (with reasons) are its hard
16
+ negatives. The verdict record mirrors base/acceptance's `Verdict` schema so the
17
+ corpus is drop-in for that reader (field mapping: see `verdict.py`).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ __version__ = "0.3.0"
22
+
23
+ from .queue import KINDS, file_request, list_pending, get_request
24
+ from .verdict import accept, reject, list_verdicts
25
+ from . import effects
26
+
27
+ __all__ = [
28
+ "KINDS", "file_request", "list_pending", "get_request",
29
+ "accept", "reject", "list_verdicts", "effects", "__version__",
30
+ ]
goldenreq/cli.py ADDED
@@ -0,0 +1,60 @@
1
+ """cli.py — `goldenreq list | accept | reject` (argparse, stdlib only)."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+
8
+ from .queue import list_pending
9
+ from .verdict import accept, reject
10
+
11
+
12
+ def main(argv: list[str] | None = None) -> int:
13
+ p = argparse.ArgumentParser(prog="goldenreq",
14
+ description="the acceptance gate for goldenization — nothing self-promotes")
15
+ p.add_argument("--queue", default=None, help="queue JSONL path ($GOLDENREQ_QUEUE_PATH)")
16
+ sub = p.add_subparsers(dest="cmd", required=True)
17
+
18
+ lp = sub.add_parser("list", help="list pending requests (the handler's inbox)")
19
+ lp.add_argument("--json", action="store_true", help="emit raw JSON lines")
20
+
21
+ ap = sub.add_parser("accept", help="accept a request → fire its promotion effect")
22
+ ap.add_argument("request_id")
23
+ ap.add_argument("--by", required=True, help="ruling actor (human:<who> / model:<id>)")
24
+ ap.add_argument("--reason", default="")
25
+
26
+ rp = sub.add_parser("reject", help="reject a request (reason = hard negative)")
27
+ rp.add_argument("request_id")
28
+ rp.add_argument("--by", required=True, help="ruling actor (human:<who> / model:<id>)")
29
+ rp.add_argument("--reason", required=True)
30
+
31
+ args = p.parse_args(argv)
32
+
33
+ if args.cmd == "list":
34
+ pending = list_pending(args.queue)
35
+ if args.json:
36
+ for r in pending:
37
+ print(json.dumps(r, sort_keys=True))
38
+ elif not pending:
39
+ print("no pending requests")
40
+ else:
41
+ for r in pending:
42
+ print(f"{r['request_id']} {r['kind']:<16} {r['candidate_id']} "
43
+ f"→ {r['requested_promotion']} (by {r['requester']}, "
44
+ f"{len(r.get('evidence', []))} receipt(s))")
45
+ return 0
46
+
47
+ try:
48
+ if args.cmd == "accept":
49
+ v = accept(args.request_id, args.by, args.reason, queue_path=args.queue)
50
+ else:
51
+ v = reject(args.request_id, args.by, args.reason, queue_path=args.queue)
52
+ except (ValueError, FileNotFoundError, NotImplementedError) as e:
53
+ print(f"error: {e}", file=sys.stderr)
54
+ return 1
55
+ print(json.dumps(v, sort_keys=True, indent=2))
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ raise SystemExit(main())
goldenreq/effects.py ADDED
@@ -0,0 +1,105 @@
1
+ """effects.py — the promotion effects, dispatched ONLY on an accepted verdict.
2
+
3
+ `dispatch` routes an accepted request to its effect by `kind`. An effect is the
4
+ thing the gauge was NOT allowed to do itself; here it runs because a handler
5
+ accepted. Each returns an `outcome` dict that verdict.py stores in the record.
6
+
7
+ Honesty rule: two effects are real (they have a testable local target), two are
8
+ stubs (they'd need cross-package wiring we cannot exercise here). The stubs
9
+ raise `NotImplementedError` and point at the exact upstream call to make — we do
10
+ NOT fake an integration we can't test.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+ # the personapack activation_grade ladder, BEST → WORST.
17
+ # Mirror of personapack.entry.GRADES (kept as a literal so goldenreq stays
18
+ # dependency-free; a drift here is caught by the pack-grade-bump test).
19
+ PACK_GRADES: tuple[str, ...] = ("tested-with-receipt", "proven-in-forge", "seed")
20
+
21
+
22
+ def dispatch(request: dict, *, by: str, **kwargs) -> dict:
23
+ """Route an ACCEPTED request to its promotion effect. Returns the outcome dict."""
24
+ kind = request.get("kind")
25
+ if kind == "chain-golden":
26
+ return mark_chain_golden(request, by=by, **kwargs)
27
+ if kind == "pack-grade-bump":
28
+ new_grade = bump_pack_grade(request.get("requested_promotion") or _current_from(request))
29
+ return {"effect": "pack-grade-bump", "new_grade": new_grade,
30
+ "note": "pure result — personapack applies it to the PackEntry"}
31
+ if kind == "rule-adopt":
32
+ return adopt_rule(request, by=by, **kwargs)
33
+ if kind == "template-promote":
34
+ return promote_template(request, by=by, **kwargs)
35
+ raise ValueError(f"no effect for kind {request.get('kind')!r}")
36
+
37
+
38
+ def _current_from(request: dict) -> str:
39
+ # if requested_promotion wasn't a grade, fall back to the weakest rung
40
+ return "seed"
41
+
42
+
43
+ # ── chain-golden: append an ACCEPTED-BY marker into the GBA KB note ──────────
44
+ def mark_chain_golden(request: dict, *, by: str, **_) -> dict:
45
+ """Write the golden marker into the GBA KB.
46
+
47
+ `candidate_id` is the path to the graded KB note (a `kb/<topic>.md`, headed
48
+ with a `glyphs:` grade line — see chainaios bandit.kb_instructions). We
49
+ APPEND an ACCEPTED-BY line; we NEVER rewrite the agent's own notes. The
50
+ marker is what turns "the gauge claims 🏆🏆" into "a handler certified it
51
+ golden" — the thing self-folding used to do without authority.
52
+ """
53
+ note = Path(request["candidate_id"])
54
+ if not note.exists():
55
+ raise FileNotFoundError(f"KB note not found: {note}")
56
+ marker = (f"\nACCEPTED-BY: {by} — goldenized on request {request['request_id']} "
57
+ f"(promotion: {request.get('requested_promotion')}); "
58
+ f"evidence: {', '.join(request.get('evidence', [])) or 'none'}. 🏆🏆 → golden\n")
59
+ with open(note, "a", encoding="utf-8") as f:
60
+ f.write(marker)
61
+ return {"effect": "chain-golden", "kb_note": str(note), "marker": marker.strip()}
62
+
63
+
64
+ # ── pack-grade-bump: a PURE function personapack applies itself ──────────────
65
+ def bump_pack_grade(current_grade: str) -> str:
66
+ """Return the next-higher personapack `activation_grade`, or the same grade
67
+ if already at the top. PURE — personapack constructs the new PackEntry with it.
68
+
69
+ Ladder (best→worst): tested-with-receipt > proven-in-forge > seed.
70
+ A bump moves ONE rung toward tested-with-receipt.
71
+ """
72
+ if current_grade not in PACK_GRADES:
73
+ raise ValueError(f"grade {current_grade!r} not in {PACK_GRADES}")
74
+ i = PACK_GRADES.index(current_grade)
75
+ return PACK_GRADES[max(0, i - 1)]
76
+
77
+
78
+ # ── honest stubs: real cross-package integration we cannot test here ─────────
79
+ def adopt_rule(request: dict, *, by: str, **_) -> dict:
80
+ """STUB. A real rule-adopt would call rulecatcher's adopt path:
81
+
82
+ rulecatcher.engine.adopt_rules(connection, rule_ids, actor=by,
83
+ source="goldenreq", reason=<verdict reason>)
84
+
85
+ (see packages/rulecatcher/src/rulecatcher/engine.py, `adopt_rules`).
86
+ That needs a live rulecatcher sqlite `Connection` and real candidate rule ids
87
+ threaded through the request — wiring goldenreq cannot construct or exercise
88
+ in isolation. Not faked.
89
+ """
90
+ raise NotImplementedError(
91
+ "rule-adopt: wire request.candidate_id (rule ids) + a rulecatcher "
92
+ "Connection to rulecatcher.engine.adopt_rules(actor=by, source='goldenreq'). "
93
+ "Deliberately unimplemented — no fake integration.")
94
+
95
+
96
+ def promote_template(request: dict, *, by: str, **_) -> dict:
97
+ """STUB. A real template-promote would move a forged language/template from
98
+ candidate into the canonical set (the chainaios `construct_language` /
99
+ template store). That store's promotion API is not defined yet, so there is
100
+ nothing honest to call. Not faked.
101
+ """
102
+ raise NotImplementedError(
103
+ "template-promote: no canonical-template promotion API exists to call "
104
+ "yet; when one does, dispatch request.candidate_id to it here. "
105
+ "Deliberately unimplemented — no fake integration.")
goldenreq/queue.py ADDED
@@ -0,0 +1,132 @@
1
+ """queue.py — the goldenization request queue (append-only JSONL).
2
+
3
+ Same append-only ledger idiom as the KB reward record and base/acceptance's
4
+ verdict corpus (a `write` is one JSON line; nothing is ever mutated in place).
5
+
6
+ A REQUEST is a *gauge-passed, handler-pending* event: a gauge — the bandit's
7
+ 2×🏆 on a constructed chain, a rulecatcher candidate rule, a forged
8
+ language/template, a personapack `activation_grade` claim — asks for a
9
+ promotion it may NOT grant itself. The request carries its RECEIPTS (evidence)
10
+ and sits pending until a handler rules on it (see ``verdict.py``). This is
11
+ JobWorld's ``supposedly_done`` made explicit, and base/acceptance's
12
+ ``handler.verdict == "escalate"`` (gauge-passed, handler-pending) as a queue.
13
+
14
+ THE LAW: the gauge never certifies itself. Filing a request is the ONLY thing a
15
+ gauge may do toward promotion; the promotion effect fires only on an accepted
16
+ verdict.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import uuid
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+
26
+ # the four things that can be promoted (each has a real gauge upstream)
27
+ KINDS: tuple[str, ...] = (
28
+ "chain-golden", # a constructed bandit chain earned 🏆 twice (chainaios)
29
+ "rule-adopt", # a rulecatcher candidate rule wants adoption
30
+ "template-promote", # a forged language / template wants to become canon
31
+ "pack-grade-bump", # a personapack PackEntry wants a higher activation_grade
32
+ )
33
+
34
+ QUEUE_ENV = "GOLDENREQ_QUEUE_PATH"
35
+ VERDICTS_ENV = "GOLDENREQ_VERDICTS_PATH"
36
+ DEFAULT_QUEUE = "goldenreq_queue.jsonl"
37
+ DEFAULT_VERDICTS = "verdicts.jsonl"
38
+
39
+
40
+ def now_iso() -> str:
41
+ return datetime.now(timezone.utc).isoformat()
42
+
43
+
44
+ def resolve_queue_path(path: str | os.PathLike | None = None) -> Path:
45
+ return Path(path or os.environ.get(QUEUE_ENV) or DEFAULT_QUEUE)
46
+
47
+
48
+ def resolve_verdicts_path(queue_path: str | os.PathLike | None = None) -> Path:
49
+ """The verdicts corpus sits beside the queue (or at $GOLDENREQ_VERDICTS_PATH).
50
+ Mirrors base/acceptance's `resolve_corpus_path` — env override, else default."""
51
+ env = os.environ.get(VERDICTS_ENV)
52
+ if env:
53
+ return Path(env)
54
+ return resolve_queue_path(queue_path).parent / DEFAULT_VERDICTS
55
+
56
+
57
+ def _append(path: Path, record: dict) -> None:
58
+ path.parent.mkdir(parents=True, exist_ok=True)
59
+ with open(path, "a", encoding="utf-8") as f:
60
+ f.write(json.dumps(record, sort_keys=True) + "\n")
61
+
62
+
63
+ def _read_all(path: Path) -> list[dict]:
64
+ out: list[dict] = []
65
+ if not Path(path).exists():
66
+ return out
67
+ with open(path, "r", encoding="utf-8") as f:
68
+ for line in f:
69
+ line = line.strip()
70
+ if not line:
71
+ continue
72
+ try:
73
+ out.append(json.loads(line))
74
+ except Exception:
75
+ continue
76
+ return out
77
+
78
+
79
+ def new_request_id() -> str:
80
+ return "gr-" + uuid.uuid4().hex[:24]
81
+
82
+
83
+ def file_request(kind: str, candidate_id: str, evidence: list[str],
84
+ requested_promotion: str, requester: str,
85
+ queue_path: str | os.PathLike | None = None) -> str:
86
+ """Append one goldenization request to the queue. Returns its request_id.
87
+
88
+ `evidence` = the receipts (graded KB note paths, cert ids, forge reports).
89
+ A request with no evidence is still filed (honest — an unevidenced ask is a
90
+ weak ask a handler will likely reject), but evidence is what an acceptance
91
+ model later learns from, so gauges SHOULD attach it.
92
+ """
93
+ if kind not in KINDS:
94
+ raise ValueError(f"kind {kind!r} not in {KINDS}")
95
+ rid = new_request_id()
96
+ rec = {
97
+ "request_id": rid,
98
+ "ts": now_iso(),
99
+ "kind": kind,
100
+ "candidate_id": candidate_id,
101
+ "evidence": list(evidence or []),
102
+ "requested_promotion": requested_promotion,
103
+ "requester": requester,
104
+ "status": "pending",
105
+ }
106
+ _append(resolve_queue_path(queue_path), rec)
107
+ return rid
108
+
109
+
110
+ def get_request(request_id: str, queue_path: str | os.PathLike | None = None) -> dict | None:
111
+ for rec in _read_all(resolve_queue_path(queue_path)):
112
+ if rec.get("request_id") == request_id:
113
+ return rec
114
+ return None
115
+
116
+
117
+ def ruled_request_ids(queue_path: str | os.PathLike | None = None) -> set[str]:
118
+ """request_ids that already carry a handler verdict (accept OR reject)."""
119
+ vpath = resolve_verdicts_path(queue_path)
120
+ return {v.get("context_ref") for v in _read_all(vpath) if v.get("context_ref")}
121
+
122
+
123
+ def list_pending(queue_path: str | os.PathLike | None = None) -> list[dict]:
124
+ """Requests with no handler verdict yet — the handler's inbox.
125
+
126
+ A request is pending ⇔ no verdict record points at it (verdict.context_ref
127
+ == request_id). That is base/acceptance's `escalate` state: gauge-passed,
128
+ handler-pending.
129
+ """
130
+ ruled = ruled_request_ids(queue_path)
131
+ return [r for r in _read_all(resolve_queue_path(queue_path))
132
+ if r.get("request_id") not in ruled]
goldenreq/verdict.py ADDED
@@ -0,0 +1,133 @@
1
+ """verdict.py — the HANDLER ruling on a goldenization request.
2
+
3
+ `accept` / `reject` are the ONLY authority in this package. Nothing self-promotes:
4
+ a promotion effect (effects.py) fires only when a handler `accept`s. A `reject`
5
+ carries a reason — those reasons are the HARD NEGATIVES for the learned
6
+ acceptance model that later staffs this gate.
7
+
8
+ Verdict record shape — MIRRORS base/acceptance's `acceptance.schema.Verdict`
9
+ (read via `git show acceptance-handler:base/acceptance/acceptance/schema.py`).
10
+ We do NOT import across branches; we reproduce the shape as a plain dict so the
11
+ verdicts corpus is drop-in compatible with that reader. Field mapping:
12
+
13
+ base/acceptance Verdict field ← goldenreq source
14
+ ------------------------------ ----------------------------------------
15
+ id ← a fresh verdict id ("vd-…")
16
+ ts ← ruling timestamp (ISO-8601)
17
+ domain ← "goldenize" (base lists this exact domain)
18
+ artifact_ref ← request.candidate_id (what was judged)
19
+ context_ref ← request.request_id (the request it belongs to)
20
+ gauge.source ← request.requester (the self-claiming gauge)
21
+ gauge.score ← request.requested_promotion (the gauge's claim)
22
+ gauge.reasons ← request.evidence (the receipts it filed)
23
+ handler.verdict ← "accept" | "reject" (base also has "escalate"
24
+ = still-pending; a pending request is exactly
25
+ that state, so it has NO verdict record yet)
26
+ handler.actor ← `by`, coerced to "human:<who>" / "model:<id>"
27
+ handler.reasons ← [reason] (rejections MUST carry one)
28
+ handler.edits_made ← None (goldenreq handlers rule, they don't edit)
29
+ outcome ← the promotion effect's result on accept, else None
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import re
35
+ import uuid
36
+
37
+ from . import effects
38
+ from .queue import (
39
+ get_request, now_iso, resolve_queue_path, resolve_verdicts_path,
40
+ ruled_request_ids, _append, _read_all,
41
+ )
42
+
43
+ ACTOR_PATTERN = re.compile(r"^(human|model):.+$") # base/acceptance schema.ACTOR_PATTERN
44
+
45
+
46
+ def new_verdict_id() -> str:
47
+ return "vd-" + uuid.uuid4().hex[:24]
48
+
49
+
50
+ def _actor(by: str) -> str:
51
+ """Coerce `by` to the base schema's `^(human|model):.+$` actor form.
52
+ A bare name is assumed human (the default handler is a person doing QA)."""
53
+ by = (by or "").strip()
54
+ if not by:
55
+ raise ValueError("a verdict requires `by` (the ruling actor)")
56
+ return by if ACTOR_PATTERN.match(by) else f"human:{by}"
57
+
58
+
59
+ def _build_verdict(request: dict, decision: str, by: str,
60
+ reasons: list[str], outcome: dict | None) -> dict:
61
+ """One acceptance event, in base/acceptance Verdict shape (see module docstring)."""
62
+ return {
63
+ "id": new_verdict_id(),
64
+ "ts": now_iso(),
65
+ "domain": "goldenize",
66
+ "artifact_ref": request["candidate_id"],
67
+ "context_ref": request["request_id"],
68
+ "gauge": {
69
+ "source": request.get("requester", ""),
70
+ "score": request.get("requested_promotion"),
71
+ "reasons": list(request.get("evidence", [])),
72
+ },
73
+ "handler": {
74
+ "verdict": decision,
75
+ "actor": _actor(by),
76
+ "reasons": list(reasons),
77
+ "edits_made": None,
78
+ },
79
+ "outcome": outcome,
80
+ }
81
+
82
+
83
+ def _guard(request_id: str, queue_path) -> dict:
84
+ request = get_request(request_id, queue_path=queue_path)
85
+ if request is None:
86
+ raise ValueError(f"no such request: {request_id}")
87
+ if request_id in ruled_request_ids(queue_path):
88
+ raise ValueError(f"request {request_id} already has a verdict (no double-ruling)")
89
+ return request
90
+
91
+
92
+ def accept(request_id: str, by: str, reason: str = "", *,
93
+ queue_path: str | os.PathLike | None = None,
94
+ verdicts_path: str | os.PathLike | None = None,
95
+ effect_kwargs: dict | None = None) -> dict:
96
+ """ACCEPT a request → fire its promotion effect, then record the verdict.
97
+
98
+ The effect runs BEFORE the record is written so its result is captured in
99
+ `outcome` (the audit trail). If the effect raises, nothing is recorded and
100
+ the request stays pending — no half-promotion. Returns the verdict record.
101
+ """
102
+ from pathlib import Path
103
+ request = _guard(request_id, queue_path)
104
+ outcome = effects.dispatch(request, by=_actor(by), **(effect_kwargs or {}))
105
+ verdict = _build_verdict(request, "accept", by, [reason] if reason else [], outcome)
106
+ vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
107
+ _append(vpath, verdict)
108
+ return verdict
109
+
110
+
111
+ def reject(request_id: str, by: str, reason: str, *,
112
+ queue_path: str | os.PathLike | None = None,
113
+ verdicts_path: str | os.PathLike | None = None) -> dict:
114
+ """REJECT a request → NO effect, record the verdict with its reason.
115
+
116
+ The reason is mandatory: rejections are the hard negatives the learned
117
+ acceptance model trains on. Nothing is promoted.
118
+ """
119
+ if not (reason or "").strip():
120
+ raise ValueError("reject requires a reason (it is the hard negative)")
121
+ request = _guard(request_id, queue_path)
122
+ verdict = _build_verdict(request, "reject", by, [reason], None)
123
+ from pathlib import Path
124
+ vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
125
+ _append(vpath, verdict)
126
+ return verdict
127
+
128
+
129
+ def list_verdicts(queue_path: str | os.PathLike | None = None,
130
+ verdicts_path: str | os.PathLike | None = None) -> list[dict]:
131
+ from pathlib import Path
132
+ vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
133
+ return _read_all(vpath)
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: goldenreq
3
+ Version: 0.3.0
4
+ Summary: The acceptance gate for goldenization: gauges FILE requests carrying receipts; a handler ACCEPTS/REJECTS; only an accept fires the promotion effect. Nothing self-promotes.
5
+ Requires-Python: >=3.11
@@ -0,0 +1,10 @@
1
+ goldenreq/__init__.py,sha256=Y95IRc1wCYU-X3rVkF4hcPVOtVDmbu9nSDXXdXRAG0Q,1391
2
+ goldenreq/cli.py,sha256=so4bcVTInq5oRsXf7hdTORBXe2VtNUZHQ29ELenDPdk,2306
3
+ goldenreq/effects.py,sha256=6O5twoewSxkHVU4RxLfBTxKP2QaHJz6hZqkOqbR8o0o,5208
4
+ goldenreq/queue.py,sha256=Qus0wAGuF_YdKK6HkXpzshg9KwXJob13TIl_QU1DxGQ,4991
5
+ goldenreq/verdict.py,sha256=SMqN2cxMkcXvvabjsqBw98H_TqSCMg-HGeI83Z2BIXg,6010
6
+ goldenreq-0.3.0.dist-info/METADATA,sha256=UOF9FDyZmLAG_7O7TiwX1N1GMGiVtB_8_ASd4cAQZJk,258
7
+ goldenreq-0.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
8
+ goldenreq-0.3.0.dist-info/entry_points.txt,sha256=fWR8LNGor1SxWas2Fj6AmVOxeDGvvjVYmkCboe5dlLc,49
9
+ goldenreq-0.3.0.dist-info/top_level.txt,sha256=zddDlHNcMlMYu0sgmXNVNleddIlCAeg2Oauf5c0KyAQ,10
10
+ goldenreq-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ goldenreq = goldenreq.cli:main
@@ -0,0 +1 @@
1
+ goldenreq