@really-knows-ai/foundry 2.3.2 → 3.0.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.
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 +533 -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 +433 -0
  28. package/dist/docs/concepts.md +395 -0
  29. package/dist/docs/getting-started.md +344 -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 +328 -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 +62 -40
  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 +191 -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
@@ -6,19 +6,36 @@ description: Creates a new artefact type, checking for conflicts with existing t
6
6
 
7
7
  # Add Artefact Type
8
8
 
9
- You help the user create a new artefact type. You ensure it doesn't conflict with existing types, scaffold the directory structure, and walk the user through defining laws and validation.
9
+ You help the user create a new artefact type. You ensure it avoids conflicts with existing types, scaffold the directory structure, and walk the user through defining laws and their optional validators.
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
  ```
@@ -94,10 +106,32 @@ Ask:
94
106
  > Do you want to define any type-specific laws for this artefact type? (Global laws in `foundry/laws/` will apply automatically.)
95
107
 
96
108
  If yes, walk through each law using the same format as `add-law`:
97
- - Draft each law
109
+ - Draft each law, adding validators where a deterministic check applies
98
110
  - Check for conflicts with global laws and any existing type-specific laws
99
111
  - Confirm with the user
100
112
 
113
+ Each law may declare an optional `validators:` block. Include validators only when a deterministic check is needed. The format matches `add-law`:
114
+
115
+ ```markdown
116
+ ## <law-id>
117
+
118
+ <What this law checks — one or two sentences.>
119
+
120
+ validators:
121
+ - id: validator-id
122
+ command: ./script.sh
123
+ failure-means: (optional description)
124
+ ```
125
+
126
+ The `validators` block is optional. When present, `quench` runs each validator for this law. Validator scripts live within the artefact type directory (e.g., `foundry/artefacts/<type>/check-foo.mjs`).
127
+
128
+ **Use existing libraries:** Before writing custom validation logic, search npm for well-tested libraries that solve the problem (e.g., `syllable` for syllable counting, `natural` for NLP tasks). Hand-rolled heuristics are fragile — prefer battle-tested packages. Install them as project dependencies.
129
+
130
+ Check the project's `package.json` for `"type": "module"`:
131
+ - If ESM (`"type": "module"`): use `import` syntax, or name scripts with `.mjs` extension
132
+ - If CommonJS (no `"type"` field or `"type": "commonjs"`): `require()` is fine, or use `.cjs` extension
133
+ - When in doubt, use `.mjs` or `.cjs` extensions to be explicit regardless of project settings
134
+
101
135
  ### 6. Appraisers (optional)
102
136
 
103
137
  Ask:
@@ -123,46 +157,34 @@ appraisers:
123
157
 
124
158
  List the available appraisers from `foundry/appraisers/*.md` so the user can see their options.
125
159
 
126
- ### 7. Validation (optional)
160
+ ### 7. Validate the draft
127
161
 
128
- Ask:
162
+ Call `foundry_config_validate_artefact_type({ name: "<id>", body: "<full markdown>" })`.
129
163
 
130
- > Do you want to define any deterministic validation commands for this artefact type?
164
+ 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.
131
165
 
132
- If yes, walk through each validation entry:
133
- - A `## heading` (identifier)
134
- - A `Command:` line with `{file}` placeholder
135
- - A `Failure means:` line explaining what a non-zero exit indicates
166
+ ### 8. Create the file
136
167
 
137
- If the user wants validation scripts (not just inline commands), create them as separate files in the artefact type directory.
168
+ Call `foundry_config_create_artefact_type({ name: "<id>", body: "<full markdown>" })`. The tool:
138
169
 
139
- **Use existing libraries:** Before writing custom validation logic, search npm for well-tested libraries that solve the problem (e.g., `syllable` for syllable counting, `natural` for NLP tasks). Hand-rolled heuristics are fragile — prefer battle-tested packages. Install them as project dependencies.
170
+ - re-validates the body (TOCTOU);
171
+ - writes `foundry/artefacts/<id>/definition.md`;
172
+ - produces one git commit on the current `config/*` branch.
140
173
 
141
- Check the project's `package.json` for `"type": "module"`:
142
- - If ESM (`"type": "module"`): use `import` syntax, or name scripts with `.mjs` extension
143
- - If CommonJS (no `"type"` field or `"type": "commonjs"`): `require()` is fine, or use `.cjs` extension
144
- - When in doubt, use `.mjs` or `.cjs` extensions to be explicit regardless of project settings
174
+ 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.
145
175
 
146
- ### 8. Scaffold
176
+ Show the user the resulting commit hash from the response.
147
177
 
148
- Create the directory and files:
149
-
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
- ```
178
+ ### 9. Add laws file (if defined)
156
179
 
157
- If laws or validation were skipped, do not create empty files.
180
+ 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.
158
181
 
159
- ### 9. Confirm
182
+ ### 10. 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