wogiflow 2.21.0 → 2.22.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 (252) hide show
  1. package/.claude/commands/wogi-finalize.md +83 -0
  2. package/.claude/rules/_internal/self-maintenance.md +1 -1
  3. package/.claude/settings.json +1 -1
  4. package/lib/commands/login.js +1 -1
  5. package/lib/installer.js +5 -5
  6. package/lib/release-channel.js +1 -1
  7. package/lib/skill-registry.js +3 -3
  8. package/lib/workspace-events.js +1 -1
  9. package/lib/workspace-gates.js +2 -2
  10. package/lib/workspace-intelligence.js +1 -1
  11. package/lib/workspace-routing.js +1 -1
  12. package/lib/workspace.js +16 -17
  13. package/package.json +2 -2
  14. package/scripts/base-workflow-step.js +2 -2
  15. package/scripts/flow-adaptive-learning.js +6 -6
  16. package/scripts/flow-api-index.js +2 -2
  17. package/scripts/flow-architect-pass.js +1 -1
  18. package/scripts/flow-ask.js +1 -1
  19. package/scripts/flow-assumption-detector.js +1 -1
  20. package/scripts/flow-audit-gates.js +38 -12
  21. package/scripts/flow-audit.js +4 -4
  22. package/scripts/flow-auto-context.js +3 -3
  23. package/scripts/flow-background.js +1 -1
  24. package/scripts/flow-best-of-n.js +7 -7
  25. package/scripts/flow-bridge.js +3 -3
  26. package/scripts/flow-bug.js +2 -2
  27. package/scripts/flow-bulk-loop.js +7 -7
  28. package/scripts/flow-cascade-completion.js +2 -2
  29. package/scripts/flow-cascade.js +1 -1
  30. package/scripts/flow-checkpoint.js +2 -2
  31. package/scripts/flow-clarifying-questions.js +2 -2
  32. package/scripts/flow-cli.js +2 -2
  33. package/scripts/flow-code-intelligence.js +4 -4
  34. package/scripts/flow-community-sync.js +6 -6
  35. package/scripts/flow-community.js +1 -1
  36. package/scripts/flow-completion-truth-gate.js +161 -5
  37. package/scripts/flow-complexity.js +1 -1
  38. package/scripts/flow-config-defaults.js +9 -1
  39. package/scripts/flow-config-interactive.js +2 -2
  40. package/scripts/flow-config-loader.js +1 -1
  41. package/scripts/flow-config-migrate.js +5 -6
  42. package/scripts/flow-consistency-check.js +5 -5
  43. package/scripts/flow-context-compact/expander.js +1 -1
  44. package/scripts/flow-context-compact/index.js +2 -2
  45. package/scripts/flow-context-compact/section-extractor.js +3 -3
  46. package/scripts/flow-context-compact/summary-tree.js +1 -1
  47. package/scripts/flow-context-estimator.js +1 -1
  48. package/scripts/flow-context-gatherer.js +6 -6
  49. package/scripts/flow-context-generator.js +6 -6
  50. package/scripts/flow-context-init.js +2 -2
  51. package/scripts/flow-context-manager.js +1 -1
  52. package/scripts/flow-context-manifest.js +1 -1
  53. package/scripts/flow-context-monitor.js +5 -5
  54. package/scripts/flow-context-orchestrator.js +2 -2
  55. package/scripts/flow-context-scoring.js +4 -4
  56. package/scripts/flow-contract-scan.js +1 -1
  57. package/scripts/flow-correct.js +3 -3
  58. package/scripts/flow-damage-control.js +2 -2
  59. package/scripts/flow-deploy-gate.js +2 -2
  60. package/scripts/flow-deploy-history.js +1 -1
  61. package/scripts/flow-diff.js +3 -3
  62. package/scripts/flow-done-gates.js +1 -1
  63. package/scripts/flow-done.js +7 -7
  64. package/scripts/flow-durable-session.js +1 -1
  65. package/scripts/flow-entropy-monitor.js +3 -3
  66. package/scripts/flow-epics.js +5 -5
  67. package/scripts/flow-error-recovery.js +4 -4
  68. package/scripts/flow-eval-judge.js +5 -5
  69. package/scripts/flow-eval.js +7 -7
  70. package/scripts/flow-export-scanner.js +5 -5
  71. package/scripts/flow-extraction-review.js +1 -1
  72. package/scripts/flow-failure-learning.js +9 -9
  73. package/scripts/flow-feature.js +5 -5
  74. package/scripts/flow-figma-confirm.js +1 -1
  75. package/scripts/flow-figma-extract.js +2 -2
  76. package/scripts/flow-figma-index.js +2 -2
  77. package/scripts/flow-figma-match.js +1 -1
  78. package/scripts/flow-figma-mcp-server.js +3 -3
  79. package/scripts/flow-figma-orchestrator.js +1 -1
  80. package/scripts/flow-figma-registry.js +2 -2
  81. package/scripts/flow-function-index.js +2 -2
  82. package/scripts/flow-gate-confidence.js +2 -2
  83. package/scripts/flow-gate-telemetry.js +1 -1
  84. package/scripts/flow-gitignore.js +1 -1
  85. package/scripts/flow-guided-edit.js +3 -3
  86. package/scripts/flow-health.js +95 -8
  87. package/scripts/flow-hooks.js +3 -3
  88. package/scripts/flow-hybrid-detect.js +2 -2
  89. package/scripts/flow-hybrid-interactive.js +1 -1
  90. package/scripts/flow-hybrid-test.js +1 -1
  91. package/scripts/flow-hypothesis-generator.js +4 -4
  92. package/scripts/flow-instruction-richness.js +11 -11
  93. package/scripts/flow-intent-bootstrap.js +1 -1
  94. package/scripts/flow-intent-framing.js +1 -1
  95. package/scripts/flow-item-link.js +2 -2
  96. package/scripts/flow-knowledge-router.js +7 -7
  97. package/scripts/flow-knowledge-sync.js +3 -3
  98. package/scripts/flow-learning-orchestrator.js +1 -1
  99. package/scripts/flow-links.js +2 -2
  100. package/scripts/flow-log-manager.js +2 -2
  101. package/scripts/flow-logic-adversary.js +5 -4
  102. package/scripts/flow-long-input-chunking.js +1 -1
  103. package/scripts/flow-long-input-cli.js +3 -3
  104. package/scripts/flow-long-input.js +18 -18
  105. package/scripts/flow-loop-retry-learning.js +2 -2
  106. package/scripts/flow-lsp.js +4 -4
  107. package/scripts/flow-mcp-docs.js +1 -1
  108. package/scripts/flow-memory-blocks.js +5 -5
  109. package/scripts/flow-memory-compactor.js +3 -3
  110. package/scripts/flow-memory-db.js +4 -4
  111. package/scripts/flow-memory-sync.js +3 -3
  112. package/scripts/flow-metrics.js +2 -2
  113. package/scripts/flow-migrate-igr.js +2 -2
  114. package/scripts/flow-migrate.js +2 -2
  115. package/scripts/flow-model-adapter.js +4 -4
  116. package/scripts/flow-model-caller.js +8 -8
  117. package/scripts/flow-model-config.js +5 -5
  118. package/scripts/flow-model-profile.js +7 -7
  119. package/scripts/flow-model-router.js +5 -5
  120. package/scripts/flow-model-types.js +3 -3
  121. package/scripts/flow-models.js +8 -8
  122. package/scripts/flow-morning.js +1 -1
  123. package/scripts/flow-multi-approach.js +1 -1
  124. package/scripts/flow-orchestrate-context.js +2 -2
  125. package/scripts/flow-orchestrate-llm.js +4 -4
  126. package/scripts/flow-orchestrate-rollback.js +1 -1
  127. package/scripts/flow-orchestrate-state.js +6 -6
  128. package/scripts/flow-orchestrate-templates.js +1 -1
  129. package/scripts/flow-orchestrate-validation.js +2 -2
  130. package/scripts/flow-orchestrate-validator.js +1 -1
  131. package/scripts/flow-orchestrate.js +25 -25
  132. package/scripts/flow-parallel.js +1 -1
  133. package/scripts/flow-pattern-enforcer.js +7 -7
  134. package/scripts/flow-pattern-extractor.js +3 -3
  135. package/scripts/flow-peer-review.js +8 -8
  136. package/scripts/flow-pending.js +1 -1
  137. package/scripts/flow-permissions.js +1 -1
  138. package/scripts/flow-phased-task.js +1 -1
  139. package/scripts/flow-plan.js +1 -1
  140. package/scripts/flow-prd-manager.js +2 -2
  141. package/scripts/flow-product-scanner.js +2 -2
  142. package/scripts/flow-progress-tracker.js +2 -2
  143. package/scripts/flow-progress.js +1 -1
  144. package/scripts/flow-project-analyzer.js +3 -3
  145. package/scripts/flow-prompt-capture.js +2 -2
  146. package/scripts/flow-prompt-composer.js +3 -3
  147. package/scripts/flow-prompt-template.js +4 -4
  148. package/scripts/flow-providers.js +31 -23
  149. package/scripts/flow-queue.js +1 -1
  150. package/scripts/flow-registry-manager.js +4 -4
  151. package/scripts/flow-regression.js +1 -1
  152. package/scripts/flow-response-parser.js +1 -1
  153. package/scripts/flow-resume.js +1 -1
  154. package/scripts/flow-review-passes/index.js +2 -2
  155. package/scripts/flow-review-passes/integration.js +3 -3
  156. package/scripts/flow-review-passes/logic.js +3 -3
  157. package/scripts/flow-review-passes/security.js +2 -2
  158. package/scripts/flow-review-passes/structure.js +1 -1
  159. package/scripts/flow-review.js +11 -11
  160. package/scripts/flow-revision-tracker.js +2 -2
  161. package/scripts/flow-roadmap.js +2 -2
  162. package/scripts/flow-run-trace.js +1 -1
  163. package/scripts/flow-safety.js +3 -3
  164. package/scripts/flow-scanner-base.js +1 -1
  165. package/scripts/flow-scenario-engine.js +7 -7
  166. package/scripts/flow-schema-drift.js +4 -3
  167. package/scripts/flow-section-index.js +2 -2
  168. package/scripts/flow-section-resolver.js +4 -4
  169. package/scripts/flow-semantic-match.js +3 -3
  170. package/scripts/flow-session-end.js +56 -0
  171. package/scripts/flow-session-learning.js +2 -2
  172. package/scripts/flow-setup-hooks.js +1 -1
  173. package/scripts/flow-skill-create.js +3 -3
  174. package/scripts/flow-skill-freshness.js +2 -2
  175. package/scripts/flow-skill-generator.js +6 -6
  176. package/scripts/flow-skill-learn.js +7 -7
  177. package/scripts/flow-skill-matcher.js +2 -2
  178. package/scripts/flow-solution-optimizer.js +1 -1
  179. package/scripts/flow-spec-generator.js +5 -5
  180. package/scripts/flow-spec-verifier.js +2 -2
  181. package/scripts/flow-stack-wizard.js +6 -6
  182. package/scripts/flow-standards-checker.js +8 -8
  183. package/scripts/flow-standards-gate.js +4 -4
  184. package/scripts/flow-standards-learner.js +2 -2
  185. package/scripts/flow-start.js +9 -9
  186. package/scripts/flow-stats-collector.js +2 -2
  187. package/scripts/flow-status.js +1 -1
  188. package/scripts/flow-step-changelog.js +3 -3
  189. package/scripts/flow-step-complexity.js +1 -1
  190. package/scripts/flow-step-coverage.js +3 -3
  191. package/scripts/flow-step-knowledge.js +2 -2
  192. package/scripts/flow-step-pr-tests.js +2 -2
  193. package/scripts/flow-step-regression.js +3 -3
  194. package/scripts/flow-step-review.js +5 -5
  195. package/scripts/flow-story.js +2 -2
  196. package/scripts/flow-strict-adherence.js +2 -2
  197. package/scripts/flow-structure-sensor.js +283 -0
  198. package/scripts/flow-sync-anonymizer.js +3 -3
  199. package/scripts/flow-task-checkpoint.js +2 -2
  200. package/scripts/flow-task-classifier.js +2 -2
  201. package/scripts/flow-task-completion-summary.js +1 -1
  202. package/scripts/flow-task-enforcer.js +5 -5
  203. package/scripts/flow-tech-debt.js +3 -3
  204. package/scripts/flow-template-extractor.js +3 -3
  205. package/scripts/flow-templates.js +1 -1
  206. package/scripts/flow-test-api.js +12 -12
  207. package/scripts/flow-test-discovery.js +9 -9
  208. package/scripts/flow-test-generate.js +5 -5
  209. package/scripts/flow-test-integrity.js +3 -3
  210. package/scripts/flow-test-ui.js +8 -8
  211. package/scripts/flow-testing-deps.js +4 -4
  212. package/scripts/flow-tiered-learning.js +3 -3
  213. package/scripts/flow-todowrite-sync.js +1 -1
  214. package/scripts/flow-trap-zone.js +1 -1
  215. package/scripts/flow-verification-profile.js +9 -9
  216. package/scripts/flow-verify.js +2 -2
  217. package/scripts/flow-version-check.js +2 -2
  218. package/scripts/flow-webmcp-generator.js +3 -3
  219. package/scripts/flow-wiring-verifier.js +13 -13
  220. package/scripts/flow-workflow-steps.js +3 -3
  221. package/scripts/flow-workflow.js +1 -1
  222. package/scripts/flow-worktree.js +1 -1
  223. package/scripts/hooks/adapters/base-adapter.js +2 -2
  224. package/scripts/hooks/core/commit-log-gate.js +2 -2
  225. package/scripts/hooks/core/component-check.js +3 -3
  226. package/scripts/hooks/core/config-change.js +1 -1
  227. package/scripts/hooks/core/deploy-gate.js +2 -1
  228. package/scripts/hooks/core/git-safety-gate.js +1 -1
  229. package/scripts/hooks/core/instructions-loaded.js +1 -1
  230. package/scripts/hooks/core/loop-check.js +1 -1
  231. package/scripts/hooks/core/manager-boundary-gate.js +3 -2
  232. package/scripts/hooks/core/observation-capture.js +6 -6
  233. package/scripts/hooks/core/phase-gate.js +4 -4
  234. package/scripts/hooks/core/pre-compact.js +1 -1
  235. package/scripts/hooks/core/routing-gate.js +1 -1
  236. package/scripts/hooks/core/session-context.js +1 -1
  237. package/scripts/hooks/core/session-end.js +3 -3
  238. package/scripts/hooks/core/session-history.js +1 -1
  239. package/scripts/hooks/core/setup-handler.js +1 -1
  240. package/scripts/hooks/core/task-boundary-reset.js +2 -4
  241. package/scripts/hooks/core/task-completed.js +13 -7
  242. package/scripts/hooks/core/task-created.js +1 -1
  243. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  244. package/scripts/hooks/entry/claude-code/permission-denied.js +4 -2
  245. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +1 -1
  246. package/scripts/hooks/git/post-commit.js +1 -1
  247. package/scripts/postinstall.js +7 -7
  248. package/scripts/preuninstall.js +5 -5
  249. package/scripts/registries/component-registry.js +2 -2
  250. package/scripts/registries/contract-scanner.js +11 -11
  251. package/scripts/registries/schema-registry.js +5 -5
  252. package/scripts/registries/service-registry.js +9 -9
