gaia-cli 4.3.6__tar.gz → 4.3.8__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 (104) hide show
  1. {gaia_cli-4.3.6/src/gaia_cli.egg-info → gaia_cli-4.3.8}/PKG-INFO +6 -2
  2. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/README.md +5 -1
  3. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/pyproject.toml +1 -1
  4. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/graph.py +10 -1
  5. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/main.py +73 -4
  6. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/push.py +10 -3
  7. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/registry.py +1 -1
  8. {gaia_cli-4.3.6 → gaia_cli-4.3.8/src/gaia_cli.egg-info}/PKG-INFO +6 -2
  9. gaia_cli-4.3.8/tests/test_evidence_inheritance.py +97 -0
  10. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_workflows.py +1 -12
  11. gaia_cli-4.3.6/tests/test_evidence_inheritance.py +0 -62
  12. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/LICENSE +0 -0
  13. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/setup.cfg +0 -0
  14. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/__init__.py +0 -0
  15. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/__main__.py +0 -0
  16. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/authz.py +0 -0
  17. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/cardRenderer.py +0 -0
  18. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/combinator.py +0 -0
  19. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/commands/__init__.py +0 -0
  20. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/commands/dev.py +0 -0
  21. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/commands/stats.py +0 -0
  22. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/gaia.json +0 -0
  23. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/named/devin-ai/autonomous-swe.md +0 -0
  24. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/named/karpathy/autoresearch.md +0 -0
  25. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/named-skills.json +0 -0
  26. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/combination.schema.json +0 -0
  27. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/meta.json +0 -0
  28. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/namedSkill.schema.json +0 -0
  29. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/pluginConfig.schema.json +0 -0
  30. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/realSkillCatalog.schema.json +0 -0
  31. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/skill.schema.json +0 -0
  32. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/skillBatch.schema.json +0 -0
  33. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/skillSuite.schema.json +0 -0
  34. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/data/registry/schema/skillTree.schema.json +0 -0
  35. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/embeddings.py +0 -0
  36. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/evidence.py +0 -0
  37. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/formatting.py +0 -0
  38. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/hook.py +0 -0
  39. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/install.py +0 -0
  40. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/interactive.py +0 -0
  41. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/leveling.py +0 -0
  42. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/localContext.py +0 -0
  43. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/name.py +0 -0
  44. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/pathEngine.py +0 -0
  45. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/prWriter.py +0 -0
  46. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/promotion.py +0 -0
  47. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/redaction.py +0 -0
  48. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/resolver.py +0 -0
  49. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/scanner.py +0 -0
  50. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/semantic_search.py +0 -0
  51. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/timeline.py +0 -0
  52. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/treeManager.py +0 -0
  53. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/__init__.py +0 -0
  54. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/app.py +0 -0
  55. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/__init__.py +0 -0
  56. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/agent.py +0 -0
  57. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/hero.py +0 -0
  58. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/levelup.py +0 -0
  59. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/scan.py +0 -0
  60. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/screens/tree.py +0 -0
  61. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/theme.tcss +0 -0
  62. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/tui/tokens.py +0 -0
  63. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli/versioning.py +0 -0
  64. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli.egg-info/SOURCES.txt +0 -0
  65. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli.egg-info/dependency_links.txt +0 -0
  66. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli.egg-info/entry_points.txt +0 -0
  67. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli.egg-info/requires.txt +0 -0
  68. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/src/gaia_cli.egg-info/top_level.txt +0 -0
  69. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_authz.py +0 -0
  70. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_card_renderer.py +0 -0
  71. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_crawlers.py +0 -0
  72. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_docs_site.py +0 -0
  73. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_docs_skill_explorer.py +0 -0
  74. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_dx.py +0 -0
  75. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_embeddings.py +0 -0
  76. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_formatting.py +0 -0
  77. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_graph.py +0 -0
  78. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_install.py +0 -0
  79. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_intake.py +0 -0
  80. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_interactive.py +0 -0
  81. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_leveling.py +0 -0
  82. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_lifecycle.py +0 -0
  83. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_local_context.py +0 -0
  84. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_meta_ops.py +0 -0
  85. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_named_skills.py +0 -0
  86. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_packaging.py +0 -0
  87. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_path_command.py +0 -0
  88. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_path_engine.py +0 -0
  89. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_pr540_review.py +0 -0
  90. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_pr_writer.py +0 -0
  91. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_promotion.py +0 -0
  92. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_push.py +0 -0
  93. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_real_skill_catalog.py +0 -0
  94. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_redaction.py +0 -0
  95. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_registry_layout.py +0 -0
  96. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_scanner.py +0 -0
  97. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_stats.py +0 -0
  98. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_suite_install.py +0 -0
  99. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_timelines.py +0 -0
  100. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_treeManager.py +0 -0
  101. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_tui_tokens.py +0 -0
  102. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_unlocked_at_datetime.py +0 -0
  103. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_validate.py +0 -0
  104. {gaia_cli-4.3.6 → gaia_cli-4.3.8}/tests/test_verify_evidence.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaia-cli
