agent-devkit 0.2.0 → 0.3.0

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 (172) hide show
  1. package/README.md +48 -6
  2. package/bin/agent.mjs +133 -7
  3. package/package.json +1 -1
  4. package/runtime/README.md +187 -5
  5. package/runtime/agent +31 -5
  6. package/runtime/agents/README.md +18 -0
  7. package/runtime/agents/contribution-reviewer/AGENTS.md +8 -0
  8. package/runtime/agents/contribution-reviewer/README.md +8 -0
  9. package/runtime/agents/contribution-reviewer/agent.yaml +40 -0
  10. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/capability.yaml +27 -0
  11. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/decision-rules.md +5 -0
  12. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/workflow.md +6 -0
  13. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/capability.yaml +25 -0
  14. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/decision-rules.md +5 -0
  15. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/workflow.md +5 -0
  16. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/capability.yaml +26 -0
  17. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/decision-rules.md +5 -0
  18. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/workflow.md +6 -0
  19. package/runtime/agents/contribution-reviewer/infra/README.md +6 -0
  20. package/runtime/agents/contribution-reviewer/knowledge/context.md +8 -0
  21. package/runtime/agents/contribution-reviewer/knowledge/system.md +8 -0
  22. package/runtime/agents/contribution-reviewer/templates/README.md +3 -0
  23. package/runtime/agents/knowledge-author/AGENTS.md +7 -0
  24. package/runtime/agents/knowledge-author/README.md +7 -0
  25. package/runtime/agents/knowledge-author/agent.yaml +37 -0
  26. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/capability.yaml +30 -0
  27. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/decision-rules.md +6 -0
  28. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/workflow.md +7 -0
  29. package/runtime/agents/knowledge-author/infra/.gitkeep +1 -0
  30. package/runtime/agents/knowledge-author/knowledge/context.md +4 -0
  31. package/runtime/agents/knowledge-author/knowledge/system.md +4 -0
  32. package/runtime/agents/knowledge-author/templates/.gitkeep +1 -0
  33. package/runtime/agents/knowledge-curator/AGENTS.md +7 -0
  34. package/runtime/agents/knowledge-curator/README.md +6 -0
  35. package/runtime/agents/knowledge-curator/agent.yaml +37 -0
  36. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/capability.yaml +29 -0
  37. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/decision-rules.md +6 -0
  38. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/workflow.md +7 -0
  39. package/runtime/agents/knowledge-curator/infra/.gitkeep +1 -0
  40. package/runtime/agents/knowledge-curator/knowledge/context.md +4 -0
  41. package/runtime/agents/knowledge-curator/knowledge/system.md +4 -0
  42. package/runtime/agents/knowledge-curator/templates/.gitkeep +1 -0
  43. package/runtime/agents/knowledge-infra-builder/AGENTS.md +8 -0
  44. package/runtime/agents/knowledge-infra-builder/README.md +8 -0
  45. package/runtime/agents/knowledge-infra-builder/agent.yaml +38 -0
  46. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/capability.yaml +30 -0
  47. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/decision-rules.md +6 -0
  48. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/workflow.md +7 -0
  49. package/runtime/agents/knowledge-infra-builder/infra/.gitkeep +1 -0
  50. package/runtime/agents/knowledge-infra-builder/knowledge/context.md +4 -0
  51. package/runtime/agents/knowledge-infra-builder/knowledge/system.md +4 -0
  52. package/runtime/agents/knowledge-infra-builder/templates/.gitkeep +1 -0
  53. package/runtime/agents/knowledge-owner/AGENTS.md +7 -0
  54. package/runtime/agents/knowledge-owner/README.md +6 -0
  55. package/runtime/agents/knowledge-owner/agent.yaml +37 -0
  56. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/capability.yaml +28 -0
  57. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/decision-rules.md +6 -0
  58. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/workflow.md +7 -0
  59. package/runtime/agents/knowledge-owner/infra/.gitkeep +1 -0
  60. package/runtime/agents/knowledge-owner/knowledge/context.md +4 -0
  61. package/runtime/agents/knowledge-owner/knowledge/system.md +4 -0
  62. package/runtime/agents/knowledge-owner/templates/.gitkeep +1 -0
  63. package/runtime/agents/knowledge-reviewer/AGENTS.md +7 -0
  64. package/runtime/agents/knowledge-reviewer/README.md +7 -0
  65. package/runtime/agents/knowledge-reviewer/agent.yaml +36 -0
  66. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/capability.yaml +26 -0
  67. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/decision-rules.md +6 -0
  68. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/workflow.md +7 -0
  69. package/runtime/agents/knowledge-reviewer/infra/.gitkeep +1 -0
  70. package/runtime/agents/knowledge-reviewer/knowledge/context.md +4 -0
  71. package/runtime/agents/knowledge-reviewer/knowledge/system.md +4 -0
  72. package/runtime/agents/knowledge-reviewer/templates/.gitkeep +1 -0
  73. package/runtime/agents/local-memory-manager/AGENTS.md +5 -0
  74. package/runtime/agents/local-memory-manager/README.md +7 -0
  75. package/runtime/agents/local-memory-manager/agent.yaml +38 -0
  76. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/capability.yaml +19 -0
  77. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/decision-rules.md +5 -0
  78. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/workflow.md +6 -0
  79. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/capability.yaml +19 -0
  80. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/decision-rules.md +5 -0
  81. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/workflow.md +5 -0
  82. package/runtime/agents/local-memory-manager/infra/.gitkeep +1 -0
  83. package/runtime/agents/local-memory-manager/knowledge/context.md +4 -0
  84. package/runtime/agents/local-memory-manager/knowledge/system.md +4 -0
  85. package/runtime/agents/local-memory-manager/templates/.gitkeep +1 -0
  86. package/runtime/agents/memory-sync-manager/AGENTS.md +7 -0
  87. package/runtime/agents/memory-sync-manager/README.md +7 -0
  88. package/runtime/agents/memory-sync-manager/agent.yaml +37 -0
  89. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/capability.yaml +29 -0
  90. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/decision-rules.md +6 -0
  91. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/workflow.md +7 -0
  92. package/runtime/agents/memory-sync-manager/infra/.gitkeep +1 -0
  93. package/runtime/agents/memory-sync-manager/knowledge/context.md +4 -0
  94. package/runtime/agents/memory-sync-manager/knowledge/system.md +4 -0
  95. package/runtime/agents/memory-sync-manager/templates/.gitkeep +1 -0
  96. package/runtime/agents/shared-memory-curator/AGENTS.md +5 -0
  97. package/runtime/agents/shared-memory-curator/README.md +6 -0
  98. package/runtime/agents/shared-memory-curator/agent.yaml +38 -0
  99. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/capability.yaml +19 -0
  100. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/decision-rules.md +5 -0
  101. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/workflow.md +5 -0
  102. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/capability.yaml +19 -0
  103. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/decision-rules.md +5 -0
  104. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/workflow.md +5 -0
  105. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/capability.yaml +19 -0
  106. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/decision-rules.md +5 -0
  107. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/workflow.md +5 -0
  108. package/runtime/agents/shared-memory-curator/infra/.gitkeep +1 -0
  109. package/runtime/agents/shared-memory-curator/knowledge/context.md +5 -0
  110. package/runtime/agents/shared-memory-curator/knowledge/system.md +4 -0
  111. package/runtime/agents/shared-memory-curator/templates/.gitkeep +1 -0
  112. package/runtime/cli/README.md +35 -4
  113. package/runtime/cli/aikit/__init__.py +1 -1
  114. package/runtime/cli/aikit/agent_registry.py +4 -2
  115. package/runtime/cli/aikit/agentic_commands.py +158 -0
  116. package/runtime/cli/aikit/app_home.py +1 -0
  117. package/runtime/cli/aikit/audit.py +16 -6
  118. package/runtime/cli/aikit/catalog.py +278 -8
  119. package/runtime/cli/aikit/cli_dispatch.py +489 -13
  120. package/runtime/cli/aikit/cli_parser.py +145 -7
  121. package/runtime/cli/aikit/contribution.py +132 -2
  122. package/runtime/cli/aikit/doctor_runtime.py +85 -0
  123. package/runtime/cli/aikit/eval.py +356 -10
  124. package/runtime/cli/aikit/human_output.py +310 -4
  125. package/runtime/cli/aikit/interactive_wizard.py +148 -0
  126. package/runtime/cli/aikit/knowledge_base.py +1067 -0
  127. package/runtime/cli/aikit/llm.py +12 -4
  128. package/runtime/cli/aikit/local_artifacts.py +444 -0
  129. package/runtime/cli/aikit/local_llm.py +161 -0
  130. package/runtime/cli/aikit/main.py +15 -0
  131. package/runtime/cli/aikit/mcp_manifest.py +798 -0
  132. package/runtime/cli/aikit/mcp_tools.py +643 -5
  133. package/runtime/cli/aikit/memory.py +405 -0
  134. package/runtime/cli/aikit/mini_brain.py +20 -1
  135. package/runtime/cli/aikit/natural_prompt_runtime.py +125 -1
  136. package/runtime/cli/aikit/ollama.py +64 -15
  137. package/runtime/cli/aikit/onboarding.py +551 -0
  138. package/runtime/cli/aikit/output.py +67 -0
  139. package/runtime/cli/aikit/prompt_injection.py +12 -1
  140. package/runtime/cli/aikit/roadmap_cli.py +1 -1
  141. package/runtime/cli/aikit/secrets.py +3 -2
  142. package/runtime/cli/aikit/setup_wizard_payload.py +3 -0
  143. package/runtime/cli/aikit/shared_memory.py +415 -0
  144. package/runtime/cli/aikit/specialist_readiness.py +152 -0
  145. package/runtime/cli/aikit/tasks.py +104 -1
  146. package/runtime/cli/aikit/team.py +380 -0
  147. package/runtime/cli/aikit/toolchain.py +7 -2
  148. package/runtime/cli/aikit/workflows.py +115 -14
  149. package/runtime/providers/knowledge-github.yaml +40 -0
  150. package/runtime/providers/knowledge-google-drive.yaml +32 -0
  151. package/runtime/providers/knowledge-local.yaml +26 -0
  152. package/runtime/providers/knowledge-notion.yaml +32 -0
  153. package/runtime/providers/knowledge-obsidian.yaml +24 -0
  154. package/runtime/providers/knowledge-onedrive.yaml +36 -0
  155. package/runtime/providers/knowledge-s3.yaml +45 -0
  156. package/runtime/providers/knowledge-sharepoint.yaml +39 -0
  157. package/runtime/providers/knowledge-supabase.yaml +43 -0
  158. package/runtime/providers/knowledge-vector.yaml +39 -0
  159. package/runtime/requirements.txt +6 -0
  160. package/runtime/scripts/docker-cli-qa.sh +453 -0
  161. package/runtime/scripts/release-catalog-snapshot.json +55 -4
  162. package/runtime/scripts/release-gate.py +54 -13
  163. package/runtime/tooling/toolchain.yaml +92 -0
  164. package/runtime/vendor/skills/napkin/napkin.md +21 -7
  165. package/runtime/workflows/azure-card-analysis/README.md +3 -0
  166. package/runtime/workflows/azure-card-analysis/workflow.yaml +30 -0
  167. package/runtime/workflows/daily-pr-review/README.md +3 -0
  168. package/runtime/workflows/daily-pr-review/workflow.yaml +31 -0
  169. package/runtime/workflows/incident-analysis/README.md +3 -0
  170. package/runtime/workflows/incident-analysis/workflow.yaml +33 -0
  171. package/runtime/workflows/release-prep/README.md +3 -0
  172. package/runtime/workflows/release-prep/workflow.yaml +30 -0
