@really-knows-ai/foundry 2.3.1 → 3.0.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/README.md +200 -198
- package/dist/.opencode/plugins/foundry-tools/appraiser-tools.js +28 -0
- package/dist/.opencode/plugins/foundry-tools/artefact-tools.js +58 -0
- package/dist/.opencode/plugins/foundry-tools/assay-tools.js +92 -0
- package/dist/.opencode/plugins/foundry-tools/attestation-tools.js +191 -0
- package/dist/.opencode/plugins/foundry-tools/config-create-tools.js +128 -0
- package/dist/.opencode/plugins/foundry-tools/config-law-tools.js +380 -0
- package/dist/.opencode/plugins/foundry-tools/config-tools.js +43 -0
- package/dist/.opencode/plugins/foundry-tools/feedback-tools.js +234 -0
- package/dist/.opencode/plugins/foundry-tools/git-helpers.js +354 -0
- package/dist/.opencode/plugins/foundry-tools/git-tools.js +181 -0
- package/dist/.opencode/plugins/foundry-tools/helpers.js +340 -0
- package/dist/.opencode/plugins/foundry-tools/history-tools.js +20 -0
- package/dist/.opencode/plugins/foundry-tools/memory-admin-tools.js +296 -0
- package/dist/.opencode/plugins/foundry-tools/memory-helpers.js +104 -0
- package/dist/.opencode/plugins/foundry-tools/memory-tools.js +286 -0
- package/dist/.opencode/plugins/foundry-tools/orchestrate-tool.js +159 -0
- package/dist/.opencode/plugins/foundry-tools/snapshot-tools.js +104 -0
- package/dist/.opencode/plugins/foundry-tools/stage-tools.js +186 -0
- package/dist/.opencode/plugins/foundry-tools/validate-tools.js +263 -0
- package/dist/.opencode/plugins/foundry-tools/workfile-tools.js +102 -0
- package/dist/.opencode/plugins/foundry.js +105 -0
- package/dist/CHANGELOG.md +490 -0
- package/dist/LICENSE +21 -0
- package/dist/README.md +278 -0
- package/dist/docs/README.md +59 -0
- package/dist/docs/architecture.md +434 -0
- package/dist/docs/concepts.md +396 -0
- package/dist/docs/getting-started.md +345 -0
- package/dist/docs/memory-maintenance.md +176 -0
- package/dist/docs/tools.md +1411 -0
- package/dist/docs/work-spec.md +283 -0
- package/dist/scripts/lib/artefacts.js +151 -0
- package/dist/scripts/lib/assay/loader.js +151 -0
- package/dist/scripts/lib/assay/parse-jsonl.js +102 -0
- package/dist/scripts/lib/assay/permissions.js +52 -0
- package/dist/scripts/lib/assay/run.js +219 -0
- package/dist/scripts/lib/assay/spawn-with-timeout.js +138 -0
- package/dist/scripts/lib/attestation/attest.js +111 -0
- package/dist/scripts/lib/attestation/canonical-json.js +109 -0
- package/dist/scripts/lib/attestation/hash.js +17 -0
- package/dist/scripts/lib/attestation/parse.js +14 -0
- package/dist/scripts/lib/attestation/payload.js +106 -0
- package/dist/scripts/lib/attestation/render.js +16 -0
- package/dist/scripts/lib/attestation/verify.js +15 -0
- package/dist/scripts/lib/branch-guard.js +72 -0
- package/dist/scripts/lib/config-creators/appraiser.js +9 -0
- package/dist/scripts/lib/config-creators/artefact-type.js +9 -0
- package/dist/scripts/lib/config-creators/cycle.js +11 -0
- package/dist/scripts/lib/config-creators/factory.js +49 -0
- package/dist/scripts/lib/config-creators/flow.js +11 -0
- package/dist/scripts/lib/config-validators/appraiser.js +49 -0
- package/dist/scripts/lib/config-validators/artefact-type.js +38 -0
- package/dist/scripts/lib/config-validators/cycle.js +131 -0
- package/dist/scripts/lib/config-validators/flow.js +57 -0
- package/dist/scripts/lib/config-validators/helpers.js +96 -0
- package/dist/scripts/lib/config-validators/law.js +96 -0
- package/dist/scripts/lib/config.js +393 -0
- package/dist/scripts/lib/failed-flow.js +131 -0
- package/dist/scripts/lib/feedback-store.js +249 -0
- package/dist/scripts/lib/feedback-transitions.js +105 -0
- package/dist/scripts/lib/finalize.js +70 -0
- package/dist/scripts/lib/foundational-guards.js +13 -0
- package/dist/scripts/lib/git-bridge.js +77 -0
- package/dist/scripts/lib/git-finish/work-finish.js +233 -0
- package/dist/scripts/lib/git-policy.js +101 -0
- package/dist/scripts/lib/guards.js +125 -0
- package/dist/scripts/lib/history.js +132 -0
- package/dist/scripts/lib/memory/admin/create-edge-type.js +91 -0
- package/dist/scripts/lib/memory/admin/create-entity-type.js +43 -0
- package/dist/scripts/lib/memory/admin/create-extractor.js +67 -0
- package/dist/scripts/lib/memory/admin/drop-edge-type.js +40 -0
- package/dist/scripts/lib/memory/admin/drop-entity-type.js +172 -0
- package/dist/scripts/lib/memory/admin/dump.js +47 -0
- package/dist/scripts/lib/memory/admin/helpers.js +31 -0
- package/dist/scripts/lib/memory/admin/init.js +170 -0
- package/dist/scripts/lib/memory/admin/live-store.js +76 -0
- package/dist/scripts/lib/memory/admin/reembed.js +285 -0
- package/dist/scripts/lib/memory/admin/rename-edge-type.js +54 -0
- package/dist/scripts/lib/memory/admin/rename-entity-type.js +151 -0
- package/dist/scripts/lib/memory/admin/reset.js +24 -0
- package/dist/scripts/lib/memory/admin/vacuum.js +9 -0
- package/dist/scripts/lib/memory/admin/validate.js +19 -0
- package/dist/scripts/lib/memory/config.js +149 -0
- package/dist/scripts/lib/memory/cozo.js +136 -0
- package/dist/scripts/lib/memory/drift.js +71 -0
- package/dist/scripts/lib/memory/embeddings.js +128 -0
- package/dist/scripts/lib/memory/frontmatter.js +75 -0
- package/dist/scripts/lib/memory/ndjson.js +84 -0
- package/dist/scripts/lib/memory/paths.js +25 -0
- package/dist/scripts/lib/memory/permissions.js +41 -0
- package/dist/scripts/lib/memory/prompt.js +109 -0
- package/dist/scripts/lib/memory/query.js +56 -0
- package/dist/scripts/lib/memory/reads.js +109 -0
- package/dist/scripts/lib/memory/schema.js +64 -0
- package/dist/scripts/lib/memory/search.js +73 -0
- package/dist/scripts/lib/memory/singleton.js +49 -0
- package/dist/scripts/lib/memory/store.js +162 -0
- package/dist/scripts/lib/memory/types.js +93 -0
- package/dist/scripts/lib/memory/validate.js +58 -0
- package/dist/scripts/lib/memory/writes.js +40 -0
- package/{scripts → dist/scripts}/lib/pending.js +7 -2
- package/dist/scripts/lib/secret.js +59 -0
- package/{scripts → dist/scripts}/lib/slug.js +3 -2
- package/dist/scripts/lib/snapshot/finish.js +103 -0
- package/dist/scripts/lib/snapshot/inspect.js +253 -0
- package/dist/scripts/lib/snapshot/render.js +55 -0
- package/dist/scripts/lib/sort-fs-check.js +121 -0
- package/dist/scripts/lib/sort-routing.js +101 -0
- package/{scripts → dist/scripts}/lib/stage-guard.js +12 -6
- package/{scripts → dist/scripts}/lib/state.js +4 -0
- package/dist/scripts/lib/token.js +57 -0
- package/dist/scripts/lib/tracing.js +59 -0
- package/dist/scripts/lib/ulid.js +100 -0
- package/dist/scripts/lib/validator-jsonl.js +162 -0
- package/{scripts → dist/scripts}/lib/workfile.js +38 -20
- package/dist/scripts/orchestrate-cycle.js +215 -0
- package/dist/scripts/orchestrate-phases.js +314 -0
- package/dist/scripts/orchestrate.js +163 -0
- package/dist/scripts/sort.js +278 -0
- package/{skills → dist/skills}/add-appraiser/SKILL.md +42 -6
- package/{skills → dist/skills}/add-artefact-type/SKILL.md +49 -21
- package/{skills → dist/skills}/add-cycle/SKILL.md +60 -14
- package/dist/skills/add-extractor/SKILL.md +133 -0
- package/{skills → dist/skills}/add-flow/SKILL.md +39 -7
- package/dist/skills/add-law/SKILL.md +191 -0
- package/dist/skills/add-memory-edge-type/SKILL.md +52 -0
- package/dist/skills/add-memory-entity-type/SKILL.md +74 -0
- package/{skills → dist/skills}/appraise/SKILL.md +62 -13
- package/dist/skills/assay/SKILL.md +72 -0
- package/dist/skills/change-embedding-model/SKILL.md +58 -0
- package/dist/skills/drop-memory-edge-type/SKILL.md +54 -0
- package/dist/skills/drop-memory-entity-type/SKILL.md +57 -0
- package/dist/skills/dry-run/SKILL.md +116 -0
- package/{skills → dist/skills}/flow/SKILL.md +15 -2
- package/dist/skills/forge/SKILL.md +121 -0
- package/dist/skills/human-appraise/SKILL.md +153 -0
- package/{skills → dist/skills}/init-foundry/SKILL.md +23 -4
- package/dist/skills/init-memory/SKILL.md +92 -0
- package/{skills → dist/skills}/orchestrate/SKILL.md +30 -4
- package/dist/skills/quench/SKILL.md +99 -0
- package/{skills → dist/skills}/refresh-agents/SKILL.md +1 -1
- package/dist/skills/rename-memory-edge-type/SKILL.md +50 -0
- package/dist/skills/rename-memory-entity-type/SKILL.md +51 -0
- package/dist/skills/reset-memory/SKILL.md +54 -0
- package/dist/skills/upgrade-foundry/SKILL.md +192 -0
- package/package.json +34 -17
- package/.opencode/plugins/foundry.js +0 -761
- package/CHANGELOG.md +0 -90
- package/docs/concepts.md +0 -59
- package/docs/getting-started.md +0 -78
- package/docs/work-spec.md +0 -193
- package/scripts/lib/artefacts.js +0 -124
- package/scripts/lib/config.js +0 -175
- package/scripts/lib/feedback-transitions.js +0 -25
- package/scripts/lib/feedback.js +0 -440
- package/scripts/lib/finalize.js +0 -41
- package/scripts/lib/history.js +0 -59
- package/scripts/lib/secret.js +0 -23
- package/scripts/lib/tags.js +0 -108
- package/scripts/lib/token.js +0 -26
- package/scripts/orchestrate.js +0 -418
- package/scripts/sort.js +0 -370
- package/scripts/validate-tags.js +0 -54
- package/skills/add-law/SKILL.md +0 -105
- package/skills/forge/SKILL.md +0 -88
- package/skills/human-appraise/SKILL.md +0 -82
- package/skills/quench/SKILL.md +0 -62
- package/skills/upgrade-foundry/SKILL.md +0 -216
- /package/{skills → dist/skills}/list-agents/SKILL.md +0 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sort — deterministic routing for a Foundry Cycle.
|
|
5
|
+
*
|
|
6
|
+
* Reads WORK.md, WORK.feedback.yaml, and WORK.history.yaml to determine
|
|
7
|
+
* the next stage to execute, or signal completion/blocked.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/sort.js [--work WORK.md] [--history WORK.history.yaml]
|
|
11
|
+
*
|
|
12
|
+
* Output (stdout): a full stage alias (e.g., forge:write-haiku), 'done', or 'blocked'
|
|
13
|
+
* Exit code: 0 on success, 1 on error
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseFrontmatter } from './lib/workfile.js';
|
|
17
|
+
import { loadHistory } from './lib/history.js';
|
|
18
|
+
import { openFeedbackStore } from './lib/feedback-store.js';
|
|
19
|
+
import { ulid as defaultUlid } from './lib/ulid.js';
|
|
20
|
+
import {
|
|
21
|
+
baseStage,
|
|
22
|
+
findFirst,
|
|
23
|
+
determineRoute,
|
|
24
|
+
} from './lib/sort-routing.js';
|
|
25
|
+
import {
|
|
26
|
+
defaultIO,
|
|
27
|
+
checkModifiedFiles,
|
|
28
|
+
getDirtyToolManagedFiles,
|
|
29
|
+
} from './lib/sort-fs-check.js';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Top-level deadlock pass (spec §6.1)
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Walk the feedback store and write a `state=deadlocked` snapshot for every
|
|
37
|
+
* non-resolved item whose history depth has reached the configured threshold.
|
|
38
|
+
* One atomic batch write via `store.writeDeadlockedSnapshots(ids, ...)`.
|
|
39
|
+
*
|
|
40
|
+
* Sort is the only writer of `state=deadlocked` per spec §6.1.
|
|
41
|
+
*
|
|
42
|
+
* @returns {boolean} true iff at least one snapshot was written.
|
|
43
|
+
*/
|
|
44
|
+
function runDeadlockPass(store, { threshold, enabled, cycle }) {
|
|
45
|
+
if (!enabled) return false;
|
|
46
|
+
const qualifying = store.list().filter(item => {
|
|
47
|
+
// history[0] is the most recent state per the feedback-store invariant
|
|
48
|
+
// (entries are prepended to keep newest at head).
|
|
49
|
+
const head = item.history[0];
|
|
50
|
+
if (head.state === 'resolved' || head.state === 'deadlocked') return false;
|
|
51
|
+
return item.history.length >= threshold;
|
|
52
|
+
});
|
|
53
|
+
if (qualifying.length === 0) return false;
|
|
54
|
+
store.writeDeadlockedSnapshots(
|
|
55
|
+
qualifying.map(it => it.id),
|
|
56
|
+
`depth >= threshold=${threshold}`,
|
|
57
|
+
'sort',
|
|
58
|
+
cycle,
|
|
59
|
+
);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// runSort — structured result for programmatic use
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
function isDispatchableRoute(route) {
|
|
68
|
+
return typeof route === 'string' && /^(assay|forge|quench|appraise|human-appraise):/.test(route);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateStages(stages) {
|
|
72
|
+
if (!stages || !Array.isArray(stages)) return { error: 'No stages in WORK.md frontmatter' };
|
|
73
|
+
if (!findFirst(stages, 'forge')) return { error: 'stages must include at least one forge stage' };
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateWorkMd(workPath, io) {
|
|
78
|
+
if (!io.exists(workPath)) return { error: 'WORK.md not found' };
|
|
79
|
+
const workText = io.readFile(workPath);
|
|
80
|
+
const frontmatter = parseFrontmatter(workText);
|
|
81
|
+
if (!frontmatter.cycle) return { error: 'No cycle in WORK.md frontmatter' };
|
|
82
|
+
const stagesError = validateStages(frontmatter.stages);
|
|
83
|
+
if (stagesError) return stagesError;
|
|
84
|
+
return { frontmatter, cycle: frontmatter.cycle, stages: frontmatter.stages };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function extractFrontmatterDefaults(frontmatter) {
|
|
88
|
+
return {
|
|
89
|
+
maxIterations: frontmatter['max-iterations'] ?? 3,
|
|
90
|
+
humanAppraiseEnabled: frontmatter['human-appraise'] === true,
|
|
91
|
+
deadlockAppraise: frontmatter['deadlock-appraise'] !== false,
|
|
92
|
+
deadlockIterations: frontmatter['deadlock-iterations'] ?? 5,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkDirtyFiles(history, io) {
|
|
97
|
+
if (history.length === 0) return null;
|
|
98
|
+
const dirty = getDirtyToolManagedFiles(io);
|
|
99
|
+
if (dirty.length === 0) return null;
|
|
100
|
+
return `Uncommitted tool-managed files since last sort: ${dirty.join(', ')}. `
|
|
101
|
+
+ `Each stage's commit is performed internally by foundry_orchestrate; `
|
|
102
|
+
+ `if you see this, the prior stage's commit was skipped or aborted. `
|
|
103
|
+
+ `Re-run foundry_orchestrate or commit the listed files manually before retrying.`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function loadFeedbackAndRunDeadlock(cycle, deadlockIterations, deadlockAppraise, io) {
|
|
107
|
+
const store = openFeedbackStore('WORK.feedback.yaml', io);
|
|
108
|
+
runDeadlockPass(store, { threshold: deadlockIterations, enabled: deadlockAppraise, cycle });
|
|
109
|
+
const feedback = store.list().map(item => ({
|
|
110
|
+
id: item.id,
|
|
111
|
+
file: item.file,
|
|
112
|
+
state: item.history[0].state,
|
|
113
|
+
depth: item.history.length,
|
|
114
|
+
}));
|
|
115
|
+
const anyDeadlocked = feedback.some(f => f.state === 'deadlocked');
|
|
116
|
+
return { feedback, anyDeadlocked };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveCycleDef(cycleDef, frontmatter, foundryDir, cycle) {
|
|
120
|
+
return cycleDef || frontmatter['cycle-def'] || `${foundryDir}/cycles/${cycle}.md`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function checkModifiedFilesAfterLastStage({ history, foundryDir, cycleDef, cycle, frontmatter, io }) {
|
|
124
|
+
const nonSortHistory = history.filter(e => baseStage(e.stage || '') !== 'sort');
|
|
125
|
+
if (nonSortHistory.length === 0) return { nonSortHistory };
|
|
126
|
+
const lastBase = baseStage(nonSortHistory[nonSortHistory.length - 1].stage || '');
|
|
127
|
+
const resolvedCycleDef = resolveCycleDef(cycleDef, frontmatter, foundryDir, cycle);
|
|
128
|
+
const result = checkModifiedFiles(lastBase, foundryDir, resolvedCycleDef, cycle, io);
|
|
129
|
+
if (!result.ok) {
|
|
130
|
+
return { error: `File modification violation after ${lastBase} stage: ${result.violations.join(', ')}` };
|
|
131
|
+
}
|
|
132
|
+
return { nonSortHistory };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getCurrentNonSortStage(nonSortHistory) {
|
|
136
|
+
return nonSortHistory.length > 0 ? nonSortHistory[nonSortHistory.length - 1].stage : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveDeadlockRoute(stages, nonSortHistory, cycle) {
|
|
140
|
+
const currentNonSort = getCurrentNonSortStage(nonSortHistory);
|
|
141
|
+
if (currentNonSort && baseStage(currentNonSort) === 'human-appraise') return 'blocked';
|
|
142
|
+
return findFirst(stages, 'human-appraise') || `human-appraise:${cycle}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveRoute(ctx) {
|
|
146
|
+
if (ctx.anyDeadlocked) return resolveDeadlockRoute(ctx.stages, ctx.nonSortHistory, ctx.cycle);
|
|
147
|
+
return determineRoute(ctx.stages, ctx.history, ctx.feedback, ctx.maxIterations);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resolveModel(route, frontmatter, agentsDir, io) {
|
|
151
|
+
const routeBase = baseStage(route);
|
|
152
|
+
if (!frontmatter.models || !frontmatter.models[routeBase]) return null;
|
|
153
|
+
const modelId = frontmatter.models[routeBase];
|
|
154
|
+
const model = `foundry-${modelId.replace(/[/.]/g, '-')}`;
|
|
155
|
+
const agentPath = `${agentsDir}/${model}.md`;
|
|
156
|
+
if (!io.exists(agentPath)) {
|
|
157
|
+
return {
|
|
158
|
+
error: `Missing required subagent: ${model}.md is not present in ${agentsDir}/. `
|
|
159
|
+
+ `Run the refresh-agents skill to regenerate agent files, then restart.`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return model;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function checkModel(route, frontmatter, agentsDir, io) {
|
|
166
|
+
const modelResult = resolveModel(route, frontmatter, agentsDir, io);
|
|
167
|
+
if (modelResult && modelResult.error) return { error: modelResult.error };
|
|
168
|
+
return { model: typeof modelResult === 'string' ? modelResult : null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function mintToken({ route, model, mint, cycle, now, ulid }) {
|
|
172
|
+
const result = { route, ...(model ? { model } : {}) };
|
|
173
|
+
if (mint && isDispatchableRoute(route)) {
|
|
174
|
+
const token = mint({ route, cycle, exp: now + 10 * 60 * 1000, nonce: ulid(now) });
|
|
175
|
+
if (token) result.token = token;
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// runSort is decomposed into single-purpose phase helpers above so the
|
|
181
|
+
// orchestrating function itself stays within the configured complexity
|
|
182
|
+
// limit. Each phase either returns an error envelope (handled by
|
|
183
|
+
// firstError) or contributes data to the routing decision.
|
|
184
|
+
|
|
185
|
+
function firstError(...envelopes) {
|
|
186
|
+
for (const env of envelopes) {
|
|
187
|
+
if (env && env.error) return env.error;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function preparePhases({ workPath, historyPath, foundryDir, cycleDef, io }) {
|
|
193
|
+
const validation = validateWorkMd(workPath, io);
|
|
194
|
+
if (validation.error) return { kind: 'blocked', details: validation.error };
|
|
195
|
+
const { frontmatter, cycle, stages } = validation;
|
|
196
|
+
const defaults = extractFrontmatterDefaults(frontmatter);
|
|
197
|
+
const history = loadHistory(historyPath, cycle, io);
|
|
198
|
+
const dirtyError = checkDirtyFiles(history, io);
|
|
199
|
+
if (dirtyError) return { kind: 'violation', details: dirtyError };
|
|
200
|
+
const { feedback, anyDeadlocked } = loadFeedbackAndRunDeadlock(
|
|
201
|
+
cycle, defaults.deadlockIterations, defaults.deadlockAppraise, io,
|
|
202
|
+
);
|
|
203
|
+
const fileCheck = checkModifiedFilesAfterLastStage({
|
|
204
|
+
history, foundryDir, cycleDef, cycle, frontmatter, io,
|
|
205
|
+
});
|
|
206
|
+
const violation = firstError(fileCheck);
|
|
207
|
+
if (violation) return { kind: 'violation', details: violation };
|
|
208
|
+
return {
|
|
209
|
+
kind: 'ok',
|
|
210
|
+
frontmatter, cycle, stages, defaults, history, feedback, anyDeadlocked,
|
|
211
|
+
nonSortHistory: fileCheck.nonSortHistory,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const RUN_SORT_DEFAULTS = Object.freeze({
|
|
216
|
+
workPath: 'WORK.md',
|
|
217
|
+
historyPath: 'WORK.history.yaml',
|
|
218
|
+
foundryDir: 'foundry',
|
|
219
|
+
cycleDef: undefined,
|
|
220
|
+
agentsDir: '.opencode/agents',
|
|
221
|
+
mint: undefined,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
function withRunSortDefaults(args) {
|
|
225
|
+
const merged = { ...RUN_SORT_DEFAULTS, ...args };
|
|
226
|
+
if (merged.now === undefined) merged.now = Date.now();
|
|
227
|
+
if (merged.ulid === undefined) merged.ulid = defaultUlid;
|
|
228
|
+
return merged;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function buildRouteCtx(prep) {
|
|
232
|
+
return {
|
|
233
|
+
stages: prep.stages,
|
|
234
|
+
history: prep.history,
|
|
235
|
+
feedback: prep.feedback,
|
|
236
|
+
maxIterations: prep.defaults.maxIterations,
|
|
237
|
+
cycle: prep.cycle,
|
|
238
|
+
anyDeadlocked: prep.anyDeadlocked,
|
|
239
|
+
nonSortHistory: prep.nonSortHistory,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function runSort(args = {}, io = defaultIO) {
|
|
244
|
+
const opts = withRunSortDefaults(args);
|
|
245
|
+
const prep = preparePhases({ ...opts, io });
|
|
246
|
+
if (prep.kind !== 'ok') return { route: prep.kind, details: prep.details };
|
|
247
|
+
|
|
248
|
+
const route = resolveRoute(buildRouteCtx(prep));
|
|
249
|
+
const modelCheck = checkModel(route, prep.frontmatter, opts.agentsDir, io);
|
|
250
|
+
if (modelCheck.error) return { route: 'violation', details: modelCheck.error };
|
|
251
|
+
|
|
252
|
+
return mintToken({
|
|
253
|
+
route, model: modelCheck.model, mint: opts.mint, cycle: prep.cycle, now: opts.now, ulid: opts.ulid,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Exports (for testing) — keep main() private
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
export { parseArtefactsTable } from './lib/artefacts.js';
|
|
262
|
+
export { loadHistory } from './lib/history.js';
|
|
263
|
+
export { parseFrontmatter } from './lib/workfile.js';
|
|
264
|
+
export {
|
|
265
|
+
baseStage,
|
|
266
|
+
findFirst,
|
|
267
|
+
nextInRoute,
|
|
268
|
+
determineRoute,
|
|
269
|
+
nextAfterQuench,
|
|
270
|
+
nextAfterAppraise,
|
|
271
|
+
} from './lib/sort-routing.js';
|
|
272
|
+
export {
|
|
273
|
+
globMatch,
|
|
274
|
+
getModifiedFiles,
|
|
275
|
+
getAllowedPatterns,
|
|
276
|
+
checkModifiedFiles,
|
|
277
|
+
getDirtyToolManagedFiles,
|
|
278
|
+
} from './lib/sort-fs-check.js';
|
|
@@ -10,9 +10,32 @@ You help the user create a new appraiser personality. You ensure it's genuinely
|
|
|
10
10
|
|
|
11
11
|
## Prerequisites
|
|
12
12
|
|
|
13
|
-
Before running this skill, verify
|
|
13
|
+
Before running this skill, verify all three of the following:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
1. The `foundry/` directory exists in the project root. If it does not
|
|
16
|
+
exist, stop and tell the user:
|
|
17
|
+
|
|
18
|
+
> Foundry is not initialized in this project. Run the
|
|
19
|
+
> `init-foundry` skill first to create the foundry/ directory
|
|
20
|
+
> structure.
|
|
21
|
+
|
|
22
|
+
2. The current git branch is a `config/*` branch. Run
|
|
23
|
+
`git rev-parse --abbrev-ref HEAD` and confirm it matches
|
|
24
|
+
`config/<description>`.
|
|
25
|
+
|
|
26
|
+
3. If the branch does not start with `config/`, instruct the user to
|
|
27
|
+
create one before continuing:
|
|
28
|
+
|
|
29
|
+
> Foundry configuration changes must be made on a config/* branch.
|
|
30
|
+
> From a clean main branch, call:
|
|
31
|
+
>
|
|
32
|
+
> `foundry_git_branch({ kind: "config", description: "<short-name>" })`
|
|
33
|
+
>
|
|
34
|
+
> Then re-run this skill.
|
|
35
|
+
|
|
36
|
+
If the user is on a `dry-run/*/*` branch, they must finish
|
|
37
|
+
that dry-run first (`foundry_git_finish({ message, confirm: true })`)
|
|
38
|
+
before re-running this skill on the parent `config/*`.
|
|
16
39
|
|
|
17
40
|
## Protocol
|
|
18
41
|
|
|
@@ -81,11 +104,25 @@ Iterate until the user is happy with the personality description. Key things to
|
|
|
81
104
|
- Does the description give the LLM enough direction to adopt a consistent voice?
|
|
82
105
|
- Is it clear what this appraiser would flag vs let pass?
|
|
83
106
|
|
|
84
|
-
### 6.
|
|
107
|
+
### 6. Validate the draft
|
|
108
|
+
|
|
109
|
+
Call `foundry_config_validate_appraiser({ name: "<id>", body: "<full markdown>" })`.
|
|
110
|
+
|
|
111
|
+
If the result is `{ ok: false, errors: [...] }`, address each error (adjust the body) and re-run until you get `{ ok: true }`. Common issues: missing required frontmatter keys, references to artefact types or flows that don't exist yet.
|
|
112
|
+
|
|
113
|
+
### 7. Create the file
|
|
114
|
+
|
|
115
|
+
Call `foundry_config_create_appraiser({ name: "<id>", body: "<full markdown>" })`. The tool:
|
|
116
|
+
|
|
117
|
+
- re-validates the body (TOCTOU);
|
|
118
|
+
- writes `foundry/appraisers/<id>.md`;
|
|
119
|
+
- produces one git commit on the current `config/*` branch.
|
|
120
|
+
|
|
121
|
+
If the tool returns `{ ok: false, errors }` because the target file already exists, the user should edit the file by hand on this `config/*` branch — `foundry_config_create_appraiser` does not support updates.
|
|
85
122
|
|
|
86
|
-
|
|
123
|
+
Show the user the resulting commit hash from the response.
|
|
87
124
|
|
|
88
|
-
###
|
|
125
|
+
### 8. Mention artefact type configuration
|
|
89
126
|
|
|
90
127
|
After creating the appraiser, remind the user:
|
|
91
128
|
|
|
@@ -101,7 +138,6 @@ After creating the appraiser, remind the user:
|
|
|
101
138
|
|
|
102
139
|
## What you do NOT do
|
|
103
140
|
|
|
104
|
-
- You do not write files without showing the user first
|
|
105
141
|
- You do not skip the semantic overlap check
|
|
106
142
|
- You do not modify artefact type definitions — that is the user's choice
|
|
107
143
|
- You do not create appraisers with duplicate ids
|
|
@@ -10,9 +10,32 @@ You help the user create a new artefact type. You ensure it doesn't conflict wit
|
|
|
10
10
|
|
|
11
11
|
## Prerequisites
|
|
12
12
|
|
|
13
|
-
Before running this skill, verify
|
|
13
|
+
Before running this skill, verify all three of the following:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
1. The `foundry/` directory exists in the project root. If it does not
|
|
16
|
+
exist, stop and tell the user:
|
|
17
|
+
|
|
18
|
+
> Foundry is not initialized in this project. Run the
|
|
19
|
+
> `init-foundry` skill first to create the foundry/ directory
|
|
20
|
+
> structure.
|
|
21
|
+
|
|
22
|
+
2. The current git branch is a `config/*` branch. Run
|
|
23
|
+
`git rev-parse --abbrev-ref HEAD` and confirm it matches
|
|
24
|
+
`config/<description>`.
|
|
25
|
+
|
|
26
|
+
3. If the branch does not start with `config/`, instruct the user to
|
|
27
|
+
create one before continuing:
|
|
28
|
+
|
|
29
|
+
> Foundry configuration changes must be made on a config/* branch.
|
|
30
|
+
> From a clean main branch, call:
|
|
31
|
+
>
|
|
32
|
+
> `foundry_git_branch({ kind: "config", description: "<short-name>" })`
|
|
33
|
+
>
|
|
34
|
+
> Then re-run this skill.
|
|
35
|
+
|
|
36
|
+
If the user is on a `dry-run/*/*` branch, they must finish
|
|
37
|
+
that dry-run first (`foundry_git_finish({ message, confirm: true })`)
|
|
38
|
+
before re-running this skill on the parent `config/*`.
|
|
16
39
|
|
|
17
40
|
## Protocol
|
|
18
41
|
|
|
@@ -21,8 +44,7 @@ Before running this skill, verify that the `foundry/` directory exists in the pr
|
|
|
21
44
|
From the user's prompt, establish:
|
|
22
45
|
- `id` — lowercase, hyphenated identifier
|
|
23
46
|
- `name` — human-readable name
|
|
24
|
-
- `file-patterns` — glob patterns for files this type produces
|
|
25
|
-
- `output` — output directory
|
|
47
|
+
- `file-patterns` — glob patterns for files this type produces (forge's write scope is exactly these patterns)
|
|
26
48
|
- A prose description of what this artefact type is
|
|
27
49
|
|
|
28
50
|
If any of these are missing, ask.
|
|
@@ -65,16 +87,12 @@ Present the definition to the user:
|
|
|
65
87
|
|
|
66
88
|
```markdown
|
|
67
89
|
---
|
|
68
|
-
id: <id>
|
|
69
90
|
name: <name>
|
|
70
91
|
file-patterns:
|
|
71
92
|
- "<pattern>"
|
|
72
|
-
output: <output-dir>
|
|
73
|
-
appraisers:
|
|
74
|
-
count: 3
|
|
75
93
|
---
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
## Definition
|
|
78
96
|
|
|
79
97
|
<description>
|
|
80
98
|
```
|
|
@@ -137,26 +155,36 @@ Check the project's `package.json` for `"type": "module"`:
|
|
|
137
155
|
- If CommonJS (no `"type"` field or `"type": "commonjs"`): `require()` is fine, or use `.cjs` extension
|
|
138
156
|
- When in doubt, use `.mjs` or `.cjs` extensions to be explicit regardless of project settings
|
|
139
157
|
|
|
140
|
-
### 8.
|
|
158
|
+
### 8. Validate the draft
|
|
141
159
|
|
|
142
|
-
|
|
160
|
+
Call `foundry_config_validate_artefact_type({ name: "<id>", body: "<full markdown>" })`.
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
If the result is `{ ok: false, errors: [...] }`, address each error (adjust the body) and re-run until you get `{ ok: true }`. Common issues: missing required frontmatter keys, references to artefact types or flows that don't exist yet.
|
|
163
|
+
|
|
164
|
+
### 9. Create the file
|
|
165
|
+
|
|
166
|
+
Call `foundry_config_create_artefact_type({ name: "<id>", body: "<full markdown>" })`. The tool:
|
|
167
|
+
|
|
168
|
+
- re-validates the body (TOCTOU);
|
|
169
|
+
- writes `foundry/artefacts/<id>/definition.md`;
|
|
170
|
+
- produces one git commit on the current `config/*` branch.
|
|
171
|
+
|
|
172
|
+
If the tool returns `{ ok: false, errors }` because the target file already exists, the user should edit the file by hand on this `config/*` branch — `foundry_config_create_artefact_type` does not support updates.
|
|
173
|
+
|
|
174
|
+
Show the user the resulting commit hash from the response.
|
|
175
|
+
|
|
176
|
+
### 10. Add laws and validation files (if defined)
|
|
177
|
+
|
|
178
|
+
The create tool writes only `definition.md`. If you drafted any type-specific laws in step 5, append them to `foundry/artefacts/<id>/laws.md` by hand on this same `config/*` branch (use the `Edit` tool to create the file) and commit that as a separate microcommit.
|
|
150
179
|
|
|
151
|
-
If
|
|
180
|
+
If you drafted validation commands in step 7, write `foundry/artefacts/<id>/validation.md` (and any companion validation script files) by hand and commit as a separate microcommit.
|
|
152
181
|
|
|
153
|
-
###
|
|
182
|
+
### 11. Confirm
|
|
154
183
|
|
|
155
|
-
Show the user the complete file listing and
|
|
184
|
+
Show the user the complete file listing and the commit hashes.
|
|
156
185
|
|
|
157
186
|
## What you do NOT do
|
|
158
187
|
|
|
159
188
|
- You do not create artefact types with overlapping file patterns — this is a hard block
|
|
160
|
-
- You do not write files without showing the user first
|
|
161
189
|
- You do not skip the naming or glob checks
|
|
162
190
|
- You do not create laws without checking for conflicts (delegate to add-law pattern)
|
|
@@ -10,9 +10,32 @@ You help the user create a new foundry cycle and add it to an existing foundry f
|
|
|
10
10
|
|
|
11
11
|
## Prerequisites
|
|
12
12
|
|
|
13
|
-
Before running this skill, verify
|
|
13
|
+
Before running this skill, verify all three of the following:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
1. The `foundry/` directory exists in the project root. If it does not
|
|
16
|
+
exist, stop and tell the user:
|
|
17
|
+
|
|
18
|
+
> Foundry is not initialized in this project. Run the
|
|
19
|
+
> `init-foundry` skill first to create the foundry/ directory
|
|
20
|
+
> structure.
|
|
21
|
+
|
|
22
|
+
2. The current git branch is a `config/*` branch. Run
|
|
23
|
+
`git rev-parse --abbrev-ref HEAD` and confirm it matches
|
|
24
|
+
`config/<description>`.
|
|
25
|
+
|
|
26
|
+
3. If the branch does not start with `config/`, instruct the user to
|
|
27
|
+
create one before continuing:
|
|
28
|
+
|
|
29
|
+
> Foundry configuration changes must be made on a config/* branch.
|
|
30
|
+
> From a clean main branch, call:
|
|
31
|
+
>
|
|
32
|
+
> `foundry_git_branch({ kind: "config", description: "<short-name>" })`
|
|
33
|
+
>
|
|
34
|
+
> Then re-run this skill.
|
|
35
|
+
|
|
36
|
+
If the user is on a `dry-run/*/*` branch, they must finish
|
|
37
|
+
that dry-run first (`foundry_git_finish({ message, confirm: true })`)
|
|
38
|
+
before re-running this skill on the parent `config/*`.
|
|
16
39
|
|
|
17
40
|
## Protocol
|
|
18
41
|
|
|
@@ -27,7 +50,7 @@ Verify the flow exists. If it doesn't, tell the user and ask if they want to cre
|
|
|
27
50
|
From the user's prompt, establish:
|
|
28
51
|
- `id` — lowercase, hyphenated identifier for the foundry cycle
|
|
29
52
|
- `name` — human-readable name
|
|
30
|
-
- `output` — the artefact type this foundry cycle produces (must exist in `foundry/artefacts/`)
|
|
53
|
+
- `output-type` — the artefact type this foundry cycle produces (must exist in `foundry/artefacts/`)
|
|
31
54
|
- `inputs` — artefact types this cycle reads, with a contract type:
|
|
32
55
|
- `type`: `any-of` (at least one must exist) or `all-of` (all must exist)
|
|
33
56
|
- `artefacts`: list of artefact type IDs
|
|
@@ -66,7 +89,7 @@ Ask the user:
|
|
|
66
89
|
|
|
67
90
|
### 5. Validate artefact types
|
|
68
91
|
|
|
69
|
-
For `output` and each entry in `inputs`:
|
|
92
|
+
For `output-type` and each entry in `inputs`:
|
|
70
93
|
- Verify the artefact type exists in `foundry/artefacts/<type>/definition.md`
|
|
71
94
|
- If it doesn't, tell the user and ask if they want to create it first (separate skill)
|
|
72
95
|
|
|
@@ -75,12 +98,12 @@ For `output` and each entry in `inputs`:
|
|
|
75
98
|
Read the flow definition from `foundry/flows/<flow-id>.md`. Check:
|
|
76
99
|
|
|
77
100
|
- No existing foundry cycle in the foundry flow already outputs the same artefact type. Two foundry cycles producing the same type in one foundry flow is a conflict — the file modification enforcement can't distinguish which foundry cycle owns the files.
|
|
78
|
-
- Each `input` artefact type is produced by
|
|
101
|
+
- Each `input` artefact type is produced by some cycle that can run before this one according to the flow's `targets` graph (a reachable predecessor). If an input references an artefact type that no reachable predecessor outputs, warn:
|
|
79
102
|
|
|
80
|
-
> Input `<type>` is not produced by any
|
|
103
|
+
> Input `<type>` is not produced by any reachable predecessor of this foundry cycle in the flow's `targets` graph. The artefact won't exist when this foundry cycle runs.
|
|
81
104
|
>
|
|
82
105
|
> Options:
|
|
83
|
-
> 1. Add a foundry cycle that produces `<type>`
|
|
106
|
+
> 1. Add a foundry cycle that produces `<type>` and route to this cycle via `targets`
|
|
84
107
|
> 2. Remove `<type>` from inputs (this foundry cycle won't have that context)
|
|
85
108
|
> 3. Proceed anyway (the artefact may exist from a previous foundry flow run)
|
|
86
109
|
|
|
@@ -106,7 +129,7 @@ Present the foundry cycle definition to the user:
|
|
|
106
129
|
---
|
|
107
130
|
id: <id>
|
|
108
131
|
name: <name>
|
|
109
|
-
output: <artefact-type-id>
|
|
132
|
+
output-type: <artefact-type-id>
|
|
110
133
|
inputs:
|
|
111
134
|
type: <any-of|all-of>
|
|
112
135
|
artefacts:
|
|
@@ -138,19 +161,42 @@ For input validation:
|
|
|
138
161
|
- Verify that at least one cycle in the flow has the input artefact type(s) as its output
|
|
139
162
|
- If using `all-of`, verify all input types are producible
|
|
140
163
|
|
|
141
|
-
### 11.
|
|
164
|
+
### 11. Validate the draft
|
|
165
|
+
|
|
166
|
+
Call `foundry_config_validate_cycle({ name: "<id>", body: "<full markdown>" })`.
|
|
167
|
+
|
|
168
|
+
If the result is `{ ok: false, errors: [...] }`, address each error (adjust the body) and re-run until you get `{ ok: true }`. Common issues: missing required frontmatter keys, references to artefact types or flows that don't exist yet.
|
|
169
|
+
|
|
170
|
+
### 12. Create the cycle file
|
|
171
|
+
|
|
172
|
+
Call `foundry_config_create_cycle({ name: "<id>", body: "<full markdown>" })`. The tool:
|
|
142
173
|
|
|
143
|
-
-
|
|
144
|
-
-
|
|
174
|
+
- re-validates the body (TOCTOU);
|
|
175
|
+
- writes `foundry/cycles/<id>.md`;
|
|
176
|
+
- produces one git commit on the current `config/*` branch.
|
|
177
|
+
|
|
178
|
+
If the tool returns `{ ok: false, errors }` because the target file already exists, the user should edit the file by hand on this `config/*` branch — `foundry_config_create_cycle` does not support updates.
|
|
179
|
+
|
|
180
|
+
Show the user the resulting commit hash from the response.
|
|
181
|
+
|
|
182
|
+
### 13. Add the cycle to the flow's cycle list
|
|
183
|
+
|
|
184
|
+
`foundry_config_create_cycle` writes the cycle file only. The cycle still needs to appear in the parent flow's `## Cycles` list.
|
|
185
|
+
|
|
186
|
+
Edit `foundry/flows/<flow-id>.md` by hand on this same `config/*` branch using the `Edit` tool. Add the new cycle id under `## Cycles` (if not already present). Commit that edit by hand as a separate microcommit, e.g.:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
git add foundry/flows/<flow-id>.md
|
|
190
|
+
git commit -m "config(flow): add <cycle-id> to <flow-id>"
|
|
191
|
+
```
|
|
145
192
|
|
|
146
|
-
###
|
|
193
|
+
### 14. Confirm
|
|
147
194
|
|
|
148
|
-
Show the user the
|
|
195
|
+
Show the user the cycle file, the updated flow file, and both commit hashes.
|
|
149
196
|
|
|
150
197
|
## What you do NOT do
|
|
151
198
|
|
|
152
199
|
- You do not create foundry cycles that output an artefact type already produced by another foundry cycle in the same foundry flow
|
|
153
|
-
- You do not write files without showing the user first
|
|
154
200
|
- You do not skip artefact type validation
|
|
155
201
|
- You do not create artefact types — that is a separate skill
|
|
156
202
|
- You do not create foundry flows — that is a separate skill
|