3
- Version: 4.3.6
3
+ Version: 4.3.8
4
4
  Summary: Gaia AI Agent Skill Registry CLI
5
5
  Author: Gaia contributors
6
6
  License: MIT
@@ -167,7 +167,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
167
167
  **1. Install the CLI**
168
168
 
169
169
  <!-- gaia:version-start -->
170
- Current Gaia CLI version: `4.3.6`.
170
+ Current Gaia CLI version: `4.3.8`.
171
171
 
172
172
  ```bash
173
173
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -212,6 +212,8 @@ packaging-specific tests are skipped locally with guidance to install developer
212
212
 
213
213
  ```bash
214
214
  gaia init --user your-username
215
+ # Detected repo: your-username/your-repo
216
+ # Initialize Gaia on this repository? [Y/n]: Y
215
217
  gaia scan
216
218
  ```
217
219
 
@@ -221,9 +223,11 @@ Detects skills your agent demonstrates.
221
223
 
222
224
  ```bash
223
225
  gaia push
226
+ # Push skills to gaia registry from your-username/your-repo? [Y/n]: Y
224
227
  ```
225
228
 
226
229
  A GitHub issue opens automatically. Maintainers review and promote; your name attaches at 2★.
230
+ If no remote is detected, Gaia will guide you to add one.
227
231
 
228
232
  **4. Bond your agent (optional)**
229
233
 
@@ -118,7 +118,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
118
118
  **1. Install the CLI**
119
119
 
120
120
  <!-- gaia:version-start -->
121
- Current Gaia CLI version: `4.3.6`.
121
+ Current Gaia CLI version: `4.3.8`.
122
122
 
123
123
  ```bash
124
124
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -163,6 +163,8 @@ packaging-specific tests are skipped locally with guidance to install developer
163
163
 
164
164
  ```bash
165
165
  gaia init --user your-username
166
+ # Detected repo: your-username/your-repo
167
+ # Initialize Gaia on this repository? [Y/n]: Y
166
168
  gaia scan
167
169
  ```
168
170
 
@@ -172,9 +174,11 @@ Detects skills your agent demonstrates.
172
174
 
173
175
  ```bash
174
176
  gaia push
177
+ # Push skills to gaia registry from your-username/your-repo? [Y/n]: Y
175
178
  ```
176
179
 
177
180
  A GitHub issue opens automatically. Maintainers review and promote; your name attaches at 2★.
181
+ If no remote is detected, Gaia will guide you to add one.
178
182
 
179
183
  **4. Bond your agent (optional)**
180
184
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gaia-cli"
7
- version = "4.3.6"
7
+ version = "4.3.8"
8
8
  description = "Gaia AI Agent Skill Registry CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -39,6 +39,11 @@ def _registry_root(registry_path: str | os.PathLike[str]) -> Path:
39
39
 
40
40
  def load_graph(registry_path: str | os.PathLike[str] = ".") -> dict[str, Any]:
41
41
  graph_path = Path(registry_graph_path(_registry_root(registry_path)))
42
+ if not graph_path.exists():
43
+ raise FileNotFoundError(
44
+ f"Registry graph not found at {graph_path}. "
45
+ "Run gaia init from a gaia-skill-tree clone."
46
+ )
42
47
  with graph_path.open("r", encoding="utf-8") as f:
43
48
  return json.load(f)
44
49
 
@@ -1204,7 +1209,11 @@ def graph_command(args: Any) -> None:
1204
1209
  except Exception:
1205
1210
  pass # Degrade gracefully to canon mode
