@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.
Files changed (170) hide show
  1. package/README.md +200 -198
  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 +42 -6
  122. package/{skills → dist/skills}/add-artefact-type/SKILL.md +49 -21
  123. package/{skills → dist/skills}/add-cycle/SKILL.md +60 -14
  124. package/dist/skills/add-extractor/SKILL.md +133 -0
  125. package/{skills → dist/skills}/add-flow/SKILL.md +39 -7
  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 -90
  150. package/docs/concepts.md +0 -59
  151. package/docs/getting-started.md +0 -78
  152. package/docs/work-spec.md +0 -193
  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 -105
  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,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 that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
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. 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.
85
122
 
86
- Create `foundry/appraisers/<id>.md` with the agreed definition.
123
+ Show the user the resulting commit hash from the response.
87
124
 
88
- ### 7. Mention artefact type configuration
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 that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
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
- # <Name>
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. Scaffold
158
+ ### 8. Validate the draft
141
159
 
142
- Create the directory and files:
160
+ Call `foundry_config_validate_artefact_type({ name: "<id>", body: "<full markdown>" })`.
143
161
 
144
- ```
145
- foundry/artefacts/<id>/
146
- definition.md # always created
147
- laws.md # created if laws were defined
148
- validation.md # created if validation commands were defined
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 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.
152
181
 
153
- ### 9. Confirm
182
+ ### 11. Confirm
154
183
 
155
- Show the user the complete file listing and contents. Confirm before writing.
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 that the `foundry/` directory exists in the project root. If it does not exist, stop and tell the user:
13
+ Before running this skill, verify all three of the following:
14
14
 
15
- > Foundry is not initialized in this project. Run the `init-foundry` skill first to create the foundry/ directory structure.
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 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:
79
102
 
80
- > 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.
81
104
  >
82
105
  > Options:
83
- > 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`
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. 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:
142
173
 
143
- - Create `foundry/cycles/<id>.md` with the cycle definition
144
- - Update `foundry/flows/<flow-id>.md` to add the cycle to the `## Cycles` list (if not already present)
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
- ### 12. Confirm
193
+ ### 14. Confirm
147
194
 
148
- 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.
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