scopemate 0.1.0__py3-none-any.whl → 0.1.2__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 +36 -3
- scopemate/models.py +63 -1
- scopemate/storage.py +218 -4
- scopemate/task_analysis.py +47 -6
- {scopemate-0.1.0.dist-info → scopemate-0.1.2.dist-info}/METADATA +32 -5
- scopemate-0.1.2.dist-info/RECORD +17 -0
- scopemate-0.1.0.dist-info/RECORD +0 -17
- {scopemate-0.1.0.dist-info → scopemate-0.1.2.dist-info}/WHEEL +0 -0
- {scopemate-0.1.0.dist-info → scopemate-0.1.2.dist-info}/entry_points.txt +0 -0
- {scopemate-0.1.0.dist-info → scopemate-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {scopemate-0.1.0.dist-info → scopemate-0.1.2.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 ""
|
scopemate/llm.py
CHANGED
@@ -26,12 +26,45 @@ def call_llm(prompt: str, model: str = DEFAULT_MODEL) -> dict:
|
|
26
26
|
"""
|
27
27
|
Invoke LLM to get a structured JSON response.
|
28
28
|
|
29
|
+
This function is the core LLM integration point for scopemate, handling all
|
30
|
+
communication with the OpenAI API. It's designed to always return structured
|
31
|
+
JSON data that can be easily processed by the application.
|
32
|
+
|
33
|
+
The function:
|
34
|
+
1. Creates an OpenAI client using the default API credentials
|
35
|
+
2. Configures a system prompt that instructs the model to return valid JSON
|
36
|
+
3. Sends the user's prompt with the task-specific instructions
|
37
|
+
4. Sets response_format to force JSON output
|
38
|
+
5. Parses and returns the JSON response
|
39
|
+
|
40
|
+
Error handling is built in to gracefully handle JSON parsing failures by
|
41
|
+
printing diagnostic information and returning an empty dictionary rather
|
42
|
+
than crashing.
|
43
|
+
|
29
44
|
Args:
|
30
|
-
prompt: The prompt to send to the LLM
|
31
|
-
|
45
|
+
prompt (str): The prompt to send to the LLM, containing full instructions
|
46
|
+
and any task data needed for context
|
47
|
+
model (str): The OpenAI model identifier to use (defaults to DEFAULT_MODEL)
|
32
48
|
|
33
49
|
Returns:
|
34
|
-
A dictionary containing the parsed JSON response
|
50
|
+
dict: A dictionary containing the parsed JSON response from the LLM.
|
51
|
+
Returns an empty dict {} if parsing fails.
|
52
|
+
|
53
|
+
Example:
|
54
|
+
```python
|
55
|
+
# Create a prompt asking for task breakdown
|
56
|
+
prompt = f"Break down this task into subtasks: {task.title}"
|
57
|
+
|
58
|
+
# Call the LLM and get structured data back
|
59
|
+
response = call_llm(prompt)
|
60
|
+
|
61
|
+
# Process the structured response
|
62
|
+
if "subtasks" in response:
|
63
|
+
for subtask_data in response["subtasks"]:
|
64
|
+
# Create a new subtask from the data
|
65
|
+
subtask = ScopeMateTask(**subtask_data)
|
66
|
+
tasks.append(subtask)
|
67
|
+
```
|
35
68
|
"""
|
36
69
|
client = OpenAI()
|
37
70
|
response = client.chat.completions.create(
|
scopemate/models.py
CHANGED
@@ -144,7 +144,69 @@ class Meta(BaseModel):
|
|
144
144
|
|
145
145
|
|
146
146
|
class ScopeMateTask(BaseModel):
|
147
|
-
"""
|
147
|
+
"""
|
148
|
+
A Purpose/Context/Outcome task representing a unit of work.
|
149
|
+
|
150
|
+
ScopeMateTask is the core data model in scopemate, representing a single unit of work
|
151
|
+
with well-defined purpose, scope, and outcome. The model follows a comprehensive and
|
152
|
+
structured approach to task definition that ensures clarity in task planning and execution.
|
153
|
+
|
154
|
+
Each task has:
|
155
|
+
1. Purpose - the "why" behind the task (detailed_description, alignment, urgency)
|
156
|
+
2. Scope - the "how big" and "what's involved" (size, time_estimate, dependencies, risks)
|
157
|
+
3. Outcome - the "what will be delivered" (type, definition, acceptance criteria, metrics)
|
158
|
+
4. Meta - tracking information (status, priority, dates, confidence, team)
|
159
|
+
|
160
|
+
Tasks can form a hierarchical structure through the parent_id field, allowing complex
|
161
|
+
work to be broken down into manageable subtasks. The hierarchy supports:
|
162
|
+
- Parent tasks: higher-level tasks that can be decomposed
|
163
|
+
- Child tasks: more specific tasks that contribute to a parent
|
164
|
+
- Root tasks: top-level tasks with no parent
|
165
|
+
- Leaf tasks: tasks with no children
|
166
|
+
|
167
|
+
The model enforces validation rules through Pydantic, ensuring data integrity
|
168
|
+
across all fields (e.g., valid size values, time estimates, status, etc.).
|
169
|
+
|
170
|
+
Attributes:
|
171
|
+
id (str): Unique identifier for the task
|
172
|
+
title (str): Short descriptive title
|
173
|
+
purpose (Purpose): Why the task matters
|
174
|
+
scope (Scope): Size, time, dependencies and risks
|
175
|
+
outcome (Outcome): Delivered value and validation methods
|
176
|
+
meta (Meta): Status, timing, and tracking information
|
177
|
+
parent_id (Optional[str]): ID of parent task if this is a subtask
|
178
|
+
|
179
|
+
Example:
|
180
|
+
```python
|
181
|
+
task = ScopeMateTask(
|
182
|
+
id="TASK-abc123",
|
183
|
+
title="Implement user authentication",
|
184
|
+
purpose=Purpose(
|
185
|
+
detailed_description="We need secure authentication for users",
|
186
|
+
alignment=["Security", "User experience"],
|
187
|
+
urgency="strategic"
|
188
|
+
),
|
189
|
+
scope=Scope(
|
190
|
+
size="complex",
|
191
|
+
time_estimate="sprint",
|
192
|
+
dependencies=["API design", "Database setup"],
|
193
|
+
risks=["Security vulnerabilities", "Performance issues"]
|
194
|
+
),
|
195
|
+
outcome=Outcome(
|
196
|
+
type="customer-facing",
|
197
|
+
detailed_outcome_definition="Complete authentication system with login/logout",
|
198
|
+
acceptance_criteria=["User can log in", "User can log out", "Password reset works"]
|
199
|
+
),
|
200
|
+
meta=Meta(
|
201
|
+
status="backlog",
|
202
|
+
priority=1,
|
203
|
+
created=get_utc_now(),
|
204
|
+
updated=get_utc_now(),
|
205
|
+
team="Backend"
|
206
|
+
)
|
207
|
+
)
|
208
|
+
```
|
209
|
+
"""
|
148
210
|
id: str
|
149
211
|
title: str = Field(..., description="Short descriptive title")
|
150
212
|
purpose: Purpose
|
scopemate/storage.py
CHANGED
@@ -6,6 +6,7 @@ This module manages persistence of task data to disk and loading from files.
|
|
6
6
|
"""
|
7
7
|
import os
|
8
8
|
import json
|
9
|
+
from datetime import datetime
|
9
10
|
from typing import List, Dict, Any
|
10
11
|
|
11
12
|
from pydantic import ValidationError
|
@@ -37,28 +38,241 @@ def save_plan(tasks: List[ScopeMateTask], filename: str) -> None:
|
|
37
38
|
"""
|
38
39
|
Save tasks to a plan file.
|
39
40
|
|
41
|
+
This function serializes a list of ScopeMateTask objects to JSON and writes them
|
42
|
+
to a file. The file format uses a consistent structure with a top-level "tasks"
|
43
|
+
array containing serialized task objects. This ensures compatibility with other
|
44
|
+
tooling and future versions of scopemate.
|
45
|
+
|
46
|
+
The function handles all serialization details including proper encoding and
|
47
|
+
indentation for readability. Each task is completely serialized with all its
|
48
|
+
nested structures (purpose, scope, outcome, meta) for complete persistence.
|
49
|
+
|
40
50
|
Args:
|
41
|
-
tasks: List of ScopeMateTask objects to save
|
51
|
+
tasks: List of ScopeMateTask objects to save to disk
|
42
52
|
filename: Path to save the plan file
|
53
|
+
|
54
|
+
Side Effects:
|
55
|
+
- Writes to file system at the specified path
|
56
|
+
- Prints confirmation message upon successful save
|
57
|
+
|
58
|
+
Example:
|
59
|
+
```python
|
60
|
+
tasks = [task1, task2, task3] # List of ScopeMateTask objects
|
61
|
+
save_plan(tasks, "project_alpha_plan.json")
|
62
|
+
# Saves all tasks to project_alpha_plan.json with proper formatting
|
63
|
+
```
|
43
64
|
"""
|
44
65
|
payload = {"tasks": [t.model_dump() for t in tasks]}
|
45
66
|
with open(filename, "w", encoding="utf-8") as f:
|
46
67
|
json.dump(payload, f, indent=2)
|
47
68
|
print(f"✅ Plan saved to {filename}.")
|
69
|
+
|
70
|
+
# Automatically generate markdown version with the same basename
|
71
|
+
md_filename = os.path.splitext(filename)[0] + ".md"
|
72
|
+
save_markdown_plan(payload, md_filename)
|
73
|
+
|
74
|
+
|
75
|
+
def save_markdown_plan(data: Dict[str, Any], filename: str) -> None:
|
76
|
+
"""
|
77
|
+
Save tasks to a Markdown file for human readability.
|
78
|
+
|
79
|
+
This function converts the JSON task data into a well-structured Markdown format
|
80
|
+
for easier reading and sharing with team members who may not use scopemate directly.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
data: Dictionary containing the tasks data (with "tasks" key)
|
84
|
+
filename: Path to save the Markdown file
|
85
|
+
"""
|
86
|
+
markdown = generate_markdown_from_json(data)
|
87
|
+
with open(filename, "w", encoding="utf-8") as f:
|
88
|
+
f.write(markdown)
|
89
|
+
print(f"✅ Markdown version saved to {filename}.")
|
90
|
+
|
91
|
+
|
92
|
+
def generate_markdown_from_json(data: Dict[str, Any]) -> str:
|
93
|
+
"""
|
94
|
+
Convert scopemate JSON data to a well-structured Markdown format.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
data: The scopemate JSON data as a dictionary
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
A string containing the Markdown representation
|
101
|
+
"""
|
102
|
+
# Start building markdown content
|
103
|
+
md = ["# Project Scope Plan\n"]
|
104
|
+
md.append(f"*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
|
105
|
+
|
106
|
+
# Add summary section
|
107
|
+
tasks = data.get("tasks", [])
|
108
|
+
md.append(f"## Summary\n\n")
|
109
|
+
md.append(f"This document contains **{len(tasks)}** tasks.\n\n")
|
110
|
+
|
111
|
+
# Get counts by size complexity
|
112
|
+
size_counts = {}
|
113
|
+
for task in tasks:
|
114
|
+
if "scope" in task and "size" in task["scope"]:
|
115
|
+
size = task["scope"]["size"]
|
116
|
+
size_counts[size] = size_counts.get(size, 0) + 1
|
117
|
+
|
118
|
+
if size_counts:
|
119
|
+
md.append("**Complexity Breakdown:**\n\n")
|
120
|
+
for size, count in size_counts.items():
|
121
|
+
md.append(f"- {size.capitalize()}: {count} task(s)\n")
|
122
|
+
md.append("\n")
|
123
|
+
|
124
|
+
# Create hierarchical task structure
|
125
|
+
main_tasks = [t for t in tasks if not t.get("parent_id")]
|
126
|
+
child_tasks = {}
|
127
|
+
for task in tasks:
|
128
|
+
if task.get("parent_id"):
|
129
|
+
if task["parent_id"] not in child_tasks:
|
130
|
+
child_tasks[task["parent_id"]] = []
|
131
|
+
child_tasks[task["parent_id"]].append(task)
|
132
|
+
|
133
|
+
# Add detailed task section
|
134
|
+
md.append("## Task Details\n\n")
|
135
|
+
|
136
|
+
# Process main tasks with their children
|
137
|
+
for task in main_tasks:
|
138
|
+
md.extend(format_task_as_markdown(task, child_tasks, 0))
|
139
|
+
|
140
|
+
return "\n".join(md)
|
141
|
+
|
142
|
+
|
143
|
+
def format_task_as_markdown(task: Dict[str, Any], child_tasks: Dict[str, List[Dict[str, Any]]], level: int) -> List[str]:
|
144
|
+
"""
|
145
|
+
Format a single task and its children as Markdown.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
task: The task data
|
149
|
+
child_tasks: Dictionary mapping parent_id to list of child tasks
|
150
|
+
level: Current indentation level
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
List of markdown formatted lines
|
154
|
+
"""
|
155
|
+
md_lines = []
|
156
|
+
|
157
|
+
# Add task title with appropriate heading level
|
158
|
+
heading_level = "###" + "#" * level
|
159
|
+
task_id = task.get("id", "NO-ID")
|
160
|
+
title = task.get("title", "Untitled Task")
|
161
|
+
md_lines.append(f"{heading_level} {task_id}: {title}\n")
|
162
|
+
|
163
|
+
# Add purpose section
|
164
|
+
if "purpose" in task:
|
165
|
+
purpose = task["purpose"]
|
166
|
+
md_lines.append("**Purpose:**\n\n")
|
167
|
+
if "detailed_description" in purpose:
|
168
|
+
md_lines.append(f"{purpose['detailed_description']}\n\n")
|
169
|
+
if "alignment" in purpose and purpose["alignment"]:
|
170
|
+
md_lines.append("*Strategic Alignment:* ")
|
171
|
+
md_lines.append(", ".join(purpose["alignment"]))
|
172
|
+
md_lines.append("\n\n")
|
173
|
+
if "urgency" in purpose:
|
174
|
+
md_lines.append(f"*Urgency:* {purpose['urgency'].capitalize()}\n\n")
|
175
|
+
|
176
|
+
# Add scope section
|
177
|
+
if "scope" in task:
|
178
|
+
scope = task["scope"]
|
179
|
+
md_lines.append("**Scope:**\n\n")
|
180
|
+
if "size" in scope:
|
181
|
+
md_lines.append(f"*Size:* {scope['size'].capitalize()}\n\n")
|
182
|
+
if "time_estimate" in scope:
|
183
|
+
md_lines.append(f"*Time Estimate:* {scope['time_estimate'].capitalize()}\n\n")
|
184
|
+
if "dependencies" in scope and scope["dependencies"]:
|
185
|
+
md_lines.append("*Dependencies:*\n\n")
|
186
|
+
for dep in scope["dependencies"]:
|
187
|
+
md_lines.append(f"- {dep}\n")
|
188
|
+
md_lines.append("\n")
|
189
|
+
if "risks" in scope and scope["risks"]:
|
190
|
+
md_lines.append("*Risks:*\n\n")
|
191
|
+
for risk in scope["risks"]:
|
192
|
+
md_lines.append(f"- {risk}\n")
|
193
|
+
md_lines.append("\n")
|
194
|
+
|
195
|
+
# Add outcome section
|
196
|
+
if "outcome" in task:
|
197
|
+
outcome = task["outcome"]
|
198
|
+
md_lines.append("**Outcome:**\n\n")
|
199
|
+
if "type" in outcome:
|
200
|
+
md_lines.append(f"*Type:* {outcome['type'].capitalize().replace('-', ' ')}\n\n")
|
201
|
+
if "detailed_outcome_definition" in outcome:
|
202
|
+
md_lines.append(f"{outcome['detailed_outcome_definition']}\n\n")
|
203
|
+
if "acceptance_criteria" in outcome and outcome["acceptance_criteria"]:
|
204
|
+
md_lines.append("*Acceptance Criteria:*\n\n")
|
205
|
+
for ac in outcome["acceptance_criteria"]:
|
206
|
+
md_lines.append(f"- {ac}\n")
|
207
|
+
md_lines.append("\n")
|
208
|
+
if "metric" in outcome and outcome["metric"]:
|
209
|
+
md_lines.append(f"*Success Metric:* {outcome['metric']}\n\n")
|
210
|
+
if "validation_method" in outcome and outcome["validation_method"]:
|
211
|
+
md_lines.append(f"*Validation Method:* {outcome['validation_method']}\n\n")
|
212
|
+
|
213
|
+
# Add meta section
|
214
|
+
if "meta" in task:
|
215
|
+
meta = task["meta"]
|
216
|
+
md_lines.append("**Meta:**\n\n")
|
217
|
+
if "status" in meta:
|
218
|
+
md_lines.append(f"*Status:* {meta['status'].capitalize()}\n")
|
219
|
+
if "priority" in meta and meta["priority"] is not None:
|
220
|
+
md_lines.append(f"*Priority:* {meta['priority']}\n")
|
221
|
+
if "confidence" in meta:
|
222
|
+
md_lines.append(f"*Confidence:* {meta['confidence'].capitalize()}\n")
|
223
|
+
if "team" in meta and meta["team"]:
|
224
|
+
md_lines.append(f"*Team:* {meta['team']}\n")
|
225
|
+
md_lines.append("\n")
|
226
|
+
|
227
|
+
# Add separator line if not the last task
|
228
|
+
md_lines.append("---\n\n")
|
229
|
+
|
230
|
+
# Process children recursively
|
231
|
+
if task.get("id") in child_tasks:
|
232
|
+
for child in child_tasks[task["id"]]:
|
233
|
+
md_lines.extend(format_task_as_markdown(child, child_tasks, level + 1))
|
234
|
+
|
235
|
+
return md_lines
|
48
236
|
|
49
237
|
|
50
238
|
def load_plan(filename: str) -> List[ScopeMateTask]:
|
51
239
|
"""
|
52
240
|
Load tasks from a plan file.
|
53
241
|
|
242
|
+
This function reads a JSON file containing serialized tasks and deserializes them
|
243
|
+
into ScopeMateTask objects. It handles various backward compatibility issues and
|
244
|
+
performs validation on the loaded data to ensure integrity.
|
245
|
+
|
246
|
+
The function is robust against various common issues:
|
247
|
+
- It properly handles missing parent_id fields for backward compatibility
|
248
|
+
- It removes legacy fields that may exist in older files
|
249
|
+
- It skips invalid tasks with validation errors rather than failing entirely
|
250
|
+
- It provides clear warnings about skipped tasks
|
251
|
+
|
54
252
|
Args:
|
55
|
-
filename: Path to the plan file
|
253
|
+
filename: Path to the plan file to load
|
56
254
|
|
57
255
|
Returns:
|
58
|
-
List of ScopeMateTask objects
|
256
|
+
List of validated ScopeMateTask objects from the file
|
59
257
|
|
60
258
|
Raises:
|
61
|
-
FileNotFoundError: If the file doesn't exist
|
259
|
+
FileNotFoundError: If the specified file doesn't exist
|
260
|
+
|
261
|
+
Example:
|
262
|
+
```python
|
263
|
+
try:
|
264
|
+
tasks = load_plan("project_alpha_plan.json")
|
265
|
+
print(f"Loaded {len(tasks)} tasks successfully")
|
266
|
+
|
267
|
+
# Process loaded tasks
|
268
|
+
for task in tasks:
|
269
|
+
if task.meta.status == "backlog":
|
270
|
+
# Do something with backlog tasks...
|
271
|
+
pass
|
272
|
+
except FileNotFoundError:
|
273
|
+
print("Plan file not found, starting with empty task list")
|
274
|
+
tasks = []
|
275
|
+
```
|
62
276
|
"""
|
63
277
|
if not os.path.exists(filename):
|
64
278
|
raise FileNotFoundError(f"File not found: {filename}")
|
scopemate/task_analysis.py
CHANGED
@@ -15,11 +15,30 @@ def check_and_update_parent_estimates(tasks: List[ScopeMateTask]) -> List[ScopeM
|
|
15
15
|
"""
|
16
16
|
Check and update parent task estimates based on child task complexity.
|
17
17
|
|
18
|
+
This function ensures consistency in the task hierarchy by making sure that
|
19
|
+
parent tasks have appropriate size and time estimates relative to their children.
|
20
|
+
If a child task has a higher complexity or longer time estimate than its parent,
|
21
|
+
the parent's estimates are automatically increased to maintain logical consistency.
|
22
|
+
|
23
|
+
The function works by:
|
24
|
+
1. Creating maps of task IDs to task objects and parent IDs
|
25
|
+
2. Computing complexity values for all tasks based on their size and time estimates
|
26
|
+
3. Identifying inconsistencies where child tasks have higher complexity than parents
|
27
|
+
4. Updating parent estimates to match or exceed their children's complexity
|
28
|
+
5. Recursively propagating updates up the task hierarchy to maintain consistency
|
29
|
+
|
18
30
|
Args:
|
19
|
-
tasks: List of ScopeMateTask objects
|
31
|
+
tasks: List of ScopeMateTask objects to analyze and update
|
20
32
|
|
21
33
|
Returns:
|
22
|
-
Updated list of ScopeMateTask objects
|
34
|
+
Updated list of ScopeMateTask objects with consistent parent-child estimates
|
35
|
+
|
36
|
+
Example:
|
37
|
+
```python
|
38
|
+
# Before: parent task has "straightforward" size but child has "complex" size
|
39
|
+
updated_tasks = check_and_update_parent_estimates(tasks)
|
40
|
+
# After: parent task is updated to "complex" or higher to maintain consistency
|
41
|
+
```
|
23
42
|
"""
|
24
43
|
# Create a map of tasks by ID for easy access
|
25
44
|
task_map = {t.id: t for t in tasks}
|
@@ -229,14 +248,36 @@ def should_decompose_task(task: ScopeMateTask, depth: int, max_depth: int, is_le
|
|
229
248
|
"""
|
230
249
|
Determine if a task should be broken down based on complexity and time estimates.
|
231
250
|
|
251
|
+
This function applies a set of heuristics to decide whether a task needs further
|
252
|
+
decomposition. The decision is based on multiple factors:
|
253
|
+
|
254
|
+
1. Task depth in the hierarchy - tasks at or beyond max_depth are never decomposed
|
255
|
+
2. Task complexity - "complex", "uncertain", or "pioneering" tasks should be broken down
|
256
|
+
3. Time estimate - tasks with long durations should be broken down into smaller units
|
257
|
+
4. Leaf status - whether the task already has subtasks
|
258
|
+
|
259
|
+
The breakdown logic implements a graduated approach where:
|
260
|
+
- Very complex tasks are always broken down (unless at max depth)
|
261
|
+
- Long duration tasks are broken down, especially if they're leaf tasks
|
262
|
+
- Tasks with "week" duration are broken down up to max_depth
|
263
|
+
- Tasks at depth 2+ with "sprint" duration aren't broken down (unless they're "multi-sprint")
|
264
|
+
|
232
265
|
Args:
|
233
266
|
task: The ScopeMateTask to evaluate
|
234
|
-
depth: Current depth in the task hierarchy
|
235
|
-
max_depth: Maximum allowed depth
|
236
|
-
is_leaf: Whether this
|
267
|
+
depth: Current depth in the task hierarchy (0 for root tasks)
|
268
|
+
max_depth: Maximum allowed depth for the task hierarchy
|
269
|
+
is_leaf: Whether this task currently has no children
|
237
270
|
|
238
271
|
Returns:
|
239
|
-
True if the task should be broken down, False otherwise
|
272
|
+
True if the task should be broken down into subtasks, False otherwise
|
273
|
+
|
274
|
+
Example:
|
275
|
+
```python
|
276
|
+
task = get_task_by_id("TASK-123")
|
277
|
+
depth = get_task_depth(task, task_depths, tasks)
|
278
|
+
if should_decompose_task(task, depth, max_depth=5, is_leaf=True):
|
279
|
+
subtasks = suggest_breakdown(task)
|
280
|
+
```
|
240
281
|
"""
|
241
282
|
# Always respect max depth limit
|
242
283
|
if depth >= max_depth:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: scopemate
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: 🪜 A CLI tool for Purpose/Scope/Outcome planning
|
5
5
|
Author: Anoop Thomas Mathew
|
6
6
|
Author-email: Anoop Thomas Mathew <atmb4u@gmail.com>
|
@@ -78,6 +78,7 @@ Scopemate is built around a three-part framework for strategic decision making:
|
|
78
78
|
- Pydantic validation
|
79
79
|
- Cross-platform support (Windows, macOS, Linux)
|
80
80
|
- Works with Python 3.10 and above
|
81
|
+
- Automatic Markdown export of plans for easy sharing
|
81
82
|
|
82
83
|
## Requirements
|
83
84
|
|
@@ -171,11 +172,10 @@ scopemate --help
|
|
171
172
|
|
172
173
|
# Generate a project plan with purpose and outcome
|
173
174
|
scopemate --purpose="Build a REST API for user management" --outcome="A documented API with authentication and user CRUD operations" --output="project_plan.json"
|
174
|
-
|
175
|
-
# Fix inconsistent estimates in an existing plan
|
176
|
-
scopemate --fix-estimates --input="project_plan.json" --output="fixed_plan.json"
|
177
175
|
```
|
178
176
|
|
177
|
+
**Note:** A Markdown version of the output is automatically generated alongside the JSON file. For example, if you specify `--output="project_plan.json"`, a file named `project_plan.md` will also be created.
|
178
|
+
|
179
179
|
### Interactive Mode Workflow
|
180
180
|
|
181
181
|
The interactive mode (`scopemate --interactive`) will guide you through:
|
@@ -205,7 +205,9 @@ The interactive mode (`scopemate --interactive`) will guide you through:
|
|
205
205
|
|
206
206
|
### Output Format
|
207
207
|
|
208
|
-
scopemate generates
|
208
|
+
scopemate generates both JSON and Markdown output files:
|
209
|
+
|
210
|
+
1. **JSON Output** - Structured data format with the following structure:
|
209
211
|
|
210
212
|
```json
|
211
213
|
{
|
@@ -245,6 +247,14 @@ scopemate generates a structured JSON output with the following format:
|
|
245
247
|
}
|
246
248
|
```
|
247
249
|
|
250
|
+
2. **Markdown Output** - Human-readable format automatically generated with the same basename as the JSON file. The Markdown output includes:
|
251
|
+
- A summary of the plan with task counts and complexity breakdown
|
252
|
+
- Hierarchical task structure preserving parent-child relationships
|
253
|
+
- All relevant task details formatted for easy reading
|
254
|
+
- Properly formatted sections for purpose, scope, outcome, and metadata
|
255
|
+
|
256
|
+
This dual output approach makes it easy to both process the data programmatically (using the JSON) and share the plan with team members (using the Markdown).
|
257
|
+
|
248
258
|
### Integrating with Other Tools
|
249
259
|
|
250
260
|
You can use scopemate's JSON output with other project management tools:
|
@@ -295,6 +305,23 @@ uv pip install -r requirements-dev.txt
|
|
295
305
|
uv pip install -e .
|
296
306
|
```
|
297
307
|
|
308
|
+
#### Using pipx
|
309
|
+
|
310
|
+
[pipx](https://github.com/pypa/pipx) is useful for installing and running Python applications in isolated environments:
|
311
|
+
|
312
|
+
```bash
|
313
|
+
# Install pipx if you don't have it
|
314
|
+
pip install pipx
|
315
|
+
|
316
|
+
# Clone the repository
|
317
|
+
git clone https://github.com/atmb4u/scopemate.git
|
318
|
+
cd scopemate
|
319
|
+
|
320
|
+
# Install the package in development mode with force flag
|
321
|
+
# This is useful when making changes and wanting to test the CLI immediately
|
322
|
+
pipx install --force .
|
323
|
+
```
|
324
|
+
|
298
325
|
### Running Tests
|
299
326
|
|
300
327
|
```bash
|
@@ -0,0 +1,17 @@
|
|
1
|
+
scopemate/__init__.py,sha256=sMbeuIqGXqrO47d1LD4gk6r5cLHuZmRtVXR797c8K2s,472
|
2
|
+
scopemate/__main__.py,sha256=nPNZe_QEoOHQ_hXf17w72BHz1UFPKuW2g3whTLwuM8E,195
|
3
|
+
scopemate/breakdown.py,sha256=mwIDzf7m2GHVkDrRmMyeS8v2pNd99U3vlcPCtOajvs0,21048
|
4
|
+
scopemate/cli.py,sha256=qh6iFleQc8Bld0iptyUgRm5Ga3GtZsvE6Y-q6vbm3dk,6891
|
5
|
+
scopemate/core.py,sha256=wpXCpb5Kdpqul9edNCx2Da94137XCc1w-3KQc9-Tf3s,700
|
6
|
+
scopemate/engine.py,sha256=8yQoxSECJCGuNSIwS-qFoaOGM1iaZ-y4Lo7k5Q6v-mk,16992
|
7
|
+
scopemate/interaction.py,sha256=SeqQVME0MATK-m-M7T9nNHkcJ_VCRhlqydL_vQaSMWk,10893
|
8
|
+
scopemate/llm.py,sha256=k_1FQdg_TRt_51Yu_g_PjO0pBnmFmWHoK_-KEPGlASM,15526
|
9
|
+
scopemate/models.py,sha256=Q3SUoHu_4RejDAocEr83I00wGvxhDoJ1COVqjPsr4DQ,7738
|
10
|
+
scopemate/storage.py,sha256=uV1-7IdwJwBENeNoO9Y3WwUUXd-jA2NvKdiERGGhmV8,11642
|
11
|
+
scopemate/task_analysis.py,sha256=Mic0FOOy_BWI1_5TQmh__39miOcZBZ7mTUcCkv1DvkI,14967
|
12
|
+
scopemate-0.1.2.dist-info/licenses/LICENSE,sha256=4fqQFK5AkkXmg6FBG9Wr06gCR7BMQl02TvsPYt-YL6s,1076
|
13
|
+
scopemate-0.1.2.dist-info/METADATA,sha256=APn2Igy4cl3jhcP9gh6gwOHnBLMnN3CPsL4MrvDVabc,11774
|
14
|
+
scopemate-0.1.2.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
15
|
+
scopemate-0.1.2.dist-info/entry_points.txt,sha256=XXusGEDxI6NlrYmSBcPDtjV3QvsHWVWPSrt4zD4UcLg,49
|
16
|
+
scopemate-0.1.2.dist-info/top_level.txt,sha256=riMrI_jMCfZMb7-ecWBwqOBLdUsnPOxSu2Pgvqx7Too,10
|
17
|
+
scopemate-0.1.2.dist-info/RECORD,,
|
scopemate-0.1.0.dist-info/RECORD
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
scopemate/__init__.py,sha256=3cyRjjI2wC2pG8VeXONVOrow_dJMKUCzre8EtYfrV0s,472
|
2
|
-
scopemate/__main__.py,sha256=nPNZe_QEoOHQ_hXf17w72BHz1UFPKuW2g3whTLwuM8E,195
|
3
|
-
scopemate/breakdown.py,sha256=lasv2Gt1yziE_GV4DgCBWVg3TIPuggT3Tf77ny5n7Iw,19650
|
4
|
-
scopemate/cli.py,sha256=o05Od9NEP76sQRegc3dqs-d-DCpYip2KC1GXjriiCZQ,5059
|
5
|
-
scopemate/core.py,sha256=wpXCpb5Kdpqul9edNCx2Da94137XCc1w-3KQc9-Tf3s,700
|
6
|
-
scopemate/engine.py,sha256=wlE39lzKoJthi6twestlyEqvEjqNEXQYvNTOfT4aGZw,9521
|
7
|
-
scopemate/interaction.py,sha256=qWFU3QM_KPwaGdh4Rw7ewCIeYiT6Wa_H9e6bnmNoJzw,9531
|
8
|
-
scopemate/llm.py,sha256=hD37Mk54kchdECKYmCNF3yxg0U-vW-h4y8tpNghlS3Q,14031
|
9
|
-
scopemate/models.py,sha256=ZvFn8iegMDgCgoLjvWxj7_C7XLDWrgkF8ySuktfRaqw,4962
|
10
|
-
scopemate/storage.py,sha256=lloD_2f2E3q_inHLiL9Kp8F_tyeerG45_rSLKXvGh4Y,3102
|
11
|
-
scopemate/task_analysis.py,sha256=I-tH62MfYAwlHbLonjlPKBGa-X_II9QqpWS_OsjLaxU,12644
|
12
|
-
scopemate-0.1.0.dist-info/licenses/LICENSE,sha256=4fqQFK5AkkXmg6FBG9Wr06gCR7BMQl02TvsPYt-YL6s,1076
|
13
|
-
scopemate-0.1.0.dist-info/METADATA,sha256=Wmb1wOEmCkigLUXIS0d1BujGzV0uTpx9IpNIvyrtIJI,10557
|
14
|
-
scopemate-0.1.0.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
15
|
-
scopemate-0.1.0.dist-info/entry_points.txt,sha256=XXusGEDxI6NlrYmSBcPDtjV3QvsHWVWPSrt4zD4UcLg,49
|
16
|
-
scopemate-0.1.0.dist-info/top_level.txt,sha256=riMrI_jMCfZMb7-ecWBwqOBLdUsnPOxSu2Pgvqx7Too,10
|
17
|
-
scopemate-0.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|