@@ -79,6 +79,79 @@ Pre-finalization checks:
79
79
 
80
80
  If checks fail, display warnings and suggest fixes before proceeding.
81
81
 
82
+ ### Step 2.5: Merge-Plan Gate (when `mergePlan.threshold` exceeded)
83
+
84
+ **Activates when** the branch carries more commits than `config.finalization.mergePlan.threshold` (default **5**) OR the diff is flagged cross-repo by the workspace manifest. The gate writes — and requires the AI to fill in — `.workflow/scratch/merge-plan.md`. The gate exists because the "1-2h mostly mechanical" audit that predicted a 27-conflict merge (wogi-hub, 2026-04-16) counted commits-per-file without reading diff content; the fix is to force per-commit action assignment in a file.
85
+
86
+ **Mechanical invariants (gate blocks on violation):**
87
+
88
+ 1. For every commit in `git log <base>..<branch>`, the plan MUST contain one line starting with the short SHA and a tagged action. No commits in an "unaccounted" bucket.
89
+ 2. Allowed actions: `port | adapt | skip-style | superseded | skip-with-reason`.
90
+ 3. `git log <base>..<branch> | wc -l` MUST equal the count of SHA-prefixed lines in the plan. Mismatch → hard-stop until reconciled.
91
+ 4. `skip-with-reason` entries MUST include a one-line reason after a `—` (em dash).
92
+
93
+ **Structural-change detection (before plan write):**
94
+
95
+ Run the structure-change sensor (`scripts/flow-structure-sensor.js`) on the diff. If ≥ `config.finalization.mergePlan.restructureThreshold` (default **20%**) of changed files match one of these restructure patterns, display a STRUCTURAL CHANGE warning at the top of the plan and bias the default action for affected commits to `adapt`:
96
+
97
+ | Pattern | Example | Meaning |
98
+ |---------|---------|---------|
99
+ | `X.tsx` deleted + `X/X.tsx` added | `Card.tsx` → `Card/Card.tsx` | folder-per-component |
100
+ | `X.ts` deleted + `<dir>/X.ts` added at deeper depth | `utils.ts` → `utils/date.ts` | split into submodule |
101
+ | `X` deleted + `X.<ext>` added elsewhere | `types.ts` → `types/index.ts` | barrel introduction |
102
+
103
+ **Procedure:**
104
+
105
+ ```bash
106
+ # 1. Gather commit list
107
+ git log --pretty='%h %s' <base>..<branch> > .workflow/scratch/.merge-plan-commits.txt
108
+
109
+ # 2. Run structure sensor
110
+ node node_modules/wogiflow/scripts/flow-structure-sensor.js <base>..<branch> > .workflow/scratch/.merge-plan-sensor.json
111
+
112
+ # 3. Write .workflow/scratch/merge-plan.md using the template below,
113
+ # one SHA-prefixed line per commit. Read the FULL diff of each commit
114
+ # (not just the subject line) before assigning an action — that is
115
+ # the whole point of this gate.
116
+
117
+ # 4. Verify the mechanical invariant
118
+ test "$(git log --oneline <base>..<branch> | wc -l)" -eq \
119
+ "$(grep -cE '^[a-f0-9]{7,}\s' .workflow/scratch/merge-plan.md)"
120
+ ```
121
+
122
+ **Plan template** (write verbatim, then fill each row by reading the full diff):
123
+
124
+ ```markdown
125
+ # Merge plan: <branch> → <base>
126
+
127
+ Commits: N (from `git log <base>..<branch>`)
128
+ Structural-change sensor: <WARN|clean> — N/M files match restructure patterns
129
+ Cross-repo impact: <list workspace members affected, or "single-repo">
130
+
131
+ ## Per-commit actions
132
+
133
+ | SHA | Subject | Action | Notes |
134
+ |-----|---------|--------|-------|
135
+ | abc1234 | feat: add login form | port | — |
136
+ | def5678 | refactor: split Card.tsx into Card/ | adapt | folder-per-component restructure |
137
+ | ghi9012 | chore: lint fixes | skip-style | — |
138
+ | jkl3456 | revert: roll back header | skip-with-reason | superseded by mno7890 |
139
+
140
+ ## Structural risks
141
+
142
+ <leave empty if sensor is clean; otherwise list each pattern hit>
143
+
144
+ ## Content risks
145
+
146
+ <list overlaps in shared types, DTOs, API surface that need manual review>
147
+ ```
148
+
149
+ **When the plan is complete**, the gate verifies the commit-count invariant (step 4) and proceeds to Step 3 (options). If invariant fails, the command stops with the reconciliation command printed.
150
+
151
+ **Skip conditions:**
152
+ - `config.finalization.mergePlan.enabled: false` — opt-out for users who don't want the gate
153
+ - Branch commits ≤ threshold AND single-repo — small merges don't need a plan
154
+
82
155
  ### Step 3: Present Options