@@ -199,6 +199,37 @@ O comando retorna erro apenas para problemas estruturais do runtime, como raiz
199
199
  inexistente. Provider opcional ausente, LLM sem chave ou CLI de host nao
200
200
  instalada aparecem no bloco de diagnostico e podem ser resolvidos sob demanda.
201
201
 
202
+ ## Onboarding
203
+
204
+ Executar apenas `agent` inicia o status/wizard local. Para planejar setup sem
205
+ executar instalacoes externas:
206
+
207
+ ```bash
208
+ agent onboard minimal
209
+ agent onboard complete
210
+ ```
211
+
212
+ `minimal` cobre identidade, coordenador LLM, mini-cerebro Qwen3-0.6B via
213
+ Ollama e memoria local. `complete` inclui tambem toolchain, providers/sources,
214
+ catalogo de agentes, automacoes locais, tarefas, notificacoes, knowledge e
215
+ memoria compartilhada. Instalacoes externas continuam exigindo opt-in.
216
+
217
+ Backups locais de memoria e personalidade sao gerenciados por:
218
+
219
+ ```bash
220
+ agent memory backup create --title "Antes da migracao"
221
+ export AGENT_DEVKIT_BACKUP_PASSPHRASE="frase longa"
222
+ agent memory backup create --title "Antes da migracao" --encrypted --passphrase-env AGENT_DEVKIT_BACKUP_PASSPHRASE
223
+ agent memory backup list
224
+ agent memory backup restore <backup-id> --yes
225
+ agent memory backup restore --file ./backup.adkmb --passphrase-env AGENT_DEVKIT_BACKUP_PASSPHRASE --yes
226
+ agent memory backup delete <backup-id> --yes
227
+ ```
228
+
229
+ O backup criptografado gera um pacote portatil `.adkmb` e remove a copia local
230
+ em claro dentro da pasta do backup. Esse fluxo nao executa upload remoto. Sync
231
+ remoto continua exigindo provider, criptografia e opt-in explicito.
232
+
202
233
  ## Backends LLM
