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 CHANGED
@@ -3,7 +3,7 @@ SpecPulse: Specification-Driven Development Framework
3
3
  Built for the AI era
4
4
  """
5
5
 
6
- __version__ = "1.3.2"
6
+ __version__ = "1.3.3"
7
7
  __author__ = "SpecPulse"
8
8
  __url__ = "https://github.com/specpulse"
9
9
 
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 == ".sh":
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
 
@@ -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-001.md"
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-001.md"
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
- if [ "$TOTAL_TASKS" -gt 0 ]; then
136
- COMPLETION_PERCENTAGE=$((COMPLETED_TASKS * 100 / TOTAL_TASKS))
137
- else
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.2
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.0)**:
70
- > - 🚀 **Continuous Task Execution**: New `/sp-execute` command for non-stop task completion
71
- > - **Flow State Development**: Execute all tasks without interruptions
72
- > - 🔄 **Smart Task Progression**: Automatic advancement through task lists
73
- > - 🎯 **Batch Processing**: Complete entire features in one command
74
- > - **Previous (v1.2.0)**: Microservice decomposition, service-based planning, smart workflow detection
75
- > - **Previous (v1.1.0)**: Command prefix system, multi-spec workflow, versioned files
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=g_B-PLlQJTQfZFEW4aHlWd_Bvt3ZTqvzjNnFFDR2xOI,270
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=K1kQ5KotsU54kvXYen2fOWGmIM--feQa3bmnEzmRT3k,32831
3
+ specpulse/cli/main.py,sha256=8kiDlmVLe42AjUAaBKfzxSFJqZsk0gddYGGD-gUzs2s,34363
4
4
  specpulse/core/__init__.py,sha256=VKHGdPDM38QM9yDIq05jxol4q8WFlFAeltOzYLsPuto,132
5
- specpulse/core/specpulse.py,sha256=glEYB-AiXN6wTKgVc3Yu0BHjUMByNBzcjMds7CA-FBo,32717
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.sh,sha256=yJhAKa8Ay_76nvFOrGnAzqPhZWvCWuFCI04b5B1k2LI,4302
30
- specpulse/resources/scripts/sp-pulse-spec.sh,sha256=F8UFjx94h38WGCfMPhRV4vtJxMhU3VEllQmbP6Jk9Zk,4297
31
- specpulse/resources/scripts/sp-pulse-task.sh,sha256=SzimpHA4iIX0FrUZXavUl-b9AmXnTvXl6jreq0mCb4A,5144
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-1.3.2.dist-info/licenses/LICENSE,sha256=ImSFezK-Phy0oTBqZipcajsJfPZT8mmg8m3jkgNdNhA,1066
44
- specpulse-1.3.2.dist-info/METADATA,sha256=nNoy4_h7HMxM2IWzmPsELyiYS2Jl9KVGxGta85DKV5I,21109
45
- specpulse-1.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- specpulse-1.3.2.dist-info/entry_points.txt,sha256=GwMaCKWSCFZlJMrTNavEYrnREynIS6dM3B0ILxnaouA,83
47
- specpulse-1.3.2.dist-info/top_level.txt,sha256=pEfkFil5xe38J00OHrBrJ07xpbm6bRnGhI8B07O9KeI,10
48
- specpulse-1.3.2.dist-info/RECORD,,
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,,