scopemate 0.1.0__py3-none-any.whl → 0.2.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.
- scopemate/__init__.py +1 -1
- scopemate/breakdown.py +30 -2
- scopemate/cli.py +57 -11
- scopemate/engine.py +188 -10
- scopemate/interaction.py +34 -4
- scopemate/llm.py +257 -38
- scopemate/models.py +63 -1
- scopemate/storage.py +218 -4
- scopemate/task_analysis.py +47 -6
- {scopemate-0.1.0.dist-info → scopemate-0.2.0.dist-info}/METADATA +143 -12
- scopemate-0.2.0.dist-info/RECORD +17 -0
- {scopemate-0.1.0.dist-info → scopemate-0.2.0.dist-info}/WHEEL +1 -1
- scopemate-0.1.0.dist-info/RECORD +0 -17
- {scopemate-0.1.0.dist-info → scopemate-0.2.0.dist-info}/entry_points.txt +0 -0
- {scopemate-0.1.0.dist-info → scopemate-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {scopemate-0.1.0.dist-info → scopemate-0.2.0.dist-info}/top_level.txt +0 -0
scopemate/__init__.py
CHANGED
scopemate/breakdown.py
CHANGED
@@ -21,11 +21,39 @@ def suggest_breakdown(task: ScopeMateTask) -> List[ScopeMateTask]:
|
|
21
21
|
"""
|
22
22
|
Use LLM to suggest a breakdown of a task into smaller subtasks.
|
23
23
|
|
24
|
+
This function is a critical part of the scopemate workflow. It uses a Large Language
|
25
|
+
Model to analyze a task and suggest appropriate subtasks that collectively accomplish
|
26
|
+
the parent task's goal. The function handles both complexity-based and time-based
|
27
|
+
breakdowns, ensuring that complex tasks are simplified and long-duration tasks are
|
28
|
+
broken into manageable timeframes.
|
29
|
+
|
30
|
+
The function works through these stages:
|
31
|
+
1. Analyze if the breakdown is needed due to complexity or time duration
|
32
|
+
2. Formulate a specialized prompt for the LLM with appropriate constraints
|
33
|
+
3. Process the LLM's response to extract valid subtask definitions
|
34
|
+
4. Convert the raw LLM output into proper ScopeMateTask objects
|
35
|
+
5. Present the suggestions to the user through an interactive selection process
|
36
|
+
|
37
|
+
The subtasks generated are guaranteed to be:
|
38
|
+
- Smaller in scope than the parent task
|
39
|
+
- Less complex than the parent task
|
40
|
+
- Shorter in duration than the parent task
|
41
|
+
- Collectivey covering all aspects needed to accomplish the parent task
|
42
|
+
|
24
43
|
Args:
|
25
|
-
task: The ScopeMateTask to break down
|
44
|
+
task: The ScopeMateTask to break down into smaller subtasks
|
26
45
|
|
27
46
|
Returns:
|
28
|
-
List of ScopeMateTask objects representing subtasks
|
47
|
+
List of ScopeMateTask objects representing the subtasks, after user interaction
|
48
|
+
|
49
|
+
Example:
|
50
|
+
```python
|
51
|
+
parent_task = get_task_by_id("TASK-123")
|
52
|
+
subtasks = suggest_breakdown(parent_task)
|
53
|
+
if subtasks:
|
54
|
+
print(f"Created {len(subtasks)} subtasks")
|
55
|
+
tasks.extend(subtasks)
|
56
|
+
```
|
29
57
|
"""
|
30
58
|
# Check if we're breaking down due to size complexity or time estimate
|
31
59
|
is_complex = task.scope.size in ["complex", "uncertain", "pioneering"]
|
scopemate/cli.py
CHANGED
@@ -9,12 +9,14 @@ outcome, and output file.
|
|
9
9
|
import sys
|
10
10
|
import argparse
|
11
11
|
import uuid
|
12
|
+
import json
|
13
|
+
import os
|
12
14
|
from typing import List
|
13
15
|
|
14
16
|
from .models import (
|
15
17
|
ScopeMateTask, Purpose, Scope, Outcome, Meta, get_utc_now
|
16
18
|
)
|
17
|
-
from .storage import save_plan
|
19
|
+
from .storage import save_plan, save_markdown_plan, generate_markdown_from_json
|
18
20
|
from .llm import estimate_scope, generate_title_from_purpose_outcome
|
19
21
|
from .breakdown import suggest_breakdown
|
20
22
|
from .task_analysis import check_and_update_parent_estimates
|
@@ -101,36 +103,76 @@ def process_task_with_breakdown(task: ScopeMateTask) -> List[ScopeMateTask]:
|
|
101
103
|
|
102
104
|
|
103
105
|
def command_line() -> None:
|
104
|
-
"""
|
106
|
+
"""
|
107
|
+
Process command line arguments and execute appropriate actions.
|
108
|
+
|
109
|
+
This function is the primary entry point for the scopemate CLI, responsible
|
110
|
+
for parsing command-line arguments and routing execution to the appropriate
|
111
|
+
workflow based on those arguments. It supports two main modes of operation:
|
112
|
+
|
113
|
+
1. Interactive mode (--interactive): Launches the full guided workflow with
|
114
|
+
the TaskEngine for an interactive task creation and breakdown experience.
|
115
|
+
|
116
|
+
2. Non-interactive mode (--purpose and --outcome): Creates a task directly
|
117
|
+
from command-line arguments, generates subtasks using LLM, and saves the
|
118
|
+
resulting task hierarchy to a JSON file with an automatic Markdown version.
|
119
|
+
|
120
|
+
The function validates required arguments depending on the mode, provides
|
121
|
+
helpful error messages when arguments are missing, and handles the entire
|
122
|
+
lifecycle of task creation, breakdown, and saving in non-interactive mode.
|
123
|
+
|
124
|
+
Command line arguments:
|
125
|
+
--interactive: Flag to launch interactive workflow
|
126
|
+
--purpose: Text describing why the task matters (required in non-interactive mode)
|
127
|
+
--outcome: Text describing what will be delivered (required in non-interactive mode)
|
128
|
+
--output: Path to save the output JSON file (default: scopemate_plan.json)
|
129
|
+
|
130
|
+
Side Effects:
|
131
|
+
- Saves task data to a file on disk (both JSON and Markdown versions)
|
132
|
+
- Prints progress and error messages to stdout
|
133
|
+
- Exits with non-zero status code on errors
|
134
|
+
|
135
|
+
Example Usage:
|
136
|
+
```bash
|
137
|
+
# Interactive mode
|
138
|
+
scopemate --interactive
|
139
|
+
|
140
|
+
# Non-interactive mode
|
141
|
+
scopemate --purpose "Improve website performance" \
|
142
|
+
--outcome "Page load time under 2 seconds" \
|
143
|
+
--output "perf_project.json"
|
144
|
+
```
|
145
|
+
|
146
|
+
Note: Markdown files are automatically generated with the same base name
|
147
|
+
as the JSON file (e.g., "perf_project.md" for "perf_project.json").
|
148
|
+
"""
|
105
149
|
parser = argparse.ArgumentParser(
|
106
|
-
description="🪜 scopemate
|
107
|
-
epilog="Purpose: why it matters\n"
|
108
|
-
"Outcome: what will change once it's done\n"
|
109
|
-
"Scope: how will be delivered (this is where LLM can help)",
|
150
|
+
description="🪜 scopemate - Break down complex projects with LLMs",
|
110
151
|
formatter_class=argparse.RawTextHelpFormatter
|
111
152
|
)
|
112
153
|
|
113
154
|
parser.add_argument(
|
114
155
|
"--interactive",
|
115
156
|
action="store_true",
|
116
|
-
help="
|
157
|
+
help="💡 Interactive mode"
|
117
158
|
)
|
118
159
|
|
119
160
|
parser.add_argument(
|
120
161
|
"--outcome",
|
121
|
-
help="🎯
|
162
|
+
help="🎯 What will change once this is done?"
|
122
163
|
)
|
123
164
|
|
124
165
|
parser.add_argument(
|
125
166
|
"--purpose",
|
126
|
-
help="🧭
|
167
|
+
help="🧭 Why does this matter now?"
|
127
168
|
)
|
128
169
|
|
129
170
|
parser.add_argument(
|
130
171
|
"--output",
|
131
172
|
default="scopemate_plan.json",
|
132
|
-
help="
|
173
|
+
help="🗂️ (default: scopemate_plan.json)"
|
133
174
|
)
|
175
|
+
|
134
176
|
args = parser.parse_args()
|
135
177
|
|
136
178
|
# Check if running in interactive mode
|
@@ -154,8 +196,12 @@ def command_line() -> None:
|
|
154
196
|
print("Generating subtasks...")
|
155
197
|
all_tasks = process_task_with_breakdown(task)
|
156
198
|
|
157
|
-
# Save plan to output file
|
199
|
+
# Save plan to output file (this will also create the MD version)
|
158
200
|
save_plan(all_tasks, args.output)
|
201
|
+
|
202
|
+
# Show message about MD file creation
|
203
|
+
md_filename = os.path.splitext(args.output)[0] + ".md"
|
204
|
+
print(f"✅ Both JSON and Markdown versions have been saved. You can share {md_filename} with team members.")
|
159
205
|
|
160
206
|
|
161
207
|
def main():
|
scopemate/engine.py
CHANGED
@@ -25,10 +25,25 @@ from .interaction import prompt_user, build_root_task, print_summary
|
|
25
25
|
class TaskEngine:
|
26
26
|
"""
|
27
27
|
Main engine for scopemate that coordinates task creation, breakdown, and management.
|
28
|
+
|
29
|
+
The TaskEngine is the central coordinator of the scopemate application, managing the entire
|
30
|
+
lifecycle of tasks from creation to breakdown to final output. It handles loading and saving
|
31
|
+
task data, organizing the task hierarchy, and orchestrating the breakdown of complex tasks
|
32
|
+
into simpler subtasks.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
tasks (List[ScopeMateTask]): List of all tasks in the current session
|
36
|
+
task_depths (Dict[str, int]): Mapping of task IDs to their depth in the hierarchy
|
37
|
+
max_depth (int): Maximum allowed depth for task nesting (default: 5)
|
28
38
|
"""
|
29
39
|
|
30
40
|
def __init__(self):
|
31
|
-
"""
|
41
|
+
"""
|
42
|
+
Initialize the TaskEngine with empty task list and depth tracking.
|
43
|
+
|
44
|
+
Creates a new TaskEngine instance with an empty task list and depth tracking dictionary.
|
45
|
+
Sets the default maximum depth for task hierarchies to 5 levels.
|
46
|
+
"""
|
32
47
|
self.tasks: List[ScopeMateTask] = []
|
33
48
|
self.task_depths: Dict[str, int] = {}
|
34
49
|
self.max_depth: int = 5 # Maximum depth of task hierarchy
|
@@ -37,8 +52,21 @@ class TaskEngine:
|
|
37
52
|
"""
|
38
53
|
Load tasks from checkpoint file if it exists.
|
39
54
|
|
55
|
+
Checks for the existence of a checkpoint file and prompts the user about whether
|
56
|
+
to resume from this checkpoint. If the user confirms, loads the tasks from the
|
57
|
+
checkpoint file into the engine.
|
58
|
+
|
40
59
|
Returns:
|
41
|
-
True if checkpoint was loaded, False otherwise
|
60
|
+
bool: True if checkpoint was loaded successfully, False otherwise
|
61
|
+
|
62
|
+
Example:
|
63
|
+
```python
|
64
|
+
engine = TaskEngine()
|
65
|
+
if engine.load_from_checkpoint():
|
66
|
+
print(f"Loaded {len(engine.tasks)} tasks from checkpoint")
|
67
|
+
else:
|
68
|
+
print("Starting with a new task list")
|
69
|
+
```
|
42
70
|
"""
|
43
71
|
if checkpoint_exists():
|
44
72
|
resume = prompt_user(
|
@@ -58,11 +86,24 @@ class TaskEngine:
|
|
58
86
|
"""
|
59
87
|
Load tasks from a user-specified file.
|
60
88
|
|
89
|
+
Prompts the user about whether to load an existing plan. If confirmed,
|
90
|
+
asks for the filename and attempts to load tasks from that file.
|
91
|
+
|
61
92
|
Args:
|
62
|
-
default_filename: Default filename to suggest
|
93
|
+
default_filename (str): Default filename to suggest to the user
|
63
94
|
|
64
95
|
Returns:
|
65
|
-
True if file was loaded, False otherwise
|
96
|
+
bool: True if file was loaded successfully, False otherwise
|
97
|
+
|
98
|
+
Raises:
|
99
|
+
FileNotFoundError: Handled internally, prints error message if file not found
|
100
|
+
|
101
|
+
Example:
|
102
|
+
```python
|
103
|
+
engine = TaskEngine()
|
104
|
+
if engine.load_from_file("my_project_plan.json"):
|
105
|
+
print(f"Loaded {len(engine.tasks)} tasks from file")
|
106
|
+
```
|
66
107
|
"""
|
67
108
|
choice = prompt_user("Load existing plan?", default="n", choices=["y","n"])
|
68
109
|
if choice.lower() == "y":
|
@@ -76,12 +117,58 @@ class TaskEngine:
|
|
76
117
|
return False
|
77
118
|
|
78
119
|
def create_new_task(self) -> None:
|
79
|
-
"""
|
120
|
+
"""
|
121
|
+
Create a new root task interactively.
|
122
|
+
|
123
|
+
Initiates an interactive dialog to build a new root task, adds it to the task list,
|
124
|
+
and automatically saves a checkpoint. The dialog collects all necessary information
|
125
|
+
for a well-defined task including title, purpose, scope, and expected outcomes.
|
126
|
+
|
127
|
+
Side Effects:
|
128
|
+
- Appends new task to self.tasks
|
129
|
+
- Saves checkpoint to disk
|
130
|
+
|
131
|
+
Example:
|
132
|
+
```python
|
133
|
+
engine = TaskEngine()
|
134
|
+
engine.create_new_task() # Interactively creates a new root task
|
135
|
+
```
|
136
|
+
"""
|
80
137
|
self.tasks.append(build_root_task())
|
81
138
|
save_checkpoint(self.tasks)
|
82
139
|
|
83
140
|
def breakdown_complex_tasks(self) -> None:
|
84
|
-
"""
|
141
|
+
"""
|
142
|
+
Process all tasks and break down complex ones.
|
143
|
+
|
144
|
+
This is a core function that analyzes all tasks to identify those that are too complex
|
145
|
+
or have long durations, then interactively breaks them down into smaller subtasks.
|
146
|
+
It maintains the task hierarchy, updates parent-child relationships, and ensures
|
147
|
+
estimate consistency across the task tree.
|
148
|
+
|
149
|
+
The function uses a breadth-first approach to process tasks, breaking down parent tasks
|
150
|
+
before their children, and saving checkpoints after each breakdown.
|
151
|
+
|
152
|
+
Algorithm:
|
153
|
+
1. Initialize task depths to track hierarchy
|
154
|
+
2. Process each task and check if it needs breakdown
|
155
|
+
3. For tasks needing breakdown, use LLM to suggest subtasks
|
156
|
+
4. Add approved subtasks to the task list
|
157
|
+
5. Update parent estimates based on subtask characteristics
|
158
|
+
6. Save checkpoint after each task breakdown
|
159
|
+
|
160
|
+
Side Effects:
|
161
|
+
- Modifies self.tasks by adding subtasks
|
162
|
+
- Updates self.task_depths with new depth information
|
163
|
+
- Saves checkpoints to disk after each breakdown
|
164
|
+
|
165
|
+
Example:
|
166
|
+
```python
|
167
|
+
engine = TaskEngine()
|
168
|
+
engine.load_from_file("my_project.json")
|
169
|
+
engine.breakdown_complex_tasks() # Interactively breaks down complex tasks
|
170
|
+
```
|
171
|
+
"""
|
85
172
|
# Initialize depth tracking
|
86
173
|
self.task_depths = _initialize_task_depths(self.tasks)
|
87
174
|
|
@@ -127,7 +214,28 @@ class TaskEngine:
|
|
127
214
|
save_checkpoint(self.tasks)
|
128
215
|
|
129
216
|
def handle_long_duration_tasks(self) -> None:
|
130
|
-
"""
|
217
|
+
"""
|
218
|
+
Find and handle long duration leaf tasks.
|
219
|
+
|
220
|
+
Identifies leaf tasks (tasks with no children) that have long duration estimates,
|
221
|
+
presents them to the user, and offers the opportunity to break them down further.
|
222
|
+
This helps ensure that all tasks in the final plan are of manageable size.
|
223
|
+
|
224
|
+
The function uses task_analysis.find_long_duration_leaf_tasks to identify candidates
|
225
|
+
for further breakdown, then interactively processes user-selected tasks.
|
226
|
+
|
227
|
+
Side Effects:
|
228
|
+
- May modify self.tasks by adding subtasks for long-duration leaf tasks
|
229
|
+
- Updates parent estimates via check_and_update_parent_estimates
|
230
|
+
- Saves checkpoints to disk after each breakdown
|
231
|
+
|
232
|
+
Example:
|
233
|
+
```python
|
234
|
+
engine = TaskEngine()
|
235
|
+
engine.load_from_file("my_project.json")
|
236
|
+
engine.handle_long_duration_tasks() # Interactively processes long-duration tasks
|
237
|
+
```
|
238
|
+
"""
|
131
239
|
# Find long duration leaf tasks
|
132
240
|
long_duration_leaf_tasks = find_long_duration_leaf_tasks(self.tasks)
|
133
241
|
|
@@ -172,7 +280,27 @@ class TaskEngine:
|
|
172
280
|
print("Invalid selection, skipping breakdown.")
|
173
281
|
|
174
282
|
def finalize_plan(self) -> None:
|
175
|
-
"""
|
283
|
+
"""
|
284
|
+
Review and save the final plan.
|
285
|
+
|
286
|
+
Performs a final consistency check on all task estimates, displays a summary
|
287
|
+
of the entire task hierarchy to the user, and prompts for saving the finalized
|
288
|
+
plan to a permanent file. If saved, removes the temporary checkpoint file.
|
289
|
+
|
290
|
+
Side Effects:
|
291
|
+
- Ensures consistency in parent-child estimate relationships
|
292
|
+
- Saves final plan to user-specified file
|
293
|
+
- May delete checkpoint file if plan is saved
|
294
|
+
|
295
|
+
Example:
|
296
|
+
```python
|
297
|
+
engine = TaskEngine()
|
298
|
+
engine.load_from_file("my_project.json")
|
299
|
+
engine.breakdown_complex_tasks()
|
300
|
+
engine.handle_long_duration_tasks()
|
301
|
+
engine.finalize_plan() # Review and save final plan
|
302
|
+
```
|
303
|
+
"""
|
176
304
|
# Final check of parent-child estimate consistency
|
177
305
|
self.tasks = check_and_update_parent_estimates(self.tasks)
|
178
306
|
|
@@ -187,7 +315,30 @@ class TaskEngine:
|
|
187
315
|
print(f"Plan left in checkpoint '{CHECKPOINT_FILE}'. Run again to resume.")
|
188
316
|
|
189
317
|
def run(self) -> None:
|
190
|
-
"""
|
318
|
+
"""
|
319
|
+
Run the full interactive workflow.
|
320
|
+
|
321
|
+
Executes the complete scopemate workflow from start to finish:
|
322
|
+
1. Displays introduction to the user
|
323
|
+
2. Loads existing checkpoint or creates new task
|
324
|
+
3. Processes and breaks down complex tasks
|
325
|
+
4. Handles long-duration leaf tasks
|
326
|
+
5. Finalizes and saves the plan
|
327
|
+
|
328
|
+
This is the main entry point for using the TaskEngine to build a complete
|
329
|
+
task breakdown plan interactively.
|
330
|
+
|
331
|
+
Side Effects:
|
332
|
+
- Interacts with the user via console
|
333
|
+
- Modifies task list based on user input
|
334
|
+
- Creates/updates files on disk for checkpoints and final plan
|
335
|
+
|
336
|
+
Example:
|
337
|
+
```python
|
338
|
+
engine = TaskEngine()
|
339
|
+
engine.run() # Runs the complete interactive workflow
|
340
|
+
```
|
341
|
+
"""
|
191
342
|
# Display introduction
|
192
343
|
print("=== scopemate Action Plan Builder ===")
|
193
344
|
print("This tool helps break down complex tasks and maintain consistent time estimates.")
|
@@ -210,13 +361,40 @@ class TaskEngine:
|
|
210
361
|
self.finalize_plan()
|
211
362
|
|
212
363
|
def run_interactive(self) -> None:
|
213
|
-
"""
|
364
|
+
"""
|
365
|
+
Run the interactive mode of the application.
|
366
|
+
|
367
|
+
This is a placeholder method for running an alternative interactive mode.
|
368
|
+
Currently just prints the header and would be extended in future versions.
|
369
|
+
|
370
|
+
Example:
|
371
|
+
```python
|
372
|
+
engine = TaskEngine()
|
373
|
+
engine.run_interactive() # Would run an alternative interactive mode
|
374
|
+
```
|
375
|
+
"""
|
214
376
|
print("=== scopemate Action Plan Builder ===")
|
215
377
|
|
216
378
|
|
217
379
|
def interactive_builder():
|
218
380
|
"""
|
219
381
|
Legacy function for backward compatibility that runs the TaskEngine.
|
382
|
+
|
383
|
+
Creates a TaskEngine instance and runs the full workflow, handling
|
384
|
+
KeyboardInterrupt exceptions by saving progress to a checkpoint.
|
385
|
+
|
386
|
+
This function provides backward compatibility with older versions
|
387
|
+
that used this entry point directly.
|
388
|
+
|
389
|
+
Side Effects:
|
390
|
+
- Creates and runs a TaskEngine instance
|
391
|
+
- Handles KeyboardInterrupt by saving checkpoint
|
392
|
+
|
393
|
+
Example:
|
394
|
+
```python
|
395
|
+
from scopemate.engine import interactive_builder
|
396
|
+
interactive_builder() # Runs the complete workflow
|
397
|
+
```
|
220
398
|
"""
|
221
399
|
engine = TaskEngine()
|
222
400
|
try:
|
scopemate/interaction.py
CHANGED
@@ -25,13 +25,43 @@ def prompt_user(
|
|
25
25
|
"""
|
26
26
|
Prompt user for input with optional default and choices validation.
|
27
27
|
|
28
|
+
This function is the primary method for all user interaction in scopemate. It
|
29
|
+
handles displaying prompts, validating input against constraints, and providing
|
30
|
+
default values when the user presses Enter without typing anything.
|
31
|
+
|
32
|
+
The function implements a validation loop that ensures users can only provide
|
33
|
+
valid input. If choices are specified, the function performs case-insensitive
|
34
|
+
validation against those choices and re-prompts when input is invalid.
|
35
|
+
|
36
|
+
Features:
|
37
|
+
- Displays a prompt with an optional default value in square brackets
|
38
|
+
- Supports empty input with a default fallback value
|
39
|
+
- Validates input against a predefined set of case-insensitive choices
|
40
|
+
- Provides clear error messages when input doesn't match required choices
|
41
|
+
- Loops until valid input is received
|
42
|
+
|
28
43
|
Args:
|
29
|
-
prompt: The prompt text to display
|
30
|
-
default
|
31
|
-
choices
|
44
|
+
prompt (str): The prompt text to display to the user
|
45
|
+
default (Optional[str]): Default value to use if user submits empty input
|
46
|
+
choices (Optional[List[str]]): List of valid input choices for validation
|
32
47
|
|
33
48
|
Returns:
|
34
|
-
|
49
|
+
str: The validated user input
|
50
|
+
|
51
|
+
Example:
|
52
|
+
```python
|
53
|
+
# Simple prompt with no constraints
|
54
|
+
name = prompt_user("Enter your name")
|
55
|
+
|
56
|
+
# Prompt with a default value
|
57
|
+
team = prompt_user("Select team", default="Backend")
|
58
|
+
|
59
|
+
# Prompt with choices validation
|
60
|
+
response = prompt_user("Continue?", default="y", choices=["y", "n"])
|
61
|
+
if response.lower() == "y":
|
62
|
+
# Proceed with action
|
63
|
+
pass
|
64
|
+
```
|
35
65
|
"""
|
36
66
|
while True:
|
37
67
|
suffix = f" [{default}]" if default is not None else ""
|