@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.
Files changed (92) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +11 -0
  4. package/agents/gaia-orchestrator.md +4 -10
  5. package/bin/README.md +1 -1
  6. package/bin/cli/approvals.py +0 -144
  7. package/bin/cli/cleanup.py +1 -30
  8. package/bin/cli/context.py +379 -6
  9. package/bin/cli/doctor.py +1 -1
  10. package/bin/cli/history.py +18 -6
  11. package/bin/cli/install.py +4 -2
  12. package/bin/cli/memory.py +86 -10
  13. package/bin/cli/metrics.py +1216 -275
  14. package/bin/cli/query.py +89 -0
  15. package/bin/cli/scan.py +68 -2
  16. package/bin/cli/uninstall.py +9 -5
  17. package/bin/pre-publish-validate.js +7 -4
  18. package/gaia/project.py +49 -20
  19. package/gaia/store/reader.py +175 -11
  20. package/gaia/store/schema.sql +180 -1
  21. package/gaia/store/writer.py +1146 -165
  22. package/hooks/README.md +2 -2
  23. package/hooks/adapters/claude_code.py +6 -12
  24. package/hooks/elicitation_result.py +4 -9
  25. package/hooks/modules/agents/contract_validator.py +2 -1
  26. package/hooks/modules/agents/handoff_persister.py +5 -220
  27. package/hooks/modules/agents/task_info_builder.py +9 -2
  28. package/hooks/modules/agents/transcript_reader.py +41 -21
  29. package/hooks/modules/audit/logger.py +8 -3
  30. package/hooks/modules/context/context_injector.py +10 -3
  31. package/hooks/modules/core/__init__.py +5 -2
  32. package/hooks/modules/core/logging_setup.py +60 -0
  33. package/hooks/modules/core/paths.py +0 -12
  34. package/hooks/modules/security/approval_grants.py +24 -92
  35. package/hooks/modules/security/gaia_db_write_guard.py +7 -2
  36. package/hooks/modules/security/mutative_verbs.py +194 -11
  37. package/hooks/modules/session/pending_scanner.py +24 -222
  38. package/hooks/modules/session/session_manifest.py +13 -288
  39. package/hooks/post_compact.py +4 -9
  40. package/hooks/post_tool_use.py +4 -9
  41. package/hooks/pre_compact.py +5 -10
  42. package/hooks/pre_tool_use.py +4 -11
  43. package/hooks/session_end_hook.py +4 -9
  44. package/hooks/session_start.py +18 -21
  45. package/hooks/stop_hook.py +5 -10
  46. package/hooks/subagent_start.py +5 -10
  47. package/hooks/subagent_stop.py +9 -23
  48. package/hooks/task_completed.py +5 -10
  49. package/hooks/user_prompt_submit.py +4 -9
  50. package/package.json +1 -1
  51. package/pyproject.toml +1 -1
  52. package/scripts/migrations/schema.checksum +2 -2
  53. package/scripts/migrations/v21_to_v22.sql +80 -0
  54. package/scripts/migrations/v22_to_v23.sql +20 -0
  55. package/scripts/migrations/v23_to_v24.sql +30 -0
  56. package/scripts/migrations/v24_to_v25.sql +77 -0
  57. package/scripts/migrations/v25_to_v26.sql +116 -0
  58. package/skills/README.md +8 -0
  59. package/skills/agent-approval-protocol/SKILL.md +27 -27
  60. package/skills/agent-approval-protocol/reference.md +20 -12
  61. package/skills/agent-protocol/SKILL.md +1 -1
  62. package/skills/agent-protocol/examples.md +17 -9
  63. package/skills/agent-response/SKILL.md +1 -1
  64. package/skills/diagram-builder/GLOSSARY.md +77 -0
  65. package/skills/diagram-builder/SKILL.md +156 -0
  66. package/skills/diagram-builder/assets/README.md +45 -0
  67. package/skills/diagram-builder/assets/data/data.generated.js +77 -0
  68. package/skills/diagram-builder/assets/data/document.yaml +12 -0
  69. package/skills/diagram-builder/assets/data/pages/overview.yaml +47 -0
  70. package/skills/diagram-builder/assets/engine/build-data.mjs +51 -0
  71. package/skills/diagram-builder/assets/engine/engine.js +863 -0
  72. package/skills/diagram-builder/assets/index.html +638 -0
  73. package/skills/diagram-builder/assets/package.json +15 -0
  74. package/skills/diagram-builder/assets/tools/verify.mjs +128 -0
  75. package/skills/diagram-builder/reference.md +444 -0
  76. package/skills/execution/SKILL.md +3 -3
  77. package/skills/gaia-patterns/reference.md +2 -2
  78. package/skills/memory/SKILL.md +49 -0
  79. package/skills/orchestrator-present-approval/SKILL.md +90 -79
  80. package/skills/orchestrator-present-approval/reference.md +59 -42
  81. package/skills/orchestrator-present-approval/template.md +16 -14
  82. package/skills/pending-approvals/SKILL.md +80 -29
  83. package/skills/pending-approvals/reference.md +48 -60
  84. package/skills/security-tiers/SKILL.md +1 -1
  85. package/skills/subagent-request-approval/SKILL.md +65 -68
  86. package/skills/subagent-request-approval/reference.md +63 -63
  87. package/skills/visual-verify/SKILL.md +107 -0
  88. package/skills/visual-verify/scripts/screenshot.js +210 -0
  89. package/tools/scan/classify.py +584 -15
  90. package/tools/scan/migrate_workspace.py +9 -4
  91. package/tools/scan/store_populator.py +231 -2
  92. package/tools/scan/tests/conftest.py +31 -0
