feed-the-machine 1.6.0 → 1.7.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.
- package/LICENSE +21 -21
- package/README.md +170 -170
- package/bin/brain.py +1340 -0
- package/bin/convert_claude_skills_to_codex.py +490 -0
- package/bin/generate-manifest.mjs +463 -463
- package/bin/harden_codex_skills.py +141 -0
- package/bin/install.mjs +491 -491
- package/bin/migrate-eng-buddy-data.py +875 -0
- package/bin/playbook_engine/__init__.py +1 -0
- package/bin/playbook_engine/conftest.py +8 -0
- package/bin/playbook_engine/extractor.py +33 -0
- package/bin/playbook_engine/manager.py +102 -0
- package/bin/playbook_engine/models.py +84 -0
- package/bin/playbook_engine/registry.py +35 -0
- package/bin/playbook_engine/test_extractor.py +72 -0
- package/bin/playbook_engine/test_integration.py +129 -0
- package/bin/playbook_engine/test_manager.py +85 -0
- package/bin/playbook_engine/test_models.py +166 -0
- package/bin/playbook_engine/test_registry.py +67 -0
- package/bin/playbook_engine/test_tracer.py +86 -0
- package/bin/playbook_engine/tracer.py +93 -0
- package/bin/tasks_db.py +456 -0
- package/docs/HOOKS.md +243 -243
- package/docs/INBOX.md +233 -233
- package/ftm/SKILL.md +125 -122
- package/ftm-audit/SKILL.md +623 -623
- package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
- package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
- package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
- package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
- package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
- package/ftm-audit/scripts/run-knip.sh +23 -23
- package/ftm-audit.yml +2 -2
- package/ftm-brainstorm/SKILL.md +1003 -498
- package/ftm-brainstorm/evals/evals.json +180 -100
- package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
- package/ftm-brainstorm/references/agent-prompts.md +552 -224
- package/ftm-brainstorm/references/plan-template.md +209 -121
- package/ftm-brainstorm.yml +2 -2
- package/ftm-browse/SKILL.md +454 -454
- package/ftm-browse/daemon/browser-manager.ts +206 -206
- package/ftm-browse/daemon/bun.lock +30 -30
- package/ftm-browse/daemon/cli.ts +347 -347
- package/ftm-browse/daemon/commands.ts +410 -410
- package/ftm-browse/daemon/main.ts +357 -357
- package/ftm-browse/daemon/package.json +17 -17
- package/ftm-browse/daemon/server.ts +189 -189
- package/ftm-browse/daemon/snapshot.ts +519 -519
- package/ftm-browse/daemon/tsconfig.json +22 -22
- package/ftm-browse.yml +4 -4
- package/ftm-capture/SKILL.md +370 -370
- package/ftm-capture.yml +4 -4
- package/ftm-codex-gate/SKILL.md +361 -361
- package/ftm-codex-gate.yml +2 -2
- package/ftm-config/SKILL.md +422 -345
- package/ftm-config.default.yml +125 -82
- package/ftm-config.yml +44 -2
- package/ftm-council/SKILL.md +416 -416
- package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
- package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
- package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
- package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
- package/ftm-council.yml +2 -2
- package/ftm-dashboard/SKILL.md +163 -163
- package/ftm-dashboard.yml +4 -4
- package/ftm-debug/SKILL.md +1037 -1037
- package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
- package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
- package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
- package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
- package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
- package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
- package/ftm-debug.yml +2 -2
- package/ftm-diagram/SKILL.md +277 -277
- package/ftm-diagram.yml +2 -2
- package/ftm-executor/SKILL.md +777 -777
- package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
- package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
- package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
- package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
- package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
- package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
- package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
- package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
- package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -59
- package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
- package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
- package/ftm-executor/runtime/package.json +8 -8
- package/ftm-executor.yml +2 -2
- package/ftm-git/SKILL.md +441 -441
- package/ftm-git/evals/evals.json +26 -26
- package/ftm-git/evals/promptfoo.yaml +75 -75
- package/ftm-git/hooks/post-commit-experience.sh +92 -92
- package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
- package/ftm-git/references/protocols/REMEDIATION.md +139 -139
- package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
- package/ftm-git.yml +2 -2
- package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/adapters/_retry.py +64 -64
- package/ftm-inbox/backend/adapters/base.py +230 -230
- package/ftm-inbox/backend/adapters/freshservice.py +104 -104
- package/ftm-inbox/backend/adapters/gmail.py +125 -125
- package/ftm-inbox/backend/adapters/jira.py +136 -136
- package/ftm-inbox/backend/adapters/registry.py +192 -192
- package/ftm-inbox/backend/adapters/slack.py +110 -110
- package/ftm-inbox/backend/db/connection.py +54 -54
- package/ftm-inbox/backend/db/schema.py +78 -78
- package/ftm-inbox/backend/executor/__init__.py +7 -7
- package/ftm-inbox/backend/executor/engine.py +149 -149
- package/ftm-inbox/backend/executor/step_runner.py +98 -98
- package/ftm-inbox/backend/main.py +103 -103
- package/ftm-inbox/backend/models/__init__.py +1 -1
- package/ftm-inbox/backend/models/unified_task.py +36 -36
- package/ftm-inbox/backend/planner/__init__.py +6 -6
- package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/generator.py +127 -127
- package/ftm-inbox/backend/planner/schema.py +34 -34
- package/ftm-inbox/backend/requirements.txt +5 -5
- package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/routes/execute.py +186 -186
- package/ftm-inbox/backend/routes/health.py +52 -52
- package/ftm-inbox/backend/routes/inbox.py +68 -68
- package/ftm-inbox/backend/routes/plan.py +271 -271
- package/ftm-inbox/bin/launchagent.mjs +91 -91
- package/ftm-inbox/bin/setup.mjs +188 -188
- package/ftm-inbox/bin/start.sh +10 -10
- package/ftm-inbox/bin/status.sh +17 -17
- package/ftm-inbox/bin/stop.sh +8 -8
- package/ftm-inbox/config.example.yml +55 -55
- package/ftm-inbox/package-lock.json +2898 -2898
- package/ftm-inbox/package.json +26 -26
- package/ftm-inbox/postcss.config.js +6 -6
- package/ftm-inbox/src/app.css +199 -199
- package/ftm-inbox/src/app.html +18 -18
- package/ftm-inbox/src/lib/api.ts +166 -166
- package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
- package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
- package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
- package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
- package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
- package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
- package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
- package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
- package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
- package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
- package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
- package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
- package/ftm-inbox/src/lib/theme.ts +47 -47
- package/ftm-inbox/src/routes/+layout.svelte +76 -76
- package/ftm-inbox/src/routes/+page.svelte +401 -401
- package/ftm-inbox/svelte.config.js +12 -12
- package/ftm-inbox/tailwind.config.ts +63 -63
- package/ftm-inbox/tsconfig.json +13 -13
- package/ftm-inbox/vite.config.ts +6 -6
- package/ftm-intent/SKILL.md +241 -241
- package/ftm-intent.yml +2 -2
- package/ftm-manifest.json +3794 -3794
- package/ftm-map/SKILL.md +291 -291
- package/ftm-map/scripts/db.py +712 -712
- package/ftm-map/scripts/index.py +415 -415
- package/ftm-map/scripts/parser.py +224 -224
- package/ftm-map/scripts/queries/go-tags.scm +20 -20
- package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
- package/ftm-map/scripts/queries/python-tags.scm +31 -31
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
- package/ftm-map/scripts/queries/rust-tags.scm +37 -37
- package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
- package/ftm-map/scripts/query.py +301 -301
- package/ftm-map/scripts/ranker.py +377 -377
- package/ftm-map/scripts/requirements.txt +5 -5
- package/ftm-map/scripts/setup-hooks.sh +27 -27
- package/ftm-map/scripts/setup.sh +56 -56
- package/ftm-map/scripts/test_db.py +364 -364
- package/ftm-map/scripts/test_parser.py +174 -174
- package/ftm-map/scripts/test_query.py +183 -183
- package/ftm-map/scripts/test_ranker.py +199 -199
- package/ftm-map/scripts/views.py +591 -591
- package/ftm-map.yml +2 -2
- package/ftm-mind/SKILL.md +201 -1943
- package/ftm-mind/evals/promptfoo.yaml +142 -142
- package/ftm-mind/references/blackboard-protocol.md +110 -0
- package/ftm-mind/references/blackboard-schema.md +328 -328
- package/ftm-mind/references/complexity-guide.md +110 -110
- package/ftm-mind/references/complexity-sizing.md +138 -0
- package/ftm-mind/references/decide-act-protocol.md +172 -0
- package/ftm-mind/references/direct-execution.md +51 -0
- package/ftm-mind/references/environment-discovery.md +77 -0
- package/ftm-mind/references/event-registry.md +319 -319
- package/ftm-mind/references/mcp-inventory.md +300 -296
- package/ftm-mind/references/ops-routing.md +47 -0
- package/ftm-mind/references/orient-protocol.md +234 -0
- package/ftm-mind/references/personality.md +40 -0
- package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
- package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
- package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
- package/ftm-mind/references/reflexion-protocol.md +249 -249
- package/ftm-mind/references/routing/SCENARIOS.md +22 -22
- package/ftm-mind/references/routing-scenarios.md +35 -35
- package/ftm-mind.yml +2 -2
- package/ftm-ops.yml +4 -0
- package/ftm-pause/SKILL.md +395 -395
- package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
- package/ftm-pause/references/protocols/VALIDATION.md +80 -80
- package/ftm-pause.yml +2 -2
- package/ftm-researcher/SKILL.md +275 -275
- package/ftm-researcher/evals/agent-diversity.yaml +17 -17
- package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
- package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
- package/ftm-researcher/references/adaptive-search.md +116 -116
- package/ftm-researcher/references/agent-prompts.md +193 -193
- package/ftm-researcher/references/council-integration.md +193 -193
- package/ftm-researcher/references/output-format.md +203 -203
- package/ftm-researcher/references/synthesis-pipeline.md +165 -165
- package/ftm-researcher/scripts/score_credibility.py +234 -234
- package/ftm-researcher/scripts/validate_research.py +92 -92
- package/ftm-researcher.yml +2 -2
- package/ftm-resume/SKILL.md +518 -518
- package/ftm-resume/references/protocols/VALIDATION.md +172 -172
- package/ftm-resume.yml +2 -2
- package/ftm-retro/SKILL.md +380 -380
- package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
- package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
- package/ftm-retro.yml +2 -2
- package/ftm-routine/SKILL.md +170 -170
- package/ftm-routine.yml +4 -4
- package/ftm-state/blackboard/capabilities.json +5 -5
- package/ftm-state/blackboard/capabilities.schema.json +27 -27
- package/ftm-state/blackboard/context.json +37 -23
- package/ftm-state/blackboard/experiences/doom-statusline-fix.json +26 -0
- package/ftm-state/blackboard/experiences/hackathon-pages-site.json +26 -0
- package/ftm-state/blackboard/experiences/hindsight-sso-kickoff.json +42 -0
- package/ftm-state/blackboard/experiences/index.json +58 -9
- package/ftm-state/blackboard/experiences/learning-ragnarok-api-access.json +23 -0
- package/ftm-state/blackboard/experiences/nordlayer-members-auto-assign.json +26 -0
- package/ftm-state/blackboard/experiences/saml2aws-stale-session-fix.json +41 -0
- package/ftm-state/blackboard/patterns.json +6 -6
- package/ftm-state/schemas/context.schema.json +130 -130
- package/ftm-state/schemas/experience-index.schema.json +77 -77
- package/ftm-state/schemas/experience.schema.json +78 -78
- package/ftm-state/schemas/patterns.schema.json +44 -44
- package/ftm-upgrade/SKILL.md +194 -194
- package/ftm-upgrade/scripts/check-version.sh +76 -76
- package/ftm-upgrade/scripts/upgrade.sh +143 -143
- package/ftm-upgrade.yml +2 -2
- package/ftm-verify.yml +2 -2
- package/ftm.yml +2 -2
- package/hooks/ftm-auto-log.sh +137 -0
- package/hooks/ftm-blackboard-enforcer.sh +93 -93
- package/hooks/ftm-discovery-reminder.sh +90 -90
- package/hooks/ftm-drafts-gate.sh +61 -61
- package/hooks/ftm-event-logger.mjs +107 -107
- package/hooks/ftm-install-hooks.sh +240 -0
- package/hooks/ftm-learning-capture.sh +117 -0
- package/hooks/ftm-map-autodetect.sh +79 -79
- package/hooks/ftm-pending-sync-check.sh +22 -22
- package/hooks/ftm-plan-gate.sh +92 -92
- package/hooks/ftm-post-commit-trigger.sh +57 -57
- package/hooks/ftm-post-compaction.sh +138 -0
- package/hooks/ftm-pre-compaction.sh +147 -0
- package/hooks/ftm-session-end.sh +52 -0
- package/hooks/ftm-session-snapshot.sh +213 -0
- package/hooks/settings-template.json +81 -81
- package/install.sh +363 -363
- package/package.json +84 -84
- package/uninstall.sh +25 -25
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Plan generator — invokes Claude CLI to produce a structured YAML execution plan.
|
|
3
|
-
|
|
4
|
-
The generator calls `claude -p <prompt> --output-format json`, parses the JSON
|
|
5
|
-
envelope to extract the text result, then extracts the embedded YAML block.
|
|
6
|
-
|
|
7
|
-
On timeout or any subprocess error the function returns a safe error dict so
|
|
8
|
-
callers can surface the failure without crashing.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import json
|
|
14
|
-
import subprocess
|
|
15
|
-
|
|
16
|
-
import yaml
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def generate_plan(task_data: dict, capabilities: dict | None = None) -> dict:
|
|
20
|
-
"""Invoke Claude CLI to generate a structured plan for a task.
|
|
21
|
-
|
|
22
|
-
Returns a dict with keys:
|
|
23
|
-
- steps: list[dict] — parsed plan steps (empty on failure)
|
|
24
|
-
- yaml_content: str — raw YAML string
|
|
25
|
-
- raw_response: str — full text from Claude (useful for debugging)
|
|
26
|
-
- error: str | None — set only on failure
|
|
27
|
-
"""
|
|
28
|
-
cap_note = ""
|
|
29
|
-
if capabilities:
|
|
30
|
-
available = ", ".join(
|
|
31
|
-
k for k, v in capabilities.items() if v
|
|
32
|
-
)
|
|
33
|
-
if available:
|
|
34
|
-
cap_note = f"\nAvailable integrations: {available}"
|
|
35
|
-
|
|
36
|
-
prompt = f"""Generate a structured execution plan for this task.
|
|
37
|
-
|
|
38
|
-
Task: {task_data.get('title', '')}
|
|
39
|
-
Source: {task_data.get('source', '')}
|
|
40
|
-
Body: {task_data.get('body', '')}{cap_note}
|
|
41
|
-
|
|
42
|
-
Return a YAML object with this structure:
|
|
43
|
-
steps:
|
|
44
|
-
- id: 1
|
|
45
|
-
title: "Step description"
|
|
46
|
-
target_system: "jira|freshservice|slack|gmail|local"
|
|
47
|
-
method_primary: "tool or action"
|
|
48
|
-
method_fallback: "alternative if primary unavailable"
|
|
49
|
-
risk_level: "low|medium|high"
|
|
50
|
-
approval_required: false
|
|
51
|
-
rollback: "how to undo"
|
|
52
|
-
|
|
53
|
-
Only return the YAML, no other text."""
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
result = subprocess.run(
|
|
57
|
-
["claude", "-p", prompt, "--output-format", "json"],
|
|
58
|
-
capture_output=True,
|
|
59
|
-
text=True,
|
|
60
|
-
timeout=120,
|
|
61
|
-
)
|
|
62
|
-
if result.returncode != 0:
|
|
63
|
-
return {"error": result.stderr or "Claude CLI returned non-zero exit", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
64
|
-
|
|
65
|
-
# Claude CLI JSON envelope: {"result": "<text>", ...}
|
|
66
|
-
try:
|
|
67
|
-
output = json.loads(result.stdout)
|
|
68
|
-
text = output.get("result", result.stdout)
|
|
69
|
-
except json.JSONDecodeError:
|
|
70
|
-
text = result.stdout
|
|
71
|
-
|
|
72
|
-
yaml_content = _extract_yaml(text)
|
|
73
|
-
try:
|
|
74
|
-
plan_data = yaml.safe_load(yaml_content) or {}
|
|
75
|
-
except yaml.YAMLError as exc:
|
|
76
|
-
return {
|
|
77
|
-
"error": f"YAML parse error: {exc}",
|
|
78
|
-
"steps": [],
|
|
79
|
-
"yaml_content": yaml_content,
|
|
80
|
-
"raw_response": text,
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
raw_steps = plan_data.get("steps", [])
|
|
84
|
-
# Normalise: ensure each step has all expected keys with safe defaults
|
|
85
|
-
steps = [_normalise_step(i + 1, s) for i, s in enumerate(raw_steps)]
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
"steps": steps,
|
|
89
|
-
"yaml_content": yaml_content,
|
|
90
|
-
"raw_response": text,
|
|
91
|
-
"error": None,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
except subprocess.TimeoutExpired:
|
|
95
|
-
return {"error": "Plan generation timed out after 120s", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
96
|
-
except FileNotFoundError:
|
|
97
|
-
return {"error": "Claude CLI not found — ensure 'claude' is on PATH", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
98
|
-
except Exception as exc:
|
|
99
|
-
return {"error": str(exc), "steps": [], "yaml_content": "", "raw_response": ""}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _extract_yaml(text: str) -> str:
|
|
103
|
-
"""Extract YAML content from Claude's response, handling optional code fences."""
|
|
104
|
-
if "```yaml" in text:
|
|
105
|
-
start = text.index("```yaml") + 7
|
|
106
|
-
end = text.index("```", start)
|
|
107
|
-
return text[start:end].strip()
|
|
108
|
-
if "```" in text:
|
|
109
|
-
start = text.index("```") + 3
|
|
110
|
-
end = text.index("```", start)
|
|
111
|
-
return text[start:end].strip()
|
|
112
|
-
return text.strip()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _normalise_step(index: int, raw: dict) -> dict:
|
|
116
|
-
"""Ensure a raw step dict has all required keys."""
|
|
117
|
-
return {
|
|
118
|
-
"id": raw.get("id", index),
|
|
119
|
-
"title": raw.get("title", f"Step {index}"),
|
|
120
|
-
"target_system": raw.get("target_system", "local"),
|
|
121
|
-
"method_primary": raw.get("method_primary", ""),
|
|
122
|
-
"method_fallback": raw.get("method_fallback", ""),
|
|
123
|
-
"risk_level": raw.get("risk_level", "low"),
|
|
124
|
-
"approval_required": bool(raw.get("approval_required", False)),
|
|
125
|
-
"rollback": raw.get("rollback", ""),
|
|
126
|
-
"status": "pending",
|
|
127
|
-
}
|
|
1
|
+
"""
|
|
2
|
+
Plan generator — invokes Claude CLI to produce a structured YAML execution plan.
|
|
3
|
+
|
|
4
|
+
The generator calls `claude -p <prompt> --output-format json`, parses the JSON
|
|
5
|
+
envelope to extract the text result, then extracts the embedded YAML block.
|
|
6
|
+
|
|
7
|
+
On timeout or any subprocess error the function returns a safe error dict so
|
|
8
|
+
callers can surface the failure without crashing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_plan(task_data: dict, capabilities: dict | None = None) -> dict:
|
|
20
|
+
"""Invoke Claude CLI to generate a structured plan for a task.
|
|
21
|
+
|
|
22
|
+
Returns a dict with keys:
|
|
23
|
+
- steps: list[dict] — parsed plan steps (empty on failure)
|
|
24
|
+
- yaml_content: str — raw YAML string
|
|
25
|
+
- raw_response: str — full text from Claude (useful for debugging)
|
|
26
|
+
- error: str | None — set only on failure
|
|
27
|
+
"""
|
|
28
|
+
cap_note = ""
|
|
29
|
+
if capabilities:
|
|
30
|
+
available = ", ".join(
|
|
31
|
+
k for k, v in capabilities.items() if v
|
|
32
|
+
)
|
|
33
|
+
if available:
|
|
34
|
+
cap_note = f"\nAvailable integrations: {available}"
|
|
35
|
+
|
|
36
|
+
prompt = f"""Generate a structured execution plan for this task.
|
|
37
|
+
|
|
38
|
+
Task: {task_data.get('title', '')}
|
|
39
|
+
Source: {task_data.get('source', '')}
|
|
40
|
+
Body: {task_data.get('body', '')}{cap_note}
|
|
41
|
+
|
|
42
|
+
Return a YAML object with this structure:
|
|
43
|
+
steps:
|
|
44
|
+
- id: 1
|
|
45
|
+
title: "Step description"
|
|
46
|
+
target_system: "jira|freshservice|slack|gmail|local"
|
|
47
|
+
method_primary: "tool or action"
|
|
48
|
+
method_fallback: "alternative if primary unavailable"
|
|
49
|
+
risk_level: "low|medium|high"
|
|
50
|
+
approval_required: false
|
|
51
|
+
rollback: "how to undo"
|
|
52
|
+
|
|
53
|
+
Only return the YAML, no other text."""
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
["claude", "-p", prompt, "--output-format", "json"],
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
timeout=120,
|
|
61
|
+
)
|
|
62
|
+
if result.returncode != 0:
|
|
63
|
+
return {"error": result.stderr or "Claude CLI returned non-zero exit", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
64
|
+
|
|
65
|
+
# Claude CLI JSON envelope: {"result": "<text>", ...}
|
|
66
|
+
try:
|
|
67
|
+
output = json.loads(result.stdout)
|
|
68
|
+
text = output.get("result", result.stdout)
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
text = result.stdout
|
|
71
|
+
|
|
72
|
+
yaml_content = _extract_yaml(text)
|
|
73
|
+
try:
|
|
74
|
+
plan_data = yaml.safe_load(yaml_content) or {}
|
|
75
|
+
except yaml.YAMLError as exc:
|
|
76
|
+
return {
|
|
77
|
+
"error": f"YAML parse error: {exc}",
|
|
78
|
+
"steps": [],
|
|
79
|
+
"yaml_content": yaml_content,
|
|
80
|
+
"raw_response": text,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
raw_steps = plan_data.get("steps", [])
|
|
84
|
+
# Normalise: ensure each step has all expected keys with safe defaults
|
|
85
|
+
steps = [_normalise_step(i + 1, s) for i, s in enumerate(raw_steps)]
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"steps": steps,
|
|
89
|
+
"yaml_content": yaml_content,
|
|
90
|
+
"raw_response": text,
|
|
91
|
+
"error": None,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
except subprocess.TimeoutExpired:
|
|
95
|
+
return {"error": "Plan generation timed out after 120s", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
return {"error": "Claude CLI not found — ensure 'claude' is on PATH", "steps": [], "yaml_content": "", "raw_response": ""}
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
return {"error": str(exc), "steps": [], "yaml_content": "", "raw_response": ""}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _extract_yaml(text: str) -> str:
|
|
103
|
+
"""Extract YAML content from Claude's response, handling optional code fences."""
|
|
104
|
+
if "```yaml" in text:
|
|
105
|
+
start = text.index("```yaml") + 7
|
|
106
|
+
end = text.index("```", start)
|
|
107
|
+
return text[start:end].strip()
|
|
108
|
+
if "```" in text:
|
|
109
|
+
start = text.index("```") + 3
|
|
110
|
+
end = text.index("```", start)
|
|
111
|
+
return text[start:end].strip()
|
|
112
|
+
return text.strip()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _normalise_step(index: int, raw: dict) -> dict:
|
|
116
|
+
"""Ensure a raw step dict has all required keys."""
|
|
117
|
+
return {
|
|
118
|
+
"id": raw.get("id", index),
|
|
119
|
+
"title": raw.get("title", f"Step {index}"),
|
|
120
|
+
"target_system": raw.get("target_system", "local"),
|
|
121
|
+
"method_primary": raw.get("method_primary", ""),
|
|
122
|
+
"method_fallback": raw.get("method_fallback", ""),
|
|
123
|
+
"risk_level": raw.get("risk_level", "low"),
|
|
124
|
+
"approval_required": bool(raw.get("approval_required", False)),
|
|
125
|
+
"rollback": raw.get("rollback", ""),
|
|
126
|
+
"status": "pending",
|
|
127
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Pydantic models for structured execution plans.
|
|
3
|
-
|
|
4
|
-
A Plan contains an ordered list of PlanSteps. Each step tracks its approval
|
|
5
|
-
state independently so the operator can approve individual steps before
|
|
6
|
-
the executor runs them.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from pydantic import BaseModel, Field
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class PlanStep(BaseModel):
|
|
16
|
-
id: int
|
|
17
|
-
title: str
|
|
18
|
-
target_system: str = ""
|
|
19
|
-
method_primary: str = ""
|
|
20
|
-
method_fallback: str = ""
|
|
21
|
-
risk_level: str = "low" # low | medium | high
|
|
22
|
-
approval_required: bool = False
|
|
23
|
-
rollback: str = ""
|
|
24
|
-
status: str = "pending" # pending | approved | rejected | running | completed | failed
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class Plan(BaseModel):
|
|
28
|
-
id: int | None = None
|
|
29
|
-
task_id: int
|
|
30
|
-
steps: list[PlanStep] = Field(default_factory=list)
|
|
31
|
-
status: str = "draft" # draft | approved | executing | completed | failed
|
|
32
|
-
yaml_content: str = ""
|
|
33
|
-
created_at: str | None = None
|
|
34
|
-
updated_at: str | None = None
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for structured execution plans.
|
|
3
|
+
|
|
4
|
+
A Plan contains an ordered list of PlanSteps. Each step tracks its approval
|
|
5
|
+
state independently so the operator can approve individual steps before
|
|
6
|
+
the executor runs them.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlanStep(BaseModel):
|
|
16
|
+
id: int
|
|
17
|
+
title: str
|
|
18
|
+
target_system: str = ""
|
|
19
|
+
method_primary: str = ""
|
|
20
|
+
method_fallback: str = ""
|
|
21
|
+
risk_level: str = "low" # low | medium | high
|
|
22
|
+
approval_required: bool = False
|
|
23
|
+
rollback: str = ""
|
|
24
|
+
status: str = "pending" # pending | approved | rejected | running | completed | failed
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Plan(BaseModel):
|
|
28
|
+
id: int | None = None
|
|
29
|
+
task_id: int
|
|
30
|
+
steps: list[PlanStep] = Field(default_factory=list)
|
|
31
|
+
status: str = "draft" # draft | approved | executing | completed | failed
|
|
32
|
+
yaml_content: str = ""
|
|
33
|
+
created_at: str | None = None
|
|
34
|
+
updated_at: str | None = None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
fastapi>=0.111.0
|
|
2
|
-
uvicorn[standard]>=0.29.0
|
|
3
|
-
pyyaml>=6.0.1
|
|
4
|
-
requests>=2.31.0
|
|
5
|
-
pydantic>=2.6.0
|
|
1
|
+
fastapi>=0.111.0
|
|
2
|
+
uvicorn[standard]>=0.29.0
|
|
3
|
+
pyyaml>=6.0.1
|
|
4
|
+
requests>=2.31.0
|
|
5
|
+
pydantic>=2.6.0
|
|
Binary file
|