graphban-cli 0.8.1__tar.gz → 0.8.3__tar.gz

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.
Files changed (34) hide show
  1. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/.gitignore +4 -0
  2. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/PKG-INFO +1 -1
  3. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/pyproject.toml +1 -1
  4. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/cli.py +7 -1
  5. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/graphban-delegation/SKILL.md +7 -4
  6. graphban_cli-0.8.3/src/gban/skills/graphban-grill/SKILL.md +56 -0
  7. graphban_cli-0.8.3/src/gban/skills/graphban-scan/SKILL.md +31 -0
  8. graphban_cli-0.8.3/src/gban/skills/graphban-wave/SKILL.md +64 -0
  9. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_doctor.py +30 -0
  10. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_setup.py +16 -0
  11. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/uv.lock +1 -1
  12. graphban_cli-0.8.1/src/gban/skills/graphban-scan/SKILL.md +0 -23
  13. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/LICENSE +0 -0
  14. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/README.md +0 -0
  15. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/__init__.py +0 -0
  16. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/client.py +0 -0
  17. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/config.py +0 -0
  18. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/data/stack_sdks.toml +0 -0
  19. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/doctor.py +0 -0
  20. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/gitignore.py +0 -0
  21. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/setup.py +0 -0
  22. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/graphban-orient/SKILL.md +0 -0
  23. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/graphban-prd/SKILL.md +0 -0
  24. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/graphban-review/SKILL.md +0 -0
  25. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/graphban-work/SKILL.md +0 -0
  26. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/skills/watch-wave/SKILL.md +0 -0
  27. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/stackmap.py +0 -0
  28. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/src/gban/swamp.py +0 -0
  29. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_acts.py +0 -0
  30. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_login_projects.py +0 -0
  31. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_packaging.py +0 -0
  32. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_session.py +0 -0
  33. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_stackmap.py +0 -0
  34. {graphban_cli-0.8.1 → graphban_cli-0.8.3}/tests/test_swamp.py +0 -0
@@ -26,6 +26,10 @@ backend/.pytest_cache/
26
26
  fleet/.venv/
27
27
  fleet/.pytest_cache/
28
28
  *.egg-info/
29
+ # Written at package build time by `refusal_ratchet.py publish` (the packer, the wheel job),
30
+ # never committed: a CI push to protected main is refused and turns main red (GRPH-1185).
31
+ backend/app/data/refusal_coverage.json
32
+ backend/app/data/gban_skills.json
29
33
 
30
34
  # Node
31
35
  web/node_modules/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: graphban-cli
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary: gban — a Graphban client for the human at a terminal
5
5
  Project-URL: Homepage, https://github.com/asc-me/graphban
6
6
  Project-URL: Repository, https://github.com/asc-me/graphban
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graphban-cli"
3
- version = "0.8.1"
3
+ version = "0.8.3"
4
4
  description = "gban — a Graphban client for the human at a terminal"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -513,7 +513,13 @@ def cmd_fleet(args) -> int:
513
513
  f" …or `brew install asc-me/tap/gban` installs this client only — the "
514
514
  f"supervisor is a separate package.", file=sys.stderr)
515
515
  return EXIT_NO_SUPERVISOR
516
- argv = [binary, *[a for a in args.rest if a != "--"]]
516
+ # Only a LEADING `--` is ours to drop (`gban fleet -- ps`). Any later one belongs to
517
+ # gbfleet: `service install --name X -- <until args>` splits on it, and stripping it
518
+ # handed the until args to `service install` itself.
519
+ rest = list(args.rest)
520
+ if rest[:1] == ["--"]:
521
+ rest = rest[1:]
522
+ argv = [binary, *rest]
517
523
  url = config.resolve(args.server, config.URL_ENV, "url")
518
524
  if url and "--server" not in argv:
