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.
- package/.claude/commands/wogi-bug.md +30 -0
- package/.claude/commands/wogi-debug-hypothesis.md +33 -0
- package/.claude/commands/wogi-morning.md +1 -2
- package/.claude/commands/wogi-review.md +31 -2
- package/.claude/commands/wogi-start.md +32 -0
- package/.claude/commands/wogi-statusline-setup.md +12 -0
- package/.claude/commands/wogi-story.md +3 -2
- package/.claude/docs/claude-code-compatibility.md +40 -0
- package/.claude/docs/phases/01-explore.md +2 -1
- package/.claude/docs/phases/03-implement.md +4 -0
- package/.claude/docs/phases/04-verify.md +45 -0
- package/.claude/rules/README.md +36 -0
- package/.claude/rules/_internal/worker-tool-first-turn.md +82 -0
- package/.claude/rules/alternative-execpolicy-toml-command-policy.md +11 -0
- package/.claude/rules/alternative-hand-edit-ready-json-to-register-orpha.md +11 -0
- package/.claude/rules/alternative-permission-ruleset-per-phase.md +11 -0
- package/.claude/rules/alternative-short-name.md +12 -0
- package/.claude/rules/alternative-wogi-flow-as-mcp-client-oauth-manager.md +11 -0
- package/.claude/rules/architecture/hook-three-layer.md +68 -0
- package/.claude/rules/dual-repo-architecture-2026-02-28.md +18 -0
- package/.claude/rules/github-release-workflow-2026-01-30.md +16 -0
- package/.claude/settings.json +1 -1
- package/.workflow/agents/logic-adversary.md +2 -1
- package/.workflow/agents/personas/README.md +48 -0
- package/.workflow/agents/personas/platform-rigor.md +38 -0
- package/.workflow/agents/personas/scale-skeptic.md +28 -0
- package/.workflow/agents/personas/security-hawk.md +34 -0
- package/.workflow/agents/personas/simplicity-champion.md +37 -0
- package/.workflow/agents/personas/user-advocate.md +36 -0
- package/.workflow/bridges/base-bridge.js +46 -23
- package/.workflow/templates/claude-md.hbs +44 -122
- package/.workflow/templates/partials/feature-dossiers.hbs +33 -0
- package/.workflow/templates/partials/intent-grounded-reasoning.hbs +2 -12
- package/.workflow/templates/partials/methodology-rules.hbs +85 -79
- package/.workflow/templates/tier3-dom-field-inventory.md +102 -0
- package/lib/fuzzy-patch.js +251 -0
- package/lib/installer.js +8 -0
- package/lib/memory-proposal-store.js +458 -0
- package/lib/mode-schema.js +255 -0
- package/lib/skill-proposal-store.js +432 -0
- package/lib/skill-registry.js +1 -1
- package/lib/wogi-claude +84 -9
- package/lib/wogi-claude-expect.exp +113 -76
- package/lib/workspace-channel-server.js +19 -0
- package/lib/workspace-contracts.js +1 -1
- package/lib/workspace-dispatch-tracking.js +144 -0
- package/lib/workspace-gates.js +1 -1
- package/lib/workspace-ipc-sqlite.js +550 -0
- package/lib/workspace-messages.js +92 -0
- package/lib/workspace-routing.js +1 -1
- package/lib/workspace-task-injector.js +223 -0
- package/lib/workspace.js +23 -0
- package/lib/worktree-review.js +315 -0
- package/package.json +2 -2
- package/scripts/base-workflow-step.js +1 -1
- package/scripts/flow +28 -4
- package/scripts/flow-ac-scope-preservation.js +238 -0
- package/scripts/flow-auto-review-worker.js +75 -0
- package/scripts/flow-auto-review.js +102 -0
- package/scripts/flow-autonomous-detector.js +118 -0
- package/scripts/flow-autonomous-mode.js +153 -0
- package/scripts/flow-best-of-n.js +1 -1
- package/scripts/flow-bulk-loop.js +1 -1
- package/scripts/flow-checkpoint.js +2 -6
- package/scripts/flow-community-sync.js +1 -1
- package/scripts/flow-completion-summary.js +176 -0
- package/scripts/flow-completion-truth-gate.js +343 -4
- package/scripts/flow-config-defaults.js +52 -5
- package/scripts/flow-context-compact/expander.js +1 -1
- package/scripts/flow-context-compact/section-extractor.js +2 -2
- package/scripts/flow-context-gatherer.js +1 -1
- package/scripts/flow-context-generator.js +1 -1
- package/scripts/flow-context-scoring.js +1 -1
- package/scripts/flow-correct.js +1 -1
- package/scripts/flow-decision-authority.js +66 -15
- package/scripts/flow-done.js +33 -1
- package/scripts/flow-epic-cascade.js +171 -0
- package/scripts/flow-epics.js +2 -7
- package/scripts/flow-eval-judge.js +1 -1
- package/scripts/flow-eval.js +1 -1
- package/scripts/flow-export-scanner.js +2 -6
- package/scripts/flow-failure-learning.js +1 -1
- package/scripts/flow-feature-dossier.js +787 -0
- package/scripts/flow-figma-extract.js +2 -2
- package/scripts/flow-figma-generate.js +1 -1
- package/scripts/flow-gate-confidence.js +1 -1
- package/scripts/flow-health.js +52 -1
- package/scripts/flow-hooks.js +1 -1
- package/scripts/flow-id.js +19 -3
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-knowledge-router.js +1 -1
- package/scripts/flow-knowledge-sync.js +1 -1
- package/scripts/flow-logic-adversary.js +76 -1
- package/scripts/flow-logic-rules.js +380 -0
- package/scripts/flow-long-input.js +5 -5
- package/scripts/flow-memory-sync.js +1 -1
- package/scripts/flow-memory.js +78 -7
- package/scripts/flow-migrate.js +1 -1
- package/scripts/flow-model-caller.js +1 -1
- package/scripts/flow-models.js +2 -2
- package/scripts/flow-morning.js +0 -17
- package/scripts/flow-multi-approach.js +1 -1
- package/scripts/flow-orchestrate-context.js +4 -4
- package/scripts/flow-orchestrate-templates.js +1 -1
- package/scripts/flow-orchestrate.js +8 -8
- package/scripts/flow-peer-review.js +1 -1
- package/scripts/flow-phase.js +9 -0
- package/scripts/flow-proactive-compact.js +1 -1
- package/scripts/flow-providers.js +1 -1
- package/scripts/flow-question-queue.js +255 -0
- package/scripts/flow-repo-map.js +312 -0
- package/scripts/flow-review-passes/index.js +1 -1
- package/scripts/flow-review-passes/integration.js +1 -1
- package/scripts/flow-review-passes/structure.js +1 -1
- package/scripts/flow-revision-tracker.js +1 -1
- package/scripts/flow-section-resolver.js +1 -1
- package/scripts/flow-session-end.js +74 -5
- package/scripts/flow-session-state.js +103 -1
- package/scripts/flow-setup-hooks.js +1 -1
- package/scripts/flow-skeptical-evaluator.js +274 -0
- package/scripts/flow-skill-generator.js +3 -3
- package/scripts/flow-skill-learn.js +3 -6
- package/scripts/flow-skill-manage.js +248 -0
- package/scripts/flow-spec-verifier.js +1 -1
- package/scripts/flow-standards-checker.js +75 -0
- package/scripts/flow-standards-gate.js +1 -1
- package/scripts/flow-statusline-setup.js +8 -2
- package/scripts/flow-step-changelog.js +2 -2
- package/scripts/flow-step-coverage.js +1 -1
- package/scripts/flow-step-knowledge.js +1 -1
- package/scripts/flow-step-regression.js +1 -1
- package/scripts/flow-step-simplifier.js +1 -1
- package/scripts/flow-task-analyzer.js +1 -1
- package/scripts/flow-task-classifier.js +1 -1
- package/scripts/flow-task-enforcer.js +1 -1
- package/scripts/flow-template-extractor.js +1 -1
- package/scripts/flow-trap-zone.js +1 -1
- package/scripts/flow-utils.js +4 -0
- package/scripts/flow-worker-question-classifier.js +51 -5
- package/scripts/flow-workspace-migrate-ipc.js +216 -0
- package/scripts/flow-workspace-summary.js +256 -0
- package/scripts/hooks/adapters/base-adapter.js +2 -2
- package/scripts/hooks/core/feature-dossier-gate.js +194 -0
- package/scripts/hooks/core/observation-capture.js +24 -0
- package/scripts/hooks/core/overdue-dispatches.js +20 -1
- package/scripts/hooks/core/phase-gate.js +15 -1
- package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
- package/scripts/hooks/core/post-compact.js +5 -2
- package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
- package/scripts/hooks/core/routing-gate.js +58 -0
- package/scripts/hooks/core/session-context.js +108 -0
- package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
- package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
- package/scripts/hooks/core/session-end.js +25 -0
- package/scripts/hooks/core/setup-handler.js +1 -1
- package/scripts/hooks/core/task-boundary-reset.js +110 -4
- package/scripts/hooks/core/worker-boundary-gate.js +71 -0
- package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
- package/scripts/hooks/entry/claude-code/session-start.js +74 -30
- package/scripts/hooks/entry/claude-code/stop.js +47 -1
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
- 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
|
-
|
|
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 = [],
|
|
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 = [],
|
|
290
|
+
const { files = [], _previousResults = {} } = context;
|
|
291
291
|
const config = getConfig();
|
|
292
292
|
const namingConvention = config.componentReuse?.namingConvention || 'kebab-case';
|
|
293
293
|
|
|
@@ -25,7 +25,7 @@ const {
|
|
|
25
25
|
isGitRepo,
|
|
26
26
|
getGitStatus,
|
|
27
27
|
safeJsonParse,
|
|
28
|
-
|
|
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
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
};
|