@really-knows-ai/foundry 3.1.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +10 -14
  2. package/dist/.opencode/plugins/foundry-tools/agent-refresh.js +185 -0
  3. package/dist/.opencode/plugins/foundry-tools/helpers.js +7 -1
  4. package/dist/.opencode/plugins/foundry-tools/refresh-agents-tool.js +7 -68
  5. package/dist/.opencode/plugins/foundry.js +119 -5
  6. package/dist/CHANGELOG.md +60 -0
  7. package/dist/README.md +10 -14
  8. package/dist/docs/README.md +1 -1
  9. package/dist/docs/architecture.md +3 -3
  10. package/dist/docs/concepts.md +1 -1
  11. package/dist/docs/getting-started.md +2 -4
  12. package/dist/scripts/lib/foundational-guards.js +1 -1
  13. package/dist/scripts/lib/memory/admin/init.js +1 -1
  14. package/dist/skills/add-extractor/SKILL.md +1 -3
  15. package/dist/skills/add-memory-edge-type/SKILL.md +1 -3
  16. package/dist/skills/add-memory-entity-type/SKILL.md +1 -3
  17. package/dist/skills/appraise/SKILL.md +1 -1
  18. package/dist/skills/change-embedding-model/SKILL.md +1 -3
  19. package/dist/skills/drop-memory-edge-type/SKILL.md +1 -3
  20. package/dist/skills/drop-memory-entity-type/SKILL.md +1 -3
  21. package/dist/skills/flow/SKILL.md +1 -1
  22. package/dist/skills/forge/SKILL.md +1 -1
  23. package/dist/skills/human-appraise/SKILL.md +1 -1
  24. package/dist/skills/init-memory/SKILL.md +1 -3
  25. package/dist/skills/quench/SKILL.md +1 -1
  26. package/dist/skills/rename-memory-edge-type/SKILL.md +1 -3
  27. package/dist/skills/rename-memory-entity-type/SKILL.md +1 -3
  28. package/dist/skills/reset-memory/SKILL.md +1 -3
  29. package/dist/skills/upgrade-foundry/SKILL.md +2 -2
  30. package/package.json +1 -1
  31. package/dist/skills/init-foundry/SKILL.md +0 -96
package/README.md CHANGED
@@ -105,7 +105,7 @@ Add the plugin to `opencode.json`:
105
105
 
106
106
  Restart OpenCode so the plugin registers its tools and skills. You will see new
107
107
  tools and skills become available in OpenCode's command palette once the restart
108
- completes. The `init-foundry` skill and flow-management tools are now ready to use.
108
+ completes. Flow-management tools are now ready to use.
109
109
 
110
110
  ---
111
111
 
@@ -132,23 +132,19 @@ Add the plugin to `opencode.json` (see Install section above):
132
132
 
133
133
  Then restart OpenCode so the plugin registers its tools and skills. You will see new
134
134
  tools and skills become available in OpenCode's command palette once the restart
135
- completes. The `init-foundry` skill and flow-management tools are now ready to use.
135
+ completes. Flow-management tools are now ready to use.
136
136
 
137
137
  ### Phase 2 — Initialise
138
138
 
139
- Open OpenCode in your project repo and say:
139
+ Restart OpenCode after adding the plugin. On boot, the plugin's config hook runs
140
+ a decision tree: if `foundry/` is missing or its VERSION does not match the
141
+ installed plugin version, it bootstraps the directory structure, generates
142
+ `foundry-<model>` stage agent files, installs the user-facing `Foundry` guide
143
+ agent, and tells you to restart again.
140
144
 
141
- ```
142
- > run init-foundry
143
- ```
144
-
145
- Foundry scaffolds a `foundry/` directory, generates one `foundry-<model>` stage agent
146
- file per model available in your session, installs the user-facing `Foundry` guide
147
- agent, commits the structure, and asks you to restart.
148
-
149
- Restart OpenCode so the new agents register. After the restart, switch to the
150
- **Foundry** agent. The Foundry agent is the normal interface for authoring and
151
- running Foundry workflows.
145
+ Restart OpenCode a second time so the new agents register. After the restart,
146
+ switch to the **Foundry** agent. The Foundry agent is the normal interface for
147
+ authoring and running Foundry workflows.
152
148
 
153
149
  ### Phase 3 — Ask the Foundry agent for a flow
154
150
 