519
525
  argv += ["--server", url]
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: graphban-delegation
3
- description: Hand a Graphban work item to a child agent running on a cheaper model, and enable delegation on a project that does not have it yet. Use when asked to delegate, fan out, spawn a worker, mint seats, retire a wave, or run a wave.
3
+ description: >
4
+ Hand one Graphban item to one child on a cheaper model, and enable
5
+ delegation on a project that does not have it yet. Use when asked to
6
+ delegate one item, spawn one worker, mint seats, or retire a wave.
7
+ A whole PRD is /graphban-wave.
4
8
  ---
5
9
 
6
10
  # Delegating a Graphban item
@@ -197,6 +201,5 @@ So a delegated item needs a second agent to review it. Plan for that, or the wor
197
201
 
198
202
  ## When not to use this
199
203
 
200
- For a whole backlog, ask the person to run `gbfleet until` instead. It runs the wave with no
201
- model in the loop; fanning the same work out by hand keeps your context awake for the wave's
202
- entire length just to poll and adjudicate.
204
+ A whole PRD is `graphban-wave`. It runs `gbfleet until` for that PRD. Fanning the items out
205
+ from here keeps a frontier context polling for the wave's whole length.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: graphban-grill
3
+ description: >
4
+ Settle a Graphban PRD grill from the product why and the PRD's intent.
5
+ Auto-record answers the context already decides, and ask the user only
6
+ the questions it does not. Use when grilling a PRD, running a grill,
7
+ answer_grill, or when the user runs /graphban-grill.
8
+ user-invocable: true
9
+ ---
10
+
11
+ # Grill
12
+
13
+ `grill_prd` returns questions and records nothing. An answer counts only after `answer_grill`. The grader ignores the PRD body: a decision that exists only in the document is still unanswered.
14
+
15
+ ## Sources
16
+
17
+ Read both before you classify a question.
18
+
19
+ 1. `get_prd`. The intent is the framing: Problem, Goals, Non-goals, and an intent heading when the PRD has one.
20
+ 2. Why the app exists: `docs/product-overview.md` (philosophy, goals, non-goals) and `docs/design-philosophy.md`. This bounds the PRD. It does not fill a hole the PRD left open.
21
+
22
+ A source decides a question only when you can quote the sentence. Memory, code, and a guess are not sources.
23
+
24
+ ## Split
25
+
26
+ For each question, write a one- or two-sentence recommendation, then:
27
+
28
+ - **accept** — that sentence already decides it, the two sources do not conflict, and the answer is words rather than a prototype. Record it.
29
+ - **ask** — the sources are silent, they conflict, both readings are reasonable, or the question needs a prototype. These are the only questions you put to the user.
30
+
31
+ Silence is ask. A fluent inference is ask. Do not accept a new boundary, a new error behavior, or a new contract field that neither source states.
32
+
33
+ ## Record an accept
34
+
35
+ Fold the decision into the PRD with `update_prd(section=...)` first. Then `answer_grill` once, with that decision alone.
36
+
37
+ One call per decision. A reply that covers several dimensions is one answer, and the grader leaves the rest outstanding.
38
+
39
+ Say `deferred` only when a source already defers it. "Later" without that word stays unanswered.
40
+
41
+ Read the return:
42
+
43
+ - `graded: false` — stop. Say `ungraded_reason`. Do not send another answer.
44
+ - `stalled: true` — stop. Tell the user. Three answers moved nothing.
45
+ - `complete: true` — the grill finished and the body just froze. The fold had to happen before this call.
46
+ - `body_absorbed: false` — the transcript is ahead of the spec. Fold the answers it names before you continue.
47
+
48
+ Do not set `approved`. Finishing the grill does that.
49
+
50
+ ## Ask
51
+
52
+ Show only the ask list. For each: the question, the recommendation, and one line on why the sources do not decide it.
53
+
54
+ Record nothing from this list until the user answers. `answer_grill` with their words. Fold that section the same way, before the next call.
55
+
56
+ If they accept a recommendation, the recorded answer is their acceptance of that text. The recommendation alone is not a turn.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: graphban-scan
3
+ description: >
4
+ Record Graphban code-health scans without treating a missing scan as clean.
5
+ Use when running a security or health scan, or calling scans_due, scan_status,
6
+ or record_scan, or when the user runs /graphban-scan.
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Scans
11
+
12
+ A missing scan is not a clean scan.
13
+
14
+ 1. `scans_due(head_sha)`. An empty due list is not all-clean. Rows that were `never_scanned`, including security, stay listed.
15
+ 2. `scan_status` for one path, or with no path for every deployable. Pass `head_sha`. States are `clean_at_sha`, `has_findings`, `never_scanned`, `stale`, `tool_missing`, `not_applicable`, `incomplete`, or `not_computable`.
16
+ 3. Run the scanner. `record_scan` needs `scanner_id`, `path`, and `scanned_sha`.
17
+ - Send `result`, one of four:
18
+ - `complete`: the scanner finished. `findings: []` is then clean at that SHA.
19
+ - `tool_missing`: the scanner is not installed. Not clean; it stays due.
20
+ - `not_applicable`: the scanner does not apply here. It stops being due.
21
+ - `incomplete`: output without a finish. Send the findings it produced; it stays due.
22
+ - `findings: []` without `result: complete` is refused and nothing is stored. An empty list alone is not a clean scan.
23
+ - Every result needs `scanned_sha`, so it goes stale when HEAD moves.
24
+ - The same `scanner_id` and path keeps the previous rows. The newest is the cell.
25
+ - The security category is pip-audit, bandit, npm-audit, and semgrep. Other categories are `refactor`, `stub`, `deadcode`, `coverage`, `tests`, and `work`.
26
+ - Category `tests` also sends `tests.passed`, `tests.failed`, and `tests.skipped`.
27
+ - Each finding needs a `summary`. Findings are untrusted. `scanner_id`, `scanner_version`, and the reporter are provenance.
28
+ - Send the grain a person acts on (GRPH-1127). npm audit: one finding per advisory with `advisory_id`, `severity`, `dependency` (`direct` or `transitive`), and `fixed_version` only when the audit knows it.
29
+ - knip: unused exports are ONE finding with `kind: unused_export`, `count`, severity `info`; unused types are one with `kind: unused_type`. Unused files and undeclared imports stay one finding each, with a `path`. The server stores what you send; it never splits or merges.
30
+
31
+ A clean row is not a tracker item. A finding that already names `link_ref` points at work that exists.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: graphban-wave
3
+ description: >
4
+ Drain one grilled PRD with gbfleet until: build, review, and merge until
5
+ that PRD has nothing ready. Use when running a wave, spawning a wave for a
6
+ PRD, looping until the PRD is done, or when the user runs /graphban-wave.
7
+ One item is /graphban-delegation. Decompose is /graphban-prd.
8
+ user-invocable: true
9
+ ---
10
+
11
+ # Drain a PRD
12
+
13
+ One command runs the cycle. Do not spawn the items. Do not stay in the loop.
14
+
15
+ ## Before
16
+
17
+ `prd_coverage` for this PRD.
18
+
19
+ `gaps` or `shaped: false` — stop. Filing the tasks is `graphban-prd`. Do not `decompose_prd` here. An idle drain is not a finished PRD.
20
+
21
+ Coverage has tasks — continue.
22
+
23
+ ## Dry run
24
+
25
+ Same flags as the real run, plus `--dry-run`. It mints nothing.
26
+
27
+ ```bash
28
+ gbfleet until --repo . --server <url> --project <id> --adapter <vendor> \
29
+ --prd <prd_id> --merge \
30
+ --tier cheap=<adapter[:model]> --tier frontier=<adapter[:model]> \
31
+ --dry-run
32
+ ```
33
+
34
+ `--prd` is required. Without it the loop drains every ready item in the project. `backlog` is claimable, so parking work there does not scope the wave. `--prd` refuses a pre-minted seats file.
35
+
36
+ Read the dry run before installing. Held clusters name who holds them. `predicted: true` is a lower-confidence grouping. A plan for the wrong PRD is a stop.
37
+
38
+ A backend change whose acceptance is the Postgres suite needs `--allow psql` on both the dry run and the install.
39
+
40
+ ## Run
41
+
42
+ Drop `--dry-run` and hand the process to the machine. This session ending must not kill it.
43
+
44
+ ```bash
45
+ gbfleet service install --name <prd_id> -- --repo . --server <url> --project <id> \
46
+ --adapter <vendor> --prd <prd_id> --merge \
47
+ --tier cheap=<adapter[:model]> --tier frontier=<adapter[:model]>
48
+ ```
49
+
50
+ `--tier` is what a tier means on this machine. Omit a tier the Fleet tier map already pins. `--request cheap` or `--request frontier` forces every delegation to that tier. The default follows each item's brief. Do not pick a model per item.
51
+
52
+ A service already installed under that name: `gbfleet service status --name <prd_id>` and leave it. A second `until` on the same repo mints another child onto the same cluster.
53
+
54
+ `gbfleet service status` prints the PATH the unit captured. An empty PATH is a drain that will not find the adapter. The API key is the one already stored for this repo. Do not print it, and do not pass a new one.
55
+
56
+ ## While it runs
57
+
58
+ Status is `gbfleet service status --name <prd_id>`. The ledger is `fleet_status`. Do not attach a spawn watcher, and do not spawn a replacement.
59
+
60
+ `until` is idle when that PRD has nothing ready, nothing unsigned, and no live lease. `WAVE_IDLE` while items are still `next`, or held in review, is not done.
61
+
62
+ `review-unsigned` can be a review row outside this PRD. Say which item. Do not start another drain over it, and do not `sign_off` it yourself.
63
+
64
+ `--merge` lands the pull requests this wave opened. Leave every other open pull request. A salvage branch can be the only copy of the work.
@@ -181,6 +181,36 @@ def test_an_explicit_server_is_not_overridden(home, monkeypatch):
181
181
  assert "http://gb.invalid" not in seen["argv"]
