specpulse 1.0.3__py3-none-any.whl → 1.0.5__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.
Files changed (32) hide show
  1. specpulse/__init__.py +1 -1
  2. specpulse/resources/commands/claude/plan.md +180 -50
  3. specpulse/resources/commands/claude/pulse.md +79 -24
  4. specpulse/resources/commands/claude/spec.md +156 -43
  5. specpulse/resources/commands/claude/task.md +229 -48
  6. specpulse/resources/commands/gemini/plan.toml +59 -48
  7. specpulse/resources/commands/gemini/pulse.toml +21 -39
  8. specpulse/resources/commands/gemini/spec.toml +40 -28
  9. specpulse/resources/commands/gemini/task.toml +69 -51
  10. specpulse/resources/memory/constitution.md +2 -2
  11. specpulse/resources/scripts/pulse-init.ps1 +186 -0
  12. specpulse/resources/scripts/pulse-init.py +171 -0
  13. specpulse/resources/scripts/pulse-init.sh +80 -21
  14. specpulse/resources/scripts/pulse-plan.ps1 +251 -0
  15. specpulse/resources/scripts/pulse-plan.py +191 -0
  16. specpulse/resources/scripts/pulse-plan.sh +113 -12
  17. specpulse/resources/scripts/pulse-spec.ps1 +185 -0
  18. specpulse/resources/scripts/pulse-spec.py +167 -0
  19. specpulse/resources/scripts/pulse-spec.sh +86 -11
  20. specpulse/resources/scripts/pulse-task.ps1 +263 -0
  21. specpulse/resources/scripts/pulse-task.py +237 -0
  22. specpulse/resources/scripts/pulse-task.sh +123 -9
  23. specpulse/resources/templates/plan.md +142 -287
  24. specpulse/resources/templates/spec.md +80 -246
  25. specpulse/resources/templates/task.md +114 -93
  26. {specpulse-1.0.3.dist-info → specpulse-1.0.5.dist-info}/METADATA +67 -34
  27. specpulse-1.0.5.dist-info/RECORD +41 -0
  28. specpulse-1.0.3.dist-info/RECORD +0 -33
  29. {specpulse-1.0.3.dist-info → specpulse-1.0.5.dist-info}/WHEEL +0 -0
  30. {specpulse-1.0.3.dist-info → specpulse-1.0.5.dist-info}/entry_points.txt +0 -0
  31. {specpulse-1.0.3.dist-info → specpulse-1.0.5.dist-info}/licenses/LICENSE +0 -0
  32. {specpulse-1.0.3.dist-info → specpulse-1.0.5.dist-info}/top_level.txt +0 -0
@@ -124,7 +124,7 @@ complexity_exceptions:
124
124
  violation: "Using 4 projects instead of 3"
125
125
  justification: "Authentication requires separate service for security isolation"
126
126
  approved_by: "Team Lead"