@@ -0,0 +1,185 @@
1
+ // Shared agent-refresh utility for the Foundry plugin.
2
+ // Provides refreshAgents, detectChanges, and writeFoundryGuideAgent
3
+ // used by both the config hook and the foundry_refresh_agents tool.
4
+
5
+ import path from 'path';
6
+ import { execFileSync } from 'child_process';
7
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, existsSync } from 'fs';
8
+ import { createHash } from 'crypto';
9
+
10
+ const AGENT_FRONTMATTER_TEMPLATE = `---
11
+ description: "Foundry stage agent using MODEL_ID"
12
+ mode: subagent
13
+ model: "MODEL_ID"
14
+ hidden: true
15
+ ---
16
+ You are a Foundry stage agent. Follow the skill instructions provided in your task prompt exactly.
17
+ `;
18
+
19
+ function makeSlug(modelId) {
20
+ return modelId.replace(/[/.]/g, '-');
21
+ }
22
+
23
+ function buildAgentContent(modelId) {
24
+ return AGENT_FRONTMATTER_TEMPLATE.replace(/MODEL_ID/g, modelId);
25
+ }
26
+
27
+ function listModels(worktree) {
28
+ const stdout = execFileSync('opencode', ['models'], {
29
+ cwd: worktree,
30
+ encoding: 'utf8',
31
+ stdio: ['pipe', 'pipe', 'pipe'],
32
+ env: { ...process.env, FOUNDRY_SKIP_BOOTSTRAP: '1' },
33
+ });
34
+ return stdout
35
+ .split('\n')
36
+ .map(line => line.trim())
37
+ .filter(line => line.length > 0);
38
+ }
39
+
40
+ function deleteStaleAgents(agentsDir) {
41
+ let existing;
42
+ try {
43
+ existing = readdirSync(agentsDir);
44
+ } catch {
45
+ existing = [];
46
+ }
47
+ for (const entry of existing) {
48
+ if (entry.startsWith('foundry-') && entry.endsWith('.md')) {
49
+ unlinkSync(path.join(agentsDir, entry));
50
+ }
51
+ }
52
+ }
53
+
54
+ function writeAgentFiles(agentsDir, models) {
55
+ for (const modelId of models) {
56
+ const slug = makeSlug(modelId);
57
+ const filePath = path.join(agentsDir, `foundry-${slug}.md`);
58
+ writeFileSync(filePath, buildAgentContent(modelId), 'utf8');
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Snapshot the current foundry-*.md agent files in the agents directory.
64
+ * Returns a plain object mapping filename → sha256 hex digest.
65
+ * Returns an empty object when the directory does not exist.
66
+ */
67
+ function takeSnapshot(agentsDir) {
68
+ const snapshot = {};
69
+ try {
70
+ const entries = readdirSync(agentsDir)
71
+ .filter(e => e.startsWith('foundry-') && e.endsWith('.md'))
72
+ .sort();
73
+ for (const entry of entries) {
74
+ const content = readFileSync(path.join(agentsDir, entry));
75
+ snapshot[entry] = createHash('sha256').update(content).digest('hex');
76
+ }
77
+ } catch {
78
+ // Directory does not exist yet — empty snapshot
79
+ }
80
+ return snapshot;
81
+ }
82
+
83
+ /**
84
+ * Compare two snapshots for equality.
85
+ * Returns true when both have the same files with the same content hashes.
86
+ */
87
+ function snapshotsEqual(a, b) {
88
+ const aKeys = Object.keys(a).sort();
89
+ const bKeys = Object.keys(b).sort();
90
+ if (aKeys.length !== bKeys.length) return false;
91
+ return aKeys.every(k => a[k] === b[k]);
92
+ }
93
+
94
+ /**
95
+ * Run `opencode models`, delete stale foundry-*.md agent files,
96
+ * and write new ones for each model returned.
97
+ *
98
+ * @param {string} worktree - Absolute path to the project worktree root.
99
+ * @returns {{ ok: true, count: number } | { ok: false, error: string }}
100
+ */
101
+ export function refreshAgents(worktree) {
102
+ try {
103
+ const models = listModels(worktree);
104
+ if (models.length === 0) {
105
+ return { ok: false, error: 'No models returned by `opencode models`. Is the opencode CLI available?' };
106
+ }
107
+
108
+ const agentsDir = path.join(worktree, '.opencode', 'agents');
109
+ mkdirSync(agentsDir, { recursive: true });
110
+ deleteStaleAgents(agentsDir);
111
+ writeAgentFiles(agentsDir, models);
112
+
113
+ return { ok: true, count: models.length };
114
+ } catch (err) {
115
+ return { ok: false, error: err.message ?? String(err) };
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Snapshot the current foundry-*.md agent files, run refreshAgents,
121
+ * then compare the before/after file sets to detect changes.
122
+ *
123
+ * When refreshAgents returns { ok: false, error }, the error is
124
+ * propagated immediately without performing a comparison.
125
+ *
126
+ * @param {string} worktree - Absolute path to the project worktree root.
127
+ * @returns {{ ok: true, changed: boolean, count: number } | { ok: false, error: string }}
128
+ */
129
+ export function detectChanges(worktree) {
130
+ const agentsDir = path.join(worktree, '.opencode', 'agents');
131
+ const before = takeSnapshot(agentsDir);
132
+
133
+ const result = refreshAgents(worktree);
134
+ if (!result.ok) {
135
+ return result;
136
+ }
137
+
138
+ const after = takeSnapshot(agentsDir);
139
+ const changed = !snapshotsEqual(before, after);
140
+
141
+ return { ok: true, changed, count: result.count };
142
+ }
143
+
144
+ /**
145
+ * Resolve the guide agent source path within the installed package.
146
+ * Prefers dist/agents/foundry.md and falls back to src/agents/foundry.md.
147
+ */
148
+ function resolveGuideSource(packageRoot) {
149
+ const distPath = path.join(packageRoot, 'dist', 'agents', 'foundry.md');
150
+ if (existsSync(distPath)) return distPath;
151
+ return path.join(packageRoot, 'src', 'agents', 'foundry.md');
152
+ }
153
+
154
+ /**
155
+ * Copy the Foundry guide agent (foundry.md) from the installed package
156
+ * to the project's .opencode/agents/ directory.
157
+ *
158
+ * Resolves the source from `packageRoot/dist/agents/foundry.md` and
159
+ * falls back to `packageRoot/src/agents/foundry.md` when the dist
160
+ * path does not exist. Skips writing when the target file already
161
+ * exists (uses existsSync check).
162
+ *
163
+ * @param {string} worktree - Absolute path to the project worktree root.
164
+ * @param {string} packageRoot - Absolute path to the installed package root.
165
+ * @returns {{ ok: true, written: boolean } | { ok: false, error: string }}
166
+ */
167
+ export function writeFoundryGuideAgent(worktree, packageRoot) {
168
+ const targetDir = path.join(worktree, '.opencode', 'agents');
169
+ const targetPath = path.join(targetDir, 'foundry.md');
170
+
171
+ if (existsSync(targetPath)) {
172
+ return { ok: true, written: false };
173
+ }
174
+
175
+ const sourcePath = resolveGuideSource(packageRoot);
176
+
177
+ try {
178
+ const content = readFileSync(sourcePath, 'utf8');
179
+ mkdirSync(targetDir, { recursive: true });
180
+ writeFileSync(targetPath, content, 'utf8');
181
+ return { ok: true, written: true };
182
+ } catch (err) {
183
+ return { ok: false, error: `Failed to write guide agent: ${err.message ?? String(err)}` };
184
+ }
185
+ }
@@ -139,7 +139,13 @@ Scripts are located at: ${path.join(packageRoot, 'scripts')}
139
139
  </FOUNDRY_CONTEXT>`;
140
140
  }
141
141
 
142
- export function getBootstrapContent(directory, packageRoot) {
142
+ export function getBootstrapContent(directory, packageRoot, restartNeeded = false) {
143
+ if (restartNeeded) {
144
+ return `<FOUNDRY_CONTEXT>
145
+ Foundry initialised. Restart OpenCode so the Foundry agent and model-routing agents register. After restart, switch to the Foundry agent to author and run workflows.
146
+ </FOUNDRY_CONTEXT>`;
147
+ }
148
+
143
149
  const foundryDir = path.join(directory, 'foundry');
144
150
  const foundryExists = existsSync(foundryDir) && statSync(foundryDir).isDirectory();
145
151
 
@@ -1,71 +1,4 @@
1
- import path from 'path';
2
- import { execFileSync } from 'child_process';
3
- import { mkdirSync, readdirSync, writeFileSync, unlinkSync } from 'fs';
4
-
5
- const AGENT_FRONTMATTER_TEMPLATE = `---
6
- description: "Foundry stage agent using MODEL_ID"
7
- mode: subagent
8
- model: "MODEL_ID"
9
- hidden: true
10
- ---
11
- You are a Foundry stage agent. Follow the skill instructions provided in your task prompt exactly.
12
- `;
13
-
14
- function makeSlug(modelId) {
15
- return modelId.replace(/[/.]/g, '-');
16
- }
17
-
18
- function buildAgentContent(modelId) {
19
- return AGENT_FRONTMATTER_TEMPLATE.replace(/MODEL_ID/g, modelId);
20
- }
21
-
22
- function listModels(worktree) {
23
- const stdout = execFileSync('opencode', ['models'], {
24
- cwd: worktree,
25
- encoding: 'utf8',
26
- stdio: ['pipe', 'pipe', 'pipe'],
27
- });
28
- return stdout
29
- .split('\n')
30
- .map(line => line.trim())
31
- .filter(line => line.length > 0);
32
- }
33
-
34
- function deleteStaleAgents(agentsDir) {
35
- let existing;
36
- try {
37
- existing = readdirSync(agentsDir);
38
- } catch {
39
- existing = [];
40
- }
41
- for (const entry of existing) {
42
- if (entry.startsWith('foundry-') && entry.endsWith('.md')) {
43
- unlinkSync(path.join(agentsDir, entry));
44
- }
45
- }
46
- }
47
-
48
- function writeAgentFiles(agentsDir, models) {
49
- for (const modelId of models) {
50
- const slug = makeSlug(modelId);
51
- const filePath = path.join(agentsDir, `foundry-${slug}.md`);
52
- writeFileSync(filePath, buildAgentContent(modelId), 'utf8');
53
- }
54
- }
55
-
56
- function refreshAgents(worktree) {
57
- const models = listModels(worktree);
58
- if (models.length === 0) {
59
- return { ok: false, error: 'No models returned by `opencode models`. Is the opencode CLI available?' };
60
- }
61
-
62
- const agentsDir = path.join(worktree, '.opencode', 'agents');
63
- mkdirSync(agentsDir, { recursive: true });
64
- deleteStaleAgents(agentsDir);
65
- writeAgentFiles(agentsDir, models);
66
-
67
- return { ok: true, count: models.length };
68
- }
1
+ import { refreshAgents } from './agent-refresh.js';
69
2
 
70
3
  export function createRefreshAgentsTool({ tool }) {
71
4
  return {
@@ -75,6 +8,12 @@ export function createRefreshAgentsTool({ tool }) {
75
8
  async execute(_args, context) {
76
9
  try {
77
10
  const result = refreshAgents(context.worktree);
11
+ if (!result.ok) {
12
+ return JSON.stringify({
13
+ ok: false,
14
+ error: `foundry_refresh_agents: ${result.error}`,
15
+ });
16
+ }
78
17
  return JSON.stringify(result);
79
18
  } catch (err) {
80
19
  return JSON.stringify({
@@ -1,17 +1,22 @@
1
1
  /**
2
2
  * Foundry plugin for OpenCode.ai
3
3
  *
4
- * All skills are always registered. Individual skills check for foundry/ dir.
5
- * - If foundry/ exists: pipeline context injected into first message
6
- * - If foundry/ does not exist: minimal prompt guiding user to init-foundry
7
- * Multi-model agents are managed as .opencode/agents/foundry-*.md files via the refresh-agents skill.
4
+ * The config hook runs a boot decision tree on every plugin load: if foundry/
5
+ * is missing or its VERSION mismatches, it bootstraps the directory structure,
6
+ * agent files, and guide agent, then sets a restartNeeded flag. If VERSION
7
+ * matches but the agent file set changed, only agents are refreshed. The
8
+ * message-transform hook injects either a restart prompt or the full Foundry
9
+ * context based on the restartNeeded flag. All skills are always registered;
10
+ * individual skills check for foundry/ dir.
8
11
  */
9
12
 
10
13
  import path from 'path';
11
14
  import { fileURLToPath } from 'url';
15
+ import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
12
16
  import { tool } from '@opencode-ai/plugin';
13
17
  import { createPendingStore } from '../../scripts/lib/pending.js';
14
18
  import { getBootstrapContent } from './foundry-tools/helpers.js';
19
+ import { refreshAgents, detectChanges, writeFoundryGuideAgent } from './foundry-tools/agent-refresh.js';
15
20
  import { createHistoryTools } from './foundry-tools/history-tools.js';
16
21
  import { createStageTools } from './foundry-tools/stage-tools.js';
17
22
  import { createWorkfileTools } from './foundry-tools/workfile-tools.js';
@@ -35,6 +40,109 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
35
40
  const packageRoot = path.resolve(__dirname, '../..');
36
41
  const allSkillsDir = path.join(packageRoot, 'skills');
37
42
 
43
+ // Module-level flag shared between config and message-transform hooks.
44
+ let restartNeeded = false;
45
+
46
+ // -- Bootstrap helpers --
47
+
48
+ function bootstrapDirectories(worktree) {
49
+ const foundryDir = path.join(worktree, 'foundry');
50
+ mkdirSync(foundryDir, { recursive: true });
51
+ for (const sub of ['artefacts', 'flows', 'cycles', 'laws', 'appraisers']) {
52
+ const subDir = path.join(foundryDir, sub);
53
+ mkdirSync(subDir, { recursive: true });
54
+ const gitkeep = path.join(subDir, '.gitkeep');
55
+ if (!existsSync(gitkeep)) {
56
+ writeFileSync(gitkeep, '', 'utf8');
57
+ }
58
+ }
59
+ }
60
+
61
+ function ensureNewlineSuffix(str) {
62
+ if (str !== '' && !str.endsWith('\n')) return str + '\n';
63
+ return str;
64
+ }
65
+
66
+ function bootstrapGitignore(worktree) {
67
+ const gitignorePath = path.join(worktree, '.gitignore');
68
+ let content = '';
69
+ if (existsSync(gitignorePath)) {
70
+ content = readFileSync(gitignorePath, 'utf8');
71
+ }
72
+ content = ensureNewlineSuffix(content);
73
+ const existingLines = content.split('\n').map(l => l.trim());
74
+ const lines = ['.snapshots/', 'node_modules/', '.DS_Store'];
75
+ for (const line of lines) {
76
+ if (existingLines.includes(line)) continue;
77
+ content += `${line}\n`;
78
+ }
79
+ writeFileSync(gitignorePath, content, 'utf8');
80
+ }
81
+
82
+ function runBootstrapSequence(worktree, pkgRoot) {
83
+ bootstrapDirectories(worktree);
84
+ bootstrapGitignore(worktree);
85
+ refreshAgents(worktree);
86
+ writeFoundryGuideAgent(worktree, pkgRoot);
87
+ const pkg = JSON.parse(readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
88
+ writeFileSync(path.join(worktree, 'foundry', 'VERSION'), pkg.version, 'utf8');
89
+ }
90
+
91
+ function checkVersionMatch(foundryDir, pkgRoot) {
92
+ try {
93
+ const installedVersion = readFileSync(path.join(foundryDir, 'VERSION'), 'utf8').trim();
94
+ const pkg = JSON.parse(readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
95
+ return installedVersion === pkg.version;
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ function isFoundryPopulated(worktree) {
102
+ const foundryDir = path.join(worktree, 'foundry');
103
+ if (!existsSync(foundryDir)) return false;
104
+ return readdirSync(foundryDir).some(e => e !== '.gitkeep');
105
+ }
106
+
107
+ function ensureGuideAgent(worktree, pkgRoot) {
108
+ const guideAgentPath = path.join(worktree, '.opencode', 'agents', 'foundry.md');
109
+ if (!existsSync(guideAgentPath)) {
110
+ writeFoundryGuideAgent(worktree, pkgRoot);
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ function runConfigBootstrap(worktree, pkgRoot) {
117
+ if (!isFoundryPopulated(worktree)) {
118
+ runBootstrapSequence(worktree, pkgRoot);
119
+ return true;
120
+ }
121
+
122
+ const foundryDir = path.join(worktree, 'foundry');
123
+ if (!checkVersionMatch(foundryDir, pkgRoot)) {
124
+ runBootstrapSequence(worktree, pkgRoot);
125
+ return true;
126
+ }
127
+
128
+ const result = detectChanges(worktree);
129
+ const changed = result.ok && result.changed;
130
+ const guideWritten = ensureGuideAgent(worktree, pkgRoot);
131
+ return changed || guideWritten;
132
+ }
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
+
38
146
  export { buildCyclePromptExtras } from './foundry-tools/helpers.js';
39
147
 
40
148
  function buildTools(createTool, pending) {
@@ -84,10 +192,12 @@ export const FoundryPlugin = async ({ directory }) => {
84
192
  if (!config.skills.paths.includes(allSkillsDir)) {
85
193
  config.skills.paths.push(allSkillsDir);
86
194
  }
195
+
196
+ restartNeeded = runPluginBootstrap(directory, packageRoot);
87
197
  },
88
198
 
89
199
  'experimental.chat.messages.transform': async (_input, output) => {
90
- const bootstrap = getBootstrapContent(directory, packageRoot);
200
+ const bootstrap = getBootstrapContent(directory, packageRoot, restartNeeded);
91
201
  if (!bootstrap) return;
92
202
 
93
203
  const firstUser = getFirstUserWithParts(output);
@@ -103,5 +213,9 @@ export const FoundryPlugin = async ({ directory }) => {
103
213
  };
104
214
 
105
215
  Object.defineProperty(plugin, Symbol.for('foundry.test.pending'), { value: pending });
216
+ Object.defineProperty(plugin, Symbol.for('foundry.test.restartNeeded'), {
217
+ get: () => restartNeeded,
218
+ configurable: true,
219
+ });
106
220
  return plugin;
107
221
  };
package/dist/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.2.1] - 2026-05-14
4
+
5
+ ### Fixed
6
+
7
+ - **Plugin hangs on startup due to infinite recursion.** The config hook's
8
+ `refreshAgents` call spawns `opencode models` via `execFileSync`. When the
9
+ project directory has the foundry plugin configured, the child process
10
+ loads plugins too, triggering another `opencode models` — infinite
11
+ synchronous recursion that hangs the parent. The plugin now sets
12
+ `FOUNDRY_SKIP_BOOTSTRAP=1` in the child process environment and skips the
13
+ bootstrap in the config hook when that variable is set.
14
+
15
+ ## [3.2.0] - 2026-05-14
16
+
17
+ Plugin auto-bootstrapping release. Foundry now ensures the guide agent is
18
+ present on every plugin load and deletes the now-redundant `init-foundry`
19
+ skill. The project also ships a publish-release skill and documented
20
+ workflows for plans and git worktrees.
21
+
22
+ ### Added
23
+
24
+ - **Bootstrap logic** (`src/plugin.js`). On every `detectChanges` call,
25
+ Foundry ensures the guide agent file (`foundry.md`) exists in
26
+ `.opencode/agents/`, running the same deterministic agent-refresh path
27
+ that the `foundry_refresh_agents` tool uses. If the guide agent is
28
+ newly created, a post-install restart message is injected into the
29
+ detection context.
30
+ - **`publish-release` skill** (`.opencode/skills/publish-release/`). A
31
+ workflow skill that commits loose changes, runs the quality gate,
32
+ bumps the version, updates the changelog, tags, pushes, and publishes
33
+ to npm.
34
+
35
+ ### Changed
36
+
37
+ - **`init-foundry` skill removed.** The manual installation step is
38
+ replaced by deterministic auto-bootstrapping on plugin load.
39
+ `init-foundry` was the last on-ramp skill; new users no longer need
40
+ to run any skill after `pnpm add`.
41
+ - **Shared agent-refresh utility extracted** (`src/lib/agent-refresh.js`).
42
+ The deterministic agent generation logic that lived in
43
+ `scripts/tools/foundry_refresh_agents.js` is now a shared module,
44
+ consumed by both the tool and the `detectChanges` bootstrap path.
45
+ - **Phase reviews run in parallel** with partitioned iteration via
46
+ parallel implementer agents.
47
+ - **Implementer agent model** updated to `deepseek-v4-flash`.
48
+
49
+ ### Fixed
50
+
51
+ - **Guide agent now always present after `detectChanges`.** Previously
52
+ the guide agent could be absent until `init-foundry` or
53
+ `foundry_refresh_agents` was explicitly run.
54
+ - **Guide agent preserved during `foundry_refresh_agents`.** The refresh
55
+ tool now ensures `foundry.md` is generated alongside stage agents,
56
+ not just preserved.
57
+
58
+ ### Docs
59
+
60
+ - Git worktrees in `.worktrees/` directory documented.
61
+ - Plans directory workflow and phased planning skills documented.
62
+
3
63
  ## [3.1.0] - 2026-05-11
4
64
 
5
65
  The Foundry guide agent release. Foundry now ships a user-facing agent that
package/dist/README.md CHANGED
@@ -105,7 +105,7 @@ Add the plugin to `opencode.json`:
105
105
 
106
106
  Restart OpenCode so the plugin registers its tools and skills. You will see new
107
107
  tools and skills become available in OpenCode's command palette once the restart
108
- completes. The `init-foundry` skill and flow-management tools are now ready to use.
108
+ completes. Flow-management tools are now ready to use.
109
109
 
110
110
  ---
111
111
 
@@ -132,23 +132,19 @@ Add the plugin to `opencode.json` (see Install section above):
132
132
 
133
133
  Then restart OpenCode so the plugin registers its tools and skills. You will see new
134
134
  tools and skills become available in OpenCode's command palette once the restart
135
- completes. The `init-foundry` skill and flow-management tools are now ready to use.
135
+ completes. Flow-management tools are now ready to use.
136
136
 
137
137
  ### Phase 2 — Initialise
138
138
 
139
- Open OpenCode in your project repo and say:
139
+ Restart OpenCode after adding the plugin. On boot, the plugin's config hook runs
140
+ a decision tree: if `foundry/` is missing or its VERSION does not match the
141
+ installed plugin version, it bootstraps the directory structure, generates
142
+ `foundry-<model>` stage agent files, installs the user-facing `Foundry` guide
143
+ agent, and tells you to restart again.
140
144
 
141
- ```
142
- > run init-foundry
143
- ```
144
-
145
- Foundry scaffolds a `foundry/` directory, generates one `foundry-<model>` stage agent
146
- file per model available in your session, installs the user-facing `Foundry` guide
147
- agent, commits the structure, and asks you to restart.
148
-
149
- Restart OpenCode so the new agents register. After the restart, switch to the
150
- **Foundry** agent. The Foundry agent is the normal interface for authoring and
151
- running Foundry workflows.
145
+ Restart OpenCode a second time so the new agents register. After the restart,
146
+ switch to the **Foundry** agent. The Foundry agent is the normal interface for
147
+ authoring and running Foundry workflows.
152
148
 
153
149
  ### Phase 3 — Ask the Foundry agent for a flow
154
150
 
@@ -10,7 +10,7 @@ Getting oriented with Foundry means understanding both the concepts it uses and
10
10
 
11
11
  **Reading order:** Work through them in order; [getting-started.md](getting-started.md) builds hands-on confidence, and [concepts.md](concepts.md) provides reference depth. Most implementers spend 1–2 hours on getting-started before moving to Reference materials.
12
12
 
13
- - **getting-started.md** — Complete end-to-end installation, bootstrap (`init-foundry`), and first flow walkthrough. Read this immediately after installing the plugin and before authoring any of your own configuration.
13
+ - **getting-started.md** — Complete end-to-end installation, auto-bootstrapping, and first flow walkthrough. Read this immediately after installing the plugin and before authoring any of your own configuration.
14
14
 
15
15
  It establishes the operating model, directory structure, and practical confidence in one pass. Includes hands-on guidance on authoring the five foundational concepts (artefact types, laws, appraisers, cycles, flows) with worked examples you can run against real code. Also covers the optional flow-memory path: initialise memory, declare vocabulary, add extractors, and opt a cycle into assay for codebase-aware flows.
16
16
 
@@ -300,7 +300,7 @@ Different stages can run on different models for cognitive diversity. Cycle defi
300
300
 
301
301
  ### Agent files
302
302
 
303
- The user-facing `Foundry` agent is installed by `init-foundry` as `.opencode/agents/foundry.md`. Users switch to this agent after restarting OpenCode. It guides authoring and flow execution while generated `foundry-*` stage agents remain hidden routing targets for specific models.
303
+ The user-facing `Foundry` agent is installed by the plugin's `config` hook as `.opencode/agents/foundry.md`. Users switch to this agent after restarting OpenCode. It guides authoring and flow execution while generated `foundry-*` stage agents remain hidden routing targets for specific models.
304
304
 
305
305
  `foundry_refresh_agents()` generates a `foundry-<slug>.md` agent file in `.opencode/agents/` for every model available in the session, where `<slug>` is the model ID with both `/` and `.` replaced by `-` (e.g. `anthropic-claude-opus-4-7.md`).
306
306
 
@@ -330,7 +330,7 @@ Implementation: `src/plugin/tools/helpers.js` (`buildCyclePromptExtras`) and `sr
330
330
  │ │ ├── quench/
331
331
  │ │ ├── appraise/
332
332
  │ │ ├── human-appraise/
333
- │ │ ├── init-foundry/ # authoring
333
+ │ │ ├── add-artefact-type/ # authoring
334
334
  │ │ ├── add-artefact-type/
335
335
  │ │ ├── add-law/
336
336
  │ │ ├── add-appraiser/
@@ -391,7 +391,7 @@ Implementation: `src/plugin/tools/helpers.js` (`buildCyclePromptExtras`) and `sr
391
391
  └── README.md
392
392
  ```
393
393
 
394
- ### User project (after `init-foundry`)
394
+ ### User project (after auto-bootstrapping)
395
395
 
396
396
  ```
397
397
  your-project/
@@ -252,7 +252,7 @@ plain-files directory at `.snapshots/<runId>/` on the parent
252
252
  - `trace.jsonl` — the full tool-call trace.
253
253
 
254
254
  `runId` is `<branch-slug>-<ulid>`. Snapshots are gitignored
255
- (`.snapshots/` is added to `.gitignore` by `init-foundry`) and
255
+ (`.snapshots/` is added to `.gitignore` by the plugin's auto-bootstrapping) and
256
256
  accumulate locally; `foundry_snapshot_list` enumerates them,
257
257
  `foundry_snapshot_show` returns a structured summary,
258
258
  `foundry_snapshot_delete` removes one, and
@@ -31,11 +31,9 @@ pnpm add -D @really-knows-ai/foundry
31
31
 
32
32
  ## Initialise
33
33
 
34
- Run the `init-foundry` skill.
34
+ Restart OpenCode after adding the plugin. On boot, the plugin's config hook checks project state: if `foundry/` is missing or its VERSION does not match the installed plugin version, it bootstraps the directory structure, generates model-routing `foundry-*` stage agents, installs the user-facing `Foundry` guide agent, and prompts a second restart.
35
35
 
36
- Initialisation installs the user-facing `Foundry` agent at `.opencode/agents/foundry.md` and generates model-routing `foundry-*` stage agents for any models available in your OpenCode session.
37
-
38
- Restart OpenCode after initialisation. Then switch to the **Foundry** agent before authoring flows. The Foundry agent understands Foundry's authoring workflow and handles dependent setup such as artefact types, laws, validators, appraisers, cycles, and config branches.
36
+ Restart OpenCode again so the new agents register. Then switch to the **Foundry** agent before authoring flows. The Foundry agent understands Foundry's authoring workflow and handles dependent setup such as artefact types, laws, validators, appraisers, cycles, and config branches.
39
37
 
40
38
  The `.foundry/` runtime directory (holding `.secret` for stage tokens) is created automatically on first plugin boot and added to `.gitignore`.
41
39
 
@@ -8,6 +8,6 @@ export function requireFoundryRoot(io) {
8
8
  if (io.exists('foundry')) return { ok: true };
9
9
  return {
10
10
  ok: false,
11
- error: 'foundry/ directory not found at worktree root. Run the init-foundry skill to scaffold it.',
11
+ error: 'foundry/ directory not found at worktree root. Restart OpenCode to initialise Foundry.',
12
12
  };
13
13
  }
@@ -87,7 +87,7 @@ function buildSchema(embeddingsEnabled, model, dimensions) {
87
87
 
88
88
  async function validatePrerequisites(io, p) {
89
89
  if (!(await io.exists('foundry'))) {
90
- throw new Error('foundry/ does not exist; run init-foundry first');
90
+ throw new Error('foundry/ does not exist. Restart OpenCode to initialise Foundry.');
91
91
  }
92
92
  if (await io.exists(p.root)) {
93
93
  throw new Error('foundry/memory/ already exists');
@@ -15,9 +15,7 @@ Before running this skill, verify all of the following:
15
15
  1. The `foundry/` directory exists in the project root. If it does not
16
16
  exist, stop and tell the user:
17
17
 
18
- > Foundry is not initialized in this project. Run the
19
- > `init-foundry` skill first to create the foundry/ directory
20
- > structure.
18
+ > Restart OpenCode to initialise Foundry, then retry this command.
21
19
 
22
20
  2. The current git branch is a `config/*` branch. Run
23
21
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -13,9 +13,7 @@ Before running this skill, verify all of the following:
13
13
  1. The `foundry/` directory exists in the project root. If it does not
14
14
  exist, stop and tell the user:
15
15
 
16
- > Foundry is not initialized in this project. Run the
17
- > `init-foundry` skill first to create the foundry/ directory
18
- > structure.
16
+ > Restart OpenCode to initialise Foundry, then retry this command.
19
17
 
20
18
  2. The current git branch is a `config/*` branch. Run
21
19
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -16,9 +16,7 @@ Before running this skill, verify all of the following:
16
16
  1. The `foundry/` directory exists in the project root. If it does not
17
17
  exist, stop and tell the user:
18
18
 
19
- > Foundry is not initialized in this project. Run the
20
- > `init-foundry` skill first to create the foundry/ directory
21
- > structure.
19
+ > Restart OpenCode to initialise Foundry, then retry this command.
22
20
 
23
21
  2. The current git branch is a `config/*` branch. Run
24
22
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -12,7 +12,7 @@ You orchestrate subjective appraisal of an artefact by dispatching independent s
12
12
 
13
13
  Before running this skill, verify that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
15
+ > Restart OpenCode to initialise Foundry, then retry this command.
16
16
 
17
17
  ## Stage lifecycle (mandatory)
18
18
 
@@ -16,9 +16,7 @@ Before running this skill, verify all of the following:
16
16
  1. The `foundry/` directory exists in the project root. If it does not
17
17
  exist, stop and tell the user:
18
18
 
19
- > Foundry is not initialized in this project. Run the
20
- > `init-foundry` skill first to create the foundry/ directory
21
- > structure.
19
+ > Restart OpenCode to initialise Foundry, then retry this command.
22
20
 
23
21
  2. The current git branch is a `config/*` branch. Run
24
22
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -15,9 +15,7 @@ Before running this skill, verify all of the following:
15
15
  1. The `foundry/` directory exists in the project root. If it does not
16
16
  exist, stop and tell the user:
17
17
 
18
- > Foundry is not initialized in this project. Run the
19
- > `init-foundry` skill first to create the foundry/ directory
20
- > structure.
18
+ > Restart OpenCode to initialise Foundry, then retry this command.
21
19
 
22
20
  2. The current git branch is a `config/*` branch. Run
23
21
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -16,9 +16,7 @@ Before running this skill, verify all of the following:
16
16
  1. The `foundry/` directory exists in the project root. If it does not
17
17
  exist, stop and tell the user:
18
18
 
19
- > Foundry is not initialized in this project. Run the
20
- > `init-foundry` skill first to create the foundry/ directory
21
- > structure.
19
+ > Restart OpenCode to initialise Foundry, then retry this command.
22
20
 
23
21
  2. The current git branch is a `config/*` branch. Run
24
22
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -15,7 +15,7 @@ A foundry flow reads a flow definition, creates a work branch, and executes cycl
15
15
 
16
16
  Before running this skill, verify that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
17
17
 
18
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
18
+ > Restart OpenCode to initialise Foundry, then retry this command.
19
19
 
20
20
  ## Starting a flow
21
21
 
@@ -12,7 +12,7 @@ You produce or revise artefacts. You read the work file to understand the goal,
12
12
 
13
13
  Before running this skill, verify that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
15
+ > Restart OpenCode to initialise Foundry, then retry this command.
16
16
 
17
17
  ## Stage lifecycle (mandatory)
18
18
 
@@ -12,7 +12,7 @@ You are a human quality gate. Sort has routed to you either because the LLM appr
12
12
 
13
13
  Before running this skill, verify that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
15
+ > Restart OpenCode to initialise Foundry, then retry this command.
16
16
 
17
17
  ## Stage lifecycle (mandatory)
18
18
 
@@ -19,9 +19,7 @@ Before running this skill, verify all of the following:
19
19
  1. The `foundry/` directory exists in the project root. If it does not
20
20
  exist, stop and tell the user:
21
21
 
22
- > Foundry is not initialized in this project. Run the
23
- > `init-foundry` skill first to create the foundry/ directory
24
- > structure.
22
+ > Restart OpenCode to initialise Foundry, then retry this command.
25
23
 
26
24
  2. The current git branch is a `config/*` branch. Run
27
25
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -12,7 +12,7 @@ You run deterministic checks on an artefact by executing the CLI commands define
12
12
 
13
13
  Before running this skill, verify that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
15
+ > Restart OpenCode to initialise Foundry, then retry this command.
16
16
 
17
17
  ## Stage lifecycle (mandatory)
18
18
 
@@ -13,9 +13,7 @@ Before running this skill, verify all of the following:
13
13
  1. The `foundry/` directory exists in the project root. If it does not
14
14
  exist, stop and tell the user:
15
15
 
16
- > Foundry is not initialized in this project. Run the
17
- > `init-foundry` skill first to create the foundry/ directory
18
- > structure.
16
+ > Restart OpenCode to initialise Foundry, then retry this command.
19
17
 
20
18
  2. The current git branch is a `config/*` branch. Run
21
19
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -13,9 +13,7 @@ Before running this skill, verify all of the following:
13
13
  1. The `foundry/` directory exists in the project root. If it does not
14
14
  exist, stop and tell the user:
15
15
 
16
- > Foundry is not initialized in this project. Run the
17
- > `init-foundry` skill first to create the foundry/ directory
18
- > structure.
16
+ > Restart OpenCode to initialise Foundry, then retry this command.
19
17
 
20
18
  2. The current git branch is a `config/*` branch. Run
21
19
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -16,9 +16,7 @@ Before running this skill, verify all of the following:
16
16
  1. The `foundry/` directory exists in the project root. If it does not
17
17
  exist, stop and tell the user:
18
18
 
19
- > Foundry is not initialized in this project. Run the
20
- > `init-foundry` skill first to create the foundry/ directory
21
- > structure.
19
+ > Restart OpenCode to initialise Foundry, then retry this command.
22
20
 
23
21
  2. The current git branch is a `config/*` branch. Run
24
22
  `git rev-parse --abbrev-ref HEAD` and confirm it matches
@@ -14,7 +14,7 @@ This is a rebuild-style upgrade. Treat the old `foundry/` directory as source ma
14
14
 
15
15
  Before running this skill, verify that the project root contains `foundry/`. If it does not, stop and tell the user:
16
16
 
17
- > Foundry is not initialised in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
17
+ > Restart OpenCode to initialise Foundry, then retry this command.
18
18
 
19
19
  Verify a safe base state before making changes:
20
20
 
@@ -53,7 +53,7 @@ Do not modify the preserved source directory after moving it. Read from it as so
53
53
 
54
54
  ### 3. Initialise current-version configuration
55
55
 
56
- Run the current `init-foundry` flow to create a fresh `foundry/` directory for the installed Foundry version.
56
+ On next restart, the plugin's config hook will auto-initialise a fresh `foundry/` directory.
57
57
 
58
58
  After initialisation, confirm the new config directory exists and contains the expected current top-level structure.
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@really-knows-ai/foundry",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
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",
@@ -1,96 +0,0 @@
1
- ---
2
- name: init-foundry
3
- type: atomic
4
- description: Initialise a Foundry project by creating the foundry/ directory structure
5
- ---
6
-
7
- # Initialise Foundry
8
-
9
- Set up the `foundry/` directory structure in the current project.
10
-
11
- ## Prerequisites
12
-
13
- - The project must not already have a `foundry/` directory.
14
-
15
- ## Steps
16
-
17
- 1. **Check for existing foundry/ directory**
18
- - If `foundry/` already exists, inform the user and stop.
19
-
20
- 2. **Create the directory structure**
21
- Create the following directories, each with a `.gitkeep` file:
22
-
23
- ```
24
- foundry/
25
- artefacts/.gitkeep
26
- flows/.gitkeep
27
- cycles/.gitkeep
28
- laws/.gitkeep
29
- appraisers/.gitkeep
30
- ```
31
-
32
- 3. **Update `.gitignore`**
33
-
34
- Append the following lines to the project's `.gitignore` (creating
35
- the file if absent), skipping any that are already present:
36
-
37
- ```
38
- .snapshots/
39
- node_modules/
40
- .DS_Store
41
- ```
42
-
43
- - `.snapshots/` keeps dry-run snapshots out of git.
44
- - `node_modules/` keeps any npm dependencies (e.g. validator
45
- packages) out of git. Without it, foundry's `config/*` tools
46
- reject calls with `unexpected_files` as soon as the user runs
47
- `npm install`.
48
- - `.DS_Store` keeps macOS metadata out of git.
49
-
50
- The plugin will idempotently append `.foundry/` itself on first
51
- boot, so you do not need to add that line.
52
-
53
- 4. **Generate model-routing agent files**
54
-
55
- Call `foundry_refresh_agents()` to generate model-routing `.opencode/agents/foundry-*.md` files.
56
-
57
- 5. **Install the Foundry guide agent**
58
-
59
- Create `.opencode/agents/foundry.md` from the packaged Foundry guide
60
- agent template. Copy whichever template path exists: prefer
61
- `dist/agents/foundry.md` when running from the built package, then
62
- fall back to `src/agents/foundry.md` when running from a source
63
- checkout. This user-facing agent is installed during `init-foundry`;
64
- `foundry_refresh_agents()` manages only generated `foundry-*` stage
65
- agents.
66
-
67
- 6. **Commit the structure**
68
-
69
- ```bash
70
- git add foundry/ .gitignore .opencode/agents/foundry.md .opencode/agents/foundry-*.md
71
- git commit -m "feat: initialise Foundry project structure"
72
- ```
73
-
74
- 7. **Guide next steps**
75
-
76
- Tell the user:
77
-
78
- > Foundry is initialised. **Restart OpenCode** so the new Foundry agents register.
79
- >
80
- > After the restart, switch to the **Foundry** agent. The Foundry agent is the user-facing guide for setting up artefact types, laws, validators, appraisers, cycles, and flows.
81
- >
82
- > Then ask the Foundry agent for the outcome you want, for example:
83
- >
84
- > `set up a flow that writes haikus`
85
- >
86
- > The first time the plugin boots in this project, it will create the
87
- > `.foundry/` runtime directory and idempotently append `.foundry/` to
88
- > `.gitignore` so the per-worktree HMAC key stays out of git. The
89
- > `.snapshots/` line was added by this skill to keep dry-run snapshots
90
- > out of git.
91
- >
92
- > **Optional: Flow Memory**
93
- >
94
- > If your flows need persistent knowledge, ask the Foundry agent to add
95
- > flow memory. Memory is useful for projects that need to track code
96
- > structure, dependencies, or domain knowledge across flow runs.