vibesuite 1.0.0
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.
- package/.agent/skills/agent-recovery/SKILL.md +147 -0
- package/.agent/skills/code-review/SKILL.md +81 -0
- package/.agent/skills/component-analysis/SKILL.md +103 -0
- package/.agent/skills/git-worktree/SKILL.md +78 -0
- package/.agent/skills/github-ops/SKILL.md +220 -0
- package/.agent/skills/github-ops/scripts/publish_issues.ps1 +443 -0
- package/.agent/skills/github-ops/scripts/smart-ops.ps1 +128 -0
- package/.agent/skills/github-ops/scripts/smart-ops.sh +130 -0
- package/.agent/skills/google-trends/SKILL.md +157 -0
- package/.agent/skills/google-trends/scripts/node_modules/.modules.yaml +21 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/LICENSE +22 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/Readme.md +1157 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs +16 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/index.js +24 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/argument.js +149 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js +2509 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/error.js +39 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/help.js +520 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/option.js +330 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/package-support.json +16 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/package.json +84 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/typings/esm.d.mts +3 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/commander@12.1.0/node_modules/commander/typings/index.d.ts +969 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/CHANGES.md +132 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/LICENSE +21 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/README.md +561 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/lib/google-trends-api.min.js +2 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/package.json +68 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/src/api.js +18 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/src/index.js +27 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/src/request.js +69 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/google-trends-api@4.9.2/node_modules/google-trends-api/src/utilities.js +395 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm/lock.yaml +31 -0
- package/.agent/skills/google-trends/scripts/node_modules/.pnpm-workspace-state-v1.json +25 -0
- package/.agent/skills/google-trends/scripts/package.json +17 -0
- package/.agent/skills/google-trends/scripts/pnpm-lock.yaml +31 -0
- package/.agent/skills/google-trends/scripts/search.js +168 -0
- package/.agent/skills/high-fidelity-extraction/SKILL.md +59 -0
- package/.agent/skills/prime-agent/SKILL.md +97 -0
- package/.agent/skills/security-audit/SKILL.md +81 -0
- package/.agent/skills/seo-ready/SKILL.md +133 -0
- package/.agent/skills/spawn-task/SKILL.md +130 -0
- package/.agent/skills/sync-docs/SKILL.md +88 -0
- package/.agent/skills/vercel-ai-sdk/SKILL.md +34083 -0
- package/.agent/skills/youtube-pipeline/SKILL.md +194 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase1-strategy.md +224 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase2-packaging.md +148 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase3-scripting.md +197 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase3.5-shorts.md +271 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase4-production.md +193 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-phase5-repurposing.md +159 -0
- package/.agent/skills/youtube-pipeline/resources/youtube-pipeline.md +161 -0
- package/.agent/skills/youtube-pipeline/scripts/parse_yt_studio.ps1 +150 -0
- package/.agent/workflows/LEGACY/ANTIGRAVITY_TOOLBOX.md +200 -0
- package/.agent/workflows/LEGACY/analyze_component.md +141 -0
- package/.agent/workflows/LEGACY/build_vibecode_project.md +154 -0
- package/.agent/workflows/LEGACY/deep_code_audit.md +79 -0
- package/.agent/workflows/LEGACY/gemini-orchestrate.md +63 -0
- package/.agent/workflows/LEGACY/git_worktree.md +71 -0
- package/.agent/workflows/LEGACY/init_smart_ops.md +101 -0
- package/.agent/workflows/LEGACY/multi_agent_strategy.md +62 -0
- package/.agent/workflows/LEGACY/orchestrate.md +321 -0
- package/.agent/workflows/LEGACY/seo_ready.md +249 -0
- package/.agent/workflows/LEGACY/vibe-orchestrator.md +305 -0
- package/.agent/workflows/LEGACY/youtube-phase1-strategy.md +224 -0
- package/.agent/workflows/LEGACY/youtube-phase2-packaging.md +148 -0
- package/.agent/workflows/LEGACY/youtube-phase3-scripting.md +197 -0
- package/.agent/workflows/LEGACY/youtube-phase3.5-shorts.md +271 -0
- package/.agent/workflows/LEGACY/youtube-phase4-production.md +193 -0
- package/.agent/workflows/LEGACY/youtube-phase5-repurposing.md +159 -0
- package/.agent/workflows/LEGACY/youtube-pipeline.md +161 -0
- package/.agent/workflows/README.md +349 -0
- package/.agent/workflows/Vercel Ai SDK.md +34083 -0
- package/.agent/workflows/agent_reset.md +138 -0
- package/.agent/workflows/build_vibecode_project_v2.md +158 -0
- package/.agent/workflows/escalate.md +112 -0
- package/.agent/workflows/init_vibecode_design.md +98 -0
- package/.agent/workflows/init_vibecode_genesis.md +137 -0
- package/.agent/workflows/migrate.md +135 -0
- package/.agent/workflows/prime_agent.md +211 -0
- package/.agent/workflows/reverse_genesis.md +132 -0
- package/.agent/workflows/review_code.md +133 -0
- package/.agent/workflows/spawn-jstar-code-review.md +121 -0
- package/.agent/workflows/spawn_task.md +187 -0
- package/.agent/workflows/sync_docs.md +90 -0
- package/Legacy (Manual Method)/0 VibeCode User Manual.md +173 -0
- package/Legacy (Manual Method)/1 Project Genesis Protocol The VibeCode Workflow.md +89 -0
- package/Legacy (Manual Method)/2/342/234/250 ULTIMATE ORCHESTRATION PROMPT/342/234/250.md" +114 -0
- package/Legacy (Manual Method)/3 Design System Genesis Protocol.md +75 -0
- package/Legacy (Manual Method)/3.1.1 my_design_system_rules.md +177 -0
- package/Legacy (Manual Method)/3.1.2 Material You M3 Genesis Protocol.md +73 -0
- package/Legacy (Manual Method)/4 The Ultimate GitHub Issue Meta-Prompt Template.md +54 -0
- package/Legacy (Manual Method)/5 The Escalation & Handoff Protocol.md +97 -0
- package/Legacy (Manual Method)/8 The Seamless Migration Meta-Prompt (Your Reusable Tool).md +38 -0
- package/Legacy (Manual Method)/9 The Reverse Genesis Protocol.md +75 -0
- package/README.md +209 -0
- package/VibeCode-Agents (e.g Kilo-code)/README.md +142 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-analyzer.yaml +254 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-architect.yaml +397 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-auditor.yaml +325 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-builder.yaml +472 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-designer.yaml +305 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-documentor.yaml +222 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-escalator.yaml +255 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-isolator.yaml +332 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-onboarder.yaml +335 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-orchestrator.yaml +365 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-orchestrator_legacy.yaml +284 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-orchestrator_v1.yaml +336 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-primer.yaml +213 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-reviewer.yaml +233 -0
- package/VibeCode-Agents (e.g Kilo-code)/vibe-spawner.yaml +259 -0
- package/bin/vibesuite.js +2 -0
- package/package.json +43 -0
- package/src/cli.js +159 -0
- package/src/utils.js +76 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$DryRun = $false,
|
|
3
|
+
[switch]$Update = $false, # Update existing issues instead of skipping
|
|
4
|
+
[switch]$SyncDates = $false, # Sync dates to GitHub Project
|
|
5
|
+
[switch]$AutoArchive = $false, # Move completed issues to closed/ subfolder
|
|
6
|
+
[switch]$Force = $false, # Force sync all files (ignore change detection)
|
|
7
|
+
|
|
8
|
+
# Required for SyncDates (auto-detected from repo if not provided)
|
|
9
|
+
[string]$ProjectOwner = "",
|
|
10
|
+
[int]$ProjectNumber = 0,
|
|
11
|
+
[string]$ProjectId = "",
|
|
12
|
+
[string]$StartDateFieldId = "",
|
|
13
|
+
[string]$TargetDateFieldId = "",
|
|
14
|
+
[int]$TargetWeeks = 4,
|
|
15
|
+
|
|
16
|
+
[string]$IssueDir = "docs/issues"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
<#
|
|
20
|
+
.SYNOPSIS
|
|
21
|
+
Smart Issue Sync - Syncs markdown issue specs to GitHub with intelligent change detection.
|
|
22
|
+
|
|
23
|
+
.DESCRIPTION
|
|
24
|
+
Features:
|
|
25
|
+
- SMART SYNC: Only processes files that changed since last run (uses .issue-sync-state.json)
|
|
26
|
+
- AUTO-ARCHIVE: Moves completed .md files to closed/ subfolder
|
|
27
|
+
- AUTO-CLOSE: Closes GitHub issues when all checkboxes are ticked
|
|
28
|
+
- NATURAL SORT: Processes Issue_1, Issue_2, ... Issue_10 (not 1, 10, 11)
|
|
29
|
+
- DATE SYNC: Sets Start/Target dates on GitHub Projects
|
|
30
|
+
|
|
31
|
+
.EXAMPLE
|
|
32
|
+
# Smart sync (only changed files)
|
|
33
|
+
.\publish_issues.ps1 -Update -AutoArchive
|
|
34
|
+
|
|
35
|
+
# Force full resync
|
|
36
|
+
.\publish_issues.ps1 -Update -AutoArchive -Force
|
|
37
|
+
|
|
38
|
+
# Dry run
|
|
39
|
+
.\publish_issues.ps1 -Update -AutoArchive -DryRun
|
|
40
|
+
#>
|
|
41
|
+
|
|
42
|
+
# ============ AUTO-DETECT REPO INFO ============
|
|
43
|
+
$repoJson = gh repo view --json owner,name 2>$null | ConvertFrom-Json
|
|
44
|
+
if (-not $repoJson) {
|
|
45
|
+
Write-Host "ERROR: Not in a GitHub repository or gh CLI not authenticated" -ForegroundColor Red
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
$REPO_OWNER = $repoJson.owner.login
|
|
49
|
+
$REPO_NAME = $repoJson.name
|
|
50
|
+
$REPO = "$REPO_OWNER/$REPO_NAME"
|
|
51
|
+
|
|
52
|
+
Write-Host "Repository: $REPO" -ForegroundColor Cyan
|
|
53
|
+
|
|
54
|
+
# ============ STATE FILE FOR CHANGE DETECTION ============
|
|
55
|
+
$stateFile = Join-Path $IssueDir ".issue-sync-state.json"
|
|
56
|
+
$syncState = @{}
|
|
57
|
+
|
|
58
|
+
if (Test-Path $stateFile) {
|
|
59
|
+
try {
|
|
60
|
+
$syncState = Get-Content $stateFile -Raw | ConvertFrom-Json -AsHashtable
|
|
61
|
+
} catch {
|
|
62
|
+
$syncState = @{}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Save-SyncState {
|
|
67
|
+
if (-not $DryRun) {
|
|
68
|
+
$syncState | ConvertTo-Json -Depth 10 | Set-Content $stateFile -Encoding UTF8
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function Get-FileHash256 {
|
|
73
|
+
param([string]$Path)
|
|
74
|
+
$hash = Get-FileHash -Path $Path -Algorithm SHA256
|
|
75
|
+
return $hash.Hash
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Test-FileChanged {
|
|
79
|
+
param([string]$FilePath)
|
|
80
|
+
|
|
81
|
+
if ($Force) { return $true }
|
|
82
|
+
|
|
83
|
+
$fileName = Split-Path $FilePath -Leaf
|
|
84
|
+
$currentHash = Get-FileHash256 -Path $FilePath
|
|
85
|
+
|
|
86
|
+
if ($syncState.ContainsKey($fileName)) {
|
|
87
|
+
$lastHash = $syncState[$fileName].hash
|
|
88
|
+
return $currentHash -ne $lastHash
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return $true # New file, never synced
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Update-FileState {
|
|
95
|
+
param(
|
|
96
|
+
[string]$FilePath,
|
|
97
|
+
[string]$Status, # "synced", "archived"
|
|
98
|
+
[int]$IssueNumber = 0
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
$fileName = Split-Path $FilePath -Leaf
|
|
102
|
+
$syncState[$fileName] = @{
|
|
103
|
+
hash = (Get-FileHash256 -Path $FilePath)
|
|
104
|
+
lastSync = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss")
|
|
105
|
+
status = $Status
|
|
106
|
+
issueNumber = $IssueNumber
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# ============ VALIDATE SYNC DATES CONFIG ============
|
|
111
|
+
if ($SyncDates) {
|
|
112
|
+
if (-not $ProjectOwner) { $ProjectOwner = $REPO_OWNER }
|
|
113
|
+
|
|
114
|
+
$missingConfig = @()
|
|
115
|
+
if ($ProjectNumber -eq 0) { $missingConfig += "-ProjectNumber" }
|
|
116
|
+
if (-not $ProjectId) { $missingConfig += "-ProjectId" }
|
|
117
|
+
if (-not $StartDateFieldId) { $missingConfig += "-StartDateFieldId" }
|
|
118
|
+
if (-not $TargetDateFieldId) { $missingConfig += "-TargetDateFieldId" }
|
|
119
|
+
|
|
120
|
+
if ($missingConfig.Count -gt 0) {
|
|
121
|
+
Write-Host "ERROR: -SyncDates requires: $($missingConfig -join ', ')" -ForegroundColor Red
|
|
122
|
+
exit 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ============ FIND ISSUE FILES (Natural Sort) ============
|
|
127
|
+
$closedDir = Join-Path $IssueDir "closed"
|
|
128
|
+
|
|
129
|
+
# Get files and sort naturally (Issue_1, Issue_2, ... Issue_10)
|
|
130
|
+
$issueFiles = Get-ChildItem "$IssueDir/*.md" -ErrorAction SilentlyContinue |
|
|
131
|
+
Where-Object { $_.Name -ne "ALL_ISSUES.md" } |
|
|
132
|
+
Sort-Object {
|
|
133
|
+
if ($_.Name -match 'Issue_(\d+)') {
|
|
134
|
+
[int]$matches[1]
|
|
135
|
+
} else {
|
|
136
|
+
$_.Name
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ($issueFiles.Count -eq 0) {
|
|
141
|
+
Write-Host "No issue files found in $IssueDir/" -ForegroundColor Yellow
|
|
142
|
+
exit 0
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Write-Host "Found $($issueFiles.Count) issue files" -ForegroundColor Gray
|
|
146
|
+
|
|
147
|
+
# ============ FILTER TO CHANGED FILES ONLY ============
|
|
148
|
+
$changedFiles = @()
|
|
149
|
+
$skippedFiles = @()
|
|
150
|
+
|
|
151
|
+
foreach ($file in $issueFiles) {
|
|
152
|
+
if (Test-FileChanged -FilePath $file.FullName) {
|
|
153
|
+
$changedFiles += $file
|
|
154
|
+
} else {
|
|
155
|
+
$skippedFiles += $file
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if ($skippedFiles.Count -gt 0 -and -not $Force) {
|
|
160
|
+
Write-Host "Skipping $($skippedFiles.Count) unchanged files" -ForegroundColor DarkGray
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if ($changedFiles.Count -eq 0) {
|
|
164
|
+
Write-Host "No files have changed since last sync. Use -Force to resync all." -ForegroundColor Yellow
|
|
165
|
+
exit 0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Write-Host "Processing $($changedFiles.Count) changed files..." -ForegroundColor Cyan
|
|
169
|
+
Write-Host ""
|
|
170
|
+
|
|
171
|
+
# ============ CACHE EXISTING ISSUES ============
|
|
172
|
+
Write-Host "Fetching existing GitHub issues..." -ForegroundColor Gray
|
|
173
|
+
$existingIssues = gh issue list --repo $REPO --state all --limit 500 --json number,title,state | ConvertFrom-Json
|
|
174
|
+
$issueTitleMap = @{}
|
|
175
|
+
$issueStateMap = @{}
|
|
176
|
+
foreach ($issue in $existingIssues) {
|
|
177
|
+
$issueTitleMap[$issue.title] = $issue.number
|
|
178
|
+
$issueStateMap[$issue.title] = $issue.state
|
|
179
|
+
}
|
|
180
|
+
Write-Host "Found $($existingIssues.Count) existing issues on GitHub" -ForegroundColor Gray
|
|
181
|
+
Write-Host ""
|
|
182
|
+
|
|
183
|
+
# ============ HELPER FUNCTIONS ============
|
|
184
|
+
|
|
185
|
+
function Ensure-Label {
|
|
186
|
+
param($LabelName)
|
|
187
|
+
if (-not $DryRun) {
|
|
188
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
189
|
+
gh label create "$LabelName" --repo $REPO --color "0e8a16" 2>$null
|
|
190
|
+
$ErrorActionPreference = "Continue"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function Add-ToProject {
|
|
195
|
+
param([int]$IssueNumber)
|
|
196
|
+
|
|
197
|
+
if (-not $SyncDates -or $DryRun) { return }
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
$url = "https://github.com/$REPO/issues/$IssueNumber"
|
|
201
|
+
gh project item-add $ProjectNumber --owner $ProjectOwner --url $url 2>$null
|
|
202
|
+
} catch { }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function Sync-ProjectDates {
|
|
206
|
+
param([int]$IssueNumber, [string]$StartDate, [string]$TargetDate)
|
|
207
|
+
|
|
208
|
+
if (-not $SyncDates) { return }
|
|
209
|
+
|
|
210
|
+
Add-ToProject -IssueNumber $IssueNumber
|
|
211
|
+
|
|
212
|
+
$itemsJson = gh project item-list $ProjectNumber --owner $ProjectOwner --format json 2>$null
|
|
213
|
+
if (-not $itemsJson) { return }
|
|
214
|
+
|
|
215
|
+
$items = $itemsJson | ConvertFrom-Json
|
|
216
|
+
$item = $items.items | Where-Object { $_.content.number -eq $IssueNumber } | Select-Object -First 1
|
|
217
|
+
|
|
218
|
+
if (-not $item) { return }
|
|
219
|
+
|
|
220
|
+
if (-not $DryRun) {
|
|
221
|
+
gh project item-edit --id $item.id --project-id $ProjectId --field-id $StartDateFieldId --date $StartDate 2>$null
|
|
222
|
+
gh project item-edit --id $item.id --project-id $ProjectId --field-id $TargetDateFieldId --date $TargetDate 2>$null
|
|
223
|
+
Write-Host " Dates: $StartDate -> $TargetDate" -ForegroundColor Blue
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function Parse-IssueMetadata {
|
|
228
|
+
param($Content)
|
|
229
|
+
|
|
230
|
+
$result = @{ title = $null; labels = @() }
|
|
231
|
+
|
|
232
|
+
# Try YAML frontmatter
|
|
233
|
+
if ($Content -match '^---\s*[\r\n]+([\s\S]*?)[\r\n]+---') {
|
|
234
|
+
$yaml = $matches[1]
|
|
235
|
+
if ($yaml -match 'title:\s*["\u0027]?([^"\u0027\r\n]+)["\u0027]?') {
|
|
236
|
+
$result.title = $matches[1].Trim()
|
|
237
|
+
}
|
|
238
|
+
if ($yaml -match 'labels:\s*([^\r\n]+)') {
|
|
239
|
+
$result.labels = ($matches[1].Trim() -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# Try Bold markdown
|
|
244
|
+
if (-not $result.title -and $Content -match '\*\*Title:\*\*\s*(.+)') {
|
|
245
|
+
$result.title = $matches[1].Trim()
|
|
246
|
+
}
|
|
247
|
+
if ($result.labels.Count -eq 0 -and $Content -match '\*\*Labels:\*\*\s*(.+)') {
|
|
248
|
+
$result.labels = ($matches[1] -replace '`', '' -replace '\s', '' -split ',') | Where-Object { $_ }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# Try Markdown headers
|
|
252
|
+
if (-not $result.title -and $Content -match '## Title\s*[\r\n]+(.+)') {
|
|
253
|
+
$result.title = $matches[1].Trim()
|
|
254
|
+
}
|
|
255
|
+
if ($result.labels.Count -eq 0 -and $Content -match '## Labels\s*[\r\n]+(.+)') {
|
|
256
|
+
$result.labels = ($matches[1] -replace '`', '' -replace '\s', '' -split ',') | Where-Object { $_ }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return $result
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function Get-CheckboxStatus {
|
|
263
|
+
param($Content)
|
|
264
|
+
|
|
265
|
+
$unchecked = [regex]::Matches($Content, '[-*]\s*\[\s*\]').Count
|
|
266
|
+
$checked = [regex]::Matches($Content, '[-*]\s*\[[xX]\]').Count
|
|
267
|
+
|
|
268
|
+
return @{
|
|
269
|
+
Total = $unchecked + $checked
|
|
270
|
+
Checked = $checked
|
|
271
|
+
AllComplete = (($unchecked + $checked) -gt 0 -and $unchecked -eq 0)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function Move-ToClosedFolder {
|
|
276
|
+
param([string]$FilePath)
|
|
277
|
+
|
|
278
|
+
if (-not $AutoArchive) { return }
|
|
279
|
+
|
|
280
|
+
if (-not (Test-Path $closedDir)) {
|
|
281
|
+
if (-not $DryRun) {
|
|
282
|
+
New-Item -ItemType Directory -Path $closedDir -Force | Out-Null
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
$fileName = Split-Path $FilePath -Leaf
|
|
287
|
+
$destPath = Join-Path $closedDir $fileName
|
|
288
|
+
|
|
289
|
+
if (-not $DryRun) {
|
|
290
|
+
Move-Item -Path $FilePath -Destination $destPath -Force
|
|
291
|
+
# Remove from state since it's archived
|
|
292
|
+
$syncState.Remove($fileName)
|
|
293
|
+
Write-Host " Archived to closed/$fileName" -ForegroundColor DarkMagenta
|
|
294
|
+
} else {
|
|
295
|
+
Write-Host " [DryRun] Would archive to closed/$fileName" -ForegroundColor Gray
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function Get-CleanIssueBody {
|
|
300
|
+
param($Content)
|
|
301
|
+
|
|
302
|
+
$clean = $Content -replace '(?ms)^---\s*[\r\n]+.*?[\r\n]+---\s*[\r\n]*', ''
|
|
303
|
+
$clean = $clean -replace '(?ms)^## Title.*?(?=##|\Z)', ''
|
|
304
|
+
$clean = $clean -replace '(?ms)^## Labels.*?(?=##|\Z)', ''
|
|
305
|
+
$clean = $clean -replace '(?ms)^# GitHub Issue:.*?(?=##|\Z)', ''
|
|
306
|
+
return $clean.Trim()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# ============ MAIN LOOP ============
|
|
310
|
+
|
|
311
|
+
$processedCount = 0
|
|
312
|
+
$archivedCount = 0
|
|
313
|
+
|
|
314
|
+
foreach ($file in $changedFiles) {
|
|
315
|
+
$content = Get-Content $file.FullName -Raw
|
|
316
|
+
$parsed = Parse-IssueMetadata -Content $content
|
|
317
|
+
|
|
318
|
+
if (-not $parsed.title) {
|
|
319
|
+
Write-Warning "Could not find title for $($file.Name) - skipping"
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
$title = $parsed.title
|
|
324
|
+
# Sanitize title: replace double quotes with single quotes to avoid CLI issues
|
|
325
|
+
$title = $title -replace '"', "'"
|
|
326
|
+
$labelArray = $parsed.labels
|
|
327
|
+
$checkboxStatus = Get-CheckboxStatus -Content $content
|
|
328
|
+
$isComplete = $checkboxStatus.AllComplete
|
|
329
|
+
|
|
330
|
+
# Calculate dates
|
|
331
|
+
$fileInfo = Get-Item $file.FullName
|
|
332
|
+
$startDate = $fileInfo.CreationTime.ToString("yyyy-MM-dd")
|
|
333
|
+
$targetDate = if ($isComplete) {
|
|
334
|
+
$fileInfo.LastWriteTime.ToString("yyyy-MM-dd")
|
|
335
|
+
} else {
|
|
336
|
+
$fileInfo.CreationTime.AddDays($TargetWeeks * 7).ToString("yyyy-MM-dd")
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
Write-Host "Processing: $($file.Name)" -ForegroundColor Cyan
|
|
340
|
+
Write-Host " Title: $title"
|
|
341
|
+
if ($checkboxStatus.Total -gt 0) {
|
|
342
|
+
$color = if ($isComplete) { "Green" } else { "Yellow" }
|
|
343
|
+
Write-Host " Checkboxes: $($checkboxStatus.Checked)/$($checkboxStatus.Total)" -ForegroundColor $color
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
$cleanBody = Get-CleanIssueBody -Content $content
|
|
347
|
+
$tempBodyFile = [System.IO.Path]::GetTempFileName()
|
|
348
|
+
Set-Content -Path $tempBodyFile -Value $cleanBody -Encoding UTF8
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
$existingNumber = $issueTitleMap[$title]
|
|
352
|
+
$currentState = $issueStateMap[$title]
|
|
353
|
+
|
|
354
|
+
if ($existingNumber) {
|
|
355
|
+
if ($Update) {
|
|
356
|
+
Write-Host " Existing: #$existingNumber ($currentState) - UPDATING" -ForegroundColor Yellow
|
|
357
|
+
|
|
358
|
+
foreach ($l in $labelArray) {
|
|
359
|
+
if (![string]::IsNullOrWhiteSpace($l)) { Ensure-Label -LabelName $l }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (-not $DryRun) {
|
|
363
|
+
$ghArgs = @("issue", "edit", "$existingNumber", "--repo", "$REPO", "--title", "$title", "--body-file", "$tempBodyFile")
|
|
364
|
+
foreach ($l in $labelArray) {
|
|
365
|
+
if (![string]::IsNullOrWhiteSpace($l)) {
|
|
366
|
+
$ghArgs += "--add-label"; $ghArgs += "$l"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
& gh $ghArgs
|
|
370
|
+
|
|
371
|
+
# Close if complete and currently open
|
|
372
|
+
if ($isComplete -and $currentState -eq "OPEN") {
|
|
373
|
+
gh issue close $existingNumber --repo $REPO --comment "✅ All acceptance criteria completed"
|
|
374
|
+
Write-Host " Closed #$existingNumber" -ForegroundColor Magenta
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
Write-Host " Updated #$existingNumber" -ForegroundColor Green
|
|
378
|
+
Sync-ProjectDates -IssueNumber $existingNumber -StartDate $startDate -TargetDate $targetDate
|
|
379
|
+
Update-FileState -FilePath $file.FullName -Status "synced" -IssueNumber $existingNumber
|
|
380
|
+
$processedCount++
|
|
381
|
+
|
|
382
|
+
# Archive if complete (whether just closed or already closed)
|
|
383
|
+
if ($isComplete) {
|
|
384
|
+
Move-ToClosedFolder -FilePath $file.FullName
|
|
385
|
+
$archivedCount++
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
Write-Host " [DryRun] Would update #$existingNumber" -ForegroundColor Gray
|
|
389
|
+
if ($isComplete) {
|
|
390
|
+
if ($currentState -eq "OPEN") {
|
|
391
|
+
Write-Host " [DryRun] Would CLOSE" -ForegroundColor Magenta
|
|
392
|
+
}
|
|
393
|
+
Write-Host " [DryRun] Would ARCHIVE" -ForegroundColor DarkMagenta
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
Write-Host " Existing: #$existingNumber - SKIPPING (use -Update)" -ForegroundColor DarkGray
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
Write-Host " New issue - CREATING" -ForegroundColor Green
|
|
401
|
+
|
|
402
|
+
foreach ($l in $labelArray) {
|
|
403
|
+
if (![string]::IsNullOrWhiteSpace($l)) { Ensure-Label -LabelName $l }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (-not $DryRun) {
|
|
407
|
+
$ghArgs = @("issue", "create", "--repo", "$REPO", "--title", "$title", "--body-file", "$tempBodyFile")
|
|
408
|
+
foreach ($l in $labelArray) {
|
|
409
|
+
if (![string]::IsNullOrWhiteSpace($l)) {
|
|
410
|
+
$ghArgs += "--label"; $ghArgs += "$l"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
$issueUrl = & gh $ghArgs
|
|
414
|
+
|
|
415
|
+
if ($issueUrl -match '/issues/(\d+)$') {
|
|
416
|
+
$newNum = [int]$matches[1]
|
|
417
|
+
Write-Host " Created #$newNum" -ForegroundColor Green
|
|
418
|
+
Sync-ProjectDates -IssueNumber $newNum -StartDate $startDate -TargetDate $targetDate
|
|
419
|
+
Update-FileState -FilePath $file.FullName -Status "synced" -IssueNumber $newNum
|
|
420
|
+
$processedCount++
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
Write-Host " [DryRun] Would create issue" -ForegroundColor Gray
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} finally {
|
|
427
|
+
if (Test-Path $tempBodyFile) { Remove-Item $tempBodyFile -Force }
|
|
428
|
+
}
|
|
429
|
+
Write-Host "-------------------"
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# Save state file
|
|
433
|
+
Save-SyncState
|
|
434
|
+
|
|
435
|
+
Write-Host ""
|
|
436
|
+
Write-Host "Done!" -ForegroundColor Green
|
|
437
|
+
Write-Host " Processed: $processedCount issues"
|
|
438
|
+
if ($archivedCount -gt 0) {
|
|
439
|
+
Write-Host " Archived: $archivedCount completed issues" -ForegroundColor Magenta
|
|
440
|
+
}
|
|
441
|
+
if ($skippedFiles.Count -gt 0 -and -not $Force) {
|
|
442
|
+
Write-Host " Skipped: $($skippedFiles.Count) unchanged files" -ForegroundColor DarkGray
|
|
443
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Smart Ops - GitHub Automation Script (PowerShell)
|
|
2
|
+
# Copy to: scripts/smart-ops.ps1
|
|
3
|
+
# Replace {{PLACEHOLDERS}} with actual values
|
|
4
|
+
|
|
5
|
+
$ErrorActionPreference = "Stop"
|
|
6
|
+
|
|
7
|
+
# ============ CONFIGURATION ============
|
|
8
|
+
$REPO_OWNER = "{{OWNER}}"
|
|
9
|
+
$REPO_NAME = "{{REPO}}"
|
|
10
|
+
$REPO = "$REPO_OWNER/$REPO_NAME"
|
|
11
|
+
|
|
12
|
+
# GitHub Projects (optional - leave empty if not using)
|
|
13
|
+
$PROJECT_NUMBER = "{{PROJECT_NUMBER}}"
|
|
14
|
+
$PROJECT_ID = "{{PROJECT_ID}}"
|
|
15
|
+
$STATUS_FIELD_ID = "{{STATUS_FIELD_ID}}"
|
|
16
|
+
$TODO_OPTION_ID = "{{TODO_OPTION_ID}}"
|
|
17
|
+
$IN_PROGRESS_OPTION_ID = "{{IN_PROGRESS_OPTION_ID}}"
|
|
18
|
+
$DONE_OPTION_ID = "{{DONE_OPTION_ID}}"
|
|
19
|
+
$START_DATE_FIELD_ID = "{{START_DATE_FIELD_ID}}"
|
|
20
|
+
$TARGET_DATE_FIELD_ID = "{{TARGET_DATE_FIELD_ID}}"
|
|
21
|
+
|
|
22
|
+
$DEBUG = $env:DEBUG -eq "1"
|
|
23
|
+
|
|
24
|
+
# ============ HELPERS ============
|
|
25
|
+
|
|
26
|
+
function Test-Prerequisites {
|
|
27
|
+
$errors = 0
|
|
28
|
+
try { $null = Get-Command gh -ErrorAction Stop } catch {
|
|
29
|
+
Write-Host "X GitHub CLI not installed" -ForegroundColor Red
|
|
30
|
+
$errors++
|
|
31
|
+
}
|
|
32
|
+
try { $null = gh auth status 2>&1; if ($LASTEXITCODE -ne 0) { throw } } catch {
|
|
33
|
+
Write-Host "X Not authenticated. Run: gh auth login" -ForegroundColor Red
|
|
34
|
+
$errors++
|
|
35
|
+
}
|
|
36
|
+
if ($errors -gt 0) { exit 1 }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Get-Today { return (Get-Date -Format "yyyy-MM-dd") }
|
|
40
|
+
function Get-FutureDate { param([int]$Days = 7); return (Get-Date).AddDays($Days).ToString("yyyy-MM-dd") }
|
|
41
|
+
|
|
42
|
+
# ============ COMMANDS ============
|
|
43
|
+
|
|
44
|
+
function Start-Scan {
|
|
45
|
+
Write-Host "Scanning open issues..." -ForegroundColor Cyan
|
|
46
|
+
gh issue list --repo $REPO --state open --json number,title,labels --limit 20
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Complete-Scan {
|
|
50
|
+
Write-Host "Issues ready for completion..." -ForegroundColor Cyan
|
|
51
|
+
gh issue list --repo $REPO --state open --json number,title --limit 20
|
|
52
|
+
Write-Host "`nToday: $(Get-Today)"
|
|
53
|
+
Write-Host 'Close with: .\smart-ops.ps1 close [number]' -ForegroundColor Gray
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function New-Issue {
|
|
57
|
+
param([string]$Title, [string]$Body = "", [string]$Labels = "enhancement", [int]$Days = 7)
|
|
58
|
+
if ([string]::IsNullOrWhiteSpace($Title)) { Write-Host "X Title required" -ForegroundColor Red; return }
|
|
59
|
+
|
|
60
|
+
Write-Host "Creating: $Title (${Days}d)" -ForegroundColor Cyan
|
|
61
|
+
$url = gh issue create --repo $REPO --title $Title --body $Body --label $Labels
|
|
62
|
+
|
|
63
|
+
if ($url -match '/(\d+)$') {
|
|
64
|
+
Write-Host "Created #$($Matches[1])" -ForegroundColor Green
|
|
65
|
+
Write-Host $url
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Close-Issue {
|
|
70
|
+
param([string]$Num, [string]$Comment = "Completed")
|
|
71
|
+
if ([string]::IsNullOrWhiteSpace($Num)) { Write-Host "X Issue number required" -ForegroundColor Red; return }
|
|
72
|
+
|
|
73
|
+
$confirm = Read-Host "Close #${Num}? (y/N)"
|
|
74
|
+
if ($confirm -ne 'y') { return }
|
|
75
|
+
|
|
76
|
+
gh issue close $Num --repo $REPO --comment $Comment
|
|
77
|
+
Write-Host "Closed #$Num" -ForegroundColor Green
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Move-ToProgress {
|
|
81
|
+
param([string]$Id)
|
|
82
|
+
if ([string]::IsNullOrWhiteSpace($PROJECT_ID)) { Write-Host "X No project configured" -ForegroundColor Red; return }
|
|
83
|
+
gh project item-edit --id $Id --field-id $STATUS_FIELD_ID --project-id $PROJECT_ID --single-select-option-id $IN_PROGRESS_OPTION_ID
|
|
84
|
+
Write-Host "-> In Progress" -ForegroundColor Green
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Move-ToDone {
|
|
88
|
+
param([string]$Id)
|
|
89
|
+
if ([string]::IsNullOrWhiteSpace($PROJECT_ID)) { Write-Host "X No project configured" -ForegroundColor Red; return }
|
|
90
|
+
gh project item-edit --id $Id --field-id $STATUS_FIELD_ID --project-id $PROJECT_ID --single-select-option-id $DONE_OPTION_ID
|
|
91
|
+
Write-Host "-> Done" -ForegroundColor Green
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Show-Help {
|
|
95
|
+
Write-Host @'
|
|
96
|
+
Smart Ops - GitHub Automation
|
|
97
|
+
|
|
98
|
+
Usage: .\smart-ops.ps1 [command] [args]
|
|
99
|
+
|
|
100
|
+
Commands:
|
|
101
|
+
start List open issues
|
|
102
|
+
complete List for completion
|
|
103
|
+
create [title] [body] [labels] [days]
|
|
104
|
+
close [number] [comment]
|
|
105
|
+
progress [item_id] Move to In Progress
|
|
106
|
+
done [item_id] Move to Done
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
.\smart-ops.ps1 create "Fix bug" "Description" "bug" 3
|
|
110
|
+
.\smart-ops.ps1 close 42 "Fixed"
|
|
111
|
+
'@ -ForegroundColor Cyan
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ============ MAIN ============
|
|
115
|
+
|
|
116
|
+
Test-Prerequisites
|
|
117
|
+
|
|
118
|
+
$cmd = if ($args.Count -gt 0) { $args[0] } else { "help" }
|
|
119
|
+
|
|
120
|
+
switch ($cmd.ToLower()) {
|
|
121
|
+
"start" { Start-Scan }
|
|
122
|
+
"complete" { Complete-Scan }
|
|
123
|
+
"create" { New-Issue -Title $args[1] -Body $args[2] -Labels $args[3] -Days ([int]$args[4]) }
|
|
124
|
+
"close" { Close-Issue -Num $args[1] -Comment $args[2] }
|
|
125
|
+
"progress" { Move-ToProgress -Id $args[1] }
|
|
126
|
+
"done" { Move-ToDone -Id $args[1] }
|
|
127
|
+
default { Show-Help }
|
|
128
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Smart Ops - GitHub Automation Script (Bash)
|
|
3
|
+
# Copy to: scripts/smart-ops.sh
|
|
4
|
+
# Replace {{PLACEHOLDERS}} with actual values
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# ============ CONFIGURATION ============
|
|
9
|
+
REPO_OWNER="{{OWNER}}"
|
|
10
|
+
REPO_NAME="{{REPO}}"
|
|
11
|
+
REPO="${REPO_OWNER}/${REPO_NAME}"
|
|
12
|
+
|
|
13
|
+
# GitHub Projects (optional - leave empty if not using)
|
|
14
|
+
PROJECT_NUMBER="{{PROJECT_NUMBER}}"
|
|
15
|
+
PROJECT_ID="{{PROJECT_ID}}"
|
|
16
|
+
STATUS_FIELD_ID="{{STATUS_FIELD_ID}}"
|
|
17
|
+
TODO_OPTION_ID="{{TODO_OPTION_ID}}"
|
|
18
|
+
IN_PROGRESS_OPTION_ID="{{IN_PROGRESS_OPTION_ID}}"
|
|
19
|
+
DONE_OPTION_ID="{{DONE_OPTION_ID}}"
|
|
20
|
+
START_DATE_FIELD_ID="{{START_DATE_FIELD_ID}}"
|
|
21
|
+
TARGET_DATE_FIELD_ID="{{TARGET_DATE_FIELD_ID}}"
|
|
22
|
+
|
|
23
|
+
DEBUG="${DEBUG:-0}"
|
|
24
|
+
|
|
25
|
+
# ============ HELPERS ============
|
|
26
|
+
|
|
27
|
+
check_prerequisites() {
|
|
28
|
+
local errors=0
|
|
29
|
+
command -v gh &> /dev/null || { echo "❌ GitHub CLI not installed"; errors=$((errors + 1)); }
|
|
30
|
+
gh auth status &> /dev/null || { echo "❌ Not authenticated. Run: gh auth login"; errors=$((errors + 1)); }
|
|
31
|
+
[ "$errors" -gt 0 ] && exit 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get_today() { date +%Y-%m-%d; }
|
|
35
|
+
get_future_date() {
|
|
36
|
+
local days="${1:-7}"
|
|
37
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
38
|
+
date -v+${days}d +%Y-%m-%d
|
|
39
|
+
else
|
|
40
|
+
date -d "+${days} days" +%Y-%m-%d
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# ============ COMMANDS ============
|
|
45
|
+
|
|
46
|
+
start_scan() {
|
|
47
|
+
echo "🔍 Scanning open issues..."
|
|
48
|
+
gh issue list --repo "$REPO" --state open --json number,title,labels --limit 20
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
complete_scan() {
|
|
52
|
+
echo "📋 Issues ready for completion..."
|
|
53
|
+
gh issue list --repo "$REPO" --state open --json number,title --limit 20
|
|
54
|
+
echo ""
|
|
55
|
+
echo "Today: $(get_today)"
|
|
56
|
+
echo "Close with: ./smart-ops.sh close <number>"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
create_issue() {
|
|
60
|
+
local title="$1"
|
|
61
|
+
local body="$2"
|
|
62
|
+
local labels="${3:-enhancement}"
|
|
63
|
+
local days="${4:-7}"
|
|
64
|
+
|
|
65
|
+
[ -z "$title" ] && { echo "❌ Title required"; return 1; }
|
|
66
|
+
|
|
67
|
+
echo "📝 Creating: $title (${days}d)"
|
|
68
|
+
gh issue create --repo "$REPO" --title "$title" --body "$body" --label "$labels"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
close_issue() {
|
|
72
|
+
local num="$1"
|
|
73
|
+
local comment="${2:-Completed}"
|
|
74
|
+
|
|
75
|
+
[ -z "$num" ] && { echo "❌ Issue number required"; return 1; }
|
|
76
|
+
|
|
77
|
+
read -p "Close #$num? (y/N): " -n 1 -r; echo
|
|
78
|
+
[[ ! $REPLY =~ ^[Yy]$ ]] && return 0
|
|
79
|
+
|
|
80
|
+
gh issue close "$num" --repo "$REPO" --comment "$comment"
|
|
81
|
+
echo "✅ Closed #$num"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
move_to_progress() {
|
|
85
|
+
local id="$1"
|
|
86
|
+
[ -z "$PROJECT_ID" ] && { echo "❌ No project configured"; return 1; }
|
|
87
|
+
gh project item-edit --id "$id" --field-id "$STATUS_FIELD_ID" --project-id "$PROJECT_ID" --single-select-option-id "$IN_PROGRESS_OPTION_ID"
|
|
88
|
+
echo "✅ → In Progress"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
move_to_done() {
|
|
92
|
+
local id="$1"
|
|
93
|
+
[ -z "$PROJECT_ID" ] && { echo "❌ No project configured"; return 1; }
|
|
94
|
+
gh project item-edit --id "$id" --field-id "$STATUS_FIELD_ID" --project-id "$PROJECT_ID" --single-select-option-id "$DONE_OPTION_ID"
|
|
95
|
+
echo "✅ → Done"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
show_help() {
|
|
99
|
+
cat << 'EOF'
|
|
100
|
+
Smart Ops - GitHub Automation
|
|
101
|
+
|
|
102
|
+
Usage: ./smart-ops.sh <command> [args]
|
|
103
|
+
|
|
104
|
+
Commands:
|
|
105
|
+
start List open issues
|
|
106
|
+
complete List for completion
|
|
107
|
+
create <title> [body] [labels] [days]
|
|
108
|
+
close <number> [comment]
|
|
109
|
+
progress <item_id> Move to In Progress
|
|
110
|
+
done <item_id> Move to Done
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
./smart-ops.sh create "Fix bug" "Description" "bug" 3
|
|
114
|
+
./smart-ops.sh close 42 "Fixed"
|
|
115
|
+
EOF
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ============ MAIN ============
|
|
119
|
+
|
|
120
|
+
check_prerequisites
|
|
121
|
+
|
|
122
|
+
case "${1:-help}" in
|
|
123
|
+
start) start_scan ;;
|
|
124
|
+
complete) complete_scan ;;
|
|
125
|
+
create) create_issue "$2" "$3" "$4" "$5" ;;
|
|
126
|
+
close) close_issue "$2" "$3" ;;
|
|
127
|
+
progress) move_to_progress "$2" ;;
|
|
128
|
+
done) move_to_done "$2" ;;
|
|
129
|
+
*) show_help ;;
|
|
130
|
+
esac
|