simt-dev 0.5.0 → 0.6.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 (84) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/README.md +78 -59
  3. package/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +13 -0
  4. package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +1 -1
  5. package/docs/decisions/ADR-097-the-channels-publish-by-hand-and-the-version-rides-in-as-an-input.md +71 -0
  6. package/docs/decisions/ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md +149 -0
  7. package/package.json +4 -4
  8. package/src/agenthooks/render.mjs +87 -21
  9. package/src/argv.mjs +13 -0
  10. package/src/bench/ablate.mjs +1 -3
  11. package/src/bench/grade.mjs +1 -1
  12. package/src/bench/mine.mjs +3 -3
  13. package/src/bench/probes.mjs +1 -3
  14. package/src/bench/report.mjs +1 -3
  15. package/src/bench/run.mjs +1 -3
  16. package/src/bench/tasks.mjs +1 -3
  17. package/src/bench/variants.mjs +1 -3
  18. package/src/brief/assemble.mjs +2 -4
  19. package/src/ci/dependabot.mjs +1 -1
  20. package/src/ci/workflow.mjs +40 -12
  21. package/src/commands/agenthook.mjs +6 -2
  22. package/src/commands/bench.mjs +4 -4
  23. package/src/commands/ci.mjs +1 -1
  24. package/src/commands/compare.mjs +1 -1
  25. package/src/commands/distill.mjs +1 -1
  26. package/src/commands/gate.mjs +2 -2
  27. package/src/commands/graph.mjs +2 -2
  28. package/src/commands/harness.mjs +87 -14
  29. package/src/commands/help.mjs +29 -7
  30. package/src/commands/hooks.mjs +1 -1
  31. package/src/commands/index.mjs +17 -3
  32. package/src/commands/init.mjs +2 -2
  33. package/src/commands/local.mjs +11 -0
  34. package/src/commands/mcp.mjs +1 -1
  35. package/src/commands/pack.mjs +246 -0
  36. package/src/commands/plugin.mjs +61 -0
  37. package/src/commands/rules.mjs +1 -1
  38. package/src/commands/skills.mjs +1 -1
  39. package/src/commands/skillscompile.mjs +1 -3
  40. package/src/commands/skillsinstall.mjs +1 -1
  41. package/src/commands/sync.mjs +8 -8
  42. package/src/commands/tokens.mjs +1 -1
  43. package/src/commands/upgrade.mjs +1 -1
  44. package/src/commands/why.mjs +1 -1
  45. package/src/compare/discover.mjs +1 -1
  46. package/src/compare/hooksstate.mjs +19 -7
  47. package/src/compare/learn.mjs +2 -2
  48. package/src/compare/row.mjs +3 -3
  49. package/src/decisions/index.mjs +1 -1
  50. package/src/distill/distill.mjs +2 -2
  51. package/src/distribute/release.mjs +142 -0
  52. package/src/graph/load.mjs +9 -8
  53. package/src/hooks/dispatcher.mjs +3 -2
  54. package/src/hooks/shim.mjs +11 -6
  55. package/src/index/govern.mjs +1 -1
  56. package/src/index/map.mjs +1 -1
  57. package/src/index/persist.mjs +2 -2
  58. package/src/index/store.mjs +50 -20
  59. package/src/ledger/index.mjs +2 -2
  60. package/src/local/client.mjs +22 -8
  61. package/src/local/config.mjs +17 -0
  62. package/src/locate.mjs +22 -1
  63. package/src/mcp/prompts.mjs +118 -0
  64. package/src/mcp/resources.mjs +96 -22
  65. package/src/mcp/server.mjs +33 -6
  66. package/src/mcp/tools.mjs +12 -12
  67. package/src/measure/session.mjs +1 -1
  68. package/src/memory/decide.mjs +1 -1
  69. package/src/memory/lessons.mjs +14 -14
  70. package/src/name.mjs +11 -1
  71. package/src/package-version.mjs +12 -10
  72. package/src/packs/manage.mjs +563 -0
  73. package/src/packs.mjs +15 -5
  74. package/src/plugins.mjs +164 -0
  75. package/src/projection/loaders.mjs +84 -10
  76. package/src/projection/manifest.mjs +4 -4
  77. package/src/projection/project.mjs +3 -3
  78. package/src/projection/rules.mjs +24 -4
  79. package/src/projection/surfaces.mjs +20 -2
  80. package/src/review/critic.mjs +4 -4
  81. package/src/toolwords.mjs +8 -5
  82. package/src/tsconfig.mjs +3 -3
  83. package/src/upgrade/plan.mjs +1 -1
  84. /package/bin/{simt-dev.mjs → simt.mjs} +0 -0
