@really-knows-ai/foundry 1.4.0 → 1.5.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.
- package/.opencode/plugins/foundry.js +16 -8
- package/package.json +1 -1
- package/scripts/lib/config.js +1 -1
- package/scripts/lib/workfile.js +39 -0
- package/skills/add-artefact-type/SKILL.md +5 -1
- package/skills/appraise/SKILL.md +1 -1
- package/skills/forge/SKILL.md +1 -1
- package/skills/hitl/SKILL.md +1 -1
- package/skills/quench/SKILL.md +1 -1
- package/skills/sort/SKILL.md +3 -12
|
@@ -13,7 +13,7 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync } from
|
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
import { tool } from '@opencode-ai/plugin';
|
|
15
15
|
import { loadHistory, appendEntry, getIteration } from '../../scripts/lib/history.js';
|
|
16
|
-
import { parseFrontmatter, createWorkfile, setFrontmatterField, getFrontmatterField, enrichStages } from '../../scripts/lib/workfile.js';
|
|
16
|
+
import { parseFrontmatter, createWorkfile, setFrontmatterField, getFrontmatterField, enrichStages, parseStagesValue, parseModelsValue } from '../../scripts/lib/workfile.js';
|
|
17
17
|
import { parseArtefactsTable, addArtefactRow, setArtefactStatus } from '../../scripts/lib/artefacts.js';
|
|
18
18
|
import { addFeedbackItem, actionFeedbackItem, wontfixFeedbackItem, resolveFeedbackItem, listFeedback } from '../../scripts/lib/feedback.js';
|
|
19
19
|
import { getCycleDefinition, getArtefactType, getLaws, getValidation, getAppraisers, getFlow, selectAppraisers } from '../../scripts/lib/config.js';
|
|
@@ -141,7 +141,7 @@ export const FoundryPlugin = async ({ directory }) => {
|
|
|
141
141
|
}
|
|
142
142
|
const fm = { flow: args.flow, cycle: args.cycle, stages: enrichStages(args.stages, args.cycle), maxIterations: args.maxIterations };
|
|
143
143
|
if (args.models) {
|
|
144
|
-
|
|
144
|
+
fm.models = parseModelsValue(args.models);
|
|
145
145
|
}
|
|
146
146
|
const content = createWorkfile(fm, args.goal);
|
|
147
147
|
writeFileSync(workPath, content, 'utf-8');
|
|
@@ -179,13 +179,21 @@ export const FoundryPlugin = async ({ directory }) => {
|
|
|
179
179
|
const text = readFileSync(workPath, 'utf-8');
|
|
180
180
|
// Parse JSON values for arrays/objects, keep strings as-is
|
|
181
181
|
let value = args.value;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
if (args.key === 'stages') {
|
|
183
|
+
// Always parse stages into an array (handles JSON arrays and comma-separated strings)
|
|
184
|
+
value = parseStagesValue(args.value);
|
|
185
|
+
} else if (args.key === 'models') {
|
|
186
|
+
// Always parse models into an object (handles JSON objects and "key: value" strings)
|
|
187
|
+
value = parseModelsValue(args.value);
|
|
188
|
+
} else {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(args.value);
|
|
191
|
+
if (typeof parsed === 'object' || Array.isArray(parsed) || typeof parsed === 'number') {
|
|
192
|
+
value = parsed;
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// Not JSON, use as plain string
|
|
186
196
|
}
|
|
187
|
-
} catch {
|
|
188
|
-
// Not JSON, use as plain string
|
|
189
197
|
}
|
|
190
198
|
// Auto-enrich bare stage names with cycle ID alias
|
|
191
199
|
if (args.key === 'stages' && Array.isArray(value)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@really-knows-ai/foundry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "A structured framework for AI-driven artefact creation with deterministic routing, quality gates, and iterative refinement cycles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": ".opencode/plugins/foundry.js",
|
package/scripts/lib/config.js
CHANGED
|
@@ -114,7 +114,7 @@ export async function getValidation(foundryDir, typeId, io) {
|
|
|
114
114
|
} else if (currentId) {
|
|
115
115
|
const cmdMatch = line.match(/^Command:\s*(.+)/);
|
|
116
116
|
const failMatch = line.match(/^Failure means:\s*(.+)/);
|
|
117
|
-
if (cmdMatch) currentCommand = cmdMatch[1].trim();
|
|
117
|
+
if (cmdMatch) currentCommand = cmdMatch[1].trim().replace(/^`|`$/g, '');
|
|
118
118
|
if (failMatch) currentFailure = failMatch[1].trim();
|
|
119
119
|
}
|
|
120
120
|
}
|
package/scripts/lib/workfile.js
CHANGED
|
@@ -47,6 +47,45 @@ export function enrichStages(stages, cycleId) {
|
|
|
47
47
|
return stages.map(s => s.includes(':') ? s : `${s}:${cycleId}`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Parse a stages value from tool input.
|
|
52
|
+
* Accepts JSON array string or comma-separated string.
|
|
53
|
+
* Always returns an array of trimmed, non-empty strings.
|
|
54
|
+
*/
|
|
55
|
+
export function parseStagesValue(raw) {
|
|
56
|
+
// Try JSON first
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (Array.isArray(parsed)) return parsed;
|
|
60
|
+
} catch { /* not JSON */ }
|
|
61
|
+
// Fall back to comma-separated
|
|
62
|
+
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse a models value from tool input.
|
|
67
|
+
* Accepts JSON object string or "key: value, key: value" string.
|
|
68
|
+
* Always returns an object mapping stage base names to model IDs.
|
|
69
|
+
*/
|
|
70
|
+
export function parseModelsValue(raw) {
|
|
71
|
+
if (!raw || !raw.trim()) return {};
|
|
72
|
+
// Try JSON first
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
if (typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
|
76
|
+
} catch { /* not JSON */ }
|
|
77
|
+
// Fall back to "key: value, key: value" format
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const part of raw.split(',')) {
|
|
80
|
+
const colonIdx = part.indexOf(':');
|
|
81
|
+
if (colonIdx === -1) continue;
|
|
82
|
+
const key = part.slice(0, colonIdx).trim();
|
|
83
|
+
const val = part.slice(colonIdx + 1).trim();
|
|
84
|
+
if (key && val) result[key] = val;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
50
89
|
// ---------------------------------------------------------------------------
|
|
51
90
|
// Workfile creation
|
|
52
91
|
// ---------------------------------------------------------------------------
|
|
@@ -128,7 +128,11 @@ If yes, walk through each validation entry:
|
|
|
128
128
|
- A `Command:` line with `{file}` placeholder
|
|
129
129
|
- A `Failure means:` line explaining what a non-zero exit indicates
|
|
130
130
|
|
|
131
|
-
If the user wants validation scripts (not just inline commands), create them as separate files in the artefact type directory.
|
|
131
|
+
If the user wants validation scripts (not just inline commands), create them as separate files in the artefact type directory.
|
|
132
|
+
|
|
133
|
+
**Use existing libraries:** Before writing custom validation logic, search npm for well-tested libraries that solve the problem (e.g., `syllable` for syllable counting, `natural` for NLP tasks). Hand-rolled heuristics are fragile — prefer battle-tested packages. Install them as project dependencies.
|
|
134
|
+
|
|
135
|
+
Check the project's `package.json` for `"type": "module"`:
|
|
132
136
|
- If ESM (`"type": "module"`): use `import` syntax, or name scripts with `.mjs` extension
|
|
133
137
|
- If CommonJS (no `"type"` field or `"type": "commonjs"`): `require()` is fine, or use `.cjs` extension
|
|
134
138
|
- When in doubt, use `.mjs` or `.cjs` extensions to be explicit regardless of project settings
|
package/skills/appraise/SKILL.md
CHANGED
|
@@ -91,7 +91,7 @@ If there are no issues, return an empty list.
|
|
|
91
91
|
|
|
92
92
|
## History
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Do NOT call `foundry_history_append` — the sort skill (your caller) is responsible for writing history. Instead, return a clear summary of what you found (e.g., "3 issues found across 2 appraisers" or "No issues found") so sort can log it.
|
|
95
95
|
|
|
96
96
|
## What you do NOT do
|
|
97
97
|
|
package/skills/forge/SKILL.md
CHANGED
|
@@ -40,7 +40,7 @@ Before running this skill, verify that the `foundry/` directory exists in the pr
|
|
|
40
40
|
|
|
41
41
|
### After (both paths)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Do NOT call `foundry_history_append` — the sort skill (your caller) is responsible for writing history. Instead, return a clear summary of what you did so sort can log it.
|
|
44
44
|
|
|
45
45
|
## Unresolved feedback
|
|
46
46
|
|
package/skills/hitl/SKILL.md
CHANGED
|
@@ -36,7 +36,7 @@ Before running this skill, verify that the `foundry/` directory exists in the pr
|
|
|
36
36
|
- **Provide context** — note in the history comment for future stages to reference
|
|
37
37
|
- **Abort** — call `foundry_artefacts_set_status` with status `"blocked"`, cycle ends
|
|
38
38
|
|
|
39
|
-
5.
|
|
39
|
+
5. Do NOT call `foundry_history_append` — the sort skill (your caller) is responsible for writing history. Instead, return a clear summary of what the human said or decided so sort can log it.
|
|
40
40
|
|
|
41
41
|
6. Return control to the sort skill.
|
|
42
42
|
|
package/skills/quench/SKILL.md
CHANGED
|
@@ -35,7 +35,7 @@ There is no wont-fix for validation feedback. Deterministic rules are not negoti
|
|
|
35
35
|
|
|
36
36
|
## History
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Do NOT call `foundry_history_append` — the sort skill (your caller) is responsible for writing history. Instead, return a clear summary of what you found (e.g., "2 validation issues found" or "Validation passed") so sort can log it.
|
|
39
39
|
|
|
40
40
|
## What you do NOT do
|
|
41
41
|
|
package/skills/sort/SKILL.md
CHANGED
|
@@ -29,22 +29,13 @@ Before running this skill, verify that the `foundry/` directory exists in the pr
|
|
|
29
29
|
- `blocked` — foundry cycle is blocked (iteration limit hit with unresolved feedback), return to the cycle skill
|
|
30
30
|
- `violation` — file modification or tag validation violation detected (see `details`). The cycle halts — call `foundry_artefacts_set_status` with status `"blocked"`, and return to the cycle skill
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
4. After the subagent completes, call `foundry_history_append` with the current cycle, the **dispatched stage alias** (e.g., `forge:write-haiku`), and a comment summarizing what the subagent reported doing. This is critical — sort is the only reliable writer of stage history. Subagents must NOT write their own history entries.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- If `model` is set (e.g., `openai/gpt-4o`):
|
|
37
|
-
- Convert to agent name: `foundry-openai-gpt-4o`
|
|
38
|
-
- Dispatch with `subagent_type: "foundry-openai-gpt-4o"`
|
|
39
|
-
- If no agent with that name exists, **hard fail**: "Cycle specifies model `<model>` for stage `<base>` but no matching agent `foundry-<name>` is registered. Check your OpenCode provider config."
|
|
40
|
-
- If `model` is null:
|
|
41
|
-
- Dispatch with `subagent_type: "general"` (inherits session model)
|
|
42
|
-
|
|
43
|
-
4. After the invoked skill completes, call `foundry_sort` again. Repeat until it returns `done`, `blocked`, or `violation`.
|
|
34
|
+
5. After logging the stage history, call `foundry_sort` again. Repeat from step 1 until it returns `done`, `blocked`, or `violation`.
|
|
44
35
|
|
|
45
36
|
## What you do NOT do
|
|
46
37
|
|
|
47
38
|
- You do not make routing decisions yourself — the tool decides
|
|
48
39
|
- You do not skip calling `foundry_sort`
|
|
49
40
|
- You do not override the tool's output
|
|
50
|
-
- You do not skip the history entry — every sort invocation
|
|
41
|
+
- You do not skip the history entry — every sort invocation gets a `sort` entry, and every completed stage gets a stage entry (e.g., `forge:write-haiku`). You are the sole writer of history.
|