1206
1211
 
1207
- out_path = write_graph_artifact(registry_path, output=output, fmt=fmt, user_ctx=user_ctx)
1212
+ try:
1213
+ out_path = write_graph_artifact(registry_path, output=output, fmt=fmt, user_ctx=user_ctx)
1214
+ except FileNotFoundError as exc:
1215
+ print(str(exc), file=sys.stderr)
1216
+ return
1208
1217
  print(f" saved {out_path}")
1209
1218
 
1210
1219
  # Regenerate the GEXF from current node data
@@ -14,7 +14,7 @@ from gaia_cli.resolver import resolve_skills
14
14
  from gaia_cli.combinator import get_combinations
15
15
  from gaia_cli.treeManager import load_tree, save_tree, show_status, show_tree
16
16
  from gaia_cli.prWriter import open_pr, open_intake_issue
17
- from gaia_cli.push import build_skill_batch, write_skill_batch, build_proposed_skill, detect_source_repo
17
+ from gaia_cli.push import build_skill_batch, write_skill_batch, build_proposed_skill, detect_source_repo, NonPublicRepoError
18
18
  from gaia_cli.embeddings import generate_embeddings
19
19
  from gaia_cli.semantic_search import search as semantic_search, load_embeddings
20
20
  from gaia_cli.name import find_awakened_skill, promote_to_named, update_batch_lifecycle
@@ -321,6 +321,29 @@ def init_command(args):
321
321
  print(f" user: {username}")
322
322
  print(f" scanPaths: {scan_paths}")
323
323
 
324
+ try:
325
+ source = detect_source_repo({"gaiaUser": username})
326
+ if sys.stdin.isatty() and not getattr(args, 'yes', False):
327
+ try:
328
+ ans = input(f"Detected repo: {source}\nInitialize Gaia on this repository? [Y/n]: ").strip().lower()
329
+ except (KeyboardInterrupt, EOFError):
330
+ print()
331
+ import shutil; shutil.rmtree(config_dir, ignore_errors=True)
332
+ sys.exit(1)
333
+ if ans == 'n':
334
+ import shutil; shutil.rmtree(config_dir, ignore_errors=True)
335
+ print("Aborted.")
336
+ return
337
+ except NonPublicRepoError:
338
+ print(
339
+ "\nNo GitHub remote detected in this directory.\n"
340
+ " → To unlock the full workflow:\n"
341
+ " • Add a remote: git remote add origin https://github.com/<you>/<repo>\n"
342
+ " • Or clone the gaia-skill-tree registry and run gaia init there\n"
343
+ "Your skills are still scannable and pushable — once linked to a public repo,\n"
344
+ "approved skills will start at 2★ instead of 1★.\n"
345
+ )
346
+
324
347
  # If we're inside a registry clone, register its path globally so that
325
348
  # commands like `gaia push` work from any project without --registry.
326
349
  tree_path = user_tree_path(".", username)
@@ -358,6 +381,11 @@ def scan_command(args):
358
381
  scan_result = scan_repo_detailed()
359
382
  raw_tokens = {t.lstrip('/') for t in scan_result["tokens"]}
360
383
  graph_path = registry_graph_path(args.registry)
384
+
385
+ from gaia_cli.registry import bundled_registry_path
386
+ if not quiet and not use_json and str(args.registry) == str(bundled_registry_path()):
387
+ print("Note: using bundled registry (no local registry clone found).")
388
+
361
389
  resolved = resolve_skills(raw_tokens, registry_path=graph_path)
362
390
 
363
391
  username = config.get('gaiaUser')
@@ -387,6 +415,9 @@ def scan_command(args):
387
415
  if resolved:
388
416
  # Colored skill list with type glyphs
389
417
  graph_path_file = registry_graph_path(args.registry)
418
+ if not os.path.exists(graph_path_file):
419
+ print("Registry graph not found. Run `gaia init` from a gaia-skill-tree clone.")
420
+ return
390
421
  with open(graph_path_file, 'r', encoding='utf-8') as gf:
391
422
  gdata = json.load(gf)
392
423
  smap = {s['id']: s for s in gdata.get('skills', [])}
@@ -769,7 +800,11 @@ def promote_command(args):
769
800
 
770
801
  tree = load_tree(username, registry_path=args.registry)
771
802
  if not tree:
