fv-skills-baif 2.0.0 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to FVS (Formal Verification Skills) will be documented in th
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [2.0.2] - 2026-07-02
8
+
9
+ ### Fixed
10
+ - Codex installs now enable hooks with `[features].hooks = true` instead of an invalid root-level `hooks = true`, which current Codex parses as the hooks config table and rejects at startup.
11
+
12
+ ## [2.0.1] - 2026-07-01
13
+
14
+ Documentation polish. No code or behavior changes.
15
+
16
+ ### Changed
17
+ - README "How It Works" now documents the Aeneas extraction repair loop, the trust audit, and the paper track's crypto loop -- previously only the v1.x five-stage functional-correctness flow was described; the tagline and Prerequisites now cover both the code and paper tracks
18
+ - `/fvs:help` documents the crypto loop's `--codex` single- vs dual-runtime flag (which stages accept it, what it swaps, and the no-silent-fallback behavior), and Quick Start / Core Workflow now surface the extraction and crypto-loop entry points
19
+ - Install screenshot (`assets/terminal.svg`) version label updated to the v2.0 line
20
+
7
21
  ## [2.0.0] - 2026-06-30
8
22
 
9
23
  Structural cleanup and bundle architecture. Updating from v1.3 applies all renames, moves, and removals automatically (deleted and renamed commands/agents self-heal on install); the installer prints a one-time summary of the changes below.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # FORMAL VERIFICATION SKILLS
4
4
 
