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.
Files changed (170) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/README.md +46 -17
  3. package/agents/simt-agent.md +84 -0
  4. package/bin/simt.mjs +19 -6
  5. package/docs/decisions/ADR-009-skill-layout.md +1 -1
  6. package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
  7. package/docs/decisions/ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md +94 -0
  8. package/docs/decisions/ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md +95 -0
  9. package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +49 -0
  10. package/docs/decisions/ADR-103-co-projected-duplicates-merge-down.md +74 -0
  11. package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md +76 -0
  12. package/docs/decisions/ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md +44 -0
  13. package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
  14. package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
  15. package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
  16. 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
  17. 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
  18. package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
  19. package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
  20. package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
  21. 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
  22. 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
  23. package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
  24. package/docs/decisions/README.md +53 -0
  25. package/package.json +4 -2
  26. package/rules/core.md +2 -2
  27. package/rules/mdc/preview-postflight.mdc +5 -6
  28. package/rules/mdc/test-gate.mdc +2 -0
  29. package/rules/stacks/android.md +10 -11
  30. package/rules/stacks/cloudflare-workers.md +1 -1
  31. package/rules/stacks/connectiq.md +2 -2
  32. package/rules/stacks/container.md +2 -0
  33. package/rules/stacks/dart.md +1 -1
  34. package/rules/stacks/deno.md +1 -1
  35. package/rules/stacks/dotnet.md +1 -1
  36. package/rules/stacks/elixir.md +1 -1
  37. package/rules/stacks/go.md +1 -1
  38. package/rules/stacks/jvm.md +4 -3
  39. package/rules/stacks/node.md +2 -2
  40. package/rules/stacks/python.md +7 -6
  41. package/rules/stacks/react-native.md +3 -5
  42. package/rules/stacks/react-web.md +9 -9
  43. package/rules/stacks/ruby.md +1 -1
  44. package/rules/stacks/rust.md +1 -1
  45. package/rules/stacks/swift.md +1 -1
  46. package/rules/stacks/typescript.md +2 -2
  47. package/rules/stacks/uv.md +3 -2
  48. package/skills/simt-commands/SKILL.md +70 -0
  49. package/src/advice/advice.mjs +7 -3
  50. package/src/agenthooks/decide.mjs +238 -15
  51. package/src/agenthooks/opencode.mjs +72 -0
  52. package/src/agenthooks/policy.mjs +81 -5
  53. package/src/agenthooks/prompt.mjs +6 -2
  54. package/src/agenthooks/render.mjs +186 -19
  55. package/src/agenthooks/shell.mjs +523 -0
  56. package/src/agenthooks/shim.mjs +55 -0
  57. package/src/agents/render.mjs +277 -0
  58. package/src/argv.mjs +62 -9
  59. package/src/bench/ablate.mjs +31 -14
  60. package/src/bench/mine.mjs +48 -1
  61. package/src/bench/probes.mjs +5 -6
  62. package/src/bench/report.mjs +243 -80
  63. package/src/bench/run.mjs +12 -11
  64. package/src/bench/variants.mjs +112 -40
  65. package/src/brief/assemble.mjs +6 -0
  66. package/src/ci/workflow.mjs +16 -7
  67. package/src/commands/agent.mjs +206 -0
  68. package/src/commands/agenthook.mjs +133 -17
  69. package/src/commands/bench.mjs +2 -1
  70. package/src/commands/ci.mjs +28 -3
  71. package/src/commands/compare.mjs +8 -7
  72. package/src/commands/completions.mjs +27 -0
  73. package/src/commands/decide.mjs +20 -6
  74. package/src/commands/doctor.mjs +367 -0
  75. package/src/commands/evidence.mjs +13 -3
  76. package/src/commands/explain.mjs +52 -0
  77. package/src/commands/gate.mjs +85 -19
  78. package/src/commands/graph.mjs +18 -5
  79. package/src/commands/help.mjs +96 -26
  80. package/src/commands/hooks.mjs +258 -67
  81. package/src/commands/ignore.mjs +152 -0
  82. package/src/commands/impact.mjs +4 -2
  83. package/src/commands/index.mjs +69 -102
  84. package/src/commands/init.mjs +239 -37
  85. package/src/commands/lease.mjs +304 -0
  86. package/src/commands/lessons.mjs +77 -9
  87. package/src/commands/map.mjs +33 -18
  88. package/src/commands/mcp.mjs +2 -2
  89. package/src/commands/pack.mjs +9 -0
  90. package/src/commands/plan.mjs +13 -1
  91. package/src/commands/plugin.mjs +17 -2
  92. package/src/commands/prbody.mjs +45 -0
  93. package/src/commands/repoindex.mjs +4 -0
  94. package/src/commands/rules.mjs +219 -105
  95. package/src/commands/shared.mjs +36 -5
  96. package/src/commands/skills.mjs +329 -206
  97. package/src/commands/skillscompile.mjs +8 -7
  98. package/src/commands/skillsinstall.mjs +112 -30
  99. package/src/commands/slice.mjs +712 -0
  100. package/src/commands/status.mjs +62 -0
  101. package/src/commands/sync.mjs +146 -36
  102. package/src/commands/tests.mjs +5 -2
  103. package/src/commands/undo.mjs +114 -0
  104. package/src/commands/upgrade.mjs +103 -9
  105. package/src/commands/why.mjs +10 -4
  106. package/src/compare/hooksstate.mjs +23 -1
  107. package/src/compare/learn.mjs +82 -57
  108. package/src/compare/parallel.mjs +50 -0
  109. package/src/compare/row.mjs +26 -1
  110. package/src/completions.mjs +196 -0
  111. package/src/decisions/dir.mjs +84 -0
  112. package/src/decisions/index.mjs +109 -11
  113. package/src/diff.mjs +254 -0
  114. package/src/distill/distill.mjs +3 -1
  115. package/src/ecosystem/node-runner.mjs +144 -0
  116. package/src/explain.mjs +270 -0
  117. package/src/federation/config.mjs +44 -0
  118. package/src/graph/load.mjs +52 -24
  119. package/src/hooks/capability.mjs +14 -1
  120. package/src/hooks/dispatcher.mjs +441 -9
  121. package/src/hooks/gates.mjs +14 -3
  122. package/src/hooks/resolve.mjs +23 -3
  123. package/src/hooks/shim.mjs +12 -7
  124. package/src/identity.mjs +67 -14
  125. package/src/ignore.mjs +133 -0
  126. package/src/index/extract.mjs +192 -11
  127. package/src/index/persist.mjs +372 -55
  128. package/src/index/store.mjs +263 -60
  129. package/src/index/tests.mjs +6 -35
  130. package/src/journal.mjs +545 -0
  131. package/src/kernel/card.mjs +37 -12
  132. package/src/kernel/checks.mjs +44 -9
  133. package/src/kernel/variants.mjs +27 -16
  134. package/src/lease.mjs +195 -0
  135. package/src/ledger/index.mjs +91 -25
  136. package/src/mcp/resources.mjs +5 -3
  137. package/src/mcp/server.mjs +86 -13
  138. package/src/mcp/tools.mjs +340 -264
  139. package/src/memory/decide.mjs +98 -38
  140. package/src/memory/lessons.mjs +180 -33
  141. package/src/name.mjs +9 -0
  142. package/src/output/index.mjs +82 -12
  143. package/src/package-version.mjs +49 -1
  144. package/src/packs/manage.mjs +13 -4
  145. package/src/plan/index.mjs +37 -8
  146. package/src/plan/template.mjs +27 -5
  147. package/src/plugins.mjs +33 -8
  148. package/src/prbody.mjs +253 -0
  149. package/src/projection/baselines.mjs +1 -1
  150. package/src/projection/disclosure.mjs +2 -2
  151. package/src/projection/loaders.mjs +41 -1
  152. package/src/projection/lock.mjs +21 -14
  153. package/src/projection/manifest.mjs +82 -21
  154. package/src/projection/markers.mjs +43 -3
  155. package/src/projection/mdc.mjs +34 -7
  156. package/src/projection/project.mjs +162 -18
  157. package/src/projection/rules.mjs +54 -3
  158. package/src/projection/shared.mjs +4 -1
  159. package/src/projection/surfaces.mjs +9 -1
  160. package/src/repo-root.mjs +69 -0
  161. package/src/resolve/body.mjs +4 -0
  162. package/src/resolve/fingerprint.mjs +5 -0
  163. package/src/resolve/local.mjs +58 -0
  164. package/src/review/critic.mjs +4 -2
  165. package/src/secrets.mjs +374 -0
  166. package/src/state.mjs +76 -0
  167. package/src/status.mjs +157 -0
  168. package/src/toolwords.mjs +5 -0
  169. package/src/tsconfig.mjs +39 -99
  170. package/templates/plan/tracker.json +1 -1
