@really-knows-ai/foundry 3.8.2 → 3.8.3
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/CHANGELOG.md +14 -0
- package/dist/scripts/appraise-module.js +126 -202
- package/dist/skills/appraise/SKILL.md +36 -128
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.8.3] - 2026-05-27
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Appraise subagents no longer receive artefact content or laws in their prompt. The dispatch prompt contains only the appraiser's personality and the artefact type ID. The subagent discovers artefact files, laws, and file-patterns via tool calls (`foundry_config_artefact_type`, `foundry_config_laws`, `foundry_artefacts_list`) and reads files from the worktree.
|
|
8
|
+
|
|
9
|
+
- Appraise subagent output format changed from YAML to JSONL (one JSON object per line), matching the quench validator protocol. Required fields: `file`, `text`. Recommended: `law`, `evidence`. Optional: `severity`, `location`. The consolidate phase parses JSONL and posts feedback with tag `law:<slug>`.
|
|
10
|
+
|
|
11
|
+
- Gather phase now creates one task per appraiser (not per artefact × appraiser). Each appraiser covers all artefacts of the given type via tool-based discovery.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Removed `js-yaml` dependency from appraise-module.js. All YAML parsing and fallback line-parsing code replaced with JSONL parsing.
|
|
16
|
+
|
|
3
17
|
## [3.8.2] - 2026-05-27
|
|
4
18
|
|
|
5
19
|
### Changed
|
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
* Appraise module — gathers context for parallel appraiser dispatch and
|
|
3
3
|
* consolidates results after all appraisers have run.
|
|
4
4
|
*
|
|
5
|
-
* Gather phase: reads artefacts,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Gather phase: reads artefacts, selects appraisers, builds subagent prompts
|
|
6
|
+
* with only personality + type ID (no artefact content or laws inlined), and
|
|
7
|
+
* returns a dispatch_multi action so the orchestrator's LLM dispatches
|
|
8
|
+
* appraisers in parallel.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Each appraiser subagent discovers artefacts, laws, and file-patterns via
|
|
11
|
+
* tool calls and returns JSONL — one JSON object per line.
|
|
12
|
+
*
|
|
13
|
+
* Consolidate phase: receives lastResults from the orchestrator, parses JSONL
|
|
14
|
+
* from each appraiser, unions and de-duplicates issues, posts feedback, and
|
|
15
|
+
* finalises the stage so the orchestrator can re-sort and determine the next
|
|
16
|
+
* action.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
import { getArtefactFiles, computeArtefactVersion } from './lib/artefacts.js';
|
|
15
|
-
import { selectAppraisers,
|
|
20
|
+
import { selectAppraisers, getCycleDefinition } from './lib/config.js';
|
|
16
21
|
import { openFeedbackStore } from './lib/feedback-store.js';
|
|
17
|
-
import yaml from 'js-yaml';
|
|
18
22
|
|
|
19
23
|
// ---------------------------------------------------------------------------
|
|
20
24
|
// Public API — gather
|
|
21
25
|
// ---------------------------------------------------------------------------
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
* Gather appraise context: read draft artefacts, select appraisers,
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* Gather appraise context: read draft artefacts, select appraisers, build a
|
|
29
|
+
* dispatch_multi action with one task per appraiser. The subagent prompt
|
|
30
|
+
* contains only the appraiser personality and artefact type ID — the subagent
|
|
31
|
+
* discovers artefact files, laws, and file-patterns via tool calls.
|
|
27
32
|
*
|
|
28
33
|
* @param {object} ctx
|
|
29
34
|
* @param {string} ctx.cycleId
|
|
@@ -36,91 +41,54 @@ import yaml from 'js-yaml';
|
|
|
36
41
|
* @returns {Promise<{action: string, tasks: Array, stage: string, cycle: string}>}
|
|
37
42
|
*/
|
|
38
43
|
export async function gatherAppraiseContext(ctx) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
44
|
+
const guarded = guardAppraiseGather(ctx);
|
|
45
|
+
if (guarded) return guarded;
|
|
42
46
|
|
|
43
47
|
await resolveStaleAppraiseFeedback(ctx);
|
|
44
48
|
|
|
45
49
|
const cd = await getCycleDefinition(ctx.foundryDir, ctx.cycleId, ctx.io);
|
|
46
|
-
const outputType = cd.
|
|
47
|
-
if (
|
|
48
|
-
return violation(`cycle ${ctx.cycleId} missing output-type field`, []);
|
|
49
|
-
}
|
|
50
|
-
const baseBranch = ctx.baseBranch || 'main';
|
|
51
|
-
const artefacts = await getArtefactFiles(ctx.foundryDir, outputType, ctx.io, { baseBranch });
|
|
52
|
-
if (artefacts.length === 0) {
|
|
53
|
-
return emptyDispatch(ctx.cycleId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const typedArtefacts = artefacts.map(artefact => ({ ...artefact, type: outputType }));
|
|
57
|
-
const tasks = await collectTasks(typedArtefacts, ctx);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
action: 'dispatch_multi',
|
|
61
|
-
tasks,
|
|
62
|
-
stage: `appraise:${ctx.cycleId}`,
|
|
63
|
-
cycle: ctx.cycleId,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
50
|
+
const outputType = validateOutputType(cd, ctx.cycleId);
|
|
51
|
+
if (typeof outputType !== 'string') return outputType;
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*/
|
|
70
|
-
async function collectTasks(artefacts, ctx) {
|
|
71
|
-
const tasks = [];
|
|
72
|
-
const typeCache = new Map();
|
|
73
|
-
|
|
74
|
-
for (const artefact of artefacts) {
|
|
75
|
-
const entry = await resolveTypeEntry(artefact.type, typeCache, ctx);
|
|
76
|
-
if (!entry) continue;
|
|
53
|
+
const artefacts = await fetchAppraiseArtefacts(ctx, outputType);
|
|
54
|
+
if (!Array.isArray(artefacts)) return artefacts;
|
|
77
55
|
|
|
78
|
-
|
|
56
|
+
const appraisers = await selectAppraisers(ctx.foundryDir, outputType, { io: ctx.io });
|
|
57
|
+
if (appraisers.length === 0) {
|
|
58
|
+
return emptyDispatch(ctx.cycleId);
|
|
79
59
|
}
|
|
80
60
|
|
|
81
|
-
return
|
|
61
|
+
return buildGatherResponse(appraisers, outputType, ctx);
|
|
82
62
|
}
|
|
83
63
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*/
|
|
88
|
-
async function resolveTypeEntry(typeId, cache, ctx) {
|
|
89
|
-
if (cache.has(typeId)) {
|
|
90
|
-
return cache.get(typeId);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const [appraisers, laws] = await Promise.all([
|
|
94
|
-
selectAppraisers(ctx.foundryDir, typeId, { io: ctx.io }),
|
|
95
|
-
getLaws(ctx.foundryDir, ctx.io, { typeId }),
|
|
96
|
-
]);
|
|
64
|
+
function guardAppraiseGather(ctx) {
|
|
65
|
+
return ctx.cycleId ? null : violation('cycleId is required', []);
|
|
66
|
+
}
|
|
97
67
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
68
|
+
function validateOutputType(cd, cycleId) {
|
|
69
|
+
const outputType = cd.frontmatter['output-type'];
|
|
70
|
+
return outputType ?? violation(`cycle ${cycleId} missing output-type field`, []);
|
|
101
71
|
}
|
|
102
72
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
content = ctx.io.readFile(artefact.file);
|
|
110
|
-
}
|
|
73
|
+
async function fetchAppraiseArtefacts(ctx, outputType) {
|
|
74
|
+
const baseBranch = ctx.baseBranch || 'main';
|
|
75
|
+
const artefacts = await getArtefactFiles(ctx.foundryDir, outputType, ctx.io, { baseBranch });
|
|
76
|
+
if (artefacts.length === 0) return emptyDispatch(ctx.cycleId);
|
|
77
|
+
return artefacts;
|
|
78
|
+
}
|
|
111
79
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
80
|
+
function buildGatherResponse(appraisers, outputType, ctx) {
|
|
81
|
+
const tasks = appraisers.map(appraiser => ({
|
|
82
|
+
subagent_type: resolveSubagentType(appraiser, ctx),
|
|
83
|
+
prompt: buildAppraiserPrompt({ appraiser, typeId: outputType }),
|
|
84
|
+
}));
|
|
118
85
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
86
|
+
return {
|
|
87
|
+
action: 'dispatch_multi',
|
|
88
|
+
tasks,
|
|
89
|
+
stage: `appraise:${ctx.cycleId}`,
|
|
90
|
+
cycle: ctx.cycleId,
|
|
91
|
+
};
|
|
124
92
|
}
|
|
125
93
|
|
|
126
94
|
/**
|
|
@@ -197,9 +165,9 @@ async function resolveStaleAppraiseFeedback(ctx) {
|
|
|
197
165
|
/**
|
|
198
166
|
* Consolidate appraiser results and finalise the appraise stage.
|
|
199
167
|
*
|
|
200
|
-
* Called by orchestrator after all appraisers have completed. Parses
|
|
201
|
-
* posts combined feedback, resolves prior
|
|
202
|
-
* the cycle to the next stage via finalize.
|
|
168
|
+
* Called by orchestrator after all appraisers have completed. Parses JSONL
|
|
169
|
+
* from each appraiser's output, posts combined feedback, resolves prior
|
|
170
|
+
* appraise feedback, and advances the cycle to the next stage via finalize.
|
|
203
171
|
*
|
|
204
172
|
* @param {object} ctx
|
|
205
173
|
* @param {Array<{ok: boolean, output?: string, error?: string}>} lastResults
|
|
@@ -238,20 +206,73 @@ export async function consolidateAppraise(ctx, lastResults) {
|
|
|
238
206
|
}
|
|
239
207
|
|
|
240
208
|
/**
|
|
241
|
-
* Parse all successful appraiser outputs and de-duplicate the
|
|
242
|
-
* list by (file, law-id, issue text).
|
|
209
|
+
* Parse JSONL from all successful appraiser outputs and de-duplicate the
|
|
210
|
+
* combined issue list by (file, law-id, issue text).
|
|
243
211
|
*/
|
|
244
212
|
function parseConsolidated(successful) {
|
|
245
213
|
const all = [];
|
|
246
214
|
|
|
247
215
|
for (const result of successful) {
|
|
248
|
-
const issues =
|
|
216
|
+
const issues = parseAppraiserJsonl(result.output || '');
|
|
249
217
|
all.push(...issues);
|
|
250
218
|
}
|
|
251
219
|
|
|
252
220
|
return deduplicateIssues(all);
|
|
253
221
|
}
|
|
254
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Parse appraiser JSONL output.
|
|
225
|
+
*
|
|
226
|
+
* Each line must be a JSON object with at least `file` and `text` fields.
|
|
227
|
+
* Extra fields (`law`, `evidence`, `severity`, `location`) are preserved.
|
|
228
|
+
* The `text` field maps to the issue description used for feedback text.
|
|
229
|
+
*/
|
|
230
|
+
function parseAppraiserJsonl(output) {
|
|
231
|
+
const issues = [];
|
|
232
|
+
const lines = output.trim().split('\n');
|
|
233
|
+
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
const issue = parseAppraiserLine(line);
|
|
236
|
+
if (issue) issues.push(issue);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return issues;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseAppraiserLine(line) {
|
|
243
|
+
const trimmed = line.trim();
|
|
244
|
+
if (!trimmed) return null;
|
|
245
|
+
|
|
246
|
+
const obj = tryJsonParseLine(trimmed);
|
|
247
|
+
if (!obj) return null;
|
|
248
|
+
|
|
249
|
+
return validateJsonlIssue(obj);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function tryJsonParseLine(line) {
|
|
253
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function validateJsonlIssue(obj) {
|
|
257
|
+
if (!hasStringField(obj, 'file')) return null;
|
|
258
|
+
if (!hasStringField(obj, 'text')) return null;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
file: obj.file,
|
|
262
|
+
law: strOrEmpty(obj.law),
|
|
263
|
+
issue: obj.text,
|
|
264
|
+
evidence: strOrEmpty(obj.evidence),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function hasStringField(obj, key) {
|
|
269
|
+
return typeof obj[key] === 'string' && obj[key].length > 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function strOrEmpty(value) {
|
|
273
|
+
return typeof value === 'string' ? value : '';
|
|
274
|
+
}
|
|
275
|
+
|
|
255
276
|
/**
|
|
256
277
|
* De-duplicate an issue array by (file, law, issue text).
|
|
257
278
|
*/
|
|
@@ -332,138 +353,41 @@ function buildConsolidateSummary(count) {
|
|
|
332
353
|
// ---------------------------------------------------------------------------
|
|
333
354
|
|
|
334
355
|
/**
|
|
335
|
-
* Build a subagent prompt for
|
|
356
|
+
* Build a subagent prompt for an appraiser.
|
|
336
357
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
358
|
+
* The prompt contains only the appraiser's personality and the artefact type
|
|
359
|
+
* ID. The subagent discovers artefact files, laws, and file-patterns via tool
|
|
360
|
+
* calls and returns JSONL — one JSON object per line.
|
|
339
361
|
*/
|
|
340
|
-
function buildAppraiserPrompt({ appraiser,
|
|
341
|
-
const lawSections = laws
|
|
342
|
-
.map(law => `## ${law.id}\n\n${law.text}`)
|
|
343
|
-
.join('\n\n');
|
|
344
|
-
|
|
362
|
+
function buildAppraiserPrompt({ appraiser, typeId }) {
|
|
345
363
|
const lines = [
|
|
346
364
|
'You are an appraiser. Your personality:',
|
|
347
365
|
'',
|
|
348
366
|
appraiser.personality,
|
|
349
367
|
'',
|
|
350
|
-
|
|
351
|
-
'either:',
|
|
352
|
-
'- Note no issues (pass)',
|
|
353
|
-
'- Describe the issue, quoting evidence from the artefact',
|
|
368
|
+
`Evaluate artefacts of type "${typeId}" against applicable laws.`,
|
|
354
369
|
'',
|
|
355
|
-
'
|
|
370
|
+
'Use tools to discover context:',
|
|
371
|
+
`- foundry_config_artefact_type with typeId "${typeId}" for file-patterns`,
|
|
372
|
+
`- foundry_config_laws with typeId "${typeId}" for applicable laws (prose only)`,
|
|
373
|
+
'- foundry_artefacts_list for changed files',
|
|
374
|
+
'- Read matching files from the worktree',
|
|
356
375
|
'',
|
|
357
|
-
|
|
376
|
+
'For each law, evaluate each relevant file. If a violation is found,',
|
|
377
|
+
'output a JSONL line:',
|
|
358
378
|
'',
|
|
359
|
-
'
|
|
379
|
+
'{"file": "<path>", "law": "<law-slug>", "text": "<issue description>", "evidence": "<quote>"}',
|
|
360
380
|
'',
|
|
361
|
-
|
|
381
|
+
'`file` and `text` are required. `law` and `evidence` are recommended.',
|
|
382
|
+
'Optional fields `severity` and `location` are passed through unchanged.',
|
|
362
383
|
'',
|
|
363
|
-
'
|
|
364
|
-
'',
|
|
365
|
-
'Return a list of issues. For each issue:',
|
|
366
|
-
`- file: ${artefact.file}`,
|
|
367
|
-
' law: <law-id>',
|
|
368
|
-
' issue: <description>',
|
|
369
|
-
' evidence: <quote from artefact>',
|
|
370
|
-
'',
|
|
371
|
-
'If there are no issues, return an empty list.',
|
|
384
|
+
'Output ONLY JSONL — one JSON object per line. No markdown, no commentary.',
|
|
385
|
+
'If no issues are found, output nothing.',
|
|
372
386
|
];
|
|
373
387
|
|
|
374
388
|
return lines.join('\n');
|
|
375
389
|
}
|
|
376
390
|
|
|
377
|
-
// ---------------------------------------------------------------------------
|
|
378
|
-
// Output parsing
|
|
379
|
-
// ---------------------------------------------------------------------------
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Parse a structured issue list from an appraiser subagent output.
|
|
383
|
-
*
|
|
384
|
-
* LLM output is free-form text that may contain a YAML list of issues.
|
|
385
|
-
* Tries js-yaml first; falls back to line-scanning when the output is
|
|
386
|
-
* not clean YAML (LLMs may include surrounding text, quotes in bare
|
|
387
|
-
* strings, or other quirks that trip up a strict YAML parser).
|
|
388
|
-
*
|
|
389
|
-
* Returns an array of { file, law, issue, evidence } objects.
|
|
390
|
-
*/
|
|
391
|
-
function parseAppraiserOutput(output) {
|
|
392
|
-
const text = output || '';
|
|
393
|
-
const yamlBlock = extractYamlBlock(text);
|
|
394
|
-
const issues = tryYamlParse(yamlBlock);
|
|
395
|
-
if (issues) return issues;
|
|
396
|
-
|
|
397
|
-
return parseFallback(text);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function extractYamlBlock(text) {
|
|
401
|
-
if (text.startsWith('- file:')) return text;
|
|
402
|
-
const afterNewline = text.indexOf('\n- file:');
|
|
403
|
-
if (afterNewline >= 0) return text.slice(afterNewline + 1);
|
|
404
|
-
return text;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function tryYamlParse(yamlBlock) {
|
|
408
|
-
try {
|
|
409
|
-
const parsed = yaml.load(yamlBlock);
|
|
410
|
-
if (Array.isArray(parsed)) {
|
|
411
|
-
return parsed
|
|
412
|
-
.filter(e => e && typeof e === 'object' && e.file && e.law && e.issue)
|
|
413
|
-
.map(e => ({ file: e.file, law: e.law, issue: e.issue, evidence: e.evidence || '' }));
|
|
414
|
-
}
|
|
415
|
-
} catch { /* fall through to fallback */ }
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const FALLBACK_FIELDS = new Set(['law', 'issue', 'evidence']);
|
|
420
|
-
|
|
421
|
-
function isCompleteIssue(obj) {
|
|
422
|
-
return obj && obj.file && obj.law && obj.issue;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function applyFallbackField(kv, entry, issues) {
|
|
426
|
-
if (kv.key === 'file') {
|
|
427
|
-
const e = { file: kv.value, law: '', issue: '', evidence: '' };
|
|
428
|
-
issues.push(e);
|
|
429
|
-
return e;
|
|
430
|
-
}
|
|
431
|
-
if (entry && FALLBACK_FIELDS.has(kv.key)) {
|
|
432
|
-
entry[kv.key] = kv.value;
|
|
433
|
-
}
|
|
434
|
-
return entry;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function parseFallback(text) {
|
|
438
|
-
const issues = [];
|
|
439
|
-
let entry = null;
|
|
440
|
-
|
|
441
|
-
for (const line of text.split('\n')) {
|
|
442
|
-
const kv = parseFallbackLine(line);
|
|
443
|
-
if (kv) entry = applyFallbackField(kv, entry, issues);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return issues.filter(isCompleteIssue);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function parseFallbackLine(line) {
|
|
450
|
-
const trimmed = line.trim();
|
|
451
|
-
if (!trimmed) return null;
|
|
452
|
-
|
|
453
|
-
const colon = trimmed.indexOf(':');
|
|
454
|
-
if (colon < 1) return null;
|
|
455
|
-
|
|
456
|
-
const key = stripDash(trimmed.slice(0, colon));
|
|
457
|
-
return {
|
|
458
|
-
key: key.trim(),
|
|
459
|
-
value: trimmed.slice(colon + 1).trim(),
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function stripDash(s) {
|
|
464
|
-
return s.startsWith('- ') ? s.slice(2) : s;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
391
|
// ---------------------------------------------------------------------------
|
|
468
392
|
// Shared helpers
|
|
469
393
|
// ---------------------------------------------------------------------------
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: appraise
|
|
3
3
|
type: atomic
|
|
4
|
-
description: Subjective evaluation of an artefact against laws via
|
|
4
|
+
description: Subjective evaluation of an artefact against laws via independent appraiser subagents.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Appraise
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**This skill is subagent-only.** It describes the protocol an appraiser subagent follows when dispatched via `task()` from the orchestrate loop. Do NOT load this skill and run appraise inline — the orchestrate skill returns a `dispatch_multi` action with pre-built prompts; call `task()` with each.
|
|
10
|
+
|
|
11
|
+
You evaluate artefacts against laws. Your dispatch prompt contains your personality and the artefact type ID. You discover artefact files, laws, and file-patterns via tool calls.
|
|
10
12
|
|
|
11
13
|
## Prerequisites
|
|
12
14
|
|
|
@@ -18,152 +20,58 @@ Before running this skill, verify that the `foundry/` directory exists in the pr
|
|
|
18
20
|
|
|
19
21
|
Appraise runs inside an enforced stage. Your **first** and **last** tool calls are fixed:
|
|
20
22
|
|
|
21
|
-
1. **First:** `foundry_stage_begin({stage, cycle, token})` — copy the token verbatim from the dispatch prompt.
|
|
23
|
+
1. **First:** `foundry_stage_begin({stage, cycle, token})` — copy the token verbatim from the dispatch prompt. No other tool call is permitted before this one.
|
|
22
24
|
2. **Last:** `foundry_stage_end({summary})`.
|
|
23
25
|
|
|
24
|
-
Appraise makes **no disk writes**. Feedback output flows through
|
|
26
|
+
Appraise makes **no disk writes**. Feedback output flows through JSONL returned in your response text. The orchestrator's internal consolidate step parses the JSONL, posts feedback, and resolves prior items.
|
|
25
27
|
|
|
26
28
|
## Protocol
|
|
27
29
|
|
|
28
|
-
1. `foundry_stage_begin(...)
|
|
29
|
-
2.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
>
|
|
38
|
-
> 1. `foundry_workfile_delete({confirm: true})` to abandon the cycle.
|
|
39
|
-
> 2. Back out to main (`git checkout main`) and delete the work branch.
|
|
40
|
-
> 3. Investigate and fix the root cause of the failure before restarting.
|
|
41
|
-
|
|
42
|
-
Then return control to the user and stop.
|
|
43
|
-
- `foundry_artefacts_list({})` — enumerate the current cycle's branch artefact changes as `[{ file, state }]` entries.
|
|
44
|
-
- For each artefact change, gather its type-specific context:
|
|
45
|
-
- `foundry_config_laws` with the cycle's output type — applicable laws (global + type-specific)
|
|
46
|
-
- `foundry_config_artefact_type` with the type ID — the artefact type definition
|
|
47
|
-
- `foundry_appraisers_select` with the type ID — selected appraiser personalities with their raw model IDs
|
|
48
|
-
|
|
49
|
-
3. Dispatch each appraiser as an independent sub-agent (see Dispatch below). If this cycle produced multiple artefacts, appraisers evaluate each.
|
|
50
|
-
|
|
51
|
-
4. Collect results from all appraisers
|
|
52
|
-
|
|
53
|
-
5. Consolidate (this is judgment):
|
|
54
|
-
- Union of all issues — if any one appraiser flags it, it's feedback
|
|
55
|
-
- De-duplicate: merge overlapping observations into a single feedback item
|
|
56
|
-
- Preserve which appraiser(s) raised each issue (for traceability)
|
|
57
|
-
|
|
58
|
-
6. For each consolidated issue: `foundry_feedback_add` with `{ file, text, tag: 'law:<slug>' }`. Tags must match `law:<slug>`, and dedup uses the non-resolved `(file, tag, hash(text))` semantics described in Feedback handling.
|
|
59
|
-
|
|
60
|
-
7. If no appraiser found any issues, the artefact clears appraisal.
|
|
61
|
-
|
|
62
|
-
8. `foundry_stage_end({summary})`.
|
|
63
|
-
|
|
64
|
-
## Feedback handling
|
|
65
|
-
|
|
66
|
-
As an appraise stage, you have two feedback responsibilities:
|
|
67
|
-
|
|
68
|
-
1. **Adding new law-violation feedback.** For each unmet law, call
|
|
69
|
-
`foundry_feedback_add` with `{ file, text, tag: 'law:<slug>' }`.
|
|
70
|
-
The `source` is automatically your stage id (e.g. `appraise:write-check`).
|
|
71
|
-
The tool rejects any tag not matching `law:<slug>` during an appraise
|
|
72
|
-
stage; do not attempt bare `'appraise'` or `'review'` tags.
|
|
73
|
-
|
|
74
|
-
The tool returns `{ ok: true, id, deduped }` on success. `deduped: true`
|
|
75
|
-
means an existing non-resolved item with the same `(file, tag,
|
|
76
|
-
hash(text))` was found (no new snapshot written); `deduped: false`
|
|
77
|
-
means a new item was created. Resolved items are NOT considered for
|
|
78
|
-
dedup — a re-added item after a resolution is a legitimate new item
|
|
79
|
-
(regression feedback).
|
|
30
|
+
1. `foundry_stage_begin(...)` with the token from the dispatch prompt.
|
|
31
|
+
2. `foundry_config_artefact_type` with the type ID — get the artefact type definition and `file-patterns`.
|
|
32
|
+
3. `foundry_config_laws` with the type ID — get all applicable laws (prose only).
|
|
33
|
+
4. `foundry_artefacts_list` — enumerate the current cycle's branch artefact changes.
|
|
34
|
+
5. For each artefact file that matches the type's `file-patterns`, read the file from the worktree.
|
|
35
|
+
6. Evaluate each file against each law. For each law, either:
|
|
36
|
+
- Note no issues (pass)
|
|
37
|
+
- Describe the violation, quoting evidence from the artefact
|
|
38
|
+
7. Output JSONL. Each line is one JSON object:
|
|
80
39
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- Approve: `foundry_feedback_resolve` with `{ id, resolution: 'approved' }`.
|
|
85
|
-
`reason` is optional.
|
|
86
|
-
- Reject: `foundry_feedback_resolve` with `{ id, resolution: 'rejected', reason: '...' }`.
|
|
87
|
-
`reason` is required. A rejection sends the item back to forge for
|
|
88
|
-
another attempt (the `rejected` state is a legal forge input per
|
|
89
|
-
§5.1 rule 2).
|
|
40
|
+
```json
|
|
41
|
+
{"file": "<path>", "law": "<law-slug>", "text": "<issue description>", "evidence": "<quote from artefact>"}
|
|
42
|
+
```
|
|
90
43
|
|
|
91
|
-
|
|
92
|
-
any deadlock-override transition. On `resolution: 'approved'` for a
|
|
93
|
-
non-deadlocked item, `reason` is optional.
|
|
44
|
+
`file` and `text` are required. `law` and `evidence` are recommended — `law` tells the orchestrator which law tag to use, `evidence` quotes the offending passage. Optional extra fields (`severity`, `location`) are passed through unchanged.
|
|
94
45
|
|
|
95
|
-
|
|
96
|
-
matches your own stage id — not every appraise stage in the cycle, just yours.
|
|
97
|
-
This prevents a second appraise stage from rubber-stamping work it didn't
|
|
98
|
-
request. For deadlocked items, only human-appraise has the override authority.
|
|
46
|
+
If there are no issues, output nothing (empty response).
|
|
99
47
|
|
|
100
|
-
|
|
101
|
-
human-appraise see non-deadlocked unresolved feedback before the orchestrator routes.
|
|
102
|
-
Not available in v2.6.0; appraise stages today are the sole resolver of
|
|
103
|
-
their own non-deadlocked items.
|
|
48
|
+
Your response text is ONLY JSONL — one JSON object per line. No markdown headings, no code blocks, no commentary, no YAML.
|
|
104
49
|
|
|
105
|
-
|
|
50
|
+
8. `foundry_stage_end({summary})`. The summary describes how many issues were found (e.g. "3 issues found" or "No issues found").
|
|
106
51
|
|
|
107
|
-
|
|
108
|
-
- The appraiser's personality (from their definition)
|
|
109
|
-
- The artefact content
|
|
110
|
-
- All applicable laws (global + type-specific)
|
|
111
|
-
- Instructions to evaluate the artefact against each law and return issues as a structured list
|
|
52
|
+
## Output examples
|
|
112
53
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`foundry_appraisers_select` returns raw model IDs for each appraiser. Convert each to an agent name: `foundry-<model.replace(/[/.]/g, '-')>` — both `/` and `.` are replaced with `-`. Examples:
|
|
116
|
-
- `openai/gpt-4o` → `foundry-openai-gpt-4o`
|
|
117
|
-
- `github-copilot/claude-sonnet-4.6` → `foundry-github-copilot-claude-sonnet-4-6`
|
|
118
|
-
|
|
119
|
-
- If a model is specified: dispatch with `subagent_type: "foundry-<converted-name>"`. If no agent with that name exists, **hard fail**.
|
|
120
|
-
- If no model is specified: dispatch with `subagent_type: "general"` (inherits session model).
|
|
121
|
-
|
|
122
|
-
Note: per-appraiser `model` overrides are applied here at dispatch time. The cycle-level `models.appraise` value (if set) is used for routing-time agent-file validation only; this skill does not consult it when iterating appraisers.
|
|
123
|
-
|
|
124
|
-
Dispatch all appraisers in parallel (multiple Task calls in a single response).
|
|
125
|
-
|
|
126
|
-
### Sub-agent prompt template
|
|
54
|
+
Good (issues found):
|
|
127
55
|
|
|
128
56
|
```
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<contents of appraiser personality>
|
|
132
|
-
|
|
133
|
-
Evaluate the following artefact against each law below. For each law, either:
|
|
134
|
-
- Note no issues (pass)
|
|
135
|
-
- Describe the issue, quoting evidence from the artefact
|
|
136
|
-
|
|
137
|
-
## Artefact
|
|
138
|
-
|
|
139
|
-
<artefact content>
|
|
140
|
-
|
|
141
|
-
## Laws
|
|
142
|
-
|
|
143
|
-
<all applicable laws>
|
|
144
|
-
|
|
145
|
-
## Output
|
|
146
|
-
|
|
147
|
-
Return a list of issues. For each issue:
|
|
148
|
-
- law: <law-id>
|
|
149
|
-
- issue: <description>
|
|
150
|
-
- evidence: <quote from artefact>
|
|
151
|
-
|
|
152
|
-
If there are no issues, return an empty list.
|
|
57
|
+
{"file": "haikus/mountain.md", "law": "syllable-count", "text": "Line 2 has 8 syllables, expected 7", "evidence": "A frog jumps into the pond", "location": "2:1"}
|
|
58
|
+
{"file": "haikus/mountain.md", "law": "nature-imagery", "text": "Contains industrial imagery violating nature-only requirement", "evidence": "The rusty old machine"}
|
|
153
59
|
```
|
|
154
60
|
|
|
155
|
-
|
|
61
|
+
Good (no issues found — empty response, then stage_end):
|
|
156
62
|
|
|
157
|
-
|
|
63
|
+
(no output text)
|
|
158
64
|
|
|
159
|
-
|
|
65
|
+
## Feedback handling
|
|
160
66
|
|
|
161
|
-
|
|
67
|
+
You do NOT call `foundry_feedback_add` or `foundry_feedback_resolve`. The orchestrator's consolidate step reads your JSONL output, de-duplicates across all appraisers, posts feedback items with tag `law:<slug>`, and resolves prior appraise-sourced feedback.
|
|
162
68
|
|
|
163
69
|
## What you do NOT do
|
|
164
70
|
|
|
165
|
-
- You do not write files — feedback output goes through
|
|
166
|
-
- You do not revise the artefact.
|
|
71
|
+
- You do not write files — feedback output goes through JSONL, not `foundry_feedback_add`.
|
|
72
|
+
- You do not revise the artefact — that is the forge skill's job.
|
|
167
73
|
- You do not run deterministic validators — that is the quench skill's job.
|
|
168
|
-
- You do not
|
|
169
|
-
- You do not
|
|
74
|
+
- You do not call `foundry_feedback_add`, `foundry_feedback_action`, `foundry_feedback_wontfix`, or `foundry_feedback_resolve`.
|
|
75
|
+
- You do not call `foundry_history_append` or `foundry_git_commit` — `foundry_orchestrate` handles those.
|
|
76
|
+
- You do not register artefacts — that happens automatically.
|
|
77
|
+
- You do not output YAML, markdown, or prose — only JSONL.
|
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.3",
|
|
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",
|