@really-knows-ai/foundry 3.3.9 → 3.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/dist/.opencode/plugins/foundry-tools/artefact-tools.js +32 -43
- package/dist/.opencode/plugins/foundry-tools/attestation-tools.js +9 -2
- package/dist/.opencode/plugins/foundry-tools/orchestrate-tool.js +0 -13
- package/dist/.opencode/plugins/foundry-tools/validate-tools.js +8 -245
- package/dist/CHANGELOG.md +67 -0
- package/dist/docs/architecture.md +1 -1
- package/dist/docs/tools.md +5 -28
- package/dist/docs/work-spec.md +2 -2
- package/dist/scripts/appraise-module.js +464 -0
- package/dist/scripts/lib/artefacts.js +137 -122
- package/dist/scripts/lib/attestation/attest.js +10 -18
- package/dist/scripts/lib/attestation/payload.js +36 -10
- package/dist/scripts/lib/finalize.js +5 -5
- package/dist/scripts/lib/validation.js +244 -0
- package/dist/scripts/lib/workfile.js +0 -3
- package/dist/scripts/orchestrate-cycle.js +70 -34
- package/dist/scripts/orchestrate-phases.js +68 -38
- package/dist/scripts/orchestrate.js +169 -47
- package/dist/scripts/quench-module.js +162 -0
- package/dist/scripts/sort.js +0 -1
- package/dist/skills/appraise/SKILL.md +3 -3
- package/dist/skills/human-appraise/SKILL.md +6 -7
- package/dist/skills/orchestrate/SKILL.md +33 -6
- package/dist/skills/quench/SKILL.md +4 -4
- package/package.json +5 -5
|
@@ -1,58 +1,47 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { readFileSync,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { makeIO
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { getArtefactFiles } from '../../../scripts/lib/artefacts.js';
|
|
4
|
+
import { getCycleDefinition } from '../../../scripts/lib/config.js';
|
|
5
|
+
import { parseFrontmatter } from '../../../scripts/lib/workfile.js';
|
|
6
|
+
import { makeIO } from './helpers.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
function readWorkCycleId(worktree) {
|
|
9
|
+
const workPath = path.join(worktree, 'WORK.md');
|
|
10
|
+
if (!existsSync(workPath)) throw new Error('WORK.md not found');
|
|
11
|
+
const frontmatter = parseFrontmatter(readFileSync(workPath, 'utf-8'));
|
|
12
|
+
if (!frontmatter.cycle) throw new Error('current cycle not found in WORK.md frontmatter');
|
|
13
|
+
return frontmatter.cycle;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function readOutputType(foundryDir, cycleId, io) {
|
|
17
|
+
let cd;
|
|
18
|
+
try {
|
|
19
|
+
cd = await getCycleDefinition(foundryDir, cycleId, io);
|
|
20
|
+
} catch { return null; }
|
|
21
|
+
return cd.frontmatter && cd.frontmatter['output-type'];
|
|
22
|
+
}
|
|
9
23
|
|
|
10
24
|
function makeListTool(tool) {
|
|
11
25
|
return tool({
|
|
12
|
-
description: 'List
|
|
13
|
-
args: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return JSON.stringify(
|
|
26
|
+
description: 'List artefact changes on the current work branch for the current cycle. Returns [{ file, state }] entries.',
|
|
27
|
+
args: {},
|
|
28
|
+
async execute(_args, context) {
|
|
29
|
+
try {
|
|
30
|
+
const cycleId = readWorkCycleId(context.worktree);
|
|
31
|
+
const io = makeIO(context.worktree);
|
|
32
|
+
const outputType = await readOutputType('foundry', cycleId, io);
|
|
33
|
+
if (!outputType) return JSON.stringify([]);
|
|
34
|
+
const artefacts = await getArtefactFiles('foundry', outputType, io, { baseBranch: 'main' });
|
|
35
|
+
return JSON.stringify(artefacts);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return JSON.stringify({ error: err.message });
|
|
20
38
|
}
|
|
21
|
-
const text = readFileSync(workPath, 'utf-8');
|
|
22
|
-
const rows = parseArtefactsTable(text);
|
|
23
|
-
const filtered = args.cycle ? rows.filter(r => r.cycle === args.cycle) : rows;
|
|
24
|
-
return JSON.stringify(filtered);
|
|
25
39
|
},
|
|
26
40
|
});
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
export function createArtefactTools({ tool }) {
|
|
30
44
|
return {
|
|
31
|
-
// NOTE: `foundry_artefacts_add` was removed in v2.2.0. Artefacts are now
|
|
32
|
-
// registered automatically by the orchestrator's internal finalize step as drafts,
|
|
33
|
-
// then promoted to done|blocked via `foundry_artefacts_set_status`.
|
|
34
|
-
foundry_artefacts_set_status: tool({
|
|
35
|
-
description: 'Update the status of an artefact in WORK.md (done|blocked only)',
|
|
36
|
-
args: {
|
|
37
|
-
file: tool.schema.string().describe('Artefact file path'),
|
|
38
|
-
status: tool.schema.string().describe('New status (done|blocked)'),
|
|
39
|
-
},
|
|
40
|
-
execute: guarded('foundry_artefacts_set_status', [flowBranchGuard, gateNotFailed], async (args, context) => {
|
|
41
|
-
const io = makeIO(context.worktree);
|
|
42
|
-
const guard = requireNoActiveStage(io);
|
|
43
|
-
if (!guard.ok) return JSON.stringify({ error: `foundry_artefacts_set_status ${guard.error}` });
|
|
44
|
-
const workPath = path.join(context.worktree, 'WORK.md');
|
|
45
|
-
const text = readFileSync(workPath, 'utf-8');
|
|
46
|
-
try {
|
|
47
|
-
const updated = setArtefactStatus(text, args.file, args.status);
|
|
48
|
-
writeFileSync(workPath, updated, 'utf-8');
|
|
49
|
-
return JSON.stringify({ ok: true });
|
|
50
|
-
} catch (e) {
|
|
51
|
-
return JSON.stringify({ error: e.message });
|
|
52
|
-
}
|
|
53
|
-
}, { branchIo: branchIoFactory, io: asyncIoFactory }),
|
|
54
|
-
}),
|
|
55
|
-
|
|
56
45
|
foundry_artefacts_list: makeListTool(tool),
|
|
57
46
|
};
|
|
58
47
|
}
|
|
@@ -131,11 +131,18 @@ function commitAttestation(cwd, cycle, content, opts) {
|
|
|
131
131
|
function buildAttestationInputs(opts) {
|
|
132
132
|
return {
|
|
133
133
|
cwd: opts.cwd,
|
|
134
|
+
foundryDir: 'foundry',
|
|
134
135
|
baseBranch: opts.baseBranch,
|
|
136
|
+
branchBaseSha: opts.branchBaseSha,
|
|
135
137
|
goalText: opts.goalText,
|
|
136
138
|
archiveBranch: opts.archiveBranch,
|
|
137
139
|
archiveTipSha: opts.archiveTipSha,
|
|
138
|
-
io: {
|
|
140
|
+
io: {
|
|
141
|
+
readFile: (p) => readFileSync(p, 'utf8'),
|
|
142
|
+
fileExists: (p) => existsSync(p),
|
|
143
|
+
exists: (p) => existsSync(p),
|
|
144
|
+
exec: (args) => execFileSync(args[0], args.slice(1), { cwd: opts.cwd, encoding: 'utf8' }),
|
|
145
|
+
},
|
|
139
146
|
execGit: opts.execGit,
|
|
140
147
|
};
|
|
141
148
|
}
|
|
@@ -151,7 +158,7 @@ function createAttestTool(tool) {
|
|
|
151
158
|
return tool({
|
|
152
159
|
description:
|
|
153
160
|
'Verify the current work cycle is complete (all required stages ran, no unresolved ' +
|
|
154
|
-
'feedback
|
|
161
|
+
'feedback) and commit an ATTEST.md attestation file to the work branch. ' +
|
|
155
162
|
'foundry_git_finish will not merge without this commit at HEAD.',
|
|
156
163
|
args: {
|
|
157
164
|
baseBranch: tool.schema.string().optional()
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
1
|
import { execFileSync } from 'child_process';
|
|
3
2
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
5
3
|
import { signToken } from '../../../scripts/lib/token.js';
|
|
6
4
|
import { readOrCreateSecret } from '../../../scripts/lib/secret.js';
|
|
7
5
|
import { getCycleDefinition, getArtefactType } from '../../../scripts/lib/config.js';
|
|
8
|
-
import { addArtefactRow } from '../../../scripts/lib/artefacts.js';
|
|
9
6
|
import { stageBaseOf } from '../../../scripts/lib/stage-guard.js';
|
|
10
7
|
import { finalizeStage } from '../../../scripts/lib/finalize.js';
|
|
11
8
|
import { commitWithPolicy } from '../../../scripts/lib/git-bridge.js';
|
|
@@ -40,15 +37,6 @@ function createGitBridge(cwd) {
|
|
|
40
37
|
};
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
function makeRegisterArtefact(cwd, cycleId) {
|
|
44
|
-
const workPath = path.join(cwd, 'WORK.md');
|
|
45
|
-
return ({ file, type, status }) => {
|
|
46
|
-
const text = readFileSync(workPath, 'utf-8');
|
|
47
|
-
const updated = addArtefactRow(text, { file, type, cycle: cycleId, status });
|
|
48
|
-
writeFileSync(workPath, updated, 'utf-8');
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
40
|
async function createFinalize(cwd, io) {
|
|
53
41
|
return async ({ cycleId, stage, baseSha }) => {
|
|
54
42
|
let cycleDoc;
|
|
@@ -78,7 +66,6 @@ async function createFinalize(cwd, io) {
|
|
|
78
66
|
cycleDef,
|
|
79
67
|
artefactTypes,
|
|
80
68
|
io,
|
|
81
|
-
registerArtefact: makeRegisterArtefact(cwd, cycleId),
|
|
82
69
|
});
|
|
83
70
|
return result;
|
|
84
71
|
};
|
|
@@ -1,109 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readdir } from 'fs/promises';
|
|
3
|
-
import { join, relative, sep } from 'path';
|
|
4
|
-
import { minimatch } from 'minimatch';
|
|
5
|
-
import { getLawsForQuench, getArtefactType } from '../../../scripts/lib/config.js';
|
|
6
|
-
import { parseValidatorJsonl } from '../../../scripts/lib/validator-jsonl.js';
|
|
1
|
+
import { join } from 'path';
|
|
7
2
|
import { makeIO, branchIoFactory, asyncIoFactory, flowBranchGuard } from './helpers.js';
|
|
8
3
|
import { guarded, notFailedGuard } from '../../../scripts/lib/guards.js';
|
|
4
|
+
import { performValidation } from '../../../scripts/lib/validation.js';
|
|
9
5
|
|
|
10
6
|
const gateNotFailed = notFailedGuard(makeIO);
|
|
11
7
|
|
|
12
|
-
/**
|
|
13
|
-
* Shell-quote a string for POSIX `/bin/sh` so it is treated as a single literal
|
|
14
|
-
* argument. Wraps the value in single quotes and escapes any embedded single
|
|
15
|
-
* quotes via the `'\''` idiom. Safe for arbitrary file paths including ones
|
|
16
|
-
* containing spaces, semicolons, `$()`, backticks, quotes, and newlines.
|
|
17
|
-
*/
|
|
18
|
-
function shellQuote(value) {
|
|
19
|
-
return "'" + String(value).replace(/'/g, "'\\''") + "'";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Execute a validator command and parse its output.
|
|
24
|
-
*/
|
|
25
|
-
async function executeValidator(expanded, worktree, patterns) {
|
|
26
|
-
try {
|
|
27
|
-
const output = execSync(expanded, {
|
|
28
|
-
cwd: worktree,
|
|
29
|
-
encoding: 'utf8',
|
|
30
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
-
});
|
|
32
|
-
const { Readable } = await import('stream');
|
|
33
|
-
const stream = Readable.from([output]);
|
|
34
|
-
return await parseValidatorJsonl(stream, patterns);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
// Validator command failed - prefer stdout for JSONL (tools like rg exit 1 with results on stdout)
|
|
37
|
-
const output = (err.stdout || err.stderr || err.message || '').trim();
|
|
38
|
-
const { Readable } = await import('stream');
|
|
39
|
-
const stream = Readable.from([output]);
|
|
40
|
-
return await parseValidatorJsonl(stream, patterns);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Run all validators for laws and collect results.
|
|
46
|
-
*
|
|
47
|
-
* Aggregates per-validator parse results into a single structured payload:
|
|
48
|
-
* - `items`: each successfully parsed feedback item, annotated with the
|
|
49
|
-
* `lawId` and `validatorId` it came from. The quench skill consumes this
|
|
50
|
-
* to call `foundry_feedback_add` with tag `law:<lawId>:<validatorId>`.
|
|
51
|
-
* - `errors`: validator-level errors split by type. `parse` for malformed
|
|
52
|
-
* JSON or missing required fields, `pattern-mismatch` for files that
|
|
53
|
-
* didn't match the artefact type's `file-patterns`.
|
|
54
|
-
*/
|
|
55
|
-
async function runValidators(laws, patterns, substitutions, worktree) {
|
|
56
|
-
const results = {
|
|
57
|
-
validatorsRun: 0,
|
|
58
|
-
items: [],
|
|
59
|
-
errors: [],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
for (const law of laws) {
|
|
63
|
-
if (!law.validators || law.validators.length === 0) continue;
|
|
64
|
-
await runLawValidators(law, patterns, substitutions, worktree, results);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return results;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Run validators for a single law.
|
|
72
|
-
*/
|
|
73
|
-
async function runLawValidators(law, patterns, substitutions, worktree, results) {
|
|
74
|
-
for (const validator of law.validators) {
|
|
75
|
-
// Skip iff command uses {files} and there are no matching files.
|
|
76
|
-
// {pattern}-only and verbatim commands always run.
|
|
77
|
-
if (substitutions.files === '' && /(?:^|\s)\{files\}(?=\s|$)/.test(validator.command)) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
results.validatorsRun++;
|
|
81
|
-
const expanded = expandValidatorCommand(validator.command, substitutions);
|
|
82
|
-
const parseResult = await executeValidator(expanded, worktree, patterns);
|
|
83
|
-
collectValidatorResult(parseResult, law.id, validator.id, results);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Fold a single validator's parse result into the aggregate results.
|
|
89
|
-
*
|
|
90
|
-
* Items always flow through annotated with their `lawId` and `validatorId`,
|
|
91
|
-
* so the caller can construct `law:<lawId>:<validatorId>` feedback tags.
|
|
92
|
-
* Errors are surfaced with their type so the caller can distinguish parse
|
|
93
|
-
* failures from file-pattern mismatches.
|
|
94
|
-
*/
|
|
95
|
-
function collectValidatorResult(parseResult, lawId, validatorId, results) {
|
|
96
|
-
for (const item of parseResult.items) {
|
|
97
|
-
results.items.push({ lawId, validatorId, ...item });
|
|
98
|
-
}
|
|
99
|
-
for (const message of parseResult.parseErrors) {
|
|
100
|
-
results.errors.push({ lawId, validatorId, type: 'parse', message });
|
|
101
|
-
}
|
|
102
|
-
for (const message of parseResult.patternErrors) {
|
|
103
|
-
results.errors.push({ lawId, validatorId, type: 'pattern-mismatch', message });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
8
|
export function createValidateTools({ tool }) {
|
|
108
9
|
return {
|
|
109
10
|
foundry_validate_run: tool({
|
|
@@ -120,152 +21,14 @@ export function createValidateTools({ tool }) {
|
|
|
120
21
|
|
|
121
22
|
async function executeValidateRun(args, context) {
|
|
122
23
|
try {
|
|
123
|
-
|
|
24
|
+
const io = makeIO(context.worktree);
|
|
25
|
+
const foundryDir = join(context.worktree, 'foundry');
|
|
26
|
+
const result = await performValidation({ typeId: args.typeId, io, foundryDir });
|
|
27
|
+
return JSON.stringify(result);
|
|
124
28
|
} catch (err) {
|
|
125
29
|
return JSON.stringify({ ok: false, error: `foundry_validate_run: ${err.message}` });
|
|
126
30
|
}
|
|
127
31
|
}
|
|
128
32
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*/
|
|
132
|
-
async function getValidationPatterns(foundryDir, typeId, io) {
|
|
133
|
-
const artType = await getArtefactType(foundryDir, typeId, io);
|
|
134
|
-
return artType.frontmatter['file-patterns'] || [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function performValidation(args, context) {
|
|
138
|
-
const io = makeIO(context.worktree);
|
|
139
|
-
const foundryDir = join(context.worktree, 'foundry');
|
|
140
|
-
|
|
141
|
-
let patterns;
|
|
142
|
-
try {
|
|
143
|
-
patterns = await getValidationPatterns(foundryDir, args.typeId, io);
|
|
144
|
-
} catch (err) {
|
|
145
|
-
return JSON.stringify({ ok: false, error: err.message });
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const validationErr = validatePatterns(patterns, args.typeId);
|
|
149
|
-
if (validationErr) return JSON.stringify(validationErr);
|
|
150
|
-
|
|
151
|
-
const laws = await getLawsForQuench(foundryDir, io, { typeId: args.typeId });
|
|
152
|
-
if (!laws?.length) {
|
|
153
|
-
return JSON.stringify({ ok: true, validatorsRun: 0, items: [], errors: [] });
|
|
154
|
-
}
|
|
155
|
-
return runValidatorsAndReport(laws, patterns, context.worktree);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Run validators and report results.
|
|
160
|
-
*/
|
|
161
|
-
async function runValidatorsAndReport(laws, patterns, worktree) {
|
|
162
|
-
const expandedFiles = await expandPatterns(patterns, worktree);
|
|
163
|
-
const substitutions = {
|
|
164
|
-
pattern: patterns.map(shellQuote).join(' '),
|
|
165
|
-
files: expandedFiles.map(shellQuote).join(' '),
|
|
166
|
-
};
|
|
167
|
-
const results = await runValidators(laws, patterns, substitutions, worktree);
|
|
168
|
-
|
|
169
|
-
return JSON.stringify({
|
|
170
|
-
ok: results.errors.length === 0,
|
|
171
|
-
validatorsRun: results.validatorsRun,
|
|
172
|
-
items: results.items,
|
|
173
|
-
errors: results.errors,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Validate file patterns.
|
|
179
|
-
*/
|
|
180
|
-
function validatePatterns(patterns, typeId) {
|
|
181
|
-
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
182
|
-
return { ok: false, error: `Artefact type ${typeId} has no file-patterns` };
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
188
|
-
|
|
189
|
-
function toPosix(p) {
|
|
190
|
-
return sep === '/' ? p : p.split(sep).join('/');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function readdirSafe(dir) {
|
|
194
|
-
try {
|
|
195
|
-
return await readdir(dir, { withFileTypes: true });
|
|
196
|
-
} catch {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Recursively walk `dir` and yield POSIX-style paths relative to `root`.
|
|
203
|
-
* Skips `node_modules` and `.git` for speed; the artefacts we validate live
|
|
204
|
-
* elsewhere.
|
|
205
|
-
*/
|
|
206
|
-
async function* walkFiles(root, dir) {
|
|
207
|
-
for (const entry of await readdirSafe(dir)) {
|
|
208
|
-
const full = join(dir, entry.name);
|
|
209
|
-
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
210
|
-
yield* walkFiles(root, full);
|
|
211
|
-
} else if (entry.isFile()) {
|
|
212
|
-
yield toPosix(relative(root, full));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function fileMatchesAnyPattern(rel, patterns) {
|
|
218
|
-
for (const pattern of patterns) {
|
|
219
|
-
if (minimatch(rel, pattern)) return true;
|
|
220
|
-
}
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Expand glob patterns to actual files in the worktree.
|
|
226
|
-
*
|
|
227
|
-
* Implemented over `readdir` + `minimatch` so we work on Node 20, which lacks
|
|
228
|
-
* `fs/promises.glob` (added in Node 22).
|
|
229
|
-
*/
|
|
230
|
-
async function expandPatterns(patterns, worktree) {
|
|
231
|
-
const files = new Set();
|
|
232
|
-
for await (const rel of walkFiles(worktree, worktree)) {
|
|
233
|
-
if (fileMatchesAnyPattern(rel, patterns)) {
|
|
234
|
-
files.add(rel);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return Array.from(files).sort();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Expand validator command by replacing {pattern} and {files} placeholders.
|
|
242
|
-
*
|
|
243
|
-
* - {pattern} → space-separated, shell-quoted globs from the artefact
|
|
244
|
-
* type's `file-patterns:` array (e.g. "'haikus/*.md' 'drafts/*.md'").
|
|
245
|
-
* - {files} → space-separated, shell-quoted matching file paths in the
|
|
246
|
-
* worktree (e.g. "'haikus/one.md' 'haikus/two.md'").
|
|
247
|
-
*
|
|
248
|
-
* Both placeholders are recognised only as standalone tokens, bounded
|
|
249
|
-
* by whitespace or start/end of string. Surrounding single or double
|
|
250
|
-
* quotes around the placeholder are stripped first so authors can
|
|
251
|
-
* write `rg "{pattern}"` for readability.
|
|
252
|
-
*
|
|
253
|
-
* @param {string} command
|
|
254
|
-
* @param {{ pattern: string, files: string }} substitutions
|
|
255
|
-
* @returns {string}
|
|
256
|
-
*/
|
|
257
|
-
export function expandValidatorCommand(command, { pattern, files }) {
|
|
258
|
-
let cmd = command
|
|
259
|
-
.replace(/"\{pattern\}"/g, '{pattern}')
|
|
260
|
-
.replace(/'\{pattern\}'/g, '{pattern}')
|
|
261
|
-
.replace(/"\{files\}"/g, '{files}')
|
|
262
|
-
.replace(/'\{files\}'/g, '{files}');
|
|
263
|
-
|
|
264
|
-
cmd = cmd.replace(/(?:^|\s)\{pattern\}(?=\s|$)/g, (match) =>
|
|
265
|
-
match.startsWith('{') ? pattern : ' ' + pattern);
|
|
266
|
-
|
|
267
|
-
cmd = cmd.replace(/(?:^|\s)\{files\}(?=\s|$)/g, (match) =>
|
|
268
|
-
match.startsWith('{') ? files : ' ' + files);
|
|
269
|
-
|
|
270
|
-
return cmd;
|
|
271
|
-
}
|
|
33
|
+
// Re-export for existing tests (validator-command-expansion.test.js)
|
|
34
|
+
export { expandValidatorCommand } from '../../../scripts/lib/validation.js';
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.5.1] - 2026-05-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Run CI against the active Node release lines: 22.x, 24.x, 25.x, and 26.x.
|
|
8
|
+
- Use Node 22-compatible `mock.module({ namedExports })` test mocks.
|
|
9
|
+
|
|
10
|
+
## [3.5.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Add branch-based artefact discovery with `getArtefactFiles` and git change-state tracking.
|
|
15
|
+
- Remove the `WORK.md` artefact table, artefact registration side effects, and per-artefact status updates.
|
|
16
|
+
- Update quench, appraise, orchestration, plugin tools, and attestation to use branch artefact discovery.
|
|
17
|
+
- Remove `foundry_artefacts_set_status` and update `foundry_artefacts_list` to return `{file, state}` entries.
|
|
18
|
+
- Remove artefact table generation from new `WORK.md` files.
|
|
19
|
+
- Update tests and docs for branch artefact discovery.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Include all non-deleted artefact files in missing-model violation payloads.
|
|
24
|
+
- Thread base-branch selection through artefact discovery contexts.
|
|
25
|
+
|
|
26
|
+
## [3.4.0] - 2026-05-20
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Quench runs as an internal orchestrator module.** Quench no longer
|
|
31
|
+
dispatches an LLM subagent — it runs validators directly within the
|
|
32
|
+
orchestrator, posting feedback and resolving prior items without any
|
|
33
|
+
model involvement. A single `foundry_orchestrate` call handles the
|
|
34
|
+
full quench stage inline.
|
|
35
|
+
- **Appraise dispatch moves into the orchestrator.** Instead of
|
|
36
|
+
dispatching a single appraise subagent that can't use `task`, the
|
|
37
|
+
orchestrator gathers context internally, returns a `dispatch_multi`
|
|
38
|
+
action with pre-built prompts, and the LLM dispatches individual
|
|
39
|
+
appraiser subagents in parallel. After dispatch, the orchestrator
|
|
40
|
+
consolidates results internally — unioning, de-duplicating, and
|
|
41
|
+
posting feedback — all within the `foundry_orchestrate` loop.
|
|
42
|
+
- **New `dispatch_multi` action and `lastResults` input.** The
|
|
43
|
+
`foundry_orchestrate` tool now returns `action: "dispatch_multi"`
|
|
44
|
+
with `tasks: [{subagent_type, prompt}, ...]` and accepts
|
|
45
|
+
`lastResults: [{ok, output?, error?}, ...]` for consolidating
|
|
46
|
+
multi-dispatch results. Mutual exclusivity with `lastResult` is
|
|
47
|
+
enforced.
|
|
48
|
+
- **Stage model fallback.** When a cycle's `models:` map omits a stage,
|
|
49
|
+
the orchestrator falls back to the caller's `defaultModel`, then
|
|
50
|
+
`models.default`, then any available model in the map.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- `src/scripts/quench-module.js` — `runQuench(ctx)` runs validators
|
|
55
|
+
deterministically with no LLM.
|
|
56
|
+
- `src/scripts/appraise-module.js` — `gatherAppraiseContext(ctx)` and
|
|
57
|
+
`consolidateAppraise(ctx, lastResults)` for multi-appraiser dispatch.
|
|
58
|
+
- `src/scripts/lib/validation.js` — extracted `performValidation` and
|
|
59
|
+
related functions from `validate-tools.js`.
|
|
60
|
+
- `getArtefactsForCycle()` on `src/scripts/lib/artefacts.js`.
|
|
61
|
+
- `DISPATCH_MULTI_ACTION`, `validateDispatchMulti`, and
|
|
62
|
+
`buildDispatchMultiResponse` on `orchestrate-cycle.js`.
|
|
63
|
+
- `guardLastResults()` and `dispatchByRoute()` in `orchestrate.js`.
|
|
64
|
+
- Defensive guards in `handleSortResult` for quench and appraise routes.
|
|
65
|
+
- Integration tests: `tests/orchestrate-quench.integration.test.js`,
|
|
66
|
+
`tests/orchestrate-appraise.integration.test.js`,
|
|
67
|
+
`tests/orchestrate-contract.test.js`.
|
|
68
|
+
- Unit tests: `tests/quench-module.test.js`, `tests/appraise-module.test.js`.
|
|
69
|
+
|
|
3
70
|
## [3.3.9] - 2026-05-19
|
|
4
71
|
|
|
5
72
|
### Fixed
|
|
@@ -238,7 +238,7 @@ Input artefacts (files matching an input type's `file-patterns`) are read-only.
|
|
|
238
238
|
When an unrecoverable error occurs (e.g. assay extractor abort, invalid JSONL, or memory-sync failure), the orchestrator marks `WORK.md` frontmatter with `status: failed` and a `reason`. The flow is then locked:
|
|
239
239
|
|
|
240
240
|
- **Blocked tools.** All mutation tools refuse to run and return an error referencing the failure reason:
|
|
241
|
-
- **Lifecycle:** `foundry_stage_begin`, `foundry_orchestrate`, `foundry_workfile_create
|
|
241
|
+
- **Lifecycle:** `foundry_stage_begin`, `foundry_orchestrate`, `foundry_workfile_create`
|
|
242
242
|
- **Stage work:** `foundry_assay_run`, `foundry_validate_run`
|
|
243
243
|
- **Feedback writes:** `foundry_feedback_add`, `foundry_feedback_action`, `foundry_feedback_wontfix`, `foundry_feedback_resolve` (`foundry_feedback_list` remains callable)
|
|
244
244
|
- **Appraiser selection:** `foundry_appraisers_select`
|
package/dist/docs/tools.md
CHANGED
|
@@ -59,7 +59,6 @@ state machine, see [`docs/concepts.md`](./concepts.md) and
|
|
|
59
59
|
|
|
60
60
|
**Artefacts**
|
|
61
61
|
- [`foundry_artefacts_list`](#foundry_artefacts_list)
|
|
62
|
-
- [`foundry_artefacts_set_status`](#foundry_artefacts_set_status)
|
|
63
62
|
|
|
64
63
|
**Feedback**
|
|
65
64
|
- [`foundry_feedback_add`](#foundry_feedback_add)
|
|
@@ -326,41 +325,19 @@ flow** (escape hatch).
|
|
|
326
325
|
|
|
327
326
|
### `foundry_artefacts_list`
|
|
328
327
|
|
|
329
|
-
> List
|
|
330
|
-
> callers should always pass the current cycle to avoid stale rows.
|
|
328
|
+
> List artefact changes on the current work branch for the current cycle.
|
|
331
329
|
|
|
332
330
|
**Args:**
|
|
333
|
-
-
|
|
331
|
+
- none.
|
|
334
332
|
|
|
335
|
-
**Returns:** array of `{file,
|
|
336
|
-
|
|
333
|
+
**Returns:** array of `{file, state}` entries. `{error: "WORK.md not found"}`
|
|
334
|
+
if `WORK.md` is missing, or `{error: "current cycle not found in WORK.md frontmatter"}`
|
|
335
|
+
if `WORK.md` has no current cycle.
|
|
337
336
|
|
|
338
337
|
**Stage requirements:** none.
|
|
339
338
|
|
|
340
339
|
**Side effects:** none.
|
|
341
340
|
|
|
342
|
-
### `foundry_artefacts_set_status`
|
|
343
|
-
|
|
344
|
-
> Update the status of an artefact in `WORK.md` (`done` | `blocked`
|
|
345
|
-
> only).
|
|
346
|
-
|
|
347
|
-
**Args:**
|
|
348
|
-
- `file` (string, required): Artefact file path.
|
|
349
|
-
- `status` (string, required): New status (`done` | `blocked`).
|
|
350
|
-
|
|
351
|
-
**Returns:** `{ ok: true }`. On invalid input: `{ error: <message> }`.
|
|
352
|
-
|
|
353
|
-
**Stage requirements:** requires no active stage. Refuses on failed
|
|
354
|
-
flow.
|
|
355
|
-
|
|
356
|
-
**Failure modes:**
|
|
357
|
-
- Invalid status, unknown file, malformed table → error from
|
|
358
|
-
`setArtefactStatus`.
|
|
359
|
-
|
|
360
|
-
**Side effects:** rewrites `WORK.md`.
|
|
361
|
-
|
|
362
|
-
---
|
|
363
|
-
|
|
364
341
|
## Feedback
|
|
365
342
|
|
|
366
343
|
All feedback tools (except `foundry_feedback_list`) require an active
|
package/dist/docs/work-spec.md
CHANGED
|
@@ -168,11 +168,11 @@ A crash between the two steps leaves the live file untouched.
|
|
|
168
168
|
| Frontmatter (`flow`, `cycle`) | `foundry_workfile_create` (flow skill) | updated in place as the flow advances between cycles |
|
|
169
169
|
| Frontmatter (`stages`, `max-iterations`, `human-appraise`, `deadlock-appraise`, `deadlock-iterations`, `models`) | `foundry_orchestrate` (first call of each cycle, internally) | reset on each new cycle |
|
|
170
170
|
| Goal | `foundry_workfile_create` (flow skill) | nobody |
|
|
171
|
-
|
|
|
171
|
+
| Artefact files | forge stage writes files on disk | git tracks file changes; cycle-level state records completion or failure |
|
|
172
172
|
| `WORK.feedback.yaml` | `foundry_feedback_add` (`quench` / `appraise` / `human-appraise`) | `foundry_feedback_action` / `foundry_feedback_wontfix` (forge), `foundry_feedback_resolve` (source stage / human-appraise override); sort writes only deadlocked snapshots |
|
|
173
173
|
| `WORK.history.yaml` | `foundry_orchestrate` | `foundry_orchestrate` |
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Artefact files are discovered from branch changes matching the cycle output type's `file-patterns`.
|
|
176
176
|
|
|
177
177
|
## WORK.history.yaml
|
|
178
178
|
|