@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,92 @@
1
+ import { requireActiveStage } from '../../../scripts/lib/stage-guard.js';
2
+ import { markWorkfileFailed } from '../../../scripts/lib/failed-flow.js';
3
+ import { guarded, notFailedGuard } from '../../../scripts/lib/guards.js';
4
+ import { runAssay } from '../../../scripts/lib/assay/run.js';
5
+ import { syncStore } from '../../../scripts/lib/memory/store.js';
6
+ import { putEntity, relate as memRelate } from '../../../scripts/lib/memory/writes.js';
7
+ import { withStore } from './memory-helpers.js';
8
+ import { makeIO, branchIoFactory, asyncIoFactory, flowBranchGuard, errorJson } from './helpers.js';
9
+
10
+ const gateNotFailed = notFailedGuard(makeIO);
11
+
12
+ function truncateStderr(stderr) {
13
+ if (!stderr) return '';
14
+ const trimmed = stderr.trim();
15
+ if (trimmed.length <= 500) return trimmed;
16
+ const bytesRemaining = trimmed.length - 500;
17
+ const plural = bytesRemaining === 1 ? 'byte' : 'bytes';
18
+ return `${trimmed.slice(0, 500)}... (${bytesRemaining} more ${plural})`;
19
+ }
20
+
21
+ function handleExtractorFailure(io, res) {
22
+ const stderrSnippet = truncateStderr(res.stderr);
23
+ const msg = `assay aborted on extractor \`${res.failedExtractor}\`: ${res.reason}` +
24
+ (stderrSnippet ? ` (stderr: ${stderrSnippet})` : '');
25
+ try { markWorkfileFailed(io, msg); } catch { /* WORK.md gone? nothing we can do */ }
26
+ return JSON.stringify({
27
+ error: msg,
28
+ flow_failed: true,
29
+ aborted: true,
30
+ failedExtractor: res.failedExtractor,
31
+ reason: res.reason,
32
+ stderr: res.stderr,
33
+ perExtractor: res.perExtractor,
34
+ });
35
+ }
36
+
37
+ async function syncAfterAssay(store, memIo) {
38
+ try {
39
+ await syncStore({ store, io: memIo });
40
+ return { ok: true };
41
+ } catch (err) {
42
+ return { ok: false, err };
43
+ }
44
+ }
45
+
46
+ function handleSyncFailure(io, err) {
47
+ const msg = `assay post-run memory sync failed: ${err?.message ?? err}`;
48
+ try { markWorkfileFailed(io, msg); } catch { /* WORK.md gone? nothing we can do */ }
49
+ return JSON.stringify({ error: msg, flow_failed: true });
50
+ }
51
+
52
+ async function handleAssayRun(args, context) {
53
+ const io = makeIO(context.worktree);
54
+ const guard = requireActiveStage(io, { stageBase: 'assay', cycle: args.cycle });
55
+ if (!guard.ok) return JSON.stringify({ error: `foundry_assay_run requires active assay stage for cycle '${args.cycle}'; ${guard.error}` });
56
+
57
+ try {
58
+ const { store, vocabulary, writeEmbedder, io: memIo } = await withStore(context);
59
+ const res = await runAssay({
60
+ foundryDir: 'foundry',
61
+ cwd: context.worktree,
62
+ io: memIo,
63
+ extractors: args.extractors,
64
+ store,
65
+ vocabulary,
66
+ putEntity,
67
+ relate: memRelate,
68
+ writeEmbedder,
69
+ syncStore, // G29: Pass syncStore so it's called after each extractor
70
+ });
71
+
72
+ if (!res.ok) return handleExtractorFailure(io, res);
73
+
74
+ const syncResult = await syncAfterAssay(store, memIo);
75
+ if (!syncResult.ok) return handleSyncFailure(io, syncResult.err);
76
+
77
+ return JSON.stringify(res);
78
+ } catch (err) { return errorJson(err); }
79
+ }
80
+
81
+ export function createAssayTools({ tool }) {
82
+ return {
83
+ foundry_assay_run: tool({
84
+ description: 'Run extractors to populate flow memory. Only callable during an active assay stage. Aborts on first failure; marks the workfile failed. Extractors must output one JSON object per line (JSONL/NDJSON format), not pretty-printed multi-line JSON.',
85
+ args: {
86
+ cycle: tool.schema.string().describe('Cycle name'),
87
+ extractors: tool.schema.array(tool.schema.string()).describe('Extractor names, executed in order'),
88
+ },
89
+ execute: guarded('foundry_assay_run', [flowBranchGuard, gateNotFailed], handleAssayRun, { branchIo: branchIoFactory, io: asyncIoFactory }),
90
+ }),
91
+ };
92
+ }
@@ -0,0 +1,191 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { writeFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { extractAttestationBlock } from '../../../scripts/lib/attestation/parse.js';
5
+ import { verifyAttestationRef } from '../../../scripts/lib/attestation/verify.js';
6
+ import { buildAttestation } from '../../../scripts/lib/attestation/attest.js';
7
+ import { parseFrontmatter } from '../../../scripts/lib/workfile.js';
8
+ import { requireNoActiveStage } from '../../../scripts/lib/stage-guard.js';
9
+ import { currentBranch } from '../../../scripts/lib/branch-guard.js';
10
+ import { errorJson, makeIO, makeExec } from './helpers.js';
11
+
12
+ function refuse(error) { return JSON.stringify({ error }); }
13
+
14
+ function createShowTool(tool) {
15
+ return tool({
16
+ description: 'Show parsed attestation payload and human summary for a commit ref (default HEAD).',
17
+ args: {
18
+ ref: tool.schema.string().optional().describe('Git ref (default: HEAD)'),
19
+ },
20
+ async execute(args, context) {
21
+ try {
22
+ const ref = args.ref || 'HEAD';
23
+ const cwd = context.worktree;
24
+ const message = execFileSync('git', ['log', '-1', '--pretty=%B', ref],
25
+ { cwd, encoding: 'utf8', stdio: 'pipe' }
26
+ );
27
+ const json = extractAttestationBlock(message);
28
+ let payload;
29
+ try {
30
+ payload = JSON.parse(json);
31
+ } catch {
32
+ return errorJson(new Error(`malformed attestation JSON: ${json}`));
33
+ }
34
+ const subjectLine = message.split('\n')[0];
35
+ return JSON.stringify({ ok: true, human_summary: subjectLine, payload });
36
+ } catch (err) {
37
+ return errorJson(err);
38
+ }
39
+ },
40
+ });
41
+ }
42
+
43
+ function createVerifyTool(tool) {
44
+ return tool({
45
+ description: 'Verify GPG signature and attestation payload for a commit ref (default HEAD).',
46
+ args: {
47
+ ref: tool.schema.string().optional().describe('Git ref (default: HEAD)'),
48
+ },
49
+ async execute(args, context) {
50
+ try {
51
+ const ref = args.ref || 'HEAD';
52
+ const cwd = context.worktree;
53
+ const result = verifyAttestationRef({ cwd, ref });
54
+ return JSON.stringify({ ok: true, ...result });
55
+ } catch (err) {
56
+ return errorJson(err);
57
+ }
58
+ },
59
+ });
60
+ }
61
+
62
+ function guardStageAndBranch(cwd) {
63
+ const io = makeIO(cwd);
64
+ const stageGuard = requireNoActiveStage(io);
65
+ if (!stageGuard.ok) {
66
+ return { ok: false, error: `foundry_attest: ${stageGuard.error}` };
67
+ }
68
+ const branch = currentBranch({ exec: makeExec(cwd) });
69
+ if (!branch || !branch.startsWith('work/')) {
70
+ return { ok: false, error: `foundry_attest: must be run on a work/* branch, current branch is '${branch}'` };
71
+ }
72
+ return { ok: true, branch };
73
+ }
74
+
75
+ function refuseConfirm(branch, baseBranch) {
76
+ return JSON.stringify({
77
+ ok: false,
78
+ error: 'foundry_attest requires {confirm: true}. Re-invoke with confirm:true to write ATTEST.md.',
79
+ planned: {
80
+ action: 'verify-cycle, compute-diff-sha, write-ATTEST.md, commit',
81
+ branch,
82
+ baseBranch: baseBranch || 'main',
83
+ },
84
+ });
85
+ }
86
+
87
+ function makeExecGit(cwd, opts) {
88
+ return (argv) => {
89
+ if (argv[0] === 'diff') {
90
+ return execFileSync('git', argv, { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
91
+ }
92
+ return execFileSync('git', argv, opts).toString();
93
+ };
94
+ }
95
+
96
+ function readCycleFromWorkfile(cwd) {
97
+ const workText = readFileSync(path.join(cwd, 'WORK.md'), 'utf8');
98
+ const frontmatter = parseFrontmatter(workText);
99
+ return frontmatter.cycle ?? 'unknown';
100
+ }
101
+
102
+ function gitRevParseHead(opts) {
103
+ return execFileSync('git', ['rev-parse', 'HEAD'], opts).trim();
104
+ }
105
+
106
+ function commitErrorString(err) {
107
+ const stderr = err.stderr ?? err.stdout ?? '';
108
+ return `foundry_attest: commit failed. ${String(stderr).trim()}`;
109
+ }
110
+
111
+ function cleanupFailedCommit(attestPath, opts) {
112
+ try { execFileSync('git', ['reset', 'HEAD', 'ATTEST.md'], opts); } catch { /* best effort */ }
113
+ try { unlinkSync(attestPath); } catch { /* best effort */ }
114
+ }
115
+
116
+ function commitAttestation(cwd, cycle, content, opts) {
117
+ const attestPath = path.join(cwd, 'ATTEST.md');
118
+ writeFileSync(attestPath, content, 'utf8');
119
+ try {
120
+ execFileSync('git', ['add', 'ATTEST.md'], opts);
121
+ const commitMsg = `[${cycle}] attest: cycle complete`;
122
+ execFileSync('git', ['commit', '--no-gpg-sign', '-m', commitMsg], opts);
123
+ } catch (err) {
124
+ cleanupFailedCommit(attestPath, opts);
125
+ return { ok: false, error: commitErrorString(err) };
126
+ }
127
+ const commitSha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], opts).trim();
128
+ return { ok: true, commitSha };
129
+ }
130
+
131
+ function buildAttestationInputs(opts) {
132
+ return {
133
+ cwd: opts.cwd,
134
+ baseBranch: opts.baseBranch,
135
+ goalText: opts.goalText,
136
+ archiveBranch: opts.archiveBranch,
137
+ archiveTipSha: opts.archiveTipSha,
138
+ io: { readFile: (p) => readFileSync(p, 'utf8'), fileExists: (p) => existsSync(p) },
139
+ execGit: opts.execGit,
140
+ };
141
+ }
142
+
143
+ function handleBuildResult(result, cwd, cycle, opts) {
144
+ if (!result.ok) return JSON.stringify(result);
145
+ const commitResult = commitAttestation(cwd, cycle, result.content, opts);
146
+ if (!commitResult.ok) return JSON.stringify(commitResult);
147
+ return JSON.stringify({ ok: true, diffSha: result.diffSha, commitSha: commitResult.commitSha });
148
+ }
149
+
150
+ function createAttestTool(tool) {
151
+ return tool({
152
+ description:
153
+ 'Verify the current work cycle is complete (all required stages ran, no unresolved ' +
154
+ 'feedback, no blocked artefacts) and commit an ATTEST.md attestation file to the work branch. ' +
155
+ 'foundry_git_finish will not merge without this commit at HEAD.',
156
+ args: {
157
+ baseBranch: tool.schema.string().optional()
158
+ .describe('Branch to compute diff from (default: main)'),
159
+ message: tool.schema.string()
160
+ .describe('Goal text / human summary for the attestation'),
161
+ confirm: tool.schema.boolean().optional()
162
+ .describe('Must be true to write and commit ATTEST.md'),
163
+ },
164
+ async execute(args, context) {
165
+ const cwd = context.worktree;
166
+ const opts = { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] };
167
+ const guard = guardStageAndBranch(cwd);
168
+ if (!guard.ok) return refuse(guard.error);
169
+ if (args.confirm !== true) return refuseConfirm(guard.branch, args.baseBranch);
170
+ const baseBranch = args.baseBranch || 'main';
171
+ const execGit = makeExecGit(cwd, opts);
172
+ const cycle = readCycleFromWorkfile(cwd);
173
+ const tipSha = gitRevParseHead(opts);
174
+ const archiveBranch = `archive/${guard.branch}-${tipSha.slice(0, 7)}`;
175
+ const inputs = buildAttestationInputs({
176
+ cwd, baseBranch, goalText: args.message,
177
+ archiveBranch, archiveTipSha: tipSha, execGit,
178
+ });
179
+ const result = await buildAttestation(inputs);
180
+ return handleBuildResult(result, cwd, cycle, opts);
181
+ },
182
+ });
183
+ }
184
+
185
+ export function createAttestationTools({ tool }) {
186
+ return {
187
+ foundry_attestation_show: createShowTool(tool),
188
+ foundry_attestation_verify: createVerifyTool(tool),
189
+ foundry_attest: createAttestTool(tool),
190
+ };
191
+ }
@@ -0,0 +1,128 @@
1
+ // Tools for creating and validating the five foundry config kinds:
2
+ // artefact-type, law, appraiser, flow, cycle.
3
+ //
4
+ // `_validate_<kind>` tools are read-only — they parse the supplied body
5
+ // and report errors. They run anywhere (no branch guard, no failed-flow
6
+ // guard) so authors can iterate on a draft from any branch.
7
+ //
8
+ // `_create_<kind>` tools mutate the worktree (write the file + commit
9
+ // it) and so carry the full guard stack:
10
+ // gitRepo → foundryRoot → configBranch → notFailed
11
+ // matching the config-tier policy.
12
+
13
+ import { execFileSync } from 'child_process';
14
+ import { create as createArtefactType } from '../../../scripts/lib/config-creators/artefact-type.js';
15
+ import { create as createAppraiser } from '../../../scripts/lib/config-creators/appraiser.js';
16
+ import { create as createFlow } from '../../../scripts/lib/config-creators/flow.js';
17
+ import { create as createCycle } from '../../../scripts/lib/config-creators/cycle.js';
18
+ import { validate as validateArtefactType } from '../../../scripts/lib/config-validators/artefact-type.js';
19
+ import { validate as validateLaw } from '../../../scripts/lib/config-validators/law.js';
20
+ import { validate as validateAppraiser } from '../../../scripts/lib/config-validators/appraiser.js';
21
+ import { validate as validateFlow } from '../../../scripts/lib/config-validators/flow.js';
22
+ import { validate as validateCycle } from '../../../scripts/lib/config-validators/cycle.js';
23
+ import { requireGitRepo, requireFoundryRoot } from '../../../scripts/lib/foundational-guards.js';
24
+ import { requireOnConfigBranch } from '../../../scripts/lib/branch-guard.js';
25
+ import { guarded, notFailedGuard } from '../../../scripts/lib/guards.js';
26
+ import { UnexpectedFilesError } from '../../../scripts/lib/git-bridge.js';
27
+ import { makeIO, makeExec, makeAsyncIO, errorJson, branchIoFactory, asyncIoFactory } from './helpers.js';
28
+
29
+ // --- guard helpers ---------------------------------------------------------
30
+
31
+ function gitRepoGuard(_args, context) {
32
+ return requireGitRepo(makeIO(context.worktree));
33
+ }
34
+
35
+ function foundryRootGuard(_args, context) {
36
+ return requireFoundryRoot(makeIO(context.worktree));
37
+ }
38
+
39
+ function configBranchGuard(_args, context) {
40
+ return requireOnConfigBranch({ exec: makeExec(context.worktree) });
41
+ }
42
+
43
+ const gateNotFailed = notFailedGuard(makeIO);
44
+
45
+ // `git`-prefixed argv runner that satisfies commitWithPolicy's contract:
46
+ // it accepts argv WITHOUT the `git` prefix and prepends it itself.
47
+ function makeExecFile(cwd) {
48
+ return (argv) => execFileSync('git', argv, { cwd, encoding: 'utf8', stdio: 'pipe' });
49
+ }
50
+
51
+ const CREATE_GUARDS = [gitRepoGuard, foundryRootGuard, configBranchGuard, gateNotFailed];
52
+ const VALIDATE_GUARDS = [gitRepoGuard, foundryRootGuard];
53
+
54
+ // --- tool factories --------------------------------------------------------
55
+
56
+ // Module-level helper: returns a `makeCreate` function bound to `tool` and `baseArgs`.
57
+ function createMakeCreate(tool, baseArgs) {
58
+ return function makeCreate(toolName, creator, extraArgs = {}) {
59
+ const kind = toolName.replace('foundry_config_create_', '');
60
+ let desc = `Create a new ${kind} definition (config-tier; requires a config/* branch).`;
61
+
62
+ if (kind === 'law') {
63
+ desc += ' target must be {kind:"global", file:"<name>.md"} or {kind:"type-specific", typeId:"<id>"}.';
64
+ }
65
+
66
+ return tool({
67
+ description: desc,
68
+ args: { ...baseArgs, ...extraArgs },
69
+ execute: guarded(toolName, CREATE_GUARDS, async (args, context) => {
70
+ try {
71
+ const io = makeAsyncIO(context.worktree);
72
+ const execFile = makeExecFile(context.worktree);
73
+ const out = await creator({ ...args, io, execFile });
74
+ return JSON.stringify(out);
75
+ } catch (err) {
76
+ if (err instanceof UnexpectedFilesError) {
77
+ return JSON.stringify({ error: err.message, affected_files: err.files });
78
+ }
79
+ return errorJson(err);
80
+ }
81
+ }, { branchIo: branchIoFactory, io: asyncIoFactory }),
82
+ });
83
+ };
84
+ }
85
+
86
+ // Module-level helper: returns a `makeValidate` function bound to `tool` and `baseArgs`.
87
+ function createMakeValidate(tool, baseArgs) {
88
+ return function makeValidate(toolName, validator) {
89
+ return tool({
90
+ description: `Validate a ${toolName.replace('foundry_config_validate_', '')} body without writing it.`,
91
+ args: baseArgs,
92
+ execute: guarded(toolName, VALIDATE_GUARDS, async (args, context) => {
93
+ try {
94
+ const io = makeAsyncIO(context.worktree);
95
+ const out = await validator({ ...args, io });
96
+ return JSON.stringify(out);
97
+ } catch (err) {
98
+ return errorJson(err);
99
+ }
100
+ }, { branchIo: branchIoFactory, io: asyncIoFactory }),
101
+ });
102
+ };
103
+ }
104
+
105
+ // --- tool factory ---------------------------------------------------------
106
+
107
+ export function createConfigCreateTools({ tool }) {
108
+ const baseArgs = {
109
+ name: tool.schema.string(),
110
+ body: tool.schema.string(),
111
+ };
112
+
113
+ const makeCreate = createMakeCreate(tool, baseArgs);
114
+ const makeValidate = createMakeValidate(tool, baseArgs);
115
+
116
+ return {
117
+ foundry_config_create_artefact_type: makeCreate('foundry_config_create_artefact_type', createArtefactType),
118
+ foundry_config_create_appraiser: makeCreate('foundry_config_create_appraiser', createAppraiser),
119
+ foundry_config_create_flow: makeCreate('foundry_config_create_flow', createFlow),
120
+ foundry_config_create_cycle: makeCreate('foundry_config_create_cycle', createCycle),
121
+
122
+ foundry_config_validate_artefact_type: makeValidate('foundry_config_validate_artefact_type', validateArtefactType),
123
+ foundry_config_validate_law: makeValidate('foundry_config_validate_law', validateLaw),
124
+ foundry_config_validate_appraiser: makeValidate('foundry_config_validate_appraiser', validateAppraiser),
125
+ foundry_config_validate_flow: makeValidate('foundry_config_validate_flow', validateFlow),
126
+ foundry_config_validate_cycle: makeValidate('foundry_config_validate_cycle', validateCycle),
127
+ };
128
+ }