@juzser/blacksmith 0.1.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 (172) hide show
  1. package/.claude/agents/auditor.md +146 -0
  2. package/.claude/agents/coder.md +164 -0
  3. package/.claude/agents/grader.md +110 -0
  4. package/.claude/agents/merger.md +107 -0
  5. package/.claude/agents/planner.md +92 -0
  6. package/.claude/agents/researcher.md +105 -0
  7. package/.claude/agents/reviewer.md +171 -0
  8. package/.claude/agents/scribe.md +72 -0
  9. package/.claude/agents/security-reviewer.md +130 -0
  10. package/.claude/agents/spec-reviewer.md +215 -0
  11. package/.claude/agents/tester.md +121 -0
  12. package/.claude/agents/uiux.md +126 -0
  13. package/.claude/agents/verifier.md +103 -0
  14. package/.claude/agents/wave-runner.md +74 -0
  15. package/LICENSE +21 -0
  16. package/README.md +389 -0
  17. package/factory/orchestrator/dist/actors.js +46 -0
  18. package/factory/orchestrator/dist/agents-registry.js +308 -0
  19. package/factory/orchestrator/dist/args.js +84 -0
  20. package/factory/orchestrator/dist/artifacts.js +111 -0
  21. package/factory/orchestrator/dist/attribution.js +160 -0
  22. package/factory/orchestrator/dist/audit.js +812 -0
  23. package/factory/orchestrator/dist/autonomy.js +151 -0
  24. package/factory/orchestrator/dist/budgetAlarm.js +366 -0
  25. package/factory/orchestrator/dist/budgets.js +196 -0
  26. package/factory/orchestrator/dist/claims.js +633 -0
  27. package/factory/orchestrator/dist/cli.js +3185 -0
  28. package/factory/orchestrator/dist/commit.js +152 -0
  29. package/factory/orchestrator/dist/coverage.js +188 -0
  30. package/factory/orchestrator/dist/crossFinding.js +479 -0
  31. package/factory/orchestrator/dist/crosscheck.js +396 -0
  32. package/factory/orchestrator/dist/daemon.js +707 -0
  33. package/factory/orchestrator/dist/db/projector.js +1317 -0
  34. package/factory/orchestrator/dist/db/queries.js +1714 -0
  35. package/factory/orchestrator/dist/db/schema.js +305 -0
  36. package/factory/orchestrator/dist/delegation.js +400 -0
  37. package/factory/orchestrator/dist/diffstat.js +164 -0
  38. package/factory/orchestrator/dist/dispatchAudit.js +275 -0
  39. package/factory/orchestrator/dist/dotenv.js +72 -0
  40. package/factory/orchestrator/dist/effort.js +202 -0
  41. package/factory/orchestrator/dist/epic.js +1018 -0
  42. package/factory/orchestrator/dist/epicWidth.js +238 -0
  43. package/factory/orchestrator/dist/errorIssues.js +249 -0
  44. package/factory/orchestrator/dist/errors.js +11 -0
  45. package/factory/orchestrator/dist/escalation.js +500 -0
  46. package/factory/orchestrator/dist/events.js +1015 -0
  47. package/factory/orchestrator/dist/findingAge.js +91 -0
  48. package/factory/orchestrator/dist/findingContext.js +103 -0
  49. package/factory/orchestrator/dist/findings.js +1156 -0
  50. package/factory/orchestrator/dist/gate.js +911 -0
  51. package/factory/orchestrator/dist/gh.js +146 -0
  52. package/factory/orchestrator/dist/git.js +87 -0
  53. package/factory/orchestrator/dist/goalCheck.js +380 -0
  54. package/factory/orchestrator/dist/graph.js +61 -0
  55. package/factory/orchestrator/dist/hookDecision.js +106 -0
  56. package/factory/orchestrator/dist/immutability.js +171 -0
  57. package/factory/orchestrator/dist/impact.js +280 -0
  58. package/factory/orchestrator/dist/integration.js +141 -0
  59. package/factory/orchestrator/dist/judgePreflight.js +155 -0
  60. package/factory/orchestrator/dist/judges.js +310 -0
  61. package/factory/orchestrator/dist/lessonAudit.js +399 -0
  62. package/factory/orchestrator/dist/lessons.js +1329 -0
  63. package/factory/orchestrator/dist/mcp.js +555 -0
  64. package/factory/orchestrator/dist/paths.js +82 -0
  65. package/factory/orchestrator/dist/plan.js +502 -0
  66. package/factory/orchestrator/dist/planQuorum.js +421 -0
  67. package/factory/orchestrator/dist/policy.js +1308 -0
  68. package/factory/orchestrator/dist/policyHook.js +28 -0
  69. package/factory/orchestrator/dist/preconditions.js +47 -0
  70. package/factory/orchestrator/dist/projects.js +134 -0
  71. package/factory/orchestrator/dist/prompts.js +57 -0
  72. package/factory/orchestrator/dist/provenance.js +201 -0
  73. package/factory/orchestrator/dist/providers/api-transport.js +117 -0
  74. package/factory/orchestrator/dist/providers/cli-transport.js +208 -0
  75. package/factory/orchestrator/dist/providers/index.js +54 -0
  76. package/factory/orchestrator/dist/providers/schema-validate.js +152 -0
  77. package/factory/orchestrator/dist/providers/types.js +49 -0
  78. package/factory/orchestrator/dist/queue.js +263 -0
  79. package/factory/orchestrator/dist/quorum.js +347 -0
  80. package/factory/orchestrator/dist/quorumEscalations.js +132 -0
  81. package/factory/orchestrator/dist/results.js +65 -0
  82. package/factory/orchestrator/dist/roadmap.js +437 -0
  83. package/factory/orchestrator/dist/runtime.js +86 -0
  84. package/factory/orchestrator/dist/sameMistakeKpi.js +243 -0
  85. package/factory/orchestrator/dist/sandbox.js +165 -0
  86. package/factory/orchestrator/dist/scaffold.js +373 -0
  87. package/factory/orchestrator/dist/scheduler.js +408 -0
  88. package/factory/orchestrator/dist/schemas.js +195 -0
  89. package/factory/orchestrator/dist/security.js +222 -0
  90. package/factory/orchestrator/dist/severity.js +293 -0
  91. package/factory/orchestrator/dist/spec.js +376 -0
  92. package/factory/orchestrator/dist/specChange.js +497 -0
  93. package/factory/orchestrator/dist/stack.js +324 -0
  94. package/factory/orchestrator/dist/symbols.js +966 -0
  95. package/factory/orchestrator/dist/taskEvents.js +399 -0
  96. package/factory/orchestrator/dist/taskId.js +126 -0
  97. package/factory/orchestrator/dist/taxonomy.js +113 -0
  98. package/factory/orchestrator/dist/testSelect.js +129 -0
  99. package/factory/orchestrator/dist/testerAudit.js +264 -0
  100. package/factory/orchestrator/dist/testgate.js +171 -0
  101. package/factory/orchestrator/dist/usage.js +874 -0
  102. package/factory/orchestrator/dist/waivers.js +228 -0
  103. package/factory/orchestrator/dist/waveBudget.js +223 -0
  104. package/factory/orchestrator/dist/waveConcurrency.js +250 -0
  105. package/factory/orchestrator/dist/waveNext.js +210 -0
  106. package/factory/orchestrator/dist/waveSchedule.js +146 -0
  107. package/factory/orchestrator/dist/worktree.js +207 -0
  108. package/factory/orchestrator/drizzle/0000_broken_rockslide.sql +183 -0
  109. package/factory/orchestrator/drizzle/0001_luxuriant_zaladane.sql +1 -0
  110. package/factory/orchestrator/drizzle/0002_yellow_wild_pack.sql +8 -0
  111. package/factory/orchestrator/drizzle/0003_damp_riptide.sql +12 -0
  112. package/factory/orchestrator/drizzle/0004_broad_lady_vermin.sql +1 -0
  113. package/factory/orchestrator/drizzle/0005_romantic_jocasta.sql +2 -0
  114. package/factory/orchestrator/drizzle/0006_bent_madelyne_pryor.sql +16 -0
  115. package/factory/orchestrator/drizzle/0007_overjoyed_goliath.sql +1 -0
  116. package/factory/orchestrator/drizzle/0008_smart_iron_patriot.sql +2 -0
  117. package/factory/orchestrator/drizzle/0009_agents_epic_id.sql +2 -0
  118. package/factory/orchestrator/drizzle/0010_milestone_kind.sql +1 -0
  119. package/factory/orchestrator/drizzle/meta/_journal.json +83 -0
  120. package/factory/policies/budgets.yml +262 -0
  121. package/factory/policies/crosscheck.yml +308 -0
  122. package/factory/policies/delegation.yml +44 -0
  123. package/factory/policies/effort.yml +142 -0
  124. package/factory/policies/guardrails.yml +306 -0
  125. package/factory/policies/lessons.md +239 -0
  126. package/factory/policies/scheduler.yml +90 -0
  127. package/factory/policies/sensitive-paths.yml +148 -0
  128. package/factory/policies/severity.yml +83 -0
  129. package/factory/policies/stack.yml +60 -0
  130. package/factory/policies/taxonomy.yml +340 -0
  131. package/factory/policies/worktree.yml +95 -0
  132. package/factory/scaffold/base/.github/workflows/ci.yml +34 -0
  133. package/factory/scaffold/base/.gitignore.tmpl +11 -0
  134. package/factory/scaffold/base/AGENTS.md +33 -0
  135. package/factory/scaffold/base/README.md +25 -0
  136. package/factory/scaffold/base/biome.json.tmpl +30 -0
  137. package/factory/scaffold/base/package.json +25 -0
  138. package/factory/scaffold/base/pnpm-workspace.yaml.tmpl +16 -0
  139. package/factory/scaffold/base/src/index.ts +4 -0
  140. package/factory/scaffold/base/test/index.test.ts +8 -0
  141. package/factory/scaffold/base/tsconfig.json +20 -0
  142. package/factory/scaffold/base/tsconfig.test.json +9 -0
  143. package/factory/scaffold/base/vitest.config.ts +29 -0
  144. package/factory/scaffold/mcp/mcp.manifest.json +22 -0
  145. package/factory/scaffold/mcp/package.fragment.json +9 -0
  146. package/factory/scaffold/mcp/src/mcp/README.md +63 -0
  147. package/factory/scaffold/mcp/src/mcp/guard.ts +110 -0
  148. package/factory/scaffold/mcp/src/mcp/redact.ts +69 -0
  149. package/factory/scaffold/mcp/src/mcp/server.ts +55 -0
  150. package/factory/scaffold/mcp/src/mcp/tools/health.ts +60 -0
  151. package/factory/scaffold/mcp/test/mcp/guard.test.ts +108 -0
  152. package/factory/scaffold/mcp/test/mcp/redact.test.ts +61 -0
  153. package/factory/scaffold/mcp/test/mcp/surface.test.ts +100 -0
  154. package/factory/scaffold/ui/index.html +12 -0
  155. package/factory/scaffold/ui/package.fragment.json +13 -0
  156. package/factory/scaffold/ui/src/App.vue +10 -0
  157. package/factory/scaffold/ui/src/main.ts +5 -0
  158. package/factory/scaffold/ui/src/shims.d.ts +11 -0
  159. package/factory/scaffold/ui/vite.config.ts +20 -0
  160. package/factory/scaffold/ui-tailwind/package.fragment.json +6 -0
  161. package/factory/scaffold/ui-tailwind/vite.config.ts +26 -0
  162. package/factory/specs/schema/event.schema.json +82 -0
  163. package/factory/specs/schema/finding-evidence.schema.json +45 -0
  164. package/factory/specs/schema/finding.schema.json +115 -0
  165. package/factory/specs/schema/grader-verdict.schema.json +50 -0
  166. package/factory/specs/schema/judge-verdict.schema.json +19 -0
  167. package/factory/specs/schema/lesson.schema.json +85 -0
  168. package/factory/specs/schema/mcp-manifest.schema.json +230 -0
  169. package/factory/specs/schema/result.schema.json +81 -0
  170. package/factory/specs/schema/spec-change-request.schema.json +46 -0
  171. package/factory/specs/schema/task-spec.schema.json +134 -0
  172. package/package.json +98 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The one test that talks the actual protocol.
