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.
- package/CHANGELOG.md +122 -0
- package/README.md +78 -59
- package/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +13 -0
- package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +1 -1
- package/docs/decisions/ADR-097-the-channels-publish-by-hand-and-the-version-rides-in-as-an-input.md +71 -0
- package/docs/decisions/ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md +149 -0
- package/package.json +4 -4
- package/src/agenthooks/render.mjs +87 -21
- package/src/argv.mjs +13 -0
- package/src/bench/ablate.mjs +1 -3
- package/src/bench/grade.mjs +1 -1
- package/src/bench/mine.mjs +3 -3
- package/src/bench/probes.mjs +1 -3
- package/src/bench/report.mjs +1 -3
- package/src/bench/run.mjs +1 -3
- package/src/bench/tasks.mjs +1 -3
- package/src/bench/variants.mjs +1 -3
- package/src/brief/assemble.mjs +2 -4
- package/src/ci/dependabot.mjs +1 -1
- package/src/ci/workflow.mjs +40 -12
- package/src/commands/agenthook.mjs +6 -2
- package/src/commands/bench.mjs +4 -4
- package/src/commands/ci.mjs +1 -1
- package/src/commands/compare.mjs +1 -1
- package/src/commands/distill.mjs +1 -1
- package/src/commands/gate.mjs +2 -2
- package/src/commands/graph.mjs +2 -2
- package/src/commands/harness.mjs +87 -14
- package/src/commands/help.mjs +29 -7
- package/src/commands/hooks.mjs +1 -1
- package/src/commands/index.mjs +17 -3
- package/src/commands/init.mjs +2 -2
- package/src/commands/local.mjs +11 -0
- package/src/commands/mcp.mjs +1 -1
- package/src/commands/pack.mjs +246 -0
- package/src/commands/plugin.mjs +61 -0
- package/src/commands/rules.mjs +1 -1
- package/src/commands/skills.mjs +1 -1
- package/src/commands/skillscompile.mjs +1 -3
- package/src/commands/skillsinstall.mjs +1 -1
- package/src/commands/sync.mjs +8 -8
- package/src/commands/tokens.mjs +1 -1
- package/src/commands/upgrade.mjs +1 -1
- package/src/commands/why.mjs +1 -1
- package/src/compare/discover.mjs +1 -1
- package/src/compare/hooksstate.mjs +19 -7
- package/src/compare/learn.mjs +2 -2
- package/src/compare/row.mjs +3 -3
- package/src/decisions/index.mjs +1 -1
- package/src/distill/distill.mjs +2 -2
- package/src/distribute/release.mjs +142 -0
- package/src/graph/load.mjs +9 -8
- package/src/hooks/dispatcher.mjs +3 -2
- package/src/hooks/shim.mjs +11 -6
- package/src/index/govern.mjs +1 -1
- package/src/index/map.mjs +1 -1
- package/src/index/persist.mjs +2 -2
- package/src/index/store.mjs +50 -20
- package/src/ledger/index.mjs +2 -2
- package/src/local/client.mjs +22 -8
- package/src/local/config.mjs +17 -0
- package/src/locate.mjs +22 -1
- package/src/mcp/prompts.mjs +118 -0
- package/src/mcp/resources.mjs +96 -22
- package/src/mcp/server.mjs +33 -6
- package/src/mcp/tools.mjs +12 -12
- package/src/measure/session.mjs +1 -1
- package/src/memory/decide.mjs +1 -1
- package/src/memory/lessons.mjs +14 -14
- package/src/name.mjs +11 -1
- package/src/package-version.mjs +12 -10
- package/src/packs/manage.mjs +563 -0
- package/src/packs.mjs +15 -5
- package/src/plugins.mjs +164 -0
- package/src/projection/loaders.mjs +84 -10
- package/src/projection/manifest.mjs +4 -4
- package/src/projection/project.mjs +3 -3
- package/src/projection/rules.mjs +24 -4
- package/src/projection/surfaces.mjs +20 -2
- package/src/review/critic.mjs +4 -4
- package/src/toolwords.mjs +8 -5
- package/src/tsconfig.mjs +3 -3
- package/src/upgrade/plan.mjs +1 -1
- /package/bin/{simt-dev.mjs → simt.mjs} +0 -0
package/src/mcp/server.mjs
CHANGED
|
@@ -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
|
|
5
|
-
// The tools
|
|
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:
|
|
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
|
-
'
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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: {
|
package/src/measure/session.mjs
CHANGED
|
@@ -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
|
|
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
|
|
package/src/memory/decide.mjs
CHANGED
|
@@ -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
|
|
247
|
+
if (trimmed && !trimmed.startsWith('.simt/')) paths.add(trimmed);
|
|
248
248
|
}
|
|
249
249
|
};
|
|
250
250
|
for (const args of [
|
package/src/memory/lessons.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
299
|
-
* ensure each is recorded in `.simt
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
105
|
+
export const BIN_REL = 'bin/simt.mjs';
|
package/src/package-version.mjs
CHANGED
|
@@ -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
|
|
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,
|
|
9
|
-
//
|
|
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
|
|
12
|
-
// pins the node_modules copy for us, but nothing pins a
|
|
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
|
|
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
|
|
60
|
-
* without npm,
|
|
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
|