simt-dev 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +239 -0
  2. package/bin/simt-dev.mjs +21 -2
  3. package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
  4. package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
  5. package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
  6. package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
  7. package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
  8. package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
  9. package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
  10. package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
  11. package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
  12. package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
  13. package/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
  14. package/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
  15. package/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
  16. package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
  17. package/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
  18. package/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
  19. package/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
  20. package/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
  21. package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
  22. package/package.json +2 -1
  23. package/rules/core.md +1 -0
  24. package/rules/stacks/android.md +2 -0
  25. package/rules/stacks/cloudflare-workers.md +2 -0
  26. package/rules/stacks/connectiq.md +1 -0
  27. package/rules/stacks/container.md +2 -0
  28. package/rules/stacks/dart.md +2 -0
  29. package/rules/stacks/deno.md +2 -0
  30. package/rules/stacks/dotnet.md +2 -0
  31. package/rules/stacks/elixir.md +2 -0
  32. package/rules/stacks/go.md +2 -0
  33. package/rules/stacks/jvm.md +2 -0
  34. package/rules/stacks/node.md +2 -0
  35. package/rules/stacks/python.md +2 -0
  36. package/rules/stacks/react-native.md +2 -0
  37. package/rules/stacks/react-web.md +2 -0
  38. package/rules/stacks/ruby.md +2 -0
  39. package/rules/stacks/rust.md +2 -0
  40. package/rules/stacks/swift.md +2 -0
  41. package/rules/stacks/typescript.md +2 -0
  42. package/rules/stacks/uv.md +2 -0
  43. package/src/agenthooks/decide.mjs +123 -0
  44. package/src/agenthooks/policy.mjs +78 -0
  45. package/src/agenthooks/render.mjs +240 -0
  46. package/src/argv.mjs +55 -1
  47. package/src/bench/ablate.mjs +630 -0
  48. package/src/bench/adapters.json +149 -0
  49. package/src/bench/grade.mjs +229 -0
  50. package/src/bench/mine.mjs +811 -0
  51. package/src/bench/probes.mjs +1350 -0
  52. package/src/bench/report.mjs +899 -0
  53. package/src/bench/run.mjs +1002 -0
  54. package/src/commands/agenthook.mjs +282 -0
  55. package/src/commands/bench.mjs +359 -0
  56. package/src/commands/compare.mjs +15 -0
  57. package/src/commands/graph.mjs +45 -21
  58. package/src/commands/harness.mjs +301 -0
  59. package/src/commands/help.mjs +73 -2
  60. package/src/commands/index.mjs +67 -25
  61. package/src/commands/init.mjs +12 -8
  62. package/src/commands/mcp.mjs +20 -5
  63. package/src/commands/plan.mjs +99 -0
  64. package/src/commands/rules.mjs +42 -57
  65. package/src/commands/skills.mjs +67 -92
  66. package/src/commands/skillsinstall.mjs +17 -2
  67. package/src/commands/sync.mjs +62 -6
  68. package/src/commands/tokens.mjs +169 -0
  69. package/src/compare/row.mjs +73 -2
  70. package/src/decisions/index.mjs +23 -5
  71. package/src/graph/spec.mjs +227 -0
  72. package/src/graph/speccheck.mjs +68 -0
  73. package/src/hooks/dispatcher.mjs +35 -3
  74. package/src/kernel/card.mjs +254 -0
  75. package/src/kernel/inferability.mjs +188 -0
  76. package/src/mcp/resources.mjs +157 -0
  77. package/src/mcp/schema.mjs +139 -0
  78. package/src/mcp/server.mjs +228 -20
  79. package/src/mcp/tools.mjs +372 -1
  80. package/src/measure/context.mjs +542 -0
  81. package/src/measure/loaderView.mjs +435 -0
  82. package/src/measure/session.mjs +267 -0
  83. package/src/monorepo.mjs +339 -0
  84. package/src/output/index.mjs +78 -2
  85. package/src/packs.mjs +182 -0
  86. package/src/plan/index.mjs +650 -0
  87. package/src/plan/template.mjs +331 -0
  88. package/src/projection/loaders.mjs +689 -0
  89. package/src/projection/manifest.mjs +114 -3
  90. package/src/projection/project.mjs +140 -9
  91. package/src/projection/rules.mjs +365 -22
  92. package/src/projection/surfaces.mjs +155 -23
  93. package/src/projection/targets.mjs +37 -0
  94. package/src/toolwords.mjs +69 -0
  95. package/templates/plan/tracker.json +36 -0
