wogiflow 2.26.2 → 2.29.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 (164) 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 +84 -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-context-compact/expander.js +1 -1
  70. package/scripts/flow-context-compact/section-extractor.js +2 -2
  71. package/scripts/flow-context-gatherer.js +1 -1
  72. package/scripts/flow-context-generator.js +1 -1
  73. package/scripts/flow-context-scoring.js +1 -1
  74. package/scripts/flow-correct.js +1 -1
  75. package/scripts/flow-decision-authority.js +66 -15
  76. package/scripts/flow-done.js +33 -1
  77. package/scripts/flow-epic-cascade.js +171 -0
  78. package/scripts/flow-epics.js +2 -7
  79. package/scripts/flow-eval-judge.js +1 -1
  80. package/scripts/flow-eval.js +1 -1
  81. package/scripts/flow-export-scanner.js +2 -6
  82. package/scripts/flow-failure-learning.js +1 -1
  83. package/scripts/flow-feature-dossier.js +787 -0
  84. package/scripts/flow-figma-extract.js +2 -2
  85. package/scripts/flow-figma-generate.js +1 -1
  86. package/scripts/flow-gate-confidence.js +1 -1
  87. package/scripts/flow-health.js +52 -1
  88. package/scripts/flow-hooks.js +1 -1
  89. package/scripts/flow-id.js +19 -3
  90. package/scripts/flow-instruction-richness.js +1 -1
  91. package/scripts/flow-knowledge-router.js +1 -1
  92. package/scripts/flow-knowledge-sync.js +1 -1
  93. package/scripts/flow-logic-adversary.js +76 -1
  94. package/scripts/flow-logic-rules.js +380 -0
  95. package/scripts/flow-long-input.js +5 -5
  96. package/scripts/flow-memory-sync.js +1 -1
  97. package/scripts/flow-memory.js +78 -7
  98. package/scripts/flow-migrate.js +1 -1
  99. package/scripts/flow-model-caller.js +1 -1
  100. package/scripts/flow-models.js +2 -2
  101. package/scripts/flow-morning.js +0 -17
  102. package/scripts/flow-multi-approach.js +1 -1
  103. package/scripts/flow-orchestrate-context.js +4 -4
  104. package/scripts/flow-orchestrate-templates.js +1 -1
  105. package/scripts/flow-orchestrate.js +8 -8
  106. package/scripts/flow-peer-review.js +1 -1
  107. package/scripts/flow-phase.js +9 -0
  108. package/scripts/flow-proactive-compact.js +1 -1
  109. package/scripts/flow-providers.js +1 -1
  110. package/scripts/flow-question-queue.js +255 -0
  111. package/scripts/flow-repo-map.js +312 -0
  112. package/scripts/flow-review-passes/index.js +1 -1
  113. package/scripts/flow-review-passes/integration.js +1 -1
  114. package/scripts/flow-review-passes/structure.js +1 -1
  115. package/scripts/flow-revision-tracker.js +1 -1
  116. package/scripts/flow-section-resolver.js +1 -1
  117. package/scripts/flow-session-end.js +74 -5
  118. package/scripts/flow-session-state.js +103 -1
  119. package/scripts/flow-setup-hooks.js +1 -1
  120. package/scripts/flow-skeptical-evaluator.js +274 -0
  121. package/scripts/flow-skill-generator.js +3 -3
  122. package/scripts/flow-skill-learn.js +3 -6
  123. package/scripts/flow-skill-manage.js +248 -0
  124. package/scripts/flow-spec-verifier.js +1 -1
  125. package/scripts/flow-standards-checker.js +75 -0
  126. package/scripts/flow-standards-gate.js +1 -1
  127. package/scripts/flow-statusline-setup.js +8 -2
  128. package/scripts/flow-step-changelog.js +2 -2
  129. package/scripts/flow-step-coverage.js +1 -1
  130. package/scripts/flow-step-knowledge.js +1 -1
  131. package/scripts/flow-step-regression.js +1 -1
  132. package/scripts/flow-step-simplifier.js +1 -1
  133. package/scripts/flow-task-analyzer.js +1 -1
  134. package/scripts/flow-task-classifier.js +1 -1
  135. package/scripts/flow-task-enforcer.js +1 -1
  136. package/scripts/flow-template-extractor.js +1 -1
  137. package/scripts/flow-trap-zone.js +1 -1
  138. package/scripts/flow-utils.js +4 -0
  139. package/scripts/flow-worker-question-classifier.js +51 -5
  140. package/scripts/flow-workspace-migrate-ipc.js +216 -0
  141. package/scripts/flow-workspace-summary.js +256 -0
  142. package/scripts/hooks/adapters/base-adapter.js +2 -2
  143. package/scripts/hooks/core/feature-dossier-gate.js +194 -0
  144. package/scripts/hooks/core/observation-capture.js +24 -0
  145. package/scripts/hooks/core/overdue-dispatches.js +20 -1
  146. package/scripts/hooks/core/phase-gate.js +15 -1
  147. package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
  148. package/scripts/hooks/core/post-compact.js +5 -2
  149. package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
  150. package/scripts/hooks/core/routing-gate.js +58 -0
  151. package/scripts/hooks/core/session-context.js +108 -0
  152. package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
  153. package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
  154. package/scripts/hooks/core/session-end.js +25 -0
  155. package/scripts/hooks/core/setup-handler.js +1 -1
  156. package/scripts/hooks/core/task-boundary-reset.js +110 -4
  157. package/scripts/hooks/core/worker-boundary-gate.js +71 -0
  158. package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
  159. package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
  160. package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
  161. package/scripts/hooks/entry/claude-code/session-start.js +74 -30
  162. package/scripts/hooks/entry/claude-code/stop.js +47 -1
  163. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
  164. package/.workflow/templates/partials/user-commands.hbs +0 -20
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow — Aider-style Repo Map (wf-f3707d2f / C1).
5
+ *
6
+ * Generates a compact, task-aware repo map that fits in a bounded token
7
+ * budget. Intended for injection at Step 1 Load Context (and refresh at each
8
+ * turn during exploring + coding phases) so the AI sees:
9
+ * - TOUCHED — files the current task modifies (summary + top-level symbols)
10
+ * - ADJACENT — files that import or are imported by the touched set
11
+ * - SHAPE — compressed tree of the rest of the project (names only)
12
+ *
13
+ * This complements the existing registry maps (app-map, function-map, api-map)
14
+ * which are manually curated; the repo map is cheap, disposable, and always-fresh.
15
+ *
16
+ * Story: wf-f3707d2f (C1)
17
+ * Epic: wf-34290000
18
+ */
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+ const { execFileSync } = require('node:child_process');
23
+
24
+ const { PATHS } = require('./flow-paths');
25
+ const { getConfig } = require('./flow-config-loader');
26
+
27
+ const DEFAULT_BUDGET_BYTES = 16 * 1024; // ~4k tokens
28
+ const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.workflow', '.worktrees', 'out']);
29
+ const CODE_EXTS = new Set(['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs']);
30
+ const DOC_EXTS = new Set(['.md']);
31
+ const STATE_EXTS = new Set(['.json', '.yaml', '.yml', '.toml']);
32
+
33
+ function _getRepoMapConfig() {
34
+ const cfg = getConfig();
35
+ return cfg.repoMap || {};
36
+ }
37
+
38
+ /**
39
+ * Resolve changed-files list for a task.
40
+ * Priority: explicit opts.changedFiles → durable-session checkpoint → `git diff --name-only`.
41
+ *
42
+ * @param {object} opts
43
+ * @returns {string[]}
44
+ */
45
+ function resolveChangedFiles(opts = {}) {
46
+ if (Array.isArray(opts.changedFiles)) return opts.changedFiles;
47
+
48
+ // Try task-checkpoint.json
49
+ const checkpointPath = path.join(PATHS.state, 'task-checkpoint.json');
50
+ if (fs.existsSync(checkpointPath)) {
51
+ try {
52
+ const cp = JSON.parse(fs.readFileSync(checkpointPath, 'utf8'));
53
+ if (Array.isArray(cp.changedFiles) && cp.changedFiles.length > 0) return cp.changedFiles;
54
+ } catch { /* fall through */ }
55
+ }
56
+
57
+ // Git diff
58
+ try {
59
+ const out = execFileSync('git', ['diff', '--name-only', 'HEAD'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
60
+ const files = out.split('\n').filter(Boolean);
61
+ if (files.length > 0) return files;
62
+ } catch { /* no git */ }
63
+
64
+ try {
65
+ const out = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
66
+ return out.split('\n').map((l) => l.slice(3).trim()).filter(Boolean);
67
+ } catch { return []; }
68
+ }
69
+
70
+ /**
71
+ * Extract top-level symbol signatures from a file. Lightweight — regex only,
72
+ * no AST parser. Captures: function decls, class decls, const declarations,
73
+ * module.exports / export default, named exports.
74
+ *
75
+ * @param {string} filePath - absolute path
76
+ * @returns {{ symbols: string[], firstLine: string, loc: number }}
77
+ */
78
+ function extractSymbols(filePath) {
79
+ const out = { symbols: [], firstLine: '', loc: 0 };
80
+ let content;
81
+ try {
82
+ content = fs.readFileSync(filePath, 'utf8');
83
+ } catch { return out; }
84
+
85
+ const lines = content.split('\n');
86
+ out.loc = lines.length;
87
+ // First docblock / comment line
88
+ for (const line of lines.slice(0, 5)) {
89
+ const t = line.trim().replace(/^[*/\s]+|[*/\s]+$/g, '');
90
+ if (t.length > 4 && !/^@|^#!\//.test(t)) { out.firstLine = t.slice(0, 100); break; }
91
+ }
92
+
93
+ const patterns = [
94
+ /^(?:export\s+(?:default\s+)?)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm,
95
+ /^(?:export\s+(?:default\s+)?)?class\s+(\w+)/gm,
96
+ /^(?:export\s+)?const\s+([A-Z][A-Z0-9_]+)\s*=/gm, // SCREAMING_SNAKE constants
97
+ /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>)/gm,
98
+ ];
99
+ for (const re of patterns) {
100
+ for (const m of content.matchAll(re)) {
101
+ const sig = m[2] !== undefined ? `${m[1]}(${m[2].length > 40 ? '...' : m[2]})` : m[1];
102
+ if (!out.symbols.includes(sig)) out.symbols.push(sig);
103
+ if (out.symbols.length >= 12) break;
104
+ }
105
+ }
106
+
107
+ // module.exports = {...} named-key extraction
108
+ const mexRe = /module\.exports\s*=\s*\{([^}]+)\}/;
109
+ const mex = content.match(mexRe);
110
+ if (mex) {
111
+ const keys = mex[1].split(',').map((s) => s.trim().split(/[:=\s]/)[0]).filter(Boolean);
112
+ for (const k of keys.slice(0, 8)) if (/^\w+$/.test(k) && !out.symbols.some((s) => s.startsWith(k))) out.symbols.push(k);
113
+ }
114
+
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * Find files that import or are imported by the given seed files.
120
+ * Strict depth=1 (direct neighbors only).
121
+ *
122
+ * @param {string[]} seedFiles - paths relative to repo root
123
+ * @param {string[]} allCodeFiles - paths to consider
124
+ * @returns {string[]} paths of adjacent files
125
+ */
126
+ function findAdjacent(seedFiles, allCodeFiles) {
127
+ const seedSet = new Set(seedFiles);
128
+ const adjacent = new Set();
129
+ const seedBasenames = seedFiles.map((f) => path.basename(f, path.extname(f)));
130
+
131
+ for (const file of allCodeFiles) {
132
+ if (seedSet.has(file)) continue;
133
+ let content;
134
+ try { content = fs.readFileSync(file, 'utf8'); } catch { continue; }
135
+
136
+ // File imports something FROM the seed set
137
+ for (const base of seedBasenames) {
138
+ const re = new RegExp(`(?:require|import|from)\\s*\\(?[\`'"][^\`'"]*?${base}[^\`'"]*?[\`'"]\\)?`);
139
+ if (re.test(content)) { adjacent.add(file); break; }
140
+ }
141
+ }
142
+
143
+ // Reverse: what do seed files import?
144
+ for (const seed of seedFiles) {
145
+ let content;
146
+ try { content = fs.readFileSync(seed, 'utf8'); } catch { continue; }
147
+ const importRe = /(?:require|from)\s*\(?[`'"]([^`'"]+)[`'"]\)?/g;
148
+ for (const m of content.matchAll(importRe)) {
149
+ const resolved = _resolveImport(seed, m[1], allCodeFiles);
150
+ if (resolved && !seedSet.has(resolved)) adjacent.add(resolved);
151
+ }
152
+ }
153
+
154
+ return [...adjacent];
155
+ }
156
+
157
+ function _resolveImport(fromFile, spec, allFiles) {
158
+ if (!spec.startsWith('.')) return null; // external package
159
+ const dir = path.dirname(fromFile);
160
+ const base = path.resolve(dir, spec);
161
+ for (const ext of ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '/index.js', '/index.ts']) {
162
+ const candidate = base + ext;
163
+ const rel = path.relative(process.cwd(), candidate);
164
+ if (allFiles.includes(rel)) return rel;
165
+ }
166
+ return null;
167
+ }
168
+
169
+ /**
170
+ * Walk the repo collecting code file paths (relative to cwd).
171
+ * @returns {string[]}
172
+ */
173
+ function collectCodeFiles() {
174
+ const root = process.cwd();
175
+ const out = [];
176
+ function walk(dir) {
177
+ let entries;
178
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
179
+ for (const e of entries) {
180
+ if (e.name.startsWith('.') && e.name !== '.claude' && e.name !== '.workflow') continue;
181
+ if (IGNORED_DIRS.has(e.name)) continue;
182
+ const p = path.join(dir, e.name);
183
+ if (e.isDirectory()) walk(p);
184
+ else if (e.isFile() && CODE_EXTS.has(path.extname(e.name))) out.push(path.relative(root, p));
185
+ }
186
+ }
187
+ walk(root);
188
+ return out;
189
+ }
190
+
191
+ /**
192
+ * Generate a compact shape-of-repo summary (file count per top-level dir).
193
+ */
194
+ function generateShape(allCodeFiles) {
195
+ const buckets = {};
196
+ for (const f of allCodeFiles) {
197
+ const top = f.split('/')[0];
198
+ buckets[top] = (buckets[top] || 0) + 1;
199
+ }
200
+ return Object.entries(buckets)
201
+ .sort((a, b) => b[1] - a[1])
202
+ .slice(0, 12)
203
+ .map(([k, v]) => `${k}/ (${v})`)
204
+ .join(', ');
205
+ }
206
+
207
+ /**
208
+ * Generate the repo map markdown.
209
+ *
210
+ * @param {object} [opts]
211
+ * @param {string} [opts.taskId]
212
+ * @param {string[]} [opts.changedFiles]
213
+ * @param {number} [opts.budgetBytes] - max output size
214
+ * @param {boolean} [opts.includeShape=true]
215
+ * @returns {{ markdown: string, stats: object }}
216
+ */
217
+ function generateRepoMap(opts = {}) {
218
+ const cfg = _getRepoMapConfig();
219
+ if (cfg.enabled === false) {
220
+ return { markdown: '', stats: { skipped: true, reason: 'config-disabled' } };
221
+ }
222
+
223
+ const budget = opts.budgetBytes ?? cfg.budgetBytes ?? DEFAULT_BUDGET_BYTES;
224
+ const includeShape = opts.includeShape !== false;
225
+ const changed = resolveChangedFiles(opts).filter((f) => CODE_EXTS.has(path.extname(f)) || DOC_EXTS.has(path.extname(f)) || STATE_EXTS.has(path.extname(f)));
226
+
227
+ const allCode = collectCodeFiles();
228
+ const touched = changed.filter((f) => fs.existsSync(f));
229
+ const adjacent = touched.length > 0 ? findAdjacent(touched, allCode).slice(0, 20) : [];
230
+
231
+ const lines = [];
232
+ lines.push(`# Repo Map${opts.taskId ? ' — ' + opts.taskId : ''}`);
233
+ lines.push('');
234
+ lines.push(`Generated: ${new Date().toISOString()} | files-scanned: ${allCode.length} | touched: ${touched.length} | adjacent: ${adjacent.length}`);
235
+ lines.push('');
236
+
237
+ if (touched.length > 0) {
238
+ lines.push('## TOUCHED');
239
+ for (const f of touched.slice(0, 20)) {
240
+ const abs = path.resolve(f);
241
+ const info = extractSymbols(abs);
242
+ lines.push(`### ${f}`);
243
+ if (info.firstLine) lines.push(`_${info.firstLine}_`);
244
+ lines.push(`- LOC: ${info.loc}`);
245
+ if (info.symbols.length > 0) lines.push(`- Symbols: \`${info.symbols.slice(0, 10).join('`, `')}\``);
246
+ lines.push('');
247
+ }
248
+ } else {
249
+ lines.push('## TOUCHED\n_(no changed code files detected)_\n');
250
+ }
251
+
252
+ if (adjacent.length > 0) {
253
+ lines.push('## ADJACENT (depth=1 imports)');
254
+ for (const f of adjacent) {
255
+ const info = extractSymbols(path.resolve(f));
256
+ const sigs = info.symbols.slice(0, 5).join(', ');
257
+ lines.push(`- ${f}${sigs ? ` — \`${sigs}\`` : ''}`);
258
+ }
259
+ lines.push('');
260
+ }
261
+
262
+ if (includeShape) {
263
+ lines.push('## SHAPE');
264
+ lines.push(generateShape(allCode));
265
+ lines.push('');
266
+ }
267
+
268
+ let markdown = lines.join('\n');
269
+ const wasTruncated = markdown.length > budget;
270
+ if (wasTruncated) {
271
+ markdown = markdown.slice(0, budget - 40) + '\n\n_(repo map truncated at budget)_\n';
272
+ }
273
+
274
+ return {
275
+ markdown,
276
+ stats: {
277
+ touched: touched.length,
278
+ adjacent: adjacent.length,
279
+ filesScanned: allCode.length,
280
+ bytes: markdown.length,
281
+ budget,
282
+ truncated: wasTruncated,
283
+ },
284
+ };
285
+ }
286
+
287
+ module.exports = {
288
+ generateRepoMap,
289
+ resolveChangedFiles,
290
+ extractSymbols,
291
+ findAdjacent,
292
+ collectCodeFiles,
293
+ DEFAULT_BUDGET_BYTES,
294
+ };
295
+
296
+ // CLI
297
+ if (require.main === module) {
298
+ const args = process.argv.slice(2);
299
+ const cmd = args[0];
300
+ if (cmd === 'generate' || !cmd) {
301
+ const taskId = args.find((a) => a.startsWith('--task='))?.split('=')[1];
302
+ const budgetArg = args.find((a) => a.startsWith('--budget='))?.split('=')[1];
303
+ const result = generateRepoMap({ taskId, budgetBytes: budgetArg ? parseInt(budgetArg, 10) : undefined });
304
+ process.stdout.write(result.markdown);
305
+ if (args.includes('--stats')) {
306
+ process.stderr.write('\n\nStats: ' + JSON.stringify(result.stats) + '\n');
307
+ }
308
+ } else {
309
+ console.error('usage: flow-repo-map generate [--task=<id>] [--budget=<bytes>] [--stats]');
310
+ process.exit(2);
311
+ }
312
+ }
@@ -183,7 +183,7 @@ async function runMultiPassReview(context, options = {}) {
183
183
  passes = ['structure', 'logic', 'security', 'integration'],
184
184
  earlyExitOnCritical = reviewConfig.earlyExitOnCritical !== false,
185
185
  passForward = reviewConfig.passForward !== false,
186
- parallel = false
186
+ _parallel = false
187
187
  } = options;
188
188
 
189
189
  const results = {
@@ -502,7 +502,7 @@ function analyzeDependencyGraph(files) {
502
502
  * @returns {Promise<Object>} Pass results
503
503
  */
504
504
  async function run(context) {
505
- const { files = [], previousResults = {} } = context;
505
+ const { files = [], _previousResults = {} } = context;
506
506
 
507
507
  const issues = [];
508
508
  const suggestions = [];
@@ -287,7 +287,7 @@ function checkDirectoryStructure(files) {
287
287
  * @returns {Promise<Object>} Pass results
288
288
  */
289
289
  async function run(context) {
290
- const { files = [], previousResults = {} } = context;
290
+ const { files = [], _previousResults = {} } = context;
291
291
  const config = getConfig();
292
292
  const namingConvention = config.componentReuse?.namingConvention || 'kebab-case';
293
293
 
@@ -19,7 +19,7 @@ const {
19
19
  PATHS,
20
20
  readJson: _readJson,
21
21
  fileExists: _fileExists,
22
- getTodayDate
22
+ _getTodayDate
23
23
  } = require('./flow-utils');
24
24
  const { recordRevision, loadStats } = require('./flow-stats-collector');
25
25
 
@@ -29,7 +29,7 @@ const {
29
29
  fileExists: _fileExists,
30
30
  readFile: _readFile,
31
31
  info,
32
- warn
32
+ _warn
33
33
  } = require('./flow-utils');
34
34
 
35
35
  const {
@@ -25,7 +25,7 @@ const {
25
25
  isGitRepo,
26
26
  getGitStatus,
27
27
  safeJsonParse,
28
- getTodayDate
28
+ _getTodayDate
29
29
  } = require('./flow-utils')
30
30
  const { color, printSection, success, warn, error } = require('./flow-output');;
31
31
 
@@ -774,6 +774,64 @@ function showContextHealthSummary() {
774
774
  }
775
775
  }
776
776
 
777
+ // B7 (wf-c3b5afab): Surface gate missRate telemetry at session-end so users
778
+ // see rubber-stamping risk without running /wogi-gate-stats. Pulls from the
779
+ // existing getGateStats() API — no duplicated computation or thresholds.
780
+ const MISS_RATE_THRESHOLD = 0.10;
781
+ const MISS_WATCH_WINDOW = '7d';
782
+ const MISS_WATCH_TOP_N = 3;
783
+
784
+ function loadGateStats() {
785
+ let getGateStats;
786
+ try {
787
+ ({ getGateStats } = require('./flow-gate-telemetry'));
788
+ } catch (err) {
789
+ if (process.env.DEBUG) console.error(`[DEBUG] Gate telemetry: ${err.message}`);
790
+ return null;
791
+ }
792
+ try {
793
+ return getGateStats({ since: MISS_WATCH_WINDOW });
794
+ } catch (err) {
795
+ if (process.env.DEBUG) console.error(`[DEBUG] Gate telemetry stats: ${err.message}`);
796
+ return null;
797
+ }
798
+ }
799
+
800
+ function printGateTelemetryWatch(stats = loadGateStats()) {
801
+ console.log('');
802
+ console.log(color('yellow', 'Gate Telemetry — Miss Rate Watch (7d):'));
803
+
804
+ const perGate = stats && stats.perGate ? stats.perGate : null;
805
+ const gates = perGate ? Object.keys(perGate) : [];
806
+ if (!perGate || gates.length === 0) {
807
+ console.log(` ${color('dim', 'No telemetry yet (baseline)')}`);
808
+ return;
809
+ }
810
+
811
+ const ranked = gates
812
+ .map(id => ({ id, ...perGate[id] }))
813
+ .filter(g => g.verdicts && g.verdicts.PASS > 0)
814
+ .sort((a, b) => b.missRate - a.missRate)
815
+ .slice(0, MISS_WATCH_TOP_N);
816
+
817
+ if (ranked.length === 0) {
818
+ console.log(` ${color('dim', 'No PASS events yet — miss rate unmeasurable')}`);
819
+ return;
820
+ }
821
+
822
+ for (const g of ranked) {
823
+ const pct = (g.missRate * 100).toFixed(1);
824
+ const flagged = g.missRate >= MISS_RATE_THRESHOLD;
825
+ const line = ` ${g.id}: miss ${pct}% (${g.missedAfterPass}/${g.verdicts.PASS} PASS events)`;
826
+ if (flagged) {
827
+ console.log(color('red', `${line} ← rubber-stamping risk`));
828
+ } else {
829
+ console.log(line);
830
+ }
831
+ }
832
+ console.log(` ${color('dim', 'Threshold: miss rate ≥ 10% flags a gate as rubber-stamping. See /wogi-gate-stats for full table.')}`);
833
+ }
834
+
777
835
  /**
778
836
  * v1.8.0: Automatic memory management
779
837
  * Part of automatic memory management for teams
@@ -1346,6 +1404,9 @@ async function main() {
1346
1404
  // v1.7.0: Show context health
1347
1405
  showContextHealthSummary();
1348
1406
 
1407
+ // B7 (wf-c3b5afab): Surface gate miss-rate watch — rubber-stamping visibility
1408
+ printGateTelemetryWatch();
1409
+
1349
1410
  // v1.8.0: Automatic memory management
1350
1411
  await automaticMemoryManagement();
1351
1412
 
@@ -1376,7 +1437,15 @@ async function main() {
1376
1437
  showSummary();
1377
1438
  }
1378
1439
 
1379
- main().catch(err => {
1380
- console.error('Error:', err.message);
1381
- process.exit(1);
1382
- });
1440
+ if (require.main === module) {
1441
+ main().catch(err => {
1442
+ console.error('Error:', err.message);
1443
+ process.exit(1);
1444
+ });
1445
+ }
1446
+
1447
+ module.exports = {
1448
+ printGateTelemetryWatch,
1449
+ MISS_RATE_THRESHOLD,
1450
+ MISS_WATCH_TOP_N,
1451
+ };
@@ -94,7 +94,11 @@ function getDefaultState() {
94
94
  count: 0, // Number of bypasses in this session
95
95
  attempts: [], // Array of bypass attempt details
96
96
  autoCreatedTasks: [] // Tasks that were auto-created (bypasses)
97
- }
97
+ },
98
+ // Autonomous walk-away mode (Story C / wf-d712002e)
99
+ // active=true means the AI commits to running the queue without interrupting
100
+ // until it drains, the user sends "stop"/"pause", or the staleness threshold trips.
101
+ autonomousMode: null
98
102
  };
99
103
  }
100
104
 
@@ -915,6 +919,93 @@ if (require.main === module) {
915
919
  }
916
920
  }
917
921
 
922
+ // ============================================================
923
+ // Autonomous Mode (Story C / wf-d712002e)
924
+ // ============================================================
925
+ //
926
+ // Disk is canonical (survives SIGTERM at task-boundary-reset); cache is read-hot.
927
+ // Hot-path hooks (PreToolUse) MUST use the cache; never re-read the disk file.
928
+
929
+ const DEFAULT_AUTONOMOUS_STALENESS_MS = 60 * 60 * 1000;
930
+
931
+ let autonomousModeCache = undefined;
932
+
933
+ function getAutonomousConfig() {
934
+ const cfg = getConfig().autonomousMode || {};
935
+ return {
936
+ stalenessThresholdMs: cfg.stalenessThresholdMs ?? DEFAULT_AUTONOMOUS_STALENESS_MS,
937
+ maxAdversaryInvocations: cfg.maxAdversaryInvocations ?? 30,
938
+ maxQueueSize: cfg.maxQueueSize ?? 100
939
+ };
940
+ }
941
+
942
+ function activateAutonomousMode({ trigger } = {}) {
943
+ const runId = `auto-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
944
+ const record = {
945
+ active: true,
946
+ activatedAt: new Date().toISOString(),
947
+ trigger: trigger || 'unspecified',
948
+ runId,
949
+ adversaryInvocations: { used: 0, breakdown: { autonomousLowConfidence: 0, igrArchitect: 0, manual: 0 } }
950
+ };
951
+ saveSessionState({ autonomousMode: record });
952
+ autonomousModeCache = record;
953
+ return record;
954
+ }
955
+
956
+ function deactivateAutonomousMode() {
957
+ saveSessionState({ autonomousMode: null });
958
+ autonomousModeCache = null;
959
+ }
960
+
961
+ function getAutonomousMode() {
962
+ if (autonomousModeCache !== undefined) return autonomousModeCache;
963
+ const state = loadSessionState();
964
+ autonomousModeCache = state.autonomousMode || null;
965
+ return autonomousModeCache;
966
+ }
967
+
968
+ function isAutonomousActive() {
969
+ const mode = getAutonomousMode();
970
+ return Boolean(mode && mode.active);
971
+ }
972
+
973
+ function isAutonomousStale(mode = getAutonomousMode()) {
974
+ if (!mode || !mode.activatedAt) return false;
975
+ const ageMs = Date.now() - new Date(mode.activatedAt).getTime();
976
+ return ageMs > getAutonomousConfig().stalenessThresholdMs;
977
+ }
978
+
979
+ function rehydrateAutonomousFromDisk() {
980
+ autonomousModeCache = undefined;
981
+ const mode = getAutonomousMode();
982
+ if (mode && mode.active && isAutonomousStale(mode)) {
983
+ deactivateAutonomousMode();
984
+ return { hydrated: false, reason: 'stale', staleMode: mode };
985
+ }
986
+ return { hydrated: Boolean(mode && mode.active), mode };
987
+ }
988
+
989
+ function incrementAdversaryInvocation(source = 'manual') {
990
+ const mode = getAutonomousMode();
991
+ if (!mode || !mode.active) return { allowed: true, used: 0, cap: 0 };
992
+ const cap = getAutonomousConfig().maxAdversaryInvocations;
993
+ const used = (mode.adversaryInvocations?.used ?? 0) + 1;
994
+ const breakdown = { ...(mode.adversaryInvocations?.breakdown || {}) };
995
+ const key = source === 'igr' ? 'igrArchitect'
996
+ : source === 'lowConfidence' ? 'autonomousLowConfidence'
997
+ : 'manual';
998
+ breakdown[key] = (breakdown[key] || 0) + 1;
999
+ const updated = { ...mode, adversaryInvocations: { used, breakdown } };
1000
+ saveSessionState({ autonomousMode: updated });
1001
+ autonomousModeCache = updated;
1002
+ return { allowed: used <= cap, used, cap };
1003
+ }
1004
+
1005
+ function _resetAutonomousCacheForTests() {
1006
+ autonomousModeCache = undefined;
1007
+ }
1008
+
918
1009
  // ============================================================
919
1010
  // Exports
920
1011
  // ============================================================
@@ -979,6 +1070,17 @@ module.exports = {
979
1070
  getBypassSummary,
980
1071
  clearBypassTracking,
981
1072
 
1073
+ // Autonomous mode (Story C / wf-d712002e)
1074
+ activateAutonomousMode,
1075
+ deactivateAutonomousMode,
1076
+ getAutonomousMode,
1077
+ isAutonomousActive,
1078
+ isAutonomousStale,
1079
+ rehydrateAutonomousFromDisk,
1080
+ incrementAdversaryInvocation,
1081
+ getAutonomousConfig,
1082
+ _resetAutonomousCacheForTests,
1083
+
982
1084
  // Path
983
1085
  SESSION_PATH
984
1086
  };
@@ -18,7 +18,7 @@ const path = require('node:path');
18
18
  const {
19
19
  getProjectRoot: _getProjectRoot,
20
20
  getConfig, PATHS,
21
- getTodayDate
21
+ _getTodayDate
22
22
  } = require('./flow-utils')
23
23
  const { color, success, warn, error } = require('./flow-output');;
24
24