83
156
 
84
157
  ```
@@ -241,6 +314,12 @@ When `/wogi-start` completes a task that was executed in a worktree, the finaliz
241
314
  "includeTaskSpec": true,
242
315
  "includeCommitList": true,
243
316
  "includeFileSummary": true
317
+ },
318
+ "mergePlan": {
319
+ "enabled": true,
320
+ "threshold": 5,
321
+ "restructureThreshold": 0.20,
322
+ "alwaysForCrossRepo": true
244
323
  }
245
324
  }
246
325
  }
@@ -254,6 +333,10 @@ When `/wogi-start` completes a task that was executed in a worktree, the finaliz
254
333
  | `requirePRForTypes` | `[]` | Task types that must create a PR (useful for teams) |
255
334
  | `squashOnMerge` | `true` | Squash commits when merging |
256
335
  | `prTemplate` | `{...}` | What to include in auto-generated PR body |
336
+ | `mergePlan.enabled` | `true` | Require per-commit merge plan on large or cross-repo merges |
337
+ | `mergePlan.threshold` | `5` | Commit count above which the merge plan is required |
338
+ | `mergePlan.restructureThreshold` | `0.20` | % of changed files matching restructure patterns that triggers a structural-change warning |
339
+ | `mergePlan.alwaysForCrossRepo` | `true` | Require the plan on any cross-repo merge regardless of commit count |
257
340
 
258
341
  ## Examples
259
342
 
@@ -75,7 +75,7 @@ Don't confuse them. `agents/security.md` (persona) is different from `.workflow/
75
75
 