203
234
 
204
235
  O modo `agent "<prompt>"` exige um backend LLM. O Agent DevKit suporta tres
@@ -292,10 +323,10 @@ agent llm doctor openrouter
292
323
  ```bash
293
324
  agent ollama status
294
325
  agent ollama models
295
- agent ollama pull qwen2.5-coder --dry-run
296
- agent ollama pull qwen2.5-coder --yes
326
+ agent ollama pull qwen3:0.6b --dry-run
327
+ agent ollama pull qwen3:0.6b --yes
297
328
  ollama serve
298
- agent llm configure ollama --base-url http://localhost:11434/v1 --model qwen2.5-coder --set-default
329
+ agent llm configure ollama --base-url http://localhost:11434/v1 --model qwen3:0.6b --set-default
299
330
  agent llm doctor ollama
300
331
  ```
301
332
 
@@ -319,7 +350,7 @@ agent llm list
319
350
  agent llm configure openai --api-key-env OPENAI_API_KEY --set-default
320
351
  agent llm configure anthropic --api-key-env ANTHROPIC_API_KEY --set-default
321
352
  agent llm configure openrouter --api-key-env OPENROUTER_API_KEY --set-default
322
- agent llm configure ollama --base-url http://localhost:11434/v1 --model qwen2.5-coder --set-default
353
+ agent llm configure ollama --base-url http://localhost:11434/v1 --model qwen3:0.6b --set-default
323
354
  agent llm configure codex-cli --set-default
324
355
  agent llm configure claude-code --set-default
325
356
  agent llm set-default codex-cli
