wogiflow 1.8.7 → 1.8.9

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.
@@ -162,6 +162,15 @@ Launch all in parallel. When `config.hybrid.enabled`, route via `model` paramete
162
162
 
163
163
  **After agents complete**: Display consolidated research summary covering codebase analysis, best practices, version info, risks, standards, and consumer impact.
164
164
 
165
+ **REUSE GATE (MANDATORY)**: After consolidating agent results, check for reuse candidates:
166
+ 1. Collect all reuse candidates reported by Agent 1 (domain-keyword search) and Agent 5 (registry scan)
167
+ 2. If ANY reuse candidate has purpose overlap with planned new code → **STOP and present to user**:
168
+ - Show each candidate: name, path, purpose, similarity
169
+ - Ask: "Use existing / Extend existing / Create new (explain why)"
170
+ - Implementation BLOCKED until user decides on each candidate
171
+ 3. If no reuse candidates found → proceed normally
172
+ 4. This gate runs BEFORE spec generation — catching reuse early prevents wasted implementation
173
+
165
174
  **For L1/L0 tasks**: Offer to deepen research (exhaustive search, load all skills, full dependency tree).
166
175
 
167
176
  **Fallback**: If agents fail, log warning and proceed with remaining. Consumer Impact failure on refactor tasks = HARD BLOCK (require user confirmation). See `.claude/docs/explore-agents.md` for details.
@@ -9,23 +9,43 @@ Launch as `Agent(subagent_type=Explore)`:
9
9
  ```
10
10
  Analyze the codebase for task: "[TASK_TITLE]"
11
11
 
12
- 1. Use Glob to find files related to: [TASK_KEYWORDS]
13
- 2. Use Grep to search for patterns, function names, component references
14
- 3. Read app-map.md for existing components that could be reused
15
- 4. Read function-map.md for existing utility functions that could be reused
16
- 5. Read api-map.md for existing API endpoints that could be reused
17
- 6. Read decisions.md for patterns that must be followed
18
- 7. Map dependencies:
19
- - Files that REFERENCE the target code
20
- - Files REFERENCED BY the target code
21
- 8. Surface assumptions that need verification
12
+ STEP 1 Domain-keyword search (MANDATORY, do this FIRST):
13
+ a. Extract 3-5 domain keywords from the task title
14
+ Example: "AI Policies tab" ["policy", "policies", "automation", "AI"]
15
+ Example: "Payment service refactor" ["payment", "checkout", "billing", "transaction"]
16
+ b. For EACH keyword, run:
17
+ - Glob **/*[keyword]* to find files with that keyword in the name
18
+ - Grep for the keyword in src/ (or project root) to find references in code
19
+ c. Read EVERY file that matches these are potential reuse candidates
20
+ d. Pay special attention to files in shared/, utils/, lib/, common/ directories
21
+
22
+ STEP 2 — Registry check (read ALL registry maps):
23
+ a. Read app-map.md for existing components that could be reused
24
+ b. Read function-map.md for existing utility functions
25
+ c. Read api-map.md for existing API endpoints
26
+ d. Read any other *-map.md files in .workflow/state/
27
+ e. For each planned NEW item, check if something similar already exists
28
+
29
+ STEP 3 — Pattern & dependency analysis:
30
+ a. Read decisions.md for patterns that must be followed
31
+ b. Map dependencies:
32
+ - Files that REFERENCE the target code
33
+ - Files REFERENCED BY the target code
34
+ c. Surface assumptions that need verification
22
35
 
23
36
  Return a structured summary:
37
+ - **REUSE CANDIDATES** (MUST be first section):
38
+ List every existing file/component/function/service that overlaps
39
+ with what this task plans to create. For each: path, purpose, and
40
+ whether the task should USE it, EXTEND it, or CREATE new.
24
41
  - Related files (path + why it's relevant)
25
- - Existing components to reuse
26
- - Patterns to follow
42
+ - Patterns to follow (from decisions.md)
27
43
  - Dependency map
28
44
  - Assumptions to verify
45
+
46
+ CRITICAL: If domain-keyword search finds existing implementations that
47
+ overlap with the task's goals, this MUST be prominently flagged.
48
+ Do NOT skip Step 1 even if you think you already know the codebase.
29
49
  ```