@@ -103,12 +103,17 @@ const LIST_CACHE = { ttlMs: 3600000, cacheScope: 'private' };
103
103
  * caller names the root and the default is the process's cwd, which is what an editor's
104
104
  * stdio server sits in.
105
105
  *
106
+ * `signal` aborts the one in-flight `tools/call` (wired by `serve` to
107
+ * `notifications/cancelled`); `notify` emits `notifications/progress` while it
108
+ * runs. Both are null in tests that call this directly, and every tool must
109
+ * treat a missing context as "run to completion quietly".
110
+ *
106
111
  * @param {object|null} request a parsed JSON-RPC message
107
112
  * @param {object[]} tools
108
- * @param {{packageVersion?: string, state?: {version: string|null}, root?: string}} options
113
+ * @param {{packageVersion?: string, state?: {version: string|null}, root?: string, signal?: AbortSignal|null, notify?: ((message: object) => void)|null}} options
109
114
  * @returns {Promise<object|null>}
110
115
  */
111
- export async function handle(request, tools, { packageVersion, state, root } = {}) {
116
+ export async function handle(request, tools, { packageVersion, state, root, signal = null, notify = null } = {}) {
112
117
  if (request === null || typeof request !== 'object' || Array.isArray(request)) {
113
118
  return error(null, RPC.REQUEST, 'request must be a JSON object');
114
119
  }
@@ -195,8 +200,24 @@ export async function handle(request, tools, { packageVersion, state, root } = {
195
200
  return error(id, RPC.METHOD, `no tool named "${name}"; call tools/list first`);
196
201
  }
197
202
  const args = params?.arguments ?? {};
203
+ // The one impurity in this file, and scoped to it: progress has to reach
204
+ // the client while the tool runs, not after it returns. Nothing else
205
+ // writes here - diagnostics still go to stderr, never this stream.
206
+ const progressToken = params?._meta?.progressToken;
207
+ const toolCtx = {
208
+ signal,
209
+ onProgress:
210
+ progressToken !== undefined && progressToken !== null && typeof notify === 'function'
211
+ ? (progress, total) =>
212
+ notify({
213
+ jsonrpc: '2.0',
214
+ method: 'notifications/progress',
215
+ params: { progressToken, progress, total },
216
+ })
217
+ : null,
218
+ };
198
219
  try {
199
- const payload = await tool.run(args);
220
+ const payload = await tool.run(args, toolCtx);
200
221
  // Both blocks, always. `structuredContent` is what a client validates against
201
222
  // `outputSchema` and what it can index; the text block is what a client that
202
223
  // predates structured output reads. Emitting one without the other leaves half
@@ -384,19 +405,71 @@ function error(id, code, message) {
384
405
  export async function serve(tools, { input = process.stdin, output = process.stdout, packageVersion, onError } = {}) {
385
406
  const rl = createInterface({ input, crlfDelay: Infinity });
386
407
  const state = { version: null };
387
- for await (const line of rl) {
388
- if (line.trim() === '') continue;
389
-
390
- let response;
408
+ // The in-flight call a `notifications/cancelled` can abort. Read this way
409
+ // because the loop below is blocked awaiting the tool while the client
410
+ // decides: the event loop still turns on subprocess waits, so the peek
411
+ // fires mid-run and the tool checks the signal between rows. The async
412
+ // iterator below yields the same line afterwards and `handle` answers it
413
+ // with null, as every notification is answered - the peek consumes nothing.
414
+ let inflight = null;
415
+ const peekCancel = (line) => {
416
+ if (!inflight || line.trim() === '') return;
417
+ let message;
391
418
  try {
392
- response = await handle(JSON.parse(line), tools, { packageVersion, state });
393
- } catch (parseFailure) {
394
- response = error(null, RPC.PARSE, `invalid JSON: ${parseFailure.message}`);
419
+ message = JSON.parse(line);
420
+ } catch {
421
+ return;
422
+ }
423
+ if (message?.method === 'notifications/cancelled' && message?.params?.requestId === inflight.id) {
424
+ inflight.controller.abort();
425
+ }
426
+ };
427
+ rl.on('line', peekCancel);
428
+ try {
429
+ for await (const line of rl) {
430
+ if (line.trim() === '') continue;
431
+
432
+ // Parse and handling fail differently and must answer differently. A line that is
433
+ // not JSON has no id to answer to, so it is a parse error with `id: null`; a
434
+ // throw inside `handle` belongs to the request it was answering, so it keeps
435
+ // that request's id — answering it as a parse error hangs the client that is
436
+ // still waiting for its answer (Q12).
437
+ let response;
438
+ let message;
439
+ try {
440
+ message = JSON.parse(line);
441
+ } catch (parseFailure) {
442
+ response = error(null, RPC.PARSE, `invalid JSON: ${parseFailure.message}`);
443
+ }
444
+ if (response === undefined) {
445
+ // A notification never runs: without an id there is nothing to cancel
446
+ // and nothing to answer, so `notifications/cancelled` for an idle server
447
+ // is a no-op rather than an error.
448
+ const isNotification =
449
+ message === null || typeof message !== 'object' || Array.isArray(message) || message.id === undefined || message.id === null;
450
+ const controller = isNotification ? null : new AbortController();
451
+ if (controller) inflight = { id: message.id, controller };
452
+ try {
453
+ response = await handle(message, tools, {
454
+ packageVersion,
455
+ state,
456
+ signal: controller?.signal ?? null,
457
+ notify: (notification) => output.write(`${JSON.stringify(notification)}\n`),
458
+ });
459
+ } catch (handlerFailure) {
460
+ const id = message !== null && typeof message === 'object' && 'id' in message ? message.id : null;
461
+ response = error(id, RPC.INTERNAL, `internal error: ${handlerFailure?.message ?? handlerFailure}`);
462
+ } finally {
463
+ if (controller) inflight = null;
464
+ }
465
+ }
466
+ if (response === null) continue;
467
+ output.write(`${JSON.stringify(response)}\n`);
395
468
  }
396
- if (response === null) continue;
397
- output.write(`${JSON.stringify(response)}\n`);
469
+ return EXIT_OK;
470
+ } finally {
471
+ rl.off('line', peekCancel);
398
472
  }
399
- return EXIT_OK;
400
473
  }
401
474
 
402
475
  const EXIT_OK = 0;