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.
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/PKG-INFO +1 -1
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/pyproject.toml +1 -1
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/cli.py +31 -5
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/skills/graphban-delegation/SKILL.md +20 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_session.py +84 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/.gitignore +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/LICENSE +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/README.md +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/__init__.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/client.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/config.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/doctor.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/gitignore.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/setup.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/skills/watch-wave/SKILL.md +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/src/gban/swamp.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_acts.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_doctor.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_login_projects.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_packaging.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_setup.py +0 -0
- {graphban_cli-0.5.0 → graphban_cli-0.6.0}/tests/test_swamp.py +0 -0
|
@@ -266,15 +266,41 @@ def cmd_logout(args) -> int:
|
|
|
266
266
|
|
|
267
267
|
def cmd_whoami(args) -> int:
|
|
268
268
|
url = _server(args)
|
|
269
|
-
|
|
269
|
+
client = authenticated(url)
|
|
270
|
+
me = client.call("GET", "/api/auth/me")
|
|
270
271
|
project = config.resolve(args.project, config.PROJECT_ENV, "project")
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|