claude-cli-advanced-starter-pack 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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +597 -0
  3. package/README.md +439 -0
  4. package/bin/gtask.js +282 -0
  5. package/bin/postinstall.js +53 -0
  6. package/package.json +69 -0
  7. package/src/agents/phase-dev-templates.js +1011 -0
  8. package/src/agents/templates.js +668 -0
  9. package/src/analysis/checklist-parser.js +414 -0
  10. package/src/analysis/codebase.js +481 -0
  11. package/src/cli/menu.js +958 -0
  12. package/src/commands/claude-audit.js +1482 -0
  13. package/src/commands/claude-settings.js +2243 -0
  14. package/src/commands/create-agent.js +681 -0
  15. package/src/commands/create-command.js +337 -0
  16. package/src/commands/create-hook.js +262 -0
  17. package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
  18. package/src/commands/create-phase-dev/documentation-generator.js +352 -0
  19. package/src/commands/create-phase-dev/post-completion.js +404 -0
  20. package/src/commands/create-phase-dev/scale-calculator.js +344 -0
  21. package/src/commands/create-phase-dev/wizard.js +492 -0
  22. package/src/commands/create-phase-dev.js +481 -0
  23. package/src/commands/create-skill.js +313 -0
  24. package/src/commands/create.js +446 -0
  25. package/src/commands/decompose.js +392 -0
  26. package/src/commands/detect-tech-stack.js +768 -0
  27. package/src/commands/explore-mcp/claude-md-updater.js +252 -0
  28. package/src/commands/explore-mcp/mcp-installer.js +346 -0
  29. package/src/commands/explore-mcp/mcp-registry.js +438 -0
  30. package/src/commands/explore-mcp.js +638 -0
  31. package/src/commands/gtask-init.js +641 -0
  32. package/src/commands/help.js +128 -0
  33. package/src/commands/init.js +1890 -0
  34. package/src/commands/install.js +250 -0
  35. package/src/commands/list.js +116 -0
  36. package/src/commands/roadmap.js +750 -0
  37. package/src/commands/setup-wizard.js +482 -0
  38. package/src/commands/setup.js +351 -0
  39. package/src/commands/sync.js +534 -0
  40. package/src/commands/test-run.js +456 -0
  41. package/src/commands/test-setup.js +456 -0
  42. package/src/commands/validate.js +67 -0
  43. package/src/config/tech-stack.defaults.json +182 -0
  44. package/src/config/tech-stack.schema.json +502 -0
  45. package/src/github/client.js +359 -0
  46. package/src/index.js +84 -0
  47. package/src/templates/claude-command.js +244 -0
  48. package/src/templates/issue-body.js +284 -0
  49. package/src/testing/config.js +411 -0
  50. package/src/utils/template-engine.js +398 -0
  51. package/src/utils/validate-templates.js +223 -0
  52. package/src/utils.js +396 -0
  53. package/templates/commands/ccasp-setup.template.md +113 -0
  54. package/templates/commands/context-audit.template.md +97 -0
  55. package/templates/commands/create-task-list.template.md +382 -0
  56. package/templates/commands/deploy-full.template.md +261 -0
  57. package/templates/commands/github-task-start.template.md +99 -0
  58. package/templates/commands/github-update.template.md +69 -0
  59. package/templates/commands/happy-start.template.md +117 -0
  60. package/templates/commands/phase-track.template.md +142 -0
  61. package/templates/commands/tunnel-start.template.md +127 -0
  62. package/templates/commands/tunnel-stop.template.md +106 -0
  63. package/templates/hooks/context-guardian.template.js +173 -0
  64. package/templates/hooks/deployment-orchestrator.template.js +219 -0
  65. package/templates/hooks/github-progress-hook.template.js +197 -0
  66. package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
  67. package/templates/hooks/phase-dev-enforcer.template.js +183 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Context Guardian Hook