30
50
 
31
51
  ## Agent 2: Best Practices Researcher
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
package/lib/installer.js CHANGED
@@ -1110,6 +1110,19 @@ function createWorkflowStructure(projectRoot, config) {
1110
1110
  fs.mkdirSync(path.join(workflowDir, dir), { recursive: true });
1111
1111
  }
1112
1112
 
1113
+ // Ensure .workflow/ uses CommonJS — required when project root has "type": "module"
1114
+ // Without this, Node.js inherits ESM from the project root and .workflow/bridges/*.js
1115
+ // (which use require/module.exports) crash with "require is not defined".
1116
+ // Same pattern as scripts/package.json.
1117
+ const workflowPkgPath = path.join(workflowDir, 'package.json');
1118
+ try {
1119
+ fs.writeFileSync(workflowPkgPath, JSON.stringify({ type: 'commonjs' }, null, 2) + '\n', { flag: 'wx' });
1120
+ } catch (err) {
1121
+ if (err.code !== 'EEXIST') {
1122
+ throw err;
1123
+ }
1124
+ }
1125
+
1113
1126
  // Create config.json using shared defaults with project-specific overrides
1114
1127
  const configPath = path.join(workflowDir, 'config.json');
1115
1128
  const configContent = getDefaultConfig({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "1.8.7",
3
+ "version": "1.8.9",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "lib/",
20
20
  "scripts/",
21
21
  "templates/",
22
+ ".workflow/package.json",
22
23
  ".workflow/templates/",
23
24
  ".workflow/agents/",
24
25
  ".workflow/bridges/",
@@ -257,18 +257,30 @@ function collectShareableData(config) {
257
257
 
258
258
  /**
259
259
  * Get WogiFlow version from package.json.
260
+ * Uses require.resolve to find the actual wogiflow package, not the host project.
260
261
  * @returns {string}
261
262
  */
262
263
  let _cachedVersion = null;
263
264
  function getWogiFlowVersion() {
264
265
  if (_cachedVersion) return _cachedVersion;
265
266
  try {
266
- const pkgPath = path.join(__dirname, '..', 'package.json');
267
+ const pkgPath = require.resolve('wogiflow/package.json');
267
268
  const pkg = safeJsonParse(pkgPath, {});
268
269
  _cachedVersion = pkg.version || 'unknown';
269
270
  return _cachedVersion;
270
271
  } catch {
271
- _cachedVersion = 'unknown';
272
+ // Fallback: resolve relative to this file (works when running from source repo)
273
+ try {
274
+ const fallbackPath = path.join(__dirname, '..', 'package.json');
275
+ const pkg = safeJsonParse(fallbackPath, {});
276
+ if (pkg.name === 'wogiflow') {
277
+ _cachedVersion = pkg.version || 'unknown';
278
+ } else {
279
+ _cachedVersion = 'unknown';
280
+ }
281
+ } catch {
282
+ _cachedVersion = 'unknown';
283
+ }
272
284
  return _cachedVersion;
273
285
  }
274
286
  }
@@ -36,14 +36,66 @@ function isComponentCheckEnabled(config) {
36
36
  }
37
37
 
38
38
  /**
39
- * Get component patterns to check
39
+ * Default reusable-code path patterns by project type.
40
+ * These cover ALL areas where reusable code lives — not just UI components.
41
+ * Projects can override via config.componentReuse.patterns.
42
+ */
43
+ const DEFAULT_REUSABLE_PATTERNS = {
44
+ // Frontend-specific
45
+ frontend: [
46
+ '**/components/**', '**/ui/**', '**/src/components/**',
47
+ '**/pages/**/shared/**', '**/features/**/shared/**',
48
+ '**/layouts/**', '**/modals/**', '**/widgets/**'
49
+ ],
50
+ // Backend-specific
51
+ backend: [
52
+ '**/services/**', '**/middleware/**',
53
+ '**/models/**', '**/schemas/**', '**/validators/**',
54
+ '**/routes/**', '**/controllers/**',
55
+ '**/repositories/**', '**/providers/**'
56
+ ],
57
+ // Universal (applies to ALL project types)
58
+ universal: [
59
+ '**/utils/**', '**/helpers/**', '**/lib/**', '**/shared/**',
60
+ '**/hooks/**', '**/composables/**',
61
+ '**/api/**', '**/clients/**',
62
+ '**/types/**', '**/interfaces/**',
63
+ '**/constants/**', '**/config/**'
64
+ ]
65
+ };
66
+
67
+ /**
68
+ * Get reusable-code patterns to check.
69
+ * Combines universal patterns with project-type-specific patterns.
70
+ * Projects can override entirely via config.componentReuse.patterns.
40
71
  * @param {Object} [config] - Pre-loaded config (optional, falls back to getConfig())
41
- * @returns {string[]} Glob patterns for component directories
72
+ * @returns {string[]} Glob patterns for reusable code directories
42
73
  */
43
74
  function getComponentPatterns(config) {
44
75
  if (!config) config = getConfig();
45
- return config.componentReuse?.patterns ||
46
- ['**/components/**', '**/ui/**', '**/src/components/**'];
76
+
77
+ // If user explicitly configured patterns, use those
78
+ if (config.componentReuse?.patterns && Array.isArray(config.componentReuse.patterns)) {
79
+ return config.componentReuse.patterns;
80
+ }
81
+
82
+ // Build patterns from project type
83
+ const projectType = config.projectType || 'unknown';
84
+ const patterns = [...DEFAULT_REUSABLE_PATTERNS.universal];
85
+
86
+ if (projectType === 'frontend' || projectType === 'fullstack' || projectType === 'unknown') {
87
+ patterns.push(...DEFAULT_REUSABLE_PATTERNS.frontend);
88
+ }
89
+ if (projectType === 'backend' || projectType === 'fullstack' || projectType === 'unknown') {
90
+ patterns.push(...DEFAULT_REUSABLE_PATTERNS.backend);
91
+ }
92
+
93
+ // Add any extra patterns from config (additive)
94
+ if (config.componentReuse?.extraPatterns && Array.isArray(config.componentReuse.extraPatterns)) {
95
+ patterns.push(...config.componentReuse.extraPatterns);
96
+ }
97
+
98
+ return patterns;
47
99
  }
48
100
 
49
101
  /**
@@ -87,17 +139,53 @@ function isComponentPath(filePath, config) {
87
139
  }
88
140
 
89
141
  /**
90
- * Load the component index
142
+ * Load the component index.
143
+ * Falls back to building a minimal index from all registry maps when
144
+ * component-index.json doesn't exist.
91
145
  * @returns {Object|null} Component index or null
92
146
  */
93
147
  function loadComponentIndex() {
94
148
  try {
95
149
  const indexPath = path.join(PATHS.state, 'component-index.json');
96
- if (!fs.existsSync(indexPath)) {
97
- return null;
150
+ if (fs.existsSync(indexPath)) {
151
+ return JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
152
+ }
153
+
154
+ // Fallback: build a minimal index from all *-map.md files in state/
155
+ const stateDir = PATHS.state;
156
+ if (!stateDir || !fs.existsSync(stateDir)) return null;
157
+
158
+ const components = [];
159
+ const entries = fs.readdirSync(stateDir);
160
+ for (const entry of entries) {
161
+ if (entry.endsWith('-map.md') && /^[a-z0-9-]+\.md$/.test(entry)) {
162
+ try {
163
+ const content = fs.readFileSync(path.join(stateDir, entry), 'utf-8');
164
+ const lines = content.split('\n');
165
+ for (const line of lines) {
166
+ // Parse table rows: | Name | description | path |
167
+ const tableMatch = line.match(/^\|\s*([^|]+)\s*\|\s*([^|]*)\s*\|\s*([^|]*)\s*\|/);
168
+ if (tableMatch && !tableMatch[1].includes('---')) {
169
+ const name = tableMatch[1].trim();
170
+ if (name && name !== 'Component' && name !== 'Name' && name !== 'Function' && name !== 'Endpoint') {
171
+ components.push({
172
+ name,
173
+ description: (tableMatch[2] || '').trim(),
174
+ path: (tableMatch[3] || '').trim(),
175
+ source: entry.replace('.md', '')
176
+ });
177
+ }
178
+ }
179
+ }
180
+ } catch (err) {
181
+ // Skip unreadable map files
182
+ }
183
+ }
98
184
  }
99
- return JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
100
- } catch (_err) {
185
+
186
+ if (components.length === 0) return null;
187
+ return { components, source: 'fallback-from-maps' };
188
+ } catch (err) {
101
189
  return null;
102
190
  }
103
191
  }
@@ -136,7 +224,7 @@ function parseAppMap() {
136
224
  }
137
225
 
138
226
  return components;
139
- } catch (_err) {
227
+ } catch (err) {
140
228
  return [];
141
229
  }
142
230
  }
@@ -75,8 +75,10 @@ function hasActiveTask() {
75
75
  if (process.env.DEBUG) {
76
76
  console.error(`[routing-gate] Ready data read error: ${err.message}`);
77
77
  }
78
- // Fail-open: if can't read ready.json, assume active task exists
79
- return true;
78
+ // Fail-closed: if can't read ready.json, assume no active task.
79
+ // Fail-open here was a bypass vector — corrupted ready.json would make
80
+ // subagent routing think a task exists and skip the routing gate.
81
+ return false;
80
82
  }
81
83
  }
82
84
 
@@ -52,7 +52,7 @@ async function main() {
52
52
  process.exit(0);
53
53
  return;
54
54
  }
55
- } catch (_parseErr) {
55
+ } catch (err) {
56
56
  // Parse error - allow through (graceful degradation)
57
57
  console.log(JSON.stringify({ continue: true, hookSpecificOutput: { hookEventName: 'UserPromptSubmit' } }));
58
58
  process.exit(0);
@@ -118,7 +118,8 @@ async function main() {
118
118
 
119
119
  // v6.0: Set routing-pending flag for routing gate enforcement
120
120
  // This blocks ALL gated tool calls until a /wogi-* skill is invoked
121
- // v8.0: Always set, even with active tasks — every turn must route through /wogi-start
121
+ // v8.0: Always set, even with active tasks — every turn must route through /wogi-start.
122
+ // Exception: skipped when the prompt IS a /wogi-* command (see isWogiCommand below).
122
123
  // v6.1: Also skip when the prompt IS a /wogi-* command — the user is already routing.
123
124
  // When users type "/wogi-start ..." directly, Claude Code expands the skill inline
124
125
  // (not through the Skill tool), so clearRoutingPending() in PreToolUse never fires.
@@ -134,7 +134,12 @@ function createMinimalStructure() {
134
134
  ? fs.readFileSync(templatePath, 'utf-8')
135
135
  : JSON.stringify({ prompts: [], version: 1 }, null, 2);
136
136
  // Validate template is valid JSON before writing (with proto check)
137
- const parsed = JSON.parse(rawContent);
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(rawContent);
140
+ } catch (err) {
141
+ parsed = { prompts: [], version: 1 };
142
+ }
138
143
  if (parsed && typeof parsed === 'object') {
139
144
  for (const k of Object.keys(parsed)) {
140
145
  if (k === '__proto__' || k === 'constructor' || k === 'prototype') delete parsed[k];
@@ -384,6 +389,19 @@ function copyWorkflowManagedDirs() {
384
389
  copyDir(src, dest, false);
385
390
  }
386
391
  }
392
+
393
+ // Ensure .workflow/package.json exists with "type": "commonjs".
394
+ // Required when the project root has "type": "module" — without it,
395
+ // Node.js inherits ESM and .workflow/bridges/*.js (CJS) crash.
396
+ const workflowPkg = path.join(WORKFLOW_DIR, 'package.json');
397
+ try {
398
+ fs.writeFileSync(workflowPkg, JSON.stringify({ type: 'commonjs' }, null, 2) + '\n', { flag: 'wx' });
399
+ } catch (err) {
400
+ if (err.code !== 'EEXIST') {
401
+ console.warn(`[postinstall] Warning: Failed to create .workflow/package.json: ${err.message}`);
402
+ console.warn('[postinstall] ESM projects may have issues with WogiFlow hooks. Create this file manually with: {"type":"commonjs"}');
403
+ }
404
+ }
387
405
  }
388
406
 
389
407
  /**