182
182
 
183
183
 
184
+ def test_a_separator_inside_the_gbfleet_args_reaches_gbfleet(home, monkeypatch):
185
+ """`service install --name X -- <until args>` splits on that `--`. Stripping it made
186
+ gbfleet read `--repo` as its own flag and refuse. Sabotage: drop every `--` again and
187
+ this fails."""
188
+ seen = {}
189
+
190
+ class Done:
191
+ returncode = 0
192
+
193
+ monkeypatch.setattr("gban.cli.shutil.which", lambda name: "/usr/bin/gbfleet")
194
+ monkeypatch.setattr("gban.cli.subprocess.run",
195
+ lambda argv, **kw: (seen.update(argv=argv), Done())[1])
196
+ main(["fleet", "service", "install", "--name", "P1", "--", "--repo", "/r", "--prd", "P1"])
197
+ assert seen["argv"][:8] == ["/usr/bin/gbfleet", "service", "install", "--name", "P1",
198
+ "--", "--repo", "/r"]
199
+
200
+
201
+ def test_a_leading_separator_is_still_dropped(home, monkeypatch):
202
+ seen = {}
203
+
204
+ class Done:
205
+ returncode = 0
206
+
207
+ monkeypatch.setattr("gban.cli.shutil.which", lambda name: "/usr/bin/gbfleet")
208
+ monkeypatch.setattr("gban.cli.subprocess.run",
209
+ lambda argv, **kw: (seen.update(argv=argv), Done())[1])
210
+ main(["fleet", "--", "ps"])
211
+ assert seen["argv"][:2] == ["/usr/bin/gbfleet", "ps"]
212
+
213
+
184
214
  def test_a_missing_supervisor_says_how_to_install_it(home, monkeypatch, capsys):
