@really-knows-ai/foundry 2.3.2 → 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.
Files changed (170) hide show
  1. package/README.md +180 -369
  2. package/dist/.opencode/plugins/foundry-tools/appraiser-tools.js +28 -0
  3. package/dist/.opencode/plugins/foundry-tools/artefact-tools.js +58 -0
  4. package/dist/.opencode/plugins/foundry-tools/assay-tools.js +92 -0
  5. package/dist/.opencode/plugins/foundry-tools/attestation-tools.js +191 -0
  6. package/dist/.opencode/plugins/foundry-tools/config-create-tools.js +128 -0
  7. package/dist/.opencode/plugins/foundry-tools/config-law-tools.js +380 -0
  8. package/dist/.opencode/plugins/foundry-tools/config-tools.js +43 -0
  9. package/dist/.opencode/plugins/foundry-tools/feedback-tools.js +234 -0
  10. package/dist/.opencode/plugins/foundry-tools/git-helpers.js +354 -0
  11. package/dist/.opencode/plugins/foundry-tools/git-tools.js +181 -0
  12. package/dist/.opencode/plugins/foundry-tools/helpers.js +340 -0
  13. package/dist/.opencode/plugins/foundry-tools/history-tools.js +20 -0
  14. package/dist/.opencode/plugins/foundry-tools/memory-admin-tools.js +296 -0
  15. package/dist/.opencode/plugins/foundry-tools/memory-helpers.js +104 -0
  16. package/dist/.opencode/plugins/foundry-tools/memory-tools.js +286 -0
  17. package/dist/.opencode/plugins/foundry-tools/orchestrate-tool.js +159 -0
  18. package/dist/.opencode/plugins/foundry-tools/snapshot-tools.js +104 -0
  19. package/dist/.opencode/plugins/foundry-tools/stage-tools.js +186 -0
  20. package/dist/.opencode/plugins/foundry-tools/validate-tools.js +263 -0
  21. package/dist/.opencode/plugins/foundry-tools/workfile-tools.js +102 -0
  22. package/dist/.opencode/plugins/foundry.js +105 -0
  23. package/dist/CHANGELOG.md +490 -0
  24. package/dist/LICENSE +21 -0
  25. package/dist/README.md +278 -0
  26. package/dist/docs/README.md +59 -0
  27. package/dist/docs/architecture.md +434 -0
  28. package/dist/docs/concepts.md +396 -0
  29. package/dist/docs/getting-started.md +345 -0
  30. package/dist/docs/memory-maintenance.md +176 -0
  31. package/dist/docs/tools.md +1411 -0
  32. package/dist/docs/work-spec.md +283 -0
  33. package/dist/scripts/lib/artefacts.js +151 -0
  34. package/dist/scripts/lib/assay/loader.js +151 -0
  35. package/dist/scripts/lib/assay/parse-jsonl.js +102 -0
  36. package/dist/scripts/lib/assay/permissions.js +52 -0
  37. package/dist/scripts/lib/assay/run.js +219 -0
  38. package/dist/scripts/lib/assay/spawn-with-timeout.js +138 -0
  39. package/dist/scripts/lib/attestation/attest.js +111 -0
  40. package/dist/scripts/lib/attestation/canonical-json.js +109 -0
  41. package/dist/scripts/lib/attestation/hash.js +17 -0
  42. package/dist/scripts/lib/attestation/parse.js +14 -0
  43. package/dist/scripts/lib/attestation/payload.js +106 -0
  44. package/dist/scripts/lib/attestation/render.js +16 -0
  45. package/dist/scripts/lib/attestation/verify.js +15 -0
  46. package/dist/scripts/lib/branch-guard.js +72 -0
  47. package/dist/scripts/lib/config-creators/appraiser.js +9 -0
  48. package/dist/scripts/lib/config-creators/artefact-type.js +9 -0
  49. package/dist/scripts/lib/config-creators/cycle.js +11 -0
  50. package/dist/scripts/lib/config-creators/factory.js +49 -0
  51. package/dist/scripts/lib/config-creators/flow.js +11 -0
  52. package/dist/scripts/lib/config-validators/appraiser.js +49 -0
  53. package/dist/scripts/lib/config-validators/artefact-type.js +38 -0
  54. package/dist/scripts/lib/config-validators/cycle.js +131 -0
  55. package/dist/scripts/lib/config-validators/flow.js +57 -0
  56. package/dist/scripts/lib/config-validators/helpers.js +96 -0
  57. package/dist/scripts/lib/config-validators/law.js +96 -0
  58. package/dist/scripts/lib/config.js +393 -0
  59. package/dist/scripts/lib/failed-flow.js +131 -0
  60. package/dist/scripts/lib/feedback-store.js +249 -0
  61. package/dist/scripts/lib/feedback-transitions.js +105 -0
  62. package/dist/scripts/lib/finalize.js +70 -0
  63. package/dist/scripts/lib/foundational-guards.js +13 -0
  64. package/dist/scripts/lib/git-bridge.js +77 -0
  65. package/dist/scripts/lib/git-finish/work-finish.js +233 -0
  66. package/dist/scripts/lib/git-policy.js +101 -0
  67. package/dist/scripts/lib/guards.js +125 -0
  68. package/dist/scripts/lib/history.js +132 -0
  69. package/dist/scripts/lib/memory/admin/create-edge-type.js +91 -0
  70. package/dist/scripts/lib/memory/admin/create-entity-type.js +43 -0
  71. package/dist/scripts/lib/memory/admin/create-extractor.js +67 -0
  72. package/dist/scripts/lib/memory/admin/drop-edge-type.js +40 -0
  73. package/dist/scripts/lib/memory/admin/drop-entity-type.js +172 -0
  74. package/dist/scripts/lib/memory/admin/dump.js +47 -0
  75. package/dist/scripts/lib/memory/admin/helpers.js +31 -0
  76. package/dist/scripts/lib/memory/admin/init.js +170 -0
  77. package/dist/scripts/lib/memory/admin/live-store.js +76 -0
  78. package/dist/scripts/lib/memory/admin/reembed.js +285 -0
  79. package/dist/scripts/lib/memory/admin/rename-edge-type.js +54 -0
  80. package/dist/scripts/lib/memory/admin/rename-entity-type.js +151 -0
  81. package/dist/scripts/lib/memory/admin/reset.js +24 -0
  82. package/dist/scripts/lib/memory/admin/vacuum.js +9 -0
  83. package/dist/scripts/lib/memory/admin/validate.js +19 -0
  84. package/dist/scripts/lib/memory/config.js +149 -0
  85. package/dist/scripts/lib/memory/cozo.js +136 -0
  86. package/dist/scripts/lib/memory/drift.js +71 -0
  87. package/dist/scripts/lib/memory/embeddings.js +128 -0
  88. package/dist/scripts/lib/memory/frontmatter.js +75 -0
  89. package/dist/scripts/lib/memory/ndjson.js +84 -0
  90. package/dist/scripts/lib/memory/paths.js +25 -0
  91. package/dist/scripts/lib/memory/permissions.js +41 -0
  92. package/dist/scripts/lib/memory/prompt.js +109 -0
  93. package/dist/scripts/lib/memory/query.js +56 -0
  94. package/dist/scripts/lib/memory/reads.js +109 -0
  95. package/dist/scripts/lib/memory/schema.js +64 -0
  96. package/dist/scripts/lib/memory/search.js +73 -0
  97. package/dist/scripts/lib/memory/singleton.js +49 -0
  98. package/dist/scripts/lib/memory/store.js +162 -0
  99. package/dist/scripts/lib/memory/types.js +93 -0
  100. package/dist/scripts/lib/memory/validate.js +58 -0
  101. package/dist/scripts/lib/memory/writes.js +40 -0
  102. package/{scripts → dist/scripts}/lib/pending.js +7 -2
  103. package/dist/scripts/lib/secret.js +59 -0
  104. package/{scripts → dist/scripts}/lib/slug.js +3 -2
  105. package/dist/scripts/lib/snapshot/finish.js +103 -0
  106. package/dist/scripts/lib/snapshot/inspect.js +253 -0
  107. package/dist/scripts/lib/snapshot/render.js +55 -0
  108. package/dist/scripts/lib/sort-fs-check.js +121 -0
  109. package/dist/scripts/lib/sort-routing.js +101 -0
  110. package/{scripts → dist/scripts}/lib/stage-guard.js +12 -6
  111. package/{scripts → dist/scripts}/lib/state.js +4 -0
  112. package/dist/scripts/lib/token.js +57 -0
  113. package/dist/scripts/lib/tracing.js +59 -0
  114. package/dist/scripts/lib/ulid.js +100 -0
  115. package/dist/scripts/lib/validator-jsonl.js +162 -0
  116. package/{scripts → dist/scripts}/lib/workfile.js +38 -20
  117. package/dist/scripts/orchestrate-cycle.js +215 -0
  118. package/dist/scripts/orchestrate-phases.js +314 -0
  119. package/dist/scripts/orchestrate.js +163 -0
  120. package/dist/scripts/sort.js +278 -0
  121. package/{skills → dist/skills}/add-appraiser/SKILL.md +39 -9
  122. package/{skills → dist/skills}/add-artefact-type/SKILL.md +46 -24
  123. package/{skills → dist/skills}/add-cycle/SKILL.md +57 -17
  124. package/dist/skills/add-extractor/SKILL.md +133 -0
  125. package/{skills → dist/skills}/add-flow/SKILL.md +36 -10
  126. package/dist/skills/add-law/SKILL.md +191 -0
  127. package/dist/skills/add-memory-edge-type/SKILL.md +52 -0
  128. package/dist/skills/add-memory-entity-type/SKILL.md +74 -0
  129. package/{skills → dist/skills}/appraise/SKILL.md +62 -13
  130. package/dist/skills/assay/SKILL.md +72 -0
  131. package/dist/skills/change-embedding-model/SKILL.md +58 -0
  132. package/dist/skills/drop-memory-edge-type/SKILL.md +54 -0
  133. package/dist/skills/drop-memory-entity-type/SKILL.md +57 -0
  134. package/dist/skills/dry-run/SKILL.md +116 -0
  135. package/{skills → dist/skills}/flow/SKILL.md +15 -2
  136. package/dist/skills/forge/SKILL.md +121 -0
  137. package/dist/skills/human-appraise/SKILL.md +153 -0
  138. package/{skills → dist/skills}/init-foundry/SKILL.md +23 -4
  139. package/dist/skills/init-memory/SKILL.md +92 -0
  140. package/{skills → dist/skills}/orchestrate/SKILL.md +30 -4
  141. package/dist/skills/quench/SKILL.md +99 -0
  142. package/{skills → dist/skills}/refresh-agents/SKILL.md +1 -1
  143. package/dist/skills/rename-memory-edge-type/SKILL.md +50 -0
  144. package/dist/skills/rename-memory-entity-type/SKILL.md +51 -0
  145. package/dist/skills/reset-memory/SKILL.md +54 -0
  146. package/dist/skills/upgrade-foundry/SKILL.md +192 -0
  147. package/package.json +34 -17
  148. package/.opencode/plugins/foundry.js +0 -761
  149. package/CHANGELOG.md +0 -100
  150. package/docs/concepts.md +0 -122
  151. package/docs/getting-started.md +0 -187
  152. package/docs/work-spec.md +0 -207
  153. package/scripts/lib/artefacts.js +0 -124
  154. package/scripts/lib/config.js +0 -175
  155. package/scripts/lib/feedback-transitions.js +0 -25
  156. package/scripts/lib/feedback.js +0 -440
  157. package/scripts/lib/finalize.js +0 -41
  158. package/scripts/lib/history.js +0 -59
  159. package/scripts/lib/secret.js +0 -23
  160. package/scripts/lib/tags.js +0 -108
  161. package/scripts/lib/token.js +0 -26
  162. package/scripts/orchestrate.js +0 -418
  163. package/scripts/sort.js +0 -370
  164. package/scripts/validate-tags.js +0 -54
  165. package/skills/add-law/SKILL.md +0 -111
  166. package/skills/forge/SKILL.md +0 -88
  167. package/skills/human-appraise/SKILL.md +0 -82
  168. package/skills/quench/SKILL.md +0 -62
  169. package/skills/upgrade-foundry/SKILL.md +0 -216
  170. /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,15 +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 both of the following:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- 1. The `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
15
+ 1. The `foundry/` directory exists in the project root. If it does not
16
+ exist, stop and tell the user:
16
17
 
17
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
18
+ > Foundry is not initialized in this project. Run the
19
+ > `init-foundry` skill first to create the foundry/ directory
20
+ > structure.
18
21
 
19
- 2. The current git branch is not a work branch. Run `git rev-parse --abbrev-ref HEAD` — if it starts with `work/`, stop and tell the user:
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>`.
20
25
 