772
- print(f"No skill tree found for user '{username}'.")
803
+ if not os.path.exists(promotion_candidates_path(args.registry)):
804
+ print("No promotion candidates found. Run `gaia scan` first to detect skills.",
805
+ file=sys.stderr)
806
+ else:
807
+ print(f"No skill tree found for user '{username}'.", file=sys.stderr)
773
808
  return
774
809
 
775
810
  skill_id = getattr(args, 'skillId', None)
@@ -803,7 +838,12 @@ def promote_command(args):
803
838
  if picked:
804
839
  skill_id = picked
805
840
  if not skill_id:
806
- print("Usage: gaia promote <skill> or gaia promote --all", file=sys.stderr)
841
+ from gaia_cli.registry import promotion_candidates_path
842
+ if not os.path.exists(promotion_candidates_path(args.registry)):
843
+ print("No promotion candidates found. Run `gaia scan` first to detect skills.",
844
+ file=sys.stderr)
845
+ else:
846
+ print("Usage: gaia promote <skill> or gaia promote --all", file=sys.stderr)
807
847
  sys.exit(2)
808
848
  result = promote_from_candidates(username, skill_id, args.registry, new_display_name=display_name)
809
849
  except ValueError as exc:
@@ -1020,6 +1060,12 @@ def tree_command(args):
1020
1060
  show_tree(tree, graph_data=graph_data, registry_path=args.registry, mode=mode, canon=canon)
1021
1061
  if tree:
1022
1062
  render_user_tree_outputs(config.get('gaiaUser'), tree, graph_data, args.registry, quiet=False)
1063
+ try:
1064
+ detect_source_repo(config)
1065
+ except NonPublicRepoError:
1066
+ print("\nTip: link a public GitHub repo and approved skills will start at 2★ once named.")
1067
+ except Exception:
1068
+ pass
1023
1069
 
1024
1070
  def fuse_command(args):
1025
1071
  config = load_config()
@@ -1160,7 +1206,29 @@ def push_command(args):
1160
1206
  sys.exit(1)
1161
1207
 
1162
1208
  raw_tokens = scan_repo()
1163
- batch = build_skill_batch(raw_tokens, config, args.registry)
1209
+ try:
1210
+ batch = build_skill_batch(raw_tokens, config, args.registry)
1211
+ source_repo = batch["sourceRepo"]
1212
+ if sys.stdin.isatty() and not getattr(args, 'yes', False):
1213
+ try:
1214
+ ans = input(f"Push skills to gaia registry from {source_repo}? [Y/n]: ").strip().lower()
1215
+ except (KeyboardInterrupt, EOFError):
1216
+ print()
1217
+ sys.exit(1)
1218
+ if ans == 'n':
1219
+ print("Aborted.")
1220
+ return
1221
+ except NonPublicRepoError as exc:
1222
+ print(
1223
+ "\nYour skills are ready for review!\n"
1224
+ "Skills pushed from outside a public GitHub repo start at 1★ in the registry.\n"
1225
+ "Once you link a public repo, approved skills will start at 2★ instead.\n"
1226
+ " → Add a remote: git remote add origin https://github.com/<you>/<repo>\n",
1227
+ file=sys.stderr,
1228
+ )
1229
+ username_fallback = str(exc)
1230
+ batch = build_skill_batch(raw_tokens, config, args.registry,
1231
+ source_repo=f"{username_fallback}/local-repo")
1164
1232
 
1165
1233
  # Guard 1: check if empty initially
1166
1234
  if not batch.get("proposedSkills") and not batch.get("knownSkills"):
@@ -1689,6 +1757,7 @@ def get_parser():
1689
1757
  push_parser.add_argument('--dry-run', action='store_true', help="Print the skill batch without writing it")
1690
1758
  push_parser.add_argument('--no-issue', action='store_true', dest='no_issue', help="Write intake record without creating a GitHub issue")
1691
1759
  push_parser.add_argument('--no-pr', action='store_true', dest='no_issue', help=argparse.SUPPRESS) # backward compat alias
1760
+ push_parser.add_argument('--yes', '-y', action='store_true', dest='yes', help="Skip confirmation prompts")
1692
1761
  propose_parser = subparsers.add_parser('propose', help="Propose a single canonical skill as a named PR")