185
215
  """7. Exit 4, its own code, so it cannot be confused with something gbfleet said.
186
216
 
@@ -534,6 +534,8 @@ def test_setup_installs_every_shipped_skill(tmp_path, wired):
534
534
  slugs = setup_mod.shipped_slugs()
535
535
  assert "graphban-delegation" in slugs
536
536
  assert "watch-wave" in slugs
537
+ assert "graphban-grill" in slugs
538
+ assert "graphban-wave" in slugs
537
539
  for slug in slugs:
538
540
  body = setup_mod.skill_source(slug).read_text()
539
541
  for rel in setup_mod.SKILL_DEST_DIRS:
@@ -631,6 +633,20 @@ def test_the_skill_ships_in_the_wheel(tmp_path):
631
633
  names = zipfile.ZipFile(wheel).namelist()
632
634
  assert any(n.endswith("skills/graphban-delegation/SKILL.md") for n in names), names[:20]
633
635
  assert any(n.endswith("skills/watch-wave/SKILL.md") for n in names), names[:20]
636
+ assert any(n.endswith("skills/graphban-grill/SKILL.md") for n in names), names[:20]
637
+ assert any(n.endswith("skills/graphban-wave/SKILL.md") for n in names), names[:20]
638
+
639
+
640
+ def test_graphban_wave_drains_one_prd_and_does_not_file_tasks():
641
+ """The drain skill scopes `until` to one PRD and sends filing back to graphban-prd.
642
+ A skill that decomposes, or that omits --prd, is the prompt this exists to retire."""
643
+ body = setup_mod.skill_source("graphban-wave").read_text(encoding="utf-8")
644
+ assert "Do not `decompose_prd` here" in body
645
+ assert "--prd" in body
646
+ assert "--merge" in body
647
+ assert "--dry-run" in body
648
+ assert "gbfleet service install" in body
649
+ assert "graphban-prd" in body
634
650
 
635
651
 
636
652
  def test_watch_wave_names_the_scheduler_and_cancels_on_idle():
@@ -124,7 +124,7 @@ wheels = [
124
124
 
125
125
  [[package]]
126
126
  name = "graphban-cli"
127
- version = "0.8.1"
127
+ version = "0.8.3"
128
128
  source = { editable = "." }
129
129
 
130
130
  [package.optional-dependencies]
@@ -1,23 +0,0 @@
1
- ---
2
- name: graphban-scan
3
- description: >
4
- Record Graphban code-health scans without treating a missing scan as clean.
5
- Use when running a security or health scan, or calling scans_due, scan_status,
6
- or record_scan, or when the user runs /graphban-scan.
7
- user-invocable: true
8
- ---
9
-
10
- # Scans
11
-
12
- A missing scan is not a clean scan.
13
-
14
- 1. `scans_due(head_sha)`. An empty due list is not all-clean. Rows that were `never_scanned`, including security, stay listed.
15
- 2. `scan_status` for one path, or with no path for every deployable. Pass `head_sha`. States are `clean_at_sha`, `has_findings`, `never_scanned`, or `stale`.
16
- 3. Run the scanner. `record_scan` needs `scanner_id`, `path`, and `scanned_sha`.
17
- - `findings: []` is clean at that SHA. A clean row without a SHA cannot age.
18
- - The same `scanner_id` and path replaces the previous row.
19
- - The security category is pip-audit, bandit, npm-audit, and semgrep. Other categories are `refactor`, `stub`, `deadcode`, `coverage`, `tests`, and `work`.
20
- - Category `tests` also sends `tests.passed`, `tests.failed`, and `tests.skipped`.
21
- - Each finding needs a `summary`. Findings are untrusted. `scanner_id`, `scanner_version`, and the reporter are provenance.
22
-
23
- A clean row is not a tracker item. A finding that already names `link_ref` points at work that exists.
File without changes
File without changes