simt-dev 0.6.3 → 0.7.1
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 +162 -841
- package/README.md +21 -135
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/package.json +13 -3
- package/rules/core.md +9 -2
- package/rules/mdc/slice-commit.mdc +57 -4
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +3 -3
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +3 -2
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +141 -9
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/prompt.mjs +10 -3
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/api.mjs +29 -0
- package/src/argv.mjs +37 -3
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/adapters.json +16 -0
- package/src/bench/corpus.mjs +150 -0
- package/src/bench/grade.mjs +23 -49
- package/src/bench/mine.mjs +247 -36
- package/src/bench/probes.mjs +104 -68
- package/src/bench/process.mjs +128 -0
- package/src/bench/report.mjs +260 -84
- package/src/bench/run.mjs +251 -150
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +44 -3
- package/src/ci/workflow.mjs +46 -14
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +205 -0
- package/src/commands/agenthook.mjs +113 -20
- package/src/commands/bench.mjs +192 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +2 -1
- package/src/commands/compare.mjs +50 -49
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +10 -9
- package/src/commands/distill.mjs +4 -3
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +19 -12
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +98 -28
- package/src/commands/graph.mjs +20 -8
- package/src/commands/harness.mjs +3 -2
- package/src/commands/help.mjs +160 -31
- package/src/commands/hooks.mjs +163 -50
- package/src/commands/ignore.mjs +2 -1
- package/src/commands/impact.mjs +8 -9
- package/src/commands/index.mjs +278 -180
- package/src/commands/init.mjs +171 -38
- package/src/commands/lease.mjs +206 -39
- package/src/commands/lessons.mjs +72 -15
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +47 -27
- package/src/commands/mcp.mjs +0 -1
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +3 -3
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +5 -3
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +124 -33
- package/src/commands/shared.mjs +2 -3
- package/src/commands/skills.mjs +82 -45
- package/src/commands/skillscompile.mjs +12 -12
- package/src/commands/skillsinstall.mjs +14 -2
- package/src/commands/slice.mjs +396 -26
- package/src/commands/spec.mjs +151 -0
- package/src/commands/status.mjs +4 -7
- package/src/commands/sync.mjs +50 -33
- package/src/commands/tests.mjs +11 -11
- package/src/commands/tokens.mjs +2 -2
- package/src/commands/undo.mjs +29 -31
- package/src/commands/upgrade.mjs +40 -4
- package/src/commands/why.mjs +19 -12
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -3
- package/src/completions.mjs +5 -0
- package/src/context.mjs +64 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +17 -2
- package/src/distill/engine.mjs +5 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +42 -20
- package/src/hooks/capability.mjs +17 -4
- package/src/hooks/dispatcher.mjs +513 -36
- package/src/hooks/gates.mjs +0 -43
- package/src/hooks/resolve.mjs +11 -10
- package/src/hooks/shim.mjs +17 -9
- package/src/identity.mjs +96 -15
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +307 -47
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +372 -84
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +97 -15
- package/src/ledger/index.mjs +120 -25
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/resources.mjs +12 -13
- package/src/mcp/server.mjs +151 -38
- package/src/mcp/tools.mjs +90 -16
- package/src/measure/session.mjs +3 -2
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +111 -3
- package/src/packs/manage.mjs +17 -3
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +31 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +6 -2
- package/src/prbody.mjs +7 -5
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +31 -14
- package/src/projection/manifest.mjs +89 -53
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +58 -6
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +88 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +24 -15
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +6 -3
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +8 -0
- package/src/tsconfig.mjs +39 -99
- package/src/workspace.mjs +55 -10
- package/templates/plan/tracker.json +1 -1
- package/docs/decisions/README.md +0 -591
package/src/graph/load.mjs
CHANGED
|
@@ -117,7 +117,9 @@ async function defaultRegistry(flags) {
|
|
|
117
117
|
* failure to resolve is a WARNING, not 38 `ENOENT` lines (ADR-014).
|
|
118
118
|
*
|
|
119
119
|
* Memoised per the inputs that decide the answer: the `--workspace` flag, the
|
|
120
|
-
* `SIMT_DEV_WORKSPACE` environment
|
|
120
|
+
* `SIMT_DEV_WORKSPACE` environment, the user-config location, and the current
|
|
121
|
+
* directory, whose manifest declares the workspace the variable may point into
|
|
122
|
+
* (ADR-119). A single
|
|
121
123
|
* first-call-wins promise served one caller's workspace to every later caller with
|
|
122
124
|
* different flags (Q14) — `skills_index({workspace: ws})` followed by
|
|
123
125
|
* `skills_index({})` answered `ws` twice. Callers with the same inputs still share
|
|
@@ -129,6 +131,7 @@ export function workspace(flags = {}) {
|
|
|
129
131
|
flags?.workspace ?? null,
|
|
130
132
|
process.env.SIMT_DEV_WORKSPACE ?? null,
|
|
131
133
|
process.env.XDG_CONFIG_HOME ?? null,
|
|
134
|
+
process.cwd(),
|
|
132
135
|
]);
|
|
133
136
|
if (!workspacePromises.has(key)) workspacePromises.set(key, resolveWorkspace(flags));
|
|
134
137
|
return workspacePromises.get(key);
|
|
@@ -154,10 +157,15 @@ export async function loadRegistry(path = null, flags = {}) {
|
|
|
154
157
|
// errors are facts about ABSENCE - no registry here, or more than one and the reader must
|
|
155
158
|
// pick - and are reported as such. A named path that is missing is a different fact: the
|
|
156
159
|
// reader pointed at a file that is not there, and it names that path.
|
|
157
|
-
|
|
160
|
+
//
|
|
161
|
+
// Resolution order for the registry itself (review 0003 Q37): positional path,
|
|
162
|
+
// then `--registry`, then `SIMT_REGISTRY`, then the default tiers above.
|
|
163
|
+
const explicit =
|
|
164
|
+
path ?? flags?.registry ?? process.env.SIMT_REGISTRY ?? process.env.SIMT_DEV_REGISTRY ?? null;
|
|
165
|
+
let where = explicit;
|
|
158
166
|
let resolveErrors = null;
|
|
159
167
|
let resolvedWorkspace = null;
|
|
160
|
-
if (
|
|
168
|
+
if (explicit === null) {
|
|
161
169
|
const found = await defaultRegistry(flags);
|
|
162
170
|
where = found.where;
|
|
163
171
|
resolveErrors = found.errors;
|
|
@@ -181,7 +189,7 @@ export async function loadRegistry(path = null, flags = {}) {
|
|
|
181
189
|
registry = JSON.parse(await readFile(where, 'utf8'));
|
|
182
190
|
} catch (error) {
|
|
183
191
|
const message =
|
|
184
|
-
|
|
192
|
+
explicit === null && error.code === 'ENOENT'
|
|
185
193
|
? `no skill registry at ${where}; create one there, or name it explicitly`
|
|
186
194
|
: `${where}: unreadable or invalid JSON (${error.message})`;
|
|
187
195
|
return {
|
|
@@ -260,6 +268,11 @@ export async function loadRegistry(path = null, flags = {}) {
|
|
|
260
268
|
* broken registry rather than a wrong pointer, which is exactly backwards.
|
|
261
269
|
*/
|
|
262
270
|
function holdsAnyRepo(registry, root) {
|
|
271
|
+
// Every repo the registry names, vendored or not: the question is whether this
|
|
272
|
+
// directory is a workspace at all. Filtering out vendored sources made the check
|
|
273
|
+
// unsatisfiable the moment the last non-vendored skill left the registry - an
|
|
274
|
+
// all-vendored registry names repos just the same, and a home folder holds none
|
|
275
|
+
// of them.
|
|
263
276
|
const named = new Set(
|
|
264
277
|
(registry.skills ?? []).flatMap((s) => (s.sources ?? []).map((x) => x.repo)).filter(Boolean),
|
|
265
278
|
);
|
|
@@ -282,14 +295,20 @@ export function lintRegistry(registry) {
|
|
|
282
295
|
const threshold = registry.deferredThreshold ?? 20000;
|
|
283
296
|
|
|
284
297
|
// --- shape -----------------------------------------------------------------
|
|
298
|
+
// Every check below assumes arrays of objects. A registry that fails the shape
|
|
299
|
+
// is already fully described by the shape error; continuing past it only
|
|
300
|
+
// trades one true error for a crash (`for..of` over `undefined`) or a pile of
|
|
301
|
+
// secondary noise, so the lint stops here (review 0003 Q35).
|
|
285
302
|
if (!Array.isArray(registry.capabilities) || capabilities.size === 0) {
|
|
286
303
|
err('capabilities: missing or empty');
|
|
287
304
|
}
|
|
288
305
|
if (!Array.isArray(registry.skills) || skills.length === 0) {
|
|
289
306
|
err('skills: missing or empty');
|
|
307
|
+
}
|
|
308
|
+
if (errors.length > 0) {
|
|
290
309
|
return { sync: errors, warnings, index };
|
|
291
310
|
}
|
|
292
|
-
for (const c of registry.capabilities) {
|
|
311
|
+
for (const c of registry.capabilities ?? []) {
|
|
293
312
|
if (!c.id || !c.summary) err(`capability ${c.id ?? '<no id>'}: needs id and summary`);
|
|
294
313
|
}
|
|
295
314
|
|
|
@@ -449,22 +468,22 @@ function checkRequires(at, skill, index, err) {
|
|
|
449
468
|
if (!index.has(dep)) err(`${at}: requires unknown skill "${dep}"`);
|
|
450
469
|
if (dep === skill.name) err(`${at}: requires itself`);
|
|
451
470
|
}
|
|
452
|
-
// Cycle detection over `requires
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
471
|
+
// Cycle detection over `requires`, depth-first with a recursion stack. The old
|
|
472
|
+
// breadth-first walk shared one `seen` set across branches, so a diamond
|
|
473
|
+
// (a requires b and c; both require d) reported d's second visit as a cycle.
|
|
474
|
+
// Only a dependency already on the current path is a cycle; a node reached
|
|
475
|
+
// through two branches is sharing, not looping (review 0003 Q35).
|
|
476
|
+
const visit = (name, path) => {
|
|
477
|
+
const stack = [...path, name];
|
|
478
|
+
for (const dep of index.get(name)?.requires ?? []) {
|
|
479
|
+
if (stack.includes(dep)) {
|
|
480
|
+
err(`requires cycle: ${[...stack, dep].join(' -> ')}`);
|
|
461
481
|
return;
|
|
462
482
|
}
|
|
463
|
-
|
|
464
|
-
next.push(...(index.get(dep)?.requires ?? []));
|
|
483
|
+
visit(dep, stack);
|
|
465
484
|
}
|
|
466
|
-
|
|
467
|
-
|
|
485
|
+
};
|
|
486
|
+
visit(skill.name, []);
|
|
468
487
|
}
|
|
469
488
|
|
|
470
489
|
function checkAlias(at, skill, index, err) {
|
|
@@ -570,6 +589,10 @@ async function lintSourcesOnDisk(registry, workspaceRoot) {
|
|
|
570
589
|
const warnings = [];
|
|
571
590
|
|
|
572
591
|
for (const skill of registry.skills ?? []) {
|
|
592
|
+
// A vendored skill ships with the package; its source is the package root,
|
|
593
|
+
// not a workspace member. Verifying it against the workspace filesystem is
|
|
594
|
+
// the wrong check (the path will not exist there by design).
|
|
595
|
+
if (skill.vendored) continue;
|
|
573
596
|
for (const source of skill.sources ?? []) {
|
|
574
597
|
const at = `${skill.name}: ${source.repo}/${source.path}`;
|
|
575
598
|
if (!source.repo || !source.path) continue;
|
|
@@ -644,8 +667,7 @@ export async function measureSkill(full) {
|
|
|
644
667
|
hasEntry = false;
|
|
645
668
|
}
|
|
646
669
|
if (!hasEntry) {
|
|
647
|
-
|
|
648
|
-
throw Object.assign(new Error(`${full} has no SKILL.md entry point${md}`), { code: 'NO_SKILL_MD' });
|
|
670
|
+
throw Object.assign(new Error(`${full} has no SKILL.md entry point`), { code: 'NO_SKILL_MD' });
|
|
649
671
|
}
|
|
650
672
|
|
|
651
673
|
return { bytes, files, flat: false };
|
package/src/hooks/capability.mjs
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
import { constants as fsConstants } from 'node:fs';
|
|
22
22
|
import { access, readdir, readFile, stat } from 'node:fs/promises';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import { ECOSYSTEMS
|
|
24
|
+
import { ECOSYSTEMS } from '../projection/manifest.mjs';
|
|
25
|
+
import { loadManifest } from '../manifest/schema.mjs';
|
|
26
|
+
import { resolveNodeRunner } from '../ecosystem/node-runner.mjs';
|
|
25
27
|
|
|
26
28
|
/** Binaries the shared dispatcher shells out to directly, rather than via a script. */
|
|
27
29
|
const BIN_DIR = join('node_modules', '.bin');
|
|
@@ -104,10 +106,22 @@ async function probeTool(root, eco) {
|
|
|
104
106
|
export async function readCapability(root) {
|
|
105
107
|
const capability = { scripts: {}, binaries: [], commands: {}, tools: [], runner: null, error: null };
|
|
106
108
|
|
|
109
|
+
// Q31: a package.json names a Node repo, but not which manager it installs
|
|
110
|
+
// with. The lockfile on disk wins over the `packageManager` field (the field
|
|
111
|
+
// can disagree; `checkToolchain` reports that), and the field wins over the
|
|
112
|
+
// npm default — so a `packageManager: pnpm@9` repo resolves pnpm before its
|
|
113
|
+
// first install, and every consumer below reads the same answer.
|
|
114
|
+
let packageManager = null;
|
|
107
115
|
try {
|
|
108
116
|
const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
109
117
|
capability.scripts = manifest.scripts ?? {};
|
|
110
|
-
|
|
118
|
+
packageManager = typeof manifest.packageManager === 'string' ? manifest.packageManager : null;
|
|
119
|
+
const lockfiles = [];
|
|
120
|
+
for (const file of ['pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb', 'package-lock.json']) {
|
|
121
|
+
if (await fileExists(join(root, file))) lockfiles.push(file);
|
|
122
|
+
}
|
|
123
|
+
capability.runner = resolveNodeRunner({ lockfiles, packageManager }).id;
|
|
124
|
+
capability.packageManager = packageManager;
|
|
111
125
|
} catch (error) {
|
|
112
126
|
// A repo with no manifest is a normal shape — a Python or Gradle repo has no
|
|
113
127
|
// package.json at all — so this is reported, never thrown. The gate filter treats
|
|
@@ -157,8 +171,7 @@ export async function readCapability(root) {
|
|
|
157
171
|
// so the only evidence that can prove it is the declaration itself. A repo with no
|
|
158
172
|
// manifest simply has no declared commands.
|
|
159
173
|
try {
|
|
160
|
-
|
|
161
|
-
capability.commands = declaredCommands(manifest);
|
|
174
|
+
capability.commands = declaredCommands((await loadManifest(root)).manifest);
|
|
162
175
|
} catch {
|
|
163
176
|
capability.commands = {};
|
|
164
177
|
}
|