1693
1762
  propose_parser.add_argument('skillId', help="Canonical skill ID (accepts /skill-id form)")
1694
1763
  propose_parser.add_argument('--target', help="Named skill target in contributor/skill-name format")
@@ -15,6 +15,12 @@ from gaia_cli.registry import registry_graph_path, skill_batches_dir
15
15
 
16
16
 
17
17
  SKILL_ID_RE = re.compile(r"^[a-z][a-z0-9]*(-[a-z0-9]+)*$")
18
+
19
+
20
+ class NonPublicRepoError(RuntimeError):
21
+ """Raised when no public GitHub remote is detected for the working directory."""
22
+
23
+
18
24
  MIN_PROPOSED_TOKEN_LENGTH = 3
19
25
  PROPOSED_STOPWORDS = {
20
26
  "a", "an", "and", "are", "as", "at", "be", "by", "for", "from", "in", "is", "it", "of", "on", "or", "that", "the", "to", "was", "were", "with", "already",
@@ -68,7 +74,7 @@ def detect_source_repo(config):
68
74
  if host == "github.com" or host.endswith(".github.com"):
69
75
  return parsed.path.lstrip("/")
70
76
 
71
- return f"{config.get('gaiaUser', 'unknown')}/local-repo"
77
+ raise NonPublicRepoError(config.get('gaiaUser', 'unknown'))
72
78
 
73
79
 
74
80
  def build_proposed_skill(skill_id, source_repo):
@@ -126,11 +132,12 @@ def filter_proposed_ids(valid_tokens, canonical_ids):
126
132
  return filtered
127
133
 
128
134
 
129
- def build_skill_batch(raw_tokens, config, registry_root, now=None):
135
+ def build_skill_batch(raw_tokens, config, registry_root, now=None, source_repo=None):
130
136
  graph_path = registry_graph_path(registry_root)
131
137
  canonical_ids = load_canonical_skills(graph_path)
132
138
  canonical_map = load_canonical_skill_map(graph_path)
133
- source_repo = detect_source_repo(config)
139
+ if source_repo is None:
140
+ source_repo = detect_source_repo(config)
134
141
  timestamp = now or datetime.now(timezone.utc)
135
142
  generated_at = timestamp.replace(microsecond=0).isoformat().replace("+00:00", "Z")
136
143
 
@@ -6,7 +6,7 @@ from importlib import resources
6
6
  from pathlib import Path
7
7
 
8
8
 
9
- WRITE_COMMANDS = {"push", "propose", "name", "fuse", "embed", "sync", "promote", "graph", "release", "docs", "merge", "split", "add", "evidence"}
9
+ WRITE_COMMANDS = {"push", "propose", "name", "fuse", "embed", "sync", "promote", "release", "docs", "merge", "split", "add", "evidence"}
10
10
 
11
11
 
12
12
  def _gaia_home_dir() -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaia-cli
3
- Version: 4.3.6
3
+ Version: 4.3.8
4
4
  Summary: Gaia AI Agent Skill Registry CLI
5
5
  Author: Gaia contributors
6
6
  License: MIT
@@ -167,7 +167,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
167
167
  **1. Install the CLI**
168
168
 
169
169
  <!-- gaia:version-start -->
170
- Current Gaia CLI version: `4.3.6`.
170
+ Current Gaia CLI version: `4.3.8`.
171
171
 
172
172
  ```bash
173
173
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -212,6 +212,8 @@ packaging-specific tests are skipped locally with guidance to install developer
212
212
 
213
213
  ```bash
214
214
  gaia init --user your-username
215
+ # Detected repo: your-username/your-repo
216
+ # Initialize Gaia on this repository? [Y/n]: Y
215
217
  gaia scan
216
218
  ```
217
219
 
@@ -221,9 +223,11 @@ Detects skills your agent demonstrates.
221
223
 
222
224
  ```bash
223
225
  gaia push
226
+ # Push skills to gaia registry from your-username/your-repo? [Y/n]: Y
224
227
  ```
225
228
 
226
229
  A GitHub issue opens automatically. Maintainers review and promote; your name attaches at 2★.
230
+ If no remote is detected, Gaia will guide you to add one.
227
231
 
228
232
  **4. Bond your agent (optional)**
229
233
 
@@ -0,0 +1,97 @@
1
+ """Tests for the rank-less generic / inherited-evidence model."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ import unittest
7
+
8
+ sys.path.insert(
9
+ 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))
10
+ )
11
+
12
+ from gaia_cli.evidence import (
13
+ inherited_evidence,
14
+ merge_evidence,
15
+ build_generic_evidence_map,
16
+ )
17
+
18
+ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
+
20
+
21
+ class TestInheritedEvidence(unittest.TestCase):
22
+ def test_named_inherits_generic_capability_evidence(self):
23
+ generic = {
24
+ "id": "automated-testing",
25
+ "evidence": [
26
+ {"class": "A", "source": "https://arxiv.org/abs/0001"},
27
+ ],
28
+ }
29
+ named = {
30
+ "id": "x/pytest",
31
+ "evidence": [
32
+ {"class": "C", "source": "https://github.com/x/pytest"},
33
+ ],
34
+ }
35
+ pool = inherited_evidence(named, generic)
36
+ sources = {e["source"] for e in pool}
37
+ self.assertEqual(
38
+ sources, {"https://arxiv.org/abs/0001", "https://github.com/x/pytest"}
39
+ )
40
+
41
+ def test_merge_evidence_edge_cases(self):
42
+ self.assertEqual(merge_evidence(), [])
43
+ self.assertEqual(merge_evidence(None, None), [])
44
+ self.assertEqual(merge_evidence([], []), [])
45
+ self.assertEqual(merge_evidence([{}], [{}]), [{}])
46
+ self.assertEqual(
47
+ merge_evidence(
48
+ None, [{"source": "s1"}], [], [{"source": "s1"}, {"source": "s2"}]
49
+ ),
50
+ [{"source": "s1"}, {"source": "s2"}],
51
+ )
52
+
53
+ def test_dedup_by_source(self):
54
+ shared = {"class": "A", "source": "https://arxiv.org/abs/0001"}
55
+ pool = merge_evidence([shared], [dict(shared)])
56
+ self.assertEqual(len(pool), 1)
57
+
58
+ def test_no_generic_evidence(self):
59
+ named = {"id": "x/y", "evidence": [{"class": "C", "source": "s"}]}
60
+ self.assertEqual(len(inherited_evidence(named, None)), 1)
61
+
62
+ def test_build_generic_evidence_map(self):
63
+ skills = [{"id": "a", "evidence": [{"source": "s"}]}, {"id": "b"}]
64
+ m = build_generic_evidence_map(skills)
65
+ self.assertEqual(m["a"], [{"source": "s"}])
66
+ self.assertEqual(m["b"], [])
67
+
68
+
69
+ class TestRanklessGenerics(unittest.TestCase):
70
+ def test_no_generic_node_has_level_or_demerits(self):
71
+ graph = json.load(
72
+ open(os.path.join(REPO_ROOT, "registry", "gaia.json"), encoding="utf-8")
73
+ )
74
+ bad = [
75
+ s["id"]
76
+ for s in graph["skills"]
77
+ if "level" in s or "demerits" in s or "realVariants" in s
78
+ ]
79
+ self.assertEqual(bad, [], f"Generic refs must be rank-less; offenders: {bad}")
80
+
81
+ def test_named_skills_retain_levels(self):
82
+ idx = json.load(
83
+ open(
84
+ os.path.join(REPO_ROOT, "registry", "named-skills.json"),
85
+ encoding="utf-8",
86
+ )
87
+ )
88
+ valid = {"1★", "2★", "3★", "4★", "5★", "6★"}
89
+ for entries in idx["buckets"].values():
90
+ for e in entries:
91
+ self.assertIn(
92
+ e["level"], valid, f"{e['id']} has invalid level {e['level']}"
93
+ )
94
+
95
+
96
+ if __name__ == "__main__":
97
+ unittest.main()
@@ -4,7 +4,6 @@ import unittest
4
4
 
5
5
  REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6
6
  AUTO_TRIAGE_PATH = os.path.join(REPO_ROOT, ".github", "workflows", "auto-triage.yml")
7
- DOCS_REMINDER_PATH = os.path.join(REPO_ROOT, ".github", "workflows", "pr-docs-reminder.yml")
8
7
  BRANCH_SCOPE_PATH = os.path.join(REPO_ROOT, ".github", "workflows", "branch-scope.yml")
9
8
 
10
9
 
@@ -25,21 +24,11 @@ class TestWorkflowConfig(unittest.TestCase):
25
24
  with open(BRANCH_SCOPE_PATH, "r", encoding="utf-8") as f:
26
25
  content = f.read()
27
26
 
28
- self.assertIn("unrestricted branches (dev/*, claude/*, codex/*, chore/*) have no forward restriction", content)
27
+ self.assertIn("unrestricted branches (dev/*, claude/*, codex/*, gemini/*, chore/*) have no forward restriction", content)
29
28
  self.assertIn('[ "$PREFIX" != "unrestricted" ]', content)
30
29
  self.assertIn("skip-scope-check", content)
31
30
  self.assertNotIn("!startsWith(github.head_ref || '', 'dev/')", content)
32
31
 
33
- def test_pr_docs_reminder_shows_copyable_docs_check(self):
34
- with open(DOCS_REMINDER_PATH, "r", encoding="utf-8") as f:
35
- content = f.read()
36
-
37
- self.assertIn("pull_request:", content)
38
- self.assertIn("Repo Docs Before PR reminder", content)
39
- self.assertIn("python scripts/build_docs.py --check", content)
40
- self.assertIn("GITHUB_STEP_SUMMARY", content)
41
- self.assertNotIn("actions/checkout", content)
42
-
43
32
 
44
33
  if __name__ == "__main__":
45
34
  unittest.main()
@@ -1,62 +0,0 @@
1
- """Tests for the rank-less generic / inherited-evidence model."""
2
-
3
- import json
4
- import os
5
- import sys
6
- import unittest
7
-
8
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
9
-
10
- from gaia_cli.evidence import (
11
- inherited_evidence,
12
- merge_evidence,
13
- build_generic_evidence_map,
14
- )
15
-
16
- REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
-
18
-
19
- class TestInheritedEvidence(unittest.TestCase):
20
- def test_named_inherits_generic_capability_evidence(self):
21
- generic = {"id": "automated-testing", "evidence": [
22
- {"class": "A", "source": "https://arxiv.org/abs/0001"},
23
- ]}
24
- named = {"id": "x/pytest", "evidence": [
25
- {"class": "C", "source": "https://github.com/x/pytest"},
26
- ]}
27
- pool = inherited_evidence(named, generic)
28
- sources = {e["source"] for e in pool}
29
- self.assertEqual(sources, {"https://arxiv.org/abs/0001", "https://github.com/x/pytest"})
30
-
31
- def test_dedup_by_source(self):
32
- shared = {"class": "A", "source": "https://arxiv.org/abs/0001"}
33
- pool = merge_evidence([shared], [dict(shared)])
34
- self.assertEqual(len(pool), 1)
35
-
36
- def test_no_generic_evidence(self):
37
- named = {"id": "x/y", "evidence": [{"class": "C", "source": "s"}]}
38
- self.assertEqual(len(inherited_evidence(named, None)), 1)
39
-
40
- def test_build_generic_evidence_map(self):
41
- skills = [{"id": "a", "evidence": [{"source": "s"}]}, {"id": "b"}]
42
- m = build_generic_evidence_map(skills)
43
- self.assertEqual(m["a"], [{"source": "s"}])
44
- self.assertEqual(m["b"], [])
45
-
46
-
47
- class TestRanklessGenerics(unittest.TestCase):
48
- def test_no_generic_node_has_level_or_demerits(self):
49
- graph = json.load(open(os.path.join(REPO_ROOT, "registry", "gaia.json"), encoding="utf-8"))
50
- bad = [s["id"] for s in graph["skills"] if "level" in s or "demerits" in s or "realVariants" in s]
51
- self.assertEqual(bad, [], f"Generic refs must be rank-less; offenders: {bad}")
52
-
53
- def test_named_skills_retain_levels(self):
54
- idx = json.load(open(os.path.join(REPO_ROOT, "registry", "named-skills.json"), encoding="utf-8"))
55
- valid = {"1★", "2★", "3★", "4★", "5★", "6★"}
56
- for entries in idx["buckets"].values():
57
- for e in entries:
58
- self.assertIn(e["level"], valid, f"{e['id']} has invalid level {e['level']}")
59
-
60
-
61
- if __name__ == "__main__":
62
- unittest.main()
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