package/src/mcp/tools.mjs CHANGED
@@ -27,6 +27,7 @@ import { discover } from '../compare/discover.mjs';
27
27
  import { buildRow, summarise } from '../compare/row.mjs';
28
28
  import { GATES } from '../hooks/gates.mjs';
29
29
  import { dirtyPaths, parsePathList, readBody } from '../commands/skills.mjs';
30
+ import { planNext, planDone } from '../plan/index.mjs';
30
31
  import { resolveWorkspace } from '../workspace.mjs';
31
32
  import { measureSkill } from '../graph/load.mjs';
32
33
  import { CLI } from '../name.mjs';
@@ -35,6 +36,307 @@ const GATE_KINDS = new Map(Object.entries(GATES).map(([name, g]) => [name, g.kin
35
36
 
36
37
  /** A schema with no required fields still needs the object declared. */
37
38
  const noArgs = { type: 'object', properties: {}, required: [] };
39
+
40
+ /**
41
+ * Output schemas, one per tool, describing what a SUCCESSFUL call returns.
42
+ *
43
+ * Every one of these tools is a reader: none writes, and none writes twice. The
44
+ * annotations say so, and the schemas say what comes back — because a client that can
45
+ * validate an answer against a declared shape catches a wrong answer at the boundary,
46
+ * while one handed a bag of JSON has to trust that the tool meant it.
47
+ *
48
+ * Properties are required unless the payload can legitimately omit them: `error` is
49
+ * absent when there is none, `body` is null when there is none, and a schema that
50
+ * required the exception would describe a shape that never occurs. `additionalProperties`
51
+ * is left open, which is what the 2026-07-28 revision permits and what a reader wants:
52
+ * these payloads are data, and a new field from a future release should not make an
53
+ * older client's validation fail.
54
+ */
55
+ /**
56
+ * The shape of a failure, exported because a client validating an answer needs it and
57
+ * because the test that checks every tool's answer needs it for the failures. Two
58
+ * copies of this schema would drift, and the copy that drifted would be the one nobody
59
+ * validates against - which is exactly how a remedy quietly stops being a string.
60
+ */
61
+ export const ERROR_SHAPE = {
62
+ type: 'object',
63
+ properties: { error: { type: 'string' }, remedy: { type: ['string', 'null'] } },
64
+ required: ['error'],
65
+ };
66
+
67
+ const output = {
68
+ doctor: {
69
+ type: 'object',
70
+ properties: {
71
+ root: { type: 'string' },
72
+ workspace: {
73
+ type: 'object',
74
+ properties: {
75
+ // Null is the honest value for a repository that is not inside a workspace:
76
+ // there is no root to name, and naming the repository as one would be a claim
77
+ // about the reader's directory layout that nothing established.
78
+ root: { type: ['string', 'null'] },
79
+ source: { type: ['string', 'null'] },
80
+ detail: { type: ['string', 'null'] },
81
+ },
82
+ required: ['root', 'source'],
83
+ },
84
+ stack: { type: 'array', items: { type: 'string' } },
85
+ facts: { type: 'object', additionalProperties: { type: ['string', 'null'] } },
86
+ signals: { type: 'array', items: { type: 'string' } },
87
+ // One entry per signal, naming the file that proved it. The payload is a list of
88
+ // paths, not of records - `fingerprint` returns evidence as the paths themselves.
89
+ evidence: { type: 'array', items: { type: 'string' } },
90
+ manifest: { type: ['object', 'null'] },
91
+ error: { type: ['string', 'null'] },
92
+ },
93
+ required: ['root', 'workspace', 'stack', 'facts', 'signals', 'evidence'],
94
+ },
95
+ skills_index: {
96
+ type: 'object',
97
+ properties: {
98
+ skills: { type: 'number' },
99
+ totalBytes: { type: 'number' },
100
+ deferred: { type: 'array', items: { type: 'string' } },
101
+ workspace: { type: 'object' },
102
+ verified: { type: 'boolean' },
103
+ entries: {
104
+ type: 'array',
105
+ items: {
106
+ type: 'object',
107
+ properties: {
108
+ name: { type: 'string' },
109
+ summary: { type: 'string' },
110
+ bytes: { type: 'number' },
111
+ stack: { type: 'array', items: { type: 'string' } },
112
+ deferred: { type: 'boolean' },
113
+ },
114
+ required: ['name', 'bytes'],
115
+ },
116
+ },
117
+ },
118
+ required: ['skills', 'totalBytes', 'entries'],
119
+ },
120
+ skills_brief: {
121
+ type: 'object',
122
+ properties: {
123
+ repo: {
124
+ type: 'object',
125
+ properties: {
126
+ root: { type: 'string' },
127
+ stack: { type: 'array', items: { type: 'string' } },
128
+ facts: { type: 'array', items: { type: 'string' } },
129
+ },
130
+ required: ['root', 'stack', 'facts'],
131
+ },
132
+ task: { type: ['string', 'null'] },
133
+ budget: { type: 'number' },
134
+ inlineBytes: { type: 'number' },
135
+ selected: {
136
+ type: 'array',
137
+ items: {
138
+ type: 'object',
139
+ properties: {
140
+ name: { type: 'string' },
141
+ bytes: { type: 'number' },
142
+ summary: { type: 'string' },
143
+ why: { type: 'array', items: { type: 'string' } },
144
+ },
145
+ required: ['name', 'bytes', 'why'],
146
+ },
147
+ },
148
+ deferred: {
149
+ type: 'array',
150
+ items: {
151
+ type: 'object',
152
+ properties: { name: { type: 'string' }, bytes: { type: 'number' }, reason: { type: 'string' } },
153
+ required: ['name', 'reason'],
154
+ },
155
+ },
156
+ notSelected: {
157
+ type: 'array',
158
+ items: {
159
+ type: 'object',
160
+ properties: { name: { type: 'string' }, reason: { type: 'string' } },
161
+ required: ['name', 'reason'],
162
+ },
163
+ },
164
+ },
165
+ required: ['repo', 'budget', 'inlineBytes', 'selected', 'deferred', 'notSelected'],
166
+ },
167
+ skills_show: {
168
+ type: 'object',
169
+ properties: {
170
+ name: { type: 'string' },
171
+ summary: { type: 'string' },
172
+ bytes: { type: 'number' },
173
+ capabilities: { type: 'array', items: { type: 'string' } },
174
+ stack: { type: 'array', items: { type: 'string' } },
175
+ deferred: { type: 'boolean' },
176
+ source: { type: ['string', 'null'] },
177
+ readFrom: { type: 'string', enum: ['ok', 'empty', 'unavailable'] },
178
+ requires: { type: 'array', items: { type: 'string' } },
179
+ variants: {
180
+ type: 'array',
181
+ items: {
182
+ type: 'object',
183
+ properties: { id: { type: 'string' }, stack: { type: 'array', items: { type: 'string' } }, bytes: { type: 'number' } },
184
+ required: ['id'],
185
+ },
186
+ },
187
+ maxBytes: { type: 'number' },
188
+ body: { type: ['string', 'null'] },
189
+ },
190
+ required: ['name', 'bytes', 'readFrom', 'requires', 'variants'],
191
+ },
192
+ skills_find: {
193
+ type: 'object',
194
+ properties: {
195
+ query: { type: 'string' },
196
+ results: {
197
+ type: 'array',
198
+ items: {
199
+ type: 'object',
200
+ properties: {
201
+ name: { type: 'string' },
202
+ score: { type: 'number' },
203
+ summary: { type: 'string' },
204
+ why: { type: 'array', items: { type: 'string' } },
205
+ },
206
+ required: ['name', 'score', 'why'],
207
+ },
208
+ },
209
+ },
210
+ required: ['query', 'results'],
211
+ },
212
+ skills_why: {
213
+ type: 'object',
214
+ properties: {
215
+ name: { type: 'string' },
216
+ score: { type: 'number' },
217
+ compatible: { type: 'boolean' },
218
+ reasons: { type: 'array', items: { type: 'string' } },
219
+ repo: {
220
+ type: 'object',
221
+ properties: { root: { type: 'string' }, signals: { type: 'array', items: { type: 'string' } } },
222
+ },
223
+ task: { type: ['string', 'null'] },
224
+ pathsSeen: { type: 'array', items: { type: 'string' } },
225
+ },
226
+ required: ['name', 'score', 'compatible', 'reasons'],
227
+ },
228
+ rules_list: {
229
+ type: 'object',
230
+ properties: {
231
+ sha: { type: 'string' },
232
+ notes: { type: 'array', items: { type: 'string' } },
233
+ rules: {
234
+ type: 'array',
235
+ items: {
236
+ type: 'object',
237
+ properties: {
238
+ id: { type: ['string', 'null'] },
239
+ title: { type: ['string', 'null'] },
240
+ // A profile may name one signal or several, and the frontmatter keeps both
241
+ // spellings. Describing only the list would make a conforming rule look
242
+ // broken.
243
+ applies: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
244
+ origin: { type: 'string' },
245
+ source: { type: 'string' },
246
+ bytes: { type: 'number' },
247
+ adrs: { type: 'array', items: { type: 'string' } },
248
+ error: { type: ['string', 'null'] },
249
+ selected: { type: 'boolean' },
250
+ },
251
+ required: ['source', 'bytes', 'selected'],
252
+ },
253
+ },
254
+ },
255
+ required: ['sha', 'rules'],
256
+ },
257
+ rules_why: {
258
+ type: 'object',
259
+ properties: {
260
+ direction: { type: 'string', enum: ['rule', 'decision'] },
261
+ id: { type: 'string' },
262
+ title: { type: ['string', 'null'] },
263
+ status: { type: 'string' },
264
+ source: { type: 'string' },
265
+ origin: { type: 'string' },
266
+ uncited: { type: 'boolean' },
267
+ readWith: { type: 'string' },
268
+ supersededBy: { type: ['string', 'null'] },
269
+ supersedes: { type: 'array', items: { type: 'string' } },
270
+ // A decision record may be partially superseded; the record carries the list of
271
+ // what it replaced, so this is a list and not a flag.
272
+ partial: { type: ['boolean', 'array'] },
273
+ decisions: { type: 'array', items: { type: 'object' } },
274
+ rules: { type: 'array', items: { type: 'string' } },
275
+ },
276
+ required: ['direction', 'id'],
277
+ },
278
+ check: {
279
+ type: 'object',
280
+ properties: {
281
+ root: { type: 'string' },
282
+ ok: { type: 'boolean' },
283
+ findings: {
284
+ type: 'array',
285
+ items: {
286
+ type: 'object',
287
+ properties: {
288
+ file: { type: 'string' },
289
+ status: { type: 'string' },
290
+ detail: { type: ['string', 'null'] },
291
+ remedy: { type: ['string', 'null'] },
292
+ },
293
+ required: ['file', 'status'],
294
+ },
295
+ },
296
+ },
297
+ required: ['root', 'ok', 'findings'],
298
+ },
299
+ compare: {
300
+ type: 'object',
301
+ properties: {
302
+ root: { type: 'string' },
303
+ shape: { type: 'string', enum: ['repo', 'workspace'] },
304
+ expectedSha: { type: 'string' },
305
+ summary: { type: 'object' },
306
+ repos: { type: 'array', items: { type: 'object' } },
307
+ worktrees: { type: 'array', items: { type: 'object' } },
308
+ directories: { type: 'array', items: { type: 'string' } },
309
+ },
310
+ required: ['root', 'shape', 'expectedSha', 'summary', 'repos'],
311
+ },
312
+ plan_next: {
313
+ type: 'object',
314
+ properties: {
315
+ root: { type: 'string' },
316
+ next: { type: ['object', 'null'] },
317
+ reason: { type: ['string', 'null'] },
318
+ },
319
+ required: ['root', 'next', 'reason'],
320
+ },
321
+ plan_done: {
322
+ type: 'object',
323
+ properties: {
324
+ root: { type: 'string' },
325
+ id: { type: 'string' },
326
+ doc: { type: 'string' },
327
+ line: { type: 'number' },
328
+ from: { type: 'string' },
329
+ to: { type: 'string' },
330
+ wouldChange: { type: 'boolean' },
331
+ dryRun: { type: 'boolean' },
332
+ remedy: { type: ['string', 'null'] },
333
+ },
334
+ required: ['root', 'id', 'wouldChange', 'dryRun'],
335
+ },
336
+ /** The shape of every tool failure, which is a result and not a protocol error. */
337
+ error: ERROR_SHAPE,
338
+ };
339
+
38
340
  const rootArg = {
39
341
  type: 'object',
40
342
  properties: { root: { type: 'string', description: 'repository root; defaults to the editor cwd' } },
@@ -57,6 +359,7 @@ async function registry(args) {
57
359
 
58
360
  export const TOOLS = [
59
361
  {
362
+ outputSchema: output.doctor,
60
363
  name: 'doctor',
61
364
  description:
62
365
  '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 +382,7 @@ export const TOOLS = [
79
382
  },
80
383
 
81
384
  {
385
+ outputSchema: output.skills_index,
82
386
  name: 'skills_index',
83
387
  description:
84
388
  '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 +403,7 @@ export const TOOLS = [
99
403
  },
100
404
 
101
405
  {
406
+ outputSchema: output.skills_brief,
102
407
  name: 'skills_brief',
103
408
  description:
104
409
  '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 +470,7 @@ export const TOOLS = [
165
470
  },
166
471
 
167
472
  {
473
+ outputSchema: output.skills_show,
168
474
  name: 'skills_show',
169
475
  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
476
  inputSchema: {
@@ -206,6 +512,7 @@ export const TOOLS = [
206
512
  },
207
513
 
208
514
  {
515
+ outputSchema: output.skills_find,
209
516
  name: 'skills_find',
210
517
  description: 'Raw search across capability, keyword and summary. Use when skills_brief has nothing and you suspect a skill exists.',
211
518
  inputSchema: {
@@ -230,6 +537,7 @@ export const TOOLS = [
230
537
  },
231
538
 
232
539
  {
540
+ outputSchema: output.skills_why,
233
541
  name: 'skills_why',
234
542
  description:
235
543
  '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 +571,7 @@ export const TOOLS = [
263
571
  },
264
572
 
265
573
  {
574
+ outputSchema: output.rules_list,
266
575
  name: 'rules_list',
267
576
  description:
268
577
  'The rule catalogue, and which rules a given repository actually receives. Pass `root` to get the selection rather than just the catalogue.',
@@ -300,9 +609,10 @@ export const TOOLS = [
300
609
  },
301
610
 
302
611
  {
612
+ outputSchema: output.rules_why,
303
613
  name: 'rules_why',
304
614
  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 restsates. A rule citing no decision reports that - it is a finding, not an empty answer.',
615
+ '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
616
  inputSchema: {
307
617
  type: 'object',
308
618
  properties: { what: { type: 'string', description: 'a rule id or ADR-NNN' } },
@@ -355,6 +665,7 @@ export const TOOLS = [
355
665
  },
356
666
 
357
667
  {
668
+ outputSchema: output.check,
358
669
  name: 'check',
359
670
  description:
360
671
  '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 +682,7 @@ export const TOOLS = [
371
682
  },
372
683
 
373
684
  {
685
+ outputSchema: output.compare,
374
686
  name: 'compare',
375
687
  description:
376
688
  '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.',
@@ -401,6 +713,65 @@ export const TOOLS = [
401
713
  };
402
714
  },
403
715
  },
716
+ {
717
+ outputSchema: output.plan_next,
718
+ name: 'plan_next',
719
+ description: 'The next queued slice whose dependencies are complete, and only that slice\'s section. Pass id to read one slice whether or not it is ready.',
720
+ inputSchema: {
721
+ type: 'object',
722
+ properties: {
723
+ root: { type: 'string', description: 'repository root; defaults to the editor cwd' },
724
+ id: { type: 'string', description: 'slice id; when given, return that slice' },
725
+ },
726
+ required: [],
727
+ },
728
+ async run(args) {
729
+ const root = rootOf(args);
730
+ const id = typeof args?.id === 'string' && args.id.length > 0 ? args.id : null;
731
+ const result = await planNext(root, id);
732
+ if (result.error) throw Object.assign(new Error(result.error), { remedy: result.remedy ?? null });
733
+ return { root: result.root, next: result.next, reason: result.reason };
734
+ },
735
+ },
736
+ {
737
+ outputSchema: output.plan_done,
738
+ name: 'plan_done',
739
+ // Same function as the command, and deliberately a preview. This surface does
740
+ // not write: the row change is a diff, and the command applies it behind --yes.
741
+ description: 'Preview the one-row rewrite for a slice. The write stays on the plan done command, behind --yes.',
742
+ inputSchema: {
743
+ type: 'object',
744
+ properties: {
745
+ root: { type: 'string', description: 'repository root; defaults to the editor cwd' },
746
+ id: { type: 'string', description: 'slice id' },
747
+ sha: { type: 'string', description: 'commit sha that closed the slice' },
748
+ test: { type: 'string', description: 'test command that closed it' },
749
+ state: { type: 'string', description: 'target state; omitted means the completed state of the repository template (Complete for tracker)' },
750
+ },
751
+ required: ['id'],
752
+ },
753
+ async run(args) {
754
+ const root = rootOf(args);
755
+ const result = await planDone(root, args?.id, {
756
+ sha: args?.sha ?? null,
757
+ test: args?.test ?? null,
758
+ state: args?.state ?? null,
759
+ dryRun: true,
760
+ });
761
+ if (result.error) throw Object.assign(new Error(result.error), { remedy: result.remedy ?? null });
762
+ return {
763
+ root: result.root,
764
+ id: result.id,
765
+ doc: result.doc,
766
+ line: result.line,
767
+ from: result.from,
768
+ to: result.to,
769
+ wouldChange: result.wouldChange,
770
+ dryRun: true,
771
+ remedy: result.remedy,
772
+ };
773
+ },
774
+ },
404
775
  ];
405
776
 
406
777
  export function toolNames() {