specpulse 1.3.2__py3-none-any.whl → 1.3.4__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.
- specpulse/__init__.py +1 -1
- specpulse/cli/main.py +46 -9
- specpulse/core/specpulse.py +2 -2
- specpulse/resources/scripts/sp-pulse-init.ps1 +131 -0
- specpulse/resources/scripts/sp-pulse-plan.ps1 +143 -0
- specpulse/resources/scripts/sp-pulse-plan.sh +3 -14
- specpulse/resources/scripts/sp-pulse-spec.ps1 +126 -0
- specpulse/resources/scripts/sp-pulse-spec.sh +3 -14
- specpulse/resources/scripts/sp-pulse-task.ps1 +166 -0
- specpulse/resources/scripts/sp-pulse-task.sh +6 -18
- specpulse/utils/version_check.py +128 -0
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/METADATA +10 -8
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/RECORD +17 -12
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/WHEEL +0 -0
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/entry_points.txt +0 -0
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/licenses/LICENSE +0 -0
- {specpulse-1.3.2.dist-info → specpulse-1.3.4.dist-info}/top_level.txt +0 -0
specpulse/__init__.py
CHANGED
specpulse/cli/main.py
CHANGED
@@ -17,6 +17,7 @@ from ..core.specpulse import SpecPulse
|
|
17
17
|
from ..core.validator import Validator
|
18
18
|
from ..utils.console import Console
|
19
19
|
from ..utils.git_utils import GitUtils
|
20
|
+
from ..utils.version_check import check_pypi_version, compare_versions, get_update_message, should_check_version
|
20
21
|
|
21
22
|
|
22
23
|
class SpecPulseCLI:
|
@@ -24,7 +25,32 @@ class SpecPulseCLI:
|
|
24
25
|
self.console = Console(no_color=no_color, verbose=verbose)
|
25
26
|
self.specpulse = SpecPulse()
|
26
27
|
self.validator = Validator()
|
27
|
-
|
28
|
+
|
29
|
+
# Check for updates (non-blocking)
|
30
|
+
self._check_for_updates()
|
31
|
+
|
32
|
+
def _check_for_updates(self):
|
33
|
+
"""Check for available updates on PyPI"""
|
34
|
+
try:
|
35
|
+
if not should_check_version():
|
36
|
+
return
|
37
|
+
|
38
|
+
latest = check_pypi_version(timeout=1)
|
39
|
+
if latest:
|
40
|
+
current = __version__
|
41
|
+
is_outdated, is_major = compare_versions(current, latest)
|
42
|
+
|
43
|
+
if is_outdated:
|
44
|
+
message, color = get_update_message(current, latest, is_major)
|
45
|
+
# Only show for init command or when verbose
|
46
|
+
# Don't spam on every command
|
47
|
+
import sys
|
48
|
+
if len(sys.argv) > 1 and sys.argv[1] in ['init', '--version']:
|
49
|
+
self.console.info(message, style=color)
|
50
|
+
except:
|
51
|
+
# Never fail due to version check
|
52
|
+
pass
|
53
|
+
|
28
54
|
def init(self, project_name: Optional[str] = None,
|
29
55
|
here: bool = False,
|
30
56
|
ai: str = "claude",
|
@@ -67,10 +93,11 @@ class SpecPulseCLI:
|
|
67
93
|
".gemini/commands",
|
68
94
|
"memory",
|
69
95
|
"specs",
|
70
|
-
"plans",
|
96
|
+
"plans",
|
71
97
|
"tasks",
|
72
98
|
"scripts",
|
73
|
-
"templates"
|
99
|
+
"templates",
|
100
|
+
"templates/decomposition"
|
74
101
|
]
|
75
102
|
|
76
103
|
# Create directories with progress bar
|
@@ -198,7 +225,17 @@ class SpecPulseCLI:
|
|
198
225
|
task_template = self.specpulse.get_task_template()
|
199
226
|
with open(templates_dir / "task.md", 'w', encoding='utf-8') as f:
|
200
227
|
f.write(task_template)
|
201
|
-
|
228
|
+
|
229
|
+
# Copy decomposition templates
|
230
|
+
decomp_dir = templates_dir / "decomposition"
|
231
|
+
decomp_dir.mkdir(parents=True, exist_ok=True)
|
232
|
+
|
233
|
+
resources_decomp_dir = self.specpulse.resources_dir / "templates" / "decomposition"
|
234
|
+
if resources_decomp_dir.exists():
|
235
|
+
for template_file in resources_decomp_dir.iterdir():
|
236
|
+
if template_file.is_file():
|
237
|
+
shutil.copy2(template_file, decomp_dir / template_file.name)
|
238
|
+
|
202
239
|
self.console.success("Created templates")
|
203
240
|
|
204
241
|
def _create_memory_files(self, project_path: Path):
|
@@ -228,20 +265,20 @@ class SpecPulseCLI:
|
|
228
265
|
resources_scripts_dir = self.specpulse.resources_dir / "scripts"
|
229
266
|
|
230
267
|
# Copy all script files from resources
|
231
|
-
script_extensions = [".sh", ".py"]
|
268
|
+
script_extensions = [".sh", ".ps1", ".py"]
|
232
269
|
scripts_copied = 0
|
233
|
-
|
270
|
+
|
234
271
|
for script_file in resources_scripts_dir.iterdir():
|
235
272
|
if script_file.suffix in script_extensions:
|
236
273
|
dest_path = scripts_dir / script_file.name
|
237
274
|
shutil.copy2(script_file, dest_path)
|
238
|
-
|
275
|
+
|
239
276
|
# Make shell scripts executable
|
240
|
-
if script_file.suffix
|
277
|
+
if script_file.suffix in [".sh", ".ps1"]:
|
241
278
|
try:
|
242
279
|
os.chmod(dest_path, 0o755)
|
243
280
|
except:
|
244
|
-
pass # Windows may not support chmod
|
281
|
+
pass # Windows may not support chmod for .sh files
|
245
282
|
|
246
283
|
scripts_copied += 1
|
247
284
|
|
specpulse/core/specpulse.py
CHANGED
@@ -41,7 +41,7 @@ class SpecPulse:
|
|
41
41
|
|
42
42
|
def get_spec_template(self) -> str:
|
43
43
|
"""Get specification template from file"""
|
44
|
-
template_path = self.resources_dir / "templates" / "spec
|
44
|
+
template_path = self.resources_dir / "templates" / "spec.md"
|
45
45
|
if template_path.exists():
|
46
46
|
with open(template_path, 'r', encoding='utf-8') as f:
|
47
47
|
return f.read()
|
@@ -125,7 +125,7 @@ FR-001: [Requirement]
|
|
125
125
|
|
126
126
|
def get_plan_template(self) -> str:
|
127
127
|
"""Get implementation plan template from file"""
|
128
|
-
template_path = self.resources_dir / "templates" / "plan
|
128
|
+
template_path = self.resources_dir / "templates" / "plan.md"
|
129
129
|
if template_path.exists():
|
130
130
|
with open(template_path, 'r', encoding='utf-8') as f:
|
131
131
|
return f.read()
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# SpecPulse Feature Initialization - PowerShell Version
|
2
|
+
# Initialize a new feature with SpecPulse
|
3
|
+
|
4
|
+
param(
|
5
|
+
[Parameter(Mandatory=$true, Position=0)]
|
6
|
+
[string]$FeatureName,
|
7
|
+
|
8
|
+
[Parameter(Mandatory=$false, Position=1)]
|
9
|
+
[string]$CustomId
|
10
|
+
)
|
11
|
+
|
12
|
+
# Configuration
|
13
|
+
$ScriptName = Split-Path -Leaf $PSCommandPath
|
14
|
+
$ScriptDir = Split-Path -Parent $PSCommandPath
|
15
|
+
$ProjectRoot = Split-Path -Parent $ScriptDir
|
16
|
+
|
17
|
+
# Logging function
|
18
|
+
function Log-Message {
|
19
|
+
param([string]$Message)
|
20
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
21
|
+
Write-Host "[$timestamp] ${ScriptName}: $Message" -ForegroundColor Cyan
|
22
|
+
}
|
23
|
+
|
24
|
+
# Error handling function
|
25
|
+
function Exit-WithError {
|
26
|
+
param([string]$Message)
|
27
|
+
Write-Host "ERROR: $Message" -ForegroundColor Red
|
28
|
+
exit 1
|
29
|
+
}
|
30
|
+
|
31
|
+
# Sanitize feature name
|
32
|
+
$BranchSafeName = $FeatureName.ToLower() -replace '[^a-z0-9-]', '-' -replace '--+', '-' -replace '^-|-$', ''
|
33
|
+
|
34
|
+
if ([string]::IsNullOrEmpty($BranchSafeName)) {
|
35
|
+
Exit-WithError "Invalid feature name: '$FeatureName'"
|
36
|
+
}
|
37
|
+
|
38
|
+
# Get feature ID
|
39
|
+
if ($CustomId) {
|
40
|
+
$FeatureId = "{0:D3}" -f [int]$CustomId
|
41
|
+
} else {
|
42
|
+
$existingDirs = Get-ChildItem -Path "$ProjectRoot\specs" -Directory -Filter "[0-9]*" -ErrorAction SilentlyContinue
|
43
|
+
$nextId = if ($existingDirs) { $existingDirs.Count + 1 } else { 1 }
|
44
|
+
$FeatureId = "{0:D3}" -f $nextId
|
45
|
+
}
|
46
|
+
|
47
|
+
$BranchName = "$FeatureId-$BranchSafeName"
|
48
|
+
|
49
|
+
# Create directories
|
50
|
+
$SpecsDir = Join-Path $ProjectRoot "specs\$BranchName"
|
51
|
+
$PlansDir = Join-Path $ProjectRoot "plans\$BranchName"
|
52
|
+
$TasksDir = Join-Path $ProjectRoot "tasks\$BranchName"
|
53
|
+
|
54
|
+
Log-Message "Creating feature directories for '$FeatureName'"
|
55
|
+
|
56
|
+
try {
|
57
|
+
New-Item -ItemType Directory -Path $SpecsDir -Force | Out-Null
|
58
|
+
New-Item -ItemType Directory -Path $PlansDir -Force | Out-Null
|
59
|
+
New-Item -ItemType Directory -Path $TasksDir -Force | Out-Null
|
60
|
+
} catch {
|
61
|
+
Exit-WithError "Failed to create directories: $_"
|
62
|
+
}
|
63
|
+
|
64
|
+
# Create initial files from templates
|
65
|
+
$TemplateDir = Join-Path $ProjectRoot "templates"
|
66
|
+
|
67
|
+
$specTemplate = Join-Path $TemplateDir "spec.md"
|
68
|
+
$planTemplate = Join-Path $TemplateDir "plan.md"
|
69
|
+
$taskTemplate = Join-Path $TemplateDir "task.md"
|
70
|
+
|
71
|
+
if (-not (Test-Path $specTemplate)) {
|
72
|
+
Exit-WithError "Template not found: $specTemplate"
|
73
|
+
}
|
74
|
+
|
75
|
+
try {
|
76
|
+
Copy-Item -Path $specTemplate -Destination "$SpecsDir\spec-$FeatureId.md" -Force
|
77
|
+
Copy-Item -Path $planTemplate -Destination "$PlansDir\plan-$FeatureId.md" -Force
|
78
|
+
Copy-Item -Path $taskTemplate -Destination "$TasksDir\task-$FeatureId.md" -Force
|
79
|
+
} catch {
|
80
|
+
Exit-WithError "Failed to copy templates: $_"
|
81
|
+
}
|
82
|
+
|
83
|
+
# Update context
|
84
|
+
$ContextFile = Join-Path $ProjectRoot "memory\context.md"
|
85
|
+
$memoryDir = Split-Path -Parent $ContextFile
|
86
|
+
|
87
|
+
if (-not (Test-Path $memoryDir)) {
|
88
|
+
New-Item -ItemType Directory -Path $memoryDir -Force | Out-Null
|
89
|
+
}
|
90
|
+
|
91
|
+
$contextEntry = @"
|
92
|
+
|
93
|
+
## Active Feature: $FeatureName
|
94
|
+
- Feature ID: $FeatureId
|
95
|
+
- Branch: $BranchName
|
96
|
+
- Started: $(Get-Date -Format "yyyy-MM-ddTHH:mm:ss")
|
97
|
+
"@
|
98
|
+
|
99
|
+
try {
|
100
|
+
Add-Content -Path $ContextFile -Value $contextEntry
|
101
|
+
} catch {
|
102
|
+
Exit-WithError "Failed to update context file: $_"
|
103
|
+
}
|
104
|
+
|
105
|
+
# Create git branch if in git repo
|
106
|
+
$gitDir = Join-Path $ProjectRoot ".git"
|
107
|
+
if (Test-Path $gitDir) {
|
108
|
+
Push-Location $ProjectRoot
|
109
|
+
try {
|
110
|
+
$branchExists = git rev-parse --verify $BranchName 2>$null
|
111
|
+
if ($LASTEXITCODE -eq 0) {
|
112
|
+
Log-Message "Git branch '$BranchName' already exists, checking out"
|
113
|
+
git checkout $BranchName | Out-Null
|
114
|
+
} else {
|
115
|
+
Log-Message "Creating new git branch '$BranchName'"
|
116
|
+
git checkout -b $BranchName | Out-Null
|
117
|
+
}
|
118
|
+
} catch {
|
119
|
+
Log-Message "Warning: Git operations failed: $_"
|
120
|
+
} finally {
|
121
|
+
Pop-Location
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
Log-Message "Successfully initialized feature '$FeatureName' with ID $FeatureId"
|
126
|
+
|
127
|
+
# Output results
|
128
|
+
Write-Output "BRANCH_NAME=$BranchName"
|
129
|
+
Write-Output "SPEC_DIR=$SpecsDir"
|
130
|
+
Write-Output "FEATURE_ID=$FeatureId"
|
131
|
+
Write-Output "STATUS=initialized"
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# Generate implementation plan - PowerShell Version
|
2
|
+
|
3
|
+
param(
|
4
|
+
[Parameter(Mandatory=$true, Position=0)]
|
5
|
+
[string]$FeatureDir
|
6
|
+
)
|
7
|
+
|
8
|
+
# Configuration
|
9
|
+
$ScriptName = Split-Path -Leaf $PSCommandPath
|
10
|
+
$ScriptDir = Split-Path -Parent $PSCommandPath
|
11
|
+
$ProjectRoot = Split-Path -Parent $ScriptDir
|
12
|
+
|
13
|
+
# Logging function
|
14
|
+
function Log-Message {
|
15
|
+
param([string]$Message)
|
16
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
17
|
+
Write-Host "[$timestamp] ${ScriptName}: $Message" -ForegroundColor Cyan
|
18
|
+
}
|
19
|
+
|
20
|
+
# Error handling function
|
21
|
+
function Exit-WithError {
|
22
|
+
param([string]$Message)
|
23
|
+
Write-Host "ERROR: $Message" -ForegroundColor Red
|
24
|
+
exit 1
|
25
|
+
}
|
26
|
+
|
27
|
+
# Extract feature ID
|
28
|
+
$FeatureId = if ($FeatureDir -match '^(\d{3})') { $Matches[1] } else { "001" }
|
29
|
+
|
30
|
+
# Sanitize feature directory
|
31
|
+
$SanitizedDir = $FeatureDir -replace '[^a-zA-Z0-9_-]', ''
|
32
|
+
|
33
|
+
if ([string]::IsNullOrEmpty($SanitizedDir)) {
|
34
|
+
Exit-WithError "Invalid feature directory: '$FeatureDir'"
|
35
|
+
}
|
36
|
+
|
37
|
+
$PlanDir = Join-Path $ProjectRoot "plans\$FeatureDir"
|
38
|
+
$SpecDir = Join-Path $ProjectRoot "specs\$FeatureDir"
|
39
|
+
$TemplateFile = Join-Path $ProjectRoot "templates\plan.md"
|
40
|
+
|
41
|
+
# Ensure plans directory exists
|
42
|
+
if (-not (Test-Path $PlanDir)) {
|
43
|
+
New-Item -ItemType Directory -Path $PlanDir -Force | Out-Null
|
44
|
+
}
|
45
|
+
|
46
|
+
# Find latest spec file
|
47
|
+
if (Test-Path $SpecDir) {
|
48
|
+
$SpecFile = Get-ChildItem -Path $SpecDir -Filter "spec-*.md" -ErrorAction SilentlyContinue |
|
49
|
+
Sort-Object LastWriteTime -Descending |
|
50
|
+
Select-Object -First 1
|
51
|
+
|
52
|
+
if (-not $SpecFile) {
|
53
|
+
Exit-WithError "No specification files found in $SpecDir. Please create specification first."
|
54
|
+
}
|
55
|
+
$SpecFile = $SpecFile.FullName
|
56
|
+
} else {
|
57
|
+
Exit-WithError "Specifications directory not found: $SpecDir. Please create specification first."
|
58
|
+
}
|
59
|
+
|
60
|
+
# Find next available plan number or create new one
|
61
|
+
$existingPlans = Get-ChildItem -Path $PlanDir -Filter "plan-*.md" -ErrorAction SilentlyContinue
|
62
|
+
$planNumber = if ($existingPlans) { $existingPlans.Count + 1 } else { 1 }
|
63
|
+
$PlanFile = Join-Path $PlanDir ("plan-{0:D3}.md" -f $planNumber)
|
64
|
+
|
65
|
+
# Ensure plan template exists
|
66
|
+
if (-not (Test-Path $TemplateFile)) {
|
67
|
+
Exit-WithError "Template not found: $TemplateFile"
|
68
|
+
}
|
69
|
+
|
70
|
+
# Create plan
|
71
|
+
Log-Message "Creating implementation plan from template: $PlanFile"
|
72
|
+
try {
|
73
|
+
Copy-Item -Path $TemplateFile -Destination $PlanFile -Force
|
74
|
+
} catch {
|
75
|
+
Exit-WithError "Failed to copy plan template: $_"
|
76
|
+
}
|
77
|
+
|
78
|
+
# Validate plan structure
|
79
|
+
Log-Message "Validating implementation plan..."
|
80
|
+
|
81
|
+
# Check for required sections
|
82
|
+
$RequiredSections = @(
|
83
|
+
"## Implementation Plan:",
|
84
|
+
"## Specification Reference",
|
85
|
+
"## Phase -1: Pre-Implementation Gates",
|
86
|
+
"## Implementation Phases"
|
87
|
+
)
|
88
|
+
|
89
|
+
$content = Get-Content -Path $PlanFile -Raw
|
90
|
+
$MissingSections = @()
|
91
|
+
|
92
|
+
foreach ($section in $RequiredSections) {
|
93
|
+
if ($content -notmatch [regex]::Escape($section)) {
|
94
|
+
$MissingSections += $section
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
if ($MissingSections.Count -gt 0) {
|
99
|
+
Log-Message "WARNING: Missing required sections: $($MissingSections -join ', ')"
|
100
|
+
}
|
101
|
+
|
102
|
+
# Check Constitutional Gates
|
103
|
+
Log-Message "Checking Constitutional Gates..."
|
104
|
+
|
105
|
+
$ConstitutionalGates = @(
|
106
|
+
"Simplicity Gate",
|
107
|
+
"Anti-Abstraction Gate",
|
108
|
+
"Test-First Gate",
|
109
|
+
"Integration-First Gate",
|
110
|
+
"Research Gate"
|
111
|
+
)
|
112
|
+
|
113
|
+
foreach ($gate in $ConstitutionalGates) {
|
114
|
+
if ($content -notmatch [regex]::Escape($gate)) {
|
115
|
+
Log-Message "WARNING: Missing constitutional gate: $gate"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
# Check if specification has clarifications needed
|
120
|
+
$specContent = Get-Content -Path $SpecFile -Raw
|
121
|
+
if ($specContent -match "NEEDS CLARIFICATION") {
|
122
|
+
$ClarificationCount = ([regex]::Matches($specContent, "NEEDS CLARIFICATION")).Count
|
123
|
+
Log-Message "WARNING: Specification has $ClarificationCount clarifications needed - resolve before proceeding"
|
124
|
+
}
|
125
|
+
|
126
|
+
# Validate gate compliance
|
127
|
+
$GateStatus = "PENDING"
|
128
|
+
if ($content -match "Gate Status:.*\[(.*?)\]") {
|
129
|
+
$GateStatus = $Matches[1]
|
130
|
+
}
|
131
|
+
|
132
|
+
if ($GateStatus -ne "COMPLETED") {
|
133
|
+
Log-Message "WARNING: Constitutional gates not completed. Status: $GateStatus"
|
134
|
+
}
|
135
|
+
|
136
|
+
Log-Message "Implementation plan processing completed successfully"
|
137
|
+
|
138
|
+
# Output results
|
139
|
+
Write-Output "PLAN_FILE=$PlanFile"
|
140
|
+
Write-Output "SPEC_FILE=$SpecFile"
|
141
|
+
Write-Output "MISSING_SECTIONS=$($MissingSections.Count)"
|
142
|
+
Write-Output "CONSTITUTIONAL_GATES_STATUS=$GateStatus"
|
143
|
+
Write-Output "STATUS=ready"
|
@@ -27,6 +27,9 @@ fi
|
|
27
27
|
|
28
28
|
FEATURE_DIR="$1"
|
29
29
|
|
30
|
+
# Extract feature ID from directory name (e.g., "001-feature-name" -> "001")
|
31
|
+
FEATURE_ID=$(echo "$FEATURE_DIR" | grep -o '^[0-9]\{3\}' || echo "001")
|
32
|
+
|
30
33
|
# Sanitize feature directory
|
31
34
|
SANITIZED_DIR=$(echo "$FEATURE_DIR" | sed 's/[^a-zA-Z0-9_-]//g')
|
32
35
|
|
@@ -34,20 +37,6 @@ if [ -z "$SANITIZED_DIR" ]; then
|
|
34
37
|
error_exit "Invalid feature directory: '$FEATURE_DIR'"
|
35
38
|
fi
|
36
39
|
|
37
|
-
# Find feature directory if not provided
|
38
|
-
if [ -z "$FEATURE_DIR" ]; then
|
39
|
-
CONTEXT_FILE="$PROJECT_ROOT/memory/context.md"
|
40
|
-
if [ -f "$CONTEXT_FILE" ]; then
|
41
|
-
FEATURE_DIR=$(grep -A1 "Active Feature" "$CONTEXT_FILE" | tail -1 | cut -d: -f2 | xargs)
|
42
|
-
if [ -z "$FEATURE_DIR" ]; then
|
43
|
-
error_exit "No active feature found in context file"
|
44
|
-
fi
|
45
|
-
log "Using active feature from context: $FEATURE_DIR"
|
46
|
-
else
|
47
|
-
error_exit "No feature directory provided and no context file found"
|
48
|
-
fi
|
49
|
-
fi
|
50
|
-
|
51
40
|
PLAN_DIR="$PROJECT_ROOT/plans/${FEATURE_DIR}"
|
52
41
|
SPEC_DIR="$PROJECT_ROOT/specs/${FEATURE_DIR}"
|
53
42
|
TEMPLATE_FILE="$PROJECT_ROOT/templates/plan.md"
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Generate or update specification - PowerShell Version
|
2
|
+
|
3
|
+
param(
|
4
|
+
[Parameter(Mandatory=$true, Position=0)]
|
5
|
+
[string]$FeatureDir,
|
6
|
+
|
7
|
+
[Parameter(Mandatory=$false, Position=1)]
|
8
|
+
[string]$SpecContent
|
9
|
+
)
|
10
|
+
|
11
|
+
# Configuration
|
12
|
+
$ScriptName = Split-Path -Leaf $PSCommandPath
|
13
|
+
$ScriptDir = Split-Path -Parent $PSCommandPath
|
14
|
+
$ProjectRoot = Split-Path -Parent $ScriptDir
|
15
|
+
|
16
|
+
# Logging function
|
17
|
+
function Log-Message {
|
18
|
+
param([string]$Message)
|
19
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
20
|
+
Write-Host "[$timestamp] ${ScriptName}: $Message" -ForegroundColor Cyan
|
21
|
+
}
|
22
|
+
|
23
|
+
# Error handling function
|
24
|
+
function Exit-WithError {
|
25
|
+
param([string]$Message)
|
26
|
+
Write-Host "ERROR: $Message" -ForegroundColor Red
|
27
|
+
exit 1
|
28
|
+
}
|
29
|
+
|
30
|
+
# Extract feature ID from directory name
|
31
|
+
$FeatureId = if ($FeatureDir -match '^(\d{3})') { $Matches[1] } else { "001" }
|
32
|
+
|
33
|
+
# Sanitize feature directory
|
34
|
+
$SanitizedDir = $FeatureDir -replace '[^a-zA-Z0-9_-]', ''
|
35
|
+
|
36
|
+
if ([string]::IsNullOrEmpty($SanitizedDir)) {
|
37
|
+
Exit-WithError "Invalid feature directory: '$FeatureDir'"
|
38
|
+
}
|
39
|
+
|
40
|
+
$SpecDir = Join-Path $ProjectRoot "specs\$FeatureDir"
|
41
|
+
$TemplateFile = Join-Path $ProjectRoot "templates\spec.md"
|
42
|
+
|
43
|
+
# Ensure specs directory exists
|
44
|
+
if (-not (Test-Path $SpecDir)) {
|
45
|
+
New-Item -ItemType Directory -Path $SpecDir -Force | Out-Null
|
46
|
+
}
|
47
|
+
|
48
|
+
# Find latest spec file or create new one
|
49
|
+
if ($SpecContent) {
|
50
|
+
# Find next available spec number
|
51
|
+
$existingSpecs = Get-ChildItem -Path $SpecDir -Filter "spec-*.md" -ErrorAction SilentlyContinue
|
52
|
+
$specNumber = if ($existingSpecs) { $existingSpecs.Count + 1 } else { 1 }
|
53
|
+
$SpecFile = Join-Path $SpecDir ("spec-{0:D3}.md" -f $specNumber)
|
54
|
+
|
55
|
+
# Update specification with provided content
|
56
|
+
Log-Message "Creating specification: $SpecFile"
|
57
|
+
try {
|
58
|
+
Set-Content -Path $SpecFile -Value $SpecContent
|
59
|
+
} catch {
|
60
|
+
Exit-WithError "Failed to write specification content: $_"
|
61
|
+
}
|
62
|
+
} else {
|
63
|
+
# Find latest spec file
|
64
|
+
$latestSpec = Get-ChildItem -Path $SpecDir -Filter "spec-*.md" -ErrorAction SilentlyContinue |
|
65
|
+
Sort-Object LastWriteTime -Descending |
|
66
|
+
Select-Object -First 1
|
67
|
+
|
68
|
+
if ($latestSpec) {
|
69
|
+
$SpecFile = $latestSpec.FullName
|
70
|
+
Log-Message "Using latest specification: $SpecFile"
|
71
|
+
} else {
|
72
|
+
# No spec files found, create first one
|
73
|
+
$SpecFile = Join-Path $SpecDir "spec-001.md"
|
74
|
+
if (-not (Test-Path $TemplateFile)) {
|
75
|
+
Exit-WithError "Template not found: $TemplateFile"
|
76
|
+
}
|
77
|
+
Log-Message "Creating specification from template: $SpecFile"
|
78
|
+
try {
|
79
|
+
Copy-Item -Path $TemplateFile -Destination $SpecFile -Force
|
80
|
+
} catch {
|
81
|
+
Exit-WithError "Failed to copy specification template: $_"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
# Validate specification
|
87
|
+
Log-Message "Validating specification..."
|
88
|
+
if (-not (Test-Path $SpecFile)) {
|
89
|
+
Exit-WithError "Specification file does not exist: $SpecFile"
|
90
|
+
}
|
91
|
+
|
92
|
+
# Check for required sections
|
93
|
+
$RequiredSections = @(
|
94
|
+
"## Specification:",
|
95
|
+
"## Metadata",
|
96
|
+
"## Functional Requirements",
|
97
|
+
"## Acceptance Scenarios"
|
98
|
+
)
|
99
|
+
|
100
|
+
$content = Get-Content -Path $SpecFile -Raw
|
101
|
+
$MissingSections = @()
|
102
|
+
|
103
|
+
foreach ($section in $RequiredSections) {
|
104
|
+
if ($content -notmatch [regex]::Escape($section)) {
|
105
|
+
$MissingSections += $section
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
if ($MissingSections.Count -gt 0) {
|
110
|
+
Log-Message "WARNING: Missing required sections: $($MissingSections -join ', ')"
|
111
|
+
}
|
112
|
+
|
113
|
+
# Check for clarifications needed
|
114
|
+
$ClarificationCount = 0
|
115
|
+
if ($content -match "NEEDS CLARIFICATION") {
|
116
|
+
$ClarificationCount = ([regex]::Matches($content, "NEEDS CLARIFICATION")).Count
|
117
|
+
Log-Message "WARNING: Specification has $ClarificationCount clarifications needed"
|
118
|
+
}
|
119
|
+
|
120
|
+
Log-Message "Specification processing completed successfully"
|
121
|
+
|
122
|
+
# Output results
|
123
|
+
Write-Output "SPEC_FILE=$SpecFile"
|
124
|
+
Write-Output "CLARIFICATIONS_NEEDED=$ClarificationCount"
|
125
|
+
Write-Output "MISSING_SECTIONS=$($MissingSections.Count)"
|
126
|
+
Write-Output "STATUS=updated"
|
@@ -28,6 +28,9 @@ fi
|
|
28
28
|
FEATURE_DIR="$1"
|
29
29
|
SPEC_CONTENT="${2:-}"
|
30
30
|
|
31
|
+
# Extract feature ID from directory name (e.g., "001-feature-name" -> "001")
|
32
|
+
FEATURE_ID=$(echo "$FEATURE_DIR" | grep -o '^[0-9]\{3\}' || echo "001")
|
33
|
+
|
31
34
|
# Sanitize feature directory
|
32
35
|
SANITIZED_DIR=$(echo "$FEATURE_DIR" | sed 's/[^a-zA-Z0-9_-]//g')
|
33
36
|
|
@@ -35,20 +38,6 @@ if [ -z "$SANITIZED_DIR" ]; then
|
|
35
38
|
error_exit "Invalid feature directory: '$FEATURE_DIR'"
|
36
39
|
fi
|
37
40
|
|
38
|
-
# Find feature directory if not provided
|
39
|
-
if [ -z "$FEATURE_DIR" ]; then
|
40
|
-
CONTEXT_FILE="$PROJECT_ROOT/memory/context.md"
|
41
|
-
if [ -f "$CONTEXT_FILE" ]; then
|
42
|
-
FEATURE_DIR=$(grep -A1 "Active Feature" "$CONTEXT_FILE" | tail -1 | cut -d: -f2 | xargs)
|
43
|
-
if [ -z "$FEATURE_DIR" ]; then
|
44
|
-
error_exit "No active feature found in context file"
|
45
|
-
fi
|
46
|
-
log "Using active feature from context: $FEATURE_DIR"
|
47
|
-
else
|
48
|
-
error_exit "No feature directory provided and no context file found"
|
49
|
-
fi
|
50
|
-
fi
|
51
|
-
|
52
41
|
SPEC_DIR="$PROJECT_ROOT/specs/${FEATURE_DIR}"
|
53
42
|
TEMPLATE_FILE="$PROJECT_ROOT/templates/spec.md"
|
54
43
|
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Generate task breakdown - PowerShell Version
|
2
|
+
|
3
|
+
param(
|
4
|
+
[Parameter(Mandatory=$true, Position=0)]
|
5
|
+
[string]$FeatureDir
|
6
|
+
)
|
7
|
+
|
8
|
+
# Configuration
|
9
|
+
$ScriptName = Split-Path -Leaf $PSCommandPath
|
10
|
+
$ScriptDir = Split-Path -Parent $PSCommandPath
|
11
|
+
$ProjectRoot = Split-Path -Parent $ScriptDir
|
12
|
+
|
13
|
+
# Logging function
|
14
|
+
function Log-Message {
|
15
|
+
param([string]$Message)
|
16
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
17
|
+
Write-Host "[$timestamp] ${ScriptName}: $Message" -ForegroundColor Cyan
|
18
|
+
}
|
19
|
+
|
20
|
+
# Error handling function
|
21
|
+
function Exit-WithError {
|
22
|
+
param([string]$Message)
|
23
|
+
Write-Host "ERROR: $Message" -ForegroundColor Red
|
24
|
+
exit 1
|
25
|
+
}
|
26
|
+
|
27
|
+
# Extract feature ID
|
28
|
+
$FeatureId = if ($FeatureDir -match '^(\d{3})') { $Matches[1] } else { "001" }
|
29
|
+
|
30
|
+
# Sanitize feature directory
|
31
|
+
$SanitizedDir = $FeatureDir -replace '[^a-zA-Z0-9_-]', ''
|
32
|
+
|
33
|
+
if ([string]::IsNullOrEmpty($SanitizedDir)) {
|
34
|
+
Exit-WithError "Invalid feature directory: '$FeatureDir'"
|
35
|
+
}
|
36
|
+
|
37
|
+
$TaskDir = Join-Path $ProjectRoot "tasks\$FeatureDir"
|
38
|
+
$PlanDir = Join-Path $ProjectRoot "plans\$FeatureDir"
|
39
|
+
$SpecDir = Join-Path $ProjectRoot "specs\$FeatureDir"
|
40
|
+
$TemplateFile = Join-Path $ProjectRoot "templates\task.md"
|
41
|
+
|
42
|
+
# Ensure tasks directory exists
|
43
|
+
if (-not (Test-Path $TaskDir)) {
|
44
|
+
New-Item -ItemType Directory -Path $TaskDir -Force | Out-Null
|
45
|
+
}
|
46
|
+
|
47
|
+
# Find latest spec file
|
48
|
+
if (Test-Path $SpecDir) {
|
49
|
+
$SpecFile = Get-ChildItem -Path $SpecDir -Filter "spec-*.md" -ErrorAction SilentlyContinue |
|
50
|
+
Sort-Object LastWriteTime -Descending |
|
51
|
+
Select-Object -First 1
|
52
|
+
|
53
|
+
if (-not $SpecFile) {
|
54
|
+
Exit-WithError "No specification files found in $SpecDir. Please create specification first."
|
55
|
+
}
|
56
|
+
$SpecFile = $SpecFile.FullName
|
57
|
+
} else {
|
58
|
+
Exit-WithError "Specifications directory not found: $SpecDir. Please create specification first."
|
59
|
+
}
|
60
|
+
|
61
|
+
# Find latest plan file
|
62
|
+
if (Test-Path $PlanDir) {
|
63
|
+
$PlanFile = Get-ChildItem -Path $PlanDir -Filter "plan-*.md" -ErrorAction SilentlyContinue |
|
64
|
+
Sort-Object LastWriteTime -Descending |
|
65
|
+
Select-Object -First 1
|
66
|
+
|
67
|
+
if (-not $PlanFile) {
|
68
|
+
Exit-WithError "No plan files found in $PlanDir. Please create plan first."
|
69
|
+
}
|
70
|
+
$PlanFile = $PlanFile.FullName
|
71
|
+
} else {
|
72
|
+
Exit-WithError "Plans directory not found: $PlanDir. Please create plan first."
|
73
|
+
}
|
74
|
+
|
75
|
+
# Find next available task number or create new one
|
76
|
+
$existingTasks = Get-ChildItem -Path $TaskDir -Filter "task-*.md" -ErrorAction SilentlyContinue
|
77
|
+
$taskNumber = if ($existingTasks) { $existingTasks.Count + 1 } else { 1 }
|
78
|
+
$TaskFile = Join-Path $TaskDir ("task-{0:D3}.md" -f $taskNumber)
|
79
|
+
|
80
|
+
# Ensure task template exists
|
81
|
+
if (-not (Test-Path $TemplateFile)) {
|
82
|
+
Exit-WithError "Template not found: $TemplateFile"
|
83
|
+
}
|
84
|
+
|
85
|
+
# Create task file
|
86
|
+
Log-Message "Creating task breakdown from template: $TaskFile"
|
87
|
+
try {
|
88
|
+
Copy-Item -Path $TemplateFile -Destination $TaskFile -Force
|
89
|
+
} catch {
|
90
|
+
Exit-WithError "Failed to copy task template: $_"
|
91
|
+
}
|
92
|
+
|
93
|
+
# Validate task structure
|
94
|
+
Log-Message "Validating task breakdown..."
|
95
|
+
|
96
|
+
# Check for required sections
|
97
|
+
$RequiredSections = @(
|
98
|
+
"## Task List:",
|
99
|
+
"## Task Organization",
|
100
|
+
"## Critical Path",
|
101
|
+
"## Execution Schedule"
|
102
|
+
)
|
103
|
+
|
104
|
+
$content = Get-Content -Path $TaskFile -Raw
|
105
|
+
$MissingSections = @()
|
106
|
+
|
107
|
+
foreach ($section in $RequiredSections) {
|
108
|
+
if ($content -notmatch [regex]::Escape($section)) {
|
109
|
+
$MissingSections += $section
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
if ($MissingSections.Count -gt 0) {
|
114
|
+
Log-Message "WARNING: Missing required sections: $($MissingSections -join ', ')"
|
115
|
+
}
|
116
|
+
|
117
|
+
# Count tasks and analyze structure
|
118
|
+
$TotalTasks = ([regex]::Matches($content, "^- \[.\]", [System.Text.RegularExpressions.RegexOptions]::Multiline)).Count
|
119
|
+
$CompletedTasks = ([regex]::Matches($content, "^- \[x\]", [System.Text.RegularExpressions.RegexOptions]::Multiline)).Count
|
120
|
+
$PendingTasks = ([regex]::Matches($content, "^- \[ \]", [System.Text.RegularExpressions.RegexOptions]::Multiline)).Count
|
121
|
+
$BlockedTasks = ([regex]::Matches($content, "^- \[!\]", [System.Text.RegularExpressions.RegexOptions]::Multiline)).Count
|
122
|
+
|
123
|
+
# Check for parallel tasks
|
124
|
+
$ParallelTasks = ([regex]::Matches($content, "\[P\]")).Count
|
125
|
+
|
126
|
+
# Check constitutional gates compliance
|
127
|
+
$GatesCount = 0
|
128
|
+
if ($content -match "Constitutional Gates Compliance") {
|
129
|
+
$gatesSection = $content.Substring($content.IndexOf("Constitutional Gates Compliance"))
|
130
|
+
if ($gatesSection.Length -gt 0) {
|
131
|
+
$GatesCount = ([regex]::Matches($gatesSection.Substring(0, [Math]::Min(1000, $gatesSection.Length)), "\[ \]")).Count
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
# Check if plan has constitutional gates completed
|
136
|
+
$planContent = Get-Content -Path $PlanFile -Raw
|
137
|
+
$PlanGateStatus = "PENDING"
|
138
|
+
if ($planContent -match "Gate Status:.*\[(.*?)\]") {
|
139
|
+
$PlanGateStatus = $Matches[1]
|
140
|
+
}
|
141
|
+
|
142
|
+
if ($PlanGateStatus -ne "COMPLETED") {
|
143
|
+
Log-Message "WARNING: Implementation plan constitutional gates not completed. Task generation may be premature."
|
144
|
+
}
|
145
|
+
|
146
|
+
# Calculate completion percentage
|
147
|
+
$CompletionPercentage = 0
|
148
|
+
if ($TotalTasks -gt 0) {
|
149
|
+
$CompletionPercentage = [math]::Round(($CompletedTasks * 100) / $TotalTasks)
|
150
|
+
}
|
151
|
+
|
152
|
+
Log-Message "Task analysis completed - Total: $TotalTasks, Completed: $CompletedTasks ($CompletionPercentage%), Parallel: $ParallelTasks"
|
153
|
+
|
154
|
+
# Output comprehensive status
|
155
|
+
Write-Output "TASK_FILE=$TaskFile"
|
156
|
+
Write-Output "SPEC_FILE=$SpecFile"
|
157
|
+
Write-Output "PLAN_FILE=$PlanFile"
|
158
|
+
Write-Output "TOTAL_TASKS=$TotalTasks"
|
159
|
+
Write-Output "COMPLETED_TASKS=$CompletedTasks"
|
160
|
+
Write-Output "PENDING_TASKS=$PendingTasks"
|
161
|
+
Write-Output "BLOCKED_TASKS=$BlockedTasks"
|
162
|
+
Write-Output "PARALLEL_TASKS=$ParallelTasks"
|
163
|
+
Write-Output "CONSTITUTIONAL_GATES_PENDING=$GatesCount"
|
164
|
+
Write-Output "COMPLETION_PERCENTAGE=$CompletionPercentage"
|
165
|
+
Write-Output "MISSING_SECTIONS=$($MissingSections.Count)"
|
166
|
+
Write-Output "STATUS=generated"
|
@@ -27,6 +27,9 @@ fi
|
|
27
27
|
|
28
28
|
FEATURE_DIR="$1"
|
29
29
|
|
30
|
+
# Extract feature ID from directory name (e.g., "001-feature-name" -> "001")
|
31
|
+
FEATURE_ID=$(echo "$FEATURE_DIR" | grep -o '^[0-9]\{3\}' || echo "001")
|
32
|
+
|
30
33
|
# Sanitize feature directory
|
31
34
|
SANITIZED_DIR=$(echo "$FEATURE_DIR" | sed 's/[^a-zA-Z0-9_-]//g')
|
32
35
|
|
@@ -34,20 +37,6 @@ if [ -z "$SANITIZED_DIR" ]; then
|
|
34
37
|
error_exit "Invalid feature directory: '$FEATURE_DIR'"
|
35
38
|
fi
|
36
39
|
|
37
|
-
# Find feature directory if not provided
|
38
|
-
if [ -z "$FEATURE_DIR" ]; then
|
39
|
-
CONTEXT_FILE="$PROJECT_ROOT/memory/context.md"
|
40
|
-
if [ -f "$CONTEXT_FILE" ]; then
|
41
|
-
FEATURE_DIR=$(grep -A1 "Active Feature" "$CONTEXT_FILE" | tail -1 | cut -d: -f2 | xargs)
|
42
|
-
if [ -z "$FEATURE_DIR" ]; then
|
43
|
-
error_exit "No active feature found in context file"
|
44
|
-
fi
|
45
|
-
log "Using active feature from context: $FEATURE_DIR"
|
46
|
-
else
|
47
|
-
error_exit "No feature directory provided and no context file found"
|
48
|
-
fi
|
49
|
-
fi
|
50
|
-
|
51
40
|
TASK_DIR="$PROJECT_ROOT/tasks/${FEATURE_DIR}"
|
52
41
|
PLAN_DIR="$PROJECT_ROOT/plans/${FEATURE_DIR}"
|
53
42
|
SPEC_DIR="$PROJECT_ROOT/specs/${FEATURE_DIR}"
|
@@ -132,10 +121,9 @@ if [ "$PLAN_GATE_STATUS" != "COMPLETED" ]; then
|
|
132
121
|
fi
|
133
122
|
|
134
123
|
# Calculate completion percentage
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
COMPLETION_PERCENTAGE=0
|
124
|
+
COMPLETION_PERCENTAGE=0
|
125
|
+
if [ "${TOTAL_TASKS:-0}" -gt 0 ] && [ "${COMPLETED_TASKS:-0}" -ge 0 ]; then
|
126
|
+
COMPLETION_PERCENTAGE=$(( (COMPLETED_TASKS * 100) / TOTAL_TASKS ))
|
139
127
|
fi
|
140
128
|
|
141
129
|
log "Task analysis completed - Total: $TOTAL_TASKS, Completed: $COMPLETED_TASKS ($COMPLETION_PERCENTAGE%), Parallel: $PARALLEL_TASKS"
|
@@ -0,0 +1,128 @@
|
|
1
|
+
"""
|
2
|
+
Version checking utility for SpecPulse
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import urllib.request
|
7
|
+
import urllib.error
|
8
|
+
from typing import Optional, Tuple
|
9
|
+
from packaging import version
|
10
|
+
import socket
|
11
|
+
|
12
|
+
|
13
|
+
def check_pypi_version(package_name: str = "specpulse", timeout: int = 2) -> Optional[str]:
|
14
|
+
"""
|
15
|
+
Check the latest version of a package on PyPI
|
16
|
+
|
17
|
+
Args:
|
18
|
+
package_name: Name of the package to check
|
19
|
+
timeout: Timeout in seconds for the request
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Latest version string or None if check fails
|
23
|
+
"""
|
24
|
+
try:
|
25
|
+
# Set a short timeout to avoid blocking
|
26
|
+
socket.setdefaulttimeout(timeout)
|
27
|
+
|
28
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
29
|
+
with urllib.request.urlopen(url) as response:
|
30
|
+
data = json.loads(response.read())
|
31
|
+
return data["info"]["version"]
|
32
|
+
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError, KeyError, socket.timeout):
|
33
|
+
# Silently fail - don't interrupt user workflow
|
34
|
+
return None
|
35
|
+
finally:
|
36
|
+
# Reset default timeout
|
37
|
+
socket.setdefaulttimeout(None)
|
38
|
+
|
39
|
+
|
40
|
+
def compare_versions(current: str, latest: str) -> Tuple[bool, bool]:
|
41
|
+
"""
|
42
|
+
Compare current version with latest version
|
43
|
+
|
44
|
+
Args:
|
45
|
+
current: Current installed version
|
46
|
+
latest: Latest available version
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
Tuple of (is_outdated, is_major_update)
|
50
|
+
"""
|
51
|
+
try:
|
52
|
+
current_v = version.parse(current)
|
53
|
+
latest_v = version.parse(latest)
|
54
|
+
|
55
|
+
is_outdated = current_v < latest_v
|
56
|
+
|
57
|
+
# Check if it's a major version update
|
58
|
+
is_major = False
|
59
|
+
if is_outdated:
|
60
|
+
# Major version changed
|
61
|
+
if latest_v.major > current_v.major:
|
62
|
+
is_major = True
|
63
|
+
|
64
|
+
return is_outdated, is_major
|
65
|
+
except:
|
66
|
+
return False, False
|
67
|
+
|
68
|
+
|
69
|
+
def get_update_message(current: str, latest: str, is_major: bool) -> str:
|
70
|
+
"""
|
71
|
+
Generate update notification message
|
72
|
+
|
73
|
+
Args:
|
74
|
+
current: Current version
|
75
|
+
latest: Latest version
|
76
|
+
is_major: Whether this is a major update
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
Formatted update message
|
80
|
+
"""
|
81
|
+
if is_major:
|
82
|
+
urgency = "[!] MAJOR"
|
83
|
+
color = "bright_red"
|
84
|
+
else:
|
85
|
+
urgency = "[i]"
|
86
|
+
color = "yellow"
|
87
|
+
|
88
|
+
message = f"""
|
89
|
+
{urgency} Update Available!
|
90
|
+
Current: v{current}
|
91
|
+
Latest: v{latest}
|
92
|
+
|
93
|
+
Update with: pip install --upgrade specpulse
|
94
|
+
"""
|
95
|
+
|
96
|
+
return message, color
|
97
|
+
|
98
|
+
|
99
|
+
def should_check_version() -> bool:
|
100
|
+
"""
|
101
|
+
Determine if we should check for updates
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
True if we should check, False otherwise
|
105
|
+
"""
|
106
|
+
import os
|
107
|
+
from pathlib import Path
|
108
|
+
from datetime import datetime, timedelta
|
109
|
+
|
110
|
+
try:
|
111
|
+
# Check once per day maximum
|
112
|
+
cache_file = Path.home() / ".specpulse" / "last_version_check"
|
113
|
+
|
114
|
+
if cache_file.exists():
|
115
|
+
# Check if last check was within 24 hours
|
116
|
+
last_check = datetime.fromtimestamp(cache_file.stat().st_mtime)
|
117
|
+
if datetime.now() - last_check < timedelta(hours=24):
|
118
|
+
return False
|
119
|
+
|
120
|
+
# Create cache directory if needed
|
121
|
+
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
122
|
+
|
123
|
+
# Update timestamp
|
124
|
+
cache_file.touch()
|
125
|
+
return True
|
126
|
+
except:
|
127
|
+
# If anything fails, don't check
|
128
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: specpulse
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.4
|
4
4
|
Summary: Specification-Driven Development Framework
|
5
5
|
Home-page: https://github.com/specpulse
|
6
6
|
Author: SpecPulse
|
@@ -32,6 +32,7 @@ Requires-Dist: rich>=13.0
|
|
32
32
|
Requires-Dist: jinja2>=3.0
|
33
33
|
Requires-Dist: gitpython>=3.1
|
34
34
|
Requires-Dist: toml>=0.10
|
35
|
+
Requires-Dist: packaging>=21.0
|
35
36
|
Provides-Extra: dev
|
36
37
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
37
38
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
@@ -66,13 +67,14 @@ Dynamic: requires-python
|
|
66
67
|
|
67
68
|
SpecPulse revolutionizes AI-assisted development by enforcing a **specification-first approach**. Instead of jumping straight into code, SpecPulse ensures every feature starts with clear specifications, validated plans, and tracked tasks - guaranteeing quality from day one.
|
68
69
|
|
69
|
-
> **Latest Update (v1.3.
|
70
|
-
> -
|
71
|
-
> -
|
72
|
-
> -
|
73
|
-
> -
|
74
|
-
> - **Previous (v1.
|
75
|
-
> - **Previous (v1.
|
70
|
+
> **Latest Update (v1.3.4)**:
|
71
|
+
> - 🧪 **89% Test Coverage**: Comprehensive test suite with 450+ tests
|
72
|
+
> - 🔍 **Version Update Checking**: Automatic PyPI version checking with 24-hour caching
|
73
|
+
> - 💪 **PowerShell Support**: Full Windows PowerShell script support alongside Bash
|
74
|
+
> - 🛠️ **Enhanced Stability**: Critical path fixes and improved error handling
|
75
|
+
> - **Previous (v1.3.0)**: Continuous task execution with `/sp-execute` command
|
76
|
+
> - **Previous (v1.2.0)**: Microservice decomposition, service-based planning
|
77
|
+
> - **Previous (v1.1.0)**: Command prefix system, multi-spec workflow
|
76
78
|
|
77
79
|
### Why SpecPulse?
|
78
80
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
specpulse/__init__.py,sha256=
|
1
|
+
specpulse/__init__.py,sha256=kYgVcf5KKzNWm3EZCK0jY8hX5ygDK6-KJBe3GTPWraI,270
|
2
2
|
specpulse/cli/__init__.py,sha256=DpX6FoRJtSNjudsmZ3mTih_RIaWyiIgxew4-f8mOmFY,70
|
3
|
-
specpulse/cli/main.py,sha256=
|
3
|
+
specpulse/cli/main.py,sha256=8kiDlmVLe42AjUAaBKfzxSFJqZsk0gddYGGD-gUzs2s,34363
|
4
4
|
specpulse/core/__init__.py,sha256=VKHGdPDM38QM9yDIq05jxol4q8WFlFAeltOzYLsPuto,132
|
5
|
-
specpulse/core/specpulse.py,sha256=
|
5
|
+
specpulse/core/specpulse.py,sha256=uj6ESZdoEC6ngPg1bCGtoOSnX6pjn6de3VeGrJBX65M,32709
|
6
6
|
specpulse/core/validator.py,sha256=VsXU0qD8uUbZS3a8umqKpUx__sNlVRqTA7YTek9lezQ,18669
|
7
7
|
specpulse/resources/commands/claude/sp-continue.md,sha256=3_vikDSNRN0slNtKQGWl9dWEgkT-Q7nFpUxHKnzBCKs,5175
|
8
8
|
specpulse/resources/commands/claude/sp-decompose.md,sha256=REVLx6Nua4N0xPju6ArNswzK7ZD1N776XUhVzo31K0Y,8088
|
@@ -25,10 +25,14 @@ specpulse/resources/memory/context.md,sha256=B-6m5f4WP8x_VXp0S9fbqm8tHH-2PZ7d9VW
|
|
25
25
|
specpulse/resources/memory/decisions.md,sha256=ZwzVyOPB2mIzwveWpGmedej7ncI81GALg8WVy6AeZA4,686
|
26
26
|
specpulse/resources/scripts/sp-pulse-decompose.sh,sha256=EN8iF02pij4yse3of3fgO8ZI6YKFUV7Z7f4epEmaKXI,1431
|
27
27
|
specpulse/resources/scripts/sp-pulse-execute.sh,sha256=696vgsj0NpV-Mr0KMyoJ63NkTqmS1tmQ1X4BzK0kQGA,5493
|
28
|
+
specpulse/resources/scripts/sp-pulse-init.ps1,sha256=VXmLRg4d9xwy2Lbhrv3h7L_RJ0c-eido8zRyvNaeqx0,4058
|
28
29
|
specpulse/resources/scripts/sp-pulse-init.sh,sha256=ECqLvgt6H-Wtp1hpKhkCKv_k70ZVfkmb13YyxT8Ni2M,3285
|
29
|
-
specpulse/resources/scripts/sp-pulse-plan.
|
30
|
-
specpulse/resources/scripts/sp-pulse-
|
31
|
-
specpulse/resources/scripts/sp-pulse-
|
30
|
+
specpulse/resources/scripts/sp-pulse-plan.ps1,sha256=aEpZeYDSBeb8D86gv2U2V22wPzX54CBw8id_hFPf1GU,4456
|
31
|
+
specpulse/resources/scripts/sp-pulse-plan.sh,sha256=KHSpm3K1qyryBaiz693ytHZU61YDRLN85Ew063KFOYQ,3911
|
32
|
+
specpulse/resources/scripts/sp-pulse-spec.ps1,sha256=u71r-wfGB8f7RJCuENufCJLlSuXlWcnUHYiSpjrPEQ4,4075
|
33
|
+
specpulse/resources/scripts/sp-pulse-spec.sh,sha256=1QBHkJKpopH2Vn8fIGYSMuuiqMrtu3F1WZCyk5liN-Y,3906
|
34
|
+
specpulse/resources/scripts/sp-pulse-task.ps1,sha256=F7D6j896Cq1SlED5L2AOB_P0zu1Qda7MwUenhXWffeg,5895
|
35
|
+
specpulse/resources/scripts/sp-pulse-task.sh,sha256=E-2ld9TX4ipybtCEDH6rmcpcK_VOw3qoymBiVB51BiM,4801
|
32
36
|
specpulse/resources/templates/plan.md,sha256=KSCB5HvYRjqrM-VTe7nwJgR1MjGI748YE2BJtn41g4Q,6689
|
33
37
|
specpulse/resources/templates/spec.md,sha256=yVWn2LhZn0IMLJ9XuBjPWSWNS4GYJFowRrqRMT2wJf4,3748
|
34
38
|
specpulse/resources/templates/task.md,sha256=mGRlE4WD2E1Lh5gGr7kyOEmoXDbjLhUVMG9D7ZGCpmw,5357
|
@@ -40,9 +44,10 @@ specpulse/resources/templates/decomposition/service-plan.md,sha256=YCAu0-jRKNQ-G
|
|
40
44
|
specpulse/utils/__init__.py,sha256=VTGRDsVLl2JIXYyHB9Sidxj5acY_W-LJI2MMtY1pwb0,122
|
41
45
|
specpulse/utils/console.py,sha256=xn4Koc1_ph61bvBTlvPOwAbqZ5ac7TZ51xTWJbwG0sc,12973
|
42
46
|
specpulse/utils/git_utils.py,sha256=RzJBZoyWp5on1_wq2lY1bs__UJsGKSDpAUeDKFdRYlQ,6095
|
43
|
-
specpulse
|
44
|
-
specpulse-1.3.
|
45
|
-
specpulse-1.3.
|
46
|
-
specpulse-1.3.
|
47
|
-
specpulse-1.3.
|
48
|
-
specpulse-1.3.
|
47
|
+
specpulse/utils/version_check.py,sha256=Cg30K0BRq_y8B9v62aJbtIuBDmPnHcwHTim7HZnoGZc,3424
|
48
|
+
specpulse-1.3.4.dist-info/licenses/LICENSE,sha256=ImSFezK-Phy0oTBqZipcajsJfPZT8mmg8m3jkgNdNhA,1066
|
49
|
+
specpulse-1.3.4.dist-info/METADATA,sha256=FDdf5raQvOXwA9xWRK3QYLK8bGQtvhZStx91EnDvceY,21194
|
50
|
+
specpulse-1.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
51
|
+
specpulse-1.3.4.dist-info/entry_points.txt,sha256=GwMaCKWSCFZlJMrTNavEYrnREynIS6dM3B0ILxnaouA,83
|
52
|
+
specpulse-1.3.4.dist-info/top_level.txt,sha256=pEfkFil5xe38J00OHrBrJ07xpbm6bRnGhI8B07O9KeI,10
|
53
|
+
specpulse-1.3.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|