simt-dev 0.7.0 → 0.8.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 (235) hide show
  1. package/CHANGELOG.md +191 -870
  2. package/README.md +188 -213
  3. package/bin/simt.mjs +19 -55
  4. package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
  5. package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
  6. package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
  7. package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
  8. package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
  9. package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
  10. package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
  11. package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
  12. package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
  13. package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
  14. package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
  15. package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
  16. package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
  17. package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
  18. package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
  19. package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
  20. package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
  21. package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
  22. package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
  23. package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
  24. package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
  25. package/ecosystems/bun.json +6 -0
  26. package/ecosystems/cargo.json +25 -0
  27. package/ecosystems/composer.json +6 -0
  28. package/ecosystems/dart.json +24 -0
  29. package/ecosystems/deno.json +21 -0
  30. package/ecosystems/devcontainers.json +6 -0
  31. package/ecosystems/docker-compose.json +6 -0
  32. package/ecosystems/docker.json +6 -0
  33. package/ecosystems/dotnet.json +25 -0
  34. package/ecosystems/elixir.json +23 -0
  35. package/ecosystems/gitsubmodule.json +6 -0
  36. package/ecosystems/go.json +25 -0
  37. package/ecosystems/gradle.json +13 -0
  38. package/ecosystems/maven.json +23 -0
  39. package/ecosystems/node.json +25 -0
  40. package/ecosystems/python.json +20 -0
  41. package/ecosystems/ruby.json +23 -0
  42. package/ecosystems/swift.json +25 -0
  43. package/ecosystems/terraform.json +6 -0
  44. package/ecosystems/uv.json +6 -0
  45. package/harnesses/antigravity.json +162 -0
  46. package/harnesses/claude.json +206 -0
  47. package/harnesses/codex.json +136 -0
  48. package/harnesses/copilot.json +88 -0
  49. package/harnesses/cursor.json +140 -0
  50. package/harnesses/deepseek.json +99 -0
  51. package/harnesses/gemini.json +122 -0
  52. package/harnesses/opencode.json +173 -0
  53. package/package.json +12 -2
  54. package/rules/core.md +7 -0
  55. package/rules/mdc/intake-questions.mdc +8 -0
  56. package/rules/mdc/slice-commit.mdc +57 -4
  57. package/src/advice/advice.mjs +3 -2
  58. package/src/agenthooks/prompt.mjs +5 -2
  59. package/src/agenthooks/render.mjs +86 -180
  60. package/src/agenthooks/shim.mjs +1 -1
  61. package/src/agents/render.mjs +15 -18
  62. package/src/api.mjs +31 -0
  63. package/src/argv.mjs +48 -3
  64. package/src/bench/ablate.mjs +3 -4
  65. package/src/bench/adapters.json +163 -105
  66. package/src/bench/adapters.mjs +466 -0
  67. package/src/bench/arms.mjs +333 -0
  68. package/src/bench/grade.mjs +33 -73
  69. package/src/bench/graders.mjs +42 -0
  70. package/src/bench/mine.mjs +121 -475
  71. package/src/bench/probe-ledger.mjs +149 -0
  72. package/src/bench/probes.catalog.mjs +918 -0
  73. package/src/bench/probes.mjs +79 -1064
  74. package/src/bench/render.mjs +111 -0
  75. package/src/bench/report.mjs +120 -962
  76. package/src/bench/run.mjs +185 -802
  77. package/src/bench/variants.mjs +4 -5
  78. package/src/bench/workspace.mjs +160 -0
  79. package/src/brief/assemble.mjs +48 -15
  80. package/src/ci/dependabot.mjs +25 -55
  81. package/src/ci/workflow.mjs +30 -7
  82. package/src/cli.mjs +45 -0
  83. package/src/commands/agent.mjs +4 -13
  84. package/src/commands/agenthook.mjs +70 -40
  85. package/src/commands/bench.mjs +191 -18
  86. package/src/commands/brief.mjs +5 -4
  87. package/src/commands/ci.mjs +13 -14
  88. package/src/commands/compare.mjs +42 -42
  89. package/src/commands/completions.mjs +2 -6
  90. package/src/commands/decide.mjs +4 -4
  91. package/src/commands/distill.mjs +4 -3
  92. package/src/commands/docs.mjs +88 -0
  93. package/src/commands/doctor.mjs +11 -7
  94. package/src/commands/evidence.mjs +6 -9
  95. package/src/commands/explain.mjs +0 -1
  96. package/src/commands/gate.mjs +26 -25
  97. package/src/commands/graph.mjs +2 -3
  98. package/src/commands/harness.mjs +6 -5
  99. package/src/commands/help.mjs +168 -19
  100. package/src/commands/hooks.mjs +119 -49
  101. package/src/commands/ignore.mjs +13 -8
  102. package/src/commands/impact.mjs +8 -15
  103. package/src/commands/index.mjs +318 -193
  104. package/src/commands/init-scan.mjs +90 -0
  105. package/src/commands/init.mjs +124 -105
  106. package/src/commands/lease.mjs +73 -25
  107. package/src/commands/lessons.mjs +10 -9
  108. package/src/commands/local.mjs +3 -3
  109. package/src/commands/map.mjs +24 -14
  110. package/src/commands/mcp.mjs +2 -18
  111. package/src/commands/pack.mjs +9 -6
  112. package/src/commands/plan.mjs +6 -5
  113. package/src/commands/plugin.mjs +2 -2
  114. package/src/commands/prbody.mjs +3 -3
  115. package/src/commands/repoindex.mjs +12 -11
  116. package/src/commands/review.mjs +2 -2
  117. package/src/commands/rules.mjs +37 -16
  118. package/src/commands/shared.mjs +14 -19
  119. package/src/commands/skills.mjs +33 -32
  120. package/src/commands/skillscompile.mjs +7 -8
  121. package/src/commands/skillsinstall.mjs +7 -6
  122. package/src/commands/slice.mjs +220 -74
  123. package/src/commands/spec.mjs +167 -0
  124. package/src/commands/status.mjs +27 -18
  125. package/src/commands/sync.mjs +27 -25
  126. package/src/commands/tests.mjs +18 -21
  127. package/src/commands/tokens.mjs +21 -26
  128. package/src/commands/undo.mjs +5 -15
  129. package/src/commands/upgrade.mjs +15 -7
  130. package/src/commands/why.mjs +15 -21
  131. package/src/compare/hooksstate.mjs +2 -6
  132. package/src/compare/learn.mjs +3 -1
  133. package/src/compare/row.mjs +0 -2
  134. package/src/context.mjs +64 -0
  135. package/src/decisions/index-doc.mjs +46 -0
  136. package/src/decisions/index.mjs +3 -2
  137. package/src/diff.mjs +1 -1
  138. package/src/distill/distill.mjs +14 -1
  139. package/src/distill/engine.mjs +5 -1
  140. package/src/docs/commands.mjs +65 -0
  141. package/src/ecosystem/records.mjs +219 -0
  142. package/src/explain.mjs +31 -0
  143. package/src/gates/index.mjs +385 -0
  144. package/src/graph/compile.mjs +8 -4
  145. package/src/graph/load.mjs +6 -2
  146. package/src/graph/spec.mjs +10 -8
  147. package/src/harness/records.mjs +143 -0
  148. package/src/hooks/capability.mjs +7 -5
  149. package/src/hooks/dispatcher.mjs +209 -60
  150. package/src/hooks/gates.mjs +10 -43
  151. package/src/hooks/resolve.mjs +4 -14
  152. package/src/hooks/shim.mjs +5 -2
  153. package/src/hooks/where.mjs +2 -7
  154. package/src/identity.mjs +31 -12
  155. package/src/index/coverage.mjs +58 -0
  156. package/src/index/extract.mjs +6 -16
  157. package/src/index/gate.mjs +1 -1
  158. package/src/index/govern.mjs +26 -9
  159. package/src/index/impact.mjs +4 -8
  160. package/src/index/map.mjs +14 -15
  161. package/src/index/open.mjs +173 -0
  162. package/src/index/persist.mjs +224 -57
  163. package/src/index/store.mjs +82 -39
  164. package/src/index/tests.mjs +2 -6
  165. package/src/journal.mjs +94 -20
  166. package/src/kernel/card.mjs +37 -63
  167. package/src/kernel/checks.mjs +4 -10
  168. package/src/lease.mjs +102 -7
  169. package/src/ledger/index.mjs +150 -14
  170. package/src/local/client.mjs +4 -6
  171. package/src/locate.mjs +33 -9
  172. package/src/manifest/schema.mjs +213 -0
  173. package/src/mcp/paginate.mjs +33 -0
  174. package/src/mcp/resources.mjs +7 -10
  175. package/src/mcp/schemas.mjs +512 -0
  176. package/src/mcp/server.mjs +119 -25
  177. package/src/mcp/tools-facts.mjs +326 -0
  178. package/src/mcp/tools-read.mjs +221 -0
  179. package/src/mcp/tools.mjs +4 -967
  180. package/src/measure/corpus.mjs +150 -0
  181. package/src/measure/metrics.mjs +236 -0
  182. package/src/measure/records.mjs +211 -0
  183. package/src/measure/session.mjs +3 -2
  184. package/src/measure/stats.mjs +538 -0
  185. package/src/measure/task-schema.mjs +125 -0
  186. package/src/measure/tasks.mjs +69 -0
  187. package/src/memory/decide.mjs +6 -15
  188. package/src/memory/lesson-text.mjs +61 -0
  189. package/src/memory/lessons.mjs +29 -82
  190. package/src/name.mjs +7 -0
  191. package/src/node-version.mjs +37 -0
  192. package/src/output/index.mjs +209 -10
  193. package/src/packs/manage.mjs +32 -12
  194. package/src/packs.mjs +20 -2
  195. package/src/plan/index.mjs +1 -1
  196. package/src/plugins.mjs +22 -16
  197. package/src/prbody.mjs +7 -11
  198. package/src/projection/loaders.mjs +7 -692
  199. package/src/projection/lock.mjs +11 -1
  200. package/src/projection/manifest.mjs +29 -468
  201. package/src/projection/mdc.mjs +7 -8
  202. package/src/projection/project.mjs +126 -221
  203. package/src/projection/region.mjs +561 -0
  204. package/src/projection/rules.mjs +20 -550
  205. package/src/projection/surfaces.mjs +43 -210
  206. package/src/projection/targets.mjs +5 -5
  207. package/src/repo-root.mjs +31 -0
  208. package/src/resolve/eval.mjs +2 -1
  209. package/src/resolve/fingerprint.mjs +19 -22
  210. package/src/resolve/resolve.mjs +1 -41
  211. package/src/review/critic.mjs +10 -13
  212. package/src/run.mjs +98 -0
  213. package/src/secrets.mjs +14 -22
  214. package/src/state/events.mjs +129 -0
  215. package/src/status-snapshot.mjs +233 -0
  216. package/src/status.mjs +2 -6
  217. package/src/sys/git.mjs +82 -0
  218. package/src/sys/proc.mjs +151 -0
  219. package/src/testing/parsers.mjs +265 -0
  220. package/src/testing/patches.mjs +23 -0
  221. package/src/testing/paths.mjs +49 -0
  222. package/src/testing/runners.mjs +186 -0
  223. package/src/testing/vault.mjs +68 -0
  224. package/src/text/frontmatter.mjs +49 -0
  225. package/src/text/tokens.mjs +52 -0
  226. package/src/toolwords.mjs +3 -0
  227. package/src/util/fs.mjs +20 -0
  228. package/src/util/sort.mjs +12 -0
  229. package/src/workspace.mjs +58 -11
  230. package/src/write/plan.mjs +82 -0
  231. package/templates/plan/tracker.json +1 -0
  232. package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
  233. package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
  234. package/docs/decisions/README.md +0 -600
  235. package/src/bench/tasks.mjs +0 -36
