specpulse 1.0.4__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.
- specpulse/__init__.py +1 -1
- specpulse/resources/commands/claude/plan.md +156 -45
- specpulse/resources/commands/claude/pulse.md +63 -24
- specpulse/resources/commands/claude/spec.md +110 -31
- specpulse/resources/commands/claude/task.md +202 -43
- specpulse/resources/commands/gemini/plan.toml +13 -5
- specpulse/resources/commands/gemini/pulse.toml +4 -1
- specpulse/resources/commands/gemini/spec.toml +12 -4
- specpulse/resources/commands/gemini/task.toml +17 -5
- specpulse/resources/memory/constitution.md +2 -2
- specpulse/resources/scripts/pulse-init.ps1 +186 -0
- specpulse/resources/scripts/pulse-init.py +171 -0
- specpulse/resources/scripts/pulse-init.sh +80 -21
- specpulse/resources/scripts/pulse-plan.ps1 +251 -0
- specpulse/resources/scripts/pulse-plan.py +191 -0
- specpulse/resources/scripts/pulse-plan.sh +113 -12
- specpulse/resources/scripts/pulse-spec.ps1 +185 -0
- specpulse/resources/scripts/pulse-spec.py +167 -0
- specpulse/resources/scripts/pulse-spec.sh +86 -11
- specpulse/resources/scripts/pulse-task.ps1 +263 -0
- specpulse/resources/scripts/pulse-task.py +237 -0
- specpulse/resources/scripts/pulse-task.sh +123 -9
- specpulse/resources/templates/plan.md +142 -287
- specpulse/resources/templates/spec.md +80 -246
- specpulse/resources/templates/task.md +114 -93
- {specpulse-1.0.4.dist-info → specpulse-1.0.5.dist-info}/METADATA +29 -10
- specpulse-1.0.5.dist-info/RECORD +41 -0
- specpulse-1.0.4.dist-info/RECORD +0 -33
- {specpulse-1.0.4.dist-info → specpulse-1.0.5.dist-info}/WHEEL +0 -0
- {specpulse-1.0.4.dist-info → specpulse-1.0.5.dist-info}/entry_points.txt +0 -0
- {specpulse-1.0.4.dist-info → specpulse-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {specpulse-1.0.4.dist-info → specpulse-1.0.5.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
4
|
+
set -euo pipefail # Exit on error, unset vars, pipe failures
|
5
5
|
|
6
|
-
#
|
7
|
-
|
6
|
+
# Configuration
|
7
|
+
SCRIPT_NAME="$(basename "$0")"
|
8
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
8
9
|
|
9
|
-
#
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
echo "
|
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
|
31
|
-
|
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
|
-
|
35
|
-
|
36
|
-
echo "
|
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"
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# SpecPulse Plan Generation Script
|
2
|
+
# Cross-platform PowerShell equivalent of pulse-plan.sh
|
3
|
+
|
4
|
+
param(
|
5
|
+
[Parameter(Mandatory=$true)]
|
6
|
+
[string]$FeatureDir,
|
7
|
+
|
8
|
+
[string]$PlanContent = ""
|
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-FeatureDir {
|
34
|
+
param([string]$Dir)
|
35
|
+
|
36
|
+
if ([string]::IsNullOrWhiteSpace($Dir)) {
|
37
|
+
Exit-WithError "Feature directory cannot be empty"
|
38
|
+
}
|
39
|
+
|
40
|
+
# Remove non-alphanumeric characters except hyphens and underscores
|
41
|
+
$Sanitized = $Dir -replace '[^a-zA-Z0-9_-]', ''
|
42
|
+
|
43
|
+
if ([string]::IsNullOrWhiteSpace($Sanitized)) {
|
44
|
+
Exit-WithError "Invalid feature directory: '$Dir'"
|
45
|
+
}
|
46
|
+
|
47
|
+
return $Sanitized
|
48
|
+
}
|
49
|
+
|
50
|
+
function Find-FeatureDirectory {
|
51
|
+
if (-not (Test-Path $ContextFile)) {
|
52
|
+
Exit-WithError "No context file found and no feature directory provided"
|
53
|
+
}
|
54
|
+
|
55
|
+
try {
|
56
|
+
$Content = Get-Content $ContextFile -Raw -Encoding UTF8
|
57
|
+
# Look for "Active Feature" section
|
58
|
+
if ($Content -match 'Active Feature:\s*(.+)') {
|
59
|
+
$FeatureDir = $matches[1].Trim()
|
60
|
+
Write-Log "Using active feature from context: $FeatureDir"
|
61
|
+
return $FeatureDir
|
62
|
+
} else {
|
63
|
+
Exit-WithError "No active feature found in context file"
|
64
|
+
}
|
65
|
+
} catch {
|
66
|
+
Exit-WithError "Failed to read context file: $_"
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
function Get-CurrentFeatureDir {
|
71
|
+
param([string]$ProvidedDir)
|
72
|
+
|
73
|
+
if ($ProvidedDir) {
|
74
|
+
return Sanitize-FeatureDir -Dir $ProvidedDir
|
75
|
+
} else {
|
76
|
+
return Find-FeatureDirectory
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
function Validate-PlanStructure {
|
81
|
+
param([string]$PlanFile)
|
82
|
+
|
83
|
+
if (-not (Test-Path $PlanFile)) {
|
84
|
+
Exit-WithError "Plan file does not exist: $PlanFile"
|
85
|
+
}
|
86
|
+
|
87
|
+
# Required sections
|
88
|
+
$RequiredSections = @(
|
89
|
+
"# Implementation Plan:",
|
90
|
+
"## Technology Stack",
|
91
|
+
"## Architecture Overview",
|
92
|
+
"## Implementation Phases"
|
93
|
+
)
|
94
|
+
|
95
|
+
$Content = Get-Content $PlanFile -Raw -Encoding UTF8
|
96
|
+
$MissingSections = @()
|
97
|
+
|
98
|
+
foreach ($Section in $RequiredSections) {
|
99
|
+
if ($Content -notmatch [regex]::Escape($Section)) {
|
100
|
+
$MissingSections += $Section
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
if ($MissingSections.Count -gt 0) {
|
105
|
+
Write-Log "WARNING: Missing required sections: $($MissingSections -join ', ')"
|
106
|
+
}
|
107
|
+
|
108
|
+
return $MissingSections.Count
|
109
|
+
}
|
110
|
+
|
111
|
+
function Check-ConstitutionalGates {
|
112
|
+
param([string]$PlanFile)
|
113
|
+
|
114
|
+
if (-not (Test-Path $PlanFile)) {
|
115
|
+
Exit-WithError "Plan file does not exist: $PlanFile"
|
116
|
+
}
|
117
|
+
|
118
|
+
$Content = Get-Content $PlanFile -Raw -Encoding UTF8
|
119
|
+
$GatesStatus = @{}
|
120
|
+
|
121
|
+
# Check for constitutional gates compliance
|
122
|
+
$Gates = @{
|
123
|
+
"Simplicity Gate" = "Article VII: Simplicity Gate"
|
124
|
+
"Anti-Abstraction Gate" = "Article VII: Anti-Abstraction Gate"
|
125
|
+
"Test-First Gate" = "Article III: Test-First Gate"
|
126
|
+
"Integration-First Gate" = "Article VIII: Integration-First Gate"
|
127
|
+
"Research Gate" = "Article VI: Research Gate"
|
128
|
+
}
|
129
|
+
|
130
|
+
foreach ($Gate in $Gates.GetEnumerator()) {
|
131
|
+
if ($Content -match $Gate.Value) {
|
132
|
+
$GatesStatus[$Gate.Key] = "COMPLETED"
|
133
|
+
} else {
|
134
|
+
$GatesStatus[$Gate.Key] = "PENDING"
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
return $GatesStatus
|
139
|
+
}
|
140
|
+
|
141
|
+
function Analyze-Complexity {
|
142
|
+
param([string]$PlanFile)
|
143
|
+
|
144
|
+
if (-not (Test-Path $PlanFile)) {
|
145
|
+
Exit-WithError "Plan file does not exist: $PlanFile"
|
146
|
+
}
|
147
|
+
|
148
|
+
$Content = Get-Content $PlanFile -Raw -Encoding UTF8
|
149
|
+
|
150
|
+
# Count complexity exceptions
|
151
|
+
$ExceptionMatches = [regex]::Matches($Content, "Complexity Exception")
|
152
|
+
$ExceptionCount = $ExceptionMatches.Count
|
153
|
+
|
154
|
+
# Check for complexity tracking
|
155
|
+
$HasComplexityTracking = $Content -match "Complexity Tracking"
|
156
|
+
|
157
|
+
# Count modules/projects mentioned
|
158
|
+
$ModuleMatches = [regex]::Matches($Content, "(?i)(module|project)s?\s*[:\-]?\s*\d+")
|
159
|
+
$ModuleCount = 0
|
160
|
+
if ($ModuleMatches.Count -gt 0) {
|
161
|
+
foreach ($Match in $ModuleMatches) {
|
162
|
+
if ($Match.Value -match "\d+") {
|
163
|
+
$ModuleCount = [int]$matches[0]
|
164
|
+
break
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
return @{
|
170
|
+
ExceptionCount = $ExceptionCount
|
171
|
+
HasComplexityTracking = $HasComplexityTracking
|
172
|
+
ModuleCount = $ModuleCount
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
# Main execution
|
177
|
+
Write-Log "Processing plan..."
|
178
|
+
|
179
|
+
# Get and sanitize feature directory
|
180
|
+
$SanitizedDir = Get-CurrentFeatureDir -ProvidedDir $FeatureDir
|
181
|
+
|
182
|
+
# Set file paths
|
183
|
+
$PlanFile = Join-Path $ProjectRoot "plans" $SanitizedDir "plan.md"
|
184
|
+
$SpecFile = Join-Path $ProjectRoot "specs" $SanitizedDir "spec.md"
|
185
|
+
$TemplateFile = Join-Path $TemplatesDir "plan.md"
|
186
|
+
|
187
|
+
# Ensure plans directory exists
|
188
|
+
$PlanDir = Split-Path $PlanFile -Parent
|
189
|
+
New-Item -ItemType Directory -Path $PlanDir -Force | Out-Null
|
190
|
+
|
191
|
+
# Handle plan content
|
192
|
+
if ($PlanContent) {
|
193
|
+
Write-Log "Updating plan file: $PlanFile"
|
194
|
+
try {
|
195
|
+
Set-Content -Path $PlanFile -Value $PlanContent -Encoding UTF8
|
196
|
+
} catch {
|
197
|
+
Exit-WithError "Failed to write plan content: $_"
|
198
|
+
}
|
199
|
+
} else {
|
200
|
+
# Ensure plan file exists from template
|
201
|
+
if (-not (Test-Path $PlanFile)) {
|
202
|
+
if (-not (Test-Path $TemplateFile)) {
|
203
|
+
Exit-WithError "Template not found: $TemplateFile"
|
204
|
+
}
|
205
|
+
|
206
|
+
Write-Log "Creating plan file from template: $PlanFile"
|
207
|
+
try {
|
208
|
+
Copy-Item $TemplateFile $PlanFile -Force
|
209
|
+
} catch {
|
210
|
+
Exit-WithError "Failed to copy plan template: $_"
|
211
|
+
}
|
212
|
+
} else {
|
213
|
+
Write-Log "Plan file already exists: $PlanFile"
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
# Validate plan structure
|
218
|
+
Write-Log "Validating plan structure..."
|
219
|
+
$MissingSections = Validate-PlanStructure -PlanFile $PlanFile
|
220
|
+
|
221
|
+
# Check constitutional gates
|
222
|
+
Write-Log "Checking constitutional gates..."
|
223
|
+
$GatesStatus = Check-ConstitutionalGates -PlanFile $PlanFile
|
224
|
+
|
225
|
+
# Analyze complexity
|
226
|
+
Write-Log "Analyzing complexity..."
|
227
|
+
$ComplexityAnalysis = Analyze-Complexity -PlanFile $PlanFile
|
228
|
+
|
229
|
+
# Count completed constitutional gates
|
230
|
+
$CompletedGates = 0
|
231
|
+
foreach ($Status in $GatesStatus.Values) {
|
232
|
+
if ($Status -eq "COMPLETED") {
|
233
|
+
$CompletedGates++
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
# Check if specification exists
|
238
|
+
$SpecExists = Test-Path $SpecFile
|
239
|
+
|
240
|
+
Write-Log "Plan processing completed successfully"
|
241
|
+
|
242
|
+
# Output results
|
243
|
+
Write-Host "PLAN_FILE=$PlanFile"
|
244
|
+
Write-Host "SPEC_EXISTS=$SpecExists"
|
245
|
+
Write-Host "MISSING_SECTIONS=$MissingSections"
|
246
|
+
Write-Host "COMPLETED_GATES=$CompletedGates"
|
247
|
+
Write-Host "TOTAL_GATES=$($GatesStatus.Count)"
|
248
|
+
Write-Host "COMPLEXITY_EXCEPTIONS=$($ComplexityAnalysis.ExceptionCount)"
|
249
|
+
Write-Host "HAS_COMPLEXITY_TRACKING=$($ComplexityAnalysis.HasComplexityTracking)"
|
250
|
+
Write-Host "MODULE_COUNT=$($ComplexityAnalysis.ModuleCount)"
|
251
|
+
Write-Host "STATUS=processed"
|