3
+ *
4
+ * Monitors token usage and triggers appropriate actions at configured thresholds.
5
+ * Configured via tech-stack.json tokenManagement settings.
6
+ *
7
+ * Event: PostToolUse (monitors after each tool execution)
8
+ * Priority: {{hooks.priorities.lifecycle}}
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // Configuration from tech-stack.json
15
+ const CONFIG = {
16
+ enabled: {{tokenManagement.enabled}},
17
+ dailyBudget: {{tokenManagement.dailyBudget}},
18
+ thresholds: {
19
+ compact: {{tokenManagement.thresholds.compact}},
20
+ archive: {{tokenManagement.thresholds.archive}},
21
+ respawn: {{tokenManagement.thresholds.respawn}},
22
+ },
23
+ trackingFile: '{{tokenManagement.trackingFile}}',
24
+ };
25
+
26
+ /**
27
+ * Load current token usage data
28
+ */
29
+ function loadUsageData() {
30
+ const trackingPath = path.join(process.cwd(), CONFIG.trackingFile);
31
+
32
+ if (fs.existsSync(trackingPath)) {
33
+ try {
34
+ return JSON.parse(fs.readFileSync(trackingPath, 'utf8'));
35
+ } catch (error) {
36
+ console.warn('[context-guardian] Could not parse tracking file:', error.message);
37
+ }
38
+ }
39
+
40
+ // Return default usage data
41
+ return {
42
+ date: new Date().toISOString().split('T')[0],
43
+ sessions: [],
44
+ totalTokens: 0,
45
+ currentSession: {
46
+ id: generateSessionId(),
47
+ startTime: new Date().toISOString(),
48
+ tokens: 0,
49
+ toolCalls: 0,
50
+ },
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Save usage data
56
+ */
57
+ function saveUsageData(data) {
58
+ const trackingPath = path.join(process.cwd(), CONFIG.trackingFile);
59
+ const trackingDir = path.dirname(trackingPath);
60
+
61
+ if (!fs.existsSync(trackingDir)) {
62
+ fs.mkdirSync(trackingDir, { recursive: true });
63
+ }
64
+
65
+ fs.writeFileSync(trackingPath, JSON.stringify(data, null, 2), 'utf8');
66
+ }
67
+
68
+ /**
69
+ * Generate a simple session ID
70
+ */
71
+ function generateSessionId() {
72
+ return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
73
+ }
74
+
75
+ /**
76
+ * Estimate tokens from tool output
77
+ * Rough estimation: ~4 chars per token
78
+ */
79
+ function estimateTokens(text) {
80
+ if (!text) return 0;
81
+ const str = typeof text === 'string' ? text : JSON.stringify(text);
82
+ return Math.ceil(str.length / 4);
83
+ }
84
+
85
+ /**
86
+ * Get usage percentage
87
+ */
88
+ function getUsagePercentage(usedTokens) {
89
+ return usedTokens / CONFIG.dailyBudget;
90
+ }
91
+
92
+ /**
93
+ * Main hook handler
94
+ */
95
+ module.exports = async function contextGuardian(context) {
96
+ // Skip if disabled
97
+ if (!CONFIG.enabled) {
98
+ return { continue: true };
99
+ }
100
+
101
+ const { tool, output, error } = context;
102
+
103
+ // Load current usage
104
+ const usage = loadUsageData();
105
+
106
+ // Check if we need to reset for a new day
107
+ const today = new Date().toISOString().split('T')[0];
108
+ if (usage.date !== today) {
109
+ // Archive previous day
110
+ usage.sessions.push(usage.currentSession);
111
+ usage.date = today;
112
+ usage.totalTokens = 0;
113
+ usage.currentSession = {
114
+ id: generateSessionId(),
115
+ startTime: new Date().toISOString(),
116
+ tokens: 0,
117
+ toolCalls: 0,
118
+ };
119
+ }
120
+
121
+ // Estimate tokens from this tool call
122
+ const outputTokens = estimateTokens(output);
123
+ const errorTokens = estimateTokens(error);
124
+ const callTokens = outputTokens + errorTokens;
125
+
126
+ // Update usage
127
+ usage.currentSession.tokens += callTokens;
128
+ usage.currentSession.toolCalls += 1;
129
+ usage.totalTokens += callTokens;
130
+
131
+ // Save updated usage
132
+ saveUsageData(usage);
133
+
134
+ // Calculate usage percentage
135
+ const usagePercent = getUsagePercentage(usage.totalTokens);
136
+
137
+ // Check thresholds and add warnings
138
+ let message = null;
139
+
140
+ if (usagePercent >= CONFIG.thresholds.respawn) {
141
+ message = `⚠️ CRITICAL: Token usage at ${(usagePercent * 100).toFixed(1)}% of daily budget!
142
+ Consider respawning session to preserve budget.
143
+ Run /context-audit for details.`;
144
+ } else if (usagePercent >= CONFIG.thresholds.archive) {
145
+ message = `⚠️ WARNING: Token usage at ${(usagePercent * 100).toFixed(1)}% of daily budget.
146
+ Consider archiving this session.
147
+ Run /context-audit for recommendations.`;
148
+ } else if (usagePercent >= CONFIG.thresholds.compact) {
149
+ message = `ℹ️ Token usage at ${(usagePercent * 100).toFixed(1)}% of daily budget.
150
+ Consider compacting context soon.`;
151
+ }
152
+
153
+ return {
154
+ continue: true,
155
+ message: message,
156
+ // Add metadata for other hooks/tools to use
157
+ metadata: {
158
+ tokenUsage: {
159
+ session: usage.currentSession.tokens,
160
+ daily: usage.totalTokens,
161
+ budget: CONFIG.dailyBudget,
162
+ percentUsed: usagePercent,
163
+ status: usagePercent >= CONFIG.thresholds.respawn
164
+ ? 'critical'
165
+ : usagePercent >= CONFIG.thresholds.archive
166
+ ? 'warning'
167
+ : usagePercent >= CONFIG.thresholds.compact
168
+ ? 'attention'
169
+ : 'ok',
170
+ },
171
+ },
172
+ };
173
+ };
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Deployment Orchestrator Hook
3
+ *
4
+ * Monitors deployment-related commands and provides coordination.
5
+ * Prevents conflicting deployments and tracks deployment state.
6
+ *
7
+ * Event: PreToolUse
8
+ * Priority: {{hooks.priorities.tools}}
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { execSync } = require('child_process');
14
+
15
+ // Configuration from tech-stack.json
16
+ const CONFIG = {
17
+ backend: {
18
+ platform: '{{deployment.backend.platform}}',
19
+ projectId: '{{deployment.backend.projectId}}',
20
+ serviceId: '{{deployment.backend.serviceId}}',
21
+ },
22
+ frontend: {
23
+ platform: '{{deployment.frontend.platform}}',
24
+ projectName: '{{deployment.frontend.projectName}}',
25
+ },
26
+ };
27
+
28
+ const DEPLOYMENT_STATE_FILE = '.claude/hooks/cache/deployment-state.json';
29
+
30
+ /**
31
+ * Load deployment state
32
+ */
33
+ function loadDeploymentState() {
34
+ const statePath = path.join(process.cwd(), DEPLOYMENT_STATE_FILE);
35
+
36
+ if (fs.existsSync(statePath)) {
37
+ try {
38
+ return JSON.parse(fs.readFileSync(statePath, 'utf8'));
39
+ } catch (error) {
40
+ // Ignore parse errors
41
+ }
42
+ }
43
+
44
+ return {
45
+ backend: { status: 'idle', lastDeployment: null },
46
+ frontend: { status: 'idle', lastDeployment: null },
47
+ inProgress: false,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Save deployment state
53
+ */
54
+ function saveDeploymentState(state) {
55
+ const statePath = path.join(process.cwd(), DEPLOYMENT_STATE_FILE);
56
+ const stateDir = path.dirname(statePath);
57
+
58
+ if (!fs.existsSync(stateDir)) {
59
+ fs.mkdirSync(stateDir, { recursive: true });
60
+ }
61
+
62
+ state.lastUpdated = new Date().toISOString();
63
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
64
+ }
65
+
66
+ /**
67
+ * Check if a deployment is in progress
68
+ */
69
+ function checkDeploymentInProgress(state) {
70
+ // Check if state indicates deployment in progress
71
+ if (state.inProgress) {
72
+ // Check if it's been more than 10 minutes (likely stale)
73
+ if (state.lastUpdated) {
74
+ const lastUpdate = new Date(state.lastUpdated);
75
+ const now = new Date();
76
+ const minutes = (now - lastUpdate) / 1000 / 60;
77
+
78
+ if (minutes > 10) {
79
+ // Stale state, reset
80
+ state.inProgress = false;
81
+ state.backend.status = 'idle';
82
+ state.frontend.status = 'idle';
83
+ saveDeploymentState(state);
84
+ return false;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ /**
93
+ * Detect deployment-related commands
94
+ */
95
+ function isDeploymentCommand(tool, input) {
96
+ // MCP Railway deployment
97
+ if (tool.includes('railway') && tool.includes('deployment')) {
98
+ return { type: 'backend', platform: 'railway' };
99
+ }
100
+
101
+ // Bash commands
102
+ if (tool === 'Bash' && input && input.command) {
103
+ const cmd = input.command.toLowerCase();
104
+
105
+ // Cloudflare/Vercel/Netlify deploys
106
+ if (cmd.includes('wrangler pages deploy') || cmd.includes('wrangler deploy')) {
107
+ return { type: 'frontend', platform: 'cloudflare' };
108
+ }
109
+ if (cmd.includes('vercel') && (cmd.includes('--prod') || cmd.includes('deploy'))) {
110
+ return { type: 'frontend', platform: 'vercel' };
111
+ }
112
+ if (cmd.includes('netlify deploy')) {
113
+ return { type: 'frontend', platform: 'netlify' };
114
+ }
115
+
116
+ // Heroku
117
+ if (cmd.includes('git push heroku')) {
118
+ return { type: 'backend', platform: 'heroku' };
119
+ }
120
+
121
+ // Fly.io
122
+ if (cmd.includes('fly deploy')) {
123
+ return { type: 'backend', platform: 'fly' };
124
+ }
125
+ }
126
+
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Main hook handler
132
+ */
133
+ module.exports = async function deploymentOrchestrator(context) {
134
+ const { tool, input } = context;
135
+
136
+ // Check if this is a deployment command
137
+ const deployment = isDeploymentCommand(tool, input);
138
+
139
+ if (!deployment) {
140
+ return { continue: true };
141
+ }
142
+
143
+ // Load current deployment state
144
+ const state = loadDeploymentState();
145
+
146
+ // Check if another deployment is in progress
147
+ if (checkDeploymentInProgress(state)) {
148
+ const inProgressType = state.backend.status === 'deploying' ? 'backend' : 'frontend';
149
+
150
+ return {
151
+ continue: false, // Block the deployment
152
+ message: `⚠️ Deployment blocked: ${inProgressType} deployment already in progress.
153
+
154
+ Current state:
155
+ - Backend: ${state.backend.status}
156
+ - Frontend: ${state.frontend.status}
157
+
158
+ Wait for the current deployment to complete, or clear the state if it's stuck:
159
+ - Check deployment status in your platform dashboard
160
+ - Clear state by deleting ${DEPLOYMENT_STATE_FILE}`,
161
+ };
162
+ }
163
+
164
+ // Mark deployment as in progress
165
+ state.inProgress = true;
166
+ state[deployment.type].status = 'deploying';
167
+ state[deployment.type].startTime = new Date().toISOString();
168
+ state[deployment.type].platform = deployment.platform;
169
+ saveDeploymentState(state);
170
+
171
+ // Allow the deployment to proceed
172
+ return {
173
+ continue: true,
174
+ message: `🚀 Starting ${deployment.type} deployment to ${deployment.platform}...
175
+
176
+ Pre-flight checks:
177
+ ${CONFIG[deployment.type].platform === deployment.platform ? '✅' : '⚠️'} Platform matches config
178
+ ✅ No conflicting deployments
179
+
180
+ Deployment state saved. Run /deploy-full to see full deployment status.`,
181
+ };
182
+ };
183
+
184
+ /**
185
+ * Post-deployment cleanup (called as PostToolUse)
186
+ */
187
+ module.exports.postDeployment = async function postDeploymentOrchestrator(context) {
188
+ const { tool, input, output, error } = context;
189
+
190
+ // Check if this was a deployment command
191
+ const deployment = isDeploymentCommand(tool, input);
192
+
193
+ if (!deployment) {
194
+ return { continue: true };
195
+ }
196
+
197
+ // Load and update state
198
+ const state = loadDeploymentState();
199
+
200
+ state.inProgress = false;
201
+ state[deployment.type].status = error ? 'failed' : 'completed';
202
+ state[deployment.type].lastDeployment = new Date().toISOString();
203
+ state[deployment.type].duration = state[deployment.type].startTime
204
+ ? Math.round((Date.now() - new Date(state[deployment.type].startTime)) / 1000)
205
+ : null;
206
+
207
+ if (error) {
208
+ state[deployment.type].lastError = error.message || String(error);
209
+ }
210
+
211
+ saveDeploymentState(state);
212
+
213
+ return {
214
+ continue: true,
215
+ message: error
216
+ ? `❌ ${deployment.type} deployment failed after ${state[deployment.type].duration}s`
217
+ : `✅ ${deployment.type} deployment completed in ${state[deployment.type].duration}s`,
218
+ };
219
+ };
@@ -0,0 +1,197 @@
1
+ /**
2
+ * GitHub Progress Hook
3
+ *
4
+ * Automatically updates GitHub issues as tasks are completed.
5
+ * Monitors TodoWrite/TaskUpdate calls and syncs progress to linked GitHub issues.
6
+ *
7
+ * Event: PostToolUse
8
+ * Priority: {{hooks.priorities.automation}}
9
+ */
10
+
11
+ const { execSync } = require('child_process');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Configuration from tech-stack.json
16
+ const CONFIG = {
17
+ owner: '{{versionControl.owner}}',
18
+ repo: '{{versionControl.repo}}',
19
+ projectNumber: {{versionControl.projectBoard.number}},
20
+ enabled: '{{versionControl.projectBoard.type}}' === 'github-projects',
21
+ };
22
+
23
+ const PROGRESS_FILE = '.claude/hooks/cache/github-progress.json';
24
+
25
+ /**
26
+ * Load progress tracking data
27
+ */
28
+ function loadProgress() {
29
+ const progressPath = path.join(process.cwd(), PROGRESS_FILE);
30
+
31
+ if (fs.existsSync(progressPath)) {
32
+ try {
33
+ return JSON.parse(fs.readFileSync(progressPath, 'utf8'));
34
+ } catch (error) {
35
+ console.warn('[github-progress] Could not parse progress file');
36
+ }
37
+ }
38
+
39
+ return {
40
+ linkedIssue: null,
41
+ tasks: [],
42
+ completedTasks: [],
43
+ lastUpdate: null,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Save progress tracking data
49
+ */
50
+ function saveProgress(progress) {
51
+ const progressPath = path.join(process.cwd(), PROGRESS_FILE);
52
+ const progressDir = path.dirname(progressPath);
53
+
54
+ if (!fs.existsSync(progressDir)) {
55
+ fs.mkdirSync(progressDir, { recursive: true });
56
+ }
57
+
58
+ progress.lastUpdate = new Date().toISOString();
59
+ fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
60
+ }
61
+
62
+ /**
63
+ * Check if gh CLI is available
64
+ */
65
+ function hasGhCli() {
66
+ try {
67
+ execSync('gh --version', { stdio: 'ignore' });
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Update GitHub issue with progress
76
+ */
77
+ function updateGitHubIssue(issueNumber, completedTasks, totalTasks, latestTask) {
78
+ if (!hasGhCli()) {
79
+ console.warn('[github-progress] gh CLI not available');
80
+ return false;
81
+ }
82
+
83
+ try {
84
+ // Create progress comment
85
+ const percentage = Math.round((completedTasks / totalTasks) * 100);
86
+ const progressBar = '█'.repeat(Math.floor(percentage / 10)) + '░'.repeat(10 - Math.floor(percentage / 10));
87
+
88
+ const comment = `### Progress Update
89
+
90
+ ${progressBar} ${percentage}% (${completedTasks}/${totalTasks} tasks)
91
+
92
+ **Latest completed:** ${latestTask || 'N/A'}
93
+
94
+ ---
95
+ *Auto-updated by Claude Code github-progress-hook*`;
96
+
97
+ // Add comment to issue
98
+ execSync(
99
+ `gh issue comment ${issueNumber} --repo ${CONFIG.owner}/${CONFIG.repo} --body "${comment.replace(/"/g, '\\"')}"`,
100
+ { stdio: 'ignore' }
101
+ );
102
+
103
+ return true;
104
+ } catch (error) {
105
+ console.warn('[github-progress] Failed to update GitHub:', error.message);
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Extract linked issue from task metadata or context
112
+ */
113
+ function findLinkedIssue(input, progress) {
114
+ // Check if issue is already linked
115
+ if (progress.linkedIssue) {
116
+ return progress.linkedIssue;
117
+ }
118
+
119
+ // Try to extract from task description
120
+ if (input && input.description) {
121
+ const issueMatch = input.description.match(/#(\d+)/);
122
+ if (issueMatch) {
123
+ return parseInt(issueMatch[1], 10);
124
+ }
125
+ }
126
+
127
+ // Try to extract from subject
128
+ if (input && input.subject) {
129
+ const issueMatch = input.subject.match(/#(\d+)/);
130
+ if (issueMatch) {
131
+ return parseInt(issueMatch[1], 10);
132
+ }
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Main hook handler
140
+ */
141
+ module.exports = async function githubProgressHook(context) {
142
+ // Skip if not configured
143
+ if (!CONFIG.enabled || !CONFIG.owner || !CONFIG.repo) {
144
+ return { continue: true };
145
+ }
146
+
147
+ const { tool, input } = context;
148
+
149
+ // Only process task-related tools
150
+ if (!['TodoWrite', 'TaskUpdate', 'TaskCreate'].includes(tool)) {
151
+ return { continue: true };
152
+ }
153
+
154
+ // Load progress tracking
155
+ const progress = loadProgress();
156
+
157
+ // Check for linked issue
158
+ const issueNumber = findLinkedIssue(input, progress);
159
+
160
+ if (issueNumber && !progress.linkedIssue) {
161
+ progress.linkedIssue = issueNumber;
162
+ console.log(`[github-progress] Linked to issue #${issueNumber}`);
163
+ }
164
+
165
+ // Track task completion
166
+ if (tool === 'TaskUpdate' && input && input.status === 'completed') {
167
+ const taskId = input.taskId;
168
+ if (taskId && !progress.completedTasks.includes(taskId)) {
169
+ progress.completedTasks.push(taskId);
170
+
171
+ // Update GitHub if we have a linked issue
172
+ if (progress.linkedIssue) {
173
+ const totalTasks = progress.tasks.length || progress.completedTasks.length;
174
+ updateGitHubIssue(
175
+ progress.linkedIssue,
176
+ progress.completedTasks.length,
177
+ totalTasks,
178
+ input.subject || `Task ${taskId}`
179
+ );
180
+ }
181
+ }
182
+ }
183
+
184
+ // Track new tasks
185
+ if (tool === 'TaskCreate' && input && input.subject) {
186
+ progress.tasks.push({
187
+ id: Date.now().toString(),
188
+ subject: input.subject,
189
+ created: new Date().toISOString(),
190
+ });
191
+ }
192
+
193
+ // Save updated progress
194
+ saveProgress(progress);
195
+
196
+ return { continue: true };
197
+ };