@really-knows-ai/foundry 3.8.1 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.opencode/plugins/foundry-tools/stage-tools.js +30 -4
- package/dist/CHANGELOG.md +14 -0
- package/dist/scripts/lib/feedback-transitions.js +1 -3
- package/dist/scripts/lib/forge-contract.js +19 -30
- package/dist/scripts/lib/sort-reason.js +1 -1
- package/dist/scripts/lib/stage-calls.js +4 -0
- package/dist/scripts/orchestrate-cycle.js +10 -10
- package/dist/skills/forge/SKILL.md +18 -23
- package/dist/skills/orchestrate/SKILL.md +3 -5
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { syncStore } from '../../../scripts/lib/memory/store.js';
|
|
|
8
8
|
import { makeIO, makeMemoryIO, branchIoFactory, asyncIoFactory, flowBranchGuard } from './helpers.js';
|
|
9
9
|
import { markWorkfileFailed, readFailedStatus, clearWorkfileFailed } from '../../../scripts/lib/failed-flow.js';
|
|
10
10
|
import { guarded, notFailedGuard } from '../../../scripts/lib/guards.js';
|
|
11
|
-
import { initForgeCallLog,
|
|
11
|
+
import { initForgeCallLog, readForgeCallSet } from '../../../scripts/lib/stage-calls.js';
|
|
12
12
|
import { openFeedbackStore } from '../../../scripts/lib/feedback-store.js';
|
|
13
13
|
|
|
14
14
|
const FORGE_REQUIRED_TOOLS = [
|
|
@@ -18,6 +18,12 @@ const FORGE_REQUIRED_TOOLS = [
|
|
|
18
18
|
'foundry_config_laws',
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
+
const FORGE_FORBIDDEN_TOOLS = [
|
|
22
|
+
'foundry_feedback_action',
|
|
23
|
+
'foundry_feedback_wontfix',
|
|
24
|
+
'foundry_feedback_resolve',
|
|
25
|
+
];
|
|
26
|
+
|
|
21
27
|
function stageBase(stage) { return stage.split(':')[0]; }
|
|
22
28
|
|
|
23
29
|
const gateNotFailed = notFailedGuard(makeIO);
|
|
@@ -25,9 +31,16 @@ const gateNotFailed = notFailedGuard(makeIO);
|
|
|
25
31
|
// -- Helpers for forge tool call verification --
|
|
26
32
|
|
|
27
33
|
function verifyAndManageForgeTools(io, active) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
const callSet = readForgeCallSet(io);
|
|
35
|
+
const forbidden = FORGE_FORBIDDEN_TOOLS.filter(t => callSet.has(t));
|
|
36
|
+
const missing = FORGE_REQUIRED_TOOLS.filter(t => !callSet.has(t));
|
|
37
|
+
io.unlink('.foundry/.forge-tool-calls.jsonl');
|
|
38
|
+
if (forbidden.length) {
|
|
39
|
+
postForbiddenToolsFeedback(io, active, forbidden);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (missing.length) {
|
|
43
|
+
postMissingToolsFeedback(io, active, missing);
|
|
31
44
|
return;
|
|
32
45
|
}
|
|
33
46
|
resolveSystemFeedback(io, active);
|
|
@@ -144,6 +157,19 @@ async function executeStageEnd(args, context) {
|
|
|
144
157
|
return JSON.stringify({ ok: true, summary: args.summary });
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
function postForbiddenToolsFeedback(io, active, forbidden) {
|
|
161
|
+
try {
|
|
162
|
+
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
|
163
|
+
store.add({
|
|
164
|
+
file: '(forge)',
|
|
165
|
+
tag: 'system:forbidden-tool-calls',
|
|
166
|
+
text: `Forbidden forge tool calls: ${forbidden.join(', ')}. Forge subagents do not manage feedback — the orchestrator handles transitions.`,
|
|
167
|
+
source: active.stage,
|
|
168
|
+
cycle: active.cycle,
|
|
169
|
+
});
|
|
170
|
+
} catch { /* feedback file not initialised yet; non-critical */ }
|
|
171
|
+
}
|
|
172
|
+
|
|
147
173
|
function postMissingToolsFeedback(io, active, missing) {
|
|
148
174
|
try {
|
|
149
175
|
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.8.2] - 2026-05-27
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Forge subagent protocol simplified to three keywords: `DONE` (first generation), `ACTIONED` (file changed), `WONT-FIX: <justification>` (no changes needed). The `foundry_stage_end` summary must be exactly one of these — no descriptions, no explanations. The forge contract recognises `ACTIONED` even without a version change.
|
|
8
|
+
|
|
9
|
+
- WONT-FIX is now allowed for all feedback source types (quench, appraise, human-appraise), not just appraise. The `WONT-FIX:` keyword in the summary transitions the item regardless of source.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Forge subagents are blocked from calling `foundry_feedback_action`, `foundry_feedback_wontfix`, or `foundry_feedback_resolve` — `foundry_stage_end` checks the call log and posts `system:forbidden-tool-calls` feedback if any were called.
|
|
14
|
+
|
|
15
|
+
- Single-item dispatch prompts clarified: the orchestrator dispatches one feedback item per forge call. The orchestrate skill instructs the LLM to pass the prompt verbatim without injecting extra items from quench output.
|
|
16
|
+
|
|
3
17
|
## [3.8.1] - 2026-05-27
|
|
4
18
|
|
|
5
19
|
### Fixed
|
|
@@ -99,7 +99,5 @@ export function hashText(text) {
|
|
|
99
99
|
*/
|
|
100
100
|
export function canForgeWontFix(item, callerStageBase) {
|
|
101
101
|
if (callerStageBase !== 'forge') return false;
|
|
102
|
-
|
|
103
|
-
const sourceBase = item.source.split(':')[0];
|
|
104
|
-
return sourceBase === 'appraise';
|
|
102
|
+
return !!(item && typeof item.source === 'string' && item.source);
|
|
105
103
|
}
|
|
@@ -45,28 +45,18 @@ function handleVersionChanged(item, feedbackStore, cycleId, postVersion) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function handleWontFixWithReason(item, feedbackStore, cycleId, postVersion, reason) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
postSystemFeedback(feedbackStore, cycleId, postVersion, result.error || 'store transition failed');
|
|
59
|
-
feedbackStore.forceState(item.id, 'open', cycleId, `forge:${cycleId}`);
|
|
60
|
-
}
|
|
61
|
-
return { contractPassed: result.ok };
|
|
48
|
+
const result = feedbackStore.transition({
|
|
49
|
+
id: item.id,
|
|
50
|
+
target: 'wont-fix',
|
|
51
|
+
stage: 'forge:' + cycleId,
|
|
52
|
+
cycle: cycleId,
|
|
53
|
+
reason,
|
|
54
|
+
});
|
|
55
|
+
if (!result.ok) {
|
|
56
|
+
postSystemFeedback(feedbackStore, cycleId, postVersion, result.error || 'store transition failed');
|
|
57
|
+
feedbackStore.forceState(item.id, 'open', cycleId, `forge:${cycleId}`);
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
postSystemFeedback(
|
|
65
|
-
feedbackStore, cycleId, postVersion,
|
|
66
|
-
`wont-fix not allowed on ${sourceBase}-sourced item; wont-fix is only allowed for appraise-sourced items`,
|
|
67
|
-
);
|
|
68
|
-
feedbackStore.forceState(item.id, 'open', cycleId, `forge:${cycleId}`);
|
|
69
|
-
return { contractPassed: false };
|
|
59
|
+
return { contractPassed: result.ok };
|
|
70
60
|
}
|
|
71
61
|
|
|
72
62
|
/**
|
|
@@ -81,24 +71,23 @@ function handleWontFixWithReason(item, feedbackStore, cycleId, postVersion, reas
|
|
|
81
71
|
* @returns {{ contractPassed: boolean }}
|
|
82
72
|
*/
|
|
83
73
|
export function enforceForgeContract({ item, preVersion, postVersion, summary, feedbackStore, cycleId }) {
|
|
84
|
-
// No item means forge had no prior feedback to respond to.
|
|
85
74
|
if (!item) return { contractPassed: true };
|
|
86
75
|
|
|
87
|
-
// Version changed → forge fixed the issue
|
|
88
|
-
if (preVersion !== postVersion) {
|
|
89
|
-
return handleVersionChanged(item, feedbackStore, cycleId, postVersion);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Version unchanged — check for WONT-FIX justification
|
|
93
76
|
const wontFixMatch = summary.match(/WONT-FIX:\s*(.+)/);
|
|
77
|
+
const versionChanged = preVersion !== postVersion;
|
|
78
|
+
const actioned = summary.trim() === 'ACTIONED';
|
|
79
|
+
|
|
94
80
|
if (wontFixMatch) {
|
|
95
81
|
return handleWontFixWithReason(item, feedbackStore, cycleId, postVersion, wontFixMatch[1]);
|
|
96
82
|
}
|
|
97
83
|
|
|
98
|
-
|
|
84
|
+
if (versionChanged || actioned) {
|
|
85
|
+
return handleVersionChanged(item, feedbackStore, cycleId, postVersion);
|
|
86
|
+
}
|
|
87
|
+
|
|
99
88
|
postSystemFeedback(
|
|
100
89
|
feedbackStore, cycleId, postVersion,
|
|
101
|
-
'forge did not change artefacts and did not provide WONT-FIX justification',
|
|
90
|
+
'forge did not change artefacts and did not provide ACTIONED or WONT-FIX justification',
|
|
102
91
|
);
|
|
103
92
|
feedbackStore.forceState(item.id, 'open', cycleId, `forge:${cycleId}`);
|
|
104
93
|
return { contractPassed: false };
|
|
@@ -35,7 +35,7 @@ function forgeReason(d) {
|
|
|
35
35
|
if (d.forgeCount === 0 && d.needingForge === 0) {
|
|
36
36
|
return `starting cycle — routing to forge (iteration 1 of ${d.maxIt})`;
|
|
37
37
|
}
|
|
38
|
-
return `found ${d.needingForge} unresolved feedback item(s) —
|
|
38
|
+
return `found ${d.needingForge} unresolved feedback item(s) — dispatching one item at a time to forge (revision ${d.forgeCount + 1} of ${d.maxIt})`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function appraiseReason(d) {
|
|
@@ -29,6 +29,10 @@ function readCallSet(io) {
|
|
|
29
29
|
return called;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function readForgeCallSet(io) {
|
|
33
|
+
return readCallSet(io);
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function verifyAndClearForgeCallLog(io, expected) {
|
|
33
37
|
const called = readCallSet(io);
|
|
34
38
|
const missing = expected.filter(t => !called.has(t));
|
|
@@ -266,15 +266,17 @@ function buildForgePromptLines({ cycle, outputType, forgeItem }) {
|
|
|
266
266
|
`File: ${forgeItem.file}`,
|
|
267
267
|
`Issue: ${forgeItem.text}`,
|
|
268
268
|
``,
|
|
269
|
-
`
|
|
270
|
-
`
|
|
271
|
-
`
|
|
272
|
-
` b) If this is an appraise-sourced item (subjective quality`,
|
|
273
|
-
` feedback), you may respond with:`,
|
|
274
|
-
` WONT-FIX: <justification for why you disagree>`,
|
|
269
|
+
`Respond with EXACTLY one of:`,
|
|
270
|
+
` - ACTIONED — fix the issue by changing the artefact file`,
|
|
271
|
+
` - WONT-FIX: <justification> — the issue is already resolved or does not apply`,
|
|
275
272
|
``,
|
|
276
|
-
`
|
|
277
|
-
|
|
273
|
+
`Write NOTHING else in the stage_end summary — no descriptions, no explanations.`,
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
lines.push(
|
|
277
|
+
``,
|
|
278
|
+
`First generation — no feedback to address yet.`,
|
|
279
|
+
`Produce the artefact and call foundry_stage_end({summary: "DONE"}).`,
|
|
278
280
|
);
|
|
279
281
|
}
|
|
280
282
|
return lines;
|
|
@@ -300,8 +302,6 @@ export function renderDispatchPrompt({ stage, cycle, token, cwd, filePatterns, o
|
|
|
300
302
|
``,
|
|
301
303
|
`Your FIRST tool call MUST be foundry_stage_begin({stage, cycle, token}) using the values above.`,
|
|
302
304
|
`Your LAST tool call MUST be foundry_stage_end({summary}).`,
|
|
303
|
-
``,
|
|
304
|
-
`When done, report back a brief summary. Do NOT call foundry_history_append, foundry_git_commit, or foundry_artefacts_add — the orchestrator handles all of those.`
|
|
305
305
|
);
|
|
306
306
|
return lines.join('\n');
|
|
307
307
|
}
|
|
@@ -51,43 +51,38 @@ Forge runs inside an enforced stage. Your **first** and **last** tool calls are
|
|
|
51
51
|
- Read the selected files for context.
|
|
52
52
|
7. Produce the artefact, respecting all applicable laws from the start.
|
|
53
53
|
8. Write the artefact file to a location that matches the artefact type's `file-patterns`.
|
|
54
|
-
9. `foundry_stage_end({summary})`.
|
|
54
|
+
9. `foundry_stage_end({summary: "DONE"})`.
|
|
55
55
|
|
|
56
56
|
### Revision (feedback exists)
|
|
57
57
|
|
|
58
58
|
1. `foundry_stage_begin(...)`.
|
|
59
59
|
2. Read the artefact file.
|
|
60
60
|
3. If the cycle declares `inputs`, discover them via filesystem scan against each input type's `file-patterns` (same protocol as first-generation step 6). Re-read the relevant files — they may have changed on disk since the previous iteration (nothing in this cycle wrote to them, but the user may have modified them between iterations).
|
|
61
|
-
4. Address the single feedback item from the dispatch prompt following the feedback handling rules below
|
|
62
|
-
5. Update the artefact file.
|
|
63
|
-
6. `foundry_stage_end({summary})`.
|
|
61
|
+
4. Address the single feedback item from the dispatch prompt following the feedback handling rules below.
|
|
62
|
+
5. Update the artefact file (if fixing), or skip (if WONT-FIX).
|
|
63
|
+
6. `foundry_stage_end({summary})`. The summary must be EXACTLY one of:
|
|
64
|
+
- `"ACTIONED"` — file was changed to address the feedback
|
|
65
|
+
- `"WONT-FIX: <justification>"` — item already resolved or does not apply
|
|
66
|
+
Write NOTHING else in the summary.
|
|
64
67
|
|
|
65
68
|
## Feedback handling
|
|
66
69
|
|
|
67
|
-
The dispatch prompt
|
|
68
|
-
iteration. Each item has the shape `{ id, file, tag, text, source, state,
|
|
69
|
-
depth, reason? }`.
|
|
70
|
+
The dispatch prompt contains one feedback item to address.
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
**To fix the issue** — change the artefact file and call
|
|
73
|
+
`foundry_stage_end({summary: "ACTIONED"})`.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
**If the issue is already resolved** — call
|
|
76
|
+
`foundry_stage_end({summary: "WONT-FIX: <justification>"})`.
|
|
77
|
+
Do NOT change the file.
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
`
|
|
80
|
-
**must** be fixed. There is no wont-fix option for these.
|
|
79
|
+
**If the issue does not apply** (appraise judgement you disagree with) — same
|
|
80
|
+
`WONT-FIX:` flow.
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
returns `{ ok, id, deduped }`. `deduped: true` means an existing
|
|
84
|
-
non-resolved item with the same `(file, tag, hash(text))` was found and no
|
|
85
|
-
new item was written; the returned `id` is the existing item's id.
|
|
86
|
-
`deduped: false` means a new item was created.
|
|
82
|
+
The summary is ONLY one of these keywords. No descriptions, no explanations.
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
human-appraise can override any non-resolved item.
|
|
84
|
+
Do NOT call `foundry_feedback_action`, `foundry_feedback_wontfix`, or
|
|
85
|
+
`foundry_feedback_resolve`. The orchestrator handles transitions automatically.
|
|
91
86
|
|
|
92
87
|
## Write invariant
|
|
93
88
|
|
|
@@ -45,11 +45,8 @@ task tool:
|
|
|
45
45
|
description: "Run <stage> for <cycle>"
|
|
46
46
|
prompt: <prompt-from-payload — pass verbatim>
|
|
47
47
|
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
description: "Run <stage> for <cycle>"
|
|
51
|
-
prompt: <prompt-from-payload — pass verbatim>
|
|
52
|
-
```
|
|
48
|
+
|
|
49
|
+
**Critical for forge dispatch:** The orchestrator dispatches one feedback item per forge subagent call. The `prompt` already contains exactly one `FEEDBACK ITEM TO ADDRESS`. Pass the prompt verbatim — do NOT read quench output, do NOT add additional feedback items, do NOT inject validator results. The orchestrator will dispatch a separate `task()` call for each unresolved item.
|
|
53
50
|
|
|
54
51
|
When the task returns, call `foundry_orchestrate({lastResult: {ok: true}})`. If the task tool itself errored or reported a subagent crash, pass `{ok: false, error: '<message>'}`.
|
|
55
52
|
|
|
@@ -114,6 +111,7 @@ Report to the user: "Cycle halted (violation): `<details>`. Affected files: `<af
|
|
|
114
111
|
- You do NOT mint, modify, or cache tokens. The `prompt` from orchestrate already contains the token verbatim.
|
|
115
112
|
- `foundry_history_append`, `foundry_git_commit`, `foundry_stage_finalize`, and `foundry_sort` are not registered tools; orchestrate handles them internally via the loop.
|
|
116
113
|
- You do NOT reorder the protocol. `foundry_orchestrate` returns, you act, you call back. Nothing else between.
|
|
114
|
+
- You do NOT add extra feedback items to the forge dispatch prompt. The orchestrator dispatches one item at a time. Each prompt already contains exactly one `FEEDBACK ITEM TO ADDRESS`. Do not read quench output and inject additional items.
|
|
117
115
|
|
|
118
116
|
## Feedback visibility
|
|
119
117
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@really-knows-ai/foundry",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
4
4
|
"description": "A skill-driven framework for governed artefact generation with AI coding tools. Define your own artefact types, laws, and flows — Foundry handles the forge → quench → appraise pipeline with deterministic routing, quality gates, and iterative refinement.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/.opencode/plugins/foundry.js",
|