@@ -1,8 +1,9 @@
1
1
  // The MCP transport: newline-delimited JSON-RPC 2.0 on stdio.
2
2
  //
3
3
  // This file is the PROTOCOL and nothing else. It knows about `initialize`,
4
- // `tools/list`, `tools/call` and `ping`; it knows nothing about skills, rules or repos.
5
- // The tools themselves live in `src/mcp/tools.mjs`.
4
+ // `tools/list`, `tools/call`, `resources/*`, `prompts/*` and what remains of `ping`; it
5
+ // knows nothing about skills, rules or repos. The tools live in `src/mcp/tools.mjs`, the
6
+ // resources in `src/mcp/resources.mjs`, the prompts in `src/mcp/prompts.mjs`.
6
7
  //
7
8
  // Three decisions, each of which is a place where the obvious implementation is wrong:
8
9
  //
@@ -21,6 +22,7 @@
21
22
 
22
23
  import { createInterface } from 'node:readline';
23
24
  import { CLI } from '../name.mjs';
25
+ import { getPrompt, PROMPTS } from './prompts.mjs';
24
26
  import { RESOURCE_TEMPLATES, listResources, readResource } from './resources.mjs';
25
27
 
26
28
  /**
@@ -96,12 +98,17 @@ const LIST_CACHE = { ttlMs: 3600000, cacheScope: 'private' };
96
98
  * revision would hand it fields it did not ask for. So the version is read from the
97
99
  * request when there is one and remembered only for the clients that need it.
98
100
  *
101
+ * `root` is the repository the read surface answers for. The resources carry no argument
102
+ * channel in the specification - `resources/read` takes a URI and nothing else - so the
103
+ * caller names the root and the default is the process's cwd, which is what an editor's
104
+ * stdio server sits in.
105
+ *
99
106
  * @param {object|null} request a parsed JSON-RPC message
100
107
  * @param {object[]} tools
101
- * @param {{packageVersion?: string, state?: {version: string|null}}} options
108
+ * @param {{packageVersion?: string, state?: {version: string|null}, root?: string}} options
102
109
  * @returns {Promise<object|null>}
103
110
  */