@@ -0,0 +1,512 @@
1
+ // The MCP schemas and the two helpers every tool handler shares.
2
+ //
3
+ // Split out of tools.mjs, which crossed the repository ceiling carrying the tool list
4
+ // and the shapes it answers with. The shapes are data a client validates against; the
5
+ // handlers are the tools. A tool module imports both and defines neither.
6
+
7
+ import { loadRegistry } from '../graph/load.mjs';
8
+ import { provenanceDigest } from '../projection/project.mjs';
9
+ import { discover } from '../compare/discover.mjs';
10
+ import { buildRows, resolveConcurrency, summarise } from '../compare/row.mjs';
11
+ import { GATES } from '../hooks/gates.mjs';
12
+ import { checkRepo } from '../commands/sync.mjs';
13
+ import { diagnose, serialisedFindings } from '../commands/doctor.mjs';
14
+ import { rulesCatalogue, rulesWhyFacts } from '../commands/rules.mjs';
15
+ import { skillsBriefFacts, skillsFindFacts, skillsShowFacts, skillsWhyFacts } from '../commands/skills.mjs';
16
+ import { assembleBrief } from '../brief/assemble.mjs';
17
+ import { DEFAULT_BRIEF_BYTES } from '../brief/packet.mjs';
18
+ import { federationAccess, readFederation } from '../federation/config.mjs';
19
+ import { consult } from '../federation/query.mjs';
20
+ import { planNext, planDone } from '../plan/index.mjs';
21
+ import { buildPrBody } from '../prbody.mjs';
22
+ import { configPaths, readUserConfig, resolveWorkspace } from '../workspace.mjs';
23
+ import { measureSkill } from '../graph/load.mjs';
24
+ import { CLI } from '../name.mjs';
25
+
26
+ export const GATE_KINDS = new Map(Object.entries(GATES).map(([name, g]) => [name, g.kind]));
27
+
28
+ /** A schema with no required fields still needs the object declared. */
29
+ export const noArgs = { type: 'object', properties: {}, required: [] };
30
+
31
+ /**
32
+ * Output schemas, one per tool, describing what a SUCCESSFUL call returns.
33
+ *
34
+ * Every one of these tools is a reader: none writes, and none writes twice. The
35
+ * annotations say so, and the schemas say what comes back — because a client that can
36
+ * validate an answer against a declared shape catches a wrong answer at the boundary,
37
+ * while one handed a bag of JSON has to trust that the tool meant it.
38
+ *
39
+ * Properties are required unless the payload can legitimately omit them: `error` is
40
+ * absent when there is none, `body` is null when there is none, and a schema that
41
+ * required the exception would describe a shape that never occurs. `additionalProperties`
42
+ * is left open, which is what the 2026-07-28 revision permits and what a reader wants:
43
+ * these payloads are data, and a new field from a future release should not make an
44
+ * older client's validation fail.
45
+ */
46
+ /**
47
+ * The shape of a failure, exported because a client validating an answer needs it and
48
+ * because the test that checks every tool's answer needs it for the failures. Two
49
+ * copies of this schema would drift, and the copy that drifted would be the one nobody
50
+ * validates against - which is exactly how a remedy quietly stops being a string.
51
+ */
52
+ export const ERROR_SHAPE = {
53
+ type: 'object',
54
+ properties: { error: { type: 'string' }, remedy: { type: ['string', 'null'] } },
55
+ required: ['error'],
56
+ };
57
+
58
+ export const output = {
59
+ doctor: {
60
+ // The `doctor --json` document minus its envelope: the same findings, the
61
+ // same workspace answer, the same fingerprint. (Q15)
62
+ type: 'object',
63
+ properties: {
64
+ root: { type: 'string' },
65
+ stack: { type: 'array', items: { type: 'string' } },
66
+ facts: { type: 'array', items: { type: 'string' } },
67
+ factEvidence: { type: 'object', additionalProperties: { type: ['string', 'null'] } },
68
+ signals: { type: 'array', items: { type: 'string' } },
69
+ // One entry per signal, naming the file that proved it. The payload is a list of
70
+ // paths, not of records - `fingerprint` returns evidence as the paths themselves.
71
+ evidence: { type: 'array', items: { type: 'string' } },
72
+ manifest: { type: ['object', 'null'] },
73
+ error: { type: ['string', 'null'] },
74
+ workspace: {
75
+ type: 'object',
76
+ properties: {
77
+ // Null is the honest value for a repository that is not inside a workspace:
78
+ // there is no root to name, and naming the repository as one would be a claim
79
+ // about the reader's directory layout that nothing established.
80
+ root: { type: ['string', 'null'] },
81
+ source: { type: ['string', 'null'] },
82
+ detail: { type: ['string', 'null'] },
83
+ error: { type: ['string', 'null'] },
84
+ },
85
+ required: ['root', 'source'],
86
+ },
87
+ shared: { type: ['object', 'null'] },
88
+ findings: {
89
+ type: 'array',
90
+ items: {
91
+ type: 'object',
92
+ properties: {
93
+ id: { type: 'string' },
94
+ severity: { type: 'string' },
95
+ detail: { type: 'string' },
96
+ remedy: { type: ['string', 'null'] },
97
+ why: { type: ['string', 'null'] },
98
+ safe: { type: 'boolean' },
99
+ fixed: { type: 'boolean' },
100
+ },
101
+ required: ['id', 'severity', 'detail', 'safe', 'fixed'],
102
+ },
103
+ },
104
+ fix: {
105
+ type: 'object',
106
+ properties: {
107
+ requested: { type: 'boolean' },
108
+ applied: { type: 'boolean' },
109
+ fixed: { type: 'array', items: { type: 'string' } },
110
+ failed: { type: 'array', items: { type: 'object' } },
111
+ },
112
+ required: ['requested', 'applied', 'fixed', 'failed'],
113
+ },
114
+ remedy: { type: ['string', 'null'] },
115
+ },
116
+ required: ['root', 'workspace', 'stack', 'facts', 'signals', 'evidence', 'findings'],
117
+ },
118
+ skills_index: {
119
+ type: 'object',
120
+ properties: {
121
+ skills: { type: 'number' },
122
+ totalBytes: { type: 'number' },
123
+ deferred: { type: 'array', items: { type: 'string' } },
124
+ workspace: { type: 'object' },
125
+ verified: { type: 'boolean' },
126
+ entries: {
127
+ type: 'array',
128
+ items: {
129
+ type: 'object',
130
+ properties: {
131
+ name: { type: 'string' },
132
+ summary: { type: 'string' },
133
+ bytes: { type: 'number' },
134
+ stack: { type: 'array', items: { type: 'string' } },
135
+ deferred: { type: 'boolean' },
136
+ },
137
+ required: ['name', 'bytes'],
138
+ },
139
+ },
140
+ },
141
+ required: ['skills', 'totalBytes', 'entries'],
142
+ },
143
+ // The `skills brief --json` document minus its envelope, or the `--all` one.
144
+ // The CLI keeps its field names; this adopts them. (Q15)
145
+ skills_brief: {
146
+ anyOf: [
147
+ {
148
+ type: 'object',
149
+ properties: {
150
+ repo: {
151
+ type: 'object',
152
+ properties: {
153
+ root: { type: 'string' },
154
+ stack: { type: 'array', items: { type: 'string' } },
155
+ facts: { type: 'array', items: { type: 'string' } },
156
+ },
157
+ required: ['root', 'stack', 'facts'],
158
+ },
159
+ workspace: { type: 'string' },
160
+ task: { type: ['string', 'null'] },
161
+ budget: { type: 'number' },
162
+ inlineBytes: { type: 'number' },
163
+ resolved: {
164
+ type: 'array',
165
+ items: {
166
+ type: 'object',
167
+ properties: {
168
+ name: { type: 'string' },
169
+ bytes: { type: 'number' },
170
+ summary: { type: 'string' },
171
+ why: { type: 'array', items: { type: 'string' } },
172
+ },
173
+ required: ['name', 'bytes', 'why'],
174
+ },
175
+ },
176
+ deferred: {
177
+ type: 'array',
178
+ items: {
179
+ type: 'object',
180
+ properties: { name: { type: 'string' }, bytes: { type: 'number' }, reason: { type: 'string' } },
181
+ required: ['name', 'bytes', 'reason'],
182
+ },
183
+ },
184
+ excluded: {
185
+ type: 'array',
186
+ items: {
187
+ type: 'object',
188
+ properties: { name: { type: 'string' }, reason: { type: 'string' } },
189
+ required: ['name', 'reason'],
190
+ },
191
+ },
192
+ warning: { type: 'string' },
193
+ },
194
+ required: ['repo', 'budget', 'inlineBytes', 'resolved', 'deferred', 'excluded'],
195
+ },
196
+ {
197
+ type: 'object',
198
+ properties: {
199
+ all: { type: 'boolean', enum: [true] },
200
+ repoStack: { type: 'array', items: { type: 'string' } },
201
+ inline: { type: 'array', items: { type: 'string' } },
202
+ deferred: { type: 'array', items: { type: 'string' } },
203
+ warning: { type: 'string' },
204
+ },
205
+ required: ['all', 'repoStack', 'inline', 'deferred'],
206
+ },
207
+ ],
208
+ },
209
+ // The `skills show --json` document minus its envelope, plus the body text the
210
+ // command prints below the metadata. (Q15)
211
+ skills_show: {
212
+ type: 'object',
213
+ properties: {
214
+ name: { type: 'string' },
215
+ summary: { type: 'string' },
216
+ bytes: { type: 'number' },
217
+ capabilities: { type: 'array', items: { type: 'string' } },
218
+ stack: { type: 'array', items: { type: 'string' } },
219
+ keywords: { type: 'array', items: { type: 'string' } },
220
+ deferred: { type: 'boolean' },
221
+ requires: { type: 'array', items: { type: 'string' } },
222
+ variants: {
223
+ type: 'array',
224
+ items: {
225
+ type: 'object',
226
+ properties: { id: { type: 'string' }, stack: { type: 'array', items: { type: 'string' } }, bytes: { type: 'number' } },
227
+ required: ['id'],
228
+ },
229
+ },
230
+ sources: { type: 'array', items: { type: 'object' } },
231
+ aliasOf: { type: ['string', 'null'] },
232
+ variantOf: { type: ['string', 'null'] },
233
+ workspace: { type: 'string' },
234
+ body: { type: ['string', 'null'] },
235
+ bodyFrom: { type: 'string' },
236
+ bodyText: { type: ['string', 'null'] },
237
+ reason: { type: ['string', 'null'], description: 'why there is no body, when there is none' },
238
+ },
239
+ required: ['name', 'bytes', 'body', 'bodyFrom', 'bodyText', 'reason', 'requires', 'variants'],
240
+ },
241
+ skills_find: {
242
+ type: 'object',
243
+ properties: {
244
+ query: { type: 'string' },
245
+ results: {
246
+ type: 'array',
247
+ items: {
248
+ type: 'object',
249
+ properties: {
250
+ name: { type: 'string' },
251
+ score: { type: 'number' },
252
+ summary: { type: 'string' },
253
+ reasons: { type: 'array', items: { type: 'string' } },
254
+ },
255
+ required: ['name', 'score', 'reasons'],
256
+ },
257
+ },
258
+ },
259
+ required: ['query', 'results'],
260
+ },
261
+ // The `skills why --json` document minus its envelope. (Q15)
262
+ skills_why: {
263
+ type: 'object',
264
+ properties: {
265
+ skill: { type: 'string' },
266
+ score: { type: 'number' },
267
+ compatible: { type: 'boolean' },
268
+ gate: { type: 'string' },
269
+ repo: {
270
+ type: 'object',
271
+ properties: {
272
+ root: { type: 'string' },
273
+ stack: { type: 'array', items: { type: 'string' } },
274
+ facts: { type: 'array', items: { type: 'string' } },
275
+ evidence: { type: 'array', items: { type: 'string' } },
276
+ },
277
+ },
278
+ workspace: { type: 'object' },
279
+ paths: { type: 'array', items: { type: 'string' } },
280
+ taskTokens: { type: 'array', items: { type: 'string' } },
281
+ signals: { type: 'array', items: { type: 'string' } },
282
+ variants: { type: 'array', items: { type: 'object' } },
283
+ aliasOf: { type: ['string', 'null'] },
284
+ variantOf: { type: ['string', 'null'] },
285
+ requires: { type: 'array', items: { type: 'string' } },
286
+ },
287
+ required: ['skill', 'score', 'compatible', 'signals'],
288
+ },
289
+ rules_list: {
290
+ type: 'object',
291
+ properties: {
292
+ sha: { type: 'string' },
293
+ notes: { type: 'array', items: { type: 'string' } },
294
+ rules: {
295
+ type: 'array',
296
+ items: {
297
+ type: 'object',
298
+ properties: {
299
+ id: { type: ['string', 'null'] },
300
+ title: { type: ['string', 'null'] },
301
+ // A profile may name one signal or several, and the frontmatter keeps both
302
+ // spellings. Describing only the list would make a conforming rule look
303
+ // broken.
304
+ applies: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
305
+ origin: { type: 'string' },
306
+ source: { type: 'string' },
307
+ bytes: { type: 'number' },
308
+ adrs: { type: 'array', items: { type: 'string' } },
309
+ error: { type: ['string', 'null'] },
310
+ fallback: { type: 'boolean' },
311
+ selected: { type: ['boolean', 'null'] },
312
+ },
313
+ required: ['source', 'bytes', 'selected'],
314
+ },
315
+ },
316
+ },
317
+ required: ['sha', 'rules'],
318
+ },
319
+ // The `rules why --json` document minus its envelope, either direction. (Q15)
320
+ rules_why: {
321
+ type: 'object',
322
+ properties: {
323
+ direction: { type: 'string', enum: ['rule', 'decision'] },
324
+ id: { type: 'string' },
325
+ rule: { type: 'string' },
326
+ title: { type: ['string', 'null'] },
327
+ source: { type: 'string' },
328
+ origin: { type: 'string' },
329
+ adrs: { type: 'array', items: { type: 'object' } },
330
+ uncited: { type: 'boolean' },
331
+ others: { type: 'array', items: { type: 'string' } },
332
+ adr: { type: 'object' },
333
+ supersededBy: { type: ['object', 'null'] },
334
+ inForce: { type: 'object' },
335
+ rules: { type: 'array', items: { type: 'string' } },
336
+ },
337
+ required: ['direction', 'id'],
338
+ },
339
+ check: {
340
+ type: 'object',
341
+ properties: {
342
+ root: { type: 'string' },
343
+ ok: { type: 'boolean' },
344
+ findings: {
345
+ type: 'array',
346
+ items: {
347
+ type: 'object',
348
+ properties: {
349
+ file: { type: 'string' },
350
+ status: { type: 'string' },
351
+ detail: { type: ['string', 'null'] },
352
+ remedy: { type: ['string', 'null'] },
353
+ },
354
+ required: ['file', 'status'],
355
+ },
356
+ },
357
+ },
358
+ required: ['root', 'ok', 'findings'],
359
+ },
360
+ compare: {
361
+ type: 'object',
362
+ properties: {
363
+ root: { type: 'string' },
364
+ shape: { type: 'string', enum: ['repo', 'workspace'] },
365
+ expectedSha: { type: 'string' },
366
+ summary: { type: 'object' },
367
+ repos: { type: 'array', items: { type: 'object' } },
368
+ worktrees: { type: 'array', items: { type: 'object' } },
369
+ directories: { type: 'array', items: { type: 'string' } },
370
+ partial: { type: 'boolean' },
371
+ cursor: { type: ['string', 'null'] },
372
+ total: { type: 'number' },
373
+ },
374
+ required: ['root', 'shape', 'expectedSha', 'summary', 'repos'],
375
+ },
376
+ plan_next: {
377
+ type: 'object',
378
+ properties: {
379
+ root: { type: 'string' },
380
+ next: { type: ['object', 'null'] },
381
+ reason: { type: ['string', 'null'] },
382
+ },
383
+ required: ['root', 'next', 'reason'],
384
+ },
385
+ plan_done: {
386
+ type: 'object',
387
+ properties: {
388
+ root: { type: 'string' },
389
+ id: { type: 'string' },
390
+ doc: { type: 'string' },
391
+ line: { type: 'number' },
392
+ from: { type: 'string' },
393
+ to: { type: 'string' },
394
+ wouldChange: { type: 'boolean' },
395
+ dryRun: { type: 'boolean' },
396
+ remedy: { type: ['string', 'null'] },
397
+ },
398
+ required: ['root', 'id', 'wouldChange', 'dryRun'],
399
+ },
400
+ brief: {
401
+ type: 'object',
402
+ properties: {
403
+ root: { type: 'string' },
404
+ kind: { type: 'string' },
405
+ basis: { type: 'string' },
406
+ task: { type: 'string' },
407
+ scope: { type: 'array', items: { type: 'string' } },
408
+ budget: { type: 'number' },
409
+ bytes: { type: 'number' },
410
+ before: { type: 'number' },
411
+ floorHeld: { type: 'boolean' },
412
+ cut: { type: 'array', items: { type: 'string' } },
413
+ sections: { type: 'object', additionalProperties: { type: 'array', items: { type: 'string' } } },
414
+ text: { type: 'string' },
415
+ note: { type: ['string', 'null'] },
416
+ },
417
+ required: ['root', 'kind', 'basis', 'task', 'scope', 'budget', 'bytes', 'before', 'floorHeld', 'cut', 'sections', 'text'],
418
+ },
419
+ pr_body: {
420
+ type: 'object',
421
+ properties: {
422
+ root: { type: 'string' },
423
+ base: { type: ['string', 'null'] },
424
+ tree: { type: ['string', 'null'] },
425
+ verdict: { type: 'string' },
426
+ unreviewed: { type: 'array', items: { type: 'string' } },
427
+ paths: { type: 'array', items: { type: 'string' } },
428
+ decisions: {
429
+ type: 'array',
430
+ items: {
431
+ type: 'object',
432
+ properties: {
433
+ path: { type: 'string' },
434
+ id: { type: 'string' },
435
+ title: { type: 'string' },
436
+ status: { type: 'string' },
437
+ },
438
+ required: ['path', 'id', 'title', 'status'],
439
+ },
440
+ },
441
+ gates: {
442
+ type: 'object',
443
+ properties: {
444
+ declared: { type: 'array', items: { type: 'string' } },
445
+ evidenceMode: { type: ['string', 'null'] },
446
+ run: { type: ['object', 'null'] },
447
+ rulesOk: { type: ['boolean', 'null'] },
448
+ hooksOk: { type: ['boolean', 'null'] },
449
+ blocking: { type: 'number' },
450
+ warnings: { type: 'number' },
451
+ cached: { type: 'boolean' },
452
+ },
453
+ required: ['declared', 'blocking', 'warnings', 'cached'],
454
+ },
455
+ evidence: {
456
+ type: 'object',
457
+ properties: {
458
+ entries: { type: 'array', items: { type: 'object' } },
459
+ errors: { type: 'array', items: { type: 'string' } },
460
+ },
461
+ required: ['entries', 'errors'],
462
+ },
463
+ },
464
+ required: ['root', 'base', 'tree', 'verdict', 'unreviewed', 'decisions', 'gates', 'evidence', 'paths'],
465
+ },
466
+ federation_query: {
467
+ type: 'object',
468
+ properties: {
469
+ root: { type: 'string' },
470
+ query: { type: 'string' },
471
+ servers: { type: 'array', items: { type: 'string' } },
472
+ answers: {
473
+ type: 'array',
474
+ items: {
475
+ type: 'object',
476
+ properties: {
477
+ server: { type: 'string' },
478
+ tool: { type: 'string' },
479
+ query: { type: 'string' },
480
+ text: { type: 'string' },
481
+ },
482
+ required: ['server', 'tool', 'query', 'text'],
483
+ },
484
+ },
485
+ findings: { type: 'array', items: { type: 'string' } },
486
+ },
487
+ required: ['root', 'query', 'servers', 'answers', 'findings'],
488
+ },
489
+ /** The shape of every tool failure, which is a result and not a protocol error. */
490
+ error: ERROR_SHAPE,
491
+ };
492
+
493
+ export const rootArg = {
494
+ type: 'object',
495
+ properties: { root: { type: 'string', description: 'repository root; defaults to the editor cwd' } },
496
+ required: [],
497
+ };
498
+
499
+
500
+ export function rootOf(args) {
501
+ return typeof args?.root === 'string' && args.root.length > 0 ? args.root : process.cwd();
502
+ }
503
+
504
+ export async function registry(args) {
505
+ const { registry, errors, ok, verified, workspace } = await loadRegistry(null, args ?? {});
506
+ if (!ok) {
507
+ throw Object.assign(new Error(errors[0]), {
508
+ remedy: `run: ${CLI} graph lint`,
509
+ });
510
+ }
511
+ return { registry, verified, workspace };
512
+ }