3
+ *
4
+ * guard.test.ts and redact.test.ts check the policy in isolation; this one
5
+ * wires a real client to a real server over an in-memory transport pair and
6
+ * asserts the surface an agent would actually see. It is the difference between
7
+ * "the manifest is well-formed" and "the server built from that manifest
8
+ * answers tools/list and tools/call".
9
+ */
10
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
11
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { buildServer, loadManifest } from '../../src/mcp/server.js';
14
+
15
+ interface TextContent {
16
+ type: string;
17
+ text: string;
18
+ }
19
+
20
+ async function connectedClient(): Promise<Client> {
21
+ const server = buildServer(loadManifest());
22
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
23
+ const client = new Client({ name: 'surface-test', version: '0.0.0' });
24
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
25
+ return client;
26
+ }
27
+
28
+ function textOf(result: { content?: unknown }): string {
29
+ const content = result.content as TextContent[] | undefined;
30
+ return content?.[0]?.text ?? '';
31
+ }
32
+
33
+ describe('the MCP surface', () => {
34
+ it('advertises exactly the tools the manifest declares, and nothing else', async () => {
35
+ const manifest = loadManifest();
36
+ const client = await connectedClient();
37
+
38
+ const { tools } = await client.listTools();
39
+
40
+ expect(tools.map((tool) => tool.name).sort()).toEqual(
41
+ manifest.tools.map((tool) => tool.name).sort(),
42
+ );
43
+ await client.close();
44
+ });
45
+
46
+ it('carries the manifest annotations through to the client', async () => {
47
+ const client = await connectedClient();
48
+
49
+ const { tools } = await client.listTools();
50
+ const health = tools.find((tool) => tool.name === 'project_health');
51
+
52
+ // An agent decides whether a call is safe from these hints, so they have to
53
+ // survive the trip — asserting them on the manifest alone would not prove it.
54
+ expect(health?.annotations?.readOnlyHint).toBe(true);
55
+ expect(health?.annotations?.destructiveHint).toBe(false);
56
+ expect(health?.annotations?.openWorldHint).toBe(false);
57
+ await client.close();
58
+ });
59
+
60
+ it('answers project_health with the server identity', async () => {
61
+ const manifest = loadManifest();
62
+ const client = await connectedClient();
63
+
64
+ const result = await client.callTool({ name: 'project_health', arguments: {} });
65
+ const payload = JSON.parse(textOf(result)) as Record<string, unknown>;
66
+
67
+ expect(payload.name).toBe(manifest.name);
68
+ expect(payload.version).toBe(manifest.version);
69
+ expect(payload.transport).toBe('stdio');
70
+ expect(payload.tools).toBeUndefined();
71
+ await client.close();
72
+ });
73
+
74
+ it('lists the declared surface when asked verbosely', async () => {
75
+ const client = await connectedClient();
76
+
77
+ const result = await client.callTool({
78
+ name: 'project_health',
79
+ arguments: { verbose: true },
80
+ });
81
+ const payload = JSON.parse(textOf(result)) as { tools?: { name: string }[] };
82
+
83
+ expect(payload.tools?.map((tool) => tool.name)).toContain('project_health');
84
+ await client.close();
85
+ });
86
+
87
+ it('rejects an argument that is not in the tool input schema', async () => {
88
+ const client = await connectedClient();
89
+
90
+ const result = await client.callTool({
91
+ name: 'project_health',
92
+ arguments: { verbose: 'yes-please' },
93
+ });
94
+
95
+ // Typed input schemas (MCP-P4) are the reason unvalidated arguments never
96
+ // reach tool code; a wrong type has to come back as an error, not a crash.
97
+ expect(result.isError).toBe(true);
98
+ await client.close();
99
+ });
100
+ });
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PROJECT_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,13 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vite",
4
+ "build:ui": "vite build"
5
+ },
6
+ "dependencies": {
7
+ "vue": "3.5.40"
8
+ },
9
+ "devDependencies": {
10
+ "@vitejs/plugin-vue": "6.0.8",
11
+ "vite": "8.2.0"
12
+ }
13
+ }
@@ -0,0 +1,10 @@
1
+ <script setup lang="ts">
2
+ // Scaffold stub — replace with the project's first real page.
3
+ </script>
4
+
5
+ <template>
6
+ <main class="app">
7
+ <h1>__PROJECT_NAME__</h1>
8
+ <p>Styles start in <code>src/styles/main.css</code>.</p>
9
+ </main>
10
+ </template>
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue';
2
+ import App from './App.vue';
3
+ import './styles/main.css';
4
+
5
+ createApp(App).mount('#app');
@@ -0,0 +1,11 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+
4
+ const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
5
+ export default component;
6
+ }
7
+
8
+ // Side-effect asset imports (main.ts's `import './styles/main.css'`) need an
9
+ // ambient module declaration too, or tsc/vue-tsc fails the build on the
10
+ // first `--ui` scaffold typecheck (TS2882-class "cannot find module").
11
+ declare module '*.css';
@@ -0,0 +1,20 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import vue from '@vitejs/plugin-vue';
4
+ import { defineConfig } from 'vite';
5
+
6
+ const here = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export default defineConfig({
9
+ root: here,
10
+ plugins: [vue()],
11
+ resolve: {
12
+ alias: {
13
+ '@': path.join(here, 'src'),
14
+ },
15
+ },
16
+ build: {
17
+ outDir: path.join(here, 'dist'),
18
+ emptyOutDir: true,
19
+ },
20
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "devDependencies": {
3
+ "@tailwindcss/vite": "4.3.3",
4
+ "tailwindcss": "4.3.3"
5
+ }
6
+ }
@@ -0,0 +1,26 @@
1
+ // The Tailwind variant of this project's Vite config, chosen when the stack
2
+ // answers named `styling: tailwind`. Identical to the plain
3
+ // one but for the plugin — kept as a whole file rather than a patch because
4
+ // `copyTemplateDir` layers files, and a config assembled from fragments is a
5
+ // config nobody can read in one place.
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import tailwindcss from '@tailwindcss/vite';
9
+ import vue from '@vitejs/plugin-vue';
10
+ import { defineConfig } from 'vite';
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ export default defineConfig({
15
+ root: here,
16
+ plugins: [vue(), tailwindcss()],
17
+ resolve: {
18
+ alias: {
19
+ '@': path.join(here, 'src'),
20
+ },
21
+ },
22
+ build: {
23
+ outDir: path.join(here, 'dist'),
24
+ emptyOutDir: true,
25
+ },
26
+ });
@@ -0,0 +1,82 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/event.schema.json",
4
+ "$comment": "Taxonomy-valued fields carry an x-taxonomy annotation naming the dimension in factory/policies/taxonomy.yml. The runtime validates those values against that file at write time; enums are never inlined here to avoid drift between the schema and the taxonomy (architecture §8).",
5
+ "title": "Event",
6
+ "description": "One record in state/events/*.jsonl, the factory's durable execution substrate (architecture §7). Field names follow §7's record shape exactly: {ts, session_id, actor, event_type, task_id?, agent_id?, plan_version, causal_parent, payload}.",
7
+ "type": "object",
8
+ "required": [
9
+ "ts",
10
+ "session_id",
11
+ "actor",
12
+ "event_type",
13
+ "plan_version",
14
+ "causal_parent",
15
+ "payload"
16
+ ],
17
+ "properties": {
18
+ "ts": {
19
+ "type": "string",
20
+ "format": "date-time"
21
+ },
22
+ "session_id": {
23
+ "type": "string",
24
+ "description": "Names one event log file -- logPath() interpolates it into <dir>/<session_id>.jsonl -- and is also the key half of an event id, <session-id>#<index>. So it must be a single non-empty path segment. Structural, not a charset: '#' stays legal because parseEventId splits on the LAST one deliberately, and a '#' cannot move a file. This mirrors requireSessionIdShape() in events.ts, which guards every read and every write; an id that validator can already open satisfies this by construction, so no record on disk is rejected by it (D-197).",
25
+ "minLength": 1,
26
+ "pattern": "^[^/]+$",
27
+ "not": { "enum": [".", ".."] }
28
+ },
29
+ "actor": {
30
+ "type": "string",
31
+ "description": "Who emitted the event: 'user', 'system', an agent role, or a concrete agent_id."
32
+ },
33
+ "event_type": {
34
+ "type": "string",
35
+ "description": "Free string, not an enum — a closed list here would reject a new event type at write time and lose the record. e.g. user_prompt, dispatch_decision, error-logged, or one of the gate_event / graph_event taxonomy values (taxonomy.yml)."
36
+ },
37
+ "task_id": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "description": "Optional — absent for session-level or epic-level events. Absence is the whole signal (agents-registry.ts folds a dispatch with no task id as epic-level), so \"\" is not a way to say it: minLength 1 keeps the field to two states instead of three (D-244)."
41
+ },
42
+ "agent_id": {
43
+ "type": "string",
44
+ "minLength": 1,
45
+ "description": "Optional — concrete instance id (e.g. coder-142), not a taxonomy role. Present means it names an instance; \"\" names none, so it is refused rather than written (D-244)."
46
+ },
47
+ "plan_version": {
48
+ "type": "integer",
49
+ "minimum": 1
50
+ },
51
+ "causal_parent": {
52
+ "type": ["string", "null"],
53
+ "description": "Event id of the event that caused this one; null only for session-root events."
54
+ },
55
+ "payload": {
56
+ "type": "object",
57
+ "description": "Event-type-specific body."
58
+ },
59
+ "project": {
60
+ "type": "string",
61
+ "minLength": 1,
62
+ "description": "Optional plain-string project identifier (Phase 6b, architecture §8) — NOT a closed taxonomy.yml vocabulary value, so no x-taxonomy annotation here. Absent on every event logged before Phase 6b; db/queries.ts's read helpers treat an absent/null project as the default 'black-smith'. That default is reached by absence, so \"\" would slip past it and name a project called nothing (D-244)."
63
+ },
64
+ "edge": {
65
+ "type": "object",
66
+ "description": "Present only on edge-recorded events (architecture §7).",
67
+ "required": ["edge_type", "edge_provenance"],
68
+ "properties": {
69
+ "edge_type": {
70
+ "type": "string",
71
+ "x-taxonomy": "edge_type"
72
+ },
73
+ "edge_provenance": {
74
+ "type": "string",
75
+ "x-taxonomy": "edge_provenance"
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
80
+ },
81
+ "additionalProperties": false
82
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/finding-evidence.schema.json",
4
+ "$comment": "Taxonomy-valued fields carry an x-taxonomy annotation naming the dimension in factory/policies/taxonomy.yml. The runtime validates those values against that file at write time; enums are never inlined here to avoid drift between the schema and the taxonomy (architecture §8).",
5
+ "title": "Finding Evidence",
6
+ "description": "What a judge actually returns when its mandate is to FIND: evidence, no identity. finding.schema.json describes the record the orchestrator stores, and requires six fields a judge cannot know — finding_id, task_id, fingerprint, finding_status, found_by, found_by_provider — which findings.ts then refuses to accept from a judge (findings.evidence-carries-identity). Asking an external finder for that shape means asking it to invent a sha256 and a status, and throwing both away. This schema is the judge's half of the contract; mintFindings() adds the other half.",
7
+ "type": "object",
8
+ "required": ["file_path", "finding_category", "severity", "summary", "failure_scenario"],
9
+ "properties": {
10
+ "file_path": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "description": "Repo-relative path the finding is anchored to, normalized (forward slashes, no leading ./). The fingerprint's first component."
14
+ },
15
+ "finding_category": {
16
+ "type": "string",
17
+ "x-taxonomy": "finding_category"
18
+ },
19
+ "severity": {
20
+ "type": "string",
21
+ "x-taxonomy": "severity"
22
+ },
23
+ "summary": {
24
+ "type": "string",
25
+ "minLength": 1
26
+ },
27
+ "failure_scenario": {
28
+ "type": "object",
29
+ "description": "Concrete inputs -> wrong output. A finding without one dies at verify (agent-interviews.md V-2).",
30
+ "required": ["inputs", "expected", "actual"],
31
+ "properties": {
32
+ "inputs": { "type": "string" },
33
+ "expected": { "type": "string" },
34
+ "actual": { "type": "string" }
35
+ },
36
+ "additionalProperties": false
37
+ },
38
+ "criterion_ref": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "description": "Which acceptance criterion the evidence is against. Evidence, not identity: a spec reviewer legitimately knows which criterion it read, and cannot know the plan version it was dispatched on."
42
+ }
43
+ },
44
+ "additionalProperties": false
45
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/finding.schema.json",
4
+ "$comment": "Taxonomy-valued fields carry an x-taxonomy annotation naming the dimension in factory/policies/taxonomy.yml. The runtime validates those values against that file at write time; enums are never inlined here to avoid drift between the schema and the taxonomy (architecture §8).",
5
+ "title": "Review Finding",
6
+ "description": "One reviewer/verifier finding. Verifier's mandate is to refute, not confirm (architecture §6); a finding survives only with a concrete failure scenario (agent-interviews.md V-2).",
7
+ "type": "object",
8
+ "required": [
9
+ "finding_id",
10
+ "task_id",
11
+ "fingerprint",
12
+ "file_path",
13
+ "finding_category",
14
+ "severity",
15
+ "finding_status",
16
+ "summary",
17
+ "failure_scenario",
18
+ "found_by"
19
+ ],
20
+ "properties": {
21
+ "finding_id": { "type": "string" },
22
+ "task_id": { "type": "string" },
23
+ "epic_id": {
24
+ "type": "string",
25
+ "minLength": 1,
26
+ "description": "The epic this finding belongs to. Optional, not required: findings raised before this field existed carry the epic in the task_id prefix instead, and a task_id that names no epic yields no epic_id rather than a guessed one (D-49/P9-10)."
27
+ },
28
+ "fingerprint": {
29
+ "type": "string",
30
+ "description": "Stable hash identifying this finding for dedup and waiver lookup; waived findings are suppressed in future reviews of the same code by fingerprint match."
31
+ },
32
+ "file_path": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Repo-relative path the finding is anchored to, normalized (forward slashes, no leading ./). The fingerprint's first component, kept as a field because a hash cannot be joined against a task's claims: dispatch-time context and the stale-evidence check both need the path itself (P9-15)."
36
+ },
37
+ "finding_category": {
38
+ "type": "string",
39
+ "x-taxonomy": "finding_category"
40
+ },
41
+ "severity": {
42
+ "type": "string",
43
+ "x-taxonomy": "severity"
44
+ },
45
+ "finding_status": {
46
+ "type": "string",
47
+ "x-taxonomy": "finding_status"
48
+ },
49
+ "finding_scope": {
50
+ "type": "string",
51
+ "x-taxonomy": "finding_scope",
52
+ "description": "Absent means `diff` — findings written before the dimension existed carry no scope. `spec` says the code is right and the plan is wrong; such a finding blocks the plan, never the diff (P9-9/D-33), and requires spec_ref."
53
+ },
54
+ "spec_ref": {
55
+ "type": "object",
56
+ "description": "Which criterion of which plan version the spec finding is against. Required when finding_scope is `spec` and forbidden otherwise — enforced in findings.ts, not here, because a JSON-Schema `if/then` would have to inline the taxonomy value `spec` (see $comment).",
57
+ "required": ["plan_version", "criterion_ref"],
58
+ "properties": {
59
+ "plan_version": { "type": "integer", "minimum": 1 },
60
+ "criterion_ref": { "type": "string", "minLength": 1 }
61
+ },
62
+ "additionalProperties": false
63
+ },
64
+ "summary": {
65
+ "type": "string",
66
+ "minLength": 1
67
+ },
68
+ "failure_scenario": {
69
+ "type": "object",
70
+ "description": "Concrete inputs -> wrong output. A finding without one dies at verify (agent-interviews.md V-2).",
71
+ "required": ["inputs", "expected", "actual"],
72
+ "properties": {
73
+ "inputs": { "type": "string" },
74
+ "expected": { "type": "string" },
75
+ "actual": { "type": "string" }
76
+ },
77
+ "additionalProperties": false
78
+ },
79
+ "found_by": {
80
+ "type": "string",
81
+ "x-taxonomy": "agent"
82
+ },
83
+ "found_by_provider": {
84
+ "type": "string",
85
+ "x-taxonomy": "provider"
86
+ },
87
+ "verified_by": {
88
+ "type": "string",
89
+ "x-taxonomy": "agent"
90
+ },
91
+ "verified_by_provider": {
92
+ "type": "string",
93
+ "x-taxonomy": "provider"
94
+ },
95
+ "same_mistake_of_lesson_id": {
96
+ "type": ["string", "null"],
97
+ "description": "Set when this finding matches an approved lesson; auto-escalates one severity level (architecture §9.7)."
98
+ },
99
+ "waiver_id": {
100
+ "type": ["string", "null"],
101
+ "description": "Set once an S3/S4 finding has been waived by the operator; keyed by fingerprint, never re-asked."
102
+ },
103
+ "amends_task_ids": {
104
+ "type": "array",
105
+ "items": { "type": "string" },
106
+ "description": "Set once an amendment took this finding to `amend-pending`: the task ids that amendment added or superseded. They are the finding's discharge condition — until they land it cannot reach `amended`. Without them an amendment closes an unwaivable S1/S2 spec finding at plan-write time, naming no task outcome at all (D-127)."
107
+ },
108
+ "amends_plan_version": {
109
+ "type": "integer",
110
+ "minimum": 1,
111
+ "description": "The plan version that amendment cut, so `amends_task_ids` can be resolved against the version that defines them (D-127)."
112
+ }
113
+ },
114
+ "additionalProperties": false
115
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/grader-verdict.schema.json",
4
+ "title": "Grader Verdict",
5
+ "description": "The rubric result a grader writes into the `structured_output` of state/results/<task-id>.grader-r<round>.json (.claude/agents/grader.md, 'Output contract'). It validates the verdict, not the envelope around it: the envelope is a result and validates against result.schema.json. `status` and `overall` are plain structural enums, not taxonomy dimensions — they describe whether a criterion was met, not a controlled-vocabulary classification (architecture §8 draws that line at classification fields only). `round` is capped at 2 because the grader's loop is a hard stop at two rounds, not a suggestion.",
6
+ "type": "object",
7
+ "required": ["round", "criteria", "overall"],
8
+ "properties": {
9
+ "round": {
10
+ "type": "integer",
11
+ "minimum": 1,
12
+ "maximum": 2
13
+ },
14
+ "criteria": {
15
+ "type": "array",
16
+ "minItems": 1,
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["criterion", "status", "evidence"],
20
+ "properties": {
21
+ "criterion": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "status": {
26
+ "type": "string",
27
+ "enum": ["pass", "fail", "partial"]
28
+ },
29
+ "evidence": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ }
33
+ },
34
+ "additionalProperties": false
35
+ }
36
+ },
37
+ "overall": {
38
+ "type": "string",
39
+ "enum": ["pass", "fail"]
40
+ },
41
+ "gaps": {
42
+ "type": "array",
43
+ "items": {
44
+ "type": "string",
45
+ "minLength": 1
46
+ }
47
+ }
48
+ },
49
+ "additionalProperties": false
50
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/judge-verdict.schema.json",
4
+ "title": "Judge Verdict",
5
+ "description": "Generic binary verdict returned by an external judge for a 'verify' or 'plan-critique' JudgeRequest (architecture §6, Phase 8): does this provider agree with the native claim under judgment (a blocking finding, an epic-final verdict, ...)? A 'review' JudgeRequest validates against finding.schema.json instead (an array of findings), not this schema. `verdict` is a plain structural enum, not a taxonomy dimension — it describes a judge's agree/disagree stance, not a controlled-vocabulary tag (architecture §8 draws that line at classification fields only).",
6
+ "type": "object",
7
+ "required": ["verdict", "rationale"],
8
+ "properties": {
9
+ "verdict": {
10
+ "type": "string",
11
+ "enum": ["confirm", "refute"]
12
+ },
13
+ "rationale": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ }
17
+ },
18
+ "additionalProperties": false
19
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://black-smith.dev/schema/lesson.schema.json",
4
+ "$comment": "Taxonomy-valued fields carry an x-taxonomy annotation naming the dimension in factory/policies/taxonomy.yml. The runtime validates those values against that file at write time; enums are never inlined here to avoid drift between the schema and the taxonomy (architecture §8).",
5
+ "title": "Lesson",
6
+ "description": "A typed, principle-level memory entry (architecture §9). Instance-level transcripts are rejected at the gate. Bi-temporal supersession with provenance: nothing self-modifies without operator approval.",
7
+ "type": "object",
8
+ "required": [
9
+ "lesson_id",
10
+ "lesson_type",
11
+ "lesson_level",
12
+ "lesson_status",
13
+ "lesson_scope",
14
+ "statement",
15
+ "valid_from",
16
+ "superseded_by",
17
+ "provenance_event_ids"
18
+ ],
19
+ "properties": {
20
+ "lesson_id": { "type": "string" },
21
+ "lesson_type": {
22
+ "type": "string",
23
+ "x-taxonomy": "lesson_type"
24
+ },
25
+ "lesson_level": {
26
+ "type": "string",
27
+ "x-taxonomy": "lesson_level"
28
+ },
29
+ "lesson_status": {
30
+ "type": "string",
31
+ "x-taxonomy": "lesson_status"
32
+ },
33
+ "lesson_scope": {
34
+ "type": "string",
35
+ "x-taxonomy": "lesson_scope"
36
+ },
37
+ "statement": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "description": "Abstract, transferable, checkable principle — never an instance-level transcript."
41
+ },
42
+ "valid_from": {
43
+ "type": "string",
44
+ "format": "date-time"
45
+ },
46
+ "superseded_by": {
47
+ "type": ["string", "null"],
48
+ "description": "lesson_id of the lesson that superseded this one, or null."
49
+ },
50
+ "invalidated_by_event_id": {
51
+ "type": ["string", "null"],
52
+ "description": "Set when a later run contradicts this lesson (lesson_status: invalidated)."
53
+ },
54
+ "provenance_event_ids": {
55
+ "type": "array",
56
+ "items": { "type": "string" },
57
+ "minItems": 1,
58
+ "description": "Event-log entries that produced this lesson (traceable, never silent deletion)."
59
+ },
60
+ "evidence": {
61
+ "type": "string",
62
+ "description": "Short excerpt/summary of the error or decision checkpoint that generated this candidate."
63
+ },
64
+ "finding_category": {
65
+ "type": "string",
66
+ "x-taxonomy": "finding_category",
67
+ "description": "Phase 7 (lessons.ts): optional — only meaningful for a `rule`-typed, claim-path/stack-wide-scoped entry. Compiled into lessons.md's same-mistake entry format so severity.ts can match a new finding's category against it (architecture §9.7). Not required at write time — agent-role/case-type-scoped entries never carry one (lessons.md: \"do not participate in this per-file match\")."
68
+ },
69
+ "claim_path": {
70
+ "type": "string",
71
+ "description": "Phase 7 (lessons.ts): optional glob (picomatch), meaningful only for claim-path/stack-wide-scoped entries — the file pattern this lesson's same-mistake match applies to. Omitted/treated as \"**\" for stack-wide entries (lessons.md convention)."
72
+ },
73
+ "agent_role": {
74
+ "type": "string",
75
+ "x-taxonomy": "agent",
76
+ "description": "D-129: the selector an agent-role-scoped entry is filtered by at dispatch — required for that scope and ignored for every other. Without it the entry reaches no role at all, because the three selector scopes (claim-path/agent-role/case-type) each filter on exactly one field and a missing selector matches nothing rather than everything."
77
+ },
78
+ "case_type": {
79
+ "type": "string",
80
+ "x-taxonomy": "case",
81
+ "description": "D-129: the selector a case-type-scoped entry is filtered by at dispatch — the taxonomy `case` of the dispatching task, read off the immutable plan. Required for that scope, ignored for every other."
82
+ }
83
+ },
84
+ "additionalProperties": false
85
+ }