76
76
  When modifying flow-*.js scripts:
77
77
  - Run `node --check scripts/<file>.js` after edits
78
- - WogiFlow has no test suite - syntax checking is the safety net
78
+ - Run `npm test` — WogiFlow has a native-Node test suite (50+ files under `tests/`, 1800+ assertions) covering hooks, flow-io, security, session state, workspace gates, and more
79
79
  - Check for circular dependencies when moving shared functions
80
80
 
81
81
  ## 8. Feature Refactoring Cleanup
@@ -170,6 +170,6 @@
170
170
  },
171
171
  "_comment_dynamicHooks": "TaskCreated (2.1.84+) and PermissionDenied (2.1.88+) are added by postinstall.js when the CC version supports them. They must NOT be committed statically — CC rejects the entire settings file if it encounters an unknown hook event name.",
172
172
  "_wogiFlowManaged": true,
173
- "_wogiFlowVersion": "2.17.5",
173
+ "_wogiFlowVersion": "2.22.0",
174
174
  "_comment": "Shared WogiFlow hook configuration. Committed to repo for team use. User-specific overrides go in settings.local.json."
175
175
  }
@@ -414,7 +414,7 @@ async function login(args) {
414
414
  console.error(`\n\nServer error (${tokenResponse.statusCode}). Retrying...`);
415
415
  process.stdout.write('x');
416
416
  }
417
- } catch (err) {
417
+ } catch (_err) {
418
418
  process.stdout.write('x');
419
419
  }
420
420
  }
package/lib/installer.js CHANGED
@@ -194,7 +194,7 @@ function detectProjectScripts(projectRoot) {
194
194
  let pkgData;
195
195
  try {
196
196
  pkgData = safeReadJson(packageJsonPath);
197
- } catch (err) {
197
+ } catch (_err) {
198
198
  return detected;
199
199
  }
200
200
 
@@ -989,7 +989,7 @@ async function init(args) {
989
989
  try {
990
990
  const analyzer = require('../scripts/flow-project-analyzer');
991
991
  detection = analyzer.detectProjectType(projectRoot);
992
- } catch (err) {
992
+ } catch (_err) {
993
993
  // Fallback: use basic projectType from installer's own detection
994
994
  detection = {
995
995
  projectType: config.projectType || 'unknown',
@@ -1030,7 +1030,7 @@ async function init(args) {
1030
1030
  // Non-fatal — profile can be generated later via /wogi-test
1031
1031
  }
1032
1032
  }
1033
- } catch (err) {
1033
+ } catch (_err) {
1034
1034
  // Non-fatal — flow-verification-profile may not exist yet during initial install
1035
1035
  }
1036
1036
 
@@ -1083,7 +1083,7 @@ function updateManifestAfterInit(cliDir) {
1083
1083
  if (!manifest || !Array.isArray(manifest.files)) {
1084
1084
  manifest = { version: 1, files: [], directories: ['.workflow'] };
1085
1085
  }
1086
- } catch (err) {
1086
+ } catch (_err) {
1087
1087
  // No manifest yet — create a fresh one
1088
1088
  manifest = { version: 1, files: [], directories: ['.workflow'] };
1089
1089
  }
@@ -1129,7 +1129,7 @@ function walkDirForManifest(dir, baseDir, fileSet) {
1129
1129
  fileSet.add(path.relative(baseDir, fullPath));
1130
1130
  }
1131
1131
  }
1132
- } catch (err) {
1132
+ } catch (_err) {
1133
1133
  // Non-critical — skip unreadable dirs
1134
1134
  }
