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.
Files changed (98) hide show
  1. {aes_cli-0.7.0 → aes_cli-0.9.0}/PKG-INFO +1 -1
  2. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/__init__.py +1 -1
  3. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/__main__.py +9 -2
  4. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/init.py +102 -0
  5. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/inspect.py +44 -1
  6. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/sync.py +88 -0
  7. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/config.py +12 -0
  8. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/domains.py +10 -0
  9. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/global_config.py +17 -0
  10. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/agent.yaml.jinja +2 -2
  11. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/bom.yaml.jinja +1 -1
  12. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/instructions.md.jinja +4 -4
  13. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/local.example.yaml.jinja +2 -0
  14. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/local.yaml.jinja +2 -0
  15. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/permissions.yaml.jinja +2 -2
  16. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/skill.yaml.jinja +2 -2
  17. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/workflow.yaml.jinja +1 -1
  18. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/agent.schema.json +20 -0
  19. aes_cli-0.9.0/aes/schemas/instinct.schema.json +137 -0
  20. aes_cli-0.9.0/aes/schemas/learning-config.schema.json +129 -0
  21. aes_cli-0.9.0/aes/schemas/lifecycle.schema.json +196 -0
  22. aes_cli-0.9.0/aes/schemas/rules-config.schema.json +55 -0
  23. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/__init__.py +2 -0
  24. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/_base.py +5 -0
  25. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/_composer.py +206 -2
  26. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/claude.py +46 -1
  27. aes_cli-0.9.0/aes/targets/codex.py +125 -0
  28. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/copilot.py +24 -1
  29. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/cursor.py +24 -1
  30. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/openclaw.py +51 -5
  31. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/targets/windsurf.py +24 -1
  32. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/validator.py +83 -1
  33. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/PKG-INFO +1 -1
  34. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/SOURCES.txt +10 -0
  35. {aes_cli-0.7.0 → aes_cli-0.9.0}/pyproject.toml +1 -1
  36. aes_cli-0.9.0/tests/test_codex_target.py +304 -0
  37. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_init.py +1 -1
  38. aes_cli-0.9.0/tests/test_learning_validation.py +344 -0
  39. aes_cli-0.9.0/tests/test_lifecycle_validation.py +234 -0
  40. aes_cli-0.9.0/tests/test_project_locale.py +94 -0
  41. aes_cli-0.9.0/tests/test_rules_validation.py +146 -0
  42. {aes_cli-0.7.0 → aes_cli-0.9.0}/README.md +0 -0
  43. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/analyzer.py +0 -0
  44. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/__init__.py +0 -0
  45. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/bom.py +0 -0
  46. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/install.py +0 -0
  47. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/publish.py +0 -0
  48. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/search.py +0 -0
  49. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/status.py +0 -0
  50. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/upgrade.py +0 -0
  51. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/commands/validate.py +0 -0
  52. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/frameworks.py +0 -0
  53. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/__init__.py +0 -0
  54. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/_messages.py +0 -0
  55. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/domains_ja.py +0 -0
  56. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/i18n/ja.py +0 -0
  57. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/mcp_server.py +0 -0
  58. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/migrations.py +0 -0
  59. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/registry.py +0 -0
  60. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/agentignore.jinja +0 -0
  61. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
  62. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
  63. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/operations.md.jinja +0 -0
  64. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
  65. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/setup.md.jinja +0 -0
  66. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/skill.md.jinja +0 -0
  67. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
  68. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/memory_command.md.jinja +0 -0
  69. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/operations.md.jinja +0 -0
  70. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/orchestrator.md.jinja +0 -0
  71. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/setup.md.jinja +0 -0
  72. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/skill.md.jinja +0 -0
  73. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/scaffold/workflow_command.md.jinja +0 -0
  74. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/bom.schema.json +0 -0
  75. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/decision-record.schema.json +0 -0
  76. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/permissions.schema.json +0 -0
  77. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/registry.schema.json +0 -0
  78. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/skill.schema.json +0 -0
  79. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes/schemas/workflow.schema.json +0 -0
  80. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/dependency_links.txt +0 -0
  81. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/entry_points.txt +0 -0
  82. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/requires.txt +0 -0
  83. {aes_cli-0.7.0 → aes_cli-0.9.0}/aes_cli.egg-info/top_level.txt +0 -0
  84. {aes_cli-0.7.0 → aes_cli-0.9.0}/setup.cfg +0 -0
  85. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_analyzer.py +0 -0
  86. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_bom.py +0 -0
  87. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_frameworks.py +0 -0
  88. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_inspect.py +0 -0
  89. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_install.py +0 -0
  90. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_mcp_server.py +0 -0
  91. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_openclaw_target.py +0 -0
  92. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_publish.py +0 -0
  93. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_registry.py +0 -0
  94. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_search.py +0 -0
  95. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_status.py +0 -0
  96. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_sync.py +0 -0
  97. {aes_cli-0.7.0 → aes_cli-0.9.0}/tests/test_upgrade.py +0 -0
  98. {aes_cli-0.7.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.7.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.7.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,
@@ -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 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))}")
@@ -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.2"
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.2"
124
+ aes: "1.4"
125
125
 
126
126
  name: "{{ name }}"
127
127
  version: "0.1.0"
@@ -1,7 +1,7 @@
1
1
  # .agent/bom.yaml — Agent Bill of Materials (AI-BOM)
2
2
  # Tracks all components this agent depends on.
3
3
  # See: https://aes-official.com/spec/11-bom
4
- aes_bom: "1.2"
4
+ aes_bom: "1.4"
5
5
 
6
6
  models: []
7
7
  # - name: "claude-sonnet-4-20250514"
@@ -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.2"
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.2"
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.2"
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.2"
278
+ aes_workflow: "1.4"
279
279
 
280
280
  id: "my-workflow"
281
281
  entity: "item"
@@ -7,6 +7,8 @@
7
7
  # during `aes sync` so your personal settings apply without modifying
8
8
  # shared config files.
9
9
 
10
+ # locale: en # Override CLI language for this project (en/ja)
11
+
10
12
  permissions:
11
13
  allow:
12
14
  shell:
@@ -5,6 +5,8 @@
5
5
  #
6
6
  # Merged on top of permissions.yaml during `aes sync`.
7
7
 
8
+ # locale: en # Override CLI language for this project (en/ja)
9
+
8
10
  permissions:
9
11
  allow:
10
12
  shell:
@@ -1,6 +1,6 @@
1
1
  {% if domain_config %}
2
2
  # .agent/permissions.yaml — Agent Capability Boundaries
3
- aes_permissions: "1.2"
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.2"
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.2"
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.2"
137
+ aes_skill: "1.4"
138
138
 
139
139
  id: "{{ skill_id }}"
140
140
  name: "{{ skill_name }}"
@@ -1,4 +1,4 @@
1
- aes_workflow: "1.2"
1
+ aes_workflow: "1.4"
2
2
 
3
3
  id: "{{ workflow.id }}"
4
4
  entity: "{{ workflow.entity }}"
@@ -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,