graphban-cli 0.5.0__tar.gz → 0.6.0__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 (22) hide show
  1. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/PKG-INFO +1 -1
  2. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/pyproject.toml +1 -1
  3. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/cli.py +31 -5
  4. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/skills/graphban-delegation/SKILL.md +20 -0
  5. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_session.py +84 -0
  6. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/.gitignore +0 -0
  7. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/LICENSE +0 -0
  8. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/README.md +0 -0
  9. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/__init__.py +0 -0
  10. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/client.py +0 -0
  11. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/config.py +0 -0
  12. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/doctor.py +0 -0
  13. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/gitignore.py +0 -0
  14. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/setup.py +0 -0
  15. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/skills/watch-wave/SKILL.md +0 -0
  16. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/swamp.py +0 -0
  17. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_acts.py +0 -0
  18. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_doctor.py +0 -0
  19. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_login_projects.py +0 -0
  20. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_packaging.py +0 -0
  21. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_setup.py +0 -0
  22. {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_swamp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: graphban-cli
3
- Version: 0.5.0
3
+ Version: 0.6.0
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.5.0"
3
+ version = "0.6.0"
4
4
  description = "gban — a Graphban client for the human at a terminal"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -266,15 +266,41 @@ def cmd_logout(args) -> int:
266
266
 
267
267
  def cmd_whoami(args) -> int:
268
268
  url = _server(args)
269
- me = authenticated(url).call("GET", "/api/auth/me")
269
+ client = authenticated(url)
270
+ me = client.call("GET", "/api/auth/me")
270
271
  project = config.resolve(args.project, config.PROJECT_ENV, "project")
271
- _out({"server": url, "project": project, **me},
272
- f"{PROG}: {me.get('email') or me.get('id')} at {url}"
273
- + (f"\n project {project}" if project else "\n no default project set"),
274
- args.as_json)
272
+ payload: dict = {"server": url, "project": project, **me}
273
+ human = (f"{PROG}: {me.get('email') or me.get('id')} at {url}"
274
+ + (f"\n project {project}" if project else "\n no default project set"))
275
+ mismatch = _cwd_project_mismatch(client, project)
276
+ if mismatch:
277
+ payload["cwd_project"] = mismatch
278
+ human += f"\n WARNING: cwd resolves to project {mismatch!r}, not {project!r}"
279
+ _out(payload, human, args.as_json)
275
280
  return 0
276
281
 
277
282
 
283
+ def _cwd_project_mismatch(client, configured: str) -> str:
284
+ """Return the project id the cwd resolves to if it differs from *configured*, else "".
285
+
286
+ A session that stands in repo X but carries a credential for project Y is the exact
287
+ shadowing GRPH-928 fixes on the fleet side. The directory decides; a stored default
288
+ does not — so we ask setup.resolve_project with no `asked` and the configured project
289
+ as `stored`. If the directory resolves to a different project, name it.
290
+ """
291
+ if not configured:
292
+ return ""
293
+ try:
294
+ cwd_project, _ = setup_mod.resolve_project(client, Path.cwd(), "", configured)
295
+ except setup_mod.Unresolved:
296
+ return ""
297
+ except Exception:
298
+ return ""
299
+ if cwd_project and cwd_project != configured:
300
+ return cwd_project
301
+ return ""
302
+
303
+
278
304
  def cmd_doctor(args) -> int:
279
305
  url = config.resolve(args.server, config.URL_ENV, "url")
280
306
  project = config.resolve(args.project, config.PROJECT_ENV, "project")
@@ -124,6 +124,26 @@ spawn(enrolment_code="…", tier="cheap", item="<item>", wave="<name>")
124
124
 
125
125
  Paste `brief.text` from step 3 into the child's instructions.
126
126
 
127
+ ### Brief → pstack template (PRD-44 D3)
128
+
129
+ When the child is a pstack `poteto-agent`, map `brief.text` fields to the pstack template so
130
+ the child is a Graphban worker rather than a freelance Cursor session:
131
+
132
+ | pstack field | Graphban source |
133
+ |---|---|
134
+ | GOAL | `brief.summary` / item title |
135
+ | SCOPE | `brief.touchpoints` — **whitelist** of predicted write areas (may write these; anything else is out) |
136
+ | ACCEPTANCE | item acceptance / checklist |
137
+ | VERIFY | the operating loop in `AGENTS.md` (both DB engines; real PRD) |
138
+ | STANDING | `AGENTS.md` invariants, pasted or pointed at |
139
+
140
+ Touchpoints are a **whitelist**, not a blacklist and not default-allow. Well outside them →
141
+ stop and flag. If the PRD or item description forbids a file listed in touchpoints, the **PRD
142
+ wins**: stop and flag. Touchpoints are not a must-write list.
143
+
144
+ No schema change to `brief` is needed — the mapping is in the prompt, not the server
145
+ (PRD-44 D7).
146
+
127
147
  If the adapter is `gbagent`, `turns` and `window` are **required** — it refuses to guess them,
128
148
  and a spawn without them exits before registering. That presents as a delegation that expired
129
149
  with nothing claimed, not as an error on the spawn, so check these first when a child never
@@ -170,6 +170,90 @@ def test_a_refusal_is_printed_in_the_servers_own_words_with_its_hint(home, monke
170
170
  assert "mint a role-narrowed credential" in err
171
171
 
172
172
 
173
+ # ---- GRPH-928: whoami names the project mismatch ---------------------------------------------
174
+
175
+ class _WhoamiClient:
176
+ """A fake authenticated client that answers /api/auth/me and /api/projects."""
177
+ def __init__(self, projects):
178
+ self._projects = projects
179
+
180
+ def call(self, method, path, body=None):
181
+ if path == "/api/auth/me":
182
+ return {"email": "agent@test.com", "id": "GRPH-A1"}
183
+ if path == "/api/projects":
184
+ return self._projects
185
+ return {}
186
+
187
+
188
+ def test_whoami_warns_when_cwd_resolves_to_a_different_project(
189
+ home, tmp_path, monkeypatch, capsys):
190
+ """Sabotage: skip the comparison and this fails — the mismatch would go silent."""
191
+ config.save_settings(url="http://gb.invalid", project="agentledger")
192
+ cwd = tmp_path / "super-arc"
193
+ cwd.mkdir()
194
+ monkeypatch.chdir(cwd)
195
+ monkeypatch.setattr("gban.cli.authenticated",
196
+ lambda url: _WhoamiClient([
197
+ {"id": "agentledger", "name": "Agent Ledger"},
198
+ {"id": "super-arc", "name": "Super Arc"},
199
+ ]))
200
+ assert main(["whoami"]) == 0
201
+ out = capsys.readouterr().out
202
+ assert "WARNING" in out
203
+ assert "super-arc" in out
204
+ assert "agentledger" in out
205
+
206
+
207
+ def test_whoami_is_silent_when_cwd_matches_configured_project(
208
+ home, tmp_path, monkeypatch, capsys):
209
+ """The control: cwd and configured project agree — no warning."""
210
+ config.save_settings(url="http://gb.invalid", project="agentledger")
211
+ cwd = tmp_path / "agentledger"
212
+ cwd.mkdir()
213
+ monkeypatch.chdir(cwd)
214
+ monkeypatch.setattr("gban.cli.authenticated",
215
+ lambda url: _WhoamiClient([
216
+ {"id": "agentledger", "name": "Agent Ledger"},
217
+ ]))
218
+ assert main(["whoami"]) == 0
219
+ out = capsys.readouterr().out
220
+ assert "WARNING" not in out
221
+
222
+
223
+ def test_whoami_is_silent_when_cwd_does_not_match_any_project(
224
+ home, tmp_path, monkeypatch, capsys):
225
+ """The directory names nothing known — the check is skipped, not an error."""
226
+ config.save_settings(url="http://gb.invalid", project="agentledger")
227
+ cwd = tmp_path / "somewhere-else"
228
+ cwd.mkdir()
229
+ monkeypatch.chdir(cwd)
230
+ monkeypatch.setattr("gban.cli.authenticated",
231
+ lambda url: _WhoamiClient([
232
+ {"id": "agentledger", "name": "Agent Ledger"},
233
+ ]))
234
+ assert main(["whoami"]) == 0
235
+ out = capsys.readouterr().out
236
+ assert "WARNING" not in out
237
+
238
+
239
+ def test_whoami_json_includes_cwd_project_on_mismatch(
240
+ home, tmp_path, monkeypatch, capsys):
241
+ """The machine path carries the same signal."""
242
+ config.save_settings(url="http://gb.invalid", project="agentledger")
243
+ cwd = tmp_path / "super-arc"
244
+ cwd.mkdir()
245
+ monkeypatch.chdir(cwd)
246
+ monkeypatch.setattr("gban.cli.authenticated",
247
+ lambda url: _WhoamiClient([
248
+ {"id": "agentledger", "name": "Agent Ledger"},
249
+ {"id": "super-arc", "name": "Super Arc"},
250
+ ]))
251
+ assert main(["--json", "whoami"]) == 0
252
+ payload = json.loads(capsys.readouterr().out)
253
+ assert payload["cwd_project"] == "super-arc"
254
+ assert payload["project"] == "agentledger"
255
+
256
+
173
257
  # ---- 10: --json is the machine-readable path -------------------------------------------------
174
258
 
175
259
  def test_json_output_is_parsed_and_carries_no_human_rendering(home, tty, monkeypatch, capsys):
File without changes
File without changes
File without changes