wogiflow 2.26.2 → 2.29.1

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 (169) hide show
  1. package/.claude/commands/wogi-bug.md +30 -0
  2. package/.claude/commands/wogi-debug-hypothesis.md +33 -0
  3. package/.claude/commands/wogi-morning.md +1 -2
  4. package/.claude/commands/wogi-review.md +31 -2
  5. package/.claude/commands/wogi-start.md +32 -0
  6. package/.claude/commands/wogi-statusline-setup.md +12 -0
  7. package/.claude/commands/wogi-story.md +3 -2
  8. package/.claude/docs/claude-code-compatibility.md +40 -0
  9. package/.claude/docs/phases/01-explore.md +2 -1
  10. package/.claude/docs/phases/03-implement.md +4 -0
  11. package/.claude/docs/phases/04-verify.md +45 -0
  12. package/.claude/rules/README.md +36 -0
  13. package/.claude/rules/_internal/worker-tool-first-turn.md +82 -0
  14. package/.claude/rules/alternative-execpolicy-toml-command-policy.md +11 -0
  15. package/.claude/rules/alternative-hand-edit-ready-json-to-register-orpha.md +11 -0
  16. package/.claude/rules/alternative-permission-ruleset-per-phase.md +11 -0
  17. package/.claude/rules/alternative-short-name.md +12 -0
  18. package/.claude/rules/alternative-wogi-flow-as-mcp-client-oauth-manager.md +11 -0
  19. package/.claude/rules/architecture/hook-three-layer.md +68 -0
  20. package/.claude/rules/dual-repo-architecture-2026-02-28.md +18 -0
  21. package/.claude/rules/github-release-workflow-2026-01-30.md +16 -0
  22. package/.claude/settings.json +1 -1
  23. package/.workflow/agents/logic-adversary.md +2 -1
  24. package/.workflow/agents/personas/README.md +48 -0
  25. package/.workflow/agents/personas/platform-rigor.md +38 -0
  26. package/.workflow/agents/personas/scale-skeptic.md +28 -0
  27. package/.workflow/agents/personas/security-hawk.md +34 -0
  28. package/.workflow/agents/personas/simplicity-champion.md +37 -0
  29. package/.workflow/agents/personas/user-advocate.md +36 -0
  30. package/.workflow/bridges/base-bridge.js +46 -23
  31. package/.workflow/templates/claude-md.hbs +44 -122
  32. package/.workflow/templates/partials/feature-dossiers.hbs +33 -0
  33. package/.workflow/templates/partials/intent-grounded-reasoning.hbs +2 -12
  34. package/.workflow/templates/partials/methodology-rules.hbs +85 -79
  35. package/.workflow/templates/tier3-dom-field-inventory.md +102 -0
  36. package/lib/fuzzy-patch.js +251 -0
  37. package/lib/installer.js +8 -0
  38. package/lib/memory-proposal-store.js +458 -0
  39. package/lib/mode-schema.js +255 -0
  40. package/lib/skill-proposal-store.js +432 -0
  41. package/lib/skill-registry.js +1 -1
  42. package/lib/wogi-claude +149 -9
  43. package/lib/wogi-claude-expect.exp +113 -76
  44. package/lib/workspace-channel-server.js +19 -0
  45. package/lib/workspace-contracts.js +1 -1
  46. package/lib/workspace-dispatch-tracking.js +144 -0
  47. package/lib/workspace-gates.js +1 -1
  48. package/lib/workspace-ipc-sqlite.js +550 -0
  49. package/lib/workspace-messages.js +92 -0
  50. package/lib/workspace-routing.js +1 -1
  51. package/lib/workspace-task-injector.js +223 -0
  52. package/lib/workspace.js +23 -0
  53. package/lib/worktree-review.js +315 -0
  54. package/package.json +2 -2
  55. package/scripts/base-workflow-step.js +1 -1
  56. package/scripts/flow +28 -4
  57. package/scripts/flow-ac-scope-preservation.js +238 -0
  58. package/scripts/flow-auto-review-worker.js +75 -0
  59. package/scripts/flow-auto-review.js +102 -0
  60. package/scripts/flow-autonomous-detector.js +118 -0
  61. package/scripts/flow-autonomous-mode.js +153 -0
  62. package/scripts/flow-best-of-n.js +1 -1
  63. package/scripts/flow-bulk-loop.js +1 -1
  64. package/scripts/flow-checkpoint.js +2 -6
  65. package/scripts/flow-community-sync.js +1 -1
  66. package/scripts/flow-completion-summary.js +176 -0
  67. package/scripts/flow-completion-truth-gate.js +343 -4
  68. package/scripts/flow-config-defaults.js +52 -5
  69. package/scripts/flow-config-loader.js +3 -2
  70. package/scripts/flow-context-compact/expander.js +1 -1
  71. package/scripts/flow-context-compact/section-extractor.js +2 -2
  72. package/scripts/flow-context-gatherer.js +1 -1
  73. package/scripts/flow-context-generator.js +1 -1
  74. package/scripts/flow-context-scoring.js +1 -1
  75. package/scripts/flow-correct.js +1 -1
  76. package/scripts/flow-correction-detector.js +3 -2
  77. package/scripts/flow-decision-authority.js +66 -15
  78. package/scripts/flow-done.js +33 -1
  79. package/scripts/flow-epic-cascade.js +171 -0
  80. package/scripts/flow-epics.js +2 -7
  81. package/scripts/flow-eval-judge.js +1 -1
  82. package/scripts/flow-eval.js +1 -1
  83. package/scripts/flow-export-scanner.js +2 -6
  84. package/scripts/flow-failure-learning.js +1 -1
  85. package/scripts/flow-feature-dossier.js +787 -0
  86. package/scripts/flow-figma-extract.js +2 -2
  87. package/scripts/flow-figma-generate.js +1 -1
  88. package/scripts/flow-gate-confidence.js +1 -1
  89. package/scripts/flow-health.js +52 -1
  90. package/scripts/flow-hooks.js +1 -1
  91. package/scripts/flow-id.js +19 -3
  92. package/scripts/flow-instruction-richness.js +1 -1
  93. package/scripts/flow-knowledge-router.js +1 -1
  94. package/scripts/flow-knowledge-sync.js +1 -1
  95. package/scripts/flow-logic-adversary.js +76 -1
  96. package/scripts/flow-logic-rules.js +380 -0
  97. package/scripts/flow-long-input.js +5 -5
  98. package/scripts/flow-memory-sync.js +1 -1
  99. package/scripts/flow-memory.js +78 -7
  100. package/scripts/flow-migrate.js +1 -1
  101. package/scripts/flow-model-caller.js +1 -1
  102. package/scripts/flow-models.js +2 -2
  103. package/scripts/flow-morning.js +0 -17
  104. package/scripts/flow-multi-approach.js +1 -1
  105. package/scripts/flow-orchestrate-context.js +4 -4
  106. package/scripts/flow-orchestrate-templates.js +1 -1
  107. package/scripts/flow-orchestrate.js +8 -8
  108. package/scripts/flow-peer-review.js +1 -1
  109. package/scripts/flow-phase.js +9 -0
  110. package/scripts/flow-proactive-compact.js +1 -1
  111. package/scripts/flow-prompt-composer.js +3 -2
  112. package/scripts/flow-prompt-template.js +3 -2
  113. package/scripts/flow-providers.js +1 -1
  114. package/scripts/flow-question-queue.js +255 -0
  115. package/scripts/flow-repo-map.js +312 -0
  116. package/scripts/flow-review-passes/index.js +1 -1
  117. package/scripts/flow-review-passes/integration.js +1 -1
  118. package/scripts/flow-review-passes/structure.js +1 -1
  119. package/scripts/flow-revision-tracker.js +1 -1
  120. package/scripts/flow-section-resolver.js +1 -1
  121. package/scripts/flow-session-end.js +74 -5
  122. package/scripts/flow-session-state.js +103 -1
  123. package/scripts/flow-setup-hooks.js +1 -1
  124. package/scripts/flow-skeptical-evaluator.js +274 -0
  125. package/scripts/flow-skill-generator.js +3 -3
  126. package/scripts/flow-skill-learn.js +3 -6
  127. package/scripts/flow-skill-manage.js +248 -0
  128. package/scripts/flow-spec-verifier.js +1 -1
  129. package/scripts/flow-standards-checker.js +75 -0
  130. package/scripts/flow-standards-gate.js +1 -1
  131. package/scripts/flow-statusline-setup.js +8 -2
  132. package/scripts/flow-step-changelog.js +2 -2
  133. package/scripts/flow-step-coverage.js +1 -1
  134. package/scripts/flow-step-knowledge.js +1 -1
  135. package/scripts/flow-step-regression.js +1 -1
  136. package/scripts/flow-step-simplifier.js +1 -1
  137. package/scripts/flow-task-analyzer.js +1 -1
  138. package/scripts/flow-task-classifier.js +1 -1
  139. package/scripts/flow-task-enforcer.js +1 -1
  140. package/scripts/flow-template-extractor.js +1 -1
  141. package/scripts/flow-trap-zone.js +1 -1
  142. package/scripts/flow-utils.js +4 -0
  143. package/scripts/flow-worker-mcp-strip.js +122 -0
  144. package/scripts/flow-worker-question-classifier.js +51 -5
  145. package/scripts/flow-workspace-migrate-ipc.js +216 -0
  146. package/scripts/flow-workspace-summary.js +256 -0
  147. package/scripts/hooks/adapters/base-adapter.js +2 -2
  148. package/scripts/hooks/core/feature-dossier-gate.js +194 -0
  149. package/scripts/hooks/core/observation-capture.js +24 -0
  150. package/scripts/hooks/core/overdue-dispatches.js +20 -1
  151. package/scripts/hooks/core/phase-gate.js +15 -1
  152. package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
  153. package/scripts/hooks/core/post-compact.js +5 -2
  154. package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
  155. package/scripts/hooks/core/routing-gate.js +58 -0
  156. package/scripts/hooks/core/session-context.js +108 -0
  157. package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
  158. package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
  159. package/scripts/hooks/core/session-end.js +25 -0
  160. package/scripts/hooks/core/setup-handler.js +1 -1
  161. package/scripts/hooks/core/task-boundary-reset.js +110 -4
  162. package/scripts/hooks/core/worker-boundary-gate.js +71 -0
  163. package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
  164. package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
  165. package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
  166. package/scripts/hooks/entry/claude-code/session-start.js +74 -30
  167. package/scripts/hooks/entry/claude-code/stop.js +47 -1
  168. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
  169. package/.workflow/templates/partials/user-commands.hbs +0 -20
