wogiflow 2.10.0 → 2.12.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.
@@ -226,6 +226,41 @@ Return:
226
226
 
227
227
  **Blast-radius artifact**: Results are persisted to `.workflow/state/blast-radius-{taskId}.json` for use by downstream gates (context estimator, standards compliance, workspace dispatch).
228
228
 
229
+ ## MCP Capability Injection (Pre-Launch)
230
+
231
+ Before launching agents, inject MCP capability hints so agents can leverage available MCP tools (CC 2.1.101+ — sub-agents inherit MCP tools from parent session).
232
+
233
+ **Step 1**: Check if capabilities are classified:
234
+ ```bash
235
+ node scripts/flow-mcp-capabilities.js check-cache
236
+ ```
237
+
238
+ **Step 2**: If `cache-miss` AND you have MCP tools available (tools starting with `mcp__` in your tool catalog):
239
+ 1. Inspect your available `mcp__*` tools
240
+ 2. For each tool, classify it into a capability category using the category definitions:
241
+ ```bash
242
+ node scripts/flow-mcp-capabilities.js categories
243
+ ```
244
+ 3. Cache the classifications:
245
+ ```bash
246
+ node scripts/flow-mcp-capabilities.js cache '<json>'
247
+ ```
248
+ Format: `{ "server-name": { "tools": [{ "name": "mcp__server__tool", "description": "...", "category": "category-id" }] } }`
249
+
250
+ **Step 3**: For each agent, get its role-specific hint and append to the agent prompt:
251
+ ```bash
252
+ node scripts/flow-mcp-capabilities.js hint explore-codebase # Agent 1
253
+ node scripts/flow-mcp-capabilities.js hint explore-practices # Agent 2
254
+ node scripts/flow-mcp-capabilities.js hint explore-versions # Agent 3
255
+ node scripts/flow-mcp-capabilities.js hint explore-risk # Agent 4
256
+ node scripts/flow-mcp-capabilities.js hint explore-standards # Agent 5
257
+ node scripts/flow-mcp-capabilities.js hint explore-impact # Agent 6
258
+ ```
259
+
260
+ If the hint is non-empty, append it to the agent's prompt. If empty (no relevant MCP tools for that role), skip — the agent works fine without them.
261
+
262
+ **Skip when**: No MCP servers configured (`node scripts/flow-mcp-capabilities.js discover` returns empty), or `config.mcpCapabilities.enabled` is false.
263
+
229
264
  ## Launching
230
265
 
231
266
  All agents launch in parallel as `Agent(subagent_type=Explore)` calls in a single message. When `config.hybrid.enabled`, use the `model` parameter on each Agent call to route by task type:
@@ -0,0 +1,7 @@
1
+ {
2
+ "taskId": null,
3
+ "uniqueFiles": [],
4
+ "thresholdReached": false,
5
+ "scopeInventory": null,
6
+ "warnedAt": null
7
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "deploys": []
3
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "routes": [],
3
+ "lastUpdated": null
4
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "tasks": {}
3
+ }
package/lib/workspace.js CHANGED
@@ -717,20 +717,25 @@ grep -l '"from": "<repo-name>"' .workspace/messages/*.json 2>/dev/null
717
717
  4. If no message after 30s, check the worker's \`ready.json\` for task status
718
718
  5. Once message arrives, read it and present the results to the user
719
719
 
720
- **Message format** (what workers write automatically):
720
+ **Message format** (what workers write automatically via the task-completed hook):
721
721
  \`\`\`json
722
722
  {
723
- "id": "msg-XXXXXXXX",
723
+ "id": "msg-<taskId>-<timestamp>",
724
724
  "from": "<repo-name>",
725
725
  "to": "manager",
726
726
  "type": "task-complete",
727
727
  "subject": "Task completed: <title>",
728
- "body": "**Task**: ...\\n**Files changed**: ...\\n**Summary**: ...",
728
+ "body": "**Task**: ...\\n**Files changed**: ...\\n**Verification evidence**: ...",
729
729
  "taskId": "wf-XXXXXXXX",
730
- "status": "pending"
730
+ "status": "pending",
731
+ "verified": true,
732
+ "evidenceTier": "Tier 3 (INTERACTIVE)|Tier 2 (OBSERVATIONAL)|unknown",
733
+ "timestamp": "ISO-8601"
731
734
  }
732
735
  \`\`\`
733
736
 
737
+ **Trust model**: Messages with \`"verified": true\` went through WogiFlow's quality gates (gate latch check). Freeform curl messages from workers are progress reports, not verified completions — investigate if a worker reports "done" via curl but no structured task-complete message arrives.
738
+
734
739
  **After reading a result**: Present the findings to the user. If the task requires follow-up (e.g., bug investigation found the issue in the other repo), dispatch the fix to the appropriate worker.
735
740
 
736
741
  **You are the SINGLE interface for the user.** They should never need to look at worker terminals. Read the messages, synthesize, and present.
@@ -1128,17 +1133,21 @@ This is NON-OPTIONAL. Every channel-dispatched task MUST end with a reply to the
1128
1133
  **Talk to PEERS directly** (do NOT go through the manager) when you need:
1129
1134
  - API shape/endpoint details from the other repo ("What does POST /customers return?")
1130
1135
  - Data model clarifications ("Does the Invoice entity have a lineItems relation?")
1136
+ - **Test credentials or accounts** ("Do you have E2E login credentials?" / "Can you create a test admin user?")
1137
+ - **Shared resources** ("Can you expose a test API token?" / "What's the connection string for staging?")
1131
1138
  - Coordination on shared types or contracts
1132
1139
  - Confirmation that the other side is ready for integration
1133
1140
  - Any technical question that the other repo can answer from its codebase
1134
1141
 
1142
+ Peers can not only ANSWER questions — they can CREATE resources you need (test accounts, API tokens, seed data, environment configs).
1143
+
1135
1144
  **Talk to the MANAGER** (which means the user) only when:
1136
1145
  - You have **results to report** (task completed, investigation findings)
1137
1146
  - You need a **user decision** (design choice, product question, approval)
1138
- - You are **stuck** and no peer can help
1147
+ - You are **stuck** and no peer can help (after trying Steps 1-2 below)
1139
1148
  - You need to **escalate** something the user should know about
1140
1149
 
1141
- **Rule**: If a peer can answer your question, ask the peer. Do NOT route technical questions through the manager — the manager doesn't read source code and will just relay your question to the peer anyway. Cut out the middleman.
1150
+ **Rule**: If a peer can answer your question or create what you need, go to the peer. Do NOT route through the manager — the manager doesn't read source code and will just relay your question anyway. Cut out the middleman.
1142
1151
 
1143
1152
  **Peer channels:**
1144
1153
  ${peerNames.map(p => `- **${p}**: \`curl -s -X POST http://localhost:${channelMembers[p]?.port || '???'} -H "X-Wogi-From: ${name}" -d "your question"\``).join('\n')}
@@ -1152,15 +1161,37 @@ You are a workspace worker. There is NO human watching your terminal. You MUST o
1152
1161
  - **Auto-approve all reviews**: If /wogi-review asks for fix options, choose Option 1 (fix all) automatically.
1153
1162
  - **Never ask clarifying questions**: If something is ambiguous, make a reasonable decision and note it in your reply to the manager.
1154
1163
 
1155
- ### When to Escalate to Manager
1164
+ ### CRITICAL: Stop, Don't Degrade
1165
+
1166
+ **If you cannot verify your work to the required evidence tier, you may NOT mark the task as complete.** Report it as BLOCKED with the specific verification gap.
1167
+
1168
+ - If browser verification is required but you can't log in → BLOCKED (not done)
1169
+ - If API testing is required but the server is unreachable → BLOCKED (not done)
1170
+ - If you need credentials you don't have → resolve via Steps 1-2 below, then continue. If unresolvable → BLOCKED.
1171
+
1172
+ **Overnight runs STOP when verification is impossible.** Never degrade quality to keep the queue moving. A blocked task with honest status is infinitely better than a "completed" task that doesn't work.
1173
+
1174
+ ### When You're Blocked — Resolution Protocol
1175
+
1176
+ **Step 1: Self-resolve** — check \`.workspace/state/\` for credentials, configs, tokens, test accounts, and any other shared resources. Also check \`.workspace/messages/\` for prior conversations where the resource may have been mentioned.
1177
+
1178
+ **Step 2: Ask peers** — peers can CREATE what you need (test accounts, API tokens, seed data). Send a direct request:
1179
+ \`curl -s -X POST http://localhost:{peer_port} -H "X-Wogi-From: ${name}" -d "I need E2E test credentials. Do you have them, or can you create a test admin account?"\`
1180
+
1181
+ **Step 3: ONLY THEN escalate** to the manager, including what you already tried:
1182
+
1183
+ To escalate: \`curl -s -X POST http://localhost:${config.channels.managerPort || (config.channels.basePort - 1)} -H "X-Wogi-From: ${name}" -d "## Need Decision: [problem]
1184
+ Checked .workspace/state/: [what was found]
1185
+ Asked peers: [who, what response]
1186
+ Why this needs the owner: [explanation]"\`
1187
+
1188
+ ### When to Escalate (After Steps 1-2)
1156
1189
 
1157
1190
  Only send a question to the manager (instead of results) when:
1158
- - The task requires a **design decision** that could go multiple ways (e.g., "should we use REST or GraphQL?")
1191
+ - The task requires a **design decision** that could go multiple ways
1159
1192
  - The task would **break an API contract** that other repos depend on
1160
1193
  - The task requires **deleting user data** or making irreversible changes
1161
- - You are genuinely **stuck** and cannot proceed
1162
-
1163
- To escalate: \`curl -s -X POST http://localhost:${config.channels.managerPort || (config.channels.basePort - 1)} -H "X-Wogi-From: ${name}" -d "## Need Decision: [describe the choice and options]"\`
1194
+ - Steps 1-2 failed and you are genuinely **stuck**
1164
1195
 
1165
1196
  For everything else — just do the work and report results.
1166
1197
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -187,12 +187,61 @@ const CONFIG_DEFAULTS = {
187
187
  implementationGate: { enabled: true },
188
188
  todoWriteGate: { enabled: true, blockImplementationWithoutTask: true },
189
189
  routingGate: { enabled: true },
190
+ commitLogGate: { enabled: true },
190
191
  loopEnforcement: { enabled: true },
191
192
  hypothesisGate: {
192
193
  enabled: true,
193
194
  _comment_hypothesisGate: 'Blocks premature "fixed"/"should work" claims during bug investigation until hypothesis is verified. Pattern: hypothesis → verify → confirm → communicate.',
194
195
  blockedPhrases: ['fixed', 'should work', 'go try', 'go refresh'],
195
196
  requireExplicitVerification: true
197
+ },
198
+ deployGate: {
199
+ _comment_deployGate: 'Blocks deploy commands unless a valid HMAC-signed verification artifact exists. Off by default — opt in via `flow deploy-gate init`.',
200
+ enabled: false,
201
+ commands: [],
202
+ sourcePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue', '**/*.svelte', '**/*.css'],
203
+ requireForPriorities: ['P0', 'P1'],
204
+ blockWriteToVerifications: true,
205
+ minVerifiedRoutes: 3,
206
+ rejectLoginOnly: true
207
+ },
208
+ strikeEscalation: {
209
+ _comment_strikeEscalation: 'Mechanical strike counter. Blocks Edit/Write after repeated verification failures on the same task.',
210
+ enabled: true,
211
+ blockThreshold: 2,
212
+ escalateThreshold: 3,
213
+ hardBlockThreshold: 4,
214
+ productionCrashThreshold: 2
215
+ },
216
+ bugfixScope: {
217
+ _comment_bugfixScope: 'Pauses L3 bugfixes after N unique non-test files edited, requiring a scope inventory.',
218
+ enabled: true,
219
+ mode: 'warn',
220
+ fileThreshold: 3,
221
+ excludePatterns: ['*.test.*', '*.spec.*', '*.d.ts', '__tests__/**', '__mocks__/**'],
222
+ keywordMatchThreshold: 2,
223
+ fanOutThreshold: 10
224
+ },
225
+ revertFirst: {
226
+ _comment_revertFirst: 'For production crashes: recommends reverting before forward-fixing. Off by default.',
227
+ enabled: false,
228
+ keywords: ['production', 'crash', 'down', 'outage', '500 errors', 'users can\u0027t', 'site is broken', 'live issue'],
229
+ deployHistoryRetention: 50,
230
+ oldDeployWarningDays: 7
231
+ },
232
+ scopeMutation: {
233
+ _comment_scopeMutation: 'Agnostic gate: fix tasks creating 2+ new files → warn. Deleting pre-existing files → warn. No framework pattern matching.',
234
+ enabled: true,
235
+ newFileThreshold: 2,
236
+ mode: 'warn'
237
+ },
238
+ gitSafety: {
239
+ _comment_gitSafety: 'Auto-backup before destructive git operations. Prevents accidental loss of work from prior sessions.',
240
+ enabled: true,
241
+ maxBackwardCommits: 3,
242
+ ageThresholdHours: 24,
243
+ autoBackup: true,
244
+ maxBackupBranches: 3
196
245
  }
197
246
  },
198
247
 
@@ -342,6 +391,13 @@ const CONFIG_DEFAULTS = {
342
391
  similarityThreshold: 0.8,
343
392
  similarityWarningThreshold: 0.6
344
393
  },
394
+ // --- Workspace Sovereignty ---
395
+ workspace: {
396
+ _comment_workerGatesSovereign: 'When true, workers cannot skip their own quality gates even if the manager instructs them to',
397
+ workerGatesSovereign: true,
398
+ managerCanOverrideLevel: false,
399
+ managerCanSkipGates: false
400
+ },
345
401
  checkpoint: { enabled: false },
346
402
  regressionTesting: { enabled: false },
347
403
 
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Deploy Gate CLI
5
+ *
6
+ * Commands:
7
+ * flow deploy-gate init — Interactive setup wizard
8
+ * flow deploy-gate status — Show gate status and artifact info
9
+ * flow deploy-gate verify — Check if a valid artifact exists
10
+ * flow deploy-gate routes — Show route inventory
11
+ * flow deploy-gate add-route <path> — Manually add a route
12
+ * flow deploy-gate history — Show deploy history
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+ const { PATHS, getConfig, safeJsonParse, writeJson } = require('./flow-utils');
20
+ const {
21
+ isDeployGateEnabled,
22
+ getDeployGateConfig,
23
+ findLatestArtifact,
24
+ getRouteInventory,
25
+ addRoute,
26
+ getLastGoodDeploy,
27
+ DEPLOY_ROUTES_PATH,
28
+ DEPLOY_HISTORY_PATH
29
+ } = require('./hooks/core/deploy-gate');
30
+
31
+ // ============================================================
32
+ // CLI Commands
33
+ // ============================================================
34
+
35
+ function cmdInit() {
36
+ console.log('━━━ Deploy Gate Setup Wizard ━━━\n');
37
+
38
+ // 1. Detect package.json scripts
39
+ const pkgPath = path.join(PATHS.root, 'package.json');
40
+ const pkg = safeJsonParse(pkgPath, {});
41
+ const scripts = pkg.scripts || {};
42
+ const deployScripts = [];
43
+
44
+ for (const [name, cmd] of Object.entries(scripts)) {
45
+ if (/deploy|publish|release|sync|push/i.test(name) || /deploy|s3.*sync|vercel|netlify|fly.*deploy/i.test(cmd)) {
46
+ deployScripts.push({ name, cmd });
47
+ }
48
+ }
49
+
50
+ if (deployScripts.length > 0) {
51
+ console.log('Detected deploy-related scripts:');
52
+ for (const s of deployScripts) {
53
+ console.log(` - npm run ${s.name}: ${s.cmd}`);
54
+ }
55
+ } else {
56
+ console.log('No deploy scripts detected in package.json.');
57
+ }
58
+
59
+ // 2. Suggest common deploy command patterns
60
+ const suggestedCommands = [];
61
+ for (const s of deployScripts) {
62
+ suggestedCommands.push(`npm run ${s.name}`);
63
+ // Also extract the raw command for direct matching
64
+ if (s.cmd) suggestedCommands.push(s.cmd.split('&&')[0].trim());
65
+ }
66
+
67
+ // Add common defaults if none detected
68
+ if (suggestedCommands.length === 0) {
69
+ suggestedCommands.push(
70
+ 'aws s3 sync',
71
+ 'vercel deploy',
72
+ 'netlify deploy',
73
+ 'fly deploy',
74
+ 'docker push'
75
+ );
76
+ console.log('\nSuggested common deploy patterns (add the ones your project uses):');
77
+ for (const cmd of suggestedCommands) {
78
+ console.log(` - ${cmd}`);
79
+ }
80
+ }
81
+
82
+ // 3. Detect framework
83
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
84
+ let framework = 'unknown';
85
+ if (deps['next']) framework = 'nextjs';
86
+ else if (deps['vite'] || deps['@vitejs/plugin-react']) framework = 'vite';
87
+ else if (deps['@angular/core']) framework = 'angular';
88
+ else if (deps['vue']) framework = 'vue';
89
+ else if (deps['svelte']) framework = 'svelte';
90
+ else if (deps['express'] || deps['fastify'] || deps['@nestjs/core']) framework = 'backend';
91
+
92
+ console.log(`\nDetected framework: ${framework}`);
93
+
94
+ // 4. Suggest verification method
95
+ let verificationMethod = 'checklist';
96
+ if (deps['playwright'] || deps['@playwright/test']) verificationMethod = 'playwright';
97
+ if (framework === 'backend') verificationMethod = 'api-test';
98
+
99
+ console.log(`Suggested verification method: ${verificationMethod}`);
100
+
101
+ // 5. Generate initial route inventory from common patterns
102
+ console.log('\nScanning for routes...');
103
+ const routes = scanForRoutes(framework);
104
+ if (routes.length > 0) {
105
+ console.log(`Found ${routes.length} route(s):`);
106
+ for (const r of routes) {
107
+ console.log(` - ${r}`);
108
+ addRoute(r, 'init-wizard');
109
+ }
110
+ } else {
111
+ console.log('No routes auto-detected. Add routes manually with: flow deploy-gate add-route <path>');
112
+ }
113
+
114
+ // 6. Write suggested config
115
+ console.log('\n━━━ Suggested Configuration ━━━');
116
+ console.log('Add to .workflow/config.json under "enforcement":\n');
117
+ const suggestedConfig = {
118
+ deployGate: {
119
+ enabled: true,
120
+ commands: suggestedCommands.slice(0, 5),
121
+ sourcePatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue', '**/*.svelte', '**/*.css'],
122
+ requireForPriorities: ['P0', 'P1'],
123
+ blockWriteToVerifications: true
124
+ }
125
+ };
126
+ console.log(JSON.stringify(suggestedConfig, null, 2));
127
+
128
+ console.log('\n━━━ Setup Complete ━━━');
129
+ console.log('Next steps:');
130
+ console.log(' 1. Add the config above to .workflow/config.json');
131
+ console.log(' 2. Customize the commands list for your deploy workflow');
132
+ console.log(' 3. Run `flow deploy-gate status` to verify setup');
133
+ }
134
+
135
+ function cmdStatus() {
136
+ const config = getConfig();
137
+ const enabled = isDeployGateEnabled(config);
138
+ const gateConfig = getDeployGateConfig(config);
139
+
140
+ console.log('━━━ Deploy Gate Status ━━━\n');
141
+ console.log(`Enabled: ${enabled ? '✓ YES' : '✗ NO'}`);
142
+
143
+ if (!enabled) {
144
+ console.log('\nRun `flow deploy-gate init` to set up the deploy gate.');
145
+ return;
146
+ }
147
+
148
+ console.log(`Commands blocked: ${gateConfig.commands.length > 0 ? gateConfig.commands.join(', ') : '(none configured)'}`);
149
+ console.log(`Require verification for: ${gateConfig.requireForPriorities.join(', ')}`);
150
+ console.log(`Block Write to artifacts: ${gateConfig.blockWriteToVerifications}`);
151
+
152
+ // Check for valid artifact
153
+ const artifactResult = findLatestArtifact();
154
+ console.log(`\nLatest artifact: ${artifactResult.found ? '✓ VALID' : '✗ ' + (artifactResult.reason || 'NOT FOUND')}`);
155
+ if (artifactResult.found) {
156
+ const a = artifactResult.artifact;
157
+ console.log(` Method: ${a.method}`);
158
+ console.log(` Task: ${a.taskId}`);
159
+ console.log(` Created: ${a.createdAt}`);
160
+ console.log(` Routes verified: ${(a.routes || []).length}`);
161
+ console.log(` Evidence tier: ${a.evidenceTier}`);
162
+ }
163
+
164
+ // Route inventory
165
+ const inventory = getRouteInventory();
166
+ console.log(`\nRoute inventory: ${inventory.routes.length} route(s)`);
167
+
168
+ // Deploy history
169
+ const lastDeploy = getLastGoodDeploy();
170
+ if (lastDeploy.found) {
171
+ console.log(`Last deploy: ${lastDeploy.deploy.commitHash.slice(0, 8)} (${lastDeploy.deploy.timestamp})`);
172
+ } else {
173
+ console.log('Last deploy: (no history)');
174
+ }
175
+ }
176
+
177
+ function cmdVerify() {
178
+ const artifactResult = findLatestArtifact();
179
+ if (artifactResult.found) {
180
+ console.log('✓ Valid verification artifact found');
181
+ console.log(JSON.stringify(artifactResult.artifact, null, 2));
182
+ process.exit(0);
183
+ } else {
184
+ console.log(`✗ No valid artifact: ${artifactResult.reason}`);
185
+ process.exit(1);
186
+ }
187
+ }
188
+
189
+ function cmdRoutes() {
190
+ const inventory = getRouteInventory();
191
+ console.log('━━━ Route Inventory ━━━\n');
192
+ if (inventory.routes.length === 0) {
193
+ console.log('No routes registered. Add with: flow deploy-gate add-route <path>');
194
+ return;
195
+ }
196
+ for (const r of inventory.routes) {
197
+ console.log(` ${r.path} — added ${r.addedAt} (${r.source})`);
198
+ }
199
+ console.log(`\nTotal: ${inventory.routes.length} route(s)`);
200
+ }
201
+
202
+ function cmdAddRoute(routePath) {
203
+ if (!routePath) {
204
+ console.error('Usage: flow deploy-gate add-route <path>');
205
+ process.exit(1);
206
+ }
207
+ const added = addRoute(routePath, 'manual');
208
+ if (added) {
209
+ console.log(`✓ Added route: ${routePath}`);
210
+ } else {
211
+ console.log(`Route already exists: ${routePath}`);
212
+ }
213
+ }
214
+
215
+ function cmdHistory() {
216
+ const history = safeJsonParse(DEPLOY_HISTORY_PATH, { deploys: [] });
217
+ console.log('━━━ Deploy History ━━━\n');
218
+ if (history.deploys.length === 0) {
219
+ console.log('No deploy history recorded.');
220
+ return;
221
+ }
222
+ for (const d of history.deploys.slice(0, 10)) {
223
+ console.log(` ${d.commitHash.slice(0, 8)} | ${d.timestamp} | ${d.environment}`);
224
+ }
225
+ console.log(`\nShowing ${Math.min(10, history.deploys.length)} of ${history.deploys.length} deploys`);
226
+ }
227
+
228
+ // ============================================================
229
+ // Route Scanning Helpers
230
+ // ============================================================
231
+
232
+ function scanForRoutes(framework) {
233
+ const routes = [];
234
+ const { execSync } = require('node:child_process');
235
+
236
+ try {
237
+ // Next.js: pages or app directory
238
+ if (framework === 'nextjs') {
239
+ const patterns = ['app/**/page.tsx', 'app/**/page.jsx', 'pages/**/*.tsx', 'pages/**/*.jsx', 'src/app/**/page.tsx'];
240
+ for (const pattern of patterns) {
241
+ try {
242
+ const files = execSync(`git ls-files '${pattern}'`, { encoding: 'utf-8', cwd: PATHS.root }).trim();
243
+ if (files) {
244
+ for (const f of files.split('\n')) {
245
+ const route = '/' + f.replace(/^(src\/)?(app|pages)\//, '').replace(/(page|index)\.(tsx|jsx)$/, '').replace(/\/$/, '') || '/';
246
+ if (!routes.includes(route)) routes.push(route);
247
+ }
248
+ }
249
+ } catch (_err) {
250
+ // Pattern didn't match
251
+ }
252
+ }
253
+ }
254
+
255
+ // React Router / Vite: look for route definitions
256
+ if (framework === 'vite' || framework === 'unknown') {
257
+ try {
258
+ const routeFiles = execSync("git ls-files | grep -iE '(router|routes|App)\\.(tsx|jsx|ts|js)$'", { encoding: 'utf-8', cwd: PATHS.root }).trim();
259
+ if (routeFiles) {
260
+ for (const f of routeFiles.split('\n')) {
261
+ try {
262
+ const content = fs.readFileSync(path.join(PATHS.root, f), 'utf-8');
263
+ const pathMatches = content.matchAll(/path:\s*['"]([^'"]+)['"]/g);
264
+ for (const m of pathMatches) {
265
+ if (!routes.includes(m[1])) routes.push(m[1]);
266
+ }
267
+ } catch (_err) {
268
+ // Skip unreadable files
269
+ }
270
+ }
271
+ }
272
+ } catch (_err) {
273
+ // No route files found
274
+ }
275
+ }
276
+
277
+ // Express/backend: look for route handlers
278
+ if (framework === 'backend') {
279
+ try {
280
+ const apiFiles = execSync("git ls-files | grep -iE '(routes|controller|api).*\\.(ts|js)$'", { encoding: 'utf-8', cwd: PATHS.root }).trim();
281
+ if (apiFiles) {
282
+ for (const f of apiFiles.split('\n').slice(0, 20)) {
283
+ try {
284
+ const content = fs.readFileSync(path.join(PATHS.root, f), 'utf-8');
285
+ const pathMatches = content.matchAll(/\.(get|post|put|patch|delete)\s*\(\s*['"]([^'"]+)['"]/gi);
286
+ for (const m of pathMatches) {
287
+ const route = `${m[1].toUpperCase()} ${m[2]}`;
288
+ if (!routes.includes(route)) routes.push(route);
289
+ }
290
+ } catch (_err) {
291
+ // Skip unreadable files
292
+ }
293
+ }
294
+ }
295
+ } catch (_err) {
296
+ // No API files found
297
+ }
298
+ }
299
+ } catch (_err) {
300
+ // Scanning failed — non-critical
301
+ }
302
+
303
+ return routes;
304
+ }
305
+
306
+ // ============================================================
307
+ // CLI Entrypoint
308
+ // ============================================================
309
+
310
+ const args = process.argv.slice(2);
311
+ const command = args[0];
312
+
313
+ switch (command) {
314
+ case 'init':
315
+ cmdInit();
316
+ break;
317
+ case 'status':
318
+ cmdStatus();
319
+ break;
320
+ case 'verify':
321
+ cmdVerify();
322
+ break;
323
+ case 'routes':
324
+ cmdRoutes();
325
+ break;
326
+ case 'add-route':
327
+ cmdAddRoute(args[1]);
328
+ break;
329
+ case 'history':
330
+ cmdHistory();
331
+ break;
332
+ default:
333
+ console.log('Usage: flow deploy-gate <init|status|verify|routes|add-route|history>');
334
+ process.exit(1);
335
+ }