specfact-cli 0.4.0__py3-none-any.whl
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.
Potentially problematic release.
This version of specfact-cli might be problematic. Click here for more details.
- specfact_cli/__init__.py +14 -0
- specfact_cli/agents/__init__.py +23 -0
- specfact_cli/agents/analyze_agent.py +392 -0
- specfact_cli/agents/base.py +95 -0
- specfact_cli/agents/plan_agent.py +202 -0
- specfact_cli/agents/registry.py +176 -0
- specfact_cli/agents/sync_agent.py +133 -0
- specfact_cli/analyzers/__init__.py +10 -0
- specfact_cli/analyzers/code_analyzer.py +775 -0
- specfact_cli/cli.py +397 -0
- specfact_cli/commands/__init__.py +7 -0
- specfact_cli/commands/enforce.py +87 -0
- specfact_cli/commands/import_cmd.py +355 -0
- specfact_cli/commands/init.py +119 -0
- specfact_cli/commands/plan.py +1090 -0
- specfact_cli/commands/repro.py +172 -0
- specfact_cli/commands/sync.py +408 -0
- specfact_cli/common/__init__.py +24 -0
- specfact_cli/common/logger_setup.py +673 -0
- specfact_cli/common/logging_utils.py +41 -0
- specfact_cli/common/text_utils.py +52 -0
- specfact_cli/common/utils.py +48 -0
- specfact_cli/comparators/__init__.py +10 -0
- specfact_cli/comparators/plan_comparator.py +391 -0
- specfact_cli/generators/__init__.py +13 -0
- specfact_cli/generators/plan_generator.py +105 -0
- specfact_cli/generators/protocol_generator.py +115 -0
- specfact_cli/generators/report_generator.py +200 -0
- specfact_cli/generators/workflow_generator.py +111 -0
- specfact_cli/importers/__init__.py +6 -0
- specfact_cli/importers/speckit_converter.py +773 -0
- specfact_cli/importers/speckit_scanner.py +704 -0
- specfact_cli/models/__init__.py +32 -0
- specfact_cli/models/deviation.py +105 -0
- specfact_cli/models/enforcement.py +150 -0
- specfact_cli/models/plan.py +97 -0
- specfact_cli/models/protocol.py +28 -0
- specfact_cli/modes/__init__.py +18 -0
- specfact_cli/modes/detector.py +126 -0
- specfact_cli/modes/router.py +153 -0
- specfact_cli/sync/__init__.py +11 -0
- specfact_cli/sync/repository_sync.py +279 -0
- specfact_cli/sync/speckit_sync.py +388 -0
- specfact_cli/utils/__init__.py +57 -0
- specfact_cli/utils/console.py +69 -0
- specfact_cli/utils/feature_keys.py +213 -0
- specfact_cli/utils/git.py +241 -0
- specfact_cli/utils/ide_setup.py +381 -0
- specfact_cli/utils/prompts.py +179 -0
- specfact_cli/utils/structure.py +496 -0
- specfact_cli/utils/yaml_utils.py +200 -0
- specfact_cli/validators/__init__.py +19 -0
- specfact_cli/validators/fsm.py +260 -0
- specfact_cli/validators/repro_checker.py +320 -0
- specfact_cli/validators/schema.py +200 -0
- specfact_cli-0.4.0.dist-info/METADATA +332 -0
- specfact_cli-0.4.0.dist-info/RECORD +60 -0
- specfact_cli-0.4.0.dist-info/WHEEL +4 -0
- specfact_cli-0.4.0.dist-info/entry_points.txt +2 -0
- specfact_cli-0.4.0.dist-info/licenses/LICENSE.md +55 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plan Agent - Plan management with business logic understanding.
|
|
3
|
+
|
|
4
|
+
This module provides the PlanAgent for plan management operations with
|
|
5
|
+
enhanced prompts and context injection for CoPilot integration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from beartype import beartype
|
|
14
|
+
from icontract import ensure, require
|
|
15
|
+
|
|
16
|
+
from specfact_cli.agents.base import AgentMode
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PlanAgent(AgentMode):
|
|
20
|
+
"""
|
|
21
|
+
Plan management agent with business logic understanding.
|
|
22
|
+
|
|
23
|
+
Provides enhanced prompts for plan management operations with
|
|
24
|
+
context injection from IDE and repository analysis.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@beartype
|
|
28
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
29
|
+
@ensure(lambda result: isinstance(result, str) and bool(result), "Prompt must be non-empty string")
|
|
30
|
+
def generate_prompt(self, command: str, context: dict[str, Any] | None = None) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Generate enhanced prompt for plan management.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
command: CLI command being executed (e.g., "plan init", "plan compare")
|
|
36
|
+
context: Context dictionary with current file, selection, workspace
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Enhanced prompt optimized for CoPilot
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> agent = PlanAgent()
|
|
43
|
+
>>> prompt = agent.generate_prompt("plan init", {"current_file": "idea.yaml"})
|
|
44
|
+
>>> "interactive" in prompt.lower()
|
|
45
|
+
True
|
|
46
|
+
"""
|
|
47
|
+
if context is None:
|
|
48
|
+
context = {}
|
|
49
|
+
|
|
50
|
+
current_file = context.get("current_file", "")
|
|
51
|
+
selection = context.get("selection", "")
|
|
52
|
+
workspace = context.get("workspace", "")
|
|
53
|
+
|
|
54
|
+
if command in ("plan promote", "plan adopt"):
|
|
55
|
+
auto_plan_path = context.get("auto_plan_path", "")
|
|
56
|
+
auto_plan_content = ""
|
|
57
|
+
if auto_plan_path and Path(auto_plan_path).exists():
|
|
58
|
+
auto_plan_content = Path(auto_plan_path).read_text()[:500] # Limit length
|
|
59
|
+
|
|
60
|
+
prompt = f"""
|
|
61
|
+
Analyze the repository and cross-validate identified features/stories.
|
|
62
|
+
|
|
63
|
+
Repository Context:
|
|
64
|
+
- Workspace: {workspace or "None"}
|
|
65
|
+
- Current file: {current_file or "None"}
|
|
66
|
+
- Selection: {selection or "None"}
|
|
67
|
+
|
|
68
|
+
Auto-Derived Plan (from AST analysis):
|
|
69
|
+
{auto_plan_content[:500] if auto_plan_content else "None"}
|
|
70
|
+
|
|
71
|
+
Tasks:
|
|
72
|
+
1. Validate each identified feature exists in the codebase
|
|
73
|
+
2. Identify missing features that AST analysis missed
|
|
74
|
+
3. Identify false positives (classes/components that aren't features)
|
|
75
|
+
4. Cross-validate stories against actual code
|
|
76
|
+
5. Refine confidence scores based on code quality and documentation
|
|
77
|
+
6. Suggest theme categorization improvements
|
|
78
|
+
7. Extract business context from code comments/docs
|
|
79
|
+
|
|
80
|
+
Output Format:
|
|
81
|
+
- Validated features list with refined confidence scores
|
|
82
|
+
- Missing features (AI discovered, not in AST analysis)
|
|
83
|
+
- False positives (features to remove from plan)
|
|
84
|
+
- Story mapping validation (corrections needed)
|
|
85
|
+
- Theme categorization suggestions
|
|
86
|
+
- Business context extraction (idea, target users, value hypothesis)
|
|
87
|
+
"""
|
|
88
|
+
elif command == "plan init":
|
|
89
|
+
prompt = f"""
|
|
90
|
+
Initialize plan bundle with interactive wizard.
|
|
91
|
+
|
|
92
|
+
Context:
|
|
93
|
+
- Current file: {current_file or "None"}
|
|
94
|
+
- Selection: {selection or "None"}
|
|
95
|
+
- Workspace: {workspace or "None"}
|
|
96
|
+
|
|
97
|
+
Extract from context:
|
|
98
|
+
1. Idea from selection or current file
|
|
99
|
+
2. Business plan structure
|
|
100
|
+
3. Product plan themes
|
|
101
|
+
4. Features from workspace structure
|
|
102
|
+
|
|
103
|
+
Generate interactive prompts for missing information.
|
|
104
|
+
"""
|
|
105
|
+
elif command == "plan compare":
|
|
106
|
+
prompt = f"""
|
|
107
|
+
Compare manual vs auto-derived plans.
|
|
108
|
+
|
|
109
|
+
Context:
|
|
110
|
+
- Current file: {current_file or "None"}
|
|
111
|
+
- Selection: {selection or "None"}
|
|
112
|
+
- Workspace: {workspace or "None"}
|
|
113
|
+
|
|
114
|
+
Focus on:
|
|
115
|
+
1. Deviation explanations
|
|
116
|
+
2. Fix suggestions
|
|
117
|
+
3. Interactive deviation review
|
|
118
|
+
|
|
119
|
+
Generate rich console output with explanations.
|
|
120
|
+
"""
|
|
121
|
+
else:
|
|
122
|
+
prompt = f"Execute plan command: {command}"
|
|
123
|
+
|
|
124
|
+
return prompt.strip()
|
|
125
|
+
|
|
126
|
+
@beartype
|
|
127
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
128
|
+
@ensure(lambda result: isinstance(result, dict), "Result must be a dictionary")
|
|
129
|
+
def execute(
|
|
130
|
+
self, command: str, args: dict[str, Any] | None = None, context: dict[str, Any] | None = None
|
|
131
|
+
) -> dict[str, Any]:
|
|
132
|
+
"""
|
|
133
|
+
Execute plan command with enhanced prompts.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
command: CLI command being executed (e.g., "plan init")
|
|
137
|
+
args: Command arguments (e.g., {"idea": "idea.yaml"})
|
|
138
|
+
context: Context dictionary with current file, selection, workspace
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Command result with enhanced output
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
>>> agent = PlanAgent()
|
|
145
|
+
>>> result = agent.execute("plan init", {"idea": "idea.yaml"}, {"current_file": "idea.yaml"})
|
|
146
|
+
>>> isinstance(result, dict)
|
|
147
|
+
True
|
|
148
|
+
"""
|
|
149
|
+
if args is None:
|
|
150
|
+
args = {}
|
|
151
|
+
if context is None:
|
|
152
|
+
context = {}
|
|
153
|
+
|
|
154
|
+
# Generate enhanced prompt
|
|
155
|
+
prompt = self.generate_prompt(command, context)
|
|
156
|
+
|
|
157
|
+
# For Phase 4.1, return structured result with prompt
|
|
158
|
+
# In Phase 4.2+, this will route to actual command execution with agent mode
|
|
159
|
+
return {
|
|
160
|
+
"type": "plan",
|
|
161
|
+
"command": command,
|
|
162
|
+
"prompt": prompt,
|
|
163
|
+
"args": args,
|
|
164
|
+
"context": context,
|
|
165
|
+
"enhanced": True,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@beartype
|
|
169
|
+
@ensure(lambda result: isinstance(result, dict), "Result must be a dictionary")
|
|
170
|
+
def inject_context(self, context: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Inject context information specific to plan operations.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
context: Basic context dictionary (can be None)
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Enhanced context with plan-specific information
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
>>> agent = PlanAgent()
|
|
182
|
+
>>> enhanced = agent.inject_context({"current_file": "idea.yaml"})
|
|
183
|
+
>>> isinstance(enhanced, dict)
|
|
184
|
+
True
|
|
185
|
+
"""
|
|
186
|
+
enhanced = super().inject_context(context)
|
|
187
|
+
|
|
188
|
+
# Add plan artifacts if workspace is available
|
|
189
|
+
if enhanced.get("workspace"):
|
|
190
|
+
workspace_path = Path(enhanced["workspace"])
|
|
191
|
+
if workspace_path.exists() and workspace_path.is_dir():
|
|
192
|
+
# Find plan artifacts
|
|
193
|
+
plan_artifacts = {}
|
|
194
|
+
specfact_dir = workspace_path / ".specfact"
|
|
195
|
+
if specfact_dir.exists():
|
|
196
|
+
plans_dir = specfact_dir / "plans"
|
|
197
|
+
if plans_dir.exists():
|
|
198
|
+
plan_files = list(plans_dir.glob("*.yaml")) + list(plans_dir.glob("*.yml"))
|
|
199
|
+
plan_artifacts["plan_files"] = [str(f) for f in plan_files[:5]] # Limit to first 5
|
|
200
|
+
enhanced["plan_artifacts"] = plan_artifacts
|
|
201
|
+
|
|
202
|
+
return enhanced
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Registry - Registry for agent mode instances.
|
|
3
|
+
|
|
4
|
+
This module provides a registry for managing agent mode instances and
|
|
5
|
+
routing commands to appropriate agents based on command type.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from beartype import beartype
|
|
11
|
+
from icontract import ensure, require
|
|
12
|
+
|
|
13
|
+
from specfact_cli.agents.analyze_agent import AnalyzeAgent
|
|
14
|
+
from specfact_cli.agents.base import AgentMode
|
|
15
|
+
from specfact_cli.agents.plan_agent import PlanAgent
|
|
16
|
+
from specfact_cli.agents.sync_agent import SyncAgent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentRegistry:
|
|
20
|
+
"""
|
|
21
|
+
Registry for agent mode instances.
|
|
22
|
+
|
|
23
|
+
Provides centralized management of agent instances and routing
|
|
24
|
+
based on command type.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
"""Initialize agent registry with default agents."""
|
|
29
|
+
self._agents: dict[str, AgentMode] = {}
|
|
30
|
+
self._register_default_agents()
|
|
31
|
+
|
|
32
|
+
@beartype
|
|
33
|
+
def _register_default_agents(self) -> None:
|
|
34
|
+
"""Register default agent instances."""
|
|
35
|
+
self._agents["analyze"] = AnalyzeAgent()
|
|
36
|
+
self._agents["plan"] = PlanAgent()
|
|
37
|
+
self._agents["sync"] = SyncAgent()
|
|
38
|
+
|
|
39
|
+
@beartype
|
|
40
|
+
@require(lambda name: bool(name), "Agent name must be non-empty")
|
|
41
|
+
@require(lambda agent: isinstance(agent, AgentMode), "Agent must be AgentMode instance")
|
|
42
|
+
def register(self, name: str, agent: AgentMode) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Register an agent instance.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: Agent name (e.g., "analyze", "plan", "sync")
|
|
48
|
+
agent: Agent mode instance
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
>>> registry = AgentRegistry()
|
|
52
|
+
>>> registry.register("custom", AnalyzeAgent())
|
|
53
|
+
"""
|
|
54
|
+
self._agents[name] = agent
|
|
55
|
+
|
|
56
|
+
@beartype
|
|
57
|
+
@require(lambda name: bool(name), "Agent name must be non-empty")
|
|
58
|
+
@ensure(lambda result: result is None or isinstance(result, AgentMode), "Result must be AgentMode or None")
|
|
59
|
+
def get(self, name: str) -> AgentMode | None:
|
|
60
|
+
"""
|
|
61
|
+
Get an agent instance by name.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name: Agent name (e.g., "analyze", "plan", "sync")
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Agent mode instance or None if not found
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
>>> registry = AgentRegistry()
|
|
71
|
+
>>> agent = registry.get("analyze")
|
|
72
|
+
>>> isinstance(agent, AnalyzeAgent)
|
|
73
|
+
True
|
|
74
|
+
"""
|
|
75
|
+
return self._agents.get(name)
|
|
76
|
+
|
|
77
|
+
@beartype
|
|
78
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
79
|
+
@ensure(lambda result: result is None or isinstance(result, AgentMode), "Result must be AgentMode or None")
|
|
80
|
+
def get_agent_for_command(self, command: str) -> AgentMode | None:
|
|
81
|
+
"""
|
|
82
|
+
Get agent instance for a command.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
command: CLI command (e.g., "import from-code", "plan init", "sync spec-kit")
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Agent mode instance or None if not found
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> registry = AgentRegistry()
|
|
92
|
+
>>> agent = registry.get_agent_for_command("import from-code")
|
|
93
|
+
>>> isinstance(agent, AnalyzeAgent)
|
|
94
|
+
True
|
|
95
|
+
"""
|
|
96
|
+
# Extract command type from command string
|
|
97
|
+
command_parts = command.split()
|
|
98
|
+
if not command_parts:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
command_type = command_parts[0]
|
|
102
|
+
|
|
103
|
+
# Map command types to agent names
|
|
104
|
+
agent_map: dict[str, str] = {
|
|
105
|
+
"import": "analyze", # import from-code uses AnalyzeAgent
|
|
106
|
+
"plan": "plan",
|
|
107
|
+
"sync": "sync",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
agent_name = agent_map.get(command_type)
|
|
111
|
+
if agent_name:
|
|
112
|
+
return self.get(agent_name)
|
|
113
|
+
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
@beartype
|
|
117
|
+
def list_agents(self) -> list[str]:
|
|
118
|
+
"""
|
|
119
|
+
List all registered agent names.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
List of agent names
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
>>> registry = AgentRegistry()
|
|
126
|
+
>>> names = registry.list_agents()
|
|
127
|
+
>>> "analyze" in names
|
|
128
|
+
True
|
|
129
|
+
"""
|
|
130
|
+
return list(self._agents.keys())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Global registry instance
|
|
134
|
+
_registry: AgentRegistry | None = None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@beartype
|
|
138
|
+
@ensure(lambda result: isinstance(result, AgentRegistry), "Result must be AgentRegistry")
|
|
139
|
+
def get_registry() -> AgentRegistry:
|
|
140
|
+
"""
|
|
141
|
+
Get the global agent registry instance.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
AgentRegistry instance
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
>>> registry = get_registry()
|
|
148
|
+
>>> isinstance(registry, AgentRegistry)
|
|
149
|
+
True
|
|
150
|
+
"""
|
|
151
|
+
global _registry
|
|
152
|
+
if _registry is None:
|
|
153
|
+
_registry = AgentRegistry()
|
|
154
|
+
return _registry
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@beartype
|
|
158
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
159
|
+
@ensure(lambda result: result is None or isinstance(result, AgentMode), "Result must be AgentMode or None")
|
|
160
|
+
def get_agent(command: str) -> AgentMode | None:
|
|
161
|
+
"""
|
|
162
|
+
Get agent instance for a command (convenience function).
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
command: CLI command (e.g., "import from-code")
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Agent mode instance or None if not found
|
|
169
|
+
|
|
170
|
+
Examples:
|
|
171
|
+
>>> agent = get_agent("import from-code")
|
|
172
|
+
>>> isinstance(agent, AnalyzeAgent)
|
|
173
|
+
True
|
|
174
|
+
"""
|
|
175
|
+
registry = get_registry()
|
|
176
|
+
return registry.get_agent_for_command(command)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sync Agent - Bidirectional sync with conflict resolution.
|
|
3
|
+
|
|
4
|
+
This module provides the SyncAgent for sync operations with enhanced
|
|
5
|
+
prompts and conflict resolution assistance for CoPilot integration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from beartype import beartype
|
|
13
|
+
from icontract import ensure, require
|
|
14
|
+
|
|
15
|
+
from specfact_cli.agents.base import AgentMode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SyncAgent(AgentMode):
|
|
19
|
+
"""
|
|
20
|
+
Bidirectional sync agent with conflict resolution.
|
|
21
|
+
|
|
22
|
+
Provides enhanced prompts for sync operations with conflict
|
|
23
|
+
resolution assistance and change explanation.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@beartype
|
|
27
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
28
|
+
@ensure(lambda result: isinstance(result, str) and bool(result), "Prompt must be non-empty string")
|
|
29
|
+
def generate_prompt(self, command: str, context: dict[str, Any] | None = None) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Generate enhanced prompt for sync operation.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
command: CLI command being executed (e.g., "sync spec-kit", "sync repository")
|
|
35
|
+
context: Context dictionary with current file, selection, workspace
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Enhanced prompt optimized for CoPilot
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
>>> agent = SyncAgent()
|
|
42
|
+
>>> prompt = agent.generate_prompt("sync spec-kit", {"workspace": "."})
|
|
43
|
+
>>> "conflict" in prompt.lower()
|
|
44
|
+
True
|
|
45
|
+
"""
|
|
46
|
+
if context is None:
|
|
47
|
+
context = {}
|
|
48
|
+
|
|
49
|
+
current_file = context.get("current_file", "")
|
|
50
|
+
selection = context.get("selection", "")
|
|
51
|
+
workspace = context.get("workspace", "")
|
|
52
|
+
|
|
53
|
+
prompt = f"""
|
|
54
|
+
Perform bidirectional sync with conflict resolution.
|
|
55
|
+
|
|
56
|
+
Context:
|
|
57
|
+
- Current file: {current_file or "None"}
|
|
58
|
+
- Selection: {selection or "None"}
|
|
59
|
+
- Workspace: {workspace or "None"}
|
|
60
|
+
|
|
61
|
+
Sync strategy:
|
|
62
|
+
1. Detect changes in Spec-Kit artifacts and .specfact/ artifacts
|
|
63
|
+
2. Automatic source detection
|
|
64
|
+
3. Conflict resolution assistance
|
|
65
|
+
4. Change explanation and preview
|
|
66
|
+
|
|
67
|
+
Generate interactive conflict resolution prompts.
|
|
68
|
+
"""
|
|
69
|
+
return prompt.strip()
|
|
70
|
+
|
|
71
|
+
@beartype
|
|
72
|
+
@require(lambda command: bool(command), "Command must be non-empty")
|
|
73
|
+
@ensure(lambda result: isinstance(result, dict), "Result must be a dictionary")
|
|
74
|
+
def execute(
|
|
75
|
+
self, command: str, args: dict[str, Any] | None = None, context: dict[str, Any] | None = None
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Execute sync command with enhanced prompts.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
command: CLI command being executed (e.g., "sync spec-kit")
|
|
82
|
+
args: Command arguments (e.g., {"source": "spec-kit", "target": ".specfact"})
|
|
83
|
+
context: Context dictionary with current file, selection, workspace
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Command result with enhanced output
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
>>> agent = SyncAgent()
|
|
90
|
+
>>> result = agent.execute("sync spec-kit", {"source": "spec-kit"}, {"workspace": "."})
|
|
91
|
+
>>> isinstance(result, dict)
|
|
92
|
+
True
|
|
93
|
+
"""
|
|
94
|
+
if args is None:
|
|
95
|
+
args = {}
|
|
96
|
+
if context is None:
|
|
97
|
+
context = {}
|
|
98
|
+
|
|
99
|
+
# Generate enhanced prompt
|
|
100
|
+
prompt = self.generate_prompt(command, context)
|
|
101
|
+
|
|
102
|
+
# For Phase 4.1, return structured result with prompt
|
|
103
|
+
# In Phase 4.2+, this will route to actual command execution with agent mode
|
|
104
|
+
return {
|
|
105
|
+
"type": "sync",
|
|
106
|
+
"command": command,
|
|
107
|
+
"prompt": prompt,
|
|
108
|
+
"args": args,
|
|
109
|
+
"context": context,
|
|
110
|
+
"enhanced": True,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@beartype
|
|
114
|
+
@ensure(lambda result: isinstance(result, dict), "Result must be a dictionary")
|
|
115
|
+
def inject_context(self, context: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
116
|
+
"""
|
|
117
|
+
Inject context information specific to sync operations.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
context: Basic context dictionary (can be None)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Enhanced context with sync-specific information
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> agent = SyncAgent()
|
|
127
|
+
>>> enhanced = agent.inject_context({"workspace": "."})
|
|
128
|
+
>>> isinstance(enhanced, dict)
|
|
129
|
+
True
|
|
130
|
+
"""
|
|
131
|
+
# Sync-specific context injection will be enhanced in Phase 4.2+
|
|
132
|
+
# For now, just return the enhanced context from base class
|
|
133
|
+
return super().inject_context(context)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analyzers module for SpecFact CLI.
|
|
3
|
+
|
|
4
|
+
This module provides classes for analyzing code to extract features,
|
|
5
|
+
stories, and generate plan bundles from brownfield codebases.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from specfact_cli.analyzers.code_analyzer import CodeAnalyzer
|
|
9
|
+
|
|
10
|
+
__all__ = ["CodeAnalyzer"]
|