simt-dev 0.6.2 → 0.6.3
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 +48 -1
- package/README.md +44 -15
- package/bin/simt.mjs +19 -6
- package/docs/decisions/ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md +94 -0
- package/docs/decisions/ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md +95 -0
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +49 -0
- package/docs/decisions/ADR-103-co-projected-duplicates-merge-down.md +74 -0
- package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md +76 -0
- package/docs/decisions/ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md +44 -0
- package/docs/decisions/README.md +44 -0
- package/package.json +1 -1
- package/rules/mdc/preview-postflight.mdc +5 -6
- package/rules/stacks/android.md +7 -8
- package/rules/stacks/cloudflare-workers.md +1 -1
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/jvm.md +1 -1
- package/rules/stacks/react-native.md +3 -5
- package/rules/stacks/react-web.md +9 -9
- package/src/agenthooks/decide.mjs +101 -10
- package/src/agenthooks/policy.mjs +81 -5
- package/src/agenthooks/shell.mjs +523 -0
- package/src/argv.mjs +54 -9
- package/src/commands/agenthook.mjs +25 -3
- package/src/commands/ci.mjs +28 -3
- package/src/commands/completions.mjs +27 -0
- package/src/commands/decide.mjs +14 -1
- package/src/commands/doctor.mjs +367 -0
- package/src/commands/explain.mjs +52 -0
- package/src/commands/help.mjs +66 -17
- package/src/commands/hooks.mjs +208 -60
- package/src/commands/ignore.mjs +152 -0
- package/src/commands/index.mjs +54 -100
- package/src/commands/init.mjs +82 -12
- package/src/commands/lease.mjs +136 -0
- package/src/commands/lessons.mjs +12 -0
- package/src/commands/mcp.mjs +2 -2
- package/src/commands/pack.mjs +9 -0
- package/src/commands/plan.mjs +13 -1
- package/src/commands/plugin.mjs +16 -1
- package/src/commands/prbody.mjs +45 -0
- package/src/commands/repoindex.mjs +3 -0
- package/src/commands/rules.mjs +145 -105
- package/src/commands/shared.mjs +36 -5
- package/src/commands/skills.mjs +293 -205
- package/src/commands/skillsinstall.mjs +100 -30
- package/src/commands/slice.mjs +353 -0
- package/src/commands/status.mjs +62 -0
- package/src/commands/sync.mjs +110 -15
- package/src/commands/undo.mjs +106 -0
- package/src/commands/upgrade.mjs +70 -8
- package/src/completions.mjs +191 -0
- package/src/diff.mjs +254 -0
- package/src/explain.mjs +258 -0
- package/src/federation/config.mjs +44 -0
- package/src/graph/load.mjs +14 -5
- package/src/hooks/dispatcher.mjs +110 -0
- package/src/hooks/gates.mjs +14 -3
- package/src/hooks/resolve.mjs +14 -0
- package/src/ignore.mjs +133 -0
- package/src/index/persist.mjs +66 -9
- package/src/journal.mjs +326 -0
- package/src/lease.mjs +113 -0
- package/src/mcp/server.mjs +15 -1
- package/src/mcp/tools.mjs +296 -256
- package/src/memory/decide.mjs +6 -1
- package/src/memory/lessons.mjs +12 -0
- package/src/output/index.mjs +82 -12
- package/src/package-version.mjs +49 -1
- package/src/packs/manage.mjs +13 -4
- package/src/plan/index.mjs +6 -0
- package/src/plugins.mjs +27 -6
- package/src/prbody.mjs +251 -0
- package/src/projection/manifest.mjs +13 -2
- package/src/projection/shared.mjs +4 -1
- package/src/secrets.mjs +231 -0
- package/src/status.mjs +152 -0
package/src/mcp/tools.mjs
CHANGED
|
@@ -5,34 +5,36 @@
|
|
|
5
5
|
// a diff by a human before they touch anything (ADR-001, ADR-004). A model can call a
|
|
6
6
|
// tool; a model cannot review a diff it never showed you.
|
|
7
7
|
//
|
|
8
|
-
// The tools
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
8
|
+
// The tools return the same facts the commands render, by calling the command
|
|
9
|
+
// modules' core functions rather than rebuilding the answers. The commands render
|
|
10
|
+
// for terminals, these return JSON for a model, and the two copies had already
|
|
11
|
+
// drifted: MCP `check` reported 3 findings where the command reports 77, the MCP
|
|
12
|
+
// `doctor` was a fingerprint with none of the findings, `rules_list` missed the
|
|
13
|
+
// per-surface rules, and the skills payloads disagreed on field names (Q15). The
|
|
14
|
+
// CLI keeps its field names; the tools adopt them. What is shared is the whole
|
|
15
|
+
// answer, because two answers to one question is what ADR-035 rules out.
|
|
15
16
|
//
|
|
16
17
|
// Bodies are never inlined by `skills_brief`. Tier 1 is a set of pointers and reasons;
|
|
17
18
|
// the body is a separate call. A 384 KB reference inlined into a tool result is the exact
|
|
18
19
|
// failure progressive disclosure exists to prevent, and a transport makes it easier to
|
|
19
20
|
// cause by accident because the caller cannot see the size until it has paid for it.
|
|
20
21
|
|
|
21
|
-
import { fingerprint } from '../resolve/fingerprint.mjs';
|
|
22
22
|
import { loadRegistry } from '../graph/load.mjs';
|
|
23
|
-
import {
|
|
24
|
-
import { loadRules, select, checkTargets, provenanceDigest } from '../projection/project.mjs';
|
|
25
|
-
import { loadDecisions, byId, citations } from '../decisions/index.mjs';
|
|
23
|
+
import { provenanceDigest } from '../projection/project.mjs';
|
|
26
24
|
import { discover } from '../compare/discover.mjs';
|
|
27
25
|
import { buildRow, summarise } from '../compare/row.mjs';
|
|
28
26
|
import { GATES } from '../hooks/gates.mjs';
|
|
29
|
-
import {
|
|
27
|
+
import { checkRepo } from '../commands/sync.mjs';
|
|
28
|
+
import { diagnose, serialisedFindings } from '../commands/doctor.mjs';
|
|
29
|
+
import { rulesCatalogue, rulesWhyFacts } from '../commands/rules.mjs';
|
|
30
|
+
import { skillsBriefFacts, skillsFindFacts, skillsShowFacts, skillsWhyFacts } from '../commands/skills.mjs';
|
|
30
31
|
import { assembleBrief } from '../brief/assemble.mjs';
|
|
31
32
|
import { DEFAULT_BRIEF_BYTES } from '../brief/packet.mjs';
|
|
32
|
-
import { readFederation } from '../federation/config.mjs';
|
|
33
|
+
import { federationAccess, readFederation } from '../federation/config.mjs';
|
|
33
34
|
import { consult } from '../federation/query.mjs';
|
|
34
35
|
import { planNext, planDone } from '../plan/index.mjs';
|
|
35
|
-
import {
|
|
36
|
+
import { buildPrBody } from '../prbody.mjs';
|
|
37
|
+
import { configPaths, readUserConfig, resolveWorkspace } from '../workspace.mjs';
|
|
36
38
|
import { measureSkill } from '../graph/load.mjs';
|
|
37
39
|
import { CLI } from '../name.mjs';
|
|
38
40
|
|
|
@@ -70,9 +72,20 @@ export const ERROR_SHAPE = {
|
|
|
70
72
|
|
|
71
73
|
const output = {
|
|
72
74
|
doctor: {
|
|
75
|
+
// The `doctor --json` document minus its envelope: the same findings, the
|
|
76
|
+
// same workspace answer, the same fingerprint. (Q15)
|
|
73
77
|
type: 'object',
|
|
74
78
|
properties: {
|
|
75
79
|
root: { type: 'string' },
|
|
80
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
81
|
+
facts: { type: 'array', items: { type: 'string' } },
|
|
82
|
+
factEvidence: { type: 'object', additionalProperties: { type: ['string', 'null'] } },
|
|
83
|
+
signals: { type: 'array', items: { type: 'string' } },
|
|
84
|
+
// One entry per signal, naming the file that proved it. The payload is a list of
|
|
85
|
+
// paths, not of records - `fingerprint` returns evidence as the paths themselves.
|
|
86
|
+
evidence: { type: 'array', items: { type: 'string' } },
|
|
87
|
+
manifest: { type: ['object', 'null'] },
|
|
88
|
+
error: { type: ['string', 'null'] },
|
|
76
89
|
workspace: {
|
|
77
90
|
type: 'object',
|
|
78
91
|
properties: {
|
|
@@ -82,19 +95,40 @@ const output = {
|
|
|
82
95
|
root: { type: ['string', 'null'] },
|
|
83
96
|
source: { type: ['string', 'null'] },
|
|
84
97
|
detail: { type: ['string', 'null'] },
|
|
98
|
+
error: { type: ['string', 'null'] },
|
|
85
99
|
},
|
|
86
100
|
required: ['root', 'source'],
|
|
87
101
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
shared: { type: ['object', 'null'] },
|
|
103
|
+
findings: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
id: { type: 'string' },
|
|
109
|
+
severity: { type: 'string' },
|
|
110
|
+
detail: { type: 'string' },
|
|
111
|
+
remedy: { type: ['string', 'null'] },
|
|
112
|
+
why: { type: ['string', 'null'] },
|
|
113
|
+
safe: { type: 'boolean' },
|
|
114
|
+
fixed: { type: 'boolean' },
|
|
115
|
+
},
|
|
116
|
+
required: ['id', 'severity', 'detail', 'safe', 'fixed'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
fix: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
requested: { type: 'boolean' },
|
|
123
|
+
applied: { type: 'boolean' },
|
|
124
|
+
fixed: { type: 'array', items: { type: 'string' } },
|
|
125
|
+
failed: { type: 'array', items: { type: 'object' } },
|
|
126
|
+
},
|
|
127
|
+
required: ['requested', 'applied', 'fixed', 'failed'],
|
|
128
|
+
},
|
|
129
|
+
remedy: { type: ['string', 'null'] },
|
|
96
130
|
},
|
|
97
|
-
required: ['root', 'workspace', 'stack', 'facts', 'signals', 'evidence'],
|
|
131
|
+
required: ['root', 'workspace', 'stack', 'facts', 'signals', 'evidence', 'findings'],
|
|
98
132
|
},
|
|
99
133
|
skills_index: {
|
|
100
134
|
type: 'object',
|
|
@@ -121,53 +155,74 @@ const output = {
|
|
|
121
155
|
},
|
|
122
156
|
required: ['skills', 'totalBytes', 'entries'],
|
|
123
157
|
},
|
|
158
|
+
// The `skills brief --json` document minus its envelope, or the `--all` one.
|
|
159
|
+
// The CLI keeps its field names; this adopts them. (Q15)
|
|
124
160
|
skills_brief: {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
repo: {
|
|
161
|
+
anyOf: [
|
|
162
|
+
{
|
|
128
163
|
type: 'object',
|
|
129
164
|
properties: {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
inlineBytes: { type: 'number' },
|
|
139
|
-
selected: {
|
|
140
|
-
type: 'array',
|
|
141
|
-
items: {
|
|
142
|
-
type: 'object',
|
|
143
|
-
properties: {
|
|
144
|
-
name: { type: 'string' },
|
|
145
|
-
bytes: { type: 'number' },
|
|
146
|
-
summary: { type: 'string' },
|
|
147
|
-
why: { type: 'array', items: { type: 'string' } },
|
|
165
|
+
repo: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
root: { type: 'string' },
|
|
169
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
170
|
+
facts: { type: 'array', items: { type: 'string' } },
|
|
171
|
+
},
|
|
172
|
+
required: ['root', 'stack', 'facts'],
|
|
148
173
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
workspace: { type: 'string' },
|
|
175
|
+
task: { type: ['string', 'null'] },
|
|
176
|
+
budget: { type: 'number' },
|
|
177
|
+
inlineBytes: { type: 'number' },
|
|
178
|
+
resolved: {
|
|
179
|
+
type: 'array',
|
|
180
|
+
items: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
name: { type: 'string' },
|
|
184
|
+
bytes: { type: 'number' },
|
|
185
|
+
summary: { type: 'string' },
|
|
186
|
+
why: { type: 'array', items: { type: 'string' } },
|
|
187
|
+
},
|
|
188
|
+
required: ['name', 'bytes', 'why'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
deferred: {
|
|
192
|
+
type: 'array',
|
|
193
|
+
items: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: { name: { type: 'string' }, bytes: { type: 'number' }, reason: { type: 'string' } },
|
|
196
|
+
required: ['name', 'bytes', 'reason'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
excluded: {
|
|
200
|
+
type: 'array',
|
|
201
|
+
items: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
properties: { name: { type: 'string' }, reason: { type: 'string' } },
|
|
204
|
+
required: ['name', 'reason'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
warning: { type: 'string' },
|
|
158
208
|
},
|
|
209
|
+
required: ['repo', 'budget', 'inlineBytes', 'resolved', 'deferred', 'excluded'],
|
|
159
210
|
},
|
|
160
|
-
|
|
161
|
-
type: '
|
|
162
|
-
|
|
163
|
-
type: '
|
|
164
|
-
|
|
165
|
-
|
|
211
|
+
{
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
all: { type: 'boolean', enum: [true] },
|
|
215
|
+
repoStack: { type: 'array', items: { type: 'string' } },
|
|
216
|
+
inline: { type: 'array', items: { type: 'string' } },
|
|
217
|
+
deferred: { type: 'array', items: { type: 'string' } },
|
|
218
|
+
warning: { type: 'string' },
|
|
166
219
|
},
|
|
220
|
+
required: ['all', 'repoStack', 'inline', 'deferred'],
|
|
167
221
|
},
|
|
168
|
-
|
|
169
|
-
required: ['repo', 'budget', 'inlineBytes', 'selected', 'deferred', 'notSelected'],
|
|
222
|
+
],
|
|
170
223
|
},
|
|
224
|
+
// The `skills show --json` document minus its envelope, plus the body text the
|
|
225
|
+
// command prints below the metadata. (Q15)
|
|
171
226
|
skills_show: {
|
|
172
227
|
type: 'object',
|
|
173
228
|
properties: {
|
|
@@ -176,9 +231,8 @@ const output = {
|
|
|
176
231
|
bytes: { type: 'number' },
|
|
177
232
|
capabilities: { type: 'array', items: { type: 'string' } },
|
|
178
233
|
stack: { type: 'array', items: { type: 'string' } },
|
|
234
|
+
keywords: { type: 'array', items: { type: 'string' } },
|
|
179
235
|
deferred: { type: 'boolean' },
|
|
180
|
-
source: { type: ['string', 'null'] },
|
|
181
|
-
readFrom: { type: 'string', enum: ['ok', 'empty', 'unavailable'] },
|
|
182
236
|
requires: { type: 'array', items: { type: 'string' } },
|
|
183
237
|
variants: {
|
|
184
238
|
type: 'array',
|
|
@@ -188,10 +242,15 @@ const output = {
|
|
|
188
242
|
required: ['id'],
|
|
189
243
|
},
|
|
190
244
|
},
|
|
191
|
-
|
|
245
|
+
sources: { type: 'array', items: { type: 'object' } },
|
|
246
|
+
aliasOf: { type: ['string', 'null'] },
|
|
247
|
+
variantOf: { type: ['string', 'null'] },
|
|
248
|
+
workspace: { type: 'string' },
|
|
192
249
|
body: { type: ['string', 'null'] },
|
|
250
|
+
bodyFrom: { type: 'string' },
|
|
251
|
+
bodyText: { type: 'string' },
|
|
193
252
|
},
|
|
194
|
-
required: ['name', 'bytes', '
|
|
253
|
+
required: ['name', 'bytes', 'bodyFrom', 'requires', 'variants'],
|
|
195
254
|
},
|
|
196
255
|
skills_find: {
|
|
197
256
|
type: 'object',
|
|
@@ -205,29 +264,41 @@ const output = {
|
|
|
205
264
|
name: { type: 'string' },
|
|
206
265
|
score: { type: 'number' },
|
|
207
266
|
summary: { type: 'string' },
|
|
208
|
-
|
|
267
|
+
reasons: { type: 'array', items: { type: 'string' } },
|
|
209
268
|
},
|
|
210
|
-
required: ['name', 'score', '
|
|
269
|
+
required: ['name', 'score', 'reasons'],
|
|
211
270
|
},
|
|
212
271
|
},
|
|
213
272
|
},
|
|
214
273
|
required: ['query', 'results'],
|
|
215
274
|
},
|
|
275
|
+
// The `skills why --json` document minus its envelope. (Q15)
|
|
216
276
|
skills_why: {
|
|
217
277
|
type: 'object',
|
|
218
278
|
properties: {
|
|
219
|
-
|
|
279
|
+
skill: { type: 'string' },
|
|
220
280
|
score: { type: 'number' },
|
|
221
281
|
compatible: { type: 'boolean' },
|
|
222
|
-
|
|
282
|
+
gate: { type: 'string' },
|
|
223
283
|
repo: {
|
|
224
284
|
type: 'object',
|
|
225
|
-
properties: {
|
|
285
|
+
properties: {
|
|
286
|
+
root: { type: 'string' },
|
|
287
|
+
stack: { type: 'array', items: { type: 'string' } },
|
|
288
|
+
facts: { type: 'array', items: { type: 'string' } },
|
|
289
|
+
evidence: { type: 'array', items: { type: 'string' } },
|
|
290
|
+
},
|
|
226
291
|
},
|
|
227
|
-
|
|
228
|
-
|
|
292
|
+
workspace: { type: 'object' },
|
|
293
|
+
paths: { type: 'array', items: { type: 'string' } },
|
|
294
|
+
taskTokens: { type: 'array', items: { type: 'string' } },
|
|
295
|
+
signals: { type: 'array', items: { type: 'string' } },
|
|
296
|
+
variants: { type: 'array', items: { type: 'object' } },
|
|
297
|
+
aliasOf: { type: ['string', 'null'] },
|
|
298
|
+
variantOf: { type: ['string', 'null'] },
|
|
299
|
+
requires: { type: 'array', items: { type: 'string' } },
|
|
229
300
|
},
|
|
230
|
-
required: ['
|
|
301
|
+
required: ['skill', 'score', 'compatible', 'signals'],
|
|
231
302
|
},
|
|
232
303
|
rules_list: {
|
|
233
304
|
type: 'object',
|
|
@@ -250,7 +321,8 @@ const output = {
|
|
|
250
321
|
bytes: { type: 'number' },
|
|
251
322
|
adrs: { type: 'array', items: { type: 'string' } },
|
|
252
323
|
error: { type: ['string', 'null'] },
|
|
253
|
-
|
|
324
|
+
fallback: { type: 'boolean' },
|
|
325
|
+
selected: { type: ['boolean', 'null'] },
|
|
254
326
|
},
|
|
255
327
|
required: ['source', 'bytes', 'selected'],
|
|
256
328
|
},
|
|
@@ -258,23 +330,22 @@ const output = {
|
|
|
258
330
|
},
|
|
259
331
|
required: ['sha', 'rules'],
|
|
260
332
|
},
|
|
333
|
+
// The `rules why --json` document minus its envelope, either direction. (Q15)
|
|
261
334
|
rules_why: {
|
|
262
335
|
type: 'object',
|
|
263
336
|
properties: {
|
|
264
337
|
direction: { type: 'string', enum: ['rule', 'decision'] },
|
|
265
338
|
id: { type: 'string' },
|
|
339
|
+
rule: { type: 'string' },
|
|
266
340
|
title: { type: ['string', 'null'] },
|
|
267
|
-
status: { type: 'string' },
|
|
268
341
|
source: { type: 'string' },
|
|
269
342
|
origin: { type: 'string' },
|
|
343
|
+
adrs: { type: 'array', items: { type: 'object' } },
|
|
270
344
|
uncited: { type: 'boolean' },
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// what it replaced, so this is a list and not a flag.
|
|
276
|
-
partial: { type: ['boolean', 'array'] },
|
|
277
|
-
decisions: { type: 'array', items: { type: 'object' } },
|
|
345
|
+
others: { type: 'array', items: { type: 'string' } },
|
|
346
|
+
adr: { type: 'object' },
|
|
347
|
+
supersededBy: { type: ['object', 'null'] },
|
|
348
|
+
inForce: { type: 'object' },
|
|
278
349
|
rules: { type: 'array', items: { type: 'string' } },
|
|
279
350
|
},
|
|
280
351
|
required: ['direction', 'id'],
|
|
@@ -355,6 +426,53 @@ const output = {
|
|
|
355
426
|
},
|
|
356
427
|
required: ['root', 'kind', 'basis', 'task', 'scope', 'budget', 'bytes', 'before', 'floorHeld', 'cut', 'sections', 'text'],
|
|
357
428
|
},
|
|
429
|
+
pr_body: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
root: { type: 'string' },
|
|
433
|
+
base: { type: ['string', 'null'] },
|
|
434
|
+
tree: { type: ['string', 'null'] },
|
|
435
|
+
verdict: { type: 'string' },
|
|
436
|
+
unreviewed: { type: 'array', items: { type: 'string' } },
|
|
437
|
+
paths: { type: 'array', items: { type: 'string' } },
|
|
438
|
+
decisions: {
|
|
439
|
+
type: 'array',
|
|
440
|
+
items: {
|
|
441
|
+
type: 'object',
|
|
442
|
+
properties: {
|
|
443
|
+
path: { type: 'string' },
|
|
444
|
+
id: { type: 'string' },
|
|
445
|
+
title: { type: 'string' },
|
|
446
|
+
status: { type: 'string' },
|
|
447
|
+
},
|
|
448
|
+
required: ['path', 'id', 'title', 'status'],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
gates: {
|
|
452
|
+
type: 'object',
|
|
453
|
+
properties: {
|
|
454
|
+
declared: { type: 'array', items: { type: 'string' } },
|
|
455
|
+
evidenceMode: { type: ['string', 'null'] },
|
|
456
|
+
run: { type: ['object', 'null'] },
|
|
457
|
+
rulesOk: { type: ['boolean', 'null'] },
|
|
458
|
+
hooksOk: { type: ['boolean', 'null'] },
|
|
459
|
+
blocking: { type: 'number' },
|
|
460
|
+
warnings: { type: 'number' },
|
|
461
|
+
cached: { type: 'boolean' },
|
|
462
|
+
},
|
|
463
|
+
required: ['declared', 'blocking', 'warnings', 'cached'],
|
|
464
|
+
},
|
|
465
|
+
evidence: {
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
entries: { type: 'array', items: { type: 'object' } },
|
|
469
|
+
errors: { type: 'array', items: { type: 'string' } },
|
|
470
|
+
},
|
|
471
|
+
required: ['entries', 'errors'],
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
required: ['root', 'base', 'tree', 'verdict', 'unreviewed', 'decisions', 'gates', 'evidence', 'paths'],
|
|
475
|
+
},
|
|
358
476
|
federation_query: {
|
|
359
477
|
type: 'object',
|
|
360
478
|
properties: {
|
|
@@ -411,17 +529,16 @@ export const TOOLS = [
|
|
|
411
529
|
inputSchema: rootArg,
|
|
412
530
|
async run(args) {
|
|
413
531
|
const root = rootOf(args);
|
|
414
|
-
const ws = await
|
|
415
|
-
const result = await fingerprint(root);
|
|
532
|
+
const { result, ws, shared, findings, failure } = await diagnose(root, args ?? {});
|
|
416
533
|
return {
|
|
417
534
|
root,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
535
|
+
...result,
|
|
536
|
+
workspace: { root: ws.root ?? null, source: ws.source, detail: ws.detail, error: ws.error ?? null },
|
|
537
|
+
shared,
|
|
538
|
+
findings: serialisedFindings(findings),
|
|
539
|
+
// Read-only here: the command applies fixes behind `--fix`, the tool only reports.
|
|
540
|
+
fix: { requested: false, applied: false, fixed: [], failed: [] },
|
|
541
|
+
...(failure ?? {}),
|
|
425
542
|
};
|
|
426
543
|
},
|
|
427
544
|
},
|
|
@@ -466,51 +583,20 @@ export const TOOLS = [
|
|
|
466
583
|
},
|
|
467
584
|
async run(args) {
|
|
468
585
|
const root = rootOf(args);
|
|
469
|
-
const
|
|
470
|
-
const repo = await fingerprint(root);
|
|
471
|
-
const paths = args?.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
472
|
-
const forced = parsePathList(args?.skill);
|
|
473
|
-
const budget = Number.isInteger(args?.budget) ? args.budget : 20480;
|
|
474
|
-
|
|
475
|
-
const context = { repoStack: repo.signals, taskTokens: tokenize(args?.task ?? ''), paths };
|
|
476
|
-
const { selected, excluded } = rankSkills(reg.skills, context, { forced });
|
|
477
|
-
const expanded = closure(selected, reg.skills);
|
|
478
|
-
|
|
479
|
-
// The budget decides what is INLINED, and it is the same function the CLI
|
|
480
|
-
// uses — not a second copy of the same loop. The two copies had drifted: this one
|
|
481
|
-
// checked `deferred` *after* the budget and inlined a deferred dependency, while
|
|
482
|
-
// `skills brief` checked it first and did not. A caller reaching for the MCP tool
|
|
483
|
-
// and a caller reaching for the command got different answers to one question, and
|
|
484
|
-
// neither could see the other (ADR-035's shape, one level down).
|
|
485
|
-
//
|
|
586
|
+
const facts = await skillsBriefFacts(root, args ?? {});
|
|
486
587
|
// No body is ever returned by this tool: a caller that wants one calls
|
|
487
588
|
// skills_show, which makes the cost visible before it is paid.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
selected: picked.map((e) => ({
|
|
500
|
-
name: e.skill.name,
|
|
501
|
-
bytes: e.skill.bytes,
|
|
502
|
-
summary: e.skill.summary,
|
|
503
|
-
why: [...e.reasons, ...[...e.via].map((v) => `requires:${v}`)],
|
|
504
|
-
})),
|
|
505
|
-
deferred: deferred.map((e) => ({
|
|
506
|
-
name: e.skill.name,
|
|
507
|
-
bytes: e.skill.bytes,
|
|
508
|
-
reason: e.skill.deferred ? 'marked deferred' : 'over budget',
|
|
509
|
-
})),
|
|
510
|
-
notSelected: excluded
|
|
511
|
-
.filter((e) => !closureNames.has(e.skill.name))
|
|
512
|
-
.map((e) => ({ name: e.skill.name, reason: e.reason })),
|
|
513
|
-
};
|
|
589
|
+
if (facts.kind === 'all') {
|
|
590
|
+
return {
|
|
591
|
+
all: true,
|
|
592
|
+
repoStack: facts.repoStack,
|
|
593
|
+
inline: facts.inline.map((s) => s.name),
|
|
594
|
+
deferred: facts.deferred.map((s) => s.name),
|
|
595
|
+
...(facts.warning ? { warning: facts.warning } : {}),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
const { shown, shownDeferred, shownUsed, pointerOnly, excludedEntries, workspaceRoot, kind, ...doc } = facts;
|
|
599
|
+
return doc;
|
|
514
600
|
},
|
|
515
601
|
},
|
|
516
602
|
|
|
@@ -528,30 +614,14 @@ export const TOOLS = [
|
|
|
528
614
|
required: ['name'],
|
|
529
615
|
},
|
|
530
616
|
async run(args) {
|
|
531
|
-
const name = args?.name;
|
|
532
|
-
if (typeof name !== 'string' || name.length === 0) throw new Error('skills_show needs a skill name');
|
|
533
|
-
const { registry: reg } = await registry(args);
|
|
534
|
-
const skill = reg.skills.find((s) => s.name === name);
|
|
535
|
-
if (!skill) throw new Error(`no skill named "${name}"; call skills_index or skills_find`);
|
|
536
|
-
|
|
537
617
|
const root = rootOf(args);
|
|
538
|
-
const
|
|
539
|
-
const body = await readBody(skill, ws.root, root);
|
|
540
|
-
const source = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
541
|
-
const located = body.startsWith('(body unavailable') ? null : body;
|
|
618
|
+
const facts = await skillsShowFacts(root, args?.name, args ?? {});
|
|
542
619
|
return {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
deferred: Boolean(skill.deferred),
|
|
549
|
-
source: source ? `${source.repo}/${source.path}` : null,
|
|
550
|
-
readFrom: located === null ? 'unavailable' : body.length === 0 ? 'empty' : 'ok',
|
|
551
|
-
requires: skill.requires ?? [],
|
|
552
|
-
variants: (skill.variants ?? []).map((v) => ({ id: v.id, stack: v.stack, bytes: v.bytes })),
|
|
553
|
-
maxBytes: Number.isInteger(args?.maxBytes) ? args.maxBytes : 0,
|
|
554
|
-
body: body || null,
|
|
620
|
+
...facts.skill,
|
|
621
|
+
workspace: facts.workspace,
|
|
622
|
+
body: facts.body,
|
|
623
|
+
bodyFrom: facts.bodyFrom,
|
|
624
|
+
bodyText: facts.bodyText,
|
|
555
625
|
};
|
|
556
626
|
},
|
|
557
627
|
},
|
|
@@ -566,18 +636,7 @@ export const TOOLS = [
|
|
|
566
636
|
required: ['query'],
|
|
567
637
|
},
|
|
568
638
|
async run(args) {
|
|
569
|
-
|
|
570
|
-
if (query.trim() === '') throw new Error('skills_find needs a query');
|
|
571
|
-
const { registry: reg } = await registry(args);
|
|
572
|
-
const tokens = tokenize(query);
|
|
573
|
-
return {
|
|
574
|
-
query,
|
|
575
|
-
results: reg.skills
|
|
576
|
-
.map((skill) => ({ skill, ...scoreSkill(skill, { repoStack: [], taskTokens: tokens, paths: [] }) }))
|
|
577
|
-
.filter((r) => selectionVerdict(r.evidence, r.reasons) === null)
|
|
578
|
-
.sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
|
|
579
|
-
.map((r) => ({ name: r.skill.name, score: r.score, summary: r.skill.summary, why: r.reasons })),
|
|
580
|
-
};
|
|
639
|
+
return skillsFindFacts(typeof args?.query === 'string' ? args.query : '', args ?? {});
|
|
581
640
|
},
|
|
582
641
|
},
|
|
583
642
|
|
|
@@ -592,26 +651,8 @@ export const TOOLS = [
|
|
|
592
651
|
required: ['name'],
|
|
593
652
|
},
|
|
594
653
|
async run(args) {
|
|
595
|
-
const { registry: reg } = await registry(args);
|
|
596
|
-
const skill = reg.skills.find((s) => s.name === args?.name);
|
|
597
|
-
if (!skill) throw new Error(`no skill named "${args?.name}"; call skills_find`);
|
|
598
654
|
const root = rootOf(args);
|
|
599
|
-
|
|
600
|
-
const paths = await dirtyPaths(root);
|
|
601
|
-
const { score, reasons } = scoreSkill(skill, {
|
|
602
|
-
repoStack: repo.signals,
|
|
603
|
-
taskTokens: tokenize(args?.task ?? ''),
|
|
604
|
-
paths,
|
|
605
|
-
});
|
|
606
|
-
return {
|
|
607
|
-
name: skill.name,
|
|
608
|
-
score,
|
|
609
|
-
compatible: stackCompatible(skill, repo.signals),
|
|
610
|
-
reasons,
|
|
611
|
-
repo: { root, signals: repo.signals, evidence: repo.evidence },
|
|
612
|
-
task: args?.task ?? null,
|
|
613
|
-
pathsSeen: paths.slice(0, 20),
|
|
614
|
-
};
|
|
655
|
+
return skillsWhyFacts(root, args?.name, args ?? {});
|
|
615
656
|
},
|
|
616
657
|
},
|
|
617
658
|
|
|
@@ -626,29 +667,12 @@ export const TOOLS = [
|
|
|
626
667
|
required: [],
|
|
627
668
|
},
|
|
628
669
|
async run(args) {
|
|
629
|
-
const root = args?.root;
|
|
630
|
-
const rules = await
|
|
631
|
-
const repo = typeof root === 'string' && root ? await fingerprint(root) : null;
|
|
632
|
-
const { selected, notes } = repo
|
|
633
|
-
? select(rules, repo.stack, repo.facts)
|
|
634
|
-
: { selected: [], notes: [] };
|
|
635
|
-
const chosen = new Set(selected.map((r) => r.id));
|
|
670
|
+
const root = typeof args?.root === 'string' && args.root ? args.root : null;
|
|
671
|
+
const { rules, notes } = await rulesCatalogue(root, { selectFor: root });
|
|
636
672
|
return {
|
|
637
673
|
sha: await provenanceDigest(),
|
|
638
674
|
notes,
|
|
639
|
-
rules
|
|
640
|
-
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
|
641
|
-
.map((r) => ({
|
|
642
|
-
id: r.id,
|
|
643
|
-
title: r.title,
|
|
644
|
-
applies: r.applies,
|
|
645
|
-
origin: r.origin,
|
|
646
|
-
source: r.source,
|
|
647
|
-
bytes: r.bytes,
|
|
648
|
-
adrs: citations(r.body),
|
|
649
|
-
error: r.error,
|
|
650
|
-
selected: chosen.has(r.id),
|
|
651
|
-
})),
|
|
675
|
+
rules,
|
|
652
676
|
};
|
|
653
677
|
},
|
|
654
678
|
},
|
|
@@ -665,47 +689,7 @@ export const TOOLS = [
|
|
|
665
689
|
},
|
|
666
690
|
async run(args) {
|
|
667
691
|
const what = typeof args?.what === 'string' ? args.what : '';
|
|
668
|
-
|
|
669
|
-
const { decisions } = await loadDecisions();
|
|
670
|
-
const index = byId(decisions);
|
|
671
|
-
|
|
672
|
-
if (/^ADR-\d{3,}$/i.test(what)) {
|
|
673
|
-
const id = what.toUpperCase();
|
|
674
|
-
const d = decisions.find((x) => x.id === id);
|
|
675
|
-
if (!d) throw new Error(`no decision record ${id}`);
|
|
676
|
-
return {
|
|
677
|
-
direction: 'decision',
|
|
678
|
-
id: d.id,
|
|
679
|
-
title: d.title,
|
|
680
|
-
status: d.status,
|
|
681
|
-
// The record travels inside the installed package. A relative path would resolve
|
|
682
|
-
// against the consuming repo and name a file that is not there, which is
|
|
683
|
-
// worse than giving no path at all.
|
|
684
|
-
readWith: `${CLI} rules why ${d.id}`,
|
|
685
|
-
supersededBy: d.supersededBy,
|
|
686
|
-
supersedes: d.supersedes,
|
|
687
|
-
partial: d.partial,
|
|
688
|
-
rules: rules.filter((r) => citations(r.body).includes(id)).map((r) => r.id),
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const rule = rules.find((r) => r.id === what);
|
|
693
|
-
if (!rule) throw new Error(`no rule with id "${what}"; call rules_list`);
|
|
694
|
-
const cited = citations(rule.body);
|
|
695
|
-
return {
|
|
696
|
-
direction: 'rule',
|
|
697
|
-
id: rule.id,
|
|
698
|
-
title: rule.title,
|
|
699
|
-
source: rule.source,
|
|
700
|
-
origin: rule.origin,
|
|
701
|
-
uncited: cited.length === 0,
|
|
702
|
-
decisions: cited.map((c) => {
|
|
703
|
-
const d = index.get(c);
|
|
704
|
-
return d
|
|
705
|
-
? { id: d.id, title: d.title, status: d.status, supersededBy: d.supersededBy }
|
|
706
|
-
: { id: c, missing: true };
|
|
707
|
-
}),
|
|
708
|
-
};
|
|
692
|
+
return rulesWhyFacts(null, what, {});
|
|
709
693
|
},
|
|
710
694
|
},
|
|
711
695
|
|
|
@@ -717,12 +701,8 @@ export const TOOLS = [
|
|
|
717
701
|
inputSchema: rootArg,
|
|
718
702
|
async run(args) {
|
|
719
703
|
const root = rootOf(args);
|
|
720
|
-
const findings = await
|
|
721
|
-
|
|
722
|
-
if (r.error) findings.push({ file: r.id ?? r.file, status: 'invalid', detail: r.error });
|
|
723
|
-
}
|
|
724
|
-
const bad = findings.filter((f) => f.status !== 'current');
|
|
725
|
-
return { root, ok: bad.length === 0, findings };
|
|
704
|
+
const { ok, findings } = await checkRepo(root, {});
|
|
705
|
+
return { root, ok, findings };
|
|
726
706
|
},
|
|
727
707
|
},
|
|
728
708
|
|
|
@@ -817,11 +797,53 @@ export const TOOLS = [
|
|
|
817
797
|
};
|
|
818
798
|
},
|
|
819
799
|
},
|
|
800
|
+
// The PR-flow entry point over this surface: the same engine the `pr-body`
|
|
801
|
+
// command renders, returning JSON for a model. Read-only like everything
|
|
802
|
+
// here — it names the tree, reads the ledger and the check cache, and writes
|
|
803
|
+
// nothing, so the exit-code-never-carries-the-verdict contract stays intact.
|
|
804
|
+
{
|
|
805
|
+
outputSchema: output.pr_body,
|
|
806
|
+
name: 'pr_body',
|
|
807
|
+
// R41's 2 KB description budget is shared, not per tool: this sentence was
|
|
808
|
+
// paid for by shortening `brief` and `federation_query`, and the test on the
|
|
809
|
+
// total holds every future tool to the same trade.
|
|
810
|
+
description:
|
|
811
|
+
'The pull request body: ledger evidence for this tree, the decisions the diff touches, gate state, and the ADR-008 verdict with what was not reviewed.',
|
|
812
|
+
inputSchema: {
|
|
813
|
+
type: 'object',
|
|
814
|
+
properties: {
|
|
815
|
+
root: { type: 'string', description: 'repository root; defaults to the editor cwd' },
|
|
816
|
+
base: { type: 'string', description: 'the ref the body diffs against; defaults to origin/main, then main' },
|
|
817
|
+
},
|
|
818
|
+
required: [],
|
|
819
|
+
},
|
|
820
|
+
async run(args) {
|
|
821
|
+
const root = rootOf(args);
|
|
822
|
+
const base = typeof args?.base === 'string' && args.base.length > 0 ? args.base : null;
|
|
823
|
+
const report = await buildPrBody(root, { base });
|
|
824
|
+
if (report.error !== null) {
|
|
825
|
+
throw Object.assign(new Error(report.error), {
|
|
826
|
+
remedy: `run: ${CLI} pr-body --base <ref> with a ref git knows`,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
root,
|
|
831
|
+
base: report.base,
|
|
832
|
+
tree: report.tree,
|
|
833
|
+
verdict: report.verdict,
|
|
834
|
+
unreviewed: report.unreviewed,
|
|
835
|
+
paths: report.paths,
|
|
836
|
+
decisions: report.decisions,
|
|
837
|
+
gates: report.gates,
|
|
838
|
+
evidence: report.evidence,
|
|
839
|
+
};
|
|
840
|
+
},
|
|
841
|
+
},
|
|
820
842
|
{
|
|
821
843
|
outputSchema: output.brief,
|
|
822
844
|
name: 'brief',
|
|
823
845
|
description:
|
|
824
|
-
'The packet for one task in prompt-sized bytes:
|
|
846
|
+
'The packet for one task in prompt-sized bytes: kind, commands, files, tests, decisions, skills. With no task it describes the repository.',
|
|
825
847
|
inputSchema: {
|
|
826
848
|
type: 'object',
|
|
827
849
|
properties: {
|
|
@@ -868,7 +890,10 @@ export const TOOLS = [
|
|
|
868
890
|
outputSchema: output.federation_query,
|
|
869
891
|
name: 'federation_query',
|
|
870
892
|
description:
|
|
871
|
-
'Ask a declared code-graph server one structural question; the answer names the server
|
|
893
|
+
'Ask a declared code-graph server one structural question; the answer names the server. Starts only servers simt.json names; none declared is an error.',
|
|
894
|
+
// This tool spawns the repo manifest's `command`, so it is not read-only: a client
|
|
895
|
+
// that cached its answer as one would replay a process spawn as a pure read (Q13).
|
|
896
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
872
897
|
inputSchema: {
|
|
873
898
|
type: 'object',
|
|
874
899
|
properties: {
|
|
@@ -899,6 +924,21 @@ export const TOOLS = [
|
|
|
899
924
|
remedy: `declared servers: ${servers.map((server) => server.name).join(', ')}`,
|
|
900
925
|
});
|
|
901
926
|
}
|
|
927
|
+
// A model-supplied root can point at a directory with a crafted manifest, and a
|
|
928
|
+
// repo manifest alone must not authorise a spawn on the operator's machine (Q13).
|
|
929
|
+
const ws = await resolveWorkspace(args ?? {});
|
|
930
|
+
const user = await readUserConfig();
|
|
931
|
+
const denied = federationAccess({
|
|
932
|
+
root,
|
|
933
|
+
cwd: process.cwd(),
|
|
934
|
+
workspaceRoot: ws.root,
|
|
935
|
+
serverNames: chosen.map((server) => server.name),
|
|
936
|
+
userConfig: user.config,
|
|
937
|
+
configPath: user.path ?? configPaths()[0] ?? '~/.config/simt-dev/config.json',
|
|
938
|
+
});
|
|
939
|
+
if (denied !== null) {
|
|
940
|
+
throw Object.assign(new Error(denied.message), { remedy: denied.remedy });
|
|
941
|
+
}
|
|
902
942
|
const { answers, findings: failures } = await consult({ root, query, servers: chosen });
|
|
903
943
|
return {
|
|
904
944
|
root,
|