127
- date: "2024-01-15"
127
+ date: "2025-09-11"
128
128
  ```
129
129
 
130
130
  ### Amendment Process
@@ -233,5 +233,5 @@ While principles are immutable, their application can evolve:
233
233
  ## Living Constitution
234
234
  This constitution is a living document that learns from experience while maintaining core principles. Each project iteration strengthens these principles through practical application and refinement.
235
235
 
236
- *Last Updated: 2024-12-27*
236
+ *Last Updated: 2025-09-11*
237
237
  *Version: 2.0 - Full SDD Methodology Implementation*
@@ -0,0 +1,186 @@
1
+ # SpecPulse Feature Initialization Script
2
+ # Cross-platform PowerShell equivalent of pulse-init.sh
3
+
4
+ param(
5
+ [Parameter(Mandatory=$true)]
6
+ [string]$FeatureName,
7
+
8
+ [string]$CustomId = ""
9
+ )
10
+
11
+ $ErrorActionPreference = "Stop"
12
+ $ProgressPreference = "SilentlyContinue"
13
+
14
+ # Configuration
15
+ $ScriptName = $MyInvocation.MyCommand.Name
16
+ $ProjectRoot = $PSScriptRoot | Split-Path -Parent | Split-Path -Parent
17
+ $MemoryDir = Join-Path $ProjectRoot "memory"
18
+ $ContextFile = Join-Path $MemoryDir "context.md"
19
+ $TemplatesDir = Join-Path $ProjectRoot "resources" "templates"
20
+
21
+ function Write-Log {
22
+ param([string]$Message)
23
+ $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
24
+ Write-Host "[$Timestamp] $ScriptName : $Message" -ForegroundColor Yellow
25
+ }
26
+
27
+ function Exit-WithError {
28
+ param([string]$Message)
29
+ Write-Log "ERROR: $Message"
30
+ exit 1
31
+ }
32
+
33
+ function Sanitize-FeatureName {
34
+ param([string]$Name)
35
+
36
+ if ([string]::IsNullOrWhiteSpace($Name)) {
37
+ Exit-WithError "Feature name cannot be empty"
38
+ }
39
+
40
+ # Convert to lowercase, replace spaces and special chars with hyphens
41
+ $Sanitized = $Name.ToLower() -replace '[^a-z0-9-]', '-'
42
+ $Sanitized = $Sanitized -replace '-+', '-' # Remove consecutive hyphens
43
+ $Sanitized = $Sanitized.Trim('-') # Remove leading/trailing hyphens
44
+
45
+ if ([string]::IsNullOrWhiteSpace($Sanitized)) {
46
+ Exit-WithError "Invalid feature name: '$Name'"
47
+ }
48
+
49
+ return $Sanitized
50
+ }
51
+
52
+ function Get-FeatureId {
53
+ param([string]$CustomId)
54
+
55
+ if ($CustomId) {
56
+ return "{0:D3}" -f [int]$CustomId
57
+ }
58
+
59
+ # Find existing feature directories
60
+ $SpecsDir = Join-Path $ProjectRoot "specs"
61
+ if (Test-Path $SpecsDir) {
62
+ $Existing = Get-ChildItem -Path $SpecsDir -Directory |
63
+ Where-Object { $_.Name -match '^\d+$' } |
64
+ Sort-Object Name
65
+ $NextId = $Existing.Count + 1
66
+ } else {
67
+ $NextId = 1
68
+ }
69
+
70
+ return "{0:D3}" -f $NextId
71
+ }
72
+
73
+ function Create-Directories {
74
+ param([string]$BranchName)
75
+
76
+ $Directories = @(
77
+ (Join-Path $ProjectRoot "specs" $BranchName),
78
+ (Join-Path $ProjectRoot "plans" $BranchName),
79
+ (Join-Path $ProjectRoot "tasks" $BranchName)
80
+ )
81
+
82
+ foreach ($Directory in $Directories) {
83
+ try {
84
+ New-Item -ItemType Directory -Path $Directory -Force | Out-Null
85
+ Write-Log "Created directory: $Directory"
86
+ } catch {
87
+ Exit-WithError "Failed to create directory $Directory : $_"
88
+ }
89
+ }
90
+ }
91
+
92
+ function Copy-Templates {
93
+ param([string]$BranchName)
94
+
95
+ $Templates = @{
96
+ "spec.md" = (Join-Path $ProjectRoot "specs" $BranchName "spec.md")
97
+ "plan.md" = (Join-Path $ProjectRoot "plans" $BranchName "plan.md")
98
+ "task.md" = (Join-Path $ProjectRoot "tasks" $BranchName "tasks.md")
99
+ }
100
+
101
+ foreach ($Template in $Templates.GetEnumerator()) {
102
+ $TemplatePath = Join-Path $TemplatesDir $Template.Key
103
+ $TargetPath = $Template.Value
104
+
105
+ if (-not (Test-Path $TemplatePath)) {
106
+ Exit-WithError "Template not found: $TemplatePath"
107
+ }
108
+
109
+ try {
110
+ Copy-Item $TemplatePath $TargetPath -Force
111
+ Write-Log "Copied template to: $TargetPath"
112
+ } catch {
113
+ Exit-WithError "Failed to copy template $TemplatePath : $_"
114
+ }
115
+ }
116
+ }
117
+
118
+ function Update-Context {
119
+ param(
120
+ [string]$FeatureName,
121
+ [string]$FeatureId,
122
+ [string]$BranchName
123
+ )
124
+
125
+ try {
126
+ New-Item -ItemType Directory -Path $MemoryDir -Force | Out-Null
127
+
128
+ $ContextEntry = @"
129
+
130
+ ## Active Feature: $FeatureName
131
+ - Feature ID: $FeatureId
132
+ - Branch: $BranchName
133
+ - Started: $(Get-Date -Format "o")
134
+ "@
135
+
136
+ Add-Content -Path $ContextFile -Value $ContextEntry -Encoding UTF8
137
+ Write-Log "Updated context file: $ContextFile"
138
+ } catch {
139
+ Exit-WithError "Failed to update context file: $_"
140
+ }
141
+ }
142
+
143
+ function New-GitBranch {
144
+ param([string]$BranchName)
145
+
146
+ $GitDir = Join-Path $ProjectRoot ".git"
147
+ if (-not (Test-Path $GitDir)) {
148
+ return
149
+ }
150
+
151
+ try {
152
+ # Check if branch already exists
153
+ $ExistingBranch = git branch --list $BranchName
154
+ if ($ExistingBranch) {
155
+ Write-Log "Git branch '$BranchName' already exists, checking out"
156
+ git checkout $BranchName
157
+ } else {
158
+ Write-Log "Creating new git branch '$BranchName'"
159
+ git checkout -b $BranchName
160
+ }
161
+ } catch {
162
+ Exit-WithError "Git operation failed: $_"
163
+ }
164
+ }
165
+
166
+ # Main execution
167
+ Write-Log "Initializing feature: $FeatureName"
168
+
169
+ # Sanitize and generate identifiers
170
+ $SanitizedName = Sanitize-FeatureName -Name $FeatureName
171
+ $FeatureId = Get-FeatureId -CustomId $CustomId
172
+ $BranchName = "$FeatureId-$SanitizedName"
173
+
174
+ # Create structure
175
+ Create-Directories -BranchName $BranchName
176
+ Copy-Templates -BranchName $BranchName
177
+ Update-Context -FeatureName $FeatureName -FeatureId $FeatureId -BranchName $BranchName
178
+ New-GitBranch -BranchName $BranchName
179
+
180
+ # Output results
181
+ Write-Host "BRANCH_NAME=$BranchName"
182
+ Write-Host "SPEC_DIR=specs/$BranchName"
183
+ Write-Host "FEATURE_ID=$FeatureId"
184
+ Write-Host "STATUS=initialized"
185
+
186
+ Write-Log "Successfully initialized feature '$FeatureName' with ID $FeatureId"
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SpecPulse Feature Initialization Script
4
+ Cross-platform Python equivalent of pulse-init.sh
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import subprocess
10
+ import re
11
+ import datetime
12
+ from pathlib import Path
13
+
14
+ class SpecPulseInit:
15
+ def __init__(self):
16
+ self.script_name = Path(__file__).name
17
+ self.project_root = Path(__file__).parent.parent.parent
18
+ self.memory_dir = self.project_root / "memory"
19
+ self.context_file = self.memory_dir / "context.md"
20
+ self.templates_dir = self.project_root / "resources" / "templates"
21
+
22
+ def log(self, message):
23
+ """Log messages with timestamp"""
24
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
25
+ print(f"[{timestamp}] {self.script_name}: {message}", file=sys.stderr)
26
+
27
+ def error_exit(self, message):
28
+ """Exit with error message"""
29
+ self.log(f"ERROR: {message}")
30
+ sys.exit(1)
31
+
32
+ def sanitize_feature_name(self, feature_name):
33
+ """Sanitize feature name for safe directory naming"""
34
+ if not feature_name:
35
+ self.error_exit("Feature name cannot be empty")
36
+
37
+ # Convert to lowercase, replace spaces and special chars with hyphens
38
+ sanitized = re.sub(r'[^a-z0-9-]', '-', feature_name.lower())
39
+ sanitized = re.sub(r'-+', '-', sanitized) # Remove consecutive hyphens
40
+ sanitized = sanitized.strip('-') # Remove leading/trailing hyphens
41
+
42
+ if not sanitized:
43
+ self.error_exit(f"Invalid feature name: '{feature_name}'")
44
+
45
+ return sanitized
46
+
47
+ def get_feature_id(self, custom_id=None):
48
+ """Get next feature ID"""
49
+ if custom_id:
50
+ return f"{int(custom_id):03d}"
51
+
52
+ # Find existing feature directories
53
+ specs_dir = self.project_root / "specs"
54
+ if specs_dir.exists():
55
+ existing = [d for d in specs_dir.iterdir() if d.is_dir() and d.name.isdigit()]
56
+ next_id = len(existing) + 1
57
+ else:
58
+ next_id = 1
59
+
60
+ return f"{next_id:03d}"
61
+
62
+ def create_directories(self, branch_name):
63
+ """Create feature directories"""
64
+ dirs_to_create = [
65
+ self.project_root / "specs" / branch_name,
66
+ self.project_root / "plans" / branch_name,
67
+ self.project_root / "tasks" / branch_name
68
+ ]
69
+
70
+ for directory in dirs_to_create:
71
+ try:
72
+ directory.mkdir(parents=True, exist_ok=True)
73
+ self.log(f"Created directory: {directory}")
74
+ except Exception as e:
75
+ self.error_exit(f"Failed to create directory {directory}: {e}")
76
+
77
+ def copy_templates(self, branch_name):
78
+ """Copy templates to feature directories"""
79
+ templates = {
80
+ "spec.md": self.project_root / "specs" / branch_name / "spec.md",
81
+ "plan.md": self.project_root / "plans" / branch_name / "plan.md",
82
+ "task.md": self.project_root / "tasks" / branch_name / "tasks.md"
83
+ }
84
+
85
+ for template_name, target_path in templates.items():
86
+ template_path = self.templates_dir / template_name
87
+ if not template_path.exists():
88
+ self.error_exit(f"Template not found: {template_path}")
89
+
90
+ try:
91
+ target_path.write_text(template_path.read_text())
92
+ self.log(f"Copied template to: {target_path}")
93
+ except Exception as e:
94
+ self.error_exit(f"Failed to copy template {template_path}: {e}")
95
+
96
+ def update_context(self, feature_name, feature_id, branch_name):
97
+ """Update context file"""
98
+ try:
99
+ self.memory_dir.mkdir(parents=True, exist_ok=True)
100
+
101
+ context_entry = f"""
102
+
103
+ ## Active Feature: {feature_name}
104
+ - Feature ID: {feature_id}
105
+ - Branch: {branch_name}
106
+ - Started: {datetime.datetime.now().isoformat()}
107
+ """
108
+
109
+ with open(self.context_file, 'a', encoding='utf-8') as f:
110
+ f.write(context_entry)
111
+
112
+ self.log(f"Updated context file: {self.context_file}")
113
+ except Exception as e:
114
+ self.error_exit(f"Failed to update context file: {e}")
115
+
116
+ def create_git_branch(self, branch_name):
117
+ """Create git branch if in git repository"""
118
+ git_dir = self.project_root / ".git"
119
+ if not git_dir.exists():
120
+ return
121
+
122
+ try:
123
+ # Check if branch already exists
124
+ result = subprocess.run(
125
+ ["git", "branch", "--list", branch_name],
126
+ cwd=self.project_root,
127
+ capture_output=True,
128
+ text=True
129
+ )
130
+
131
+ if branch_name in result.stdout:
132
+ self.log(f"Git branch '{branch_name}' already exists, checking out")
133
+ subprocess.run(["git", "checkout", branch_name], cwd=self.project_root, check=True)
134
+ else:
135
+ self.log(f"Creating new git branch '{branch_name}'")
136
+ subprocess.run(["git", "checkout", "-b", branch_name], cwd=self.project_root, check=True)
137
+ except subprocess.CalledProcessError as e:
138
+ self.error_exit(f"Git operation failed: {e}")
139
+
140
+ def main(self, args):
141
+ """Main execution function"""
142
+ if len(args) < 1:
143
+ self.error_exit("Usage: python pulse-init.py <feature-name> [feature-id]")
144
+
145
+ feature_name = args[0]
146
+ custom_id = args[1] if len(args) > 1 else None
147
+
148
+ self.log(f"Initializing feature: {feature_name}")
149
+
150
+ # Sanitize and generate identifiers
151
+ sanitized_name = self.sanitize_feature_name(feature_name)
152
+ feature_id = self.get_feature_id(custom_id)
153
+ branch_name = f"{feature_id}-{sanitized_name}"
154
+
155
+ # Create structure
156
+ self.create_directories(branch_name)
157
+ self.copy_templates(branch_name)
158
+ self.update_context(feature_name, feature_id, branch_name)
159
+ self.create_git_branch(branch_name)
160
+
161
+ # Output results
162
+ print(f"BRANCH_NAME={branch_name}")
163
+ print(f"SPEC_DIR=specs/{branch_name}")
164
+ print(f"FEATURE_ID={feature_id}")
165
+ print("STATUS=initialized")
166
+
167
+ self.log(f"Successfully initialized feature '{feature_name}' with ID {feature_id}")
168
+
169
+ if __name__ == "__main__":
170
+ init = SpecPulseInit()
171
+ init.main(sys.argv[1:])
@@ -1,37 +1,96 @@
1
1
  #!/bin/bash
2
2
  # Initialize a new feature with SpecPulse
3
3
 
4
- FEATURE_NAME="${1:-feature}"
4
+ set -euo pipefail # Exit on error, unset vars, pipe failures
5
5
 
6
- # Get next feature ID
7
- FEATURE_ID=$(printf "%03d" $(ls -d specs/[0-9]* 2>/dev/null | wc -l | xargs expr 1 +))
6
+ # Configuration
7
+ SCRIPT_NAME="$(basename "$0")"
8
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
8
9
 
9
- # Create branch name
10
- BRANCH_NAME="${FEATURE_ID}-${FEATURE_NAME}"
10
+ # Function to log messages
11
+ log() {
12
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $SCRIPT_NAME: $1" >&2
13
+ }
14
+
15
+ # Function to handle errors
16
+ error_exit() {
17
+ log "ERROR: $1"
18
+ exit 1
19
+ }
20
+
21
+ # Validate arguments
22
+ if [ $# -eq 0 ]; then
23
+ error_exit "Usage: $SCRIPT_NAME <feature-name> [feature-id]"
24
+ fi
25
+
26
+ FEATURE_NAME="$1"
27
+ CUSTOM_ID="${2:-}"
28
+
29
+ # Sanitize feature name
30
+ BRANCH_SAFE_NAME=$(echo "$FEATURE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
31
+
32
+ if [ -z "$BRANCH_SAFE_NAME" ]; then
33
+ error_exit "Invalid feature name: '$FEATURE_NAME'"
34
+ fi
35
+
36
+ # Get feature ID
37
+ if [ -n "$CUSTOM_ID" ]; then
38
+ FEATURE_ID=$(printf "%03d" "$CUSTOM_ID")
39
+ else
40
+ FEATURE_ID=$(printf "%03d" $(find "$PROJECT_ROOT/specs" -maxdepth 1 -type d -name '[0-9]*' 2>/dev/null | wc -l | awk '{print $1 + 1}'))
41
+ fi
42
+
43
+ BRANCH_NAME="${FEATURE_ID}-${BRANCH_SAFE_NAME}"
11
44
 
12
45
  # Create directories
13
- mkdir -p "specs/${BRANCH_NAME}"
14
- mkdir -p "plans/${BRANCH_NAME}"
15
- mkdir -p "tasks/${BRANCH_NAME}"
46
+ SPECS_DIR="$PROJECT_ROOT/specs/${BRANCH_NAME}"
47
+ PLANS_DIR="$PROJECT_ROOT/plans/${BRANCH_NAME}"
48
+ TASKS_DIR="$PROJECT_ROOT/tasks/${BRANCH_NAME}"
49
+
50
+ log "Creating feature directories for '$FEATURE_NAME'"
51
+
52
+ mkdir -p "$SPECS_DIR" || error_exit "Failed to create specs directory: $SPECS_DIR"
53
+ mkdir -p "$PLANS_DIR" || error_exit "Failed to create plans directory: $PLANS_DIR"
54
+ mkdir -p "$TASKS_DIR" || error_exit "Failed to create tasks directory: $TASKS_DIR"
16
55
 
17
56
  # Create initial files from templates
18
- cp templates/spec.md "specs/${BRANCH_NAME}/spec.md"
19
- cp templates/plan.md "plans/${BRANCH_NAME}/plan.md"
20
- cp templates/task.md "tasks/${BRANCH_NAME}/tasks.md"
57
+ TEMPLATE_DIR="$PROJECT_ROOT/templates"
58
+
59
+ if [ ! -f "$TEMPLATE_DIR/spec.md" ]; then
60
+ error_exit "Template not found: $TEMPLATE_DIR/spec.md"
61
+ fi
62
+
63
+ cp "$TEMPLATE_DIR/spec.md" "$SPECS_DIR/spec.md" || error_exit "Failed to copy spec template"
64
+ cp "$TEMPLATE_DIR/plan.md" "$PLANS_DIR/plan.md" || error_exit "Failed to copy plan template"
65
+ cp "$TEMPLATE_DIR/task.md" "$TASKS_DIR/tasks.md" || error_exit "Failed to copy task template"
21
66
 
22
67
  # Update context
23
- echo "" >> memory/context.md
24
- echo "## Active Feature: ${FEATURE_NAME}" >> memory/context.md
25
- echo "- Feature ID: ${FEATURE_ID}" >> memory/context.md
26
- echo "- Branch: ${BRANCH_NAME}" >> memory/context.md
27
- echo "- Started: $(date -Iseconds)" >> memory/context.md
68
+ CONTEXT_FILE="$PROJECT_ROOT/memory/context.md"
69
+ mkdir -p "$(dirname "$CONTEXT_FILE")"
70
+
71
+ {
72
+ echo ""
73
+ echo "## Active Feature: $FEATURE_NAME"
74
+ echo "- Feature ID: $FEATURE_ID"
75
+ echo "- Branch: $BRANCH_NAME"
76
+ echo "- Started: $(date -Iseconds)"
77
+ } >> "$CONTEXT_FILE" || error_exit "Failed to update context file"
28
78
 
29
79
  # Create git branch if in git repo
30
- if [ -d .git ]; then
31
- git checkout -b "${BRANCH_NAME}" 2>/dev/null || git checkout "${BRANCH_NAME}"
80
+ if [ -d "$PROJECT_ROOT/.git" ]; then
81
+ cd "$PROJECT_ROOT"
82
+ if git rev-parse --verify "$BRANCH_NAME" >/dev/null 2>&1; then
83
+ log "Git branch '$BRANCH_NAME' already exists, checking out"
84
+ git checkout "$BRANCH_NAME" || error_exit "Failed to checkout existing branch"
85
+ else
86
+ log "Creating new git branch '$BRANCH_NAME'"
87
+ git checkout -b "$BRANCH_NAME" || error_exit "Failed to create new branch"
88
+ fi
32
89
  fi
33
90
 
34
- echo "BRANCH_NAME=${BRANCH_NAME}"
35
- echo "SPEC_DIR=specs/${BRANCH_NAME}"
36
- echo "FEATURE_ID=${FEATURE_ID}"
91
+ log "Successfully initialized feature '$FEATURE_NAME' with ID $FEATURE_ID"
92
+
93
+ echo "BRANCH_NAME=$BRANCH_NAME"
94
+ echo "SPEC_DIR=$SPECS_DIR"
95
+ echo "FEATURE_ID=$FEATURE_ID"
37
96
  echo "STATUS=initialized"