21
- > You're on a work branch (`<branch>`). Foundry configuration changes must be made on the base branch (usually `main`). Complete or discard the in-flight flow (`foundry_git_finish`, or switch branches and delete it), then re-run this skill from the base branch.
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/*`.
22
39
 
23
40
  ## Protocol
24
41
 
@@ -87,11 +104,25 @@ Iterate until the user is happy with the personality description. Key things to
87
104
  - Does the description give the LLM enough direction to adopt a consistent voice?
88
105
  - Is it clear what this appraiser would flag vs let pass?
89
106
 
90
- ### 6. Write the file
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.
91
122
 
92
- Create `foundry/appraisers/<id>.md` with the agreed definition.
123
+ Show the user the resulting commit hash from the response.
93
124
 
94
- ### 7. Mention artefact type configuration
125
+ ### 8. Mention artefact type configuration
95
126
 
96
127
  After creating the appraiser, remind the user:
97
128
 
@@ -107,7 +138,6 @@ After creating the appraiser, remind the user:
107
138
 
108
139
  ## What you do NOT do
109
140
 
110
- - You do not write files without showing the user first
111
141
  - You do not skip the semantic overlap check
112
142
  - You do not modify artefact type definitions — that is the user's choice
113
143
  - You do not create appraisers with duplicate ids
@@ -10,15 +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 both of the following:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- 1. The `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
15
+ 1. The `foundry/` directory exists in the project root. If it does not
16
+ exist, stop and tell the user:
16
17
 
17
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
18
+ > Foundry is not initialized in this project. Run the
19
+ > `init-foundry` skill first to create the foundry/ directory
20
+ > structure.
18
21
 
19
- 2. The current git branch is not a work branch. Run `git rev-parse --abbrev-ref HEAD` — if it starts with `work/`, stop and tell the user:
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>`.
20
25
 
21
- > You're on a work branch (`<branch>`). Foundry configuration changes must be made on the base branch (usually `main`). Complete or discard the in-flight flow (`foundry_git_finish`, or switch branches and delete it), then re-run this skill from the base branch.
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/*`.
22
39
 
23
40
  ## Protocol
24
41
 
@@ -27,8 +44,7 @@ Before running this skill, verify both of the following:
27
44
  From the user's prompt, establish:
28
45
  - `id` — lowercase, hyphenated identifier
29
46
  - `name` — human-readable name
30
- - `file-patterns` — glob patterns for files this type produces
31
- - `output` — output directory
47
+ - `file-patterns` — glob patterns for files this type produces (forge's write scope is exactly these patterns)
32
48
  - A prose description of what this artefact type is
33
49
 
34
50
  If any of these are missing, ask.
@@ -71,16 +87,12 @@ Present the definition to the user:
71
87
 
72
88
  ```markdown
73
89
  ---
74
- id: <id>
75
90
  name: <name>
76
91
  file-patterns:
77
92
  - "<pattern>"
78
- output: <output-dir>
79
- appraisers:
80
- count: 3
81
93
  ---
82
94
 
83
- # <Name>
95
+ ## Definition
84
96
 
85
97
  <description>
86
98
  ```
@@ -143,26 +155,36 @@ Check the project's `package.json` for `"type": "module"`:
143
155
  - If CommonJS (no `"type"` field or `"type": "commonjs"`): `require()` is fine, or use `.cjs` extension
144
156
  - When in doubt, use `.mjs` or `.cjs` extensions to be explicit regardless of project settings
145
157
 
146
- ### 8. Scaffold
158
+ ### 8. Validate the draft
147
159
 
148
- Create the directory and files:
160
+ Call `foundry_config_validate_artefact_type({ name: "<id>", body: "<full markdown>" })`.
149
161
 
150
- ```
151
- foundry/artefacts/<id>/
152
- definition.md # always created
153
- laws.md # created if laws were defined
154
- validation.md # created if validation commands were defined
155
- ```
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.
156
179
 
157
- If laws or validation were skipped, do not create empty files.
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.
158
181
 
159
- ### 9. Confirm
182
+ ### 11. Confirm
160
183
 
161
- Show the user the complete file listing and contents. Confirm before writing.
184
+ Show the user the complete file listing and the commit hashes.
162
185
 
163
186
  ## What you do NOT do
164
187
 
165
188
  - You do not create artefact types with overlapping file patterns — this is a hard block
166
- - You do not write files without showing the user first
167
189
  - You do not skip the naming or glob checks
168
190
  - You do not create laws without checking for conflicts (delegate to add-law pattern)
@@ -10,15 +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 both of the following:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- 1. The `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
15
+ 1. The `foundry/` directory exists in the project root. If it does not
16
+ exist, stop and tell the user:
16
17
 
17
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
18
+ > Foundry is not initialized in this project. Run the
19
+ > `init-foundry` skill first to create the foundry/ directory
20
+ > structure.
18
21
 
19
- 2. The current git branch is not a work branch. Run `git rev-parse --abbrev-ref HEAD` — if it starts with `work/`, stop and tell the user:
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>`.
20
25
 
21
- > You're on a work branch (`<branch>`). Foundry configuration changes must be made on the base branch (usually `main`). Complete or discard the in-flight flow (`foundry_git_finish`, or switch branches and delete it), then re-run this skill from the base branch.
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/*`.
22
39
 
23
40
  ## Protocol
24
41
 
@@ -33,7 +50,7 @@ Verify the flow exists. If it doesn't, tell the user and ask if they want to cre
33
50
  From the user's prompt, establish:
34
51
  - `id` — lowercase, hyphenated identifier for the foundry cycle
35
52
  - `name` — human-readable name
36
- - `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/`)
37
54
  - `inputs` — artefact types this cycle reads, with a contract type:
38
55
  - `type`: `any-of` (at least one must exist) or `all-of` (all must exist)
39
56
  - `artefacts`: list of artefact type IDs
@@ -72,7 +89,7 @@ Ask the user:
72
89
 
73
90
  ### 5. Validate artefact types
74
91
 
75
- For `output` and each entry in `inputs`:
92
+ For `output-type` and each entry in `inputs`:
76
93
  - Verify the artefact type exists in `foundry/artefacts/<type>/definition.md`
77
94
  - If it doesn't, tell the user and ask if they want to create it first (separate skill)
78
95
 
@@ -81,12 +98,12 @@ For `output` and each entry in `inputs`:
81
98
  Read the flow definition from `foundry/flows/<flow-id>.md`. Check:
82
99
 
83
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.
84
- - Each `input` artefact type is produced by an earlier foundry cycle in the foundry flow. If an input references an artefact type that no prior foundry cycle outputs, warn:
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:
85
102
 
86
- > Input `<type>` is not produced by any earlier foundry cycle in this foundry flow. The artefact won't exist when this foundry cycle runs.
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.
87
104
  >
88
105
  > Options:
89
- > 1. Add a foundry cycle that produces `<type>` before this one
106
+ > 1. Add a foundry cycle that produces `<type>` and route to this cycle via `targets`
90
107
  > 2. Remove `<type>` from inputs (this foundry cycle won't have that context)
91
108
  > 3. Proceed anyway (the artefact may exist from a previous foundry flow run)
92
109
 
@@ -112,7 +129,7 @@ Present the foundry cycle definition to the user:
112
129
  ---
113
130
  id: <id>
114
131
  name: <name>
115
- output: <artefact-type-id>
132
+ output-type: <artefact-type-id>
116
133
  inputs:
117
134
  type: <any-of|all-of>
118
135
  artefacts:
@@ -144,19 +161,42 @@ For input validation:
144
161
  - Verify that at least one cycle in the flow has the input artefact type(s) as its output
145
162
  - If using `all-of`, verify all input types are producible
146
163
 
147
- ### 11. Write files
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:
173
+
174
+ - re-validates the body (TOCTOU);
175
+ - writes `foundry/cycles/<id>.md`;
176
+ - produces one git commit on the current `config/*` branch.
148
177
 
149
- - Create `foundry/cycles/<id>.md` with the cycle definition
150
- - Update `foundry/flows/<flow-id>.md` to add the cycle to the `## Cycles` list (if not already present)
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
+ ```
151
192
 
152
- ### 12. Confirm
193
+ ### 14. Confirm
153
194
 
154
- Show the user the created/modified files and their contents.
195
+ Show the user the cycle file, the updated flow file, and both commit hashes.
155
196
 
156
197
  ## What you do NOT do
157
198
 
158
199
  - You do not create foundry cycles that output an artefact type already produced by another foundry cycle in the same foundry flow
159
- - You do not write files without showing the user first
160
200
  - You do not skip artefact type validation
161
201
  - You do not create artefact types — that is a separate skill
162
202
  - You do not create foundry flows — that is a separate skill