@really-knows-ai/foundry 3.2.0 → 3.2.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.
@@ -29,6 +29,7 @@ function listModels(worktree) {
29
29
  cwd: worktree,
30
30
  encoding: 'utf8',
31
31
  stdio: ['pipe', 'pipe', 'pipe'],
32
+ env: { ...process.env, FOUNDRY_SKIP_BOOTSTRAP: '1' },
32
33
  });
33
34
  return stdout
34
35
  .split('\n')
@@ -131,6 +131,54 @@ function runConfigBootstrap(worktree, pkgRoot) {
131
131
  return changed || guideWritten;
132
132
  }
133
133
 
134
+ function runPluginBootstrap(worktree, pkgRoot) {
135
+ // Skip if FOUNDRY_SKIP_BOOTSTRAP is set to prevent infinite recursion
136
+ // when this plugin spawns `opencode models` as a child process.
137
+ if (process.env.FOUNDRY_SKIP_BOOTSTRAP === '1') return false;
138
+ try {
139
+ return runConfigBootstrap(worktree, pkgRoot);
140
+ } catch (err) {
141
+ console.error('Foundry bootstrap error:', err.message);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ const defaultSleep = ms => new Promise(resolve => { setTimeout(resolve, ms); });
147
+
148
+ function resolveOpt(opts, key, fallback) {
149
+ return (opts && opts[key] !== undefined) ? opts[key] : fallback;
150
+ }
151
+
152
+ async function retryUntilReady(fn, opts) {
153
+ const sleep = resolveOpt(opts, 'sleep', defaultSleep);
154
+ const now = resolveOpt(opts, 'now', Date.now);
155
+ const maxMs = resolveOpt(opts, 'maxMs', 30000);
156
+ const deadline = now() + maxMs;
157
+ while (now() < deadline) {
158
+ try {
159
+ await fn();
160
+ return;
161
+ } catch {
162
+ await sleep(500);
163
+ }
164
+ }
165
+ }
166
+
167
+ async function showStartupMessage(needsRestart, directory, client, timerFns) {
168
+ if (!client) return;
169
+ if (needsRestart) {
170
+ await retryUntilReady(() => client.tui.appendPrompt({
171
+ body: { text: 'Foundry initialised. Restart OpenCode so the Foundry agent registers, then switch to it to author and run workflows.' },
172
+ }), timerFns);
173
+ return;
174
+ }
175
+ if (existsSync(path.join(directory, 'foundry'))) {
176
+ await retryUntilReady(() => client.tui.showToast({
177
+ body: { message: 'Foundry is active', variant: 'info' },
178
+ }), timerFns);
179
+ }
180
+ }
181
+
134
182
  export { buildCyclePromptExtras } from './foundry-tools/helpers.js';
135
183
 
136
184
  function buildTools(createTool, pending) {
@@ -167,7 +215,7 @@ function getFirstUserWithParts(output) {
167
215
  return firstUser;
168
216
  }
169
217
 
170
- export const FoundryPlugin = async ({ directory }) => {
218
+ export const FoundryPlugin = async ({ directory, client }) => {
171
219
  // Pending store is per-plugin-instance (shared across all tool invocations).
172
220
  const pending = createPendingStore();
173
221
 
@@ -181,12 +229,8 @@ export const FoundryPlugin = async ({ directory }) => {
181
229
  config.skills.paths.push(allSkillsDir);
182
230
  }
183
231
 
184
- // Boot decision tree: bootstrap or detect changes, then set restart flag
185
- try {
186
- restartNeeded = runConfigBootstrap(directory, packageRoot);
187
- } catch (err) {
188
- console.error('Foundry bootstrap error:', err.message);
189
- }
232
+ restartNeeded = runPluginBootstrap(directory, packageRoot);
233
+ await showStartupMessage(restartNeeded, directory, client);
190
234
  },
191
235
 
192
236
  'experimental.chat.messages.transform': async (_input, output) => {
package/dist/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.2.2] - 2026-05-14
4
+
5
+ ### Added
6
+
7
+ - **Immediate startup feedback via TUI.** Instead of waiting for the first
8
+ user prompt to inject the Foundry context message, the config hook now
9
+ shows feedback immediately. When a restart is needed, the message is
10
+ appended directly to the prompt bar via `client.tui.appendPrompt`. When
11
+ Foundry is already active, a non-intrusive toast notification confirms
12
+ readiness via `client.tui.showToast`. The `messages.transform` hook
13
+ remains as a fallback for AI context injection.
14
+ - **Injectable timer for startup retry logic.** `retryUntilReady` accepts
15
+ injectable `sleep`, `now`, and `maxMs` functions so tests can control
16
+ timing without real delays. `showStartupMessage` bails early when the
17
+ TUI client is unavailable to avoid hanging in environments without a
18
+ client.
19
+
20
+ ## [3.2.1] - 2026-05-14
21
+
22
+ ### Fixed
23
+
24
+ - **Plugin hangs on startup due to infinite recursion.** The config hook's
25
+ `refreshAgents` call spawns `opencode models` via `execFileSync`. When the
26
+ project directory has the foundry plugin configured, the child process
27
+ loads plugins too, triggering another `opencode models` — infinite
28
+ synchronous recursion that hangs the parent. The plugin now sets
29
+ `FOUNDRY_SKIP_BOOTSTRAP=1` in the child process environment and skips the
30
+ bootstrap in the config hook when that variable is set.
31
+
3
32
  ## [3.2.0] - 2026-05-14
4
33
 
5
34
  Plugin auto-bootstrapping release. Foundry now ensures the guide agent is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@really-knows-ai/foundry",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "A skill-driven framework for governed artefact generation with AI coding tools. Define your own artefact types, laws, and flows — Foundry handles the forge → quench → appraise pipeline with deterministic routing, quality gates, and iterative refinement.",
5
5
  "type": "module",
6
6
  "main": "dist/.opencode/plugins/foundry.js",