1135
1135
  }
@@ -128,7 +128,7 @@ async function getLatestVersion(channel) {
128
128
  // Get version for the channel's npm tag
129
129
  const distTags = pkg['dist-tags'] || {};
130
130
  return distTags[channelConfig.npmTag] || distTags.latest || pkg.version;
131
- } catch (err) {
131
+ } catch (_err) {
132
132
  return null;
133
133
  }
134
134
  }
@@ -154,7 +154,7 @@ async function fetchSkillIndex(projectRoot) {
154
154
  throw new Error('Invalid index data');
155
155
  }
156
156
  return parsed;
157
- } catch (err) {
157
+ } catch (_err) {
158
158
  // Return mock index for development/offline
159
159
  return {
160
160
  version: '1.0',
@@ -201,7 +201,7 @@ async function fetchSkillManifest(skillName, projectRoot) {
201
201
  throw new Error('Invalid manifest data');
202
202
  }
203
203
  return parsed;
204
- } catch (err) {
204
+ } catch (_err) {
205
205
  throw new Error(`Skill '${skillName}' not found in registry`);
206
206
  }
207
207
  }
@@ -213,7 +213,7 @@ async function fetchSkillManifest(skillName, projectRoot) {
213
213
  * @param {string} projectRoot - Project root directory
214
214
  * @returns {Promise<Object>} Downloaded files
215
215
  */
216
- async function downloadSkillFiles(skillName, manifest, projectRoot) {
216
+ async function downloadSkillFiles(skillName, manifest, _projectRoot) {
217
217
  const files = {};
218
218
  const baseUrl = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/skills/${skillName}`;
219
219
 
@@ -46,7 +46,7 @@ const EVENT_TYPES = [
46
46
 
47
47
  const EVENTS_FILE = 'events.json';
48
48
  const MAX_EVENTS = 500; // Keep last 500 events
49
- const EVENT_ID_PATTERN = /^evt-[a-f0-9]{8}$/;
49
+ const _EVENT_ID_PATTERN = /^evt-[a-f0-9]{8}$/;
50
50
 
51
51
  // ============================================================
52
52
  // Event Creation
@@ -470,7 +470,7 @@ function broadcastPostChange(workspaceRoot, fromRepo, context, options = {}) {
470
470
  * @param {Object} taskMeta
471
471
  * @returns {{ passed: boolean, message: string, severity: string }}
472
472
  */
473
- function gateDeploymentReadiness(workspaceRoot, context, taskMeta) {
473
+ function gateDeploymentReadiness(workspaceRoot, _context, taskMeta) {
474
474
  const { execFileSync } = require('node:child_process');
475
475
 
476
476
  try {
@@ -668,7 +668,7 @@ function gatePeerNotification(workspaceRoot, context, taskMeta) {
668
668
  * Gate: cascadeVerification
669
669
  * For library repos, verify all consumers were notified.
670
670
  */
671
- function gateCascadeVerification(workspaceRoot, context, taskMeta) {
671
+ function gateCascadeVerification(workspaceRoot, context, _taskMeta) {
672
672
  const gate = WORKSPACE_GATES.find(g => g.name === 'cascadeVerification');
673
673
 
674
674
  if (!context.currentMember || context.currentMember.role !== 'library') {
@@ -694,7 +694,7 @@ function analyzeReviewForCrossRepoImpact(workspaceRoot, manifest, changedFiles,
694
694
  result.endpointChanges = changedApiFiles;
695
695
 
696
696
  // Check if contract was also updated
697
- const contractsDir = path.join(workspaceRoot, '.workspace', 'contracts');
697
+ const _contractsDir = path.join(workspaceRoot, '.workspace', 'contracts');
698
698
  const contractFiles = changedFiles.filter(f => f.includes('.workspace/contracts'));
699
699
 
700
700
  if (contractFiles.length === 0 && changedApiFiles.length > 0) {
@@ -344,7 +344,7 @@ function generateParallelInvestigation(workspaceRoot, bugDescription, manifest)
344
344
  const investigators = [];
345
345
 
346
346
  for (const [name, member] of Object.entries(manifest.members)) {
347
- const repoPath = path.resolve(workspaceRoot, member.path);
347
+ const _repoPath = path.resolve(workspaceRoot, member.path);
348
348
 
349
349
  investigators.push({
350
350
  repoName: name,
package/lib/workspace.js CHANGED
@@ -17,6 +17,7 @@
17
17
 
18
18
  const fs = require('node:fs');
19
19
  const path = require('node:path');
20
+ const { safeJsonParse } = require('../scripts/flow-io');
20
21
 
21
22
  /**
22
23
  * wf-f747f993 — resolve the claude-spawning command for workspace sessions.
@@ -682,7 +683,7 @@ ${Object.entries(config.channels?.members || {}).map(([name, ch]) => `- **${name
682
683
 
683
684
  **Check if workers are running:**
684
685
  \`\`\`bash
685
- ${Object.entries(config.channels?.members || {}).map(([name, ch]) => `curl -s http://localhost:${ch.port}/health`).join('\n')}
686
+ ${Object.entries(config.channels?.members || {}).map(([_name, ch]) => `curl -s http://localhost:${ch.port}/health`).join('\n')}
686
687
  \`\`\`
687
688
 
688
689
  If a worker is down, tell the user: "Start the ${'{'}repo{'}'} worker: \`cd ${'{'}repo{'}'}/ && flow workspace start\`"
@@ -705,7 +706,7 @@ If a worker is down, tell the user: "Start the ${'{'}repo{'}'} worker: \`cd ${'{
705
706
  **Bug investigation — dispatch to all workers:**
706
707
  \`\`\`bash
707
708
  # Send investigation request to all workers in parallel
708
- ${Object.entries(config.channels?.members || {}).map(([name, ch]) =>
709
+ ${Object.entries(config.channels?.members || {}).map(([_name, ch]) =>
709
710
  `curl -s -X POST http://localhost:${ch.port} -d "Investigate: <BUG_DESCRIPTION>. Check recent changes, error logs, relevant code. Report back via workspace message."`
710
711
  ).join('\n')}
711
712
  \`\`\`
@@ -1110,7 +1111,7 @@ function generateMemberMcpConfigs(workspaceRoot, config) {
1110
1111
  let existingConfig = {};
1111
1112
  try {
1112
1113
  if (fs.existsSync(mcpJsonPath)) {
1113
- existingConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
1114
+ existingConfig = safeJsonParse(mcpJsonPath, {});
1114
1115
  }
1115
1116
  } catch (_err) {
1116
1117
  // Ignore malformed existing config
@@ -1292,7 +1293,7 @@ For everything else — just do the work and report results.
1292
1293
  let existingManagerMcp = {};
1293
1294
  try {
1294
1295
  if (fs.existsSync(managerMcpPath)) {
1295
- existingManagerMcp = JSON.parse(fs.readFileSync(managerMcpPath, 'utf-8'));
1296
+ existingManagerMcp = safeJsonParse(managerMcpPath, {});
1296
1297
  }
1297
1298
  } catch (_err) { /* ignore */ }
1298
1299
 
@@ -1315,7 +1316,7 @@ For everything else — just do the work and report results.
1315
1316
  * Initialize a Wogi Workspace
1316
1317
  * @param {string[]} args — CLI arguments
1317
1318
  */
1318
- async function initWorkspace(args) {
1319
+ async function initWorkspace(_args) {
1319
1320
  let workspaceRoot = process.cwd();
1320
1321
 
1321
1322
  // Walk up to find existing workspace root (same as startWorkerSession)
@@ -1332,11 +1333,10 @@ async function initWorkspace(args) {
1332
1333
  // If we're inside an existing workspace (found in parent or CWD)
1333
1334
  if (existingWorkspaceRoot) {
1334
1335
  // Check if channels need to be enabled (upgrade path)
1335
- let existingConfig;
1336
- try {
1337
- existingConfig = JSON.parse(fs.readFileSync(path.join(existingWorkspaceRoot, WORKSPACE_CONFIG_FILE), 'utf-8'));
1338
- } catch (err) {
1339
- console.error(`Error reading workspace config: ${err.message}`);
1336
+ const _workspaceConfigPath = path.join(existingWorkspaceRoot, WORKSPACE_CONFIG_FILE);
1337
+ const existingConfig = safeJsonParse(_workspaceConfigPath, null);
1338
+ if (!existingConfig) {
1339
+ console.error(`Error reading workspace config at ${_workspaceConfigPath}`);
1340
1340
  process.exit(1);
1341
1341
  }
1342
1342
 
@@ -1373,7 +1373,7 @@ async function initWorkspace(args) {
1373
1373
  const manifestPath = path.join(existingWorkspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
1374
1374
  try {
1375
1375
  if (fs.existsSync(manifestPath)) {
1376
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
1376
+ const manifest = safeJsonParse(manifestPath, {});
1377
1377
  const claudeMd = generateWorkspaceClaudeMd(existingConfig, manifest);
1378
1378
  fs.writeFileSync(path.join(existingWorkspaceRoot, 'CLAUDE.md'), claudeMd);
1379
1379
  console.log(' ✓ CLAUDE.md (regenerated with channel dispatch instructions)');
@@ -1614,11 +1614,10 @@ function startWorkerSession(cwd) {
1614
1614
  }
1615
1615
 
1616
1616
  // Read workspace config
1617
- let config;
1618
- try {
1619
- config = JSON.parse(fs.readFileSync(path.join(workspaceRoot, WORKSPACE_CONFIG_FILE), 'utf-8'));
1620
- } catch (err) {
1621
- console.error(`Error reading workspace config: ${err.message}`);
1617
+ const _cfgPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
1618
+ const config = safeJsonParse(_cfgPath, null);
1619
+ if (!config) {
1620
+ console.error(`Error reading workspace config at ${_cfgPath}`);
1622
1621
  process.exit(1);
1623
1622
  }
1624
1623
 
@@ -1697,7 +1696,7 @@ function startWorkerSession(cwd) {
1697
1696
  let mcpConfigValid = false;
1698
1697
  try {
1699
1698
  if (fs.existsSync(mcpJsonPath)) {
1700
- const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
1699
+ const mcpConfig = safeJsonParse(mcpJsonPath, {});
1701
1700
  mcpConfigValid = !!mcpConfig?.mcpServers?.['wogi-workspace-channel'];
1702
1701
  }
1703
1702
  } catch (_err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.21.0",
3
+ "version": "2.22.0",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "flow": "./scripts/flow",
13
- "test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
13
+ "test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
14
14
  "test:syntax": "find scripts/ lib/ -name '*.js' -not -path '*/node_modules/*' -exec node --check {} +",
15
15
  "lint": "eslint scripts/ lib/ tests/",
16
16
  "lint:ci": "eslint scripts/ lib/ tests/ --max-warnings 0",
@@ -29,7 +29,7 @@
29
29
 
30
30
  const fs = require('node:fs');
31
31
  const path = require('node:path');
32
- const { PATHS, getTodayDate } = require('./flow-utils');
32
+ const { PATHS } = require('./flow-utils');
33
33
 
34
34
  class BaseWorkflowStep {
35
35
  /**
@@ -77,7 +77,7 @@ class BaseWorkflowStep {
77
77
  * @param {object} options - Remaining options (stepConfig, mode, taskType, etc.)
78
78
  * @returns {Promise<{passed: boolean, message: string, details?: any}>}
79
79
  */
80
- async execute(files, options) {
80
+ async execute(_files, options) {
81
81
  throw new Error(`${this.name}: execute() must be overridden`);
82
82
  }
83
83
 
@@ -15,7 +15,7 @@
15
15
  const fs = require('node:fs');
16
16
  const path = require('node:path');
17
17
  const { execFileSync } = require('node:child_process');
18
- const { getProjectRoot, colors, PATHS, getTodayDate } = require('./flow-utils');
18
+ const { colors, PATHS, getTodayDate } = require('./flow-utils');
19
19
  const { error: errorMsg } = require('./flow-output');
20
20
  const { readJson } = require('./flow-io');
21
21
  const { storeSingleLearning, getAdapterPath } = require('./flow-model-adapter');
@@ -24,7 +24,7 @@ const {
24
24
  detectCategory,
25
25
  shouldEscalate: checkShouldEscalate
26
26
  } = require('./flow-failure-categories');
27
- const { validateRepoFormat, safeGitCommand, sanitizeCommitMessage } = require('./flow-security');
27
+ const { validateRepoFormat, safeGitCommand } = require('./flow-security');
28
28
 
29
29
  const LEARNING_LOG_PATH = path.join(PATHS.state, 'adaptive-learning.json');
30
30
  const STRATEGY_STATS_PATH = path.join(PATHS.state, 'strategy-effectiveness.json');
@@ -66,7 +66,7 @@ const ERROR_CATEGORIES = {
66
66
  * @param {object} context - Task context
67
67
  * @returns {object} Failure analysis
68
68
  */
69
- function analyzeFailure(error, output, context = {}) {
69
+ function analyzeFailure(error, output, _context = {}) {
70
70
  const errorStr = String(error);
71
71
  const outputStr = String(output || '');
72
72
 
@@ -393,7 +393,7 @@ function generateGuidanceFromFailures(category, details) {
393
393
  switch (category) {
394
394
  case 'IMPORT_ERROR': {
395
395
  const modules = details.map(d => d.moduleName).filter(Boolean);
396
- const exports = details.map(d => d.exportName).filter(Boolean);
396
+ const _exports = details.map(d => d.exportName).filter(Boolean);
397
397
  return {
398
398
  do: 'Copy import paths exactly from the "Available Imports" section',
399
399
  dont: modules.length > 0
@@ -530,7 +530,7 @@ function getBestStrategy(modelName, category) {
530
530
  /**
531
531
  * Check if a similar learning already exists
532
532
  */
533
- function isDuplicateLearning(modelName, category, details) {
533
+ function isDuplicateLearning(modelName, category, _details) {
534
534
  const adapterPath = getAdapterPath(modelName);
535
535
  if (!fs.existsSync(adapterPath)) return false;
536
536
 
@@ -800,7 +800,7 @@ function checkGitHubCLI() {
800
800
  try {
801
801
  execFileSync('gh', ['auth', 'status'], { stdio: 'pipe' });
802
802
  return { available: true };
803
- } catch (err) {
803
+ } catch (_err) {
804
804
  return { available: false, error: 'gh CLI not authenticated. Run: gh auth login' };
805
805
  }
806
806
  }
@@ -15,11 +15,11 @@
15
15
 
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
- const { getProjectRoot, getConfig, color, success, warn, error, safeJsonParse, PATHS } = require('./flow-utils');
18
+ const { color, success, warn, error, safeJsonParse, PATHS } = require('./flow-utils');
19
19
  const {
20
20
  findSimilarItems,
21
21
  generateAIDecisionPrompt,
22
- generateContextBlock,
22
+ generateContextBlock: _generateContextBlock,
23
23
  getMatchConfig
24
24
  } = require('./flow-semantic-match');
25
25
  const { BaseScanner, PROJECT_ROOT } = require('./flow-scanner-base');
@@ -42,7 +42,7 @@ const path = require('node:path');
42
42
  const { PATHS } = require('./flow-paths');
43
43
  const { fileExists, ensureDir, readFile } = require('./flow-io');
44
44
  const { getConfig } = require('./flow-config-loader');
45
- const { color, info, warn, error } = require('./flow-output');
45
+ const { color, warn, error } = require('./flow-output');
46
46
 
47
47
  const gateTelemetry = require('./flow-gate-telemetry');
48
48
 
@@ -69,7 +69,7 @@ function clearPendingQuestion() {
69
69
  try {
70
70
  fs.unlinkSync(p);
71
71
  return { cleared: true, wasPresent: true };
72
- } catch (err) {
72
+ } catch (_err) {
73
73
  return { cleared: false, wasPresent: true };
74
74
  }
75
75
  }
@@ -159,7 +159,7 @@ function detectAssumptions(params) {
159
159
  /**
160
160
  * Detect technical assumptions (framework, patterns, etc.)
161
161
  */
162
- function detectTechnicalAssumptions(title, description, context) {
162
+ function detectTechnicalAssumptions(title, description, _context) {
163
163
  const assumptions = [];
164
164
  const text = `${title} ${description}`.toLowerCase();
165
165
 
@@ -33,7 +33,7 @@ const { execFileSync, execSync } = require('node:child_process');
33
33
  const fs = require('node:fs');
34
34
  const path = require('node:path');
35
35
 
36
- const { PATHS, getConfig, safeJsonParse, color } = require('./flow-utils');
36
+ const { PATHS, safeJsonParse } = require('./flow-utils');
37
37
 
38
38
  // ============================================================
39
39
  // Score Cap Thresholds
@@ -90,15 +90,25 @@ function runProjectScript(scriptName, timeout = 60000) {
90
90
  stdio: ['pipe', 'pipe', 'pipe'],
91
91
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' }
92
92
  });
93
- return { exists: true, passed: true, output: output.substring(0, 5000), errorCount: 0 };
93
+ return { exists: true, passed: true, output: truncateOutput(output), errorCount: 0, rawOutput: output };
94
94
  } catch (err) {
95
95
  const output = (err.stdout || '') + (err.stderr || '');
96
- // Count error lines
97
96
  const errorCount = (output.match(/error TS\d+|Error:|ERROR/gi) || []).length;
98
- return { exists: true, passed: false, output: output.substring(0, 5000), errorCount };
97
+ return { exists: true, passed: false, output: truncateOutput(output), errorCount, rawOutput: output };
99
98
  }
100
99
  }
101
100
 
101
+ /**
102
+ * Truncate output while preserving head AND tail so that end-of-output
103
+ * summary lines (e.g. "N problems (X errors, Y warnings)") remain parseable.
104
+ */
105
+ function truncateOutput(output) {
106
+ if (!output || output.length <= 5000) return output;
107
+ const head = output.substring(0, 3500);
108
+ const tail = output.substring(output.length - 1500);
109
+ return `${head}\n\n... [truncated ${output.length - 5000} chars] ...\n\n${tail}`;
110
+ }
111
+
102
112
  /**
103
113
  * Gate: Build check — does the project compile?
104
114
  */
@@ -161,16 +171,22 @@ function checkTypecheck() {
161
171
  function checkLint() {
162
172
  const result = runProjectScript('lint', 60000);
163
173
 
164
- // Parse error/warning counts
174
+ // Parse error/warning counts from the RAW (untruncated) output so the
175
+ // end-of-output ESLint summary line is never missed. Bug fixed 2026-04-16:
176
+ // previously used result.output which was truncated to 5000 chars, causing
177
+ // runs with many warnings to silently report "0 warnings".
165
178
  let errorCount = 0;
166
179
  let warningCount = 0;
167
- if (result.output) {
168
- // ESLint format: "N problems (X errors, Y warnings)"
169
- const match = result.output.match(/(\d+) problems?\s*\((\d+) errors?,\s*(\d+) warnings?\)/);
170
- if (match) {
171
- errorCount = parseInt(match[2], 10);
172
- warningCount = parseInt(match[3], 10);
173
- }
180
+ const parseSource = result.rawOutput || result.output || '';
181
+ // ESLint format: "N problems (X errors, Y warnings)"
182
+ const match = parseSource.match(/(\d+) problems?\s*\((\d+) errors?,\s*(\d+) warnings?\)/);
183
+ if (match) {
184
+ errorCount = parseInt(match[2], 10);
185
+ warningCount = parseInt(match[3], 10);
186
+ } else {
187
+ // Fallback: count individual warning/error lines (still accurate if summary missing).
188
+ warningCount = (parseSource.match(/^\s*\d+:\d+\s+warning/gm) || []).length;
189
+ errorCount = (parseSource.match(/^\s*\d+:\d+\s+error/gm) || []).length;
174
190
  }
175
191
 
176
192
  let scoreCap = 100;
@@ -312,9 +328,19 @@ function checkScriptCompleteness() {
312
328
  */
313
329
  function countEslintDisables() {
314
330
  try {
331
+ // Exclude node_modules, dist, build, coverage, and .workflow runtime data.
332
+ // Bug fixed 2026-04-16: previously walked the entire repo and counted
333
+ // eslint-disable in dependencies, inflating project-health reports.
315
334
  const output = execFileSync('grep', [
316
335
  '-r', 'eslint-disable',
317
336
  '--include=*.ts', '--include=*.tsx', '--include=*.js', '--include=*.jsx',
337
+ '--exclude-dir=node_modules',
338
+ '--exclude-dir=dist',
339
+ '--exclude-dir=build',
340
+ '--exclude-dir=coverage',
341
+ '--exclude-dir=.next',
342
+ '--exclude-dir=.workflow',
343
+ '--exclude-dir=.git',
318
344
  '-c', '.'
319
345
  ], { cwd: PATHS.root, encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] }).toString();
320
346
  const lines = output.trim().split('\n').filter(Boolean);
@@ -16,12 +16,12 @@
16
16
  */
17
17
 
18
18
  const { execFileSync } = require('node:child_process');
19
- const fs = require('node:fs');
19
+ const _fs = require('node:fs');
20
20
  const path = require('node:path');
21
21
  const {
22
22
  PATHS,
23
23
  getConfig,
24
- color,
24
+ color: _color,
25
25
  safeJsonParse,
26
26
  safeJsonParseString,
27
27
  escapeRegex
@@ -55,10 +55,10 @@ function getProjectFiles(extraExcludes = []) {
55
55
  const config = getConfig();
56
56
  // escapeRegex imported from flow-utils.js
57
57
  const configExcludes = (config.audit?.exclude || []).map(p => {
58
- try { return new RegExp(`^${escapeRegex(p)}/`); } catch (err) { return null; }
58
+ try { return new RegExp(`^${escapeRegex(p)}/`); } catch (_err) { return null; }
59
59
  }).filter(Boolean);
60
60
  const allExcludes = [...DEFAULT_EXCLUDE, ...configExcludes, ...extraExcludes.map(p => {
61
- try { return new RegExp(p); } catch (err) { return null; }
61
+ try { return new RegExp(p); } catch (_err) { return null; }
62
62
  }).filter(Boolean)];
63
63
 
64
64
  return output.trim().split('\n').filter(f =>
@@ -23,13 +23,13 @@ const fs = require('node:fs');
23
23
  const path = require('node:path');
24
24
  const { execSync, spawnSync } = require('node:child_process');
25
25
  const {
26
- getProjectRoot,
26
+ getProjectRoot: _getProjectRoot,
27
27
  getConfig,
28
28
  PATHS,
29
29
  colors,
30
30
  isAstGrepAvailable,
31
31
  astGrepSearch,
32
- AST_PATTERNS,
32
+ AST_PATTERNS: _AST_PATTERNS,
33
33
  findReactComponents,
34
34
  findCustomHooks,
35
35
  findTypeDefinitions,
@@ -902,7 +902,7 @@ async function getLegacyContext(description, options = {}, config = null) {
902
902
 
903
903
  const autoConf = config.taskContext?.auto || config.context?.auto;
904
904
  const maxFiles = options.maxFiles || autoConf?.maxFilesToLoad || 10;
905
- const showFiles = options.showFiles ?? autoConf?.showLoadedFiles ?? true;
905
+ const _showFiles = options.showFiles ?? autoConf?.showLoadedFiles ?? true;
906
906
 
907
907
  // Extract keywords
908
908
  const keywords = extractKeywords(description);