aes-cli 0.6.0__tar.gz → 0.9.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 (99) hide show
  1. {aes_cli-0.6.0 → aes_cli-0.9.0}/PKG-INFO +1 -1
  2. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/__init__.py +1 -1
  3. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/__main__.py +9 -2
  4. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/init.py +103 -0
  5. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/inspect.py +44 -1
  6. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/status.py +6 -1
  7. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/sync.py +118 -2
  8. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/config.py +12 -0
  9. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/domains.py +211 -0
  10. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/global_config.py +17 -0
  11. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/_messages.py +1 -0
  12. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/ja.py +1 -0
  13. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/agent.yaml.jinja +42 -2
  14. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/bom.yaml.jinja +1 -1
  15. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/instructions.md.jinja +4 -4
  16. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/local.example.yaml.jinja +2 -0
  17. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/local.yaml.jinja +2 -0
  18. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/permissions.yaml.jinja +2 -2
  19. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/skill.yaml.jinja +29 -2
  20. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/workflow.yaml.jinja +1 -1
  21. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/agent.schema.json +131 -0
  22. aes_cli-0.9.0/aes/schemas/instinct.schema.json +137 -0
  23. aes_cli-0.9.0/aes/schemas/learning-config.schema.json +129 -0
  24. aes_cli-0.9.0/aes/schemas/lifecycle.schema.json +196 -0
  25. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/permissions.schema.json +35 -0
  26. aes_cli-0.9.0/aes/schemas/rules-config.schema.json +55 -0
  27. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/skill.schema.json +58 -0
  28. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/__init__.py +4 -0
  29. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/_base.py +5 -0
  30. aes_cli-0.9.0/aes/targets/_composer.py +828 -0
  31. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/claude.py +46 -1
  32. aes_cli-0.9.0/aes/targets/codex.py +125 -0
  33. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/copilot.py +24 -1
  34. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/cursor.py +24 -1
  35. aes_cli-0.9.0/aes/targets/openclaw.py +553 -0
  36. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/windsurf.py +24 -1
  37. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/validator.py +83 -1
  38. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/PKG-INFO +1 -1
  39. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/SOURCES.txt +12 -0
  40. {aes_cli-0.6.0 → aes_cli-0.9.0}/pyproject.toml +1 -1
  41. aes_cli-0.9.0/tests/test_codex_target.py +304 -0
  42. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_init.py +3 -3
  43. aes_cli-0.9.0/tests/test_learning_validation.py +344 -0
  44. aes_cli-0.9.0/tests/test_lifecycle_validation.py +234 -0
  45. aes_cli-0.9.0/tests/test_openclaw_target.py +262 -0
  46. aes_cli-0.9.0/tests/test_project_locale.py +94 -0
  47. aes_cli-0.9.0/tests/test_rules_validation.py +146 -0
  48. aes_cli-0.6.0/aes/targets/_composer.py +0 -338
  49. {aes_cli-0.6.0 → aes_cli-0.9.0}/README.md +0 -0
  50. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/analyzer.py +0 -0
  51. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/__init__.py +0 -0
  52. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/bom.py +0 -0
  53. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/install.py +0 -0
  54. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/publish.py +0 -0
  55. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/search.py +0 -0
  56. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/upgrade.py +0 -0
  57. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/validate.py +0 -0
  58. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/frameworks.py +0 -0
  59. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/__init__.py +0 -0
  60. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/domains_ja.py +0 -0
  61. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/mcp_server.py +0 -0
  62. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/migrations.py +0 -0
  63. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/registry.py +0 -0
  64. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/agentignore.jinja +0 -0
  65. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
  66. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
  67. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/operations.md.jinja +0 -0
  68. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
  69. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/setup.md.jinja +0 -0
  70. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/skill.md.jinja +0 -0
  71. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
  72. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/memory_command.md.jinja +0 -0
  73. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/operations.md.jinja +0 -0
  74. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/orchestrator.md.jinja +0 -0
  75. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/setup.md.jinja +0 -0
  76. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/skill.md.jinja +0 -0
  77. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/workflow_command.md.jinja +0 -0
  78. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/bom.schema.json +0 -0
  79. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/decision-record.schema.json +0 -0
  80. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/registry.schema.json +0 -0
  81. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/workflow.schema.json +0 -0
  82. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/dependency_links.txt +0 -0
  83. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/entry_points.txt +0 -0
  84. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/requires.txt +0 -0
  85. {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/top_level.txt +0 -0
  86. {aes_cli-0.6.0 → aes_cli-0.9.0}/setup.cfg +0 -0
  87. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_analyzer.py +0 -0
  88. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_bom.py +0 -0
  89. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_frameworks.py +0 -0
  90. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_inspect.py +0 -0
  91. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_install.py +0 -0
  92. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_mcp_server.py +0 -0
  93. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_publish.py +0 -0
  94. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_registry.py +0 -0
  95. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_search.py +0 -0
  96. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_status.py +0 -0
  97. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_sync.py +0 -0
  98. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_upgrade.py +0 -0
  99. {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aes-cli
3
- Version: 0.6.0
3
+ Version: 0.9.0
4
4
  Summary: CLI tool for the Agentic Engineering Standard
5
5
  Author: Hiro
6
6
  License: Apache-2.0
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.6.0"
5
+ __version__ = "0.9.0"
@@ -53,7 +53,7 @@ def cli(lang: Optional[str] = None) -> None:
53
53
  """
54
54
  from aes.i18n import init_locale
55
55
 
56
- # Priority: --lang flag > AES_LANG env > ~/.aes/config.yaml > first-run prompt
56
+ # Priority: --lang flag > AES_LANG env > .agent/local.yaml > ~/.aes/config.yaml > first-run prompt
57
57
  if lang:
58
58
  init_locale(lang)
59
59
  return
@@ -63,7 +63,14 @@ def cli(lang: Optional[str] = None) -> None:
63
63
  init_locale(env_lang)
64
64
  return
65
65
 
66
- from aes.global_config import get_locale
66
+ from pathlib import Path
67
+ from aes.global_config import get_locale, get_project_locale
68
+
69
+ project_locale = get_project_locale(Path.cwd())
70
+ if project_locale:
71
+ init_locale(project_locale)
72
+ return
73
+
67
74
  saved = get_locale()
68
75
  if saved is None and sys.stdin.isatty():
69
76
  _prompt_language()
@@ -22,9 +22,17 @@ from aes.config import (
22
22
  AGENTIGNORE_FILE,
23
23
  BOM_FILE,
24
24
  DECISIONS_DIR,
25
+ INSTINCTS_DIR,
26
+ LEARNING_CONFIG_FILE,
27
+ LEARNING_DIR,
28
+ LIFECYCLE_FILE,
25
29
  LOCAL_EXAMPLE_FILE,
26
30
  LOCAL_FILE,
31
+ LOGS_DIR,
32
+ RULES_CONFIG_FILE,
33
+ RULES_DIR,
27
34
  SCAFFOLD_DIR,
35
+ SCRIPTS_DIR,
28
36
  SKILLS_DIR,
29
37
  REGISTRY_DIR,
30
38
  WORKFLOWS_DIR,
@@ -254,6 +262,7 @@ def _get_agent_integrated_types() -> list:
254
262
  return [
255
263
  (t("init.type_ml"), "ml"),
256
264
  (t("init.type_research"), "research"),
265
+ (t("init.type_assistant"), "assistant"),
257
266
  (t("init.type_custom"), "other"),
258
267
  ]
259
268
 
@@ -734,6 +743,100 @@ def init_cmd(
734
743
  content = _render_template(env, "operations.md.jinja", ops_context)
735
744
  (agent_dir / MEMORY_DIR / "operations.md").write_text(content)
736
745
 
746
+ # Lifecycle hooks (all domains)
747
+ if domain_config.scaffold_lifecycle:
748
+ (agent_dir / SCRIPTS_DIR).mkdir(exist_ok=True)
749
+ (agent_dir / LOGS_DIR).mkdir(exist_ok=True)
750
+ lifecycle_content = (
751
+ "# .agent/lifecycle.yaml — Lifecycle Hooks\n"
752
+ "apiVersion: aes/v1\n"
753
+ "kind: Lifecycle\n"
754
+ f"\nprofile: {domain_config.lifecycle_profile}\n"
755
+ "\nhooks:\n"
756
+ " on_session_start:\n"
757
+ " - name: restore-context\n"
758
+ " description: Load previous session summary into context\n"
759
+ " profile: minimal\n"
760
+ " action: script\n"
761
+ " command: node .agent/scripts/restore-context.js\n"
762
+ " timeout_seconds: 10\n"
763
+ " async: false\n"
764
+ " fail_strategy: warn\n"
765
+ "\n"
766
+ " on_session_end:\n"
767
+ " - name: persist-summary\n"
768
+ " description: Save session summary for next session\n"
769
+ " profile: minimal\n"
770
+ " action: script\n"
771
+ " command: node .agent/scripts/persist-summary.js\n"
772
+ " timeout_seconds: 15\n"
773
+ " async: true\n"
774
+ " fail_strategy: warn\n"
775
+ )
776
+ (agent_dir / LIFECYCLE_FILE).write_text(lifecycle_content)
777
+
778
+ # Continuous learning (agent-integrated domains only)
779
+ if domain_config.scaffold_learning:
780
+ (agent_dir / LEARNING_DIR).mkdir(exist_ok=True)
781
+ (agent_dir / INSTINCTS_DIR / "active").mkdir(parents=True, exist_ok=True)
782
+ (agent_dir / INSTINCTS_DIR / "candidates").mkdir(exist_ok=True)
783
+ (agent_dir / INSTINCTS_DIR / "archived").mkdir(exist_ok=True)
784
+ learning_content = (
785
+ "# .agent/learning/config.yaml\n"
786
+ "apiVersion: aes/v1\n"
787
+ "kind: LearningConfig\n"
788
+ "\nextraction:\n"
789
+ " enabled: true\n"
790
+ " auto_extract: true\n"
791
+ " min_session_length: 5\n"
792
+ " max_candidates_per_session: 3\n"
793
+ "\nconfidence:\n"
794
+ " initial_score: 0.4\n"
795
+ " promotion_threshold: 0.6\n"
796
+ " promotion_min_validations: 3\n"
797
+ " decay_rate_per_week: 0.01\n"
798
+ " min_score: 0.3\n"
799
+ "\ncontext_loading:\n"
800
+ " max_instincts_in_context: 10\n"
801
+ " sort_by: confidence_score\n"
802
+ " token_budget: 2000\n"
803
+ " format: compact\n"
804
+ )
805
+ (agent_dir / LEARNING_CONFIG_FILE).write_text(learning_content)
806
+
807
+ # Rules & conventions (all domains)
808
+ if domain_config.scaffold_rules:
809
+ (agent_dir / RULES_DIR).mkdir(exist_ok=True)
810
+ (agent_dir / RULES_DIR / "common").mkdir(exist_ok=True)
811
+ rules_content = (
812
+ "# .agent/rules/rules.yaml\n"
813
+ "apiVersion: aes/v1\n"
814
+ "kind: RulesConfig\n"
815
+ "\nloading:\n"
816
+ " always: [common]\n"
817
+ )
818
+ (agent_dir / RULES_CONFIG_FILE).write_text(rules_content)
819
+
820
+ # Starter rule
821
+ starter_rule = (
822
+ "---\n"
823
+ "name: testing\n"
824
+ "scope: common\n"
825
+ "priority: high\n"
826
+ "---\n\n"
827
+ "# Testing Standards\n\n"
828
+ "## Requirements\n"
829
+ "- All public functions must have unit tests\n"
830
+ "- No test should take longer than 5 seconds\n\n"
831
+ "## Patterns\n"
832
+ "- Use Arrange-Act-Assert structure\n"
833
+ "- Mock external dependencies\n\n"
834
+ "## Anti-patterns\n"
835
+ "- Never test implementation details\n"
836
+ "- Never share state between tests\n"
837
+ )
838
+ (agent_dir / RULES_DIR / "common" / "testing.md").write_text(starter_rule)
839
+
737
840
  # Auto-sync: generate tool-specific config files
738
841
  synced_files = run_sync(project_root, force=True, quiet=True)
739
842
  _write_mcp_config(project_root)
@@ -12,7 +12,11 @@ import yaml
12
12
  from rich.console import Console
13
13
  from rich.table import Table
14
14
 
15
- from aes.config import AGENT_DIR, BOM_FILE, DECISIONS_DIR
15
+ from aes.config import (
16
+ AGENT_DIR, BOM_FILE, DECISIONS_DIR,
17
+ INSTINCTS_DIR, LEARNING_CONFIG_FILE, LIFECYCLE_FILE,
18
+ RULES_CONFIG_FILE, RULES_DIR,
19
+ )
16
20
  from aes.i18n import t
17
21
  from aes.registry import (
18
22
  fetch_index,
@@ -246,6 +250,45 @@ def _inspect_local(path: str) -> None:
246
250
  console.print(f" {t('inspect.decisions_count', count=dr_count)}")
247
251
  console.print()
248
252
 
253
+ # Lifecycle hooks
254
+ lifecycle_path = agent_dir / LIFECYCLE_FILE
255
+ if lifecycle_path.exists():
256
+ lc = _load_yaml(lifecycle_path)
257
+ hooks = lc.get("hooks", {}) or {}
258
+ hook_count = sum(
259
+ len(v) if isinstance(v, list) else (1 if isinstance(v, dict) else 0)
260
+ for v in hooks.values()
261
+ )
262
+ profile = lc.get("profile", "standard")
263
+ console.print(f"[bold]Lifecycle[/]")
264
+ console.print(f" Profile: {profile} | Hooks: {hook_count}")
265
+ console.print()
266
+
267
+ # Learning / instincts
268
+ learning_config_path = agent_dir / LEARNING_CONFIG_FILE
269
+ if learning_config_path.exists():
270
+ lconf = _load_yaml(learning_config_path)
271
+ extraction = lconf.get("extraction", {}).get("enabled", True)
272
+ instincts_base = agent_dir / INSTINCTS_DIR
273
+ active = len(list((instincts_base / "active").glob("*.instinct.yaml"))) if (instincts_base / "active").exists() else 0
274
+ candidates = len(list((instincts_base / "candidates").glob("*.instinct.yaml"))) if (instincts_base / "candidates").exists() else 0
275
+ archived = len(list((instincts_base / "archived").glob("*.instinct.yaml"))) if (instincts_base / "archived").exists() else 0
276
+ console.print(f"[bold]Learning[/]")
277
+ console.print(f" Extraction: {'enabled' if extraction else 'disabled'} | Active: {active} | Candidates: {candidates} | Archived: {archived}")
278
+ console.print()
279
+
280
+ # Rules
281
+ rules_config_path = agent_dir / RULES_CONFIG_FILE
282
+ if rules_config_path.exists():
283
+ rconf = _load_yaml(rules_config_path)
284
+ languages = rconf.get("languages", [])
285
+ rules_base = agent_dir / RULES_DIR
286
+ rule_count = len(list(rules_base.glob("**/*.md"))) if rules_base.exists() else 0
287
+ lang_str = ", ".join(languages) if languages else "auto-detect"
288
+ console.print(f"[bold]Rules[/]")
289
+ console.print(f" Languages: {lang_str} | Rule files: {rule_count}")
290
+ console.print()
291
+
249
292
  # Summary
250
293
  console.print(f"[bold]{t('inspect.summary')}[/]")
251
294
  console.print(f" {t('inspect.skills_count', count=len(skills))}")
@@ -70,7 +70,12 @@ def status_cmd(path: str) -> None:
70
70
 
71
71
  for name in TARGET_NAMES:
72
72
  adapter = TARGETS[name]()
73
- plan = adapter.plan(ctx, force=True)
73
+ try:
74
+ plan = adapter.plan(ctx, force=True)
75
+ except Exception:
76
+ # Target-specific validation failure (e.g. openclaw requires
77
+ # identity/model) — skip incompatible targets silently in status.
78
+ continue
74
79
  for gf in plan.files:
75
80
  would_generate[gf.relative_path] = gf.content
76
81
 
@@ -16,10 +16,15 @@ from rich.console import Console
16
16
  from aes.config import (
17
17
  AGENT_DIR,
18
18
  COMMANDS_DIR,
19
+ INSTINCTS_DIR,
20
+ LEARNING_CONFIG_FILE,
21
+ LIFECYCLE_FILE,
19
22
  LOCAL_FILE,
20
23
  MANIFEST_FILE,
21
24
  MEMORY_DIR,
22
25
  PERMISSIONS_FILE,
26
+ RULES_CONFIG_FILE,
27
+ RULES_DIR,
23
28
  SKILLS_DIR,
24
29
  )
25
30
  from aes.i18n import t
@@ -64,7 +69,15 @@ def run_sync(
64
69
  all_plans: List[SyncPlan] = []
65
70
  for name in selected:
66
71
  adapter = TARGETS[name]()
67
- all_plans.append(adapter.plan(ctx, force))
72
+ try:
73
+ all_plans.append(adapter.plan(ctx, force))
74
+ except click.ClickException:
75
+ # Target-specific validation failure — skip when syncing
76
+ # multiple targets (e.g. openclaw requires identity/model
77
+ # which non-assistant projects won't have).
78
+ if len(selected) == 1:
79
+ raise
80
+ continue
68
81
 
69
82
  sync_manifest = _load_sync_manifest(project_root)
70
83
  written = 0
@@ -158,6 +171,14 @@ def _load_agent_context(project_root: Path) -> AgentContext:
158
171
  "negative_triggers": skill_data.get("negative_triggers", []),
159
172
  "activation": skill_data.get("activation", "explicit"),
160
173
  "allowed_tools": skill_data.get("allowed_tools"),
174
+ "version": skill_data.get("version", "0.1.0"),
175
+ "emoji": skill_data.get("emoji", ""),
176
+ "license": skill_data.get("license", "MIT"),
177
+ "user_invocable": skill_data.get("user_invocable", True),
178
+ "primary_env": skill_data.get("primary_env", ""),
179
+ "requires_bins": (skill_data.get("requires") or {}).get("bins", []),
180
+ "requires_env": (skill_data.get("requires") or {}).get("env", []),
181
+ "mcp_server": skill_data.get("mcp_server"),
161
182
  }
162
183
  if skill_id not in skill_metadata:
163
184
  skill_metadata[skill_id] = {
@@ -208,6 +229,84 @@ def _load_agent_context(project_root: Path) -> AgentContext:
208
229
  if local_perms and permissions:
209
230
  permissions = _deep_merge(permissions, local_perms)
210
231
 
232
+ # Load lifecycle.yaml
233
+ lifecycle: Optional[dict] = None
234
+ lifecycle_path = agent_dir / LIFECYCLE_FILE
235
+ if lifecycle_path.exists():
236
+ with open(lifecycle_path) as f:
237
+ lifecycle = yaml.safe_load(f) or {}
238
+
239
+ # Load learning config and active instincts
240
+ learning_config: Optional[dict] = None
241
+ active_instincts: List[dict] = []
242
+ learning_config_path = agent_dir / LEARNING_CONFIG_FILE
243
+ if learning_config_path.exists():
244
+ with open(learning_config_path) as f:
245
+ learning_config = yaml.safe_load(f) or {}
246
+
247
+ instincts_active_dir = agent_dir / INSTINCTS_DIR / "active"
248
+ if instincts_active_dir.exists():
249
+ max_instincts = 10
250
+ if learning_config:
251
+ max_instincts = (
252
+ learning_config.get("context_loading", {})
253
+ .get("max_instincts_in_context", 10)
254
+ )
255
+ raw_instincts = []
256
+ for inst_file in sorted(instincts_active_dir.glob("*.instinct.yaml")):
257
+ with open(inst_file) as f:
258
+ inst_data = yaml.safe_load(f)
259
+ if inst_data:
260
+ raw_instincts.append(inst_data)
261
+ # Sort by confidence score descending
262
+ raw_instincts.sort(
263
+ key=lambda i: i.get("confidence", {}).get("score", 0),
264
+ reverse=True,
265
+ )
266
+ active_instincts = raw_instincts[:max_instincts]
267
+
268
+ # Load rules config and rule files
269
+ rules_config: Optional[dict] = None
270
+ rules_files: Dict[str, str] = {}
271
+ rules_config_path = agent_dir / RULES_CONFIG_FILE
272
+ if rules_config_path.exists():
273
+ with open(rules_config_path) as f:
274
+ rules_config = yaml.safe_load(f) or {}
275
+
276
+ rules_base = agent_dir / RULES_DIR
277
+ overrides = rules_config.get("overrides", {})
278
+
279
+ # Determine which language directories to load
280
+ languages = rules_config.get("languages", [])
281
+ if not languages:
282
+ # Auto-detect from project root
283
+ detection = rules_config.get("detection", {})
284
+ for lang, patterns in detection.items():
285
+ for pattern in patterns:
286
+ if list(project_root.glob(pattern)):
287
+ languages.append(lang)
288
+ break
289
+
290
+ # Always-load directories (default: common)
291
+ always = rules_config.get("loading", {}).get("always", ["common"])
292
+ dirs_to_load = list(always) + languages
293
+
294
+ for dir_name in dirs_to_load:
295
+ rule_dir = rules_base / dir_name
296
+ if not rule_dir.exists():
297
+ continue
298
+ for md_file in sorted(rule_dir.glob("*.md")):
299
+ content = md_file.read_text()
300
+ # Resolve ${variable} placeholders from overrides
301
+ rule_name = md_file.stem
302
+ rule_overrides = overrides.get(rule_name, {})
303
+ for var_name, var_value in rule_overrides.items():
304
+ content = content.replace(
305
+ f"${{{var_name}}}", str(var_value)
306
+ )
307
+ key = f"{dir_name}/{md_file.name}"
308
+ rules_files[key] = content
309
+
211
310
  return AgentContext(
212
311
  project_root=project_root,
213
312
  agent_dir=agent_dir,
@@ -220,6 +319,11 @@ def _load_agent_context(project_root: Path) -> AgentContext:
220
319
  memory_project=memory_project,
221
320
  skill_metadata=skill_metadata,
222
321
  local_config=local_config,
322
+ lifecycle=lifecycle,
323
+ learning_config=learning_config,
324
+ active_instincts=active_instincts,
325
+ rules_config=rules_config,
326
+ rules_files=rules_files,
223
327
  )
224
328
 
225
329
 
@@ -305,7 +409,19 @@ def sync_cmd(
305
409
  all_plans: List[SyncPlan] = []
306
410
  for target_name in selected:
307
411
  adapter = TARGETS[target_name]()
308
- sync_plan = adapter.plan(ctx, force)
412
+ try:
413
+ sync_plan = adapter.plan(ctx, force)
414
+ except click.ClickException as exc:
415
+ # Target-specific validation failure (e.g. openclaw requires
416
+ # identity/model). When syncing a single explicit target, re-raise
417
+ # so the user sees the error. When syncing all targets, skip and
418
+ # warn — not every project is compatible with every target.
419
+ if len(selected) == 1:
420
+ raise
421
+ console.print(
422
+ f" [yellow]⚠ {target_name}:[/] {exc.format_message()}"
423
+ )
424
+ continue
309
425
  all_plans.append(sync_plan)
310
426
 
311
427
  # Execute plans
@@ -34,6 +34,14 @@ OVERRIDES_DIR = "overrides"
34
34
  LOCAL_FILE = "local.yaml"
35
35
  LOCAL_EXAMPLE_FILE = "local.example.yaml"
36
36
  BOM_FILE = "bom.yaml"
37
+ LIFECYCLE_FILE = "lifecycle.yaml"
38
+ LEARNING_DIR = "learning"
39
+ LEARNING_CONFIG_FILE = "learning/config.yaml"
40
+ INSTINCTS_DIR = "learning/instincts"
41
+ RULES_DIR = "rules"
42
+ RULES_CONFIG_FILE = "rules/rules.yaml"
43
+ SCRIPTS_DIR = "scripts"
44
+ LOGS_DIR = "logs"
37
45
 
38
46
  # Schema file mapping
39
47
  SCHEMA_MAP = {
@@ -44,4 +52,8 @@ SCHEMA_MAP = {
44
52
  "permissions": "permissions.schema.json",
45
53
  "bom": "bom.schema.json",
46
54
  "decision-record": "decision-record.schema.json",
55
+ "lifecycle": "lifecycle.schema.json",
56
+ "instinct": "instinct.schema.json",
57
+ "learning-config": "learning-config.schema.json",
58
+ "rules-config": "rules-config.schema.json",
47
59
  }