@@ -0,0 +1,102 @@
1
+ # Tier-3 DOM Field Inventory Snapshot
2
+
3
+ **Purpose**: Structured template for Tier-3 (INTERACTIVE) verification of any UI surface that contains user-input fields — forms, filters, settings panels, search bars, wizards. Prevents "vanishing field" bugs where a field silently disappears during refactor.
4
+
5
+ **When to use**: Validating phase (see `.claude/docs/phases/04-verify.md`). Required for any task that:
6
+ - Modifies a form, filter group, wizard step, or settings section
7
+ - Adds / removes / renames a field in an existing UI
8
+ - Refactors a component whose children include `<input>`, `<select>`, `<textarea>`, or a custom input component
9
+
10
+ **Story**: wf-f9431ef6 (B3)
11
+ **Consumer**: `/wogi-start` validating phase, `/wogi-test-browser`, `flow-skeptical-evaluator.js`
12
+
13
+ ---
14
+
15
+ ## Protocol
16
+
17
+ ### Step 1 — Before-change baseline (BEFORE any code changes)
18
+
19
+ Navigate to the page/component and capture the field inventory:
20
+
21
+ ```
22
+ Page URL: <url or route>
23
+ Component: <top-level component path>
24
+ Captured at: <ISO timestamp>
25
+
26
+ Fields (one row per input):
27
+ 1. name="<input name or data-testid>"
28
+ label="<visible label text>"
29
+ type=<text|email|password|select|checkbox|radio|textarea|custom>
30
+ default=<default value>
31
+ required=<yes|no>
32
+ validation=<"min=3", "regex=...", or "none">
33
+ visible=<yes|no (conditional rendering)>
34
+ aria-label=<screen-reader label if different from label>
35
+
36
+ 2. ...
37
+ ```
38
+
39
+ Save as: `.workflow/verifications/<taskId>/dom-inventory-before.md`
40
+
41
+ ### Step 2 — After-change snapshot (AFTER implementation + lint/typecheck pass)
42
+
43
+ Re-navigate to the same page/component with the new code and capture the same schema.
44
+
45
+ Save as: `.workflow/verifications/<taskId>/dom-inventory-after.md`
46
+
47
+ ### Step 3 — Diff and surface changes
48
+
49
+ For each field present in BEFORE:
50
+ - **preserved**: field still present with same name, label, type, validation → OK
51
+ - **modified**: field present but with different label / type / validation → REVIEW (was the change intentional?)
52
+ - **vanished**: field absent from AFTER → **CRITICAL** (unless intentional per the task spec)
53
+
54
+ For each field present in AFTER but not in BEFORE:
55
+ - **added**: new field → REVIEW (was it in the task spec? does it need validation?)
56
+
57
+ ### Step 4 — Reconcile against the spec
58
+
59
+ Open the task spec (`.workflow/changes/*/<taskId>.md`) and check that every `vanished`, `modified`, and `added` field is explicitly named in an acceptance criterion.
60
+
61
+ If ANY change is NOT in the spec:
62
+ - STOP
63
+ - Surface the unplanned change to the user
64
+ - Either: (a) the change is unintentional → revert, (b) the change is correct but spec is stale → user approves the spec update
65
+
66
+ ### Step 5 — Persist
67
+
68
+ Write the reconciliation report to `.workflow/verifications/<taskId>/dom-diff.md`:
69
+
70
+ ```markdown
71
+ # DOM Field Diff — <taskId>
72
+
73
+ ## Preserved (N)
74
+ ✓ field-1 (label, type)
75
+ ...
76
+
77
+ ## Modified (N)
78
+ ~ field-2: label changed "Foo" → "Bar" — PLANNED per AC-3
79
+ ...
80
+
81
+ ## Vanished (N)
82
+ ✗ field-3 — UNEXPECTED; reverting / asking user
83
+
84
+ ## Added (N)
85
+ + field-4 (label, type) — PLANNED per AC-5
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Why this template exists
91
+
92
+ Prior incidents (see `feedback-patterns.md`): forms silently lost fields during component refactors. Lint, typecheck, and even smoke tests passed because the missing field had no consumer in the critical path. Users discovered the loss days later, sometimes after data was submitted with missing values.
93
+
94
+ Tier-3 field-inventory snapshots turn "silently vanished" into "mechanically diffable". The structured format makes it greppable and reviewable, not just narrative-memory.
95
+
96
+ ---
97
+
98
+ ## Configuration
99
+
100
+ - Template is referenced by path from phase docs; no config toggle.
101
+ - If a task's files contain `.tsx|.jsx|.vue|.svelte` and a form element, validating phase should prompt for this inventory.
102
+ - When using `/wogi-test-browser`, the MCP tool can produce the BEFORE / AFTER snapshots automatically.
@@ -0,0 +1,251 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Wogi Flow — Fuzzy Find-Replace Patching
5
+ *
6
+ * Applies a `find → replace` edit to a source string, tolerating benign
7
+ * whitespace drift (CRLF↔LF, trailing whitespace, extra blank lines) without
8
+ * tolerating semantic drift (reordered lines, changed tokens). Used by F1
9
+ * `flow skill patch` when the agent stages a find/replace pair.
10
+ *
11
+ * Strategy:
12
+ * 1. Exact match → confidence 1.0, replace in place.
13
+ * 2. Normalized-haystack ↔ normalized-needle exact match → locate original
14
+ * bounds in the raw haystack, replace, confidence 1.0 (whitespace drift).
15
+ * 3. Sliding-window Levenshtein similarity over normalized text.
16
+ * Best window score ≥ threshold → replace that window; otherwise reject.
17
+ *
18
+ * The similarity metric is 1 − levenshtein(a,b) / max(len(a), len(b)), so
19
+ * reordered lines (many single-char moves) score far below 0.85 and are
20
+ * rejected as "semantic drift, not whitespace drift".
21
+ */
22
+
23
+ const DEFAULT_THRESHOLD = 0.85;
24
+
25
+ /**
26
+ * Normalize text for similarity comparison:
27
+ * - CRLF → LF
28
+ * - Strip trailing whitespace per line
29
+ * - Collapse runs of spaces/tabs inside a line to a single space
30
+ * - Trim leading/trailing blank lines
31
+ */
32
+ function normalize(text) {
33
+ if (typeof text !== 'string') return '';
34
+ const unix = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
35
+ const lines = unix.split('\n').map((ln) => ln.replace(/[ \t]+/g, ' ').replace(/\s+$/, ''));
36
+ // Trim leading / trailing blank lines but preserve interior blanks.
37
+ let start = 0;
38
+ let end = lines.length;
39
+ while (start < end && lines[start] === '') start++;
40
+ while (end > start && lines[end - 1] === '') end--;
41
+ return lines.slice(start, end).join('\n');
42
+ }
43
+
44
+ /**
45
+ * Levenshtein distance with O(min(a,b)) memory (two-row DP).
46
+ */
47
+ function levenshtein(a, b) {
48
+ if (a === b) return 0;
49
+ if (!a.length) return b.length;
50
+ if (!b.length) return a.length;
51
+ // Ensure a is the shorter string to cap memory.
52
+ if (a.length > b.length) { const t = a; a = b; b = t; }
53
+
54
+ const aLen = a.length;
55
+ const bLen = b.length;
56
+ let prev = new Array(aLen + 1);
57
+ let curr = new Array(aLen + 1);
58
+ for (let i = 0; i <= aLen; i++) prev[i] = i;
59
+
60
+ for (let j = 1; j <= bLen; j++) {
61
+ curr[0] = j;
62
+ const bc = b.charCodeAt(j - 1);
63
+ for (let i = 1; i <= aLen; i++) {
64
+ const cost = a.charCodeAt(i - 1) === bc ? 0 : 1;
65
+ const del = prev[i] + 1;
66
+ const ins = curr[i - 1] + 1;
67
+ const sub = prev[i - 1] + cost;
68
+ curr[i] = del < ins ? (del < sub ? del : sub) : (ins < sub ? ins : sub);
69
+ }
70
+ const tmp = prev; prev = curr; curr = tmp;
71
+ }
72
+ return prev[aLen];
73
+ }
74
+
75
+ function similarity(a, b) {
76
+ const maxLen = Math.max(a.length, b.length);
77
+ if (maxLen === 0) return 1;
78
+ return 1 - levenshtein(a, b) / maxLen;
79
+ }
80
+
81
+ /**
82
+ * Find the bounds in `rawHaystack` whose normalized form equals `normalizedNeedle`.
83
+ * Walks candidate slices, expanding around each line boundary.
84
+ * Returns { start, end } indices in rawHaystack, or null if not found.
85
+ */
86
+ function findNormalizedMatch(rawHaystack, normalizedNeedle) {
87
+ const unix = rawHaystack.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
88
+ // Compute line offsets (each entry = start index of that line in `unix`).
89
+ const lineStarts = [0];
90
+ for (let i = 0; i < unix.length; i++) {
91
+ if (unix.charCodeAt(i) === 10) lineStarts.push(i + 1);
92
+ }
93
+ lineStarts.push(unix.length + 1); // sentinel
94
+
95
+ const needleLineCount = normalizedNeedle.split('\n').length;
96
+ // Try windows of size [needleLineCount-1 .. needleLineCount+1] lines.
97
+ for (let size = Math.max(1, needleLineCount - 1); size <= needleLineCount + 1; size++) {
98
+ for (let s = 0; s + size < lineStarts.length; s++) {
99
+ const startIdx = lineStarts[s];
100
+ // end = start of line s+size, minus the trailing newline (if any).
101
+ let endIdx = lineStarts[s + size] - 1;
102
+ if (endIdx < startIdx) endIdx = startIdx;
103
+ const slice = unix.slice(startIdx, endIdx);
104
+ if (normalize(slice) === normalizedNeedle) {
105
+ // Map back to rawHaystack offsets. Since we only replaced CRLF/CR with
106
+ // LF (same character count for CR→LF; CRLF→LF drops one), we need to
107
+ // translate `unix` offsets back to `rawHaystack` offsets.
108
+ const rawStart = translateUnixToRaw(rawHaystack, startIdx);
109
+ const rawEnd = translateUnixToRaw(rawHaystack, endIdx);
110
+ return { start: rawStart, end: rawEnd };
111
+ }
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Translate an offset in the LF-normalized version of `raw` back to an offset
119
+ * in `raw` itself. Handles CRLF and bare CR.
120
+ */
121
+ function translateUnixToRaw(raw, unixOffset) {
122
+ let rawI = 0;
123
+ let unixI = 0;
124
+ while (unixI < unixOffset && rawI < raw.length) {
125
+ const c = raw.charCodeAt(rawI);
126
+ if (c === 13 /* \r */) {
127
+ // CR or CRLF — consumes 1 or 2 raw chars for 1 unix LF.
128
+ if (rawI + 1 < raw.length && raw.charCodeAt(rawI + 1) === 10) {
129
+ rawI += 2;
130
+ } else {
131
+ rawI += 1;
132
+ }
133
+ unixI += 1;
134
+ } else {
135
+ rawI += 1;
136
+ unixI += 1;
137
+ }
138
+ }
139
+ return rawI;
140
+ }
141
+
142
+ /**
143
+ * Slide windows of approximately `|needle|` chars across `haystack`, computing
144
+ * normalized similarity. Returns the best { start, end, confidence } or null.
145
+ *
146
+ * For performance, we step by 1 char but only compute Levenshtein on windows
147
+ * of length within ±20% of the needle length (after normalization is cheap).
148
+ */
149
+ function bestFuzzyWindow(rawHaystack, normalizedNeedle) {
150
+ const needleLen = normalizedNeedle.length;
151
+ if (needleLen === 0 || rawHaystack.length === 0) return null;
152
+
153
+ const minLen = Math.max(1, Math.floor(needleLen * 0.8));
154
+ const maxLen = Math.ceil(needleLen * 1.2);
155
+
156
+ let best = { start: 0, end: 0, confidence: 0 };
157
+
158
+ // Step size scales with haystack size to keep this O(n * |needle|).
159
+ // For small files (skills <10KB), step=1 is fine.
160
+ const step = rawHaystack.length > 20000 ? Math.max(1, Math.floor(needleLen / 4)) : 1;
161
+
162
+ for (let start = 0; start <= rawHaystack.length - minLen; start += step) {
163
+ for (let len = minLen; len <= maxLen && start + len <= rawHaystack.length; len += Math.max(1, Math.floor(len / 8))) {
164
+ const windowRaw = rawHaystack.slice(start, start + len);
165
+ const windowNorm = normalize(windowRaw);
166
+ if (!windowNorm) continue;
167
+ const sim = similarity(windowNorm, normalizedNeedle);
168
+ if (sim > best.confidence) {
169
+ best = { start, end: start + len, confidence: sim };
170
+ }
171
+ }
172
+ }
173
+
174
+ return best.confidence > 0 ? best : null;
175
+ }
176
+
177
+ /**
178
+ * Apply a fuzzy patch. Returns:
179
+ * { applied: true, result: string, confidence: number, mode: 'exact'|'normalized'|'fuzzy' }
180
+ * { applied: false, confidence: number, reason: string }
181
+ *
182
+ * Never throws on bad matches — rejection is signaled via applied=false.
183
+ * Throws only on invalid arguments.
184
+ */
185
+ function applyFuzzyPatch(haystack, find, replace, options = {}) {
186
+ if (typeof haystack !== 'string') throw new Error('haystack must be a string');
187
+ if (typeof find !== 'string') throw new Error('find must be a string');
188
+ if (typeof replace !== 'string') throw new Error('replace must be a string');
189
+ const threshold = typeof options.threshold === 'number' ? options.threshold : DEFAULT_THRESHOLD;
190
+ if (threshold < 0 || threshold > 1) throw new Error('threshold must be between 0 and 1');
191
+ if (find === '') throw new Error('find must not be empty');
192
+
193
+ // Tier 1: exact match.
194
+ const exactIdx = haystack.indexOf(find);
195
+ if (exactIdx !== -1) {
196
+ return {
197
+ applied: true,
198
+ result: haystack.slice(0, exactIdx) + replace + haystack.slice(exactIdx + find.length),
199
+ confidence: 1.0,
200
+ mode: 'exact',
201
+ };
202
+ }
203
+
204
+ const normalizedNeedle = normalize(find);
205
+ if (!normalizedNeedle) {
206
+ return { applied: false, confidence: 0, reason: 'find reduces to empty after normalization' };
207
+ }
208
+
209
+ // Tier 2: whitespace-drift match (normalized equality).
210
+ const bounds = findNormalizedMatch(haystack, normalizedNeedle);
211
+ if (bounds) {
212
+ return {
213
+ applied: true,
214
+ result: haystack.slice(0, bounds.start) + replace + haystack.slice(bounds.end),
215
+ confidence: 1.0,
216
+ mode: 'normalized',
217
+ };
218
+ }
219
+
220
+ // Tier 3: fuzzy sliding window.
221
+ const win = bestFuzzyWindow(haystack, normalizedNeedle);
222
+ const confidence = win ? win.confidence : 0;
223
+ if (!win || confidence < threshold) {
224
+ return {
225
+ applied: false,
226
+ confidence,
227
+ reason: `best match confidence ${confidence.toFixed(3)} below threshold ${threshold}`,
228
+ };
229
+ }
230
+
231
+ return {
232
+ applied: true,
233
+ result: haystack.slice(0, win.start) + replace + haystack.slice(win.end),
234
+ confidence,
235
+ mode: 'fuzzy',
236
+ };
237
+ }
238
+
239
+ module.exports = {
240
+ applyFuzzyPatch,
241
+ DEFAULT_THRESHOLD,
242
+ // Exposed for targeted tests.
243
+ _internal: {
244
+ normalize,
245
+ levenshtein,
246
+ similarity,
247
+ findNormalizedMatch,
248
+ bestFuzzyWindow,
249
+ translateUnixToRaw,
250
+ },
251
+ };
package/lib/installer.js CHANGED
@@ -783,6 +783,14 @@ Generated by Wogi Flow v${config.version}
783
783
  fs.writeFileSync(rulesFilePath, simpleContent);
784
784
  console.log(` Created ${resources.rulesFile}`);
785
785
 
786
+ // A1 (wf-a346c915): Also emit AGENTS.md for cross-tool portability (Codex, Cline, etc.).
787
+ // CLAUDE.md remains canonical; AGENTS.md is an identical sibling.
788
+ if (config?.cli?.generateAgentsMd !== false && resources.rulesFile === 'CLAUDE.md') {
789
+ const agentsPath = path.join(projectRoot, 'AGENTS.md');
790
+ fs.writeFileSync(agentsPath, simpleContent);
791
+ console.log(` Created AGENTS.md`);
792
+ }
793
+
786
794
  console.log(` Configured ${cli.name}`);
787
795
  }
788
796