package/bin/cli/memory.py CHANGED
@@ -13,10 +13,17 @@ Mutating subcommands operate on the curated ``memory`` table in
13
13
  ``~/.gaia/gaia.db`` (the project-level / user-level / feedback notes):
14
14
 
15
15
  add --name=<slug> --type=<project|user|feedback> --body="..."
16
- [--description=...] [--workspace=<ws>] [--json]
16
+ [--description=...] [--workspace=<ws>]
17
+ [--project=<name> | --project-ref=<identity>] [--json]
17
18
  DB-only writer; no filesystem side
18
19
  effects (no .md under
19
20
  ~/.claude/projects/.../memory/).
21
+ --project anchors memory.project_ref
22
+ (N3, forward-only) by resolving a
23
+ project name within --workspace to
24
+ its stable project_identity; never
25
+ guesses (clear error if the project
26
+ does not exist or has no identity).
20
27
  """
21
28
 
22
29
  from __future__ import annotations
@@ -706,6 +713,8 @@ def _cmd_add(args) -> int:
706
713
  workspace = _resolve_workspace(getattr(args, "workspace", None))
707
714
  class_flag = getattr(args, "class_", None)
708
715
  status_flag = getattr(args, "status", None)
716
+ project_flag = getattr(args, "project", None)
717
+ project_ref_flag = getattr(args, "project_ref", None)
709
718
 
710
719
  if not name:
711
720
  return _err("--name is required", as_json)
@@ -731,7 +740,8 @@ def _cmd_add(args) -> int:
731
740
 
732
741
  try:
733
742
  from gaia.store.writer import (
734
- upsert_memory, reclassify_memory, VALID_MEMORY_TYPES,
743
+ upsert_memory, reclassify_memory, resolve_project_ref,
744
+ VALID_MEMORY_TYPES,
735
745
  )
736
746
  except ImportError as exc:
737
747
  return _err(f"gaia.store.writer not importable: {exc}", as_json)
@@ -742,6 +752,19 @@ def _cmd_add(args) -> int:
742
752
  as_json,
743
753
  )
744
754
 
755
+ # N3: forward-only project_ref anchor. --project resolves a project name
756
+ # (within `workspace`) to its stable project_identity; --project-ref
757
+ # passes an already-known identity directly. Neither guesses: an
758
+ # unresolvable --project is a clear error, never a silent NULL.
759
+ project_ref = None
760
+ if project_flag is not None:
761
+ try:
762
+ project_ref = resolve_project_ref(workspace, project_flag)
763
+ except ValueError as exc:
764
+ return _err(str(exc), as_json)
765
+ elif project_ref_flag is not None:
766
+ project_ref = project_ref_flag
767
+
745
768
  try:
746
769
  res = upsert_memory(
747
770
  workspace,
@@ -749,6 +772,7 @@ def _cmd_add(args) -> int:
749
772
  type=mem_type,
750
773
  body=body,
751
774
  description=description,
775
+ project_ref=project_ref,
752
776
  )
753
777
  except ValueError as exc:
754
778
  return _err(str(exc), as_json)
@@ -796,6 +820,8 @@ def _cmd_add(args) -> int:
796
820
  "body_preview": snippet,
797
821
  "updated_at": res.get("updated_at"),
798
822
  }
823
+ if project_ref is not None:
824
+ out["project_ref"] = project_ref
799
825
  if reclassify_result is not None:
800
826
  out["class"] = reclassify_result["class"]
801
827
  out["memory_status"] = reclassify_result["memory_status"]
@@ -805,6 +831,8 @@ def _cmd_add(args) -> int:
805
831
  print(f"{verb} memory '{name}' (type={mem_type}, workspace={workspace})")
806
832
  if description:
807
833
  print(f" description: {description}")
834
+ if project_ref is not None:
835
+ print(f" project_ref: {project_ref}")
808
836
  print(f" body: {snippet}")
809
837
  if reclassify_result is not None:
810
838
  print(
@@ -1004,6 +1032,9 @@ def _cmd_get_relevant(args) -> int:
1004
1032
  "SELECT name, type, description, updated_at, class, status "
1005
1033
  "FROM memory "
1006
1034
  "WHERE workspace = ? "
1035
+ # scan-v2 SV3: a soft-deleted (tombstoned) row must not be
1036
+ # injected into the SessionStart memory block.
1037
+ " AND deleted_at IS NULL "
1007
1038
  " AND name NOT IN ("
1008
1039
  " SELECT dst_name FROM memory_links "
1009
1040
  " WHERE workspace = ? AND kind = 'supersedes'"
@@ -1384,18 +1415,27 @@ def _cmd_curated_show(args) -> int:
1384
1415
 
1385
1416
 
1386
1417
  def _cmd_delete(args) -> int:
1387
- """Hard-delete a curated memory row (FTS5 mirror cleared via trigger)."""
1418
+ """Soft-delete (tombstone) a curated memory row -- scan-v2 SV3.
1419
+
1420
+ By default this is a SOFT delete: the row's ``deleted_at`` is stamped so the
1421
+ row and its body survive (recoverable, invisible to reads). ``--hard``
1422
+ performs the irreversible physical DELETE, reserved for explicit human
1423
+ curation. Both paths keep the FTS5 mirror in sync via triggers.
1424
+ """
1388
1425
  as_json = getattr(args, "json", False)
1389
1426
  workspace = _resolve_workspace(getattr(args, "workspace", None))
1390
1427
  name = args.name
1391
1428
  skip_confirm = getattr(args, "yes", False)
1429
+ hard = getattr(args, "hard", False)
1392
1430
 
1393
1431
  try:
1394
1432
  from gaia.store.writer import get_memory, delete_memory
1395
1433
  except ImportError as exc:
1396
1434
  return _err(f"gaia.store.writer not importable: {exc}", as_json)
1397
1435
 
1398
- row = get_memory(workspace, name)
1436
+ # For a hard delete we may target a row that is already tombstoned; reach it
1437
+ # via include_deleted so a soft-then-hard flow works.
1438
+ row = get_memory(workspace, name, include_deleted=hard)
1399
1439
  if row is None:
1400
1440
  return _err(
1401
1441
  f"memory '{name}' not found in workspace '{workspace}'",
@@ -1403,7 +1443,8 @@ def _cmd_delete(args) -> int:
1403
1443
  )
1404
1444
 
1405
1445
  if not skip_confirm:
1406
- prompt = f"Delete memory '{name}' (type={row['type']})? [y/N] "
1446
+ verb = "HARD-delete (irreversible)" if hard else "delete (tombstone)"
1447
+ prompt = f"{verb} memory '{name}' (type={row['type']})? [y/N] "
1407
1448
  try:
1408
1449
  answer = input(prompt)
1409
1450
  except EOFError:
@@ -1417,7 +1458,7 @@ def _cmd_delete(args) -> int:
1417
1458
  return 0
1418
1459
 
1419
1460
  try:
1420
- deleted = delete_memory(workspace, name)
1461
+ deleted = delete_memory(workspace, name, hard=hard)
1421
1462
  except PermissionError as exc:
1422
1463
  return _err(str(exc), as_json)
1423
1464
  if not deleted:
@@ -1426,15 +1467,18 @@ def _cmd_delete(args) -> int:
1426
1467
  as_json,
1427
1468
  )
1428
1469
 
1470
+ mode = "hard" if hard else "tombstone"
1429
1471
  if as_json:
1430
1472
  print(json.dumps({
1431
1473
  "deleted": True,
1474
+ "mode": mode,
1432
1475
  "name": name,
1433
1476
  "workspace": workspace,
1434
1477
  "previous_type": row["type"],
1435
1478
  }, indent=2, default=str))
1436
1479
  else:
1437
- print(f"Deleted memory '{name}' (workspace={workspace!r}, "
1480
+ verb = "Hard-deleted" if hard else "Tombstoned"
1481
+ print(f"{verb} memory '{name}' (workspace={workspace!r}, "
1438
1482
  f"previous_type={row['type']!r})")
1439
1483
  return 0
1440
1484
 
@@ -1937,10 +1981,18 @@ def register(subparsers):
1937
1981
  # -- delete -------------------------------------------------------------
1938
1982
  delete_p = actions.add_parser(
1939
1983
  "delete",
1940
- help="Hard-delete a curated memory row",
1941
- description="Drop the row; the FTS5 mirror is cleared via trigger.",
1984
+ help="Soft-delete (tombstone) a curated memory row; --hard to purge",
1985
+ description=(
1986
+ "Tombstone the row by default (deleted_at stamped; row + body "
1987
+ "survive, invisible to reads, recoverable). Use --hard for the "
1988
+ "irreversible physical DELETE (explicit human curation only)."
1989
+ ),
1942
1990
  formatter_class=_argparse.RawDescriptionHelpFormatter,
1943
- epilog="Examples:\n gaia memory delete project_old --yes\n",
1991
+ epilog=(
1992
+ "Examples:\n"
1993
+ " gaia memory delete project_old --yes\n"
1994
+ " gaia memory delete project_old --hard --yes\n"
1995
+ ),
1944
1996
  )
1945
1997
  delete_p.add_argument("name", help="Curated memory slug.")
1946
1998
  delete_p.add_argument("--workspace", default=None, metavar="W",
@@ -1949,6 +2001,11 @@ def register(subparsers):
1949
2001
  "--yes", action="store_true", default=False,
1950
2002
  help="Skip confirm prompt. bool. Default: false.",
1951
2003
  )
2004
+ delete_p.add_argument(
2005
+ "--hard", action="store_true", default=False,
2006
+ help="Physically DELETE the row (irreversible). Default: false "
2007
+ "(soft-delete/tombstone).",
2008
+ )
1952
2009
  delete_p.add_argument(
1953
2010
  "--json", action="store_true", default=False,
1954
2011
  help="Emit JSON. bool.",
@@ -2131,6 +2188,25 @@ def register(subparsers):
2131
2188
  )
2132
2189
  add_p.add_argument("--description", default=None,
2133
2190
  help="Short summary. Shown in list.")
2191
+ _add_project_group = add_p.add_mutually_exclusive_group()
2192
+ _add_project_group.add_argument(
2193
+ "--project", default=None,
2194
+ help=(
2195
+ "N3: anchor this memory to a project by NAME (resolved within "
2196
+ "--workspace to its stable projects.project_identity, persisted "
2197
+ "as memory.project_ref). Forward-only: errors clearly if the "
2198
+ "project does not exist or has no project_identity yet -- never "
2199
+ "guesses. Mutually exclusive with --project-ref."
2200
+ ),
2201
+ )
2202
+ _add_project_group.add_argument(
2203
+ "--project-ref", dest="project_ref", default=None,
2204
+ help=(
2205
+ "N3: anchor this memory directly to a known stable "
2206
+ "project_identity string, bypassing name resolution. Use "
2207
+ "--project instead unless you already hold the identity value."
2208
+ ),
2209
+ )
2134
2210
  add_p.add_argument(
2135
2211
  "--class", dest="class_", default=None,
2136
2212
  choices=("anchor", "thread", "log"),