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.
- {aes_cli-0.6.0 → aes_cli-0.9.0}/PKG-INFO +1 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/__init__.py +1 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/__main__.py +9 -2
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/init.py +103 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/inspect.py +44 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/status.py +6 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/sync.py +118 -2
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/config.py +12 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/domains.py +211 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/global_config.py +17 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/_messages.py +1 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/ja.py +1 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/agent.yaml.jinja +42 -2
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/bom.yaml.jinja +1 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/instructions.md.jinja +4 -4
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/local.example.yaml.jinja +2 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/local.yaml.jinja +2 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/permissions.yaml.jinja +2 -2
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/skill.yaml.jinja +29 -2
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/workflow.yaml.jinja +1 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/agent.schema.json +131 -0
- aes_cli-0.9.0/aes/schemas/instinct.schema.json +137 -0
- aes_cli-0.9.0/aes/schemas/learning-config.schema.json +129 -0
- aes_cli-0.9.0/aes/schemas/lifecycle.schema.json +196 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/permissions.schema.json +35 -0
- aes_cli-0.9.0/aes/schemas/rules-config.schema.json +55 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/skill.schema.json +58 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/__init__.py +4 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/_base.py +5 -0
- aes_cli-0.9.0/aes/targets/_composer.py +828 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/claude.py +46 -1
- aes_cli-0.9.0/aes/targets/codex.py +125 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/copilot.py +24 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/cursor.py +24 -1
- aes_cli-0.9.0/aes/targets/openclaw.py +553 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/targets/windsurf.py +24 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/validator.py +83 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/PKG-INFO +1 -1
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/SOURCES.txt +12 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/pyproject.toml +1 -1
- aes_cli-0.9.0/tests/test_codex_target.py +304 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_init.py +3 -3
- aes_cli-0.9.0/tests/test_learning_validation.py +344 -0
- aes_cli-0.9.0/tests/test_lifecycle_validation.py +234 -0
- aes_cli-0.9.0/tests/test_openclaw_target.py +262 -0
- aes_cli-0.9.0/tests/test_project_locale.py +94 -0
- aes_cli-0.9.0/tests/test_rules_validation.py +146 -0
- aes_cli-0.6.0/aes/targets/_composer.py +0 -338
- {aes_cli-0.6.0 → aes_cli-0.9.0}/README.md +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/analyzer.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/__init__.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/bom.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/install.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/publish.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/search.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/upgrade.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/commands/validate.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/frameworks.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/__init__.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/i18n/domains_ja.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/mcp_server.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/migrations.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/registry.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/agentignore.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/operations.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/setup.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/skill.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/memory_command.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/operations.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/orchestrator.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/setup.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/skill.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/scaffold/workflow_command.md.jinja +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/bom.schema.json +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/decision-record.schema.json +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/registry.schema.json +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes/schemas/workflow.schema.json +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/dependency_links.txt +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/entry_points.txt +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/requires.txt +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/aes_cli.egg-info/top_level.txt +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/setup.cfg +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_analyzer.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_bom.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_frameworks.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_inspect.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_install.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_mcp_server.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_publish.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_registry.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_search.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_status.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_sync.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_upgrade.py +0 -0
- {aes_cli-0.6.0 → aes_cli-0.9.0}/tests/test_validate.py +0 -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|