simt-dev 0.6.2 → 0.7.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 +83 -1
- package/README.md +46 -17
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -6
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- 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/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/README.md +53 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- package/rules/mdc/preview-postflight.mdc +5 -6
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +10 -11
- package/rules/stacks/cloudflare-workers.md +1 -1
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +4 -3
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/react-native.md +3 -5
- package/rules/stacks/react-web.md +9 -9
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +238 -15
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/policy.mjs +81 -5
- package/src/agenthooks/prompt.mjs +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shell.mjs +523 -0
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +62 -9
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +133 -17
- package/src/commands/bench.mjs +2 -1
- package/src/commands/ci.mjs +28 -3
- package/src/commands/compare.mjs +8 -7
- package/src/commands/completions.mjs +27 -0
- package/src/commands/decide.mjs +20 -6
- package/src/commands/doctor.mjs +367 -0
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/explain.mjs +52 -0
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +96 -26
- package/src/commands/hooks.mjs +258 -67
- package/src/commands/ignore.mjs +152 -0
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +69 -102
- package/src/commands/init.mjs +239 -37
- package/src/commands/lease.mjs +304 -0
- package/src/commands/lessons.mjs +77 -9
- package/src/commands/map.mjs +33 -18
- 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 +17 -2
- package/src/commands/prbody.mjs +45 -0
- package/src/commands/repoindex.mjs +4 -0
- package/src/commands/rules.mjs +219 -105
- package/src/commands/shared.mjs +36 -5
- package/src/commands/skills.mjs +329 -206
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +112 -30
- package/src/commands/slice.mjs +712 -0
- package/src/commands/status.mjs +62 -0
- package/src/commands/sync.mjs +146 -36
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +114 -0
- package/src/commands/upgrade.mjs +103 -9
- package/src/commands/why.mjs +10 -4
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -1
- package/src/completions.mjs +196 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +254 -0
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +270 -0
- package/src/federation/config.mjs +44 -0
- package/src/graph/load.mjs +52 -24
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +441 -9
- package/src/hooks/gates.mjs +14 -3
- package/src/hooks/resolve.mjs +23 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- package/src/ignore.mjs +133 -0
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +372 -55
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +545 -0
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +195 -0
- package/src/ledger/index.mjs +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +86 -13
- package/src/mcp/tools.mjs +340 -264
- package/src/memory/decide.mjs +98 -38
- package/src/memory/lessons.mjs +180 -33
- package/src/name.mjs +9 -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 +37 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +33 -8
- package/src/prbody.mjs +253 -0
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +21 -14
- package/src/projection/manifest.mjs +82 -21
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +54 -3
- package/src/projection/shared.mjs +4 -1
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +374 -0
- package/src/state.mjs +76 -0
- package/src/status.mjs +157 -0
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- package/templates/plan/tracker.json +1 -1
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
|
-
import {
|
|
25
|
+
import { buildRows, resolveConcurrency, 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'],
|
|
@@ -310,6 +381,9 @@ const output = {
|
|
|
310
381
|
repos: { type: 'array', items: { type: 'object' } },
|
|
311
382
|
worktrees: { type: 'array', items: { type: 'object' } },
|
|
312
383
|
directories: { type: 'array', items: { type: 'string' } },
|
|
384
|
+
partial: { type: 'boolean' },
|
|
385
|
+
cursor: { type: ['string', 'null'] },
|
|
386
|
+
total: { type: 'number' },
|
|
313
387
|
},
|
|
314
388
|
required: ['root', 'shape', 'expectedSha', 'summary', 'repos'],
|
|
315
389
|
},
|
|
@@ -355,6 +429,53 @@ const output = {
|
|
|
355
429
|
},
|
|
356
430
|
required: ['root', 'kind', 'basis', 'task', 'scope', 'budget', 'bytes', 'before', 'floorHeld', 'cut', 'sections', 'text'],
|
|
357
431
|
},
|
|
432
|
+
pr_body: {
|
|
433
|
+
type: 'object',
|
|
434
|
+
properties: {
|
|
435
|
+
root: { type: 'string' },
|
|
436
|
+
base: { type: ['string', 'null'] },
|
|
437
|
+
tree: { type: ['string', 'null'] },
|
|
438
|
+
verdict: { type: 'string' },
|
|
439
|
+
unreviewed: { type: 'array', items: { type: 'string' } },
|
|
440
|
+
paths: { type: 'array', items: { type: 'string' } },
|
|
441
|
+
decisions: {
|
|
442
|
+
type: 'array',
|
|
443
|
+
items: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: {
|
|
446
|
+
path: { type: 'string' },
|
|
447
|
+
id: { type: 'string' },
|
|
448
|
+
title: { type: 'string' },
|
|
449
|
+
status: { type: 'string' },
|
|
450
|
+
},
|
|
451
|
+
required: ['path', 'id', 'title', 'status'],
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
gates: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
properties: {
|
|
457
|
+
declared: { type: 'array', items: { type: 'string' } },
|
|
458
|
+
evidenceMode: { type: ['string', 'null'] },
|
|
459
|
+
run: { type: ['object', 'null'] },
|
|
460
|
+
rulesOk: { type: ['boolean', 'null'] },
|
|
461
|
+
hooksOk: { type: ['boolean', 'null'] },
|
|
462
|
+
blocking: { type: 'number' },
|
|
463
|
+
warnings: { type: 'number' },
|
|
464
|
+
cached: { type: 'boolean' },
|
|
465
|
+
},
|
|
466
|
+
required: ['declared', 'blocking', 'warnings', 'cached'],
|
|
467
|
+
},
|
|
468
|
+
evidence: {
|
|
469
|
+
type: 'object',
|
|
470
|
+
properties: {
|
|
471
|
+
entries: { type: 'array', items: { type: 'object' } },
|
|
472
|
+
errors: { type: 'array', items: { type: 'string' } },
|
|
473
|
+
},
|
|
474
|
+
required: ['entries', 'errors'],
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
required: ['root', 'base', 'tree', 'verdict', 'unreviewed', 'decisions', 'gates', 'evidence', 'paths'],
|
|
478
|
+
},
|
|
358
479
|
federation_query: {
|
|
359
480
|
type: 'object',
|
|
360
481
|
properties: {
|
|
@@ -411,17 +532,16 @@ export const TOOLS = [
|
|
|
411
532
|
inputSchema: rootArg,
|
|
412
533
|
async run(args) {
|
|
413
534
|
const root = rootOf(args);
|
|
414
|
-
const ws = await
|
|
415
|
-
const result = await fingerprint(root);
|
|
535
|
+
const { result, ws, shared, findings, failure } = await diagnose(root, args ?? {});
|
|
416
536
|
return {
|
|
417
537
|
root,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
538
|
+
...result,
|
|
539
|
+
workspace: { root: ws.root ?? null, source: ws.source, detail: ws.detail, error: ws.error ?? null },
|
|
540
|
+
shared,
|
|
541
|
+
findings: serialisedFindings(findings),
|
|
542
|
+
// Read-only here: the command applies fixes behind `--fix`, the tool only reports.
|
|
543
|
+
fix: { requested: false, applied: false, fixed: [], failed: [] },
|
|
544
|
+
...(failure ?? {}),
|
|
425
545
|
};
|
|
426
546
|
},
|
|
427
547
|
},
|
|
@@ -431,7 +551,13 @@ export const TOOLS = [
|
|
|
431
551
|
name: 'skills_index',
|
|
432
552
|
description:
|
|
433
553
|
'Full skill catalogue: every skill with summary, size, stack tags and deferred flag. The fallback when brief returns nothing useful.',
|
|
434
|
-
inputSchema:
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
registry: { type: 'string', description: 'path to skills.graph.json; else --registry / SIMT_REGISTRY / default tiers' },
|
|
558
|
+
},
|
|
559
|
+
required: [],
|
|
560
|
+
},
|
|
435
561
|
async run(args) {
|
|
436
562
|
const { registry: reg, verified, workspace } = await registry(args);
|
|
437
563
|
return {
|
|
@@ -461,56 +587,26 @@ export const TOOLS = [
|
|
|
461
587
|
budget: { type: 'number', description: 'context budget in bytes (default 20480)' },
|
|
462
588
|
skill: { type: 'string', description: 'force a skill in, comma-separated' },
|
|
463
589
|
all: { type: 'boolean', description: 'ignore the budget' },
|
|
590
|
+
registry: { type: 'string', description: 'path to skills.graph.json; else --registry / SIMT_REGISTRY / default tiers' },
|
|
464
591
|
},
|
|
465
592
|
required: [],
|
|
466
593
|
},
|
|
467
594
|
async run(args) {
|
|
468
595
|
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
|
-
//
|
|
596
|
+
const facts = await skillsBriefFacts(root, args ?? {});
|
|
486
597
|
// No body is ever returned by this tool: a caller that wants one calls
|
|
487
598
|
// 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
|
-
};
|
|
599
|
+
if (facts.kind === 'all') {
|
|
600
|
+
return {
|
|
601
|
+
all: true,
|
|
602
|
+
repoStack: facts.repoStack,
|
|
603
|
+
inline: facts.inline.map((s) => s.name),
|
|
604
|
+
deferred: facts.deferred.map((s) => s.name),
|
|
605
|
+
...(facts.warning ? { warning: facts.warning } : {}),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const { shown, shownDeferred, shownUsed, pointerOnly, excludedEntries, workspaceRoot, kind, ...doc } = facts;
|
|
609
|
+
return doc;
|
|
514
610
|
},
|
|
515
611
|
},
|
|
516
612
|
|
|
@@ -524,34 +620,19 @@ export const TOOLS = [
|
|
|
524
620
|
name: { type: 'string' },
|
|
525
621
|
root: { type: 'string' },
|
|
526
622
|
maxBytes: { type: 'number', description: 'hard ceiling on the returned text' },
|
|
623
|
+
registry: { type: 'string', description: 'path to skills.graph.json; else --registry / SIMT_REGISTRY / default tiers' },
|
|
527
624
|
},
|
|
528
625
|
required: ['name'],
|
|
529
626
|
},
|
|
530
627
|
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
628
|
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;
|
|
629
|
+
const facts = await skillsShowFacts(root, args?.name, args ?? {});
|
|
542
630
|
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,
|
|
631
|
+
...facts.skill,
|
|
632
|
+
workspace: facts.workspace,
|
|
633
|
+
body: facts.body,
|
|
634
|
+
bodyFrom: facts.bodyFrom,
|
|
635
|
+
bodyText: facts.bodyText,
|
|
555
636
|
};
|
|
556
637
|
},
|
|
557
638
|
},
|
|
@@ -562,22 +643,16 @@ export const TOOLS = [
|
|
|
562
643
|
description: 'Raw search across capability, keyword and summary. Use when skills_brief has nothing and you suspect a skill exists.',
|
|
563
644
|
inputSchema: {
|
|
564
645
|
type: 'object',
|
|
565
|
-
properties: {
|
|
646
|
+
properties: {
|
|
647
|
+
query: { type: 'string' },
|
|
648
|
+
root: { type: 'string' },
|
|
649
|
+
registry: { type: 'string', description: 'path to skills.graph.json; else --registry / SIMT_REGISTRY / default tiers' },
|
|
650
|
+
},
|
|
566
651
|
required: ['query'],
|
|
567
652
|
},
|
|
568
653
|
async run(args) {
|
|
569
|
-
const
|
|
570
|
-
|
|
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
|
-
};
|
|
654
|
+
const root = rootOf(args);
|
|
655
|
+
return skillsFindFacts(root, typeof args?.query === 'string' ? args.query : '', args ?? {});
|
|
581
656
|
},
|
|
582
657
|
},
|
|
583
658
|
|
|
@@ -588,30 +663,17 @@ export const TOOLS = [
|
|
|
588
663
|
'The signal chain for one skill in one repository: its score, the reasons, whether the stack gate admits it, and what would select it.',
|
|
589
664
|
inputSchema: {
|
|
590
665
|
type: 'object',
|
|
591
|
-
properties: {
|
|
666
|
+
properties: {
|
|
667
|
+
name: { type: 'string' },
|
|
668
|
+
root: { type: 'string' },
|
|
669
|
+
task: { type: 'string' },
|
|
670
|
+
registry: { type: 'string', description: 'path to skills.graph.json; else --registry / SIMT_REGISTRY / default tiers' },
|
|
671
|
+
},
|
|
592
672
|
required: ['name'],
|
|
593
673
|
},
|
|
594
674
|
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
675
|
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
|
-
};
|
|
676
|
+
return skillsWhyFacts(root, args?.name, args ?? {});
|
|
615
677
|
},
|
|
616
678
|
},
|
|
617
679
|
|
|
@@ -626,29 +688,12 @@ export const TOOLS = [
|
|
|
626
688
|
required: [],
|
|
627
689
|
},
|
|
628
690
|
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));
|
|
691
|
+
const root = typeof args?.root === 'string' && args.root ? args.root : null;
|
|
692
|
+
const { rules, notes } = await rulesCatalogue(root, { selectFor: root });
|
|
636
693
|
return {
|
|
637
694
|
sha: await provenanceDigest(),
|
|
638
695
|
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
|
-
})),
|
|
696
|
+
rules,
|
|
652
697
|
};
|
|
653
698
|
},
|
|
654
699
|
},
|
|
@@ -665,47 +710,7 @@ export const TOOLS = [
|
|
|
665
710
|
},
|
|
666
711
|
async run(args) {
|
|
667
712
|
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
|
-
};
|
|
713
|
+
return rulesWhyFacts(null, what, {});
|
|
709
714
|
},
|
|
710
715
|
},
|
|
711
716
|
|
|
@@ -717,12 +722,8 @@ export const TOOLS = [
|
|
|
717
722
|
inputSchema: rootArg,
|
|
718
723
|
async run(args) {
|
|
719
724
|
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 };
|
|
725
|
+
const { ok, findings } = await checkRepo(root, {});
|
|
726
|
+
return { root, ok, findings };
|
|
726
727
|
},
|
|
727
728
|
},
|
|
728
729
|
|
|
@@ -730,23 +731,37 @@ export const TOOLS = [
|
|
|
730
731
|
outputSchema: output.compare,
|
|
731
732
|
name: 'compare',
|
|
732
733
|
description:
|
|
733
|
-
'The workspace matrix:
|
|
734
|
+
'The workspace matrix: adoption, region, SHA, hooks and gates per repo, plus problems. Pages with limit/cursor; honors progressToken and cancellation.',
|
|
734
735
|
inputSchema: {
|
|
735
736
|
type: 'object',
|
|
736
737
|
properties: {
|
|
737
738
|
root: { type: 'string', description: 'a workspace directory, or one repository' },
|
|
738
739
|
workspace: { type: 'string', description: 'workspace root when root is not given' },
|
|
740
|
+
limit: { type: 'number', description: 'page size; without it every row is built' },
|
|
741
|
+
cursor: { type: 'string', description: 'opaque page start from a previous partial answer' },
|
|
742
|
+
concurrency: { type: 'number', description: 'rows built at once (default 4)' },
|
|
739
743
|
},
|
|
740
744
|
required: [],
|
|
741
745
|
},
|
|
742
|
-
async run(args) {
|
|
746
|
+
async run(args, ctx = {}) {
|
|
743
747
|
const root = args?.root ?? args?.workspace ?? (await resolveWorkspace(args ?? {})).root ?? process.cwd();
|
|
744
748
|
const found = await discover(root);
|
|
745
749
|
if (found.error) throw Object.assign(new Error(`${root}: ${found.error}`), { remedy: 'pass root' });
|
|
746
750
|
const expectedSha = await provenanceDigest();
|
|
747
751
|
const context = { expectedSha, gates: new Set(Object.keys(GATES)), gateKinds: GATE_KINDS };
|
|
748
|
-
|
|
749
|
-
|
|
752
|
+
// A page names its start as an index into discovery order, which is
|
|
753
|
+
// stable between calls on an unchanged estate; garbage parses to 0
|
|
754
|
+
// rather than erroring, because a dashboard that refuses to run over a
|
|
755
|
+
// stale cursor is worse than one that restarts the listing.
|
|
756
|
+
const start = Math.max(0, parseInt(typeof args?.cursor === 'string' ? args.cursor : '0', 10) || 0);
|
|
757
|
+
const limit = typeof args?.limit === 'number' && args.limit >= 1 ? Math.floor(args.limit) : null;
|
|
758
|
+
const page = limit === null ? found.repos : found.repos.slice(start, start + limit);
|
|
759
|
+
const rows = await buildRows(page, context, {
|
|
760
|
+
concurrency: resolveConcurrency(args ?? {}),
|
|
761
|
+
signal: ctx?.signal ?? null,
|
|
762
|
+
onBatch: ctx?.onProgress ? (done, total) => ctx.onProgress(start + done, found.repos.length) : null,
|
|
763
|
+
});
|
|
764
|
+
const next = limit !== null && start + page.length < found.repos.length ? String(start + page.length) : null;
|
|
750
765
|
return {
|
|
751
766
|
root,
|
|
752
767
|
shape: found.shape,
|
|
@@ -755,6 +770,7 @@ export const TOOLS = [
|
|
|
755
770
|
repos: rows,
|
|
756
771
|
worktrees: found.worktrees.map((w) => ({ name: w.name, owner: w.ownerName })),
|
|
757
772
|
directories: found.directories,
|
|
773
|
+
...(limit === null ? {} : { partial: next !== null, cursor: next, total: found.repos.length }),
|
|
758
774
|
};
|
|
759
775
|
},
|
|
760
776
|
},
|
|
@@ -817,11 +833,53 @@ export const TOOLS = [
|
|
|
817
833
|
};
|
|
818
834
|
},
|
|
819
835
|
},
|
|
836
|
+
// The PR-flow entry point over this surface: the same engine the `pr-body`
|
|
837
|
+
// command renders, returning JSON for a model. Read-only like everything
|
|
838
|
+
// here — it names the tree, reads the ledger and the check cache, and writes
|
|
839
|
+
// nothing, so the exit-code-never-carries-the-verdict contract stays intact.
|
|
840
|
+
{
|
|
841
|
+
outputSchema: output.pr_body,
|
|
842
|
+
name: 'pr_body',
|
|
843
|
+
// R41's 2 KB description budget is shared, not per tool: this sentence was
|
|
844
|
+
// paid for by shortening `brief` and `federation_query`, and the test on the
|
|
845
|
+
// total holds every future tool to the same trade.
|
|
846
|
+
description:
|
|
847
|
+
'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.',
|
|
848
|
+
inputSchema: {
|
|
849
|
+
type: 'object',
|
|
850
|
+
properties: {
|
|
851
|
+
root: { type: 'string', description: 'repository root; defaults to the editor cwd' },
|
|
852
|
+
base: { type: 'string', description: 'the ref the body diffs against; defaults to origin/main, then main' },
|
|
853
|
+
},
|
|
854
|
+
required: [],
|
|
855
|
+
},
|
|
856
|
+
async run(args) {
|
|
857
|
+
const root = rootOf(args);
|
|
858
|
+
const base = typeof args?.base === 'string' && args.base.length > 0 ? args.base : null;
|
|
859
|
+
const report = await buildPrBody(root, { base });
|
|
860
|
+
if (report.error !== null) {
|
|
861
|
+
throw Object.assign(new Error(report.error), {
|
|
862
|
+
remedy: `run: ${CLI} pr-body --base <ref> with a ref git knows`,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
root,
|
|
867
|
+
base: report.base,
|
|
868
|
+
tree: report.tree,
|
|
869
|
+
verdict: report.verdict,
|
|
870
|
+
unreviewed: report.unreviewed,
|
|
871
|
+
paths: report.paths,
|
|
872
|
+
decisions: report.decisions,
|
|
873
|
+
gates: report.gates,
|
|
874
|
+
evidence: report.evidence,
|
|
875
|
+
};
|
|
876
|
+
},
|
|
877
|
+
},
|
|
820
878
|
{
|
|
821
879
|
outputSchema: output.brief,
|
|
822
880
|
name: 'brief',
|
|
823
881
|
description:
|
|
824
|
-
'The packet for one task in prompt-sized bytes:
|
|
882
|
+
'The packet for one task in prompt-sized bytes: kind, commands, files, tests, decisions, skills. With no task it describes the repository.',
|
|
825
883
|
inputSchema: {
|
|
826
884
|
type: 'object',
|
|
827
885
|
properties: {
|
|
@@ -868,7 +926,10 @@ export const TOOLS = [
|
|
|
868
926
|
outputSchema: output.federation_query,
|
|
869
927
|
name: 'federation_query',
|
|
870
928
|
description:
|
|
871
|
-
'Ask a declared code-graph server one structural question; the answer names the server
|
|
929
|
+
'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.',
|
|
930
|
+
// This tool spawns the repo manifest's `command`, so it is not read-only: a client
|
|
931
|
+
// that cached its answer as one would replay a process spawn as a pure read (Q13).
|
|
932
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
872
933
|
inputSchema: {
|
|
873
934
|
type: 'object',
|
|
874
935
|
properties: {
|
|
@@ -899,6 +960,21 @@ export const TOOLS = [
|
|
|
899
960
|
remedy: `declared servers: ${servers.map((server) => server.name).join(', ')}`,
|
|
900
961
|
});
|
|
901
962
|
}
|
|
963
|
+
// A model-supplied root can point at a directory with a crafted manifest, and a
|
|
964
|
+
// repo manifest alone must not authorise a spawn on the operator's machine (Q13).
|
|
965
|
+
const ws = await resolveWorkspace(args ?? {});
|
|
966
|
+
const user = await readUserConfig();
|
|
967
|
+
const denied = federationAccess({
|
|
968
|
+
root,
|
|
969
|
+
cwd: process.cwd(),
|
|
970
|
+
workspaceRoot: ws.root,
|
|
971
|
+
serverNames: chosen.map((server) => server.name),
|
|
972
|
+
userConfig: user.config,
|
|
973
|
+
configPath: user.path ?? configPaths()[0] ?? '~/.config/simt-dev/config.json',
|
|
974
|
+
});
|
|
975
|
+
if (denied !== null) {
|
|
976
|
+
throw Object.assign(new Error(denied.message), { remedy: denied.remedy });
|
|
977
|
+
}
|
|
902
978
|
const { answers, findings: failures } = await consult({ root, query, servers: chosen });
|
|
903
979
|
return {
|
|
904
980
|
root,
|