@@ -1,3 +1,3 @@
1
1
  """Public CLI implementation for AI DevKit."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
@@ -5,8 +5,6 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
8
- import yaml
9
-
10
8
  from cli.aikit.write_policy import normalize_write_policy, write_policy_public_fields
11
9
 
12
10
 
@@ -96,6 +94,10 @@ def find_capability(registry: dict[str, Any], agent_id: str, capability_id: str)
96
94
 
97
95
 
98
96
  def read_yaml(path: Path) -> dict[str, Any]:
97
+ try:
98
+ import yaml # type: ignore
99
+ except ImportError as exc:
100
+ raise RuntimeError("PyYAML is required to read Agent DevKit manifests. Install requirements.txt.") from exc
99
101
  data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
100
102
  return data if isinstance(data, dict) else {}
101
103
 
@@ -0,0 +1,158 @@
1
+ """Explicit agentic planning and orchestration CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from cli.aikit.core.requests import AgentPromptRequest
9
+ from cli.aikit.errors import DevKitError
10
+ from cli.aikit.natural_prompt_runtime import run_agent_prompt_request
11
+ from cli.aikit.orchestrator import build_execution_plan
12
+ from cli.aikit.runtime_paths import ROOT
13
+
14
+
15
+ def agentic_plan(root: Path, prompt_parts: list[str] | tuple[str, ...] | None) -> dict[str, Any]:
16
+ prompt = normalize_prompt(prompt_parts, command="plan")
17
+ plan = build_execution_plan(root, prompt, dry_run=True)
18
+ return {
19
+ "kind": "agentic-plan",
20
+ "status": plan.get("status") or "planned",
21
+ "ok": True,
22
+ "dry_run": True,
23
+ "prompt_received": True,
24
+ "prompt_length": len(prompt),
25
+ "summary": plan_summary(plan),
26
+ "execution_plan": plan,
27
+ "orchestration_trace": plan.get("trace", []),
28
+ "response": "Plano agentico gerado sem executar LLM, automacoes ou escritas externas.",
29
+ }
30
+
31
+
32
+ def agentic_execute(
33
+ prompt_parts: list[str] | tuple[str, ...] | None,
34
+ *,
35
+ llm: str | None = None,
36
+ dry_run: bool = False,
37
+ session_id: str | None = None,
38
+ new_session: bool = False,
39
+ no_llm_fallback: bool = False,
40
+ prog_name: str = "agent",
41
+ project: str | None = None,
42
+ mode: str = "execute",
43
+ ) -> dict[str, Any]:
44
+ prompt = normalize_prompt(prompt_parts, command=mode)
45
+ if dry_run:
46
+ payload = agentic_plan(ROOT, [prompt])
47
+ payload["command_mode"] = mode
48
+ return payload
49
+ result = run_agent_prompt_request(
50
+ AgentPromptRequest(
51
+ prompt=prompt,
52
+ llm=llm,
53
+ dry_run=False,
54
+ session_id=session_id,
55
+ new_session=new_session,
56
+ no_llm_fallback=no_llm_fallback,
57
+ prog_name=prog_name,
58
+ project=project,
59
+ )
60
+ )
61
+ return attach_agentic_metadata(result, prompt=prompt, mode=mode)
62
+
63
+
64
+ def normalize_prompt(prompt_parts: list[str] | tuple[str, ...] | None, *, command: str) -> str:
65
+ prompt = " ".join(str(part) for part in (prompt_parts or [])).strip()
66
+ if not prompt:
67
+ raise DevKitError(f"agent {command} requires a natural-language prompt")
68
+ return prompt
69
+
70
+
71
+ def plan_summary(plan: dict[str, Any]) -> dict[str, Any]:
72
+ specialist_tasks = [task for task in plan.get("specialist_tasks") or [] if isinstance(task, dict)]
73
+ configuration_tasks = [task for task in plan.get("configuration_tasks") or [] if isinstance(task, dict)]
74
+ review_task = plan.get("review_task") if isinstance(plan.get("review_task"), dict) else {}
75
+ model_plan = plan.get("model_plan") if isinstance(plan.get("model_plan"), dict) else {}
76
+ routing_decision = plan.get("routing_decision") if isinstance(plan.get("routing_decision"), dict) else {}
77
+ autonomy = plan.get("autonomy_contract") if isinstance(plan.get("autonomy_contract"), dict) else {}
78
+ needs_input = (
79
+ plan.get("status") == "needs-input"
80
+ or bool(configuration_tasks)
81
+ or model_plan.get("strategy") == "human"
82
+ or autonomy.get("requires_human") is True
83
+ or autonomy.get("status") == "needs-input"
84
+ )
85
+ return {
86
+ "routing_status": routing_decision.get("status"),
87
+ "selected_agent_id": routing_decision.get("selected_agent_id"),
88
+ "selected_capability_id": routing_decision.get("selected_capability_id"),
89
+ "model_strategy": model_plan.get("strategy"),
90
+ "local_llm_selected": model_plan.get("local_llm_selected"),
91
+ "specialist_tasks": len(specialist_tasks),
92
+ "configuration_tasks": len(configuration_tasks),
93
+ "review_required": bool(review_task.get("required") or review_task.get("status") in {"pending", "required"}),
94
+ "collaboration_enabled": bool(plan.get("collaboration_enabled")),
95
+ "controller_enabled": bool(plan.get("controller_enabled")),
96
+ "needs_input": needs_input,
97
+ }
98
+
99
+
100
+ def attach_agentic_metadata(result: dict[str, Any], *, prompt: str, mode: str) -> dict[str, Any]:
101
+ plan = result.get("execution_plan") if isinstance(result.get("execution_plan"), dict) else None
102
+ if plan is None:
103
+ plan = local_shortcut_execution_plan(prompt=prompt, result=result, mode=mode)
104
+ result["execution_plan"] = plan
105
+ result.setdefault("orchestration_trace", plan.get("trace", []))
106
+ result["agentic_summary"] = plan_summary(plan)
107
+ result["command_mode"] = mode
108
+ result["agentic_command"] = mode
109
+ return result
110
+
111
+
112
+ def local_shortcut_execution_plan(*, prompt: str, result: dict[str, Any], mode: str) -> dict[str, Any]:
113
+ status = str(result.get("status") or "ok")
114
+ local_mode = str(result.get("mode") or result.get("action") or "local-shortcut")
115
+ ok = result.get("ok") is not False and status not in {"blocked", "failed", "needs-input", "needs-review"}
116
+ return {
117
+ "schema_version": "ai-devkit.agentic-plan/v1",
118
+ "status": "completed" if ok else status,
119
+ "prompt": prompt,
120
+ "dry_run": False,
121
+ "command_mode": mode,
122
+ "routing_decision": {
123
+ "status": "selected",
124
+ "selected_agent_id": "agent-devkit",
125
+ "selected_capability_id": local_mode,
126
+ "method": "local-shortcut",
127
+ "confidence": 1.0,
128
+ },
129
+ "model_plan": {
130
+ "strategy": "deterministic-local",
131
+ "local_llm_selected": False,
132
+ "local_llm_recommended": False,
133
+ "fallback": None,
134
+ },
135
+ "specialist_tasks": [],
136
+ "configuration_tasks": [],
137
+ "review_task": {
138
+ "agent_id": "execution-reviewer",
139
+ "capability_id": "review-final-output",
140
+ "status": "not-required",
141
+ "required": False,
142
+ },
143
+ "review_gate": {
144
+ "required": False,
145
+ "status": "not-required",
146
+ "reason": "local deterministic shortcut",
147
+ },
148
+ "collaboration_enabled": False,
149
+ "controller_enabled": False,
150
+ "trace": [
151
+ {
152
+ "agent_id": "task-orchestrator",
153
+ "action": "local-shortcut",
154
+ "mode": local_mode,
155
+ "status": status,
156
+ }
157
+ ],
158
+ }
@@ -20,6 +20,7 @@ APP_DIRS = (
20
20
  "memory",
21
21
  "sessions",
22
22
  "tasks",
23
+ "backups",
23
24
  "policies",
24
25
  "audit",
25
26
  "secrets",
@@ -58,7 +58,7 @@ def record_audit(
58
58
  execution_id = f"exec_{now_utc().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex[:8]}"
59
59
  prompt = extract_prompt(args)
60
60
  try:
61
- safe_result = redact_value(result or {})
61
+ safe_result = redact_value(result or {}, redact_access_keys=True)
62
62
  safe_prompt = redact_secrets(prompt) if prompt else None
63
63
  safe_error = redact_secrets(error) if error else None
64
64
  except Exception as exc: # noqa: BLE001 - never write an unredacted audit fallback.
@@ -227,25 +227,35 @@ def find_audit_json(execution_id: str) -> Path:
227
227
  return matches[0]
228
228
 
229
229
 
230
- def redact_value(value: Any) -> Any:
230
+ def redact_value(value: Any, *, redact_access_keys: bool = False, parent_key: str | None = None) -> Any:
231
231
  if isinstance(value, str):
232
232
  return redact_secrets(value)
233
233
  if isinstance(value, list):
234
- return [redact_value(item) for item in value]
234
+ return [redact_value(item, redact_access_keys=redact_access_keys, parent_key=parent_key) for item in value]
235
235
  if isinstance(value, tuple):
236
- return [redact_value(item) for item in value]
236
+ return [redact_value(item, redact_access_keys=redact_access_keys, parent_key=parent_key) for item in value]
237
237
  if isinstance(value, dict):
238
238
  redacted: dict[str, Any] = {}
239
239
  for key, item in value.items():
240
240
  key_text = str(key)
241
- if secret_key(key_text):
241
+ if secret_key(key_text) or access_key(key_text, parent_key=parent_key, enabled=redact_access_keys):
242
242
  redacted[key_text] = "[REDACTED_SECRET]"
243
243
  else:
244
- redacted[key_text] = redact_value(item)
244
+ redacted[key_text] = redact_value(item, redact_access_keys=redact_access_keys, parent_key=key_text)
245
245
  return redacted
246
246
  return value
247
247
 
248
248
 
249
+ def access_key(key: str, *, parent_key: str | None, enabled: bool) -> bool:
250
+ if not enabled:
251
+ return False
252
+ normalized = key.lower().replace("-", "_")
253
+ if normalized in {"owner_key", "contributor_key", "shared_key"}:
254
+ return True
255
+ parent = (parent_key or "").lower().replace("-", "_")
256
+ return normalized == "key" and parent in {"owner_access", "contributor_access", "shared_access"}
257
+
258
+
249
259
  def secret_key(key: str) -> bool:
250
260
  normalized = key.lower().replace("-", "_")
251
261
  if normalized in {"token_estimate", "tokens", "prompt_tokens", "completion_tokens", "total_tokens"}:
@@ -2,24 +2,28 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import re
6
7
  from pathlib import Path
7
8
  from typing import Any
8
9
 
10
+ from cli.aikit.app_home import cache_home
9
11
  from cli.aikit.agent_registry import load_agent_registry
10
12
  from cli.aikit.errors import DevKitError
13
+ from cli.aikit.extensions import local_extensions_list
14
+ from cli.aikit.local_artifacts import local_agent_list, local_automation_list, script_list, skill_list
11
15
  from cli.aikit.providers import ProviderRegistryError, list_providers
12
16
  from cli.aikit.runtime_paths import ROOT
17
+ from cli.aikit.toolchain import list_toolchain
18
+ from cli.aikit.workflows import workflow_list
13
19
 
14
20
 
15
21
  CATALOG_SCHEMA_VERSION = "agent-devkit.catalog/v1"
16
22
 
17
23
 
18
- def catalog_list(root: Path | None = None, *, item_type: str | None = None) -> dict[str, Any]:
24
+ def catalog_list(root: Path | None = None, *, item_type: str | None = None, filters: dict[str, Any] | None = None) -> dict[str, Any]:
19
25
  root = root or ROOT
20
- items = catalog_items(root)
21
- if item_type:
22
- items = [item for item in items if item["type"] == item_type]
26
+ items = filtered_catalog_items(root, item_type=item_type, filters=filters)
23
27
  return {
24
28
  "kind": "catalog",
25
29
  "schema_version": CATALOG_SCHEMA_VERSION,
@@ -28,20 +32,19 @@ def catalog_list(root: Path | None = None, *, item_type: str | None = None) -> d
28
32
  "query": None,
29
33
  "type": item_type,
30
34
  "count": len(items),
35
+ "filters": public_filters(item_type=item_type, filters=filters),
31
36
  "items": items,
32
37
  }
33
38
 
34
39
 
35
- def catalog_search(query: str, root: Path | None = None, *, item_type: str | None = None) -> dict[str, Any]:
40
+ def catalog_search(query: str, root: Path | None = None, *, item_type: str | None = None, filters: dict[str, Any] | None = None) -> dict[str, Any]:
36
41
  root = root or ROOT
37
42
  query = (query or "").strip()
38
43
  if not query:
39
44
  raise DevKitError("catalog search requires a query")
40
45
  tokens = tokenize(query)
41
46
  scored: list[tuple[int, dict[str, Any]]] = []
42
- for item in catalog_items(root):
43
- if item_type and item["type"] != item_type:
44
- continue
47
+ for item in filtered_catalog_items(root, item_type=item_type, filters=filters):
45
48
  score = match_score(item, tokens)
46
49
  if score > 0:
47
50
  enriched = dict(item)
@@ -56,6 +59,7 @@ def catalog_search(query: str, root: Path | None = None, *, item_type: str | Non
56
59
  "query": query,
57
60
  "type": item_type,
58
61
  "count": len(scored),
62
+ "filters": public_filters(item_type=item_type, filters=filters),
59
63
  "items": [item for _score, item in scored],
60
64
  }
61
65
 
@@ -80,6 +84,25 @@ def catalog_show(item_id: str, root: Path | None = None, *, item_type: str | Non
80
84
  raise DevKitError(f"catalog item not found{suffix}: {item_id}")
81
85
 
82
86
 
87
+ def catalog_rebuild_index(root: Path | None = None) -> dict[str, Any]:
88
+ root = root or ROOT
89
+ items = catalog_items(root)
90
+ path = catalog_index_path()
91
+ path.parent.mkdir(parents=True, exist_ok=True)
92
+ payload = {
93
+ "schema_version": CATALOG_SCHEMA_VERSION,
94
+ "items": items,
95
+ }
96
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
97
+ return {
98
+ "kind": "catalog-index",
99
+ "schema_version": CATALOG_SCHEMA_VERSION,
100
+ "status": "rebuilt",
101
+ "path": str(path),
102
+ "count": len(items),
103
+ }
104
+
105
+
83
106
  def catalog_items(root: Path) -> list[dict[str, Any]]:
84
107
  registry = load_agent_registry(root)
85
108
  items: list[dict[str, Any]] = []
@@ -90,6 +113,33 @@ def catalog_items(root: Path) -> list[dict[str, Any]]:
90
113
  if isinstance(capability, dict):
91
114
  items.append(capability_item(capability))
92
115
  items.extend(provider_items(root))
116
+ items.extend(workflow_items())
117
+ items.extend(tool_items(root))
118
+ items.extend(skill_items(root))
119
+ items.extend(plugin_items(root))
120
+ items.extend(extension_items())
121
+ items.extend(local_artifact_items())
122
+ return items
123
+
124
+
125
+ def filtered_catalog_items(root: Path, *, item_type: str | None = None, filters: dict[str, Any] | None = None) -> list[dict[str, Any]]:
126
+ filters = {key: value for key, value in (filters or {}).items() if value not in {None, ""}}
127
+ items = catalog_items(root)
128
+ if item_type:
129
+ items = [
130
+ item
131
+ for item in items
132
+ if item["type"] == item_type or item.get("local_kind") == item_type
133
+ ]
134
+ for key, value in filters.items():
135
+ if key == "provider":
136
+ items = [item for item in items if value in (item.get("providers_required") or []) or item.get("provider") == value]
137
+ elif key == "status":
138
+ items = [item for item in items if item.get("status") == value]
139
+ elif key == "write_policy":
140
+ items = [item for item in items if item.get("write_policy") == value]
141
+ elif key == "readiness":
142
+ items = [item for item in items if (item.get("readiness") or {}).get("status") == value]
93
143
  return items
94
144
 
95
145
 
@@ -105,6 +155,7 @@ def agent_item(agent: dict[str, Any]) -> dict[str, Any]:
105
155
  "write_policy": agent.get("write_policy"),
106
156
  "write_policy_metadata": agent.get("write_policy_metadata"),
107
157
  "source_contract": None,
158
+ "provider": None,
108
159
  "providers_required": [],
109
160
  "runner": None,
110
161
  "agent_mode": agent.get("agent_mode") or {},
@@ -142,6 +193,7 @@ def capability_item(capability: dict[str, Any]) -> dict[str, Any]:
142
193
  "write_policy": capability.get("write_policy"),
143
194
  "write_policy_metadata": capability.get("write_policy_metadata"),
144
195
  "source_contract": capability.get("source_contract") or capability.get("source"),
196
+ "provider": provider,
145
197
  "providers_required": providers_required,
146
198
  "runner": capability.get("runner") or (capability.get("runtime") or {}).get("runner"),
147
199
  "agent_mode": None,
@@ -174,6 +226,7 @@ def provider_items(root: Path) -> list[dict[str, Any]]:
174
226
  "path": provider.get("path"),
175
227
  "write_policy": "read_only",
176
228
  "source_contract": None,
229
+ "provider": provider.get("id"),
177
230
  "providers_required": [],
178
231
  "runner": None,
179
232
  "agent_mode": None,
@@ -185,6 +238,223 @@ def provider_items(root: Path) -> list[dict[str, Any]]:
185
238
  return items
186
239
 
187
240
 
241
+ def workflow_items() -> list[dict[str, Any]]:
242
+ try:
243
+ payload = workflow_list()
244
+ except Exception:
245
+ return []
246
+ return [
247
+ {
248
+ "id": str(workflow.get("id") or ""),
249
+ "type": "workflow",
250
+ "description": workflow.get("description") or workflow.get("title") or "",
251
+ "status": "ok",
252
+ "version": None,
253
+ "path": workflow.get("path"),
254
+ "write_policy": workflow.get("write_policy"),
255
+ "source_contract": None,
256
+ "provider": None,
257
+ "providers_required": workflow.get("providers_required") or [],
258
+ "runner": "workflow",
259
+ "agent_mode": None,
260
+ "routing": {},
261
+ "prompt_examples": workflow.get("examples") or [],
262
+ "readiness": {"status": "ready"},
263
+ "next_step": f"agent workflow show {workflow.get('id')}",
264
+ }
265
+ for workflow in payload.get("items") or []
266
+ if isinstance(workflow, dict)
267
+ ]
268
+
269
+
270
+ def tool_items(root: Path) -> list[dict[str, Any]]:
271
+ try:
272
+ payload = list_toolchain(root)
273
+ except Exception:
274
+ return []
275
+ return [
276
+ {
277
+ "id": str(tool.get("id") or ""),
278
+ "type": "tool",
279
+ "description": tool.get("notes") or tool.get("label") or "",
280
+ "status": "required" if tool.get("required") else "optional",
281
+ "version": None,
282
+ "path": None,
283
+ "write_policy": "confirm",
284
+ "source_contract": None,
285
+ "provider": None,
286
+ "providers_required": [],
287
+ "runner": tool.get("command"),
288
+ "agent_mode": None,
289
+ "routing": {},
290
+ "prompt_examples": [],
291
+ "readiness": {"status": "requires-install-check", "command": tool.get("command")},
292
+ "next_step": f"agent toolchain doctor {tool.get('id')}",
293
+ }
294
+ for tool in payload.get("items") or []
295
+ if isinstance(tool, dict)
296
+ ]
297
+
298
+
299
+ def skill_items(root: Path) -> list[dict[str, Any]]:
300
+ catalog = parse_markdown_catalog(root / "vendor" / "skills" / "CATALOG.md", item_type="skill")
301
+ if catalog:
302
+ return catalog
303
+ return filesystem_items(root / "vendor" / "skills", item_type="skill", marker="SKILL.md")
304
+
305
+
306
+ def plugin_items(root: Path) -> list[dict[str, Any]]:
307
+ catalog = parse_markdown_catalog(root / "vendor" / "plugins" / "CATALOG.md", item_type="plugin")
308
+ if catalog:
309
+ return catalog
310
+ return filesystem_items(root / "vendor" / "plugins", item_type="plugin", marker="README.md")
311
+
312
+
313
+ def extension_items() -> list[dict[str, Any]]:
314
+ try:
315
+ payload = local_extensions_list()
316
+ except Exception:
317
+ return []
318
+ return [
319
+ {
320
+ "id": str(item.get("id") or ""),
321
+ "type": "extension",
322
+ "description": f"Local extension at {item.get('path')}",
323
+ "status": "enabled" if item.get("enabled") else "disabled",
324
+ "version": None,
325
+ "path": item.get("path"),
326
+ "write_policy": "local_config_write",
327
+ "source_contract": None,
328
+ "provider": None,
329
+ "providers_required": [],
330
+ "runner": None,
331
+ "agent_mode": None,
332
+ "routing": {},
333
+ "prompt_examples": [],
334
+ "readiness": {"status": "ready" if item.get("enabled") else "disabled"},
335
+ "next_step": f"agent local validate {item.get('id')}",
336
+ }
337
+ for item in payload.get("items") or []
338
+ if isinstance(item, dict)
339
+ ]
340
+
341
+
342
+ def local_artifact_items() -> list[dict[str, Any]]:
343
+ payloads = []
344
+ for factory, item_type, command in (
345
+ (skill_list, "skill", "agent skill show"),
346
+ (script_list, "script", "agent script run"),
347
+ (local_agent_list, "agent", "agent agents validate"),
348
+ (local_automation_list, "automation", "agent local automation show"),
349
+ ):
350
+ try:
351
+ payload = factory()
352
+ except Exception:
353
+ continue
354
+ for item in payload.get("items") or []:
355
+ if not isinstance(item, dict):
356
+ continue
357
+ payloads.append(
358
+ {
359
+ "id": str(item.get("id") or ""),
360
+ "type": item_type,
361
+ "local_kind": item.get("kind"),
362
+ "origin": "local",
363
+ "description": f"Local {item_type} at {item.get('path')}",
364
+ "status": "enabled" if item.get("enabled") else "disabled",
365
+ "version": None,
366
+ "path": item.get("path"),
367
+ "write_policy": "local_config_write",
368
+ "source_contract": None,
369
+ "provider": None,
370
+ "providers_required": [],
371
+ "runner": None,
372
+ "agent_mode": None,
373
+ "routing": {},
374
+ "prompt_examples": [],
375
+ "readiness": {"status": "ready" if item.get("enabled") else "disabled"},
376
+ "next_step": f"{command} {item.get('id')}",
377
+ }
378
+ )
379
+ return payloads
380
+
381
+
382
+ def parse_markdown_catalog(path: Path, *, item_type: str) -> list[dict[str, Any]]:
383
+ if not path.exists():
384
+ return []
385
+ items: list[dict[str, Any]] = []
386
+ for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
387
+ stripped = line.strip()
388
+ if not stripped.startswith("| `"):
389
+ continue
390
+ cells = [cell.strip() for cell in stripped.strip("|").split("|")]
391
+ if len(cells) < 3:
392
+ continue
393
+ item_id = cells[0].strip("` ")
394
+ description = cells[1]
395
+ path_cell = cells[-1].strip("` ")
396
+ items.append(
397
+ {
398
+ "id": item_id,
399
+ "type": item_type,
400
+ "description": description,
401
+ "status": "available",
402
+ "version": None,
403
+ "path": path_cell,
404
+ "write_policy": "read_only",
405
+ "source_contract": None,
406
+ "provider": None,
407
+ "providers_required": [],
408
+ "runner": None,
409
+ "agent_mode": None,
410
+ "routing": {},
411
+ "prompt_examples": [],
412
+ "readiness": {"status": "available"},
413
+ "next_step": f"Use {item_type} {item_id} when relevant.",
414
+ }
415
+ )
416
+ return items
417
+
418
+
419
+ def filesystem_items(root: Path, *, item_type: str, marker: str) -> list[dict[str, Any]]:
420
+ if not root.exists():
421
+ return []
422
+ items = []
423
+ for marker_path in sorted(root.glob(f"**/{marker}")):
424
+ item_root = marker_path.parent
425
+ items.append(
426
+ {
427
+ "id": item_root.name,
428
+ "type": item_type,
429
+ "description": "",
430
+ "status": "available",
431
+ "version": None,
432
+ "path": str(item_root.relative_to(ROOT)) if item_root.is_relative_to(ROOT) else str(item_root),
433
+ "write_policy": "read_only",
434
+ "source_contract": None,
435
+ "provider": None,
436
+ "providers_required": [],
437
+ "runner": None,
438
+ "agent_mode": None,
439
+ "routing": {},
440
+ "prompt_examples": [],
441
+ "readiness": {"status": "available"},
442
+ }
443
+ )
444
+ return items
445
+
446
+
447
+ def catalog_index_path() -> Path:
448
+ return cache_home() / "catalog-index.json"
449
+
450
+
451
+ def public_filters(*, item_type: str | None, filters: dict[str, Any] | None) -> dict[str, Any]:
452
+ payload = {key: value for key, value in (filters or {}).items() if value not in {None, ""}}
453
+ if item_type:
454
+ payload["type"] = item_type
455
+ return payload
456
+
457
+
188
458
  def tokenize(value: str) -> set[str]:
189
459
  tokens = {token for token in re.findall(r"[a-z0-9]+", value.lower()) if token}
190
460
  return expand_query_tokens(tokens)