5
- **Formal verification of Rust code with AI-assisted specification and proof. Multi-framework, multi-runtime.**
5
+ **Formal verification in Lean 4 with AI-assisted specification and proof — for Rust code (via Aeneas) and for maths/crypto papers. Multi-runtime.**
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/fv-skills-baif?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/fv-skills-baif)
8
8
  [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
@@ -54,9 +54,10 @@ Verify with `/fvs:help` inside your chosen runtime.
54
54
 
55
55
  ### Prerequisites (Lean 4 / Aeneas)
56
56
 
57
- - A Lean 4 project with `lakefile.toml` and `lean-toolchain`
58
- - Aeneas-generated output (`Types.lean`, `Funs.lean`) from your Rust source
59
- - Lean 4 toolchain installed and working
57
+ - A working Lean 4 toolchain, and a Lean project with `lakefile.toml` and `lean-toolchain`
58
+ - **Functional-correctness track:** Aeneas-generated Lean (`Types.lean`, `Funs.lean`). Produce it from a Rust crate with `/fvs:aeneas-extract`, or supply the output of an existing Aeneas run.
59
+ - **Paper track:** no Rust or Aeneas needed — bring your paper/source material (PDF, LaTeX, images).
60
+ - **Optional:** the Codex CLI, to run the crypto loop's thinking stages dual-runtime (`--codex`).
60
61
 
61
62
  ### Recommended: Lean LSP MCP Server
62
63
 
@@ -150,7 +151,9 @@ Commands are grouped into five bundles. Each bundle has a **router** command tha
150
151
 
151
152
  ## How It Works
152
153
 
153
- FVS follows a five-stage workflow. Each stage builds on the previous.
154
+ ### Functional-correctness track (Rust Lean 4)
155
+
156
+ This track verifies Rust that Aeneas has lowered to Lean 4. Starting from a Rust crate, `/fvs:aeneas-extract <path>` drives it through the bounded **extraction repair loop** — pin audit → classify → auto-apply / bisect / gate / escalate → reversible records — until you reach a clean build or a documented escalation. It writes reversible source records (`src-modifications.diff` plus a derived `.json`/`.md` and `src-assumptions.md`) at the crate root and never edits generated Lean. Once you have `Types.lean` / `Funs.lean`, the five-stage verification workflow begins:
154
157
 
155
158
  ### 1. Map
156
159
 
@@ -172,6 +175,23 @@ FVS follows a five-stage workflow. Each stage builds on the previous.
172
175
 
173
176
  `/fvs:lean-refactor <spec_path>` — Refactor, simplify, and decompose verified proofs. Applies tiered heuristics (dead code removal, simp sharpening, tactic golf, smart automation) while verifying compilation after every change. Three modes: safe, balanced (default), and aggressive.
174
177
 
178
+ ### 6. Audit
179
+
180
+ `/fvs:trust-audit <target>` — Build-backed audit of the trust surface. Runs a green-build precondition, then uses `#print axioms` to classify every in-scope declaration as verified / `sorry` / axiom. The classical trio (`propext`, `Classical.choice`, `Quot.sound`) is auto-noted as Lean/Mathlib-standard; any project-custom axiom must be justified or the gate reports NOT-CLEAN. Produces a re-runnable, dependency-ordered table under `.formalising/audits/`.
181
+
182
+ ### The paper track (maths / crypto)
183
+
184
+ The paper track formalises papers directly into Lean 4 — no Rust, no Aeneas. Two entry points:
185
+
186
+ - **One-shot:** `/fvs:lean-formalise` reads your PDFs / images / LaTeX (optionally grounded in a NotebookLM knowledge base via `/fvs:kb-setup`) and produces Lean definition and spec files in a single pass.
187
+ - **Iterative crypto loop:** for larger crypto formalisations, a topic-based, restartable loop of four stages:
188
+
189
+ `/fvs:crypto-plan` → `/fvs:crypto-execute` → `/fvs:crypto-eval` → `/fvs:crypto-followup` → repeat
190
+
191
+ A high-effort thinker authors each bounded plan; the executor runs it under a green-build guard; an adversarial eval tries to refute the spec, proof, and assumptions and ends in exactly one of ACCEPT / FOLLOWUP / HUMAN_RULING / BLOCKED; follow-up turns findings into the next plan (halting for a human ruling on modeling decisions).
192
+
193
+ **Single- vs dual-runtime (`--codex`).** By default the loop is single-runtime — the thinking stages (`crypto-plan`, `crypto-eval`, `crypto-followup`) run the in-runtime `fvs-crypto-thinker`. Pass `--codex` to hand a stage's thinking to an independent **Codex CLI** thinker instead, so the adversarial planner/evaluator runs on a *different engine* than the executor and blind spots don't correlate. `crypto-execute` is the runtime-neutral executor and takes no `--codex`. Without the Codex CLI installed, a `--codex` stage stops with an install hint rather than silently falling back.
194
+
175
195
  ---
176
196
 
177
197
  ## Uninstalling
package/bin/install.js CHANGED
@@ -16,13 +16,20 @@ const reset = '\x1b[0m';
16
16
  // Codex config.toml constants
17
17
  const FVS_CODEX_MARKER = '# FVS Agent Configuration \u2014 managed by fv-skills-baif installer';
18
18
 
19
- // Codex hooks feature flag. Current Codex CLI reads a boolean `hooks` flag in
20
- // config.toml to enable its hook dispatcher; an older naming used `codex_hooks`.
21
- // Emit the canonical key and treat the legacy alias as equivalent so a reinstall
22
- // over an older config migrates the flag forward instead of leaving a duplicate.
19
+ // Codex hooks feature flag. Current Codex CLI reads feature flags under the
20
+ // `[features]` table; a root-level `hooks = true` is parsed as the hooks config
21
+ // object itself and is rejected. Emit `[features].hooks`, and treat the legacy
22
+ // alias as equivalent so a reinstall over an older config migrates forward
23
+ // instead of leaving a duplicate.
23
24
  const CODEX_HOOKS_FEATURE_KEY = 'hooks';
24
25
  const CODEX_HOOKS_FEATURE_LEGACY_KEYS = ['codex_hooks'];
25
26
  const CODEX_HOOKS_FEATURE_ALL_KEYS = [CODEX_HOOKS_FEATURE_KEY, ...CODEX_HOOKS_FEATURE_LEGACY_KEYS];
27
+ const CODEX_FEATURES_TABLE = 'features';
28
+ const FVS_CODEX_HOOKS_FEATURE_COMMENT = '# FVS-owned Codex hooks feature flag';
29
+
30
+ function isCodexHooksFeatureKey(key) {
31
+ return CODEX_HOOKS_FEATURE_ALL_KEYS.includes(key);
32
+ }
26
33
 
27
34
  // The only FVS hook script with a Codex event target is the update checker,
28
35
  // wired as a SessionStart hook. The statusline has no Codex renderer surface and
@@ -1083,6 +1090,123 @@ function removeContentRanges(content, ranges) {
1083
1090
  return cleaned;
1084
1091
  }
1085
1092
 
1093
+ function joinTomlLines(lines) {
1094
+ return lines.map((line) => line.text + line.eol).join('');
1095
+ }
1096
+
1097
+ function parseCodexHooksFeatureAssignment(lineText) {
1098
+ const m = lineText.match(/^\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_]+))\s*=\s*(true|false)(\s*(?:#.*)?)?$/);
1099
+ if (!m) return null;
1100
+ const key = m[1] || m[2] || m[3];
1101
+ if (!isCodexHooksFeatureKey(key)) return null;
1102
+ return { key, value: m[4], suffix: m[5] || '' };
1103
+ }
1104
+
1105
+ function parseRootDottedCodexHooksFeatureAssignment(lineText) {
1106
+ const m = lineText.match(/^\s*features\.(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_]+))\s*=\s*(true|false)(\s*(?:#.*)?)?$/);
1107
+ if (!m) return null;
1108
+ const key = m[1] || m[2] || m[3];
1109
+ if (!isCodexHooksFeatureKey(key)) return null;
1110
+ return { key, value: m[4], suffix: m[5] || '' };
1111
+ }
1112
+
1113
+ function renderCodexHooksFeatureAssignment(lineText) {
1114
+ const indent = (lineText.match(/^\s*/) || [''])[0];
1115
+ const parsed = parseCodexHooksFeatureAssignment(lineText);
1116
+ return `${indent}${CODEX_HOOKS_FEATURE_KEY} = true${parsed ? parsed.suffix : ''}`;
1117
+ }
1118
+
1119
+ function renderRootDottedCodexHooksFeatureAssignment(lineText) {
1120
+ const indent = (lineText.match(/^\s*/) || [''])[0];
1121
+ const parsed = parseRootDottedCodexHooksFeatureAssignment(lineText);
1122
+ return `${indent}features.${CODEX_HOOKS_FEATURE_KEY} = true${parsed ? parsed.suffix : ''}`;
1123
+ }
1124
+
1125
+ function removeRootCodexHooksFeatureFlags(content) {
1126
+ const lines = splitTomlLines(content);
1127
+ const firstHeaderIdx = lines.findIndex((line) => parseTomlTableHeader(line.text));
1128
+ let changed = false;
1129
+ const kept = lines.filter((line, index) => {
1130
+ if (firstHeaderIdx !== -1 && index >= firstHeaderIdx) return true;
1131
+ if (!parseCodexHooksFeatureAssignment(line.text)) return true;
1132
+ changed = true;
1133
+ return false;
1134
+ });
1135
+ return { content: changed ? joinTomlLines(kept) : content, changed };
1136
+ }
1137
+
1138
+ function getCodexFeaturesSection(content) {
1139
+ return getTomlTableSections(content)
1140
+ .find((section) => !section.array && section.path === CODEX_FEATURES_TABLE);
1141
+ }
1142
+
1143
+ function rewriteRootDottedCodexHooksFeature(content) {
1144
+ const lines = splitTomlLines(content);
1145
+ const firstHeaderIdx = lines.findIndex((line) => parseTomlTableHeader(line.text));
1146
+ let found = false;
1147
+ let changed = false;
1148
+ const kept = [];
1149
+
1150
+ for (let i = 0; i < lines.length; i += 1) {
1151
+ const line = lines[i];
1152
+ if (firstHeaderIdx !== -1 && i >= firstHeaderIdx) {
1153
+ kept.push(line);
1154
+ continue;
1155
+ }
1156
+
1157
+ if (!parseRootDottedCodexHooksFeatureAssignment(line.text)) {
1158
+ kept.push(line);
1159
+ continue;
1160
+ }
1161
+
1162
+ if (!found) {
1163
+ found = true;
1164
+ const rendered = renderRootDottedCodexHooksFeatureAssignment(line.text);
1165
+ if (rendered !== line.text) changed = true;
1166
+ kept.push({ ...line, text: rendered });
1167
+ } else {
1168
+ changed = true;
1169
+ }
1170
+ }
1171
+
1172
+ return { content: changed ? joinTomlLines(kept) : content, found, changed };
1173
+ }
1174
+
1175
+ function stripFvsManagedCodexHooksFeature(content) {
1176
+ // Self-heal v2.0.1 installs, which wrote the now-invalid root `hooks = true`.
1177
+ let cleaned = removeRootCodexHooksFeatureFlags(content).content;
1178
+ const featuresSection = getCodexFeaturesSection(cleaned);
1179
+ if (!featuresSection) return cleaned;
1180
+
1181
+ const body = cleaned.slice(featuresSection.headerEnd, featuresSection.end);
1182
+ const lines = splitTomlLines(body);
1183
+ const kept = [];
1184
+ let removed = false;
1185
+
1186
+ for (let i = 0; i < lines.length; i += 1) {
1187
+ const line = lines[i];
1188
+ if (line.text.trim() === FVS_CODEX_HOOKS_FEATURE_COMMENT) {
1189
+ removed = true;
1190
+ const next = lines[i + 1];
1191
+ if (next && parseCodexHooksFeatureAssignment(next.text)) {
1192
+ i += 1;
1193
+ }
1194
+ continue;
1195
+ }
1196
+ kept.push(line);
1197
+ }
1198
+
1199
+ if (!removed) return cleaned;
1200
+
1201
+ const nextBody = joinTomlLines(kept);
1202
+ const hasRemainingContent = nextBody.split(/\r?\n/).some((line) => line.trim() !== '');
1203
+ if (!hasRemainingContent) {
1204
+ return cleaned.slice(0, featuresSection.start) + cleaned.slice(featuresSection.end);
1205
+ }
1206
+
1207
+ return cleaned.slice(0, featuresSection.headerEnd) + nextBody + cleaned.slice(featuresSection.end);
1208
+ }
1209
+
1086
1210
  /**
1087
1211
  * Strip FVS sections from Codex config.toml content.
1088
1212
  *
@@ -1129,21 +1253,11 @@ function stripFvsFromCodexConfig(content) {
1129
1253
  cleaned = before + after;
1130
1254
  }
1131
1255
 
1132
- // Remove the FVS-owned root-level hooks feature flag (canonical + legacy).
1133
- // The flag is inserted by ensureCodexHooksFeature between the FVS marker and
1134
- // the first FVS table, so once the marker and tables are gone it is left
1135
- // orphaned. Only strip it when the FVS marker was present — a flag in a
1136
- // config that FVS never marked is the user's and must be preserved.
1256
+ // Remove the FVS-owned hooks feature gate. v2.0.1 emitted an invalid root
1257
+ // `hooks = true`; current installs mark their `[features].hooks` insertion so
1258
+ // uninstall can remove only the FVS-owned line and preserve user/GSD flags.
1137
1259
  if (hadFvsMarker) {
1138
- const flagLines = cleaned.split(/\r?\n/);
1139
- const firstHeaderIdx = flagLines.findIndex((l) => /^\s*\[/.test(l));
1140
- cleaned = flagLines
1141
- .filter((line, i) => {
1142
- if (firstHeaderIdx !== -1 && i >= firstHeaderIdx) return true;
1143
- const m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*true\s*$/);
1144
- return !(m && CODEX_HOOKS_FEATURE_ALL_KEYS.includes(m[1]));
1145
- })
1146
- .join('\n');
1260
+ cleaned = stripFvsManagedCodexHooksFeature(cleaned);
1147
1261
  }
1148
1262
 
1149
1263
  // Collapse runs of blank lines the removals may have left behind, then
@@ -1165,9 +1279,9 @@ function stripFvsFromCodexConfig(content) {
1165
1279
  *
1166
1280
  * On reinstall this first strips any previously-emitted FVS tables via the
1167
1281
  * TOML-aware strip (so foreign user/GSD tables survive verbatim and stale FVS
1168
- * tables are removed), then appends the freshly-generated FVS block. The
1169
- * FVS-emitted block carries no `[features]`/`multi_agent` keys, so none are
1170
- * injected into the user's config.
1282
+ * tables are removed), then appends the freshly-generated FVS agent block. The
1283
+ * hook feature gate is handled separately by ensureCodexHooksFeature so it can
1284
+ * coexist with user/GSD `[features]` settings.
1171
1285
  */
1172
1286
  function mergeCodexConfig(configPath, fvsBlock) {
1173
1287
  // Case 1: No config.toml -- create fresh.
@@ -1539,62 +1653,103 @@ function removeCodexHooksJsonSessionStart(targetDir) {
1539
1653
  return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand: null });
1540
1654
  }
1541
1655
 
1542
- // Ensure the Codex hooks feature flag is present and set true under the
1543
- // canonical key. If the file already enables the flag under the legacy key,
1544
- // rewrite that line to the canonical key (migrate forward) rather than adding a
1545
- // duplicate. Returns { content, changed }.
1656
+ // Ensure the Codex hooks feature flag is present as `[features].hooks = true`.
1657
+ // Older FVS versions wrote root-level `hooks = true`, which current Codex parses
1658
+ // as the hooks config table and rejects; remove that stale shape while inserting
1659
+ // the valid feature flag.
1546
1660
  function ensureCodexHooksFeature(configContent) {
1547
- const content = typeof configContent === 'string' ? configContent : '';
1661
+ const original = typeof configContent === 'string' ? configContent : '';
1662
+ const rootStripped = removeRootCodexHooksFeatureFlags(original);
1663
+ let content = rootStripped.content;
1548
1664
  const eol = content ? detectLineEnding(content) : '\n';
1549
- const lines = content.length ? content.split(/\r?\n/) : [];
1550
1665
 
1551
- let canonicalIdx = -1;
1552
- let legacyIdx = -1;
1553
- for (let i = 0; i < lines.length; i += 1) {
1554
- const m = lines[i].match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.+?)\s*$/);
1555
- if (!m) continue;
1556
- const key = m[1];
1557
- if (key === CODEX_HOOKS_FEATURE_KEY && canonicalIdx === -1) canonicalIdx = i;
1558
- else if (CODEX_HOOKS_FEATURE_LEGACY_KEYS.includes(key) && legacyIdx === -1) legacyIdx = i;
1559
- }
1560
-
1561
- // Only operate on root-level flag lines (not inside a [table]); a flag line
1562
- // appearing after the first [section] header is treated as section-scoped and
1563
- // left alone — FVS emits the flag at the document root before any table.
1564
- const firstHeaderIdx = lines.findIndex((l) => /^\s*\[/.test(l));
1565
- const isRootLevel = (idx) => idx !== -1 && (firstHeaderIdx === -1 || idx < firstHeaderIdx);
1566
-
1567
- if (isRootLevel(canonicalIdx)) {
1568
- if (/=\s*true\s*$/.test(lines[canonicalIdx])) return { content, changed: false };
1569
- lines[canonicalIdx] = `${CODEX_HOOKS_FEATURE_KEY} = true`;
1570
- return { content: lines.join(eol), changed: true };
1571
- }
1572
- if (isRootLevel(legacyIdx)) {
1573
- lines[legacyIdx] = `${CODEX_HOOKS_FEATURE_KEY} = true`;
1574
- return { content: lines.join(eol), changed: true };
1575
- }
1576
-
1577
- // Insert a fresh canonical flag line at the document root, above any table.
1578
- const insertLine = `${CODEX_HOOKS_FEATURE_KEY} = true`;
1579
- if (firstHeaderIdx === -1) {
1580
- const base = content.trimEnd();
1581
- const next = base ? `${insertLine}${eol}${base}${eol}` : `${insertLine}${eol}`;
1666
+ const featuresSection = getCodexFeaturesSection(content);
1667
+ if (featuresSection) {
1668
+ const body = content.slice(featuresSection.headerEnd, featuresSection.end);
1669
+ const lines = splitTomlLines(body);
1670
+ let found = false;
1671
+ let changed = rootStripped.changed;
1672
+ const kept = [];
1673
+
1674
+ for (const line of lines) {
1675
+ const parsed = parseCodexHooksFeatureAssignment(line.text);
1676
+ if (!parsed) {
1677
+ kept.push(line);
1678
+ continue;
1679
+ }
1680
+
1681
+ if (!found) {
1682
+ found = true;
1683
+ const rendered = renderCodexHooksFeatureAssignment(line.text);
1684
+ if (rendered !== line.text) changed = true;
1685
+ kept.push({ ...line, text: rendered });
1686
+ } else {
1687
+ changed = true;
1688
+ }
1689
+ }
1690
+
1691
+ if (found) {
1692
+ const next = content.slice(0, featuresSection.headerEnd) +
1693
+ joinTomlLines(kept) +
1694
+ content.slice(featuresSection.end);
1695
+ return { content: next, changed: changed || next !== original };
1696
+ }
1697
+
1698
+ const needsLineBreak = featuresSection.end > 0 &&
1699
+ !content.slice(0, featuresSection.end).endsWith('\n') &&
1700
+ !content.slice(0, featuresSection.end).endsWith('\r\n');
1701
+ const insertText = `${needsLineBreak ? eol : ''}${FVS_CODEX_HOOKS_FEATURE_COMMENT}${eol}${CODEX_HOOKS_FEATURE_KEY} = true${eol}`;
1702
+ const next = content.slice(0, featuresSection.end) + insertText + content.slice(featuresSection.end);
1703
+ return { content: next, changed: true };
1704
+ }
1705
+
1706
+ const dotted = rewriteRootDottedCodexHooksFeature(content);
1707
+ if (dotted.found) {
1708
+ return { content: dotted.content, changed: rootStripped.changed || dotted.changed || dotted.content !== original };
1709
+ }
1710
+
1711
+ const featuresBlock = [
1712
+ `[${CODEX_FEATURES_TABLE}]`,
1713
+ FVS_CODEX_HOOKS_FEATURE_COMMENT,
1714
+ `${CODEX_HOOKS_FEATURE_KEY} = true`,
1715
+ '',
1716
+ ].join(eol);
1717
+
1718
+ if (!content) return { content: featuresBlock, changed: true };
1719
+
1720
+ const firstTable = getTomlTableSections(content)[0];
1721
+ if (firstTable) {
1722
+ const before = content.slice(0, firstTable.start);
1723
+ const after = content.slice(firstTable.start);
1724
+ const needsGap = before.length > 0 && !before.endsWith(eol + eol);
1725
+ const next = before + (needsGap ? eol : '') + featuresBlock + eol + after;
1582
1726
  return { content: next, changed: true };
1583
1727
  }
1584
- lines.splice(firstHeaderIdx, 0, insertLine, '');
1585
- return { content: lines.join(eol), changed: true };
1728
+
1729
+ const base = content.trimEnd();
1730
+ const needsGap = base.length > 0;
1731
+ const next = `${base}${needsGap ? eol + eol : ''}${featuresBlock}`;
1732
+ return { content: next, changed: true };
1586
1733
  }
1587
1734
 
1588
1735
  function hasEnabledCodexHooksFeature(configContent) {
1589
1736
  if (typeof configContent !== 'string') return false;
1590
- const firstHeaderIdx = configContent.split(/\r?\n/).findIndex((l) => /^\s*\[/.test(l));
1591
- const lines = configContent.split(/\r?\n/);
1592
- for (let i = 0; i < lines.length; i += 1) {
1593
- if (firstHeaderIdx !== -1 && i >= firstHeaderIdx) break;
1594
- const m = lines[i].match(/^\s*([A-Za-z0-9_]+)\s*=\s*true\s*$/);
1595
- if (m && CODEX_HOOKS_FEATURE_ALL_KEYS.includes(m[1])) return true;
1737
+ const featuresSection = getCodexFeaturesSection(configContent);
1738
+ if (featuresSection) {
1739
+ const body = configContent.slice(featuresSection.headerEnd, featuresSection.end);
1740
+ for (const line of splitTomlLines(body)) {
1741
+ const parsed = parseCodexHooksFeatureAssignment(line.text);
1742
+ if (parsed && parsed.value === 'true') return true;
1743
+ }
1596
1744
  }
1597
- return false;
1745
+
1746
+ const lines = splitTomlLines(configContent);
1747
+ const firstHeaderIdx = lines.findIndex((line) => parseTomlTableHeader(line.text));
1748
+ return lines.some((line, index) => {
1749
+ if (firstHeaderIdx !== -1 && index >= firstHeaderIdx) return false;
1750
+ const parsed = parseRootDottedCodexHooksFeatureAssignment(line.text);
1751
+ return Boolean(parsed && parsed.value === 'true');
1752
+ });
1598
1753
  }
1599
1754
 
1600
1755
  /**
@@ -23,18 +23,25 @@ Commands are grouped into five bundles. Each bundle has a router command (e.g. `
23
23
 
24
24
  ## Quick Start
25
25
 
26
- 1. `/fvs:map-code` - Analyze project, build dependency graph
27
- 2. `/fvs:fc-plan` - Select verification targets
28
- 3. `/fvs:lean-specify <function>` - Generate spec with sorry
29
- 4. `/fvs:lean-verify <spec_path>` - Attempt proof interactively
30
- 5. `/fvs:lean-refactor <spec_path>` - Golf and clean up verified proofs
31
- 6. `/fvs:lean-formalise` - Formalise paper/math content into Lean specs
26
+ **From a Rust crate functional-correctness track:**
27
+ 1. `/fvs:aeneas-extract <path>` - Extract Rust → Lean 4 via the bounded Aeneas repair loop
28
+ 2. `/fvs:map-code` - Analyze project, build dependency graph
29
+ 3. `/fvs:fc-plan` - Select verification targets
30
+ 4. `/fvs:lean-specify <function>` - Generate spec with sorry
31
+ 5. `/fvs:lean-verify <spec_path>` - Attempt proof interactively
32
+ 6. `/fvs:lean-refactor <spec_path>` - Golf and clean up verified proofs
33
+ 7. `/fvs:trust-audit <target>` - Audit the sorry/axiom trust surface
34
+
35
+ **From a paper — paper track:**
36
+ - `/fvs:lean-formalise` - One-shot formalisation of paper/math content, or
37
+ - `/fvs:crypto-plan <topic>` - Start the multi-iteration crypto loop (see Formalise below)
32
38
 
33
39
  ## Core Workflow
34
40
 
35
41
  ```
36
- /fvs:map-code → /fvs:fc-plan → /fvs:lean-specify → /fvs:lean-verify → /fvs:lean-refactor → repeat
37
- Paper track: /fvs:lean-formalise → /fvs:lean-verify → /fvs:lean-refactor
42
+ Code track: /fvs:aeneas-extract → /fvs:map-code → /fvs:fc-plan → /fvs:lean-specify → /fvs:lean-verify → /fvs:lean-refactor → /fvs:trust-audit
43
+ Paper track: /fvs:lean-formalise → /fvs:lean-verify → /fvs:lean-refactor
44
+ Crypto loop: /fvs:crypto-plan → /fvs:crypto-execute → /fvs:crypto-eval → /fvs:crypto-followup → repeat
38
45
  ```
39
46
 
40
47
  ## Bundles
@@ -188,24 +195,28 @@ Refactor, simplify, and decompose verified Lean proofs while preserving compilat
188
195
 
189
196
  Usage: `/fvs:lean-refactor Specs/Backend/Field/Sub.lean`
190
197
 
191
- The crypto formalisation loop (plan -> execute -> eval -> follow-up) is a topic-based, multi-iteration alternative to the one-shot `lean-formalise`. Its four stages share the artifact tree under `fv-plans/<topic>/` and the loop is restartable from those records. The loop is runtime-neutral (it runs as a same-runtime pair by default).
198
+ The crypto formalisation loop (plan -> execute -> eval -> follow-up) is a topic-based, multi-iteration alternative to the one-shot `lean-formalise`. Its four stages share the artifact tree under `fv-plans/<topic>/` and the loop is restartable from those records.
192
199
 
193
- **`/fvs:crypto-plan <topic>`**
200
+ **Single- vs dual-runtime (`--codex`).** By default the loop is *single-runtime*: the high-effort thinking (planning, adversarial eval, follow-up) is done by the in-runtime `fvs-crypto-thinker`. The three *thinking* stages — `crypto-plan`, `crypto-eval`, `crypto-followup` — also accept `--codex`, which hands that stage's thinking to an independent **Codex CLI** thinker instead. That makes the loop *dual-runtime*: the adversarial planner/evaluator runs on a different engine than the executor, reducing correlated blind spots. `crypto-execute` is the runtime-neutral executor and takes no `--codex`. Pass `--codex` without the Codex CLI installed and the stage stops with an install hint (never a silent fallback) — re-run without it to stay single-runtime.
201
+
202
+ **`/fvs:crypto-plan <topic> [nN] [--codex]`**
194
203
  Author the next bounded, runtime-neutral executor plan for a topic, grounded in the paper via the NotebookLM knowledge base (answers cached under `sources/`).
195
204
 
196
205
  Usage: `/fvs:crypto-plan "CKA from KEM"`
206
+ Usage: `/fvs:crypto-plan "CKA from KEM" --codex` # hand the planning think-step to Codex
197
207
 
198
208
  **`/fvs:crypto-execute <topic> nN`**
199
- Run the current iteration's bounded plan under the green-build guard; a failed proof triggers a short interactive redirect early.
209
+ Run the current iteration's bounded plan under the green-build guard; a failed proof triggers a short interactive redirect early. (Executor stage — takes no `--codex`.)
200
210
 
201
211
  Usage: `/fvs:crypto-execute "CKA from KEM" n1`
202
212
 
203
- **`/fvs:crypto-eval <topic> nN`**
213
+ **`/fvs:crypto-eval <topic> nN [--codex]`**
204
214
  Adversarially evaluate the iteration; ends in exactly one decision (ACCEPT / FOLLOWUP / HUMAN_RULING / BLOCKED).
205
215
 
206
216
  Usage: `/fvs:crypto-eval "CKA from KEM" n1`
217
+ Usage: `/fvs:crypto-eval "CKA from KEM" n1 --codex`
207
218
 
208
- **`/fvs:crypto-followup <topic> nN`**
219
+ **`/fvs:crypto-followup <topic> nN [--codex]`**
209
220
  Convert eval findings into the next bounded follow-up plan; HALTs for a human ruling on a modeling decision.
210
221
 
211
222
  Usage: `/fvs:crypto-followup "CKA from KEM" n1`
package/fv-skills/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.0.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fv-skills-baif",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Formal verification skills for Claude Code, Codex, OpenCode, and Gemini. Rust -> Lean 4 via Aeneas.",
5
5
  "bin": {
6
6
  "fv-skills-baif": "bin/install.js"