@really-knows-ai/foundry 3.5.9 → 3.6.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/dist/.opencode/plugins/foundry-tools/feedback-tools.js +9 -5
- package/dist/.opencode/plugins/foundry-tools/orchestrate-tool.js +3 -1
- package/dist/CHANGELOG.md +25 -0
- package/dist/scripts/appraise-module.js +69 -7
- package/dist/scripts/lib/artefacts.js +43 -1
- package/dist/scripts/lib/feedback-store.js +34 -8
- package/dist/scripts/lib/finalize.js +10 -2
- package/dist/scripts/lib/forge-contract.js +93 -0
- package/dist/scripts/lib/history.js +2 -1
- package/dist/scripts/lib/sort-reason.js +3 -1
- package/dist/scripts/lib/sort-routing.js +172 -128
- package/dist/scripts/lib/workfile.js +28 -0
- package/dist/scripts/orchestrate-phases.js +49 -39
- package/dist/scripts/orchestrate-terminals.js +37 -2
- package/dist/scripts/orchestrate.js +62 -5
- package/dist/scripts/quench-module.js +54 -12
- package/dist/scripts/sort.js +28 -11
- package/package.json +1 -1
|
@@ -8,6 +8,9 @@ import matter from 'gray-matter';
|
|
|
8
8
|
import { readActiveStage, readLastStage, writeActiveStage, clearActiveStage } from './lib/state.js';
|
|
9
9
|
import { stageBaseOf } from './lib/stage-guard.js';
|
|
10
10
|
import { ulid as defaultUlid } from './lib/ulid.js';
|
|
11
|
+
import { computeArtefactVersion } from './lib/artefacts.js';
|
|
12
|
+
import { enforceForgeContract } from './lib/forge-contract.js';
|
|
13
|
+
import { loadHistory } from './lib/history.js';
|
|
11
14
|
import {
|
|
12
15
|
readCycleTargets,
|
|
13
16
|
readForgeFilePatterns,
|
|
@@ -138,7 +141,10 @@ function buildFeedback(cycleId, stageId, io) {
|
|
|
138
141
|
return {
|
|
139
142
|
add: (item) => {
|
|
140
143
|
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
|
141
|
-
return store.add({
|
|
144
|
+
return store.add({
|
|
145
|
+
file: item.file, tag: item.tag, text: item.text, source: stageId, cycle: cycleId,
|
|
146
|
+
artefact_version: item.artefact_version,
|
|
147
|
+
});
|
|
142
148
|
},
|
|
143
149
|
list: (query) => {
|
|
144
150
|
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
|
@@ -159,7 +165,7 @@ function buildQuenchContext(cycleId, args, io) {
|
|
|
159
165
|
const stageId = `quench:${cycleId}`;
|
|
160
166
|
return { cycleId, stageId, io, git: args.git, finalize: buildFinalizeWrapper(cycleId, args, io),
|
|
161
167
|
now: args.now, ulid: args.ulid, mint: args.mint, foundryDir: 'foundry', defaultModel: args.defaultModel,
|
|
162
|
-
baseBranch: args.baseBranch ?? 'main',
|
|
168
|
+
baseBranch: args.baseBranch ?? 'main', cwd: args.cwd ?? process.cwd(),
|
|
163
169
|
feedback: buildFeedback(cycleId, stageId, io) };
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -167,7 +173,7 @@ function buildAppraiseCtx(cycleId, args, io) {
|
|
|
167
173
|
const stageId = `appraise:${cycleId}`;
|
|
168
174
|
return { cycleId, io, git: args.git, finalize: buildFinalizeWrapper(cycleId, args, io),
|
|
169
175
|
foundryDir: 'foundry', defaultModel: args.defaultModel,
|
|
170
|
-
baseBranch: args.baseBranch ?? 'main',
|
|
176
|
+
baseBranch: args.baseBranch ?? 'main', cwd: args.cwd ?? process.cwd(),
|
|
171
177
|
activeStage: readActiveStage(io), lastStage: readLastStage(io),
|
|
172
178
|
feedback: buildFeedback(cycleId, stageId, io) };
|
|
173
179
|
}
|
|
@@ -184,6 +190,42 @@ function writeStageRecord(io, cycleId, route) {
|
|
|
184
190
|
writeActiveStage(io, { cycle: cycleId, stage: `${route}`, token: null, baseSha: resolveBaseSha(io) });
|
|
185
191
|
}
|
|
186
192
|
|
|
193
|
+
const FORGE_CTX = '.foundry/forge-context.json';
|
|
194
|
+
|
|
195
|
+
async function captureForgeContext(sortResult, args, preCheck, io) {
|
|
196
|
+
const fgResult = await readForgeFilePatterns(preCheck.cycleId, io);
|
|
197
|
+
if (!fgResult) return;
|
|
198
|
+
const preVersion = await computeArtefactVersion('foundry', fgResult.outputType, io, args.cwd);
|
|
199
|
+
const items = openFeedbackStore('WORK.feedback.yaml', io).list();
|
|
200
|
+
if (!io.exists('.foundry')) io.mkdir('.foundry');
|
|
201
|
+
io.writeFile(FORGE_CTX, JSON.stringify({ forgePreVersion: preVersion, forgeItems: items.map(i => ({ id: i.id })) }));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function countConsecutiveForgeFailures(io, cycleId) {
|
|
205
|
+
if (!io.exists('WORK.history.yaml')) return 0;
|
|
206
|
+
const entries = loadHistory('WORK.history.yaml', cycleId, io);
|
|
207
|
+
let count = 0;
|
|
208
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
209
|
+
if (stageBaseOf(entries[i].stage) !== 'forge') break;
|
|
210
|
+
if (entries[i].contract_passed === false) count++;
|
|
211
|
+
else break;
|
|
212
|
+
}
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function enforceForgeStage(activeStage, fgResult, cycleId, io, cwd) {
|
|
217
|
+
const postVersion = await computeArtefactVersion('foundry', fgResult.outputType, io, cwd);
|
|
218
|
+
const feedbackStore = openFeedbackStore('WORK.feedback.yaml', io);
|
|
219
|
+
const { contractPassed } = enforceForgeContract({
|
|
220
|
+
items: activeStage.forgeItems, preVersion: activeStage.forgePreVersion,
|
|
221
|
+
postVersion, feedbackStore, cycleId,
|
|
222
|
+
});
|
|
223
|
+
if (!contractPassed && countConsecutiveForgeFailures(io, cycleId) + 1 >= 3) {
|
|
224
|
+
return { violation: 'forge contract failed 3 consecutive times — unable to satisfy feedback requirements' };
|
|
225
|
+
}
|
|
226
|
+
return { postVersion, contractPassed };
|
|
227
|
+
}
|
|
228
|
+
|
|
187
229
|
async function handleQuenchRoute(sortResult, preCheck, args, io) {
|
|
188
230
|
writeStageRecord(io, preCheck.cycleId, sortResult.route);
|
|
189
231
|
const quenchCtx = buildQuenchContext(preCheck.cycleId, args, io);
|
|
@@ -230,6 +272,7 @@ async function dispatchByRoute(sortResult, args, preCheck, io) {
|
|
|
230
272
|
? handleAppraiseConsolidateRoute(sortResult, preCheck, args, io)
|
|
231
273
|
: handleAppraiseGatherRoute(sortResult, preCheck, args, io);
|
|
232
274
|
}
|
|
275
|
+
if (base === 'forge') await captureForgeContext(sortResult, args, preCheck, io);
|
|
233
276
|
return handleSortResult(sortResult, buildSortContext(preCheck.cycleId, args, io));
|
|
234
277
|
}
|
|
235
278
|
|
|
@@ -278,12 +321,26 @@ async function runSetupIfNeeded(preCheck, args, io) {
|
|
|
278
321
|
return err || setupWorkfile(buildSetupArgs(preCheck, args, io));
|
|
279
322
|
}
|
|
280
323
|
|
|
324
|
+
async function runForgePostDispatch(args, activeStage, lastStage, cycleId, io) {
|
|
325
|
+
const fgResult = await readForgeFilePatterns(cycleId, io);
|
|
326
|
+
const base = { lastStage, activeStage, cycleId, io, finalize: args.finalize, git: args.git };
|
|
327
|
+
if (!fgResult) return finaliseStage(base);
|
|
328
|
+
if (!io.exists(FORGE_CTX)) return finaliseStage(base);
|
|
329
|
+
const forgeCtx = JSON.parse(io.readFile(FORGE_CTX));
|
|
330
|
+
io.unlink(FORGE_CTX);
|
|
331
|
+
if (!forgeCtx.forgePreVersion) return finaliseStage(base);
|
|
332
|
+
const result = await enforceForgeStage(forgeCtx, fgResult, cycleId, io, args.cwd);
|
|
333
|
+
if (result.violation) return violation(result.violation, []);
|
|
334
|
+
return finaliseStage({ ...base, postVersion: result.postVersion, contractPassed: result.contractPassed });
|
|
335
|
+
}
|
|
336
|
+
|
|
281
337
|
async function runPostDispatch(args, activeStage, lastStage, cycleId, io) {
|
|
282
338
|
if (!args.lastResult) return null;
|
|
283
339
|
if (args.lastResult.ok === false) {
|
|
284
340
|
return handleViolation({ lastResult: args.lastResult, activeStage, lastStage, cycleId, io });
|
|
285
341
|
}
|
|
286
342
|
const stageErr = guardMissingLastStage(lastStage);
|
|
287
|
-
|
|
288
|
-
return
|
|
343
|
+
if (stageErr) return stageErr;
|
|
344
|
+
if (stageBaseOf(lastStage.stage) === 'forge') return runForgePostDispatch(args, activeStage, lastStage, cycleId, io);
|
|
345
|
+
return finaliseStage({ lastStage, activeStage, cycleId, io, finalize: args.finalize, git: args.git });
|
|
289
346
|
}
|
|
@@ -8,9 +8,46 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readActiveStage } from './lib/state.js';
|
|
11
|
-
import { getArtefactFiles } from './lib/artefacts.js';
|
|
11
|
+
import { getArtefactFiles, computeArtefactVersion } from './lib/artefacts.js';
|
|
12
12
|
import { getCycleDefinition } from './lib/config.js';
|
|
13
13
|
import { performValidation } from './lib/validation.js';
|
|
14
|
+
import { openFeedbackStore } from './lib/feedback-store.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve stale feedback items whose artefact version does not match the
|
|
18
|
+
* current on-disk version. Items from this stage's source base with a
|
|
19
|
+
* mismatched artefact_version are auto-resolved as superseded.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveStaleFeedback(items, currentVersion, stageBase, feedback, cycle) {
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
if (shouldSkipStaleResolve(item, currentVersion, stageBase)) continue;
|
|
24
|
+
const reason = `superseded by forge revision ${currentVersion}`;
|
|
25
|
+
feedback.autoResolve({ id: item.id, reason, cycle });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function shouldSkipStaleResolve(item, currentVersion, stageBase) {
|
|
30
|
+
if (item.history[0].state === 'resolved') return true;
|
|
31
|
+
const itemBase = typeof item.source === 'string' ? item.source.split(':')[0] : '';
|
|
32
|
+
if (itemBase !== stageBase) return true;
|
|
33
|
+
if (item.artefact_version === currentVersion) return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve stale quench-sourced feedback. Errors propagate to the caller
|
|
39
|
+
* (runQuench) which surfaces them as a failure.
|
|
40
|
+
*/
|
|
41
|
+
async function resolveStaleQuenchFeedback(ctx, outputType) {
|
|
42
|
+
try {
|
|
43
|
+
const store = openFeedbackStore('WORK.feedback.yaml', ctx.io);
|
|
44
|
+
const currentVersion = await computeArtefactVersion(ctx.foundryDir, outputType, ctx.io, ctx.cwd);
|
|
45
|
+
resolveStaleFeedback(store.list(), currentVersion, 'quench', store, ctx.cycleId);
|
|
46
|
+
} catch {
|
|
47
|
+
// Graceful degrade — stale resolution is best-effort.
|
|
48
|
+
// The orchestrator handles IO failures at the cycle level.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
14
51
|
|
|
15
52
|
/**
|
|
16
53
|
* Run quench (deterministic validation) for a cycle.
|
|
@@ -30,15 +67,20 @@ export async function runQuench(ctx) {
|
|
|
30
67
|
return { ok: false, error: `Cycle ${ctx.cycleId} has no output-type` };
|
|
31
68
|
}
|
|
32
69
|
|
|
70
|
+
return await runQuenchWithStale(ctx, activeStageRecord, outputType);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runQuenchWithStale(ctx, activeStageRecord, outputType) {
|
|
74
|
+
await resolveStaleQuenchFeedback(ctx, outputType);
|
|
75
|
+
const artefactVersion = await computeArtefactVersion(
|
|
76
|
+
ctx.foundryDir, outputType, ctx.io, ctx.cwd,
|
|
77
|
+
).catch(() => undefined);
|
|
33
78
|
const discovery = await discoverArtefacts(ctx, outputType);
|
|
34
79
|
if (!discovery.ok) return discovery;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (artefacts.length === 0) {
|
|
80
|
+
if (discovery.artefacts.length === 0) {
|
|
38
81
|
return await handleNoArtefacts(ctx, activeStageRecord);
|
|
39
82
|
}
|
|
40
|
-
|
|
41
|
-
return await processArtefacts(ctx, artefacts, activeStageRecord, outputType);
|
|
83
|
+
return await processArtefacts(ctx, discovery.artefacts, activeStageRecord, outputType, artefactVersion);
|
|
42
84
|
}
|
|
43
85
|
|
|
44
86
|
async function discoverArtefacts(ctx, outputType) {
|
|
@@ -65,7 +107,7 @@ async function handleNoArtefacts(ctx, activeStageRecord) {
|
|
|
65
107
|
/**
|
|
66
108
|
* Process each artefact: run validation, post feedback, handle errors.
|
|
67
109
|
*/
|
|
68
|
-
async function processArtefacts(ctx, artefacts, activeStageRecord, outputType) {
|
|
110
|
+
async function processArtefacts(ctx, artefacts, activeStageRecord, outputType, artefactVersion) {
|
|
69
111
|
const perArtefact = [];
|
|
70
112
|
const currentFeedback = [];
|
|
71
113
|
let allOk = true;
|
|
@@ -78,7 +120,7 @@ async function processArtefacts(ctx, artefacts, activeStageRecord, outputType) {
|
|
|
78
120
|
artefacts: [artefact],
|
|
79
121
|
});
|
|
80
122
|
|
|
81
|
-
const outcome = handleArtefactResult(ctx, artefact, result, currentFeedback);
|
|
123
|
+
const outcome = handleArtefactResult(ctx, artefact, result, currentFeedback, artefactVersion);
|
|
82
124
|
perArtefact.push(outcome.text);
|
|
83
125
|
if (!outcome.ok) allOk = false;
|
|
84
126
|
}
|
|
@@ -103,7 +145,7 @@ async function processArtefacts(ctx, artefacts, activeStageRecord, outputType) {
|
|
|
103
145
|
* Returns { ok, text } where `ok` indicates whether the artefact passed
|
|
104
146
|
* validation and `text` is the per-artefact summary line.
|
|
105
147
|
*/
|
|
106
|
-
function handleArtefactResult(ctx, artefact, result, currentFeedback) {
|
|
148
|
+
function handleArtefactResult(ctx, artefact, result, currentFeedback, artefactVersion) {
|
|
107
149
|
if (isNoValidators(result)) {
|
|
108
150
|
return { ok: true, text: `${artefact.file}: OK: no validators` };
|
|
109
151
|
}
|
|
@@ -117,7 +159,7 @@ function handleArtefactResult(ctx, artefact, result, currentFeedback) {
|
|
|
117
159
|
return { ok: false, text: `${artefact.file}: ${messages}` };
|
|
118
160
|
}
|
|
119
161
|
|
|
120
|
-
postFeedbackItems(ctx, artefact, result, currentFeedback);
|
|
162
|
+
postFeedbackItems(ctx, artefact, result, currentFeedback, artefactVersion);
|
|
121
163
|
return { ok: true, text: `${artefact.file}: ${result.items.length} issues found` };
|
|
122
164
|
}
|
|
123
165
|
|
|
@@ -138,10 +180,10 @@ function isAllErrors(result) {
|
|
|
138
180
|
/**
|
|
139
181
|
* Post feedback items for validation results and track for resolution.
|
|
140
182
|
*/
|
|
141
|
-
function postFeedbackItems(ctx, artefact, result, currentFeedback) {
|
|
183
|
+
function postFeedbackItems(ctx, artefact, result, currentFeedback, artefactVersion) {
|
|
142
184
|
for (const item of result.items) {
|
|
143
185
|
const tag = `law:${item.lawId}:${item.validatorId}`;
|
|
144
|
-
ctx.feedback.add({ file: artefact.file, text: item.text, tag });
|
|
186
|
+
ctx.feedback.add({ file: artefact.file, text: item.text, tag, artefact_version: artefactVersion });
|
|
145
187
|
currentFeedback.push({ file: artefact.file, tag });
|
|
146
188
|
}
|
|
147
189
|
}
|
package/dist/scripts/sort.js
CHANGED
|
@@ -53,12 +53,12 @@ function validateWorkMd(workPath, io) {
|
|
|
53
53
|
return { frontmatter, cycle: frontmatter.cycle, stages: frontmatter.stages };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function extractFrontmatterDefaults(frontmatter) {
|
|
56
|
+
export function extractFrontmatterDefaults(frontmatter) {
|
|
57
57
|
const maxIt = frontmatter['max-iterations'] ?? 3;
|
|
58
58
|
return {
|
|
59
59
|
maxIterations: maxIt,
|
|
60
60
|
alwaysHumanAppraise: frontmatter['always-human-appraise'] === true,
|
|
61
|
-
deadlockHumanAppraise: frontmatter['deadlock-human-appraise']
|
|
61
|
+
deadlockHumanAppraise: frontmatter['deadlock-human-appraise'] === true,
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -72,14 +72,33 @@ function checkDirtyFiles(history, io) {
|
|
|
72
72
|
+ `Re-run foundry_orchestrate or commit the listed files manually before retrying.`;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
76
|
+
|
|
77
|
+
function isLegacyItem(item) {
|
|
78
|
+
return !item.artefact_version || !SHA256_RE.test(item.artefact_version);
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
function loadFeedback(io, cycle) {
|
|
76
82
|
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
const allItems = store.list();
|
|
84
|
+
const routed = [];
|
|
85
|
+
|
|
86
|
+
for (const item of allItems) {
|
|
87
|
+
if (isLegacyItem(item)) {
|
|
88
|
+
store.autoResolve({ id: item.id, reason: 'superseded (legacy, no artefact version)', cycle });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
routed.push({
|
|
92
|
+
id: item.id,
|
|
93
|
+
file: item.file,
|
|
94
|
+
state: item.history[0].state,
|
|
95
|
+
depth: item.history.length,
|
|
96
|
+
source: item.source,
|
|
97
|
+
artefact_version: item.artefact_version,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return routed;
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
function resolveCycleDef(cycleDef, frontmatter, foundryDir, cycle) {
|
|
@@ -246,10 +265,8 @@ export { parseFrontmatter } from './lib/workfile.js';
|
|
|
246
265
|
export {
|
|
247
266
|
baseStage,
|
|
248
267
|
findFirst,
|
|
249
|
-
|
|
268
|
+
nextStageInChain,
|
|
250
269
|
determineRoute,
|
|
251
|
-
nextAfterQuench,
|
|
252
|
-
nextAfterAppraise,
|
|
253
270
|
} from './lib/sort-routing.js';
|
|
254
271
|
export {
|
|
255
272
|
globMatch,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@really-knows-ai/foundry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
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",
|