simt-dev 0.7.1 → 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.
- package/CHANGELOG.md +36 -1
- package/README.md +169 -80
- package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +3 -1
- package/rules/mdc/intake-questions.mdc +8 -0
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +7 -5
- package/src/argv.mjs +19 -0
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +155 -113
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +12 -26
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +17 -535
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +15 -1037
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +116 -971
- package/src/bench/run.mjs +164 -881
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +11 -13
- package/src/ci/dependabot.mjs +25 -55
- package/src/commands/agent.mjs +1 -9
- package/src/commands/agenthook.mjs +66 -35
- package/src/commands/bench.mjs +2 -2
- package/src/commands/ci.mjs +11 -13
- package/src/commands/docs.mjs +88 -0
- package/src/commands/gate.mjs +10 -13
- package/src/commands/harness.mjs +3 -3
- package/src/commands/help.mjs +44 -3
- package/src/commands/hooks.mjs +20 -20
- package/src/commands/ignore.mjs +11 -7
- package/src/commands/impact.mjs +4 -8
- package/src/commands/index.mjs +49 -9
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +122 -104
- package/src/commands/lease.mjs +67 -18
- package/src/commands/map.mjs +10 -5
- package/src/commands/mcp.mjs +2 -17
- package/src/commands/repoindex.mjs +8 -8
- package/src/commands/rules.mjs +8 -4
- package/src/commands/shared.mjs +12 -16
- package/src/commands/skills.mjs +5 -6
- package/src/commands/skillscompile.mjs +3 -3
- package/src/commands/skillsinstall.mjs +5 -4
- package/src/commands/slice.mjs +199 -64
- package/src/commands/spec.mjs +78 -62
- package/src/commands/status.mjs +24 -12
- package/src/commands/sync.mjs +14 -14
- package/src/commands/tests.mjs +12 -12
- package/src/commands/tokens.mjs +19 -24
- package/src/commands/upgrade.mjs +8 -4
- package/src/commands/why.mjs +6 -13
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/decisions/index.mjs +3 -2
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +2 -1
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +5 -3
- package/src/hooks/dispatcher.mjs +26 -32
- package/src/hooks/gates.mjs +12 -2
- package/src/hooks/resolve.mjs +2 -7
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +2 -11
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +16 -11
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +124 -17
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +49 -9
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -1005
- package/src/{bench → measure}/corpus.mjs +1 -1
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/output/index.mjs +122 -31
- package/src/packs/manage.mjs +15 -9
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/manifest.mjs +10 -435
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +16 -547
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +13 -1
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +0 -7
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +8 -12
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/{bench/process.mjs → sys/proc.mjs} +28 -5
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +1 -1
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +3 -1
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- 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
|
+
}
|
package/src/mcp/server.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import { createInterface } from 'node:readline';
|
|
|
24
24
|
import { CLI } from '../name.mjs';
|
|
25
25
|
import { getPrompt, PROMPTS } from './prompts.mjs';
|
|
26
26
|
import { RESOURCE_TEMPLATES, listResources, readResource } from './resources.mjs';
|
|
27
|
+
import { paginate } from './paginate.mjs';
|
|
27
28
|
import { check } from './schema.mjs';
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -207,11 +208,15 @@ export async function handle(request, tools, { packageVersion, state, root, sign
|
|
|
207
208
|
}
|
|
208
209
|
return reply({});
|
|
209
210
|
|
|
210
|
-
case 'tools/list':
|
|
211
|
+
case 'tools/list': {
|
|
212
|
+
const page = paginate(tools, params?.cursor);
|
|
213
|
+
if (page === null) return badCursor(id);
|
|
211
214
|
return reply({
|
|
212
|
-
tools:
|
|
215
|
+
tools: page.items.map((t) => describe(t, v)),
|
|
216
|
+
...nextPage(page),
|
|
213
217
|
...(cacheable(v) ? LIST_CACHE : {}),
|
|
214
218
|
});
|
|
219
|
+
}
|
|
215
220
|
|
|
216
221
|
case 'tools/call': {
|
|
217
222
|
const name = params?.name;
|
|
@@ -265,16 +270,22 @@ export async function handle(request, tools, { packageVersion, state, root, sign
|
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
|
|
268
|
-
case 'resources/list':
|
|
273
|
+
case 'resources/list': {
|
|
274
|
+
const page = paginate(await listResources(root), params?.cursor);
|
|
275
|
+
if (page === null) return badCursor(id);
|
|
269
276
|
return reply(
|
|
270
|
-
cacheable(v) ? { resources:
|
|
277
|
+
cacheable(v) ? { resources: page.items, ...nextPage(page), ...LIST_CACHE } : { resources: page.items, ...nextPage(page) },
|
|
271
278
|
);
|
|
272
|
-
|
|
279
|
+
}
|
|
280
|
+
case 'resources/templates/list': {
|
|
281
|
+
const page = paginate(RESOURCE_TEMPLATES, params?.cursor);
|
|
282
|
+
if (page === null) return badCursor(id);
|
|
273
283
|
return reply(
|
|
274
284
|
cacheable(v)
|
|
275
|
-
? { resourceTemplates:
|
|
276
|
-
: { resourceTemplates:
|
|
285
|
+
? { resourceTemplates: page.items, ...nextPage(page), ...LIST_CACHE }
|
|
286
|
+
: { resourceTemplates: page.items, ...nextPage(page) },
|
|
277
287
|
);
|
|
288
|
+
}
|
|
278
289
|
case 'resources/read': {
|
|
279
290
|
// The code for a resource that is not there CHANGED, and a server that answers
|
|
280
291
|
// with one code to both revisions is a server that is wrong to whichever half of
|
|
@@ -288,10 +299,13 @@ export async function handle(request, tools, { packageVersion, state, root, sign
|
|
|
288
299
|
cacheable(v) ? { contents: [found], ...LIST_CACHE } : { contents: [found] },
|
|
289
300
|
);
|
|
290
301
|
}
|
|
291
|
-
case 'prompts/list':
|
|
302
|
+
case 'prompts/list': {
|
|
303
|
+
const page = paginate(PROMPTS, params?.cursor);
|
|
304
|
+
if (page === null) return badCursor(id);
|
|
292
305
|
return reply(
|
|
293
|
-
cacheable(v) ? { prompts:
|
|
306
|
+
cacheable(v) ? { prompts: page.items, ...nextPage(page), ...LIST_CACHE } : { prompts: page.items, ...nextPage(page) },
|
|
294
307
|
);
|
|
308
|
+
}
|
|
295
309
|
case 'prompts/get': {
|
|
296
310
|
const name = params?.name;
|
|
297
311
|
// `getPrompt` returns null only for a prompt this server does not have: its
|
|
@@ -304,6 +318,16 @@ export async function handle(request, tools, { packageVersion, state, root, sign
|
|
|
304
318
|
if (found === null) return error(id, RPC.METHOD, `no prompt named "${String(name)}"; call prompts/list first`);
|
|
305
319
|
return reply(found);
|
|
306
320
|
}
|
|
321
|
+
case 'resources/subscribe':
|
|
322
|
+
case 'resources/unsubscribe':
|
|
323
|
+
// A named refusal, not `unknown method`. The capability block above says `resources:
|
|
324
|
+
// {}` - no subscriptions - so a well-behaved client never asks; one that asks anyway
|
|
325
|
+
// is asking a protocol question and deserves the reason rather than a message that
|
|
326
|
+
// reads as a typo and sends it hunting for a spelling.
|
|
327
|
+
return isNotification
|
|
328
|
+
? null
|
|
329
|
+
: error(id, RPC.METHOD, `${method} is not supported: this server has no change source; resources/list is always current`);
|
|
330
|
+
|
|
307
331
|
default:
|
|
308
332
|
return isNotification ? null : error(id, RPC.METHOD, `unknown method "${method}"`);
|
|
309
333
|
}
|
|
@@ -383,6 +407,22 @@ function decorate(value, version, { packageVersion }) {
|
|
|
383
407
|
};
|
|
384
408
|
}
|
|
385
409
|
|
|
410
|
+
/** The cursor field, present only when there is a next page. */
|
|
411
|
+
function nextPage(page) {
|
|
412
|
+
return page.nextCursor === null ? {} : { nextCursor: page.nextCursor };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* A cursor this server did not issue.
|
|
417
|
+
*
|
|
418
|
+
* A protocol error, not an empty page: an empty page reads as "the list ended", which is
|
|
419
|
+
* a different fact from "your cursor is stale" and leads the client to show a partial
|
|
420
|
+
* estate as a complete one.
|
|
421
|
+
*/
|
|
422
|
+
function badCursor(id) {
|
|
423
|
+
return error(id, RPC.INVALID_PARAMS, 'invalid cursor: omit it to start at the first page');
|
|
424
|
+
}
|
|
425
|
+
|
|
386
426
|
function describe(tool, version) {
|
|
387
427
|
const entry = {
|
|
388
428
|
name: tool.name,
|