simt-dev 0.2.1 → 0.2.2
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 +202 -0
- package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
- package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
- package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
- package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
- package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
- package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
- package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
- package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
- package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
- package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
- package/package.json +1 -1
- package/rules/core.md +1 -0
- package/rules/stacks/android.md +2 -0
- package/rules/stacks/cloudflare-workers.md +2 -0
- package/rules/stacks/connectiq.md +1 -0
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +2 -0
- package/rules/stacks/deno.md +2 -0
- package/rules/stacks/dotnet.md +2 -0
- package/rules/stacks/elixir.md +2 -0
- package/rules/stacks/go.md +2 -0
- package/rules/stacks/jvm.md +2 -0
- package/rules/stacks/node.md +2 -0
- package/rules/stacks/python.md +2 -0
- package/rules/stacks/react-native.md +2 -0
- package/rules/stacks/react-web.md +2 -0
- package/rules/stacks/ruby.md +2 -0
- package/rules/stacks/rust.md +2 -0
- package/rules/stacks/swift.md +2 -0
- package/rules/stacks/typescript.md +2 -0
- package/rules/stacks/uv.md +2 -0
- package/src/argv.mjs +19 -0
- package/src/commands/compare.mjs +15 -0
- package/src/commands/graph.mjs +45 -21
- package/src/commands/harness.mjs +150 -0
- package/src/commands/help.mjs +15 -1
- package/src/commands/index.mjs +43 -25
- package/src/commands/init.mjs +12 -8
- package/src/commands/mcp.mjs +20 -5
- package/src/commands/rules.mjs +42 -57
- package/src/commands/skills.mjs +67 -92
- package/src/commands/skillsinstall.mjs +17 -2
- package/src/commands/sync.mjs +62 -6
- package/src/commands/tokens.mjs +138 -0
- package/src/compare/row.mjs +73 -2
- package/src/graph/spec.mjs +227 -0
- package/src/graph/speccheck.mjs +68 -0
- package/src/hooks/dispatcher.mjs +35 -3
- package/src/mcp/schema.mjs +139 -0
- package/src/mcp/server.mjs +197 -20
- package/src/mcp/tools.mjs +288 -1
- package/src/measure/context.mjs +371 -0
- package/src/monorepo.mjs +339 -0
- package/src/output/index.mjs +78 -2
- package/src/packs.mjs +182 -0
- package/src/projection/manifest.mjs +88 -0
- package/src/projection/project.mjs +140 -9
- package/src/projection/rules.mjs +288 -20
- package/src/projection/surfaces.mjs +133 -15
- package/src/projection/targets.mjs +37 -0
package/src/mcp/tools.mjs
CHANGED
|
@@ -35,6 +35,283 @@ const GATE_KINDS = new Map(Object.entries(GATES).map(([name, g]) => [name, g.kin
|
|
|
35
35
|
|
|
36
36
|
/** A schema with no required fields still needs the object declared. */
|
|
37
37
|
const noArgs = { type: 'object', properties: {}, required: [] };
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Output schemas, one per tool, describing what a SUCCESSFUL call returns.
|
|
41
|
+
*
|
|
42
|
+
* Every one of these tools is a reader: none writes, and none writes twice. The
|
|
43
|
+
* annotations say so, and the schemas say what comes back — because a client that can
|
|
44
|
+
* validate an answer against a declared shape catches a wrong answer at the boundary,
|
|
45
|
+
* while one handed a bag of JSON has to trust that the tool meant it.
|
|
46
|
+
*
|
|
47
|
+
* Properties are required unless the payload can legitimately omit them: `error` is
|
|
48
|
+
* absent when there is none, `body` is null when there is none, and a schema that
|
|
49
|
+
* required the exception would describe a shape that never occurs. `additionalProperties`
|
|
50
|
+
* is left open, which is what the 2026-07-28 revision permits and what a reader wants:
|
|
51
|
+
* these payloads are data, and a new field from a future release should not make an
|
|
52
|
+
* older client's validation fail.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* The shape of a failure, exported because a client validating an answer needs it and
|
|
56
|
+
* because the test that checks every tool's answer needs it for the failures. Two
|
|
57
|
+
* copies of this schema would drift, and the copy that drifted would be the one nobody
|
|
58
|
+
* validates against - which is exactly how a remedy quietly stops being a string.
|
|
59
|
+
*/
|
|
60
|
+
export const ERROR_SHAPE = {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: { error: { type: 'string' }, remedy: { type: ['string', 'null'] } },
|
|
63
|
+
required: ['error'],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const output = {
|
|
67
|
+
doctor: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
root: { type: 'string' },
|
|
71
|
+
workspace: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
// Null is the honest value for a repository that is not inside a workspace:
|
|
75
|
+
// there is no root to name, and naming the repository as one would be a claim
|
|
76
|
+
// about the reader's directory layout that nothing established.
|
|
77
|
+
root: { type: ['string', 'null'] },
|
|
78
|
+
source: { type: ['string', 'null'] },
|
|
79
|
+
detail: { type: ['string', 'null'] },
|
|
80
|
+
},
|
|
81
|
+
required: ['root', 'source'],
|
|
82
|
+
},
|
|
83
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
84
|
+
facts: { type: 'object', additionalProperties: { type: ['string', 'null'] } },
|
|
85
|
+
signals: { type: 'array', items: { type: 'string' } },
|
|
86
|
+
// One entry per signal, naming the file that proved it. The payload is a list of
|
|
87
|
+
// paths, not of records - `fingerprint` returns evidence as the paths themselves.
|
|
88
|
+
evidence: { type: 'array', items: { type: 'string' } },
|
|
89
|
+
manifest: { type: ['object', 'null'] },
|
|
90
|
+
error: { type: ['string', 'null'] },
|
|
91
|
+
},
|
|
92
|
+
required: ['root', 'workspace', 'stack', 'facts', 'signals', 'evidence'],
|
|
93
|
+
},
|
|
94
|
+
skills_index: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
skills: { type: 'number' },
|
|
98
|
+
totalBytes: { type: 'number' },
|
|
99
|
+
deferred: { type: 'array', items: { type: 'string' } },
|
|
100
|
+
workspace: { type: 'object' },
|
|
101
|
+
verified: { type: 'boolean' },
|
|
102
|
+
entries: {
|
|
103
|
+
type: 'array',
|
|
104
|
+
items: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
name: { type: 'string' },
|
|
108
|
+
summary: { type: 'string' },
|
|
109
|
+
bytes: { type: 'number' },
|
|
110
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
111
|
+
deferred: { type: 'boolean' },
|
|
112
|
+
},
|
|
113
|
+
required: ['name', 'bytes'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: ['skills', 'totalBytes', 'entries'],
|
|
118
|
+
},
|
|
119
|
+
skills_brief: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
repo: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
root: { type: 'string' },
|
|
126
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
127
|
+
facts: { type: 'array', items: { type: 'string' } },
|
|
128
|
+
},
|
|
129
|
+
required: ['root', 'stack', 'facts'],
|
|
130
|
+
},
|
|
131
|
+
task: { type: ['string', 'null'] },
|
|
132
|
+
budget: { type: 'number' },
|
|
133
|
+
inlineBytes: { type: 'number' },
|
|
134
|
+
selected: {
|
|
135
|
+
type: 'array',
|
|
136
|
+
items: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
name: { type: 'string' },
|
|
140
|
+
bytes: { type: 'number' },
|
|
141
|
+
summary: { type: 'string' },
|
|
142
|
+
why: { type: 'array', items: { type: 'string' } },
|
|
143
|
+
},
|
|
144
|
+
required: ['name', 'bytes', 'why'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
deferred: {
|
|
148
|
+
type: 'array',
|
|
149
|
+
items: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: { name: { type: 'string' }, bytes: { type: 'number' }, reason: { type: 'string' } },
|
|
152
|
+
required: ['name', 'reason'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
notSelected: {
|
|
156
|
+
type: 'array',
|
|
157
|
+
items: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: { name: { type: 'string' }, reason: { type: 'string' } },
|
|
160
|
+
required: ['name', 'reason'],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
required: ['repo', 'budget', 'inlineBytes', 'selected', 'deferred', 'notSelected'],
|
|
165
|
+
},
|
|
166
|
+
skills_show: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
name: { type: 'string' },
|
|
170
|
+
summary: { type: 'string' },
|
|
171
|
+
bytes: { type: 'number' },
|
|
172
|
+
capabilities: { type: 'array', items: { type: 'string' } },
|
|
173
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
174
|
+
deferred: { type: 'boolean' },
|
|
175
|
+
source: { type: ['string', 'null'] },
|
|
176
|
+
readFrom: { type: 'string', enum: ['ok', 'empty', 'unavailable'] },
|
|
177
|
+
requires: { type: 'array', items: { type: 'string' } },
|
|
178
|
+
variants: {
|
|
179
|
+
type: 'array',
|
|
180
|
+
items: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: { id: { type: 'string' }, stack: { type: 'array', items: { type: 'string' } }, bytes: { type: 'number' } },
|
|
183
|
+
required: ['id'],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
maxBytes: { type: 'number' },
|
|
187
|
+
body: { type: ['string', 'null'] },
|
|
188
|
+
},
|
|
189
|
+
required: ['name', 'bytes', 'readFrom', 'requires', 'variants'],
|
|
190
|
+
},
|
|
191
|
+
skills_find: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
query: { type: 'string' },
|
|
195
|
+
results: {
|
|
196
|
+
type: 'array',
|
|
197
|
+
items: {
|
|
198
|
+
type: 'object',
|
|
199
|
+
properties: {
|
|
200
|
+
name: { type: 'string' },
|
|
201
|
+
score: { type: 'number' },
|
|
202
|
+
summary: { type: 'string' },
|
|
203
|
+
why: { type: 'array', items: { type: 'string' } },
|
|
204
|
+
},
|
|
205
|
+
required: ['name', 'score', 'why'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
required: ['query', 'results'],
|
|
210
|
+
},
|
|
211
|
+
skills_why: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
name: { type: 'string' },
|
|
215
|
+
score: { type: 'number' },
|
|
216
|
+
compatible: { type: 'boolean' },
|
|
217
|
+
reasons: { type: 'array', items: { type: 'string' } },
|
|
218
|
+
repo: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: { root: { type: 'string' }, signals: { type: 'array', items: { type: 'string' } } },
|
|
221
|
+
},
|
|
222
|
+
task: { type: ['string', 'null'] },
|
|
223
|
+
pathsSeen: { type: 'array', items: { type: 'string' } },
|
|
224
|
+
},
|
|
225
|
+
required: ['name', 'score', 'compatible', 'reasons'],
|
|
226
|
+
},
|
|
227
|
+
rules_list: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
properties: {
|
|
230
|
+
sha: { type: 'string' },
|
|
231
|
+
notes: { type: 'array', items: { type: 'string' } },
|
|
232
|
+
rules: {
|
|
233
|
+
type: 'array',
|
|
234
|
+
items: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
id: { type: ['string', 'null'] },
|
|
238
|
+
title: { type: ['string', 'null'] },
|
|
239
|
+
// A profile may name one signal or several, and the frontmatter keeps both
|
|
240
|
+
// spellings. Describing only the list would make a conforming rule look
|
|
241
|
+
// broken.
|
|
242
|
+
applies: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
|
|
243
|
+
origin: { type: 'string' },
|
|
244
|
+
source: { type: 'string' },
|
|
245
|
+
bytes: { type: 'number' },
|
|
246
|
+
adrs: { type: 'array', items: { type: 'string' } },
|
|
247
|
+
error: { type: ['string', 'null'] },
|
|
248
|
+
selected: { type: 'boolean' },
|
|
249
|
+
},
|
|
250
|
+
required: ['source', 'bytes', 'selected'],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ['sha', 'rules'],
|
|
255
|
+
},
|
|
256
|
+
rules_why: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
properties: {
|
|
259
|
+
direction: { type: 'string', enum: ['rule', 'decision'] },
|
|
260
|
+
id: { type: 'string' },
|
|
261
|
+
title: { type: ['string', 'null'] },
|
|
262
|
+
status: { type: 'string' },
|
|
263
|
+
source: { type: 'string' },
|
|
264
|
+
origin: { type: 'string' },
|
|
265
|
+
uncited: { type: 'boolean' },
|
|
266
|
+
readWith: { type: 'string' },
|
|
267
|
+
supersededBy: { type: ['string', 'null'] },
|
|
268
|
+
supersedes: { type: 'array', items: { type: 'string' } },
|
|
269
|
+
// A decision record may be partially superseded; the record carries the list of
|
|
270
|
+
// what it replaced, so this is a list and not a flag.
|
|
271
|
+
partial: { type: ['boolean', 'array'] },
|
|
272
|
+
decisions: { type: 'array', items: { type: 'object' } },
|
|
273
|
+
rules: { type: 'array', items: { type: 'string' } },
|
|
274
|
+
},
|
|
275
|
+
required: ['direction', 'id'],
|
|
276
|
+
},
|
|
277
|
+
check: {
|
|
278
|
+
type: 'object',
|
|
279
|
+
properties: {
|
|
280
|
+
root: { type: 'string' },
|
|
281
|
+
ok: { type: 'boolean' },
|
|
282
|
+
findings: {
|
|
283
|
+
type: 'array',
|
|
284
|
+
items: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
file: { type: 'string' },
|
|
288
|
+
status: { type: 'string' },
|
|
289
|
+
detail: { type: ['string', 'null'] },
|
|
290
|
+
remedy: { type: ['string', 'null'] },
|
|
291
|
+
},
|
|
292
|
+
required: ['file', 'status'],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
required: ['root', 'ok', 'findings'],
|
|
297
|
+
},
|
|
298
|
+
compare: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: {
|
|
301
|
+
root: { type: 'string' },
|
|
302
|
+
shape: { type: 'string', enum: ['repo', 'workspace'] },
|
|
303
|
+
expectedSha: { type: 'string' },
|
|
304
|
+
summary: { type: 'object' },
|
|
305
|
+
repos: { type: 'array', items: { type: 'object' } },
|
|
306
|
+
worktrees: { type: 'array', items: { type: 'object' } },
|
|
307
|
+
directories: { type: 'array', items: { type: 'string' } },
|
|
308
|
+
},
|
|
309
|
+
required: ['root', 'shape', 'expectedSha', 'summary', 'repos'],
|
|
310
|
+
},
|
|
311
|
+
/** The shape of every tool failure, which is a result and not a protocol error. */
|
|
312
|
+
error: ERROR_SHAPE,
|
|
313
|
+
};
|
|
314
|
+
|
|
38
315
|
const rootArg = {
|
|
39
316
|
type: 'object',
|
|
40
317
|
properties: { root: { type: 'string', description: 'repository root; defaults to the editor cwd' } },
|
|
@@ -57,6 +334,7 @@ async function registry(args) {
|
|
|
57
334
|
|
|
58
335
|
export const TOOLS = [
|
|
59
336
|
{
|
|
337
|
+
outputSchema: output.doctor,
|
|
60
338
|
name: 'doctor',
|
|
61
339
|
description:
|
|
62
340
|
'Classify a repository: its stack tags, the emitted facts with the file that proved each, and whether a simt-dev manifest is present. Call this first when you do not yet know what a repo is.',
|
|
@@ -79,6 +357,7 @@ export const TOOLS = [
|
|
|
79
357
|
},
|
|
80
358
|
|
|
81
359
|
{
|
|
360
|
+
outputSchema: output.skills_index,
|
|
82
361
|
name: 'skills_index',
|
|
83
362
|
description:
|
|
84
363
|
'The full skill catalogue: every skill with its summary, size, stack tags and whether it is deferred. Cheap enough to always afford, and the fallback when brief returns nothing useful.',
|
|
@@ -99,6 +378,7 @@ export const TOOLS = [
|
|
|
99
378
|
},
|
|
100
379
|
|
|
101
380
|
{
|
|
381
|
+
outputSchema: output.skills_brief,
|
|
102
382
|
name: 'skills_brief',
|
|
103
383
|
description:
|
|
104
384
|
'THE entry point. Resolve the skill set for a task in a repository. Returns what was selected, why each was selected, what was deferred, and what was NOT selected with the reason - the last part matters, because it is what lets you override the resolver instead of guessing at it.',
|
|
@@ -165,6 +445,7 @@ export const TOOLS = [
|
|
|
165
445
|
},
|
|
166
446
|
|
|
167
447
|
{
|
|
448
|
+
outputSchema: output.skills_show,
|
|
168
449
|
name: 'skills_show',
|
|
169
450
|
description: 'One skill body, with the metadata needed to decide whether to trust it: source path, which of the two locations it was read from, and its cited decisions.',
|
|
170
451
|
inputSchema: {
|
|
@@ -206,6 +487,7 @@ export const TOOLS = [
|
|
|
206
487
|
},
|
|
207
488
|
|
|
208
489
|
{
|
|
490
|
+
outputSchema: output.skills_find,
|
|
209
491
|
name: 'skills_find',
|
|
210
492
|
description: 'Raw search across capability, keyword and summary. Use when skills_brief has nothing and you suspect a skill exists.',
|
|
211
493
|
inputSchema: {
|
|
@@ -230,6 +512,7 @@ export const TOOLS = [
|
|
|
230
512
|
},
|
|
231
513
|
|
|
232
514
|
{
|
|
515
|
+
outputSchema: output.skills_why,
|
|
233
516
|
name: 'skills_why',
|
|
234
517
|
description:
|
|
235
518
|
'The signal chain for one skill in one repository: its score, the reasons behind it, whether the stack gate admits it at all, and what it would take to make it selected.',
|
|
@@ -263,6 +546,7 @@ export const TOOLS = [
|
|
|
263
546
|
},
|
|
264
547
|
|
|
265
548
|
{
|
|
549
|
+
outputSchema: output.rules_list,
|
|
266
550
|
name: 'rules_list',
|
|
267
551
|
description:
|
|
268
552
|
'The rule catalogue, and which rules a given repository actually receives. Pass `root` to get the selection rather than just the catalogue.',
|
|
@@ -300,9 +584,10 @@ export const TOOLS = [
|
|
|
300
584
|
},
|
|
301
585
|
|
|
302
586
|
{
|
|
587
|
+
outputSchema: output.rules_why,
|
|
303
588
|
name: 'rules_why',
|
|
304
589
|
description:
|
|
305
|
-
'Why a rule exists. Give a rule id for the decisions that settled it and whether each is still in force, or an ADR id for the rules that decision
|
|
590
|
+
'Why a rule exists. Give a rule id for the decisions that settled it and whether each is still in force, or an ADR id for the rules that decision repeats. A rule citing no decision reports that - it is a finding, not an empty answer.',
|
|
306
591
|
inputSchema: {
|
|
307
592
|
type: 'object',
|
|
308
593
|
properties: { what: { type: 'string', description: 'a rule id or ADR-NNN' } },
|
|
@@ -355,6 +640,7 @@ export const TOOLS = [
|
|
|
355
640
|
},
|
|
356
641
|
|
|
357
642
|
{
|
|
643
|
+
outputSchema: output.check,
|
|
358
644
|
name: 'check',
|
|
359
645
|
description:
|
|
360
646
|
'The gate for one repository: is its generated region current, unmanaged, drifted or malformed, and does any rule file lack an id. Read-only - it never repairs.',
|
|
@@ -371,6 +657,7 @@ export const TOOLS = [
|
|
|
371
657
|
},
|
|
372
658
|
|
|
373
659
|
{
|
|
660
|
+
outputSchema: output.compare,
|
|
374
661
|
name: 'compare',
|
|
375
662
|
description:
|
|
376
663
|
'The organisation-wide matrix: every repository under the root, its adoption, region state, pinned simt-dev SHA, hook routing and declared gates, plus the problems found. This is the command that replaces reconstructing the estate by hand.',
|