aes-cli 0.7.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.7.0 → aes_cli-0.9.0}/PKG-INFO +1 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/__init__.py +1 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/__main__.py +9 -2
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/init.py +102 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/inspect.py +44 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/sync.py +88 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/config.py +12 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/domains.py +10 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/global_config.py +17 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/agent.yaml.jinja +2 -2
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/bom.yaml.jinja +1 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/instructions.md.jinja +4 -4
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/local.example.yaml.jinja +2 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/local.yaml.jinja +2 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/permissions.yaml.jinja +2 -2
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/skill.yaml.jinja +2 -2
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/workflow.yaml.jinja +1 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/agent.schema.json +20 -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.9.0/aes/schemas/rules-config.schema.json +55 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/__init__.py +2 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/_base.py +5 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/_composer.py +206 -2
- {aes_cli-0.7.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.7.0 → aes_cli-0.9.0}/aes/targets/copilot.py +24 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/cursor.py +24 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/openclaw.py +51 -5
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/windsurf.py +24 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/validator.py +83 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/PKG-INFO +1 -1
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/SOURCES.txt +10 -0
- {aes_cli-0.7.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.7.0 → aes_cli-0.9.0}/tests/test_init.py +1 -1
- 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_project_locale.py +94 -0
- aes_cli-0.9.0/tests/test_rules_validation.py +146 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/README.md +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/analyzer.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/__init__.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/bom.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/install.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/publish.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/search.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/status.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/upgrade.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/validate.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/frameworks.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/__init__.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/_messages.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/domains_ja.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/ja.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/mcp_server.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/migrations.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/registry.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/agentignore.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/operations.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/setup.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/skill.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/memory_command.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/operations.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/orchestrator.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/setup.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/skill.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/workflow_command.md.jinja +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/bom.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/decision-record.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/permissions.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/registry.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/skill.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/workflow.schema.json +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/dependency_links.txt +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/entry_points.txt +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/requires.txt +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/top_level.txt +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/setup.cfg +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_analyzer.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_bom.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_frameworks.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_inspect.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_install.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_mcp_server.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_openclaw_target.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_publish.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_registry.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_search.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_status.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_sync.py +0 -0
- {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_upgrade.py +0 -0
- {aes_cli-0.7.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,
|
|
@@ -735,6 +743,100 @@ def init_cmd(
|
|
|
735
743
|
content = _render_template(env, "operations.md.jinja", ops_context)
|
|
736
744
|
(agent_dir / MEMORY_DIR / "operations.md").write_text(content)
|
|
737
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
|
+
|
|
738
840
|
# Auto-sync: generate tool-specific config files
|
|
739
841
|
synced_files = run_sync(project_root, force=True, quiet=True)
|
|
740
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))}")
|
|
@@ -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
|
|
@@ -224,6 +229,84 @@ def _load_agent_context(project_root: Path) -> AgentContext:
|
|
|
224
229
|
if local_perms and permissions:
|
|
225
230
|
permissions = _deep_merge(permissions, local_perms)
|
|
226
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
|
+
|
|
227
310
|
return AgentContext(
|
|
228
311
|
project_root=project_root,
|
|
229
312
|
agent_dir=agent_dir,
|
|
@@ -236,6 +319,11 @@ def _load_agent_context(project_root: Path) -> AgentContext:
|
|
|
236
319
|
memory_project=memory_project,
|
|
237
320
|
skill_metadata=skill_metadata,
|
|
238
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,
|
|
239
327
|
)
|
|
240
328
|
|
|
241
329
|
|
|
@@ -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
|
}
|
|
@@ -154,6 +154,12 @@ class DomainConfig:
|
|
|
154
154
|
heartbeat_checklist: str = ""
|
|
155
155
|
channels: Dict[str, Dict[str, str]] = field(default_factory=dict)
|
|
156
156
|
|
|
157
|
+
# Lifecycle / Learning / Rules scaffolding
|
|
158
|
+
scaffold_lifecycle: bool = True
|
|
159
|
+
scaffold_learning: bool = False # True for agent-integrated domains
|
|
160
|
+
scaffold_rules: bool = True
|
|
161
|
+
lifecycle_profile: str = "standard"
|
|
162
|
+
|
|
157
163
|
|
|
158
164
|
# ---------------------------------------------------------------------------
|
|
159
165
|
# ML domain config — drawn from examples/ml-pipeline
|
|
@@ -529,6 +535,7 @@ ML_CONFIG = DomainConfig(
|
|
|
529
535
|
{"name": "OPTUNA_TIMEOUT", "default": "300", "description": "Seconds per model for HPO"},
|
|
530
536
|
{"name": "OPTUNA_N_TRIALS", "default": "50", "description": "Max trials per model"},
|
|
531
537
|
],
|
|
538
|
+
scaffold_learning=True,
|
|
532
539
|
)
|
|
533
540
|
|
|
534
541
|
|
|
@@ -1338,6 +1345,7 @@ RESEARCH_CONFIG = DomainConfig(
|
|
|
1338
1345
|
{"name": "SEMANTIC_SCHOLAR_API_KEY", "default": "", "description": "Semantic Scholar API key (optional, increases rate limits)"},
|
|
1339
1346
|
{"name": "MAX_ITEMS_PER_RUN", "default": "50", "description": "Maximum items to process per pipeline run"},
|
|
1340
1347
|
],
|
|
1348
|
+
scaffold_learning=True,
|
|
1341
1349
|
)
|
|
1342
1350
|
|
|
1343
1351
|
|
|
@@ -1387,6 +1395,7 @@ AGENT_INTEGRATED_BASE_CONFIG = DomainConfig(
|
|
|
1387
1395
|
),
|
|
1388
1396
|
],
|
|
1389
1397
|
instructions_description="", # empty -> falls through to TODO scaffolding in template
|
|
1398
|
+
scaffold_learning=True,
|
|
1390
1399
|
)
|
|
1391
1400
|
|
|
1392
1401
|
|
|
@@ -1571,6 +1580,7 @@ ASSISTANT_CONFIG = DomainConfig(
|
|
|
1571
1580
|
{"name": "TELEGRAM_BOT_TOKEN", "description": "Bot token for Telegram integration"},
|
|
1572
1581
|
{"name": "DISCORD_BOT_TOKEN", "description": "Bot token for Discord integration"},
|
|
1573
1582
|
],
|
|
1583
|
+
scaffold_learning=True,
|
|
1574
1584
|
)
|
|
1575
1585
|
|
|
1576
1586
|
|
|
@@ -27,6 +27,23 @@ def save_global_config(config: dict) -> None:
|
|
|
27
27
|
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def get_project_locale(project_path: Path) -> Optional[str]:
|
|
31
|
+
"""Return locale from .agent/local.yaml in project, or None."""
|
|
32
|
+
local_yaml = project_path / ".agent" / "local.yaml"
|
|
33
|
+
if not local_yaml.exists():
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
with open(local_yaml) as f:
|
|
37
|
+
data = yaml.safe_load(f)
|
|
38
|
+
if isinstance(data, dict):
|
|
39
|
+
locale = data.get("locale")
|
|
40
|
+
if isinstance(locale, str) and locale:
|
|
41
|
+
return locale
|
|
42
|
+
except (OSError, yaml.YAMLError):
|
|
43
|
+
pass
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
30
47
|
def get_locale() -> Optional[str]:
|
|
31
48
|
"""Return configured locale or None."""
|
|
32
49
|
return load_global_config().get("locale")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{% if domain_config %}
|
|
2
2
|
# .agent/agent.yaml — AES Manifest
|
|
3
|
-
aes: "1.
|
|
3
|
+
aes: "1.4"
|
|
4
4
|
|
|
5
5
|
name: "{{ name }}"
|
|
6
6
|
version: "0.1.0"
|
|
@@ -121,7 +121,7 @@ environment:
|
|
|
121
121
|
{% endif %}
|
|
122
122
|
{% else %}
|
|
123
123
|
# .agent/agent.yaml — AES Manifest
|
|
124
|
-
aes: "1.
|
|
124
|
+
aes: "1.4"
|
|
125
125
|
|
|
126
126
|
name: "{{ name }}"
|
|
127
127
|
version: "0.1.0"
|
|
@@ -51,7 +51,7 @@ When creating or editing `.agent/` files, follow these formats exactly.
|
|
|
51
51
|
### Skill manifest (`.skill.yaml`)
|
|
52
52
|
|
|
53
53
|
```yaml
|
|
54
|
-
aes_skill: "1.
|
|
54
|
+
aes_skill: "1.4"
|
|
55
55
|
|
|
56
56
|
id: "my-skill"
|
|
57
57
|
name: "My Skill"
|
|
@@ -100,7 +100,7 @@ tags:
|
|
|
100
100
|
### Workflow (`.yaml`)
|
|
101
101
|
|
|
102
102
|
```yaml
|
|
103
|
-
aes_workflow: "1.
|
|
103
|
+
aes_workflow: "1.4"
|
|
104
104
|
|
|
105
105
|
id: "my-workflow"
|
|
106
106
|
entity: "item"
|
|
@@ -226,7 +226,7 @@ When creating or editing `.agent/` files, follow these formats exactly.
|
|
|
226
226
|
### Skill manifest (`.skill.yaml`)
|
|
227
227
|
|
|
228
228
|
```yaml
|
|
229
|
-
aes_skill: "1.
|
|
229
|
+
aes_skill: "1.4"
|
|
230
230
|
|
|
231
231
|
id: "my-skill"
|
|
232
232
|
name: "My Skill"
|
|
@@ -275,7 +275,7 @@ tags:
|
|
|
275
275
|
### Workflow (`.yaml`)
|
|
276
276
|
|
|
277
277
|
```yaml
|
|
278
|
-
aes_workflow: "1.
|
|
278
|
+
aes_workflow: "1.4"
|
|
279
279
|
|
|
280
280
|
id: "my-workflow"
|
|
281
281
|
entity: "item"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{% if domain_config %}
|
|
2
2
|
# .agent/permissions.yaml — Agent Capability Boundaries
|
|
3
|
-
aes_permissions: "1.
|
|
3
|
+
aes_permissions: "1.4"
|
|
4
4
|
|
|
5
5
|
allow:
|
|
6
6
|
shell:
|
|
@@ -96,7 +96,7 @@ resource_limits:
|
|
|
96
96
|
# pii_handling: "prohibit"
|
|
97
97
|
{% else %}
|
|
98
98
|
# .agent/permissions.yaml — Agent Capability Boundaries
|
|
99
|
-
aes_permissions: "1.
|
|
99
|
+
aes_permissions: "1.4"
|
|
100
100
|
|
|
101
101
|
allow:
|
|
102
102
|
shell:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{% if skill %}
|
|
2
2
|
# .agent/skills/{{ skill.id }}.skill.yaml
|
|
3
|
-
aes_skill: "1.
|
|
3
|
+
aes_skill: "1.4"
|
|
4
4
|
|
|
5
5
|
id: "{{ skill.id }}"
|
|
6
6
|
name: "{{ skill.name }}"
|
|
@@ -134,7 +134,7 @@ tags:
|
|
|
134
134
|
{% endfor %}
|
|
135
135
|
{% else %}
|
|
136
136
|
# .agent/skills/{{ skill_id }}.skill.yaml
|
|
137
|
-
aes_skill: "1.
|
|
137
|
+
aes_skill: "1.4"
|
|
138
138
|
|
|
139
139
|
id: "{{ skill_id }}"
|
|
140
140
|
name: "{{ skill_name }}"
|
|
@@ -214,6 +214,26 @@
|
|
|
214
214
|
"type": "object",
|
|
215
215
|
"description": "Top-level MCP server declarations (keyed by server name)",
|
|
216
216
|
"additionalProperties": { "$ref": "#/$defs/mcp_server_decl" }
|
|
217
|
+
},
|
|
218
|
+
"lifecycle": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"description": "Path to lifecycle.yaml relative to .agent/"
|
|
221
|
+
},
|
|
222
|
+
"learning": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"description": "Continuous learning system configuration",
|
|
225
|
+
"properties": {
|
|
226
|
+
"config": { "type": "string", "description": "Path to learning/config.yaml relative to .agent/" },
|
|
227
|
+
"enabled": { "type": "boolean", "default": true }
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"rules": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"description": "Rules and conventions system configuration",
|
|
233
|
+
"properties": {
|
|
234
|
+
"config": { "type": "string", "description": "Path to rules/rules.yaml relative to .agent/" },
|
|
235
|
+
"enabled": { "type": "boolean", "default": true }
|
|
236
|
+
}
|
|
217
237
|
}
|
|
218
238
|
},
|
|
219
239
|
"additionalProperties": false,
|