fv-skills-baif 2.1.2 → 2.2.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/CHANGELOG.md +15 -0
- package/README.md +41 -3
- package/bin/install.js +24 -10
- package/fv-skills/VERSION +1 -1
- package/package.json +4 -2
- package/scripts/build-plugin.cjs +326 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ 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.2.0] - 2026-08-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- FVS is now packaged as a deterministic dual-runtime plugin payload for Claude Code and Codex,
|
|
11
|
+
with 27 shared skills, 13 Claude agents, portable plugin-root paths, and runtime-specific update
|
|
12
|
+
guidance.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Marketplace distribution now uses the organization-level
|
|
16
|
+
`Beneficial-AI-Foundation/plugins` catalog and the `beneficial-ai-foundation` marketplace ID.
|
|
17
|
+
The catalog pins the independently released FVS payload by immutable tag and full commit SHA;
|
|
18
|
+
the FVS repository no longer publishes its own repository-level marketplace catalogs.
|
|
19
|
+
- Plugin installation documentation now distinguishes the BAIF Git catalog from OpenAI's separate
|
|
20
|
+
universal public Plugins Directory.
|
|
21
|
+
|
|
7
22
|
## [2.1.2] - 2026-08-12
|
|
8
23
|
|
|
9
24
|
### Added
|
package/README.md
CHANGED
|
@@ -42,6 +42,40 @@ Framework-specific commands (currently Lean) handle the actual specification and
|
|
|
42
42
|
|
|
43
43
|
## Getting Started
|
|
44
44
|
|
|
45
|
+
### Plugin marketplace (Claude Code and Codex)
|
|
46
|
+
|
|
47
|
+
The Beneficial AI Foundation maintains one catalog for FVS and future BAIF plugins. Add the catalog
|
|
48
|
+
once, then install FVS from its `beneficial-ai-foundation` marketplace identity:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Claude Code
|
|
52
|
+
claude plugin marketplace add Beneficial-AI-Foundation/plugins
|
|
53
|
+
claude plugin install fvs@beneficial-ai-foundation
|
|
54
|
+
|
|
55
|
+
# Codex
|
|
56
|
+
codex plugin marketplace add Beneficial-AI-Foundation/plugins
|
|
57
|
+
codex plugin add fvs@beneficial-ai-foundation
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Start a new session after installation. Run `/fvs:help` in Claude Code or mention `$fvs:help` in
|
|
61
|
+
Codex. To refresh an existing install, update the catalog and then update or reinstall FVS:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Claude Code
|
|
65
|
+
claude plugin marketplace update beneficial-ai-foundation
|
|
66
|
+
claude plugin update fvs@beneficial-ai-foundation
|
|
67
|
+
|
|
68
|
+
# Codex
|
|
69
|
+
codex plugin marketplace upgrade beneficial-ai-foundation
|
|
70
|
+
codex plugin add fvs@beneficial-ai-foundation
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The BAIF Git catalog is a versioned distribution source that can list multiple independently
|
|
74
|
+
released plugins. It is separate from OpenAI's universal public Plugins Directory, which has its
|
|
75
|
+
own per-plugin submission process.
|
|
76
|
+
|
|
77
|
+
### npm installer (all runtimes)
|
|
78
|
+
|
|
45
79
|
```bash
|
|
46
80
|
npx fv-skills-baif
|
|
47
81
|
```
|
|
@@ -50,7 +84,8 @@ The installer prompts you to choose:
|
|
|
50
84
|
1. **Runtime** — Claude Code, OpenCode, Gemini, or all
|
|
51
85
|
2. **Location** — Global (all projects) or local (current project only)
|
|
52
86
|
|
|
53
|
-
Verify with `/fvs:help` inside your chosen runtime.
|
|
87
|
+
Verify with `/fvs:help` inside your chosen runtime. The npm installer remains the distribution path
|
|
88
|
+
for OpenCode and Gemini CLI, and is also available for Claude Code and Codex.
|
|
54
89
|
|
|
55
90
|
### Prerequisites (Lean 4 / Aeneas)
|
|
56
91
|
|
|
@@ -67,6 +102,9 @@ For enhanced Lean 4 proof development with LLMs, install the [lean-lsp-mcp](http
|
|
|
67
102
|
|
|
68
103
|
### Staying Updated
|
|
69
104
|
|
|
105
|
+
For a marketplace install, invoke `/fvs:update` in Claude Code or `$fvs:update` in Codex. For an npm
|
|
106
|
+
install, run:
|
|
107
|
+
|
|
70
108
|
```bash
|
|
71
109
|
npx fv-skills-baif@latest
|
|
72
110
|
```
|
|
@@ -171,8 +209,8 @@ secrets, raw transcripts, ephemeral error dumps, unsupported guesses, or inferre
|
|
|
171
209
|
| Command | Description |
|
|
172
210
|
|---------|-------------|
|
|
173
211
|
| `/fvs:help` | Show available FVS commands and usage guide |
|
|
174
|
-
| `/fvs:update` |
|
|
175
|
-
| `/fvs:reapply-patches` |
|
|
212
|
+
| `/fvs:update` | Update FVS through the current installation channel |
|
|
213
|
+
| `/fvs:reapply-patches` | Preserve customizations across FVS updates (patches for npm installs; fork guidance for plugin installs) |
|
|
176
214
|
| `/fvs:kb-setup` | Set up NotebookLM knowledge base integration (venv, auth, config) |
|
|
177
215
|
|
|
178
216
|
---
|
package/bin/install.js
CHANGED
|
@@ -751,9 +751,25 @@ function convertClaudeToCodexMarkdown(content) {
|
|
|
751
751
|
* questions and waits rather than silently picking a default and writing
|
|
752
752
|
* artifacts.
|
|
753
753
|
*/
|
|
754
|
-
function getCodexSkillAdapterHeader(skillName) {
|
|
755
|
-
const
|
|
754
|
+
function getCodexSkillAdapterHeader(skillName, options = {}) {
|
|
755
|
+
const pluginName = options.pluginName || null;
|
|
756
|
+
const invocation = pluginName ? `$${pluginName}:${skillName}` : `$${skillName}`;
|
|
757
|
+
const pluginCompatibility = pluginName
|
|
758
|
+
? `\n## D. Shared Plugin Syntax\n- This file is shared with Claude Code. On Codex, interpret \`/${pluginName}:<name>\` references as \`$${pluginName}:<name>\`.\n- Treat \`$ARGUMENTS\` in the shared body as \`{{FVS_ARGS}}\`.\n- \`\${CLAUDE_PLUGIN_ROOT}\` is the installed plugin root. If a host leaves that token unexpanded, resolve the plugin root as two directories above this SKILL.md.\n`
|
|
759
|
+
: '';
|
|
760
|
+
const typedDispatchQualification = pluginName
|
|
761
|
+
? `\nEven when \`agent_type\` is present, typed dispatch is available only if the exact requested FVS type is advertised by the tool schema or a confirmed runtime registry. Codex marketplace plugins do not register the bundled Claude agent Markdown as typed Codex agents, so otherwise use the bundled-agent workaround below.\n`
|
|
762
|
+
: '';
|
|
763
|
+
const typedModelNote = pluginName
|
|
764
|
+
? 'The marketplace plugin does not install Codex agent TOML. Use this mapping only when the exact FVS agent type is registered independently; otherwise use the bundled-agent workaround.'
|
|
765
|
+
: 'FVS bakes each agent\'s reasoning effort into its `.toml` at install time and the model is inherited from the user\'s Codex configuration.';
|
|
766
|
+
const fallbackSteps = pluginName
|
|
767
|
+
? `1. Read \`\${CLAUDE_PLUGIN_ROOT}/agents/<agent-name>.md\` and extract its instructions. If the token is still literal, resolve the path from this SKILL.md as described above.\n2. Spawn a generic/default agent and inject those instructions as a role preamble before the task prompt.\n3. Label results clearly as \"generic-agent workaround\" so the user knows typed guarantees are not in effect.\n4. Where typed dispatch is mandatory for correctness, fail closed and report the schema limitation rather than silently degrading.`
|
|
768
|
+
: `1. Resolve your active Codex config root (the directory containing your \`config.toml\`), then read \`agents/<agent-name>.toml\` relative to that root to extract the agent's instructions.\n2. Inject those instructions as a role-preamble into a generic \`spawn_agent(message=...)\` call.\n3. Label results clearly as \"generic-agent workaround\" so the user knows typed guarantees are not in effect.\n4. Where typed dispatch is mandatory for correctness, fail closed and report the schema limitation rather than silently degrading.`;
|
|
756
769
|
return `<codex_skill_adapter>
|
|
770
|
+
This block applies only when this shared skill runs in Codex. Claude Code must ignore it and use the
|
|
771
|
+
shared workflow body with its native slash-command, question, and subagent semantics.
|
|
772
|
+
|
|
757
773
|
## A. Skill Invocation
|
|
758
774
|
- This skill is invoked by mentioning \`${invocation}\`.
|
|
759
775
|
- Treat all user text after \`${invocation}\` as \`{{FVS_ARGS}}\`.
|
|
@@ -789,26 +805,24 @@ FVS workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collabo
|
|
|
789
805
|
- **agent_type-capable schema:** \`spawn_agent\` accepts \`agent_type\`, \`message\`, \`reasoning_effort\`, \`fork_context\`, etc. — typed FVS agent dispatch is available.
|
|
790
806
|
- **Generic schema:** \`spawn_agent\` accepts only \`message\`, \`items\`, \`fork_context\` — there is **no \`agent_type\` field**. Typed FVS agent dispatch is unavailable in this session.
|
|
791
807
|
|
|
792
|
-
Before spawning, inspect the \`spawn_agent\` tool's visible parameter schema to determine which form is active
|
|
808
|
+
Before spawning, inspect the \`spawn_agent\` tool's visible parameter schema to determine which form is active.${typedDispatchQualification}
|
|
793
809
|
|
|
794
810
|
Typed mapping (agent_type-capable schema only):
|
|
795
811
|
- \`Task(subagent_type="X", prompt="Y")\` -> \`spawn_agent(agent_type="X", message="Y")\`
|
|
796
|
-
- \`Task(model="...")\` -> omit. \`spawn_agent\` has no inline \`model\` parameter
|
|
812
|
+
- \`Task(model="...")\` -> omit. \`spawn_agent\` has no inline \`model\` parameter. ${typedModelNote}
|
|
797
813
|
- \`fork_context: false\` by default -- FVS agents load their own context via \`<files_to_read>\` blocks.
|
|
798
814
|
|
|
799
815
|
Generic-agent workaround (schema with NO agent_type field):
|
|
800
816
|
When only the generic schema is available, typed FVS agent dispatch (\`fvs-researcher\`, \`fvs-executor\`, etc.) is NOT possible. This workaround is NOT equivalent to typed execution — FVS agents carry verification-aware prompts and sandbox settings a generic subagent lacks. Fallback:
|
|
801
|
-
|
|
802
|
-
2. Inject those instructions as a role-preamble into a generic \`spawn_agent(message=...)\` call.
|
|
803
|
-
3. Label results clearly as "generic-agent workaround" so the user knows typed guarantees are not in effect.
|
|
804
|
-
4. Where typed dispatch is mandatory for correctness, fail closed and report the schema limitation rather than silently degrading.
|
|
817
|
+
${fallbackSteps}
|
|
805
818
|
|
|
806
819
|
Parallel fan-out:
|
|
807
|
-
- Spawn multiple agents -> collect agent IDs -> \`
|
|
820
|
+
- Spawn multiple agents -> collect agent IDs -> call \`wait_agent(timeout_ms=...)\` (or the runtime's visible wait equivalent) until each completes
|
|
808
821
|
|
|
809
822
|
Result parsing:
|
|
810
823
|
- Look for structured markers in agent output: \`CHECKPOINT\`, \`PLAN COMPLETE\`, \`SUMMARY\`, etc.
|
|
811
|
-
-
|
|
824
|
+
- If the runtime exposes an agent cleanup or close tool, use it after collecting each result
|
|
825
|
+
${pluginCompatibility}
|
|
812
826
|
</codex_skill_adapter>`;
|
|
813
827
|
}
|
|
814
828
|
|
package/fv-skills/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fv-skills-baif",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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"
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"test": "node scripts/run-tests.cjs",
|
|
45
45
|
"build:hooks": "node scripts/build-hooks.js",
|
|
46
|
-
"
|
|
46
|
+
"build:plugin": "node scripts/build-plugin.cjs",
|
|
47
|
+
"check:plugin": "node scripts/build-plugin.cjs --check",
|
|
48
|
+
"prepublishOnly": "npm run build:hooks && npm run check:plugin"
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
extractFrontmatterAndBody,
|
|
11
|
+
extractFrontmatterField,
|
|
12
|
+
getCodexSkillAdapterHeader,
|
|
13
|
+
} = require('../bin/install.js');
|
|
14
|
+
|
|
15
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
16
|
+
const COMMITTED_PLUGIN_ROOT = path.join(ROOT, 'plugins', 'fvs');
|
|
17
|
+
const PACKAGE_JSON = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
18
|
+
const VERSION = PACKAGE_JSON.version;
|
|
19
|
+
const REPOSITORY_URL = 'https://github.com/Beneficial-AI-Foundation/formal-verification-skills';
|
|
20
|
+
const MARKETPLACE_ID = 'beneficial-ai-foundation';
|
|
21
|
+
const PLUGIN_DESCRIPTION =
|
|
22
|
+
'Formal verification workflows for Lean 4, including Rust extraction through Aeneas and paper or cryptography formalisation.';
|
|
23
|
+
const SCRIPT_FILES = [
|
|
24
|
+
'fvs-codex-think.mjs',
|
|
25
|
+
'fvs-kb-query.py',
|
|
26
|
+
'fvs-lean-style-check.mjs',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function assertSafePluginRoot(pluginRoot) {
|
|
30
|
+
const resolved = path.resolve(pluginRoot);
|
|
31
|
+
const expectedName = path.join('plugins', 'fvs');
|
|
32
|
+
if (resolved === ROOT || resolved === path.parse(resolved).root) {
|
|
33
|
+
throw new Error(`Refusing to rebuild unsafe plugin path: ${resolved}`);
|
|
34
|
+
}
|
|
35
|
+
if (resolved === COMMITTED_PLUGIN_ROOT) return;
|
|
36
|
+
if (!resolved.endsWith(`${path.sep}fvs`)) {
|
|
37
|
+
throw new Error(`Temporary plugin output must end in ${JSON.stringify(expectedName)} or /fvs`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeText(filePath, content) {
|
|
42
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
43
|
+
fs.writeFileSync(filePath, content.endsWith('\n') ? content : `${content}\n`, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeJson(filePath, payload) {
|
|
47
|
+
writeText(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function copyFile(source, destination) {
|
|
51
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
52
|
+
fs.copyFileSync(source, destination);
|
|
53
|
+
fs.chmodSync(destination, fs.statSync(source).mode);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function copyTextFile(source, destination) {
|
|
57
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
58
|
+
writeText(destination, portablePluginPaths(content));
|
|
59
|
+
fs.chmodSync(destination, fs.statSync(source).mode);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function portablePluginPaths(content) {
|
|
63
|
+
return content
|
|
64
|
+
.replace(/\$HOME\/\.claude\//g, '${CLAUDE_PLUGIN_ROOT}/')
|
|
65
|
+
.replace(/~\/\.claude\//g, '${CLAUDE_PLUGIN_ROOT}/')
|
|
66
|
+
.replace(/[ \t]+$/gm, '');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function copyTextTree(sourceRoot, destinationRoot, options = {}, relativeRoot = '') {
|
|
70
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
71
|
+
const source = path.join(sourceRoot, entry.name);
|
|
72
|
+
const destination = path.join(destinationRoot, entry.name);
|
|
73
|
+
const relative = relativeRoot ? `${relativeRoot}/${entry.name}` : entry.name;
|
|
74
|
+
if (options.exclude?.has(relative)) continue;
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
copyTextTree(source, destination, options, relative);
|
|
77
|
+
} else if (entry.isFile()) {
|
|
78
|
+
copyTextFile(source, destination);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderPluginFrontmatter(frontmatter, skillName) {
|
|
84
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
85
|
+
let replacedName = false;
|
|
86
|
+
const rendered = [];
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (/^name:\s*/.test(line)) {
|
|
89
|
+
rendered.push(`name: ${skillName}`);
|
|
90
|
+
replacedName = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// `requires` is an FVS router registry used by the npm-installed command
|
|
94
|
+
// surface, not a portable Agent Skills frontmatter field.
|
|
95
|
+
if (/^requires:\s*/.test(line)) continue;
|
|
96
|
+
rendered.push(line);
|
|
97
|
+
}
|
|
98
|
+
if (!replacedName) rendered.unshift(`name: ${skillName}`);
|
|
99
|
+
return rendered.join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function pluginRuntimeHeader() {
|
|
103
|
+
return `<plugin_runtime>\n- FVS is installed at \`\${CLAUDE_PLUGIN_ROOT}\`; hosts expand this placeholder in plugin skill content.\n- Resolve every bundled workflow, reference, template, script, and agent beneath that root.\n- When executing a shell snippet, quote the resolved plugin-root path even if an inherited example omits quotes.\n- Never write state into the plugin cache. Project state belongs under the user's current project (normally \`.formalising/\`).\n</plugin_runtime>`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderPluginUpdateSkill() {
|
|
107
|
+
return `---
|
|
108
|
+
name: update
|
|
109
|
+
description: Update the FVS marketplace plugin to its latest published version
|
|
110
|
+
allowed-tools: Bash, AskUserQuestion
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
${pluginRuntimeHeader()}
|
|
114
|
+
|
|
115
|
+
${getCodexSkillAdapterHeader('update', { pluginName: 'fvs' })}
|
|
116
|
+
|
|
117
|
+
<objective>
|
|
118
|
+
Refresh the installed FVS plugin from the Beneficial AI Foundation marketplace. Use only the command
|
|
119
|
+
for the host running this skill, obtain confirmation before changing the installation, and remind
|
|
120
|
+
the user to start a new session afterward.
|
|
121
|
+
</objective>
|
|
122
|
+
|
|
123
|
+
<process>
|
|
124
|
+
1. Read the installed version from \`\${CLAUDE_PLUGIN_ROOT}/fv-skills/VERSION\`.
|
|
125
|
+
2. Tell the user which host-specific update will run and ask for confirmation.
|
|
126
|
+
3. On Claude Code, run:
|
|
127
|
+
|
|
128
|
+
\`claude plugin update fvs@${MARKETPLACE_ID}\`
|
|
129
|
+
|
|
130
|
+
4. On Codex, refresh the marketplace snapshot and reinstall from it:
|
|
131
|
+
|
|
132
|
+
\`codex plugin marketplace upgrade ${MARKETPLACE_ID}\`
|
|
133
|
+
\`codex plugin add fvs@${MARKETPLACE_ID}\`
|
|
134
|
+
|
|
135
|
+
5. Report the command output. Do not fall back to the npm installer: marketplace and npm installs
|
|
136
|
+
are separate distribution channels.
|
|
137
|
+
6. Ask the user to start a new Claude Code or Codex session so the refreshed skills are loaded.
|
|
138
|
+
</process>
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderPluginReapplySkill() {
|
|
143
|
+
return `---
|
|
144
|
+
name: reapply-patches
|
|
145
|
+
description: Explain how to preserve FVS customizations when using the marketplace plugin
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
${pluginRuntimeHeader()}
|
|
149
|
+
|
|
150
|
+
${getCodexSkillAdapterHeader('reapply-patches', { pluginName: 'fvs' })}
|
|
151
|
+
|
|
152
|
+
<objective>
|
|
153
|
+
Keep marketplace plugin installations immutable and direct custom FVS changes into a maintained
|
|
154
|
+
fork or a project-local skill override.
|
|
155
|
+
</objective>
|
|
156
|
+
|
|
157
|
+
<process>
|
|
158
|
+
Marketplace installs run from versioned caches, so the npm installer's \`fvs-local-patches\`
|
|
159
|
+
workflow does not apply. Never edit or merge files inside \`\${CLAUDE_PLUGIN_ROOT}\`.
|
|
160
|
+
|
|
161
|
+
If the user needs a persistent customization:
|
|
162
|
+
|
|
163
|
+
1. Fork \`Beneficial-AI-Foundation/formal-verification-skills\`.
|
|
164
|
+
2. Make the change in the canonical source files and run \`npm run build:plugin\`.
|
|
165
|
+
3. Add the fork as a marketplace and install \`fvs\` from that marketplace, or keep a narrowly
|
|
166
|
+
scoped project-local skill that overrides the published behavior.
|
|
167
|
+
4. Start a new session after installing the customized plugin.
|
|
168
|
+
</process>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderPluginSkill(sourcePath, skillName) {
|
|
173
|
+
if (skillName === 'update') return renderPluginUpdateSkill();
|
|
174
|
+
if (skillName === 'reapply-patches') return renderPluginReapplySkill();
|
|
175
|
+
|
|
176
|
+
const raw = fs.readFileSync(sourcePath, 'utf8');
|
|
177
|
+
const { frontmatter, body } = extractFrontmatterAndBody(raw);
|
|
178
|
+
if (!frontmatter) throw new Error(`Missing frontmatter: ${sourcePath}`);
|
|
179
|
+
const description = extractFrontmatterField(frontmatter, 'description');
|
|
180
|
+
if (!description) throw new Error(`Missing description: ${sourcePath}`);
|
|
181
|
+
|
|
182
|
+
let portableBody = portablePluginPaths(body);
|
|
183
|
+
if (skillName === 'help') {
|
|
184
|
+
portableBody = portableBody
|
|
185
|
+
.replace('- Runs `npx fv-skills-baif` to update', '- Refreshes FVS from the configured plugin marketplace')
|
|
186
|
+
.replace('- Run after `/fvs:update` if local patches were detected', '- Explains fork or project-local customization for immutable plugin installs');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return `---\n${renderPluginFrontmatter(frontmatter, skillName)}\n---\n\n${pluginRuntimeHeader()}\n\n${getCodexSkillAdapterHeader(skillName, { pluginName: 'fvs' })}\n\n${portableBody.trimStart()}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderClaudeManifest() {
|
|
193
|
+
return {
|
|
194
|
+
$schema: 'https://json.schemastore.org/claude-code-plugin-manifest.json',
|
|
195
|
+
name: 'fvs',
|
|
196
|
+
version: VERSION,
|
|
197
|
+
description: PLUGIN_DESCRIPTION,
|
|
198
|
+
author: { name: 'Beneficial AI Foundation' },
|
|
199
|
+
homepage: REPOSITORY_URL,
|
|
200
|
+
repository: REPOSITORY_URL,
|
|
201
|
+
license: 'MIT',
|
|
202
|
+
keywords: ['formal-verification', 'lean4', 'aeneas', 'rust', 'cryptography'],
|
|
203
|
+
skills: './skills/',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderCodexManifest() {
|
|
208
|
+
return {
|
|
209
|
+
name: 'fvs',
|
|
210
|
+
version: VERSION,
|
|
211
|
+
description: PLUGIN_DESCRIPTION,
|
|
212
|
+
author: {
|
|
213
|
+
name: 'Beneficial AI Foundation',
|
|
214
|
+
url: REPOSITORY_URL,
|
|
215
|
+
},
|
|
216
|
+
homepage: REPOSITORY_URL,
|
|
217
|
+
repository: REPOSITORY_URL,
|
|
218
|
+
license: 'MIT',
|
|
219
|
+
keywords: ['formal-verification', 'lean4', 'aeneas', 'rust', 'cryptography'],
|
|
220
|
+
skills: './skills/',
|
|
221
|
+
interface: {
|
|
222
|
+
displayName: 'Formal Verification Skills',
|
|
223
|
+
shortDescription: 'Lean 4 specification, proof, and audit workflows',
|
|
224
|
+
longDescription: PLUGIN_DESCRIPTION,
|
|
225
|
+
developerName: 'Beneficial AI Foundation',
|
|
226
|
+
category: 'Productivity',
|
|
227
|
+
capabilities: ['Interactive', 'Read', 'Write'],
|
|
228
|
+
websiteURL: REPOSITORY_URL,
|
|
229
|
+
defaultPrompt: [
|
|
230
|
+
'Map this Lean project and plan the next verification target.',
|
|
231
|
+
'Generate and prove a Lean specification for this function.',
|
|
232
|
+
'Audit the sorry and axiom trust surface of this Lean target.',
|
|
233
|
+
],
|
|
234
|
+
brandColor: '#F97316',
|
|
235
|
+
logo: './assets/fvs.png',
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function buildPlugin(pluginRoot) {
|
|
241
|
+
assertSafePluginRoot(pluginRoot);
|
|
242
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
243
|
+
fs.mkdirSync(pluginRoot, { recursive: true });
|
|
244
|
+
|
|
245
|
+
writeJson(path.join(pluginRoot, '.claude-plugin', 'plugin.json'), renderClaudeManifest());
|
|
246
|
+
writeJson(path.join(pluginRoot, '.codex-plugin', 'plugin.json'), renderCodexManifest());
|
|
247
|
+
|
|
248
|
+
const commandsRoot = path.join(ROOT, 'commands', 'fvs');
|
|
249
|
+
for (const entry of fs.readdirSync(commandsRoot, { withFileTypes: true })) {
|
|
250
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
251
|
+
const skillName = entry.name.slice(0, -3);
|
|
252
|
+
const skillPath = path.join(pluginRoot, 'skills', skillName, 'SKILL.md');
|
|
253
|
+
writeText(skillPath, renderPluginSkill(path.join(commandsRoot, entry.name), skillName));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
copyTextTree(path.join(ROOT, 'agents'), path.join(pluginRoot, 'agents'));
|
|
257
|
+
// The npm-specific updater probes mutable ~/.claude and ./.claude installs.
|
|
258
|
+
// Marketplace installs are immutable and use the self-contained update skill.
|
|
259
|
+
copyTextTree(path.join(ROOT, 'fv-skills'), path.join(pluginRoot, 'fv-skills'), {
|
|
260
|
+
exclude: new Set(['workflows/update.md']),
|
|
261
|
+
});
|
|
262
|
+
for (const scriptName of SCRIPT_FILES) {
|
|
263
|
+
copyTextFile(path.join(ROOT, 'scripts', scriptName), path.join(pluginRoot, 'scripts', scriptName));
|
|
264
|
+
}
|
|
265
|
+
copyFile(path.join(ROOT, 'assets', 'FVS.png'), path.join(pluginRoot, 'assets', 'fvs.png'));
|
|
266
|
+
copyFile(path.join(ROOT, 'LICENSE'), path.join(pluginRoot, 'LICENSE'));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function treeHashes(root) {
|
|
270
|
+
const result = new Map();
|
|
271
|
+
function walk(current, relative) {
|
|
272
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
273
|
+
const absolute = path.join(current, entry.name);
|
|
274
|
+
const childRelative = relative ? `${relative}/${entry.name}` : entry.name;
|
|
275
|
+
if (entry.isDirectory()) walk(absolute, childRelative);
|
|
276
|
+
else if (entry.isFile()) {
|
|
277
|
+
const digest = crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex');
|
|
278
|
+
result.set(childRelative, digest);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
walk(root, '');
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function compareTrees(expectedRoot, actualRoot) {
|
|
287
|
+
if (!fs.existsSync(expectedRoot)) return ['committed plugin directory is missing'];
|
|
288
|
+
const expected = treeHashes(expectedRoot);
|
|
289
|
+
const actual = treeHashes(actualRoot);
|
|
290
|
+
const paths = [...new Set([...expected.keys(), ...actual.keys()])].sort();
|
|
291
|
+
return paths.filter((relative) => expected.get(relative) !== actual.get(relative));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function main() {
|
|
295
|
+
const checkOnly = process.argv.includes('--check');
|
|
296
|
+
if (!checkOnly) {
|
|
297
|
+
buildPlugin(COMMITTED_PLUGIN_ROOT);
|
|
298
|
+
console.log(`Built plugins/fvs for v${VERSION}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'fvs-plugin-check-'));
|
|
303
|
+
const generatedRoot = path.join(temporaryRoot, 'fvs');
|
|
304
|
+
try {
|
|
305
|
+
buildPlugin(generatedRoot);
|
|
306
|
+
const differences = compareTrees(COMMITTED_PLUGIN_ROOT, generatedRoot);
|
|
307
|
+
if (differences.length > 0) {
|
|
308
|
+
console.error('Generated plugin is stale. Run: npm run build:plugin');
|
|
309
|
+
for (const relative of differences.slice(0, 50)) console.error(`- ${relative}`);
|
|
310
|
+
if (differences.length > 50) console.error(`- ...and ${differences.length - 50} more`);
|
|
311
|
+
process.exitCode = 1;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
console.log(`Plugin package is synchronized for v${VERSION}`);
|
|
315
|
+
} finally {
|
|
316
|
+
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (require.main === module) main();
|
|
321
|
+
|
|
322
|
+
module.exports = {
|
|
323
|
+
buildPlugin,
|
|
324
|
+
portablePluginPaths,
|
|
325
|
+
renderPluginSkill,
|
|
326
|
+
};
|