simt-dev 0.6.3 → 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.
- package/CHANGELOG.md +35 -0
- package/README.md +2 -2
- package/agents/simt-agent.md +84 -0
- package/docs/decisions/ADR-009-skill-layout.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-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/README.md +9 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- 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 +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +8 -0
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +109 -15
- package/src/commands/bench.mjs +2 -1
- package/src/commands/compare.mjs +8 -7
- package/src/commands/decide.mjs +6 -5
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +35 -14
- package/src/commands/hooks.mjs +68 -25
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +17 -4
- package/src/commands/init.mjs +169 -37
- package/src/commands/lease.mjs +203 -35
- package/src/commands/lessons.mjs +65 -9
- package/src/commands/map.mjs +33 -18
- package/src/commands/plugin.mjs +1 -1
- package/src/commands/repoindex.mjs +1 -0
- package/src/commands/rules.mjs +92 -18
- package/src/commands/skills.mjs +60 -25
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +12 -0
- package/src/commands/slice.mjs +377 -18
- package/src/commands/sync.mjs +37 -22
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +24 -16
- package/src/commands/upgrade.mjs +33 -1
- package/src/commands/why.mjs +10 -4
- 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 -1
- package/src/completions.mjs +5 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +38 -19
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +335 -13
- package/src/hooks/resolve.mjs +9 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- 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 +296 -77
- 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 +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +81 -22
- package/src/mcp/tools.mjs +45 -9
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- 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 +21 -14
- package/src/projection/manifest.mjs +69 -19
- 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 +54 -3
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- package/templates/plan/tracker.json +1 -1
package/src/index/extract.mjs
CHANGED
|
@@ -51,13 +51,19 @@ export function extract(filePath, text, lang) {
|
|
|
51
51
|
return { symbols, imports: found.imports };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Import specifiers become edges. A relative one points at a tracked file when
|
|
56
|
+
* one matches; a non-relative one goes through the language's namespace
|
|
57
|
+
* strategy below (review 0003 Q40).
|
|
58
|
+
*/
|
|
55
59
|
export function resolveImports(filePath, imports, files, lang) {
|
|
56
60
|
const have = files instanceof Set ? files : new Set(files);
|
|
61
|
+
const stems = indexByStem(have);
|
|
62
|
+
const dirs = lang === 'go' || lang === 'csharp' ? indexDirs(have, lang === 'go' ? '.go' : '.cs') : null;
|
|
57
63
|
const edges = [];
|
|
58
64
|
const seen = new Set();
|
|
59
65
|
for (const spec of imports) {
|
|
60
|
-
const dst = resolveOne(filePath, spec, have, lang);
|
|
66
|
+
const dst = resolveOne(filePath, spec, have, stems, dirs, lang);
|
|
61
67
|
if (seen.has(dst)) continue;
|
|
62
68
|
seen.add(dst);
|
|
63
69
|
edges.push({ src: filePath, dst, kind: 'import', weight: 1 });
|
|
@@ -66,9 +72,142 @@ export function resolveImports(filePath, imports, files, lang) {
|
|
|
66
72
|
return edges;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
/** Stem (basename) to tracked paths, so a namespace lookup touches its stem group, not the tree. */
|
|
76
|
+
function indexByStem(have) {
|
|
77
|
+
const map = new Map();
|
|
78
|
+
for (const file of have) {
|
|
79
|
+
const stem = file.slice(file.lastIndexOf('/') + 1);
|
|
80
|
+
const list = map.get(stem);
|
|
81
|
+
if (list) list.push(file);
|
|
82
|
+
else map.set(stem, [file]);
|
|
83
|
+
}
|
|
84
|
+
return map;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Directory to files of one extension, for the languages whose imports name a package, not a file. */
|
|
88
|
+
function indexDirs(have, ext) {
|
|
89
|
+
const map = new Map();
|
|
90
|
+
for (const file of have) {
|
|
91
|
+
if (!file.endsWith(ext)) continue;
|
|
92
|
+
const dir = file.includes('/') ? file.slice(0, file.lastIndexOf('/')) : '';
|
|
93
|
+
const list = map.get(dir);
|
|
94
|
+
if (list) list.push(file);
|
|
95
|
+
else map.set(dir, [file]);
|
|
96
|
+
}
|
|
97
|
+
return map;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One strategy per namespace language (review 0003 Q40).
|
|
102
|
+
*
|
|
103
|
+
* Outside JS/TS, imports name namespaces: `com.foo.Bar`, `pkg.mod`,
|
|
104
|
+
* `example.com/mod/pkg`, `crate::a::b`, `A.B.C`. Each builder maps the spec
|
|
105
|
+
* to ordered path candidates; the first candidate with exactly one tracked hit
|
|
106
|
+
* wins, several hits stay the raw specifier (ambiguous is not resolved), and
|
|
107
|
+
* none keeps the old behaviour. Languages whose imports cannot name a file -
|
|
108
|
+
* a C# `using` names a namespace shared by many files, a Go import names a
|
|
109
|
+
* package - resolve only the single-file case and keep the rest as specs.
|
|
110
|
+
*/
|
|
111
|
+
function nonRelativeCandidates(spec, lang) {
|
|
112
|
+
if (lang === 'kotlin' || lang === 'java') {
|
|
113
|
+
const parts = spec.split('.');
|
|
114
|
+
if (parts.length < 2 || parts.some((p) => p === '')) return null;
|
|
115
|
+
return dottedFileCandidates(parts, lang === 'kotlin' ? ['.kt', '.kts'] : ['.java'], null);
|
|
116
|
+
}
|
|
117
|
+
if (lang === 'python') {
|
|
118
|
+
const parts = spec.split('.');
|
|
119
|
+
if (parts.some((p) => p === '')) return null;
|
|
120
|
+
const base = parts.join('/');
|
|
121
|
+
return [`${base}.py`, `${base}/__init__.py`];
|
|
122
|
+
}
|
|
123
|
+
if (lang === 'rust') {
|
|
124
|
+
const parts = spec.split('::');
|
|
125
|
+
if (parts[0] === 'crate' || parts[0] === 'self' || parts[0] === 'super') parts.shift();
|
|
126
|
+
else if (parts[0] === '') return null;
|
|
127
|
+
if (parts.length === 0 || parts.some((p) => p === '')) return null;
|
|
128
|
+
return dottedFileCandidates(parts, ['.rs'], 'mod.rs');
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Full dotted path first, then dropping a trailing Uppercase segment: a nested
|
|
135
|
+
* type lives in its outer file (`com.foo.Outer.Inner` → `Outer.kt`). The drop
|
|
136
|
+
* stops at a lowercase segment, so `java.util.List` never becomes `util.java` -
|
|
137
|
+
* a stdlib import must not resolve to a coincidental file of that name.
|
|
138
|
+
*/
|
|
139
|
+
function dottedFileCandidates(parts, exts, modFile) {
|
|
140
|
+
const out = [];
|
|
141
|
+
const segs = [...parts];
|
|
142
|
+
for (;;) {
|
|
143
|
+
const base = segs.join('/');
|
|
144
|
+
for (const ext of exts) out.push(`${base}${ext}`);
|
|
145
|
+
if (modFile) out.push(`${base}/${modFile}`);
|
|
146
|
+
const last = segs[segs.length - 1] ?? '';
|
|
147
|
+
const outer = segs[segs.length - 2] ?? '';
|
|
148
|
+
if (segs.length < 2 || !/^[A-Z]/.test(last) || (segs.length > 2 && !/^[A-Z]/.test(outer))) break;
|
|
149
|
+
segs.pop();
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** First candidate with exactly one tracked hit wins; several hits are ambiguous, not resolved. */
|
|
155
|
+
function suffixHit(candidates, have, stems) {
|
|
156
|
+
for (const candidate of candidates) {
|
|
157
|
+
if (have.has(candidate)) return candidate;
|
|
158
|
+
const stem = candidate.slice(candidate.lastIndexOf('/') + 1);
|
|
159
|
+
const hits = (stems.get(stem) ?? []).filter((f) => f === candidate || f.endsWith(`/${candidate}`));
|
|
160
|
+
if (hits.length === 1) return hits[0];
|
|
161
|
+
if (hits.length > 1) return null;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A package import resolves to its directory's single file, and only then.
|
|
168
|
+
*
|
|
169
|
+
* Stripping from the left is the go.mod-prefix strategy without reading go.mod:
|
|
170
|
+
* whatever the module prefix is, it is the part that does not match. A package
|
|
171
|
+
* of several files has no single answer a file edge could name, so it stays a
|
|
172
|
+
* specifier rather than pointing at an arbitrary member.
|
|
173
|
+
*/
|
|
174
|
+
function dirHit(spec, dirs) {
|
|
175
|
+
const parts = spec.replace(/\\/g, '/').replace(/^\.\//, '').split('/').filter((p) => p !== '');
|
|
176
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
177
|
+
const suffix = parts.slice(i).join('/');
|
|
178
|
+
const found = [];
|
|
179
|
+
for (const [dir, members] of dirs) {
|
|
180
|
+
if (dir === suffix || (suffix !== '' && dir.endsWith(`/${suffix}`))) found.push(...members);
|
|
181
|
+
}
|
|
182
|
+
if (found.length === 1) return found[0];
|
|
183
|
+
if (found.length > 1) return null;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* An explicit extension that names the sibling language (review 0003 Q41).
|
|
190
|
+
*
|
|
191
|
+
* TypeScript sources import their compiled shape (`./b.js` for `b.ts`), and
|
|
192
|
+
* Node's ESM resolution requires it; the tracked tree holds the source, so
|
|
193
|
+
* the edge must try the source extension before giving up and storing the
|
|
194
|
+
* specifier raw.
|
|
195
|
+
*/
|
|
196
|
+
const EXT_SWAPS = { '.js': ['.ts', '.tsx'], '.mjs': ['.mts'], '.cjs': ['.cts'] };
|
|
197
|
+
|
|
198
|
+
function resolveOne(filePath, spec, files, stems, dirs, lang) {
|
|
70
199
|
const rel = relativeSpec(spec);
|
|
71
|
-
if (rel === null)
|
|
200
|
+
if (rel === null) {
|
|
201
|
+
if (lang === 'go' || lang === 'csharp') {
|
|
202
|
+
// Namespace-as-directory: `A.B.C` and `example.com/mod/pkg` use dots and
|
|
203
|
+
// slashes interchangeably for what is a directory on disk.
|
|
204
|
+
const dotted = lang === 'csharp' ? String(spec).replace(/\\/g, '/').split('.').join('/') : String(spec);
|
|
205
|
+
return dirHit(dotted, dirs) ?? spec;
|
|
206
|
+
}
|
|
207
|
+
const candidates = nonRelativeCandidates(String(spec), lang);
|
|
208
|
+
if (candidates === null) return spec;
|
|
209
|
+
return suffixHit(candidates, files, stems) ?? spec;
|
|
210
|
+
}
|
|
72
211
|
const dir = filePath.includes('/') ? filePath.slice(0, filePath.lastIndexOf('/')) : '';
|
|
73
212
|
const base = slash(dir, rel);
|
|
74
213
|
const candidates = [base];
|
|
@@ -82,6 +221,13 @@ function resolveOne(filePath, spec, files, lang) {
|
|
|
82
221
|
for (const candidate of candidates) {
|
|
83
222
|
if (files.has(candidate)) return candidate;
|
|
84
223
|
}
|
|
224
|
+
for (const [from, tos] of Object.entries(EXT_SWAPS)) {
|
|
225
|
+
if (!base.endsWith(from)) continue;
|
|
226
|
+
for (const to of tos) {
|
|
227
|
+
const swapped = base.slice(0, -from.length) + to;
|
|
228
|
+
if (files.has(swapped)) return swapped;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
85
231
|
return spec;
|
|
86
232
|
}
|
|
87
233
|
|
|
@@ -146,14 +292,48 @@ function scan(text, onLine) {
|
|
|
146
292
|
return { symbols, imports };
|
|
147
293
|
}
|
|
148
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Import specifiers in a script, including an import that spans lines.
|
|
297
|
+
*
|
|
298
|
+
* Single-line regexes missed the case the review caught (Q41): an import whose
|
|
299
|
+
* `from` lands several lines below the `import`. Buffering until the closing
|
|
300
|
+
* quote (or `;`) sees the whole statement before deciding what it names.
|
|
301
|
+
* Moved here from `tests.mjs` so the index and the test mapping read the same
|
|
302
|
+
* bytes; `tests.mjs` re-exports it, so its importers do not move.
|
|
303
|
+
*/
|
|
304
|
+
export function scriptSpecifiers(text) {
|
|
305
|
+
const specs = [];
|
|
306
|
+
const lines = String(text).split('\n');
|
|
307
|
+
let buf = '';
|
|
308
|
+
let open = false;
|
|
309
|
+
for (const line of lines) {
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (!open) {
|
|
312
|
+
if (!/^(?:import|export)\b/.test(trimmed)) continue;
|
|
313
|
+
// A declaration is not an import. Capturing it would swallow the next
|
|
314
|
+
// import's `from` and report a specifier the file does not have.
|
|
315
|
+
if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var)\b/.test(trimmed) && !trimmed.includes(' from ')) continue;
|
|
316
|
+
buf = trimmed;
|
|
317
|
+
open = true;
|
|
318
|
+
} else {
|
|
319
|
+
buf += `\n${trimmed}`;
|
|
320
|
+
}
|
|
321
|
+
const done = /\bfrom\s+['"][^'"]+['"]/.test(buf) || /^import\s+['"][^'"]+['"]/.test(buf) || buf.includes(';') || buf.length > 2000;
|
|
322
|
+
if (!done) continue;
|
|
323
|
+
const from = buf.match(/\bfrom\s+['"]([^'"]+)['"]/);
|
|
324
|
+
const side = buf.match(/^import\s+['"]([^'"]+)['"]/);
|
|
325
|
+
if (from) specs.push(from[1]);
|
|
326
|
+
else if (side) specs.push(side[1]);
|
|
327
|
+
buf = '';
|
|
328
|
+
open = false;
|
|
329
|
+
}
|
|
330
|
+
for (const match of String(text).matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)) specs.push(match[1]);
|
|
331
|
+
for (const match of String(text).matchAll(/\bimport\(\s*['"]([^'"]+)['"]\s*\)/g)) specs.push(match[1]);
|
|
332
|
+
return [...new Set(specs)];
|
|
333
|
+
}
|
|
334
|
+
|
|
149
335
|
function script(text) {
|
|
150
|
-
|
|
151
|
-
const from = line.match(/^(?:import|export)\s[\s\S]*?\sfrom\s+['"]([^'"]+)['"]/);
|
|
152
|
-
const side = line.match(/^import\s+['"]([^'"]+)['"]/);
|
|
153
|
-
const req = line.match(/\brequire\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
154
|
-
if (from) imports.push(from[1]);
|
|
155
|
-
else if (side) imports.push(side[1]);
|
|
156
|
-
else if (req) imports.push(req[1]);
|
|
336
|
+
const { symbols } = scan(text, (line, raw, lineNo, symbols) => {
|
|
157
337
|
let m;
|
|
158
338
|
if ((m = line.match(new RegExp(`^export\\s+(?:default\\s+)?(?:async\\s+)?function\\s*\\*?\\s*(${IDENT})`)))) {
|
|
159
339
|
add(symbols, m[1], 'function', lineNo, raw, 1);
|
|
@@ -179,6 +359,7 @@ function script(text) {
|
|
|
179
359
|
add(symbols, m[1], 'class', lineNo, raw, line.startsWith('export ') ? 1 : 0);
|
|
180
360
|
}
|
|
181
361
|
});
|
|
362
|
+
return { symbols, imports: scriptSpecifiers(text) };
|
|
182
363
|
}
|
|
183
364
|
|
|
184
365
|
const KT_MOD = '(?:public|private|internal|protected|open|data|sealed|abstract|enum|inner|value|annotation|suspend|override|operator|inline)';
|