104
- export async function handle(request, tools, { packageVersion, state } = {}) {
111
+ export async function handle(request, tools, { packageVersion, state, root } = {}) {
105
112
  if (request === null || typeof request !== 'object' || Array.isArray(request)) {
106
113
  return error(null, RPC.REQUEST, 'request must be a JSON object');
107
114
  }
@@ -210,7 +217,7 @@ export async function handle(request, tools, { packageVersion, state } = {}) {
210
217
 
211
218
  case 'resources/list':
212
219
  return reply(
213
- cacheable(v) ? { resources: await listResources(), ...LIST_CACHE } : { resources: await listResources() },
220
+ cacheable(v) ? { resources: await listResources(root), ...LIST_CACHE } : { resources: await listResources(root) },
214
221
  );
215
222
  case 'resources/templates/list':
216
223
  return reply(
@@ -225,12 +232,28 @@ export async function handle(request, tools, { packageVersion, state } = {}) {
225
232
  // array as the answer, so "absent" can only be an error.
226
233
  const code = atLeast(v, '2026-07-28') ? RPC.RESOURCE_NOT_FOUND : RPC.RESOURCE_NOT_FOUND_LEGACY;
227
234
  const uri = request.params?.uri;
228
- const found = await readResource(uri);
235
+ const found = await readResource(uri, root);
229
236
  if (found === null) return error(id, code, `no such resource: ${String(uri)}`);
230
237
  return reply(
231
238
  cacheable(v) ? { contents: [found], ...LIST_CACHE } : { contents: [found] },
232
239
  );
233
240
  }
241
+ case 'prompts/list':
242
+ return reply(
243
+ cacheable(v) ? { prompts: PROMPTS, ...LIST_CACHE } : { prompts: PROMPTS },
244
+ );
245
+ case 'prompts/get': {
246
+ const name = params?.name;
247
+ // `getPrompt` returns null only for a prompt this server does not have: its
248
+ // argument failures degrade into the template text itself, because an unfilled
249
+ // variable is still readable and the agent it reaches can ask the user. A named
250
+ // prompt that does not exist is the client's mistake, so it is a protocol error -
251
+ // the same rule as tools/call, and for the same reason: there is no result shape
252
+ // that means absence.
253
+ const found = await getPrompt(name, params?.arguments ?? {});
254
+ if (found === null) return error(id, RPC.METHOD, `no prompt named "${String(name)}"; call prompts/list first`);
255
+ return reply(found);
256
+ }
234
257
  default:
235
258
  return isNotification ? null : error(id, RPC.METHOD, `unknown method "${method}"`);
236
259
  }
@@ -256,6 +279,10 @@ function capabilities() {
256
279
  // ever set: a capability gated on a flag nobody flips is the same promise a client
257
280
  // waits on forever, only quieter.
258
281
  resources: {},
282
+ // Two fixed templates, listed statically: there is nothing to subscribe to and
283
+ // nothing that changes without a release, so the same empty object makes the same
284
+ // promise here as above the resources.
285
+ prompts: {},
259
286
  };
260
287
  }
261
288
 
package/src/mcp/tools.mjs CHANGED
@@ -407,7 +407,7 @@ export const TOOLS = [
407
407
  outputSchema: output.doctor,
408
408
  name: 'doctor',
409
409
  description:
410
- '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.',
410
+ 'Classify a repository: stack tags, emitted facts with the file that proved each, and manifest presence. Call this first when the repo is unknown.',
411
411
  inputSchema: rootArg,
412
412
  async run(args) {
413
413
  const root = rootOf(args);
@@ -430,7 +430,7 @@ export const TOOLS = [
430
430
  outputSchema: output.skills_index,
431
431
  name: 'skills_index',
432
432
  description:
433
- '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.',
433
+ 'Full skill catalogue: every skill with summary, size, stack tags and deferred flag. The fallback when brief returns nothing useful.',
434
434
  inputSchema: noArgs,
435
435
  async run(args) {
436
436
  const { registry: reg, verified, workspace } = await registry(args);
@@ -451,7 +451,7 @@ export const TOOLS = [
451
451
  outputSchema: output.skills_brief,
452
452
  name: 'skills_brief',
453
453
  description:
454
- '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.',
454
+ 'THE entry point. Resolve the skill set for a task: selected with why, deferred, and not selected with reason - the reasons let you override the resolver instead of guessing at it.',
455
455
  inputSchema: {
456
456
  type: 'object',
457
457
  properties: {
@@ -517,7 +517,7 @@ export const TOOLS = [
517
517
  {
518
518
  outputSchema: output.skills_show,
519
519
  name: 'skills_show',
520
- 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.',
520
+ description: 'One skill body and the metadata needed to judge it: source path, the location it was read from, and its cited decisions.',
521
521
  inputSchema: {
522
522
  type: 'object',
523
523
  properties: {
@@ -585,7 +585,7 @@ export const TOOLS = [
585
585
  outputSchema: output.skills_why,
586
586
  name: 'skills_why',
587
587
  description:
588
- '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.',
588
+ '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
589
  inputSchema: {
590
590
  type: 'object',
591
591
  properties: { name: { type: 'string' }, root: { type: 'string' }, task: { type: 'string' } },
@@ -619,7 +619,7 @@ export const TOOLS = [
619
619
  outputSchema: output.rules_list,
620
620
  name: 'rules_list',
621
621
  description:
622
- 'The rule catalogue, and which rules a given repository actually receives. Pass `root` to get the selection rather than just the catalogue.',
622
+ 'The rule catalogue, and which rules a repository actually receives. Pass root for the selection, not just the catalogue.',
623
623
  inputSchema: {
624
624
  type: 'object',
625
625
  properties: { root: { type: 'string' } },
@@ -657,7 +657,7 @@ export const TOOLS = [
657
657
  outputSchema: output.rules_why,
658
658
  name: 'rules_why',
659
659
  description:
660
- '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.',
660
+ 'Why a rule exists: a rule id for the decisions that settled it, or an ADR id for the rules that repeat it. An uncited rule is a finding, reported so.',
661
661
  inputSchema: {
662
662
  type: 'object',
663
663
  properties: { what: { type: 'string', description: 'a rule id or ADR-NNN' } },
@@ -713,7 +713,7 @@ export const TOOLS = [
713
713
  outputSchema: output.check,
714
714
  name: 'check',
715
715
  description:
716
- '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.',
716
+ 'The gate for one repository: region current, unmanaged, drifted or malformed, and rule files lacking an id. Read-only - it never repairs.',
717
717
  inputSchema: rootArg,
718
718
  async run(args) {
719
719
  const root = rootOf(args);
@@ -730,7 +730,7 @@ export const TOOLS = [
730
730
  outputSchema: output.compare,
731
731
  name: 'compare',
732
732
  description:
733
- '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.',
733
+ 'The workspace matrix: every repository with its adoption, region state, pinned SHA, hook routing and gates, plus the problems found.',
734
734
  inputSchema: {
735
735
  type: 'object',
736
736
  properties: {
@@ -761,7 +761,7 @@ export const TOOLS = [
761
761
  {
762
762
  outputSchema: output.plan_next,
763
763
  name: 'plan_next',
764
- 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.',
764
+ description: "The next queued slice whose dependencies are complete; its design section only. Pass id for one slice whether or not it is ready.",
765
765
  inputSchema: {
766
766
  type: 'object',
767
767
  properties: {
@@ -821,7 +821,7 @@ export const TOOLS = [
821
821
  outputSchema: output.brief,
822
822
  name: 'brief',
823
823
  description:
824
- 'The packet for one task, in the bytes a prompt can carry: the kind of task it is, the commands that kind needs, the files the index ranks highest, the tests that cover them, the decisions that govern them, the skills that apply and the lessons that name those paths. Every argument is optional - with no task it describes the repository itself.',
824
+ 'The packet for one task in prompt-sized bytes: task kind, needed commands, ranked files, covering tests, governing decisions, applicable skills. With no task it describes the repository.',
825
825
  inputSchema: {
826
826
  type: 'object',
827
827
  properties: {
@@ -868,7 +868,7 @@ export const TOOLS = [
868
868
  outputSchema: output.federation_query,
869
869
  name: 'federation_query',
870
870
  description:
871
- 'Ask a code-graph server this repository declared one structural question, and return its answer with the name of the server that gave it. Read-only: it reads the "federation" block from simt.json and starts only the servers the manifest itself names, and a repository that declared none is told so rather than queried.',
871
+ 'Ask a declared code-graph server one structural question; the answer names the server that gave it. Starts only servers simt.json names; a repository declaring none is told so.',
872
872
  inputSchema: {
873
873
  type: 'object',
874
874
  properties: {
@@ -11,7 +11,7 @@ import { resolve } from 'node:path';
11
11
  import { tokens } from './context.mjs';
12
12
 
13
13
  const RULE_PATH_RE =
14
- /(?:^|\/)(?:AGENTS(?:\.override)?\.md|CLAUDE(?:\.local)?\.md|GEMINI\.md|SKILL\.md|\.cursor\/rules\/|\.claude\/rules\/|\.agents\/rules\/|\.github\/(?:copilot-instructions\.md|instructions\/)|\.simt-dev\/profiles\/)/;
14
+ /(?:^|\/)(?:AGENTS(?:\.override)?\.md|CLAUDE(?:\.local)?\.md|GEMINI\.md|SKILL\.md|\.cursor\/rules\/|\.claude\/rules\/|\.agents\/rules\/|\.github\/(?:copilot-instructions\.md|instructions\/)|\.simt\/profiles\/)/;
15
15
 
16
16
  const FILE_READ_TOOLS = new Set(['Read', 'read_file', 'NotebookRead', 'view_file', 'read']);
17
17
 
@@ -244,7 +244,7 @@ export async function diffAffectedPaths(root) {
244
244
  const addLines = (stdout) => {
245
245
  for (const line of String(stdout ?? '').split(/\r?\n/)) {
246
246
  const trimmed = line.trim();
247
- if (trimmed && !trimmed.startsWith('.simt-dev/')) paths.add(trimmed);
247
+ if (trimmed && !trimmed.startsWith('.simt/')) paths.add(trimmed);
248
248
  }
249
249
  };
250
250
  for (const args of [
@@ -1,6 +1,6 @@
1
1
  // Deterministic lesson candidate ledger and promotion to `docs/lessons/NNNN-<slug>.md` (plan 0003 R32).
2
2
  //
3
- // Four deterministic candidate sources write to `.simt-dev/lessons.jsonl`:
3
+ // Four deterministic candidate sources write to `.simt/lessons.jsonl`:
4
4
  // 1. `fail-then-fix`: a gate or test that fails and then passes in one session,
5
5
  // recorded as `error signature -> fix diff` (where the signature is the sha256
6
6
  // hash of the normalised first error line);
@@ -23,7 +23,7 @@ import { splitFrontmatter } from './decide.mjs';
23
23
 
24
24
  const exec = promisify(execFile);
25
25
 
26
- export const LESSONS_FILE = '.simt-dev/lessons.jsonl';
26
+ export const LESSONS_FILE = '.simt/lessons.jsonl';
27
27
  export const LESSONS_DIR = 'docs/lessons';
28
28
  export const LESSON_KEYS = new Set(['simt-trigger', 'simt-evidence', 'simt-expires']);
29
29
  const DEFAULT_EXPIRES = '2027-04-01';
@@ -137,7 +137,7 @@ function slugify(text) {
137
137
  }
138
138
 
139
139
  /**
140
- * Read all candidate records from `.simt-dev/lessons.jsonl`.
140
+ * Read all candidate records from `.simt/lessons.jsonl`.
141
141
  *
142
142
  * @param {string} root
143
143
  * @returns {Promise<{candidates: Array<object>, errors: string[]}>}
@@ -181,7 +181,7 @@ function sameEvidence(a = [], b = []) {
181
181
  }
182
182
 
183
183
  /**
184
- * Append one deterministic candidate to `.simt-dev/lessons.jsonl`, deduplicating by
184
+ * Append one deterministic candidate to `.simt/lessons.jsonl`, deduplicating by
185
185
  * `(kind, signature, evidence)`.
186
186
  *
187
187
  * @param {string} root
@@ -239,11 +239,11 @@ async function diffBetweenTrees(root, fromRef, toRef) {
239
239
  }
240
240
  try {
241
241
  const [namesOut, diffOut] = await Promise.all([
242
- exec('git', ['diff', '--name-only', fromRef, toRef, '--', '.', ':(exclude).simt-dev'], {
242
+ exec('git', ['diff', '--name-only', fromRef, toRef, '--', '.', ':(exclude).simt'], {
243
243
  cwd: root,
244
244
  maxBuffer: 8 << 20,
245
245
  }),
246
- exec('git', ['diff', '--unified=3', fromRef, toRef, '--', '.', ':(exclude).simt-dev'], {
246
+ exec('git', ['diff', '--unified=3', fromRef, toRef, '--', '.', ':(exclude).simt'], {
247
247
  cwd: root,
248
248
  maxBuffer: 8 << 20,
249
249
  }),
@@ -295,8 +295,8 @@ export async function recordFailThenFix(root, { failEntry, passEntry, failOutput
295
295
  }
296
296
 
297
297
  /**
298
- * Scan `.simt-dev/ledger.jsonl` for any fail-then-fix sequences in the session and
299
- * ensure each is recorded in `.simt-dev/lessons.jsonl`.
298
+ * Scan `.simt/ledger.jsonl` for any fail-then-fix sequences in the session and
299
+ * ensure each is recorded in `.simt/lessons.jsonl`.
300
300
  *
301
301
  * @param {string} root
302
302
  * @param {Array<object>} [entries]
@@ -306,7 +306,7 @@ export async function captureFromLedger(root, entries = null) {
306
306
  let list = entries;
307
307
  if (!Array.isArray(list)) {
308
308
  try {
309
- const raw = await readFile(join(root, '.simt-dev', 'ledger.jsonl'), 'utf8');
309
+ const raw = await readFile(join(root, '.simt', 'ledger.jsonl'), 'utf8');
310
310
  list = raw
311
311
  .split('\n')
312
312
  .map((s) => s.trim())
@@ -339,7 +339,7 @@ export async function captureFromLedger(root, entries = null) {
339
339
  }
340
340
 
341
341
  /**
342
- * Capture reverted commits from git history into `.simt-dev/lessons.jsonl`.
342
+ * Capture reverted commits from git history into `.simt/lessons.jsonl`.
343
343
  *
344
344
  * @param {string} root
345
345
  * @param {{limit?: number}} [options]
@@ -408,7 +408,7 @@ export async function captureRevertedCommits(root, options = {}) {
408
408
  }
409
409
 
410
410
  /**
411
- * Record an ADR-008 `BLOCK` review finding in `.simt-dev/lessons.jsonl`.
411
+ * Record an ADR-008 `BLOCK` review finding in `.simt/lessons.jsonl`.
412
412
  *
413
413
  * @param {string} root
414
414
  * @param {{summary: string, paths?: string[], evidence?: string[], fixDiff?: string, keywords?: string[]}} finding
@@ -437,7 +437,7 @@ export async function recordReviewBlock(root, finding) {
437
437
  }
438
438
 
439
439
  /**
440
- * Record an agent-time hook denial (R15) in `.simt-dev/lessons.jsonl`.
440
+ * Record an agent-time hook denial (R15) in `.simt/lessons.jsonl`.
441
441
  *
442
442
  * @param {string} root
443
443
  * @param {{harness?: string, event?: string, policies?: string[], messages?: string[], command?: string, writes?: string[]}} denial
@@ -612,7 +612,7 @@ async function nextLessonNumber(root) {
612
612
  }
613
613
 
614
614
  /**
615
- * Promote a lesson candidate from `.simt-dev/lessons.jsonl` to `docs/lessons/NNNN-<slug>.md`.
615
+ * Promote a lesson candidate from `.simt/lessons.jsonl` to `docs/lessons/NNNN-<slug>.md`.
616
616
  *
617
617
  * @param {string} root
618
618
  * @param {{id?: string, apply?: boolean, paths?: string[], expires?: string, title?: string}} [options]
@@ -622,7 +622,7 @@ export async function promoteLesson(root, options = {}) {
622
622
  await captureFromLedger(root);
623
623
  const { candidates } = await readLessonCandidates(root);
624
624
  if (candidates.length === 0) {
625
- return { ok: false, error: 'no lesson candidates in .simt-dev/lessons.jsonl' };
625
+ return { ok: false, error: 'no lesson candidates in .simt/lessons.jsonl' };
626
626
  }
627
627
 
628
628
  let target = null;
package/src/name.mjs CHANGED
@@ -45,6 +45,16 @@ export const MARKER_PREFIX_LEGACY = 'simt-dev';
45
45
  /** The config directory on the adopting engineer's machine. LOCKED, same argument. */
46
46
  export const CONFIG_DIR = PROJECT;
47
47
 
48
+ /**
49
+ * The per-repository state directory: the index, ledger, benchmark runs and local-model
50
+ * proposals a repository writes for itself. Renamed from `.simt-dev` to `.simt`; the
51
+ * rename is unreleased, so no repository carries the old spelling as a name this tool
52
+ * cannot rewrite. Deliberately not derived from CONFIG_DIR: that is the user-level
53
+ * config directory, and a derivation would let two different on-disk names agree by
54
+ * accident rather than by decision.
55
+ */
56
+ export const STATE_DIR = '.simt';
57
+
48
58
  /**
49
59
  * The gate this tool writes into a repository. LOCKED: it is a path in `.github`, and
50
60
  * a repo that renames the file silently stops running the gate it was adopted with.
@@ -92,4 +102,4 @@ export const CLI = 'simt';
92
102
  export const PACKAGE_NAME = PROJECT;
93
103
 
94
104
  /** The entry point, relative to the package root. LOCKED: `bin` in package.json. */
95
- export const BIN_REL = 'bin/simt-dev.mjs';
105
+ export const BIN_REL = 'bin/simt.mjs';
@@ -3,14 +3,16 @@
3
3
  // Three distinct things that must not be confused:
4
4
  // - packageVersion() is the version of the binary that is RUNNING. It is read from
5
5
  // the package this code lives in, so it is correct whether the binary was resolved
6
- // from node_modules, a global install, or a source checkout.
6
+ // from node_modules, the release binary in `.simt/bin`, a global install, or a
7
+ // source checkout.
7
8
  // - hasNpm(root) is the KIND of repository: does it have a package.json? That decides
8
- // how it obtains simt-dev at all — pinned as a devDependency where one exists, a
9
- // pinned global install where there is no node_modules to pin in.
9
+ // how it obtains simt-dev at all — pinned as a devDependency where one exists, the
10
+ // release binary into the repository where there is no node_modules to pin in
11
+ // (ADR-098).
10
12
  // - the pin is the version a repository recorded when it adopted simt-dev, stored in
11
- // the manifest's projection.version. It is what a global install must match: npm
12
- // pins the node_modules copy for us, but nothing pins a global binary, so the gate
13
- // compares the running version against the pin and refuses on a mismatch.
13
+ // the manifest's projection.version. It is what an install outside node_modules
14
+ // must match: npm pins the node_modules copy for us, but nothing pins a binary, so
15
+ // the gate compares the running version against the pin and refuses on a mismatch.
14
16
 
15
17
  import { readFileSync } from 'node:fs';
16
18
  import { stat } from 'node:fs/promises';
@@ -38,7 +40,7 @@ export function packageVersion() {
38
40
 
39
41
  /**
40
42
  * Whether a repository has a package.json, i.e. whether it pins simt-dev as a
41
- * devDependency (npm) or must fall back to a global install. Async because the render
43
+ * devDependency (npm) or must download the release binary (ADR-098). Async because the render
42
44
  * call sites that feed it (init, hooks install, the compare stale check) are all async,
43
45
  * and a missing file is an answer (false), not an error.
44
46
  */
@@ -56,9 +58,9 @@ export async function hasNpm(root) {
56
58
  * simt-dev. Returns null when there is nothing to refuse, otherwise a message that
57
59
  * names both versions and the one command that fixes it.
58
60
  *
59
- * This is the gate's fail-closed guard against a stale global binary: for a repository
60
- * without npm, simt-dev is a global install and the only thing that knows which version
61
- * it should be is the pin. A node_modules copy is pinned by npm itself, so its mismatch
61
+ * This is the gate's fail-closed guard against a stale binary: for a repository
62
+ * without npm, the copy is the release binary in `.simt/bin` (ADR-098) and the only
63
+ * thing that knows which version it should be is the pin. A node_modules copy is pinned by npm itself, so its mismatch
62
64
  * is a stale manifest, which `upgrade` refreshes — the remedy is the same either way.
63
65
  *
64
66
  * A manifest with no recorded pin (adopted before the field existed) is skipped, not