@really-knows-ai/foundry 3.0.2 → 3.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/README.md +12 -15
- package/dist/.opencode/plugins/foundry-tools/agent-refresh.js +184 -0
- package/dist/.opencode/plugins/foundry-tools/helpers.js +17 -20
- package/dist/.opencode/plugins/foundry-tools/refresh-agents-tool.js +27 -0
- package/dist/.opencode/plugins/foundry.js +114 -5
- package/dist/CHANGELOG.md +161 -0
- package/dist/README.md +12 -15
- package/dist/agents/foundry.md +37 -0
- package/dist/docs/README.md +1 -1
- package/dist/docs/architecture.md +8 -5
- package/dist/docs/concepts.md +2 -2
- package/dist/docs/getting-started.md +55 -135
- package/dist/docs/tools.md +21 -1
- package/dist/scripts/lib/foundational-guards.js +1 -1
- package/dist/scripts/lib/memory/admin/init.js +1 -1
- package/dist/scripts/sort.js +1 -1
- package/dist/skills/add-appraiser/SKILL.md +19 -34
- package/dist/skills/add-artefact-type/SKILL.md +19 -22
- package/dist/skills/add-cycle/SKILL.md +28 -37
- package/dist/skills/add-extractor/SKILL.md +21 -35
- package/dist/skills/add-flow/SKILL.md +43 -88
- package/dist/skills/add-law/SKILL.md +19 -24
- package/dist/skills/add-memory-edge-type/SKILL.md +12 -20
- package/dist/skills/add-memory-entity-type/SKILL.md +10 -19
- package/dist/skills/appraise/SKILL.md +1 -1
- package/dist/skills/change-embedding-model/SKILL.md +7 -11
- package/dist/skills/drop-memory-edge-type/SKILL.md +7 -11
- package/dist/skills/drop-memory-entity-type/SKILL.md +7 -11
- package/dist/skills/dry-run/SKILL.md +11 -28
- package/dist/skills/flow/SKILL.md +2 -2
- package/dist/skills/forge/SKILL.md +1 -1
- package/dist/skills/human-appraise/SKILL.md +1 -1
- package/dist/skills/init-memory/SKILL.md +12 -25
- package/dist/skills/list-agents/SKILL.md +1 -1
- package/dist/skills/quench/SKILL.md +1 -1
- package/dist/skills/refresh-agents/SKILL.md +4 -26
- package/dist/skills/rename-memory-edge-type/SKILL.md +7 -11
- package/dist/skills/rename-memory-entity-type/SKILL.md +7 -11
- package/dist/skills/reset-memory/SKILL.md +10 -18
- package/dist/skills/upgrade-foundry/SKILL.md +3 -3
- package/package.json +2 -1
- package/dist/skills/init-foundry/SKILL.md +0 -91
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.
|
|
108
|
+
completes. Flow-management tools are now ready to use.
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
@@ -132,26 +132,23 @@ 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.
|
|
135
|
+
completes. Flow-management tools are now ready to use.
|
|
136
136
|
|
|
137
137
|
### Phase 2 — Initialise
|
|
138
138
|
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Foundry scaffolds a `foundry/` directory, generates one `foundry-<model>` agent file
|
|
146
|
-
per model available in your session, commits the structure, and then asks you to
|
|
147
|
-
restart. All the foundational configuration directories are created; you will
|
|
148
|
-
populate them next.
|
|
149
|
-
|
|
150
|
-
Restart OpenCode so the new `foundry-<model>` agents register — multi-model dispatch cannot route to agents it cannot discover.
|
|
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.
|
|
151
148
|
|
|
152
|
-
### Phase 3 —
|
|
149
|
+
### Phase 3 — Ask the Foundry agent for a flow
|
|
153
150
|
|
|
154
|
-
|
|
151
|
+
With the **Foundry** agent active, ask it to set up a flow:
|
|
155
152
|
|
|
156
153
|
```
|
|
157
154
|
> set up a flow that writes haikus
|
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
});
|
|
33
|
+
return stdout
|
|
34
|
+
.split('\n')
|
|
35
|
+
.map(line => line.trim())
|
|
36
|
+
.filter(line => line.length > 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deleteStaleAgents(agentsDir) {
|
|
40
|
+
let existing;
|
|
41
|
+
try {
|
|
42
|
+
existing = readdirSync(agentsDir);
|
|
43
|
+
} catch {
|
|
44
|
+
existing = [];
|
|
45
|
+
}
|
|
46
|
+
for (const entry of existing) {
|
|
47
|
+
if (entry.startsWith('foundry-') && entry.endsWith('.md')) {
|
|
48
|
+
unlinkSync(path.join(agentsDir, entry));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeAgentFiles(agentsDir, models) {
|
|
54
|
+
for (const modelId of models) {
|
|
55
|
+
const slug = makeSlug(modelId);
|
|
56
|
+
const filePath = path.join(agentsDir, `foundry-${slug}.md`);
|
|
57
|
+
writeFileSync(filePath, buildAgentContent(modelId), 'utf8');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Snapshot the current foundry-*.md agent files in the agents directory.
|
|
63
|
+
* Returns a plain object mapping filename → sha256 hex digest.
|
|
64
|
+
* Returns an empty object when the directory does not exist.
|
|
65
|
+
*/
|
|
66
|
+
function takeSnapshot(agentsDir) {
|
|
67
|
+
const snapshot = {};
|
|
68
|
+
try {
|
|
69
|
+
const entries = readdirSync(agentsDir)
|
|
70
|
+
.filter(e => e.startsWith('foundry-') && e.endsWith('.md'))
|
|
71
|
+
.sort();
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const content = readFileSync(path.join(agentsDir, entry));
|
|
74
|
+
snapshot[entry] = createHash('sha256').update(content).digest('hex');
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Directory does not exist yet — empty snapshot
|
|
78
|
+
}
|
|
79
|
+
return snapshot;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compare two snapshots for equality.
|
|
84
|
+
* Returns true when both have the same files with the same content hashes.
|
|
85
|
+
*/
|
|
86
|
+
function snapshotsEqual(a, b) {
|
|
87
|
+
const aKeys = Object.keys(a).sort();
|
|
88
|
+
const bKeys = Object.keys(b).sort();
|
|
89
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
90
|
+
return aKeys.every(k => a[k] === b[k]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run `opencode models`, delete stale foundry-*.md agent files,
|
|
95
|
+
* and write new ones for each model returned.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} worktree - Absolute path to the project worktree root.
|
|
98
|
+
* @returns {{ ok: true, count: number } | { ok: false, error: string }}
|
|
99
|
+
*/
|
|
100
|
+
export function refreshAgents(worktree) {
|
|
101
|
+
try {
|
|
102
|
+
const models = listModels(worktree);
|
|
103
|
+
if (models.length === 0) {
|
|
104
|
+
return { ok: false, error: 'No models returned by `opencode models`. Is the opencode CLI available?' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const agentsDir = path.join(worktree, '.opencode', 'agents');
|
|
108
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
109
|
+
deleteStaleAgents(agentsDir);
|
|
110
|
+
writeAgentFiles(agentsDir, models);
|
|
111
|
+
|
|
112
|
+
return { ok: true, count: models.length };
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return { ok: false, error: err.message ?? String(err) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Snapshot the current foundry-*.md agent files, run refreshAgents,
|
|
120
|
+
* then compare the before/after file sets to detect changes.
|
|
121
|
+
*
|
|
122
|
+
* When refreshAgents returns { ok: false, error }, the error is
|
|
123
|
+
* propagated immediately without performing a comparison.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} worktree - Absolute path to the project worktree root.
|
|
126
|
+
* @returns {{ ok: true, changed: boolean, count: number } | { ok: false, error: string }}
|
|
127
|
+
*/
|
|
128
|
+
export function detectChanges(worktree) {
|
|
129
|
+
const agentsDir = path.join(worktree, '.opencode', 'agents');
|
|
130
|
+
const before = takeSnapshot(agentsDir);
|
|
131
|
+
|
|
132
|
+
const result = refreshAgents(worktree);
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const after = takeSnapshot(agentsDir);
|
|
138
|
+
const changed = !snapshotsEqual(before, after);
|
|
139
|
+
|
|
140
|
+
return { ok: true, changed, count: result.count };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve the guide agent source path within the installed package.
|
|
145
|
+
* Prefers dist/agents/foundry.md and falls back to src/agents/foundry.md.
|
|
146
|
+
*/
|
|
147
|
+
function resolveGuideSource(packageRoot) {
|
|
148
|
+
const distPath = path.join(packageRoot, 'dist', 'agents', 'foundry.md');
|
|
149
|
+
if (existsSync(distPath)) return distPath;
|
|
150
|
+
return path.join(packageRoot, 'src', 'agents', 'foundry.md');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Copy the Foundry guide agent (foundry.md) from the installed package
|
|
155
|
+
* to the project's .opencode/agents/ directory.
|
|
156
|
+
*
|
|
157
|
+
* Resolves the source from `packageRoot/dist/agents/foundry.md` and
|
|
158
|
+
* falls back to `packageRoot/src/agents/foundry.md` when the dist
|
|
159
|
+
* path does not exist. Skips writing when the target file already
|
|
160
|
+
* exists (uses existsSync check).
|
|
161
|
+
*
|
|
162
|
+
* @param {string} worktree - Absolute path to the project worktree root.
|
|
163
|
+
* @param {string} packageRoot - Absolute path to the installed package root.
|
|
164
|
+
* @returns {{ ok: true, written: boolean } | { ok: false, error: string }}
|
|
165
|
+
*/
|
|
166
|
+
export function writeFoundryGuideAgent(worktree, packageRoot) {
|
|
167
|
+
const targetDir = path.join(worktree, '.opencode', 'agents');
|
|
168
|
+
const targetPath = path.join(targetDir, 'foundry.md');
|
|
169
|
+
|
|
170
|
+
if (existsSync(targetPath)) {
|
|
171
|
+
return { ok: true, written: false };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const sourcePath = resolveGuideSource(packageRoot);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const content = readFileSync(sourcePath, 'utf8');
|
|
178
|
+
mkdirSync(targetDir, { recursive: true });
|
|
179
|
+
writeFileSync(targetPath, content, 'utf8');
|
|
180
|
+
return { ok: true, written: true };
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { ok: false, error: `Failed to write guide agent: ${err.message ?? String(err)}` };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -94,14 +94,13 @@ function buildFoundryNotInitializedMessage() {
|
|
|
94
94
|
return `<FOUNDRY_CONTEXT>
|
|
95
95
|
Foundry is installed but not initialised in this project. There is no foundry/ directory.
|
|
96
96
|
|
|
97
|
-
To set up Foundry,
|
|
98
|
-
and guide you through defining artefact types, laws, appraisers, cycles, and flows.
|
|
97
|
+
To set up Foundry, initialise the project first. Initialisation creates the foundry/ directory structure, installs the user-facing Foundry agent, and generates model-routing stage agents. After initialisation, restart OpenCode and switch to the Foundry agent.
|
|
99
98
|
</FOUNDRY_CONTEXT>`;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
function buildFlowList(flows) {
|
|
103
102
|
if (flows.length === 0) {
|
|
104
|
-
return '- (no flows defined yet —
|
|
103
|
+
return '- (no flows defined yet — ask the Foundry agent to set one up)';
|
|
105
104
|
}
|
|
106
105
|
return flows.map(f => {
|
|
107
106
|
const sc = f.startingCycles.length > 0 ? ` — starting cycles: ${f.startingCycles.join(', ')}` : '';
|
|
@@ -121,34 +120,32 @@ The pipeline: assay (populate memory) → forge (produce) → quench (determinis
|
|
|
121
120
|
|
|
122
121
|
${flowList}
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
by name (e.g. "Creative Flow"), or by clear paraphrase (e.g. "the creative flow",
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
When the user references any flow above — by id (e.g. "creative-flow"),
|
|
124
|
+
by name (e.g. "Creative Flow"), or by clear paraphrase (e.g. "the creative flow",
|
|
125
|
+
"use the creative pipeline") — ask the Foundry agent to run that flow with the user's
|
|
126
|
+
request as the goal. The Foundry agent handles cycle selection, work-branch creation, and
|
|
127
|
+
orchestration automatically.
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
new skills). It does NOT apply to running an existing, defined flow.
|
|
129
|
+
## Foundry agent capabilities
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- **Pipeline:** assay, forge, quench, appraise, orchestrate, flow, human-appraise
|
|
136
|
-
- **Authoring:** add-artefact-type, add-law, add-appraiser, add-cycle, add-flow, add-memory-entity-type, add-memory-edge-type, add-extractor, init-foundry
|
|
137
|
-
- **Maintenance:** upgrade-foundry, refresh-agents, list-agents, init-memory, change-embedding-model, dry-run
|
|
138
|
-
- **Memory Admin:** drop-memory-entity-type, drop-memory-edge-type, rename-memory-entity-type, rename-memory-edge-type, reset-memory
|
|
131
|
+
The Foundry agent has internal workflows for pipeline execution, authoring, maintenance, memory administration, and dry-run trials. Present these capabilities as Foundry outcomes instead of naming internal skills.
|
|
139
132
|
|
|
140
133
|
## Multi-model routing
|
|
141
134
|
|
|
142
|
-
Foundry uses \`foundry-*\`
|
|
143
|
-
Run the \`refresh-agents\` skill to regenerate them after adding or removing providers.
|
|
144
|
-
Cycle definitions can specify per-stage models via the \`models\` frontmatter map. Appraisers can override with their own \`model\` field.
|
|
135
|
+
Foundry uses generated \`foundry-*\` stage agents for cycle stage dispatch. The user-facing \`Foundry\` agent is installed as \`.opencode/agents/foundry.md\` and should be used for authoring and running Foundry workflows.
|
|
145
136
|
|
|
146
137
|
All user content lives under foundry/.
|
|
147
138
|
Scripts are located at: ${path.join(packageRoot, 'scripts')}
|
|
148
139
|
</FOUNDRY_CONTEXT>`;
|
|
149
140
|
}
|
|
150
141
|
|
|
151
|
-
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
|
+
|
|
152
149
|
const foundryDir = path.join(directory, 'foundry');
|
|
153
150
|
const foundryExists = existsSync(foundryDir) && statSync(foundryDir).isDirectory();
|
|
154
151
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { refreshAgents } from './agent-refresh.js';
|
|
2
|
+
|
|
3
|
+
export function createRefreshAgentsTool({ tool }) {
|
|
4
|
+
return {
|
|
5
|
+
foundry_refresh_agents: tool({
|
|
6
|
+
description: 'Regenerate .opencode/agents/foundry-*.md stage-agent files from the currently available models.',
|
|
7
|
+
args: {},
|
|
8
|
+
async execute(_args, context) {
|
|
9
|
+
try {
|
|
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
|
+
}
|
|
17
|
+
return JSON.stringify(result);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return JSON.stringify({
|
|
20
|
+
ok: false,
|
|
21
|
+
error: `foundry_refresh_agents: ${err.message ?? String(err)}`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Foundry plugin for OpenCode.ai
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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';
|
|
@@ -29,11 +34,103 @@ import { createMemoryTools } from './foundry-tools/memory-tools.js';
|
|
|
29
34
|
import { createMemoryAdminTools } from './foundry-tools/memory-admin-tools.js';
|
|
30
35
|
import { createSnapshotTools } from './foundry-tools/snapshot-tools.js';
|
|
31
36
|
import { createAttestationTools } from './foundry-tools/attestation-tools.js';
|
|
37
|
+
import { createRefreshAgentsTool } from './foundry-tools/refresh-agents-tool.js';
|
|
32
38
|
|
|
33
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
40
|
const packageRoot = path.resolve(__dirname, '../..');
|
|
35
41
|
const allSkillsDir = path.join(packageRoot, 'skills');
|
|
36
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
|
+
|
|
37
134
|
export { buildCyclePromptExtras } from './foundry-tools/helpers.js';
|
|
38
135
|
|
|
39
136
|
function buildTools(createTool, pending) {
|
|
@@ -55,6 +152,7 @@ function buildTools(createTool, pending) {
|
|
|
55
152
|
...createMemoryAdminTools({ tool: createTool }),
|
|
56
153
|
...createSnapshotTools({ tool: createTool }),
|
|
57
154
|
...createAttestationTools({ tool: createTool }),
|
|
155
|
+
...createRefreshAgentsTool({ tool: createTool }),
|
|
58
156
|
};
|
|
59
157
|
}
|
|
60
158
|
|
|
@@ -82,10 +180,17 @@ export const FoundryPlugin = async ({ directory }) => {
|
|
|
82
180
|
if (!config.skills.paths.includes(allSkillsDir)) {
|
|
83
181
|
config.skills.paths.push(allSkillsDir);
|
|
84
182
|
}
|
|
183
|
+
|
|
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
|
+
}
|
|
85
190
|
},
|
|
86
191
|
|
|
87
192
|
'experimental.chat.messages.transform': async (_input, output) => {
|
|
88
|
-
const bootstrap = getBootstrapContent(directory, packageRoot);
|
|
193
|
+
const bootstrap = getBootstrapContent(directory, packageRoot, restartNeeded);
|
|
89
194
|
if (!bootstrap) return;
|
|
90
195
|
|
|
91
196
|
const firstUser = getFirstUserWithParts(output);
|
|
@@ -101,5 +206,9 @@ export const FoundryPlugin = async ({ directory }) => {
|
|
|
101
206
|
};
|
|
102
207
|
|
|
103
208
|
Object.defineProperty(plugin, Symbol.for('foundry.test.pending'), { value: pending });
|
|
209
|
+
Object.defineProperty(plugin, Symbol.for('foundry.test.restartNeeded'), {
|
|
210
|
+
get: () => restartNeeded,
|
|
211
|
+
configurable: true,
|
|
212
|
+
});
|
|
104
213
|
return plugin;
|
|
105
214
|
};
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,166 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.2.0] - 2026-05-14
|
|
4
|
+
|
|
5
|
+
Plugin auto-bootstrapping release. Foundry now ensures the guide agent is
|
|
6
|
+
present on every plugin load and deletes the now-redundant `init-foundry`
|
|
7
|
+
skill. The project also ships a publish-release skill and documented
|
|
8
|
+
workflows for plans and git worktrees.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Bootstrap logic** (`src/plugin.js`). On every `detectChanges` call,
|
|
13
|
+
Foundry ensures the guide agent file (`foundry.md`) exists in
|
|
14
|
+
`.opencode/agents/`, running the same deterministic agent-refresh path
|
|
15
|
+
that the `foundry_refresh_agents` tool uses. If the guide agent is
|
|
16
|
+
newly created, a post-install restart message is injected into the
|
|
17
|
+
detection context.
|
|
18
|
+
- **`publish-release` skill** (`.opencode/skills/publish-release/`). A
|
|
19
|
+
workflow skill that commits loose changes, runs the quality gate,
|
|
20
|
+
bumps the version, updates the changelog, tags, pushes, and publishes
|
|
21
|
+
to npm.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`init-foundry` skill removed.** The manual installation step is
|
|
26
|
+
replaced by deterministic auto-bootstrapping on plugin load.
|
|
27
|
+
`init-foundry` was the last on-ramp skill; new users no longer need
|
|
28
|
+
to run any skill after `pnpm add`.
|
|
29
|
+
- **Shared agent-refresh utility extracted** (`src/lib/agent-refresh.js`).
|
|
30
|
+
The deterministic agent generation logic that lived in
|
|
31
|
+
`scripts/tools/foundry_refresh_agents.js` is now a shared module,
|
|
32
|
+
consumed by both the tool and the `detectChanges` bootstrap path.
|
|
33
|
+
- **Phase reviews run in parallel** with partitioned iteration via
|
|
34
|
+
parallel implementer agents.
|
|
35
|
+
- **Implementer agent model** updated to `deepseek-v4-flash`.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Guide agent now always present after `detectChanges`.** Previously
|
|
40
|
+
the guide agent could be absent until `init-foundry` or
|
|
41
|
+
`foundry_refresh_agents` was explicitly run.
|
|
42
|
+
- **Guide agent preserved during `foundry_refresh_agents`.** The refresh
|
|
43
|
+
tool now ensures `foundry.md` is generated alongside stage agents,
|
|
44
|
+
not just preserved.
|
|
45
|
+
|
|
46
|
+
### Docs
|
|
47
|
+
|
|
48
|
+
- Git worktrees in `.worktrees/` directory documented.
|
|
49
|
+
- Plans directory workflow and phased planning skills documented.
|
|
50
|
+
|
|
51
|
+
## [3.1.0] - 2026-05-11
|
|
52
|
+
|
|
53
|
+
The Foundry guide agent release. Foundry now ships a user-facing agent that
|
|
54
|
+
routes configuration authoring through Foundry concepts — users ask the Foundry
|
|
55
|
+
agent for outcomes, and the agent composes dependent work internally rather than
|
|
56
|
+
handing users a chain of individual tools and skills.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Foundry guide agent** (`src/agents/foundry.md`). A user-facing agent with
|
|
61
|
+
identity marker *"You are the Foundry agent"* that maps user requests to
|
|
62
|
+
Foundry concepts, routes through the standard config-branch workflow, and
|
|
63
|
+
delegates to authoring skills internally.
|
|
64
|
+
- **`foundry_refresh_agents` tool.** Deterministic stage-agent generation: runs
|
|
65
|
+
`opencode models`, deletes stale `.opencode/agents/foundry-*.md` files, and
|
|
66
|
+
writes fresh agent files — one per available model. Replaces the prior
|
|
67
|
+
skill-only protocol where the LLM had to implement the logic with shell
|
|
68
|
+
commands.
|
|
69
|
+
- **`init-foundry` installs the guide agent.** Step 5 creates
|
|
70
|
+
`.opencode/agents/foundry.md` (preferring `dist/agents/foundry.md`, falling
|
|
71
|
+
back to `src/agents/foundry.md`), then instructs the user to restart OpenCode
|
|
72
|
+
and switch to the Foundry agent.
|
|
73
|
+
- **Comprehensive guidance audit tests.** `tests/skills/foundry-guidance-audit.test.js`
|
|
74
|
+
(357 lines) and `tests/skills/authoring-guidance.test.js` (45 lines) enforce
|
|
75
|
+
that skills avoid dead-end delegation patterns, route through the Foundry
|
|
76
|
+
agent, keep internal tool-call syntax out of normal user guidance, and handle
|
|
77
|
+
config branches and dependencies internally.
|
|
78
|
+
- **Packaging test** (`tests/plugin/packaging.test.js`). Verifies the published
|
|
79
|
+
package ships `dist/agents/` so `init-foundry` can locate the guide agent
|
|
80
|
+
template in a packaged install.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **Authoring skills reworked around the Foundry agent.** `add-flow`,
|
|
85
|
+
`add-cycle`, `add-artefact-type`, `add-law`, and `add-appraiser` now include a
|
|
86
|
+
`## Foundry Agent Preflight` section; and frame user requests as Foundry
|
|
87
|
+
outcomes, compose missing dependencies internally in validation order, and
|
|
88
|
+
hide internal tool-call syntax from normal user guidance (`foundry_git_branch`,
|
|
89
|
+
`foundry_git_finish`, `foundry_config_create_*`).
|
|
90
|
+
- **Memory/config skills handle branches and dependencies internally.**
|
|
91
|
+
`init-memory`, `reset-memory`, `add-memory-entity-type`, `add-memory-edge-type`,
|
|
92
|
+
`add-extractor`, and the `rename-memory-*` / `drop-memory-*` /
|
|
93
|
+
`change-embedding-model` skills no longer tell the user to create config
|
|
94
|
+
branches or run prerequisite skills. All use *"move to a suitable `config/*`
|
|
95
|
+
branch internally when the current branch is safe"* with `work/*` /
|
|
96
|
+
`dry-run/*/*` / uncommitted-change guards.
|
|
97
|
+
- **`add-extractor` composes cycle wiring internally.** The skill updates
|
|
98
|
+
relevant cycle definitions rather than presenting manual frontmatter editing
|
|
99
|
+
as the normal outcome.
|
|
100
|
+
- **`add-cycle` hides generated stage-agent files.** Model selection guidance
|
|
101
|
+
now references *"Available session models are listed in your session
|
|
102
|
+
configuration"* instead of exposing `.opencode/agents/foundry-*.md`.
|
|
103
|
+
- **Existing-file recovery keeps the agent in the loop.** When
|
|
104
|
+
`foundry_config_create_*` returns `{ ok: false }` because the target file
|
|
105
|
+
already exists, `add-artefact-type`, `add-appraiser`, `add-law`, and
|
|
106
|
+
`add-cycle` now read the existing content, incorporate the user's requested
|
|
107
|
+
changes, propose the merged result, and commit — rather than telling the user
|
|
108
|
+
to *"edit by hand."*
|
|
109
|
+
- **Bootstrap context** (`helpers.js`) presents capabilities as Foundry
|
|
110
|
+
outcomes (*"ask the Foundry agent to add flow memory"*, *"ask the Foundry
|
|
111
|
+
agent to run that flow"*) instead of listing internal skills.
|
|
112
|
+
- **`getting-started.md` reworked.** The walkthrough now routes through the
|
|
113
|
+
Foundry agent, removes direct `foundry_git_branch({` /
|
|
114
|
+
`foundry_git_finish({` / `Run \`add-*\`` / `foundry_config_validate_*({` /
|
|
115
|
+
`foundry_config_create_*({` references, and uses `pnpm add -D
|
|
116
|
+
@really-knows-ai/foundry`.
|
|
117
|
+
- **`README.md` quick-start** updated for the install → init → ask-agent
|
|
118
|
+
workflow (`pnpm add -D @really-knows-ai/foundry`, ask the Foundry agent
|
|
119
|
+
to add a haiku flow).
|
|
120
|
+
- **`refresh-agents` skill** delegates to `foundry_refresh_agents()`. The skill
|
|
121
|
+
is now a thin wrapper around the deterministic tool.
|
|
122
|
+
- **`init-foundry`, `list-agents`, `sort.js`, `upgrade-foundry`** updated to
|
|
123
|
+
reference the `foundry_refresh_agents` tool.
|
|
124
|
+
- **Docs** (`architecture.md`, `concepts.md`, `tools.md`) updated to reference
|
|
125
|
+
guide-agent installation and the refresh tool. Tool count increased from 66 to
|
|
126
|
+
67.
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- **`dist/agents/` added to the published package.** The build script already
|
|
131
|
+
copied `src/agents/` to `dist/agents/`, but the `files` array in
|
|
132
|
+
`package.json` excluded `dist/agents/`. `init-foundry` can now locate the
|
|
133
|
+
guide agent template in a packaged install.
|
|
134
|
+
- **`foundry_refresh_agents` preserves the guide agent.** Only
|
|
135
|
+
`foundry-*.md` stage agents are regenerated; `.opencode/agents/foundry.md`
|
|
136
|
+
survives refresh.
|
|
137
|
+
|
|
138
|
+
## [3.0.3] - 2026-05-11
|
|
139
|
+
|
|
140
|
+
A patch release that makes agent-file generation deterministic by moving
|
|
141
|
+
`refresh-agents` from a skill-only protocol into a tested plugin tool.
|
|
142
|
+
|
|
143
|
+
### Added
|
|
144
|
+
|
|
145
|
+
- **`foundry_refresh_agents` tool.** Runs `opencode models`, deletes stale
|
|
146
|
+
`.opencode/agents/foundry-*.md` files, and generates fresh agent files — one
|
|
147
|
+
per available model. The tool is idempotent, handles missing directories, and
|
|
148
|
+
returns `{ ok: true, count: <n> }` on success. This replaces the prior
|
|
149
|
+
skill-only protocol where the LLM had to implement the logic with shell
|
|
150
|
+
commands, which was error-prone and non-deterministic.
|
|
151
|
+
|
|
152
|
+
### Changed
|
|
153
|
+
|
|
154
|
+
- **`refresh-agents` skill** now simply calls `foundry_refresh_agents()` and
|
|
155
|
+
reports the result.
|
|
156
|
+
- **`init-foundry` skill** step 4 now calls `foundry_refresh_agents()` instead
|
|
157
|
+
of instructing the LLM to run the `refresh-agents` skill.
|
|
158
|
+
- **`list-agents` skill** error message now references the tool.
|
|
159
|
+
- **`sort.js`** missing-agent error now references the tool.
|
|
160
|
+
- **`helpers.js`** bootstrap message now references the tool.
|
|
161
|
+
- **Docs** (`tools.md`, `getting-started.md`, `architecture.md`, `concepts.md`)
|
|
162
|
+
updated to reference the tool. Tool count increased from 65 to 66.
|
|
163
|
+
|
|
3
164
|
## [3.0.2] - 2026-05-11
|
|
4
165
|
|
|
5
166
|
A documentation and tool-correctness patch driven by a failing
|