@jaguilar87/gaia 5.1.0 → 5.1.1
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +11 -0
- package/agents/gaia-orchestrator.md +4 -10
- package/bin/README.md +1 -1
- package/bin/cli/approvals.py +0 -144
- package/bin/cli/cleanup.py +1 -30
- package/bin/cli/context.py +379 -6
- package/bin/cli/doctor.py +1 -1
- package/bin/cli/history.py +18 -6
- package/bin/cli/install.py +4 -2
- package/bin/cli/memory.py +86 -10
- package/bin/cli/metrics.py +1216 -275
- package/bin/cli/query.py +89 -0
- package/bin/cli/scan.py +68 -2
- package/bin/cli/uninstall.py +9 -5
- package/bin/pre-publish-validate.js +7 -4
- package/gaia/project.py +49 -20
- package/gaia/store/reader.py +175 -11
- package/gaia/store/schema.sql +180 -1
- package/gaia/store/writer.py +1146 -165
- package/hooks/README.md +2 -2
- package/hooks/adapters/claude_code.py +6 -12
- package/hooks/elicitation_result.py +4 -9
- package/hooks/modules/agents/contract_validator.py +2 -1
- package/hooks/modules/agents/handoff_persister.py +5 -220
- package/hooks/modules/agents/task_info_builder.py +9 -2
- package/hooks/modules/agents/transcript_reader.py +41 -21
- package/hooks/modules/audit/logger.py +8 -3
- package/hooks/modules/context/context_injector.py +10 -3
- package/hooks/modules/core/__init__.py +5 -2
- package/hooks/modules/core/logging_setup.py +60 -0
- package/hooks/modules/core/paths.py +0 -12
- package/hooks/modules/security/approval_grants.py +24 -92
- package/hooks/modules/security/gaia_db_write_guard.py +7 -2
- package/hooks/modules/security/mutative_verbs.py +194 -11
- package/hooks/modules/session/pending_scanner.py +24 -222
- package/hooks/modules/session/session_manifest.py +13 -288
- package/hooks/post_compact.py +4 -9
- package/hooks/post_tool_use.py +4 -9
- package/hooks/pre_compact.py +5 -10
- package/hooks/pre_tool_use.py +4 -11
- package/hooks/session_end_hook.py +4 -9
- package/hooks/session_start.py +18 -21
- package/hooks/stop_hook.py +5 -10
- package/hooks/subagent_start.py +5 -10
- package/hooks/subagent_stop.py +9 -23
- package/hooks/task_completed.py +5 -10
- package/hooks/user_prompt_submit.py +4 -9
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/migrations/schema.checksum +2 -2
- package/scripts/migrations/v21_to_v22.sql +80 -0
- package/scripts/migrations/v22_to_v23.sql +20 -0
- package/scripts/migrations/v23_to_v24.sql +30 -0
- package/scripts/migrations/v24_to_v25.sql +77 -0
- package/scripts/migrations/v25_to_v26.sql +116 -0
- package/skills/README.md +8 -0
- package/skills/agent-approval-protocol/SKILL.md +27 -27
- package/skills/agent-approval-protocol/reference.md +20 -12
- package/skills/agent-protocol/SKILL.md +1 -1
- package/skills/agent-protocol/examples.md +17 -9
- package/skills/agent-response/SKILL.md +1 -1
- package/skills/diagram-builder/GLOSSARY.md +77 -0
- package/skills/diagram-builder/SKILL.md +156 -0
- package/skills/diagram-builder/assets/README.md +45 -0
- package/skills/diagram-builder/assets/data/data.generated.js +77 -0
- package/skills/diagram-builder/assets/data/document.yaml +12 -0
- package/skills/diagram-builder/assets/data/pages/overview.yaml +47 -0
- package/skills/diagram-builder/assets/engine/build-data.mjs +51 -0
- package/skills/diagram-builder/assets/engine/engine.js +863 -0
- package/skills/diagram-builder/assets/index.html +638 -0
- package/skills/diagram-builder/assets/package.json +15 -0
- package/skills/diagram-builder/assets/tools/verify.mjs +128 -0
- package/skills/diagram-builder/reference.md +444 -0
- package/skills/execution/SKILL.md +3 -3
- package/skills/gaia-patterns/reference.md +2 -2
- package/skills/memory/SKILL.md +49 -0
- package/skills/orchestrator-present-approval/SKILL.md +90 -79
- package/skills/orchestrator-present-approval/reference.md +59 -42
- package/skills/orchestrator-present-approval/template.md +16 -14
- package/skills/pending-approvals/SKILL.md +80 -29
- package/skills/pending-approvals/reference.md +48 -60
- package/skills/security-tiers/SKILL.md +1 -1
- package/skills/subagent-request-approval/SKILL.md +65 -68
- package/skills/subagent-request-approval/reference.md +63 -63
- package/skills/visual-verify/SKILL.md +107 -0
- package/skills/visual-verify/scripts/screenshot.js +210 -0
- package/tools/scan/classify.py +584 -15
- package/tools/scan/migrate_workspace.py +9 -4
- package/tools/scan/store_populator.py +231 -2
- package/tools/scan/tests/conftest.py +31 -0
package/bin/cli/context.py
CHANGED
|
@@ -348,20 +348,29 @@ def _cmd_query(args) -> int:
|
|
|
348
348
|
|
|
349
349
|
|
|
350
350
|
def _cmd_wipe(args) -> int:
|
|
351
|
-
"""Handle `gaia context wipe --workspace W [--yes]`.
|
|
351
|
+
"""Handle `gaia context wipe --workspace W [--yes] [--purge-memory]`.
|
|
352
352
|
|
|
353
|
-
Deletes
|
|
354
|
-
|
|
353
|
+
Deletes the scannable rows for the workspace (CASCADE removes children).
|
|
354
|
+
scan-v2 SV3: curated memory is PRESERVED across the wipe by default; pass
|
|
355
|
+
--purge-memory to also destroy it (explicit human curation). Requires
|
|
356
|
+
interactive confirmation unless --yes is passed.
|
|
355
357
|
"""
|
|
356
358
|
workspace = getattr(args, "workspace", None)
|
|
357
359
|
if not workspace:
|
|
358
360
|
print("gaia context wipe: --workspace is required", file=sys.stderr)
|
|
359
361
|
return 2
|
|
360
362
|
|
|
363
|
+
purge_memory = getattr(args, "purge_memory", False)
|
|
361
364
|
if not getattr(args, "yes", False):
|
|
365
|
+
mem_note = (
|
|
366
|
+
"curated memory WILL ALSO BE DESTROYED"
|
|
367
|
+
if purge_memory
|
|
368
|
+
else "curated memory will be PRESERVED"
|
|
369
|
+
)
|
|
362
370
|
try:
|
|
363
371
|
ans = input(
|
|
364
|
-
f"gaia context wipe: about to delete
|
|
372
|
+
f"gaia context wipe: about to delete workspace {workspace!r} "
|
|
373
|
+
f"rows ({mem_note}).\n"
|
|
365
374
|
f"Type 'yes' to confirm: "
|
|
366
375
|
)
|
|
367
376
|
except EOFError:
|
|
@@ -376,13 +385,291 @@ def _cmd_wipe(args) -> int:
|
|
|
376
385
|
print(f"gaia context wipe: failed to import store: {exc}", file=sys.stderr)
|
|
377
386
|
return 1
|
|
378
387
|
|
|
388
|
+
purge_memory = getattr(args, "purge_memory", False)
|
|
379
389
|
try:
|
|
380
|
-
|
|
390
|
+
# scan-v2 SV3 (Vector 4): preserve curated memory across the wipe by
|
|
391
|
+
# default. --purge-memory is the explicit-human-curation escape hatch
|
|
392
|
+
# that restores the original full CASCADE (memory destroyed).
|
|
393
|
+
wipe_workspace(workspace, preserve_memory=not purge_memory)
|
|
381
394
|
except Exception as exc:
|
|
382
395
|
print(f"gaia context wipe: error: {exc}", file=sys.stderr)
|
|
383
396
|
return 1
|
|
384
397
|
|
|
385
|
-
|
|
398
|
+
if purge_memory:
|
|
399
|
+
print(f"Wiped workspace (memory PURGED): {workspace}")
|
|
400
|
+
else:
|
|
401
|
+
print(f"Wiped workspace (memory preserved): {workspace}")
|
|
402
|
+
return 0
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _cmd_move_project(args) -> int:
|
|
406
|
+
"""Handle ``gaia context move-project --decision D --from-workspace W1
|
|
407
|
+
--from-name N1 --to-workspace W2 --to-name N2 [--dry-run] [--yes] [--json]``.
|
|
408
|
+
|
|
409
|
+
EXECUTES a human-adjudicated scan-v2 move_candidate. `gaia scan` only
|
|
410
|
+
DETECTS and REPORTS a move (as `move_candidates`); this is the write path
|
|
411
|
+
that resolves one:
|
|
412
|
+
|
|
413
|
+
--decision movido -> re-key / supersede the OLD projects row to link it
|
|
414
|
+
to the successor (writes `superseded_by`, never
|
|
415
|
+
hard-deletes). Curated memory / contracts are only
|
|
416
|
+
PROPOSED for relocation, not auto-moved -- use
|
|
417
|
+
`move-memory` / `move-contracts` for those.
|
|
418
|
+
--decision duplicado -> structural no-op: both rows are legitimately
|
|
419
|
+
--decision worktree independent and are left exactly as they are.
|
|
420
|
+
|
|
421
|
+
Named with a leading `move` token so it hyphen-splits to a MUTATIVE_VERB and
|
|
422
|
+
gates as T3 without any security-layer change (same convention as
|
|
423
|
+
`move-contracts` / `move-memory`); `--dry-run` downgrades to a preview.
|
|
424
|
+
"""
|
|
425
|
+
decision = getattr(args, "decision", None)
|
|
426
|
+
from_ws = getattr(args, "from_workspace", None)
|
|
427
|
+
from_name = getattr(args, "from_name", None)
|
|
428
|
+
to_ws = getattr(args, "to_workspace", None)
|
|
429
|
+
to_name = getattr(args, "to_name", None)
|
|
430
|
+
dry_run = getattr(args, "dry_run", False)
|
|
431
|
+
as_json = getattr(args, "json", False)
|
|
432
|
+
|
|
433
|
+
if decision not in ("movido", "duplicado", "worktree"):
|
|
434
|
+
print(
|
|
435
|
+
"gaia context move-project: --decision must be one of "
|
|
436
|
+
"movido/duplicado/worktree",
|
|
437
|
+
file=sys.stderr,
|
|
438
|
+
)
|
|
439
|
+
return 2
|
|
440
|
+
if not all((from_ws, from_name, to_ws, to_name)):
|
|
441
|
+
print(
|
|
442
|
+
"gaia context move-project: --from-workspace/--from-name/"
|
|
443
|
+
"--to-workspace/--to-name are all required",
|
|
444
|
+
file=sys.stderr,
|
|
445
|
+
)
|
|
446
|
+
return 2
|
|
447
|
+
|
|
448
|
+
# duplicado / worktree: legitimate independent rows -> structural no-op.
|
|
449
|
+
if decision in ("duplicado", "worktree"):
|
|
450
|
+
result = {
|
|
451
|
+
"status": "noop",
|
|
452
|
+
"decision": decision,
|
|
453
|
+
"from": {"workspace": from_ws, "name": from_name},
|
|
454
|
+
"to": {"workspace": to_ws, "name": to_name},
|
|
455
|
+
"reason": (
|
|
456
|
+
f"decision {decision!r}: both rows are legitimately independent "
|
|
457
|
+
"-- left untouched (no re-key, no supersede)."
|
|
458
|
+
),
|
|
459
|
+
}
|
|
460
|
+
if as_json:
|
|
461
|
+
print(json.dumps(result, indent=2, default=str))
|
|
462
|
+
else:
|
|
463
|
+
print(f"[{decision}] no-op: left both project rows untouched "
|
|
464
|
+
f"({from_ws}/{from_name} and {to_ws}/{to_name}).")
|
|
465
|
+
return 0
|
|
466
|
+
|
|
467
|
+
# movido:
|
|
468
|
+
try:
|
|
469
|
+
from gaia.store.writer import resolve_move_candidate
|
|
470
|
+
except Exception as exc:
|
|
471
|
+
print(f"gaia context move-project: failed to import store: {exc}", file=sys.stderr)
|
|
472
|
+
return 1
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
preview = resolve_move_candidate(
|
|
476
|
+
from_ws, from_name, to_ws, to_name, dry_run=True,
|
|
477
|
+
)
|
|
478
|
+
except ValueError as exc:
|
|
479
|
+
print(f"gaia context move-project: {exc}", file=sys.stderr)
|
|
480
|
+
return 1
|
|
481
|
+
|
|
482
|
+
if dry_run:
|
|
483
|
+
if as_json:
|
|
484
|
+
print(json.dumps(preview, indent=2, default=str))
|
|
485
|
+
else:
|
|
486
|
+
print(f"[dry-run] move-project (movido) {from_ws}/{from_name} -> "
|
|
487
|
+
f"{to_ws}/{to_name}:")
|
|
488
|
+
print(f" action : {preview['action']}")
|
|
489
|
+
print(f" superseded_by : {preview['superseded_by']}")
|
|
490
|
+
rel = preview["proposed_relocations"]
|
|
491
|
+
print(f" proposed relocations (NOT auto-moved): "
|
|
492
|
+
f"memory={rel['memory']} contracts={rel['contracts']}")
|
|
493
|
+
print(" -> relocate collateral with `gaia context move-memory` / "
|
|
494
|
+
"`move-contracts` if desired.")
|
|
495
|
+
return 0
|
|
496
|
+
|
|
497
|
+
if not getattr(args, "yes", False):
|
|
498
|
+
print(f"About to resolve move (movido): re-key/supersede "
|
|
499
|
+
f"{from_ws}/{from_name} -> {to_ws}/{to_name} "
|
|
500
|
+
f"(action={preview['action']}, no hard-delete).")
|
|
501
|
+
try:
|
|
502
|
+
ans = input("Type 'yes' to confirm: ")
|
|
503
|
+
except EOFError:
|
|
504
|
+
ans = ""
|
|
505
|
+
if ans.strip().lower() != "yes":
|
|
506
|
+
print("Aborted (no confirmation).")
|
|
507
|
+
return 1
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
result = resolve_move_candidate(from_ws, from_name, to_ws, to_name)
|
|
511
|
+
except ValueError as exc:
|
|
512
|
+
print(f"gaia context move-project: {exc}", file=sys.stderr)
|
|
513
|
+
return 1
|
|
514
|
+
|
|
515
|
+
if as_json:
|
|
516
|
+
print(json.dumps(result, indent=2, default=str))
|
|
517
|
+
else:
|
|
518
|
+
rel = result["proposed_relocations"]
|
|
519
|
+
print(f"Resolved move (movido): {from_ws}/{from_name} -> {to_ws}/{to_name} "
|
|
520
|
+
f"(action={result['action']}, superseded_by={result['superseded_by']}).")
|
|
521
|
+
print(f"Proposed (NOT moved): memory={rel['memory']} contracts={rel['contracts']} "
|
|
522
|
+
f"still keyed to {from_ws!r} -- relocate with move-memory/move-contracts.")
|
|
523
|
+
return 0
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _cmd_move_contracts(args) -> int:
|
|
527
|
+
"""Handle ``gaia context move-contracts --from W1 --to W2 --contract C ...``.
|
|
528
|
+
|
|
529
|
+
Re-keys project_context_contracts rows between workspaces (the only
|
|
530
|
+
correction path for a mis-keyed contract, since `gaia scan` never touches
|
|
531
|
+
that table). At least one --contract is required.
|
|
532
|
+
"""
|
|
533
|
+
from_ws = getattr(args, "from_workspace", None)
|
|
534
|
+
to_ws = getattr(args, "to_workspace", None)
|
|
535
|
+
contracts = getattr(args, "contract", None) or []
|
|
536
|
+
on_conflict = getattr(args, "on_conflict", "error")
|
|
537
|
+
dry_run = getattr(args, "dry_run", False)
|
|
538
|
+
as_json = getattr(args, "json", False)
|
|
539
|
+
|
|
540
|
+
if not from_ws or not to_ws:
|
|
541
|
+
print("gaia context move-contracts: --from and --to are required", file=sys.stderr)
|
|
542
|
+
return 2
|
|
543
|
+
if not contracts:
|
|
544
|
+
print("gaia context move-contracts: at least one --contract is required", file=sys.stderr)
|
|
545
|
+
return 2
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
from gaia.store.writer import relocate_contracts
|
|
549
|
+
except Exception as exc:
|
|
550
|
+
print(f"gaia context move-contracts: failed to import store: {exc}", file=sys.stderr)
|
|
551
|
+
return 1
|
|
552
|
+
|
|
553
|
+
try:
|
|
554
|
+
preview = relocate_contracts(
|
|
555
|
+
from_ws, to_ws, contracts, on_conflict=on_conflict, dry_run=True
|
|
556
|
+
)
|
|
557
|
+
except ValueError as exc:
|
|
558
|
+
print(f"gaia context move-contracts: {exc}", file=sys.stderr)
|
|
559
|
+
return 1
|
|
560
|
+
|
|
561
|
+
if dry_run:
|
|
562
|
+
if as_json:
|
|
563
|
+
print(json.dumps(preview, indent=2, default=str))
|
|
564
|
+
else:
|
|
565
|
+
print(f"[dry-run] move-contracts {from_ws!r} -> {to_ws!r}:")
|
|
566
|
+
print(f" would move : {preview['moved']}")
|
|
567
|
+
print(f" skipped : {preview['skipped']}")
|
|
568
|
+
print(f" missing : {preview['missing']}")
|
|
569
|
+
print(f" overwritten: {preview['overwritten']}")
|
|
570
|
+
return 0
|
|
571
|
+
|
|
572
|
+
if not getattr(args, "yes", False):
|
|
573
|
+
print(f"About to move {len(preview['moved'])} contract(s) from {from_ws!r} to {to_ws!r}: {preview['moved']}")
|
|
574
|
+
try:
|
|
575
|
+
ans = input("Type 'yes' to confirm: ")
|
|
576
|
+
except EOFError:
|
|
577
|
+
ans = ""
|
|
578
|
+
if ans.strip().lower() != "yes":
|
|
579
|
+
print("Aborted (no confirmation).")
|
|
580
|
+
return 1
|
|
581
|
+
|
|
582
|
+
try:
|
|
583
|
+
result = relocate_contracts(from_ws, to_ws, contracts, on_conflict=on_conflict)
|
|
584
|
+
except ValueError as exc:
|
|
585
|
+
print(f"gaia context move-contracts: {exc}", file=sys.stderr)
|
|
586
|
+
return 1
|
|
587
|
+
|
|
588
|
+
if as_json:
|
|
589
|
+
print(json.dumps(result, indent=2, default=str))
|
|
590
|
+
else:
|
|
591
|
+
print(f"Moved contracts {from_ws!r} -> {to_ws!r}: moved={result['moved']} "
|
|
592
|
+
f"skipped={result['skipped']} missing={result['missing']} "
|
|
593
|
+
f"overwritten={result['overwritten']}")
|
|
594
|
+
return 0
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _cmd_move_memory(args) -> int:
|
|
598
|
+
"""Handle ``gaia context move-memory --from W1 --to W2 --name N ...``.
|
|
599
|
+
|
|
600
|
+
Re-keys curated `memory` rows (and their intra-set memory_links) between
|
|
601
|
+
workspaces -- the only correction path for a mis-keyed memory note, since
|
|
602
|
+
`gaia scan` never touches that table. Subject to the curated-memory write
|
|
603
|
+
guard: run from a human shell or the orchestrator/operator context (a
|
|
604
|
+
non-curator subagent dispatch is refused). At least one --name is required.
|
|
605
|
+
"""
|
|
606
|
+
from_ws = getattr(args, "from_workspace", None)
|
|
607
|
+
to_ws = getattr(args, "to_workspace", None)
|
|
608
|
+
names = getattr(args, "name", None) or []
|
|
609
|
+
on_conflict = getattr(args, "on_conflict", "error")
|
|
610
|
+
dry_run = getattr(args, "dry_run", False)
|
|
611
|
+
as_json = getattr(args, "json", False)
|
|
612
|
+
|
|
613
|
+
if not from_ws or not to_ws:
|
|
614
|
+
print("gaia context move-memory: --from and --to are required", file=sys.stderr)
|
|
615
|
+
return 2
|
|
616
|
+
if not names:
|
|
617
|
+
print("gaia context move-memory: at least one --name is required", file=sys.stderr)
|
|
618
|
+
return 2
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
from gaia.store.writer import relocate_memory, MemoryWriteForbidden
|
|
622
|
+
except Exception as exc:
|
|
623
|
+
print(f"gaia context move-memory: failed to import store: {exc}", file=sys.stderr)
|
|
624
|
+
return 1
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
preview = relocate_memory(
|
|
628
|
+
from_ws, to_ws, names, on_conflict=on_conflict, dry_run=True
|
|
629
|
+
)
|
|
630
|
+
except MemoryWriteForbidden as exc:
|
|
631
|
+
print(f"gaia context move-memory: {exc}", file=sys.stderr)
|
|
632
|
+
return 1
|
|
633
|
+
except ValueError as exc:
|
|
634
|
+
print(f"gaia context move-memory: {exc}", file=sys.stderr)
|
|
635
|
+
return 1
|
|
636
|
+
|
|
637
|
+
if dry_run:
|
|
638
|
+
if as_json:
|
|
639
|
+
print(json.dumps(preview, indent=2, default=str))
|
|
640
|
+
else:
|
|
641
|
+
print(f"[dry-run] move-memory {from_ws!r} -> {to_ws!r}:")
|
|
642
|
+
print(f" would move : {preview['moved']}")
|
|
643
|
+
print(f" skipped : {preview['skipped']}")
|
|
644
|
+
print(f" missing : {preview['missing']}")
|
|
645
|
+
print(f" overwritten : {preview['overwritten']}")
|
|
646
|
+
print(f" links_moved : {preview['links_moved']}")
|
|
647
|
+
print(f" partial_links: {preview['partial_links']}")
|
|
648
|
+
return 0
|
|
649
|
+
|
|
650
|
+
if not getattr(args, "yes", False):
|
|
651
|
+
print(f"About to move {len(preview['moved'])} memory row(s) from {from_ws!r} to {to_ws!r}: {preview['moved']}")
|
|
652
|
+
try:
|
|
653
|
+
ans = input("Type 'yes' to confirm: ")
|
|
654
|
+
except EOFError:
|
|
655
|
+
ans = ""
|
|
656
|
+
if ans.strip().lower() != "yes":
|
|
657
|
+
print("Aborted (no confirmation).")
|
|
658
|
+
return 1
|
|
659
|
+
|
|
660
|
+
try:
|
|
661
|
+
result = relocate_memory(from_ws, to_ws, names, on_conflict=on_conflict)
|
|
662
|
+
except (MemoryWriteForbidden, ValueError) as exc:
|
|
663
|
+
print(f"gaia context move-memory: {exc}", file=sys.stderr)
|
|
664
|
+
return 1
|
|
665
|
+
|
|
666
|
+
if as_json:
|
|
667
|
+
print(json.dumps(result, indent=2, default=str))
|
|
668
|
+
else:
|
|
669
|
+
print(f"Moved memory {from_ws!r} -> {to_ws!r}: moved={result['moved']} "
|
|
670
|
+
f"skipped={result['skipped']} missing={result['missing']} "
|
|
671
|
+
f"overwritten={result['overwritten']} links_moved={result['links_moved']} "
|
|
672
|
+
f"partial_links={result['partial_links']}")
|
|
386
673
|
return 0
|
|
387
674
|
|
|
388
675
|
|
|
@@ -500,6 +787,86 @@ def register(subparsers) -> None:
|
|
|
500
787
|
default=False,
|
|
501
788
|
help="Skip interactive confirmation",
|
|
502
789
|
)
|
|
790
|
+
wipe_parser.add_argument(
|
|
791
|
+
"--purge-memory",
|
|
792
|
+
action="store_true",
|
|
793
|
+
default=False,
|
|
794
|
+
help="Also destroy curated memory (default: memory is preserved across "
|
|
795
|
+
"the wipe). Explicit human curation only.",
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# gaia context move-contracts --from W1 --to W2 --contract C ...
|
|
799
|
+
# (T3: the verb token 'move-contracts' hyphen-splits to 'move', which is in
|
|
800
|
+
# MUTATIVE_VERBS -- gated as T3 without touching the security layer.)
|
|
801
|
+
mv_parser = ctx_subparsers.add_parser(
|
|
802
|
+
"move-contracts",
|
|
803
|
+
help="Re-key project_context_contracts rows between workspaces",
|
|
804
|
+
)
|
|
805
|
+
mv_parser.add_argument("--from", dest="from_workspace", metavar="W1", required=True,
|
|
806
|
+
help="Source workspace (current, wrong key)")
|
|
807
|
+
mv_parser.add_argument("--to", dest="to_workspace", metavar="W2", required=True,
|
|
808
|
+
help="Destination workspace (correct key)")
|
|
809
|
+
mv_parser.add_argument("--contract", metavar="C", action="append", default=None,
|
|
810
|
+
help="Contract name to move (repeatable)")
|
|
811
|
+
mv_parser.add_argument("--on-conflict", dest="on_conflict", default="error",
|
|
812
|
+
choices=("error", "skip", "overwrite"),
|
|
813
|
+
help="Behavior when target already has the contract")
|
|
814
|
+
mv_parser.add_argument("--dry-run", action="store_true", default=False,
|
|
815
|
+
help="Preview the move without mutating")
|
|
816
|
+
mv_parser.add_argument("--json", action="store_true", default=False,
|
|
817
|
+
help="Emit JSON")
|
|
818
|
+
mv_parser.add_argument("--yes", action="store_true", default=False,
|
|
819
|
+
help="Skip interactive confirmation")
|
|
820
|
+
|
|
821
|
+
# gaia context move-memory --from W1 --to W2 --name N ...
|
|
822
|
+
# (T3: the verb token 'move-memory' hyphen-splits to 'move', which is in
|
|
823
|
+
# MUTATIVE_VERBS -- gated as T3 without touching the security layer.)
|
|
824
|
+
mm_parser = ctx_subparsers.add_parser(
|
|
825
|
+
"move-memory",
|
|
826
|
+
help="Re-key curated `memory` rows (and intra-set links) between workspaces",
|
|
827
|
+
)
|
|
828
|
+
mm_parser.add_argument("--from", dest="from_workspace", metavar="W1", required=True,
|
|
829
|
+
help="Source workspace (current, wrong key)")
|
|
830
|
+
mm_parser.add_argument("--to", dest="to_workspace", metavar="W2", required=True,
|
|
831
|
+
help="Destination workspace (correct key)")
|
|
832
|
+
mm_parser.add_argument("--name", metavar="N", action="append", default=None,
|
|
833
|
+
help="Memory row name to move (repeatable)")
|
|
834
|
+
mm_parser.add_argument("--on-conflict", dest="on_conflict", default="error",
|
|
835
|
+
choices=("error", "skip", "overwrite"),
|
|
836
|
+
help="Behavior when target already has the memory row")
|
|
837
|
+
mm_parser.add_argument("--dry-run", action="store_true", default=False,
|
|
838
|
+
help="Preview the move without mutating")
|
|
839
|
+
mm_parser.add_argument("--json", action="store_true", default=False,
|
|
840
|
+
help="Emit JSON")
|
|
841
|
+
mm_parser.add_argument("--yes", action="store_true", default=False,
|
|
842
|
+
help="Skip interactive confirmation")
|
|
843
|
+
|
|
844
|
+
# gaia context move-project --decision D --from-workspace/--from-name
|
|
845
|
+
# --to-workspace/--to-name
|
|
846
|
+
# (T3: the verb token 'move-project' hyphen-splits to 'move', which is in
|
|
847
|
+
# MUTATIVE_VERBS -- gated as T3 without touching the security layer. This is
|
|
848
|
+
# the write path that EXECUTES a scan-v2 move_candidate a human adjudicated.)
|
|
849
|
+
mp_parser = ctx_subparsers.add_parser(
|
|
850
|
+
"move-project",
|
|
851
|
+
help="Resolve an adjudicated scan-v2 move_candidate (re-key/supersede a projects row)",
|
|
852
|
+
)
|
|
853
|
+
mp_parser.add_argument("--decision", required=True,
|
|
854
|
+
choices=("movido", "duplicado", "worktree"),
|
|
855
|
+
help="Human adjudication of the move_candidate")
|
|
856
|
+
mp_parser.add_argument("--from-workspace", dest="from_workspace", metavar="W1", required=True,
|
|
857
|
+
help="Old row workspace (move_candidate from.workspace)")
|
|
858
|
+
mp_parser.add_argument("--from-name", dest="from_name", metavar="N1", required=True,
|
|
859
|
+
help="Old row name (move_candidate from.project)")
|
|
860
|
+
mp_parser.add_argument("--to-workspace", dest="to_workspace", metavar="W2", required=True,
|
|
861
|
+
help="Successor workspace (move_candidate to.workspace)")
|
|
862
|
+
mp_parser.add_argument("--to-name", dest="to_name", metavar="N2", required=True,
|
|
863
|
+
help="Successor name (move_candidate to.project)")
|
|
864
|
+
mp_parser.add_argument("--dry-run", action="store_true", default=False,
|
|
865
|
+
help="Preview the resolution without mutating")
|
|
866
|
+
mp_parser.add_argument("--json", action="store_true", default=False,
|
|
867
|
+
help="Emit JSON")
|
|
868
|
+
mp_parser.add_argument("--yes", action="store_true", default=False,
|
|
869
|
+
help="Skip interactive confirmation")
|
|
503
870
|
|
|
504
871
|
|
|
505
872
|
def cmd_context(args) -> int:
|
|
@@ -517,6 +884,12 @@ def cmd_context(args) -> int:
|
|
|
517
884
|
return _cmd_query(args)
|
|
518
885
|
if context_cmd == "wipe":
|
|
519
886
|
return _cmd_wipe(args)
|
|
887
|
+
if context_cmd == "move-contracts":
|
|
888
|
+
return _cmd_move_contracts(args)
|
|
889
|
+
if context_cmd == "move-memory":
|
|
890
|
+
return _cmd_move_memory(args)
|
|
891
|
+
if context_cmd == "move-project":
|
|
892
|
+
return _cmd_move_project(args)
|
|
520
893
|
|
|
521
894
|
# No sub-action: print help for the context subcommand
|
|
522
895
|
import argparse
|
package/bin/cli/doctor.py
CHANGED
|
@@ -206,7 +206,7 @@ def _package_root() -> Path:
|
|
|
206
206
|
# in lock-step with the INSERT it adds to bootstrap_database.sh. If a user
|
|
207
207
|
# upgrades the CLI past a schema bump but does not re-run `gaia install`,
|
|
208
208
|
# `check_schema_version` raises a warning telling them how to repair.
|
|
209
|
-
EXPECTED_SCHEMA_VERSION =
|
|
209
|
+
EXPECTED_SCHEMA_VERSION = 26
|
|
210
210
|
|
|
211
211
|
# Locations the doctor reads outside the workspace.
|
|
212
212
|
_INSTALL_ERROR_MARKER = Path("~/.gaia/last-install-error.json").expanduser()
|
package/bin/cli/history.py
CHANGED
|
@@ -13,6 +13,7 @@ Flags:
|
|
|
13
13
|
--blocked / -b Show only BLOCKED or NEEDS_INPUT sessions
|
|
14
14
|
--agent / -a NAME Filter by agent name
|
|
15
15
|
--limit / -n N Max sessions to show (default 20)
|
|
16
|
+
--workspace NAME Workspace identity override (default: gaia.project.current())
|
|
16
17
|
--json Machine-readable output
|
|
17
18
|
"""
|
|
18
19
|
|
|
@@ -48,7 +49,7 @@ def _find_project_root() -> Path:
|
|
|
48
49
|
# Data readers
|
|
49
50
|
# ---------------------------------------------------------------------------
|
|
50
51
|
|
|
51
|
-
def _read_workflow_metrics(root: Path) -> list:
|
|
52
|
+
def _read_workflow_metrics(root: Path, workspace_override: str = None) -> list:
|
|
52
53
|
"""
|
|
53
54
|
Read agent session history from gaia.db episodes table.
|
|
54
55
|
|
|
@@ -56,6 +57,9 @@ def _read_workflow_metrics(root: Path) -> list:
|
|
|
56
57
|
The legacy episodic-memory/index.json and workflow-episodic-memory/metrics.jsonl
|
|
57
58
|
files are no longer read here.
|
|
58
59
|
|
|
60
|
+
``workspace_override`` -- explicit ``--workspace`` value from the CLI.
|
|
61
|
+
When set, it wins over ``gaia.project.current()`` resolution.
|
|
62
|
+
|
|
59
63
|
Returns a list of episode dicts (rows with an 'agent' field).
|
|
60
64
|
"""
|
|
61
65
|
import sys as _sys
|
|
@@ -71,10 +75,13 @@ def _read_workflow_metrics(root: Path) -> list:
|
|
|
71
75
|
except ImportError:
|
|
72
76
|
return []
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
ws =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
if workspace_override:
|
|
79
|
+
ws = workspace_override
|
|
80
|
+
else:
|
|
81
|
+
try:
|
|
82
|
+
ws = _project_current(cwd=root)
|
|
83
|
+
except Exception:
|
|
84
|
+
ws = None
|
|
78
85
|
|
|
79
86
|
try:
|
|
80
87
|
con = _store_connect()
|
|
@@ -199,6 +206,10 @@ def register(subparsers):
|
|
|
199
206
|
metavar="N",
|
|
200
207
|
help="Maximum number of sessions to show (default: 20)",
|
|
201
208
|
)
|
|
209
|
+
p.add_argument(
|
|
210
|
+
"--workspace", default=None,
|
|
211
|
+
help="Workspace identity override. Default: gaia.project.current().",
|
|
212
|
+
)
|
|
202
213
|
p.add_argument(
|
|
203
214
|
"--json",
|
|
204
215
|
action="store_true",
|
|
@@ -213,6 +224,7 @@ def cmd_history(args) -> int:
|
|
|
213
224
|
root = _find_project_root()
|
|
214
225
|
claude_dir = root / ".claude"
|
|
215
226
|
as_json = getattr(args, "json", False)
|
|
227
|
+
workspace_override = getattr(args, "workspace", None)
|
|
216
228
|
|
|
217
229
|
if not claude_dir.exists():
|
|
218
230
|
if as_json:
|
|
@@ -222,7 +234,7 @@ def cmd_history(args) -> int:
|
|
|
222
234
|
print(" Run: gaia scan\n")
|
|
223
235
|
return 1
|
|
224
236
|
|
|
225
|
-
entries = _read_workflow_metrics(root)
|
|
237
|
+
entries = _read_workflow_metrics(root, workspace_override)
|
|
226
238
|
|
|
227
239
|
if not entries:
|
|
228
240
|
if as_json:
|
package/bin/cli/install.py
CHANGED
|
@@ -295,7 +295,7 @@ def _run_bootstrap(db_path: str | None, verbose: bool, quiet: bool) -> dict:
|
|
|
295
295
|
|
|
296
296
|
env = os.environ.copy()
|
|
297
297
|
if db_path:
|
|
298
|
-
env["GAIA_DB"] = str(Path(db_path).expanduser())
|
|
298
|
+
env["GAIA_DB"] = str(Path(db_path).expanduser().resolve())
|
|
299
299
|
|
|
300
300
|
cmd = ["bash", str(_BOOTSTRAP_SCRIPT)]
|
|
301
301
|
|
|
@@ -355,7 +355,9 @@ def _seed_contract_permissions(db_path: str | None, quiet: bool) -> dict:
|
|
|
355
355
|
|
|
356
356
|
env = os.environ.copy()
|
|
357
357
|
resolved_db = (
|
|
358
|
-
str(Path(db_path).expanduser()
|
|
358
|
+
str(Path(db_path).expanduser().resolve())
|
|
359
|
+
if db_path
|
|
360
|
+
else str(Path("~/.gaia/gaia.db").expanduser().resolve())
|
|
359
361
|
)
|
|
360
362
|
cmd = [sys.executable, str(_SEED_CONTRACT_PERMS), "--db-path", resolved_db]
|
|
361
363
|
|