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/commands/skills.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// token instead of guessing. Suppressing it is how a discovery tool becomes a
|
|
10
10
|
// black box.
|
|
11
11
|
|
|
12
|
-
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
13
|
+
import { repoContext } from '../context.mjs';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
15
|
import { existsSync } from 'node:fs';
|
|
15
16
|
|
|
@@ -20,9 +21,11 @@ import { unknownSkills, scoreCorpus } from '../resolve/eval.mjs';
|
|
|
20
21
|
import { PACKAGE_ROOT, resolveWorkspace, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
21
22
|
import { fingerprint } from '../resolve/fingerprint.mjs';
|
|
22
23
|
import { tokenize, rankSkills, closure, selectWithinBudget, resolveAlias, scoreSkill, selectionVerdict, stackCompatible } from '../resolve/resolve.mjs';
|
|
24
|
+
import { localSkillNodes } from '../resolve/local.mjs';
|
|
23
25
|
import { nearMatches } from '../resolve/near.mjs';
|
|
24
26
|
import { CLI } from '../name.mjs';
|
|
25
27
|
import { parsePathList } from '../argv.mjs';
|
|
28
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
26
29
|
|
|
27
30
|
export { parsePathList };
|
|
28
31
|
|
|
@@ -75,18 +78,25 @@ async function skillsRegistry(args) {
|
|
|
75
78
|
return registry;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
/**
|
|
82
|
+
* The named skill: the registry first, then the repo's own skills, because a skill
|
|
83
|
+
* the repo installed is addressable by the same name (plan 0005 S3). The local lookup
|
|
84
|
+
* costs a fingerprint plus a directory scan, so it runs only when the registry did
|
|
85
|
+
* not answer.
|
|
86
|
+
*/
|
|
87
|
+
async function findSkill(registry, root, name, verb, ctx) {
|
|
79
88
|
if (!name) skillsFailure(`${verb} needs a skill name`, `usage: ${CLI} skills ${verb} <name>`, EXIT.USAGE);
|
|
80
89
|
const skill = registry.skills.find((s) => s.name === name);
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
if (skill) return skill;
|
|
91
|
+
const repoStack = (await ctx.fingerprint()).signals;
|
|
92
|
+
const local = (await localSkillNodes(root, registry, repoStack)).find((s) => s.name === name);
|
|
93
|
+
if (local) return local;
|
|
94
|
+
const near = nearMatches(name, registry.skills.map((s) => s.name));
|
|
95
|
+
skillsFailure(
|
|
96
|
+
`no skill named "${name}"${near.length ? ` (did you mean ${near.join(', ')}?)` : ''}`,
|
|
97
|
+
`run: ${CLI} skills find <query>`,
|
|
98
|
+
EXIT.USAGE,
|
|
99
|
+
);
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
/** The JSON-safe skill facts both surfaces spread: absent lists read as empty, not missing. */
|
|
@@ -106,7 +116,10 @@ function skillFacts(skill) {
|
|
|
106
116
|
/** Tier 1: the selection the task resolves to, with the text packet's display sets. */
|
|
107
117
|
export async function skillsBriefFacts(root, args = {}) {
|
|
108
118
|
const registry = await skillsRegistry(args);
|
|
109
|
-
|
|
119
|
+
// One context per call: a second question about the same repository inside this
|
|
120
|
+
// command costs nothing (review 0003 Q67).
|
|
121
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
122
|
+
const repo = await ctx.fingerprint();
|
|
110
123
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
111
124
|
const repoStack = repo.signals;
|
|
112
125
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
@@ -116,6 +129,10 @@ export async function skillsBriefFacts(root, args = {}) {
|
|
|
116
129
|
const task = args.task ?? null;
|
|
117
130
|
const repoFacts = { root, stack: repoStack, facts: repo.facts };
|
|
118
131
|
|
|
132
|
+
// The repo's own skills rank alongside the registry, so every command agrees on
|
|
133
|
+
// the names a reader in this repo can address (plan 0005 S3).
|
|
134
|
+
const pool = [...registry.skills, ...(await localSkillNodes(root, registry, repoStack))];
|
|
135
|
+
|
|
119
136
|
// A brief with no task and no paths resolves on stack alone, which produces a
|
|
120
137
|
// plausible-looking list the agent may act on. It warns rather than silently
|
|
121
138
|
// answering; under `--json` the warning is a field, not prose on stdout.
|
|
@@ -125,7 +142,7 @@ export async function skillsBriefFacts(root, args = {}) {
|
|
|
125
142
|
'pass --task "<what you are doing>" or stage files so the diff drives selection, ' +
|
|
126
143
|
'or pass --all to take everything for this stack.';
|
|
127
144
|
if (args.all) {
|
|
128
|
-
const usable =
|
|
145
|
+
const usable = pool.filter((s) => stackCompatible(s, repoStack));
|
|
129
146
|
return {
|
|
130
147
|
kind: 'all',
|
|
131
148
|
repoStack,
|
|
@@ -153,12 +170,12 @@ export async function skillsBriefFacts(root, args = {}) {
|
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
const context = { repoStack, taskTokens, paths };
|
|
156
|
-
const { selected, excluded } = rankSkills(
|
|
173
|
+
const { selected, excluded } = rankSkills(pool, context, { forced });
|
|
157
174
|
|
|
158
175
|
// Hard dependencies are pulled in even when they did not score, and even past
|
|
159
176
|
// budget: a skill that is useless without its dependency is worse than a large
|
|
160
177
|
// context, because it silently misleads.
|
|
161
|
-
const expanded = closure(selected,
|
|
178
|
+
const expanded = closure(selected, pool);
|
|
162
179
|
const { picked, deferred, used: inlineBytes } = selectWithinBudget(expanded, {
|
|
163
180
|
budget,
|
|
164
181
|
all: args.all === true,
|
|
@@ -216,31 +233,53 @@ export async function skillsBriefFacts(root, args = {}) {
|
|
|
216
233
|
};
|
|
217
234
|
}
|
|
218
235
|
|
|
219
|
-
/**
|
|
220
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Raw search across capability, keyword and summary.
|
|
238
|
+
*
|
|
239
|
+
* Local nodes are admitted on `score > 0`, not the `selectionVerdict` rule the
|
|
240
|
+
* registry nodes keep: that rule exists to compensate for find having no repo, and
|
|
241
|
+
* a local skill has one - its presence in the repo is the scoping signal the rule
|
|
242
|
+
* stands in for.
|
|
243
|
+
*/
|
|
244
|
+
export async function skillsFindFacts(root, query, args = {}) {
|
|
221
245
|
const q = String(query ?? '').trim();
|
|
222
246
|
if (!q) skillsFailure('find needs a query', `usage: ${CLI} skills find <query>`, EXIT.USAGE);
|
|
223
247
|
const registry = await skillsRegistry(args);
|
|
224
248
|
const tokens = tokenize(q);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
.
|
|
228
|
-
|
|
249
|
+
const context = { repoStack: [], taskTokens: tokens, paths: [] };
|
|
250
|
+
const results = [
|
|
251
|
+
...registry.skills
|
|
252
|
+
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
253
|
+
.filter((r) => selectionVerdict(r.evidence, r.reasons) === null),
|
|
254
|
+
...(await localSkillNodes(root, registry, (await (args.ctx ?? repoContext(root)).fingerprint()).signals))
|
|
255
|
+
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
256
|
+
.filter((r) => r.score > 0),
|
|
257
|
+
].sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
|
|
229
258
|
return {
|
|
230
259
|
query: q,
|
|
231
|
-
results: results.map((r) => ({ name: r.skill.name, score: r.score, summary: r.skill.summary, reasons: r.reasons })),
|
|
260
|
+
results: results.map((r) => ({ name: r.skill.name, score: r.score, bytes: r.skill.bytes, summary: r.skill.summary, reasons: r.reasons })),
|
|
232
261
|
};
|
|
233
262
|
}
|
|
234
263
|
|
|
235
264
|
/** Tier 2: one skill body and the metadata needed to judge it. */
|
|
236
265
|
export async function skillsShowFacts(root, name, args = {}) {
|
|
237
266
|
const registry = await skillsRegistry(args);
|
|
238
|
-
const skill = findSkill(registry, name, 'show');
|
|
267
|
+
const skill = await findSkill(registry, root, name, 'show', args.ctx ?? repoContext(root));
|
|
239
268
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
240
269
|
// The repo is a location for a declared source too, so `show` reads a body the
|
|
241
270
|
// caller already has instead of reporting the body missing.
|
|
242
271
|
const body = await sourcePath(skill, workspaceRoot, root);
|
|
243
|
-
|
|
272
|
+
// A local file is canonicalized by realpath, while a declared repo source keeps the
|
|
273
|
+
// caller's spelling: on a symlinked prefix (macOS `/var`) the label must accept the
|
|
274
|
+
// canonical root too, or a repo body is mislabelled `workspace`.
|
|
275
|
+
let realRoot = root;
|
|
276
|
+
try {
|
|
277
|
+
realRoot = await realpath(root);
|
|
278
|
+
} catch {
|
|
279
|
+
/* root does not exist; the label falls back to the un-canonical comparison */
|
|
280
|
+
}
|
|
281
|
+
const bodyFrom =
|
|
282
|
+
body === null ? 'none' : body.startsWith(`${root}/`) || body.startsWith(`${realRoot}/`) ? 'repo' : 'workspace';
|
|
244
283
|
return {
|
|
245
284
|
skill: skillFacts(skill),
|
|
246
285
|
workspace,
|
|
@@ -253,8 +292,9 @@ export async function skillsShowFacts(root, name, args = {}) {
|
|
|
253
292
|
/** The signal chain for one skill in one repository. */
|
|
254
293
|
export async function skillsWhyFacts(root, name, args = {}) {
|
|
255
294
|
const registry = await skillsRegistry(args);
|
|
256
|
-
const
|
|
257
|
-
const
|
|
295
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
296
|
+
const skill = await findSkill(registry, root, name, 'why', ctx);
|
|
297
|
+
const repo = await ctx.fingerprint();
|
|
258
298
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
259
299
|
const tokens = tokenize(args.task ?? '');
|
|
260
300
|
const { score, reasons } = scoreSkill(skill, { repoStack: repo.signals, taskTokens: tokens, paths });
|
|
@@ -352,7 +392,6 @@ export async function indexCommand(args, flags) {
|
|
|
352
392
|
'',
|
|
353
393
|
`deferred entries return a pointer, not a body. Fetch one with: ${CLI} skills show <name>`,
|
|
354
394
|
].join('\n'),
|
|
355
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
356
395
|
);
|
|
357
396
|
return EXIT.OK;
|
|
358
397
|
}
|
|
@@ -395,16 +434,17 @@ async function emitAllFacts(facts, flags) {
|
|
|
395
434
|
if (deferred.length > 0) {
|
|
396
435
|
lines.push('', `DEFERRED ${deferred.length}: ${deferred.map((s) => s.name).join(', ')}`);
|
|
397
436
|
}
|
|
398
|
-
emit(lines.join('\n')
|
|
437
|
+
emit(lines.join('\n'));
|
|
399
438
|
return EXIT.OK;
|
|
400
439
|
}
|
|
401
440
|
|
|
402
441
|
// --- find ---------------------------------------------------------------------
|
|
403
442
|
|
|
404
443
|
export async function findCommand(args, flags) {
|
|
444
|
+
const root = commandRoot(flags);
|
|
405
445
|
let facts;
|
|
406
446
|
try {
|
|
407
|
-
facts = await skillsFindFacts(args.join(' '), flags);
|
|
447
|
+
facts = await skillsFindFacts(root, args.join(' '), flags);
|
|
408
448
|
} catch (error) {
|
|
409
449
|
return fail(error.message, error.exitCode ?? EXIT.FAIL, error.remedy);
|
|
410
450
|
}
|
|
@@ -414,7 +454,6 @@ export async function findCommand(args, flags) {
|
|
|
414
454
|
if (isJson(flags)) {
|
|
415
455
|
emitJson(
|
|
416
456
|
{ ok: true, query, workspace, results },
|
|
417
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
418
457
|
);
|
|
419
458
|
return EXIT.OK;
|
|
420
459
|
}
|
|
@@ -434,17 +473,17 @@ export async function findCommand(args, flags) {
|
|
|
434
473
|
`${r.name.padEnd(26)} ${kb(r.bytes).padStart(7)} ${r.reasons.join(' ')}`,
|
|
435
474
|
),
|
|
436
475
|
].join('\n'),
|
|
437
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
438
476
|
);
|
|
439
477
|
return EXIT.OK;
|
|
440
478
|
}
|
|
441
479
|
|
|
442
480
|
// --- brief: tier 1 ------------------------------------------------------------
|
|
443
481
|
export async function briefCommand(args, flags) {
|
|
444
|
-
const root = flags
|
|
482
|
+
const root = commandRoot(flags);
|
|
483
|
+
const ctx = repoContext(root);
|
|
445
484
|
let facts;
|
|
446
485
|
try {
|
|
447
|
-
facts = await skillsBriefFacts(root, flags);
|
|
486
|
+
facts = await skillsBriefFacts(root, { ...flags, ctx });
|
|
448
487
|
} catch (error) {
|
|
449
488
|
return fail(error.message, error.exitCode ?? EXIT.FAIL, error.remedy);
|
|
450
489
|
}
|
|
@@ -463,21 +502,20 @@ export async function briefCommand(args, flags) {
|
|
|
463
502
|
}
|
|
464
503
|
|
|
465
504
|
if (facts.warning) {
|
|
466
|
-
const repo = await fingerprint(
|
|
505
|
+
const repo = await ctx.fingerprint();
|
|
467
506
|
emit(
|
|
468
507
|
[
|
|
469
508
|
`repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
470
509
|
'',
|
|
471
510
|
facts.warning,
|
|
472
511
|
].join('\n'),
|
|
473
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
474
512
|
);
|
|
475
513
|
return EXIT.OK;
|
|
476
514
|
}
|
|
477
515
|
|
|
478
516
|
const { shown, shownDeferred, shownUsed, pointerOnly, excludedEntries, workspace, workspaceRoot, budget } = facts;
|
|
479
517
|
const lines = [];
|
|
480
|
-
const repo = await fingerprint(
|
|
518
|
+
const repo = await ctx.fingerprint();
|
|
481
519
|
|
|
482
520
|
lines.push(
|
|
483
521
|
`RESOLVED ${shown.length} skills, ${kb(shownUsed)}${flags.all || shownUsed <= budget ? '' : ' (budget capped)'} - repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
@@ -498,7 +536,7 @@ export async function briefCommand(args, flags) {
|
|
|
498
536
|
lines.push('', `NOT SELECTED ${hidden.length}`, '', ` notes: ${CLI} skills why <name> | ${CLI} skills show <name>`);
|
|
499
537
|
}
|
|
500
538
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
501
|
-
emit(lines.join('\n')
|
|
539
|
+
emit(lines.join('\n'));
|
|
502
540
|
return EXIT.OK;
|
|
503
541
|
}
|
|
504
542
|
|
|
@@ -543,14 +581,14 @@ export async function briefCommand(args, flags) {
|
|
|
543
581
|
}
|
|
544
582
|
|
|
545
583
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
546
|
-
emit(lines.join('\n')
|
|
584
|
+
emit(lines.join('\n'));
|
|
547
585
|
return EXIT.OK;
|
|
548
586
|
}
|
|
549
587
|
|
|
550
588
|
// --- show: tier 2 -------------------------------------------------------------
|
|
551
589
|
|
|
552
590
|
export async function showCommand(args, flags) {
|
|
553
|
-
const root = flags
|
|
591
|
+
const root = commandRoot(flags);
|
|
554
592
|
let facts;
|
|
555
593
|
try {
|
|
556
594
|
facts = await skillsShowFacts(root, args[0], flags);
|
|
@@ -560,7 +598,7 @@ export async function showCommand(args, flags) {
|
|
|
560
598
|
const { skill, workspace, body, bodyFrom, bodyText } = facts;
|
|
561
599
|
const source = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
562
600
|
if (isJson(flags)) {
|
|
563
|
-
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText }
|
|
601
|
+
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText });
|
|
564
602
|
return EXIT.OK;
|
|
565
603
|
}
|
|
566
604
|
emit(
|
|
@@ -583,7 +621,6 @@ export async function showCommand(args, flags) {
|
|
|
583
621
|
]
|
|
584
622
|
.filter((l) => l !== null)
|
|
585
623
|
.join('\n'),
|
|
586
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
587
624
|
);
|
|
588
625
|
return EXIT.OK;
|
|
589
626
|
}
|
|
@@ -591,7 +628,7 @@ export async function showCommand(args, flags) {
|
|
|
591
628
|
// --- why ----------------------------------------------------------------------
|
|
592
629
|
|
|
593
630
|
export async function whyCommand(args, flags) {
|
|
594
|
-
const root = flags
|
|
631
|
+
const root = commandRoot(flags);
|
|
595
632
|
let facts;
|
|
596
633
|
try {
|
|
597
634
|
facts = await skillsWhyFacts(root, args[0], flags);
|
|
@@ -606,7 +643,7 @@ export async function whyCommand(args, flags) {
|
|
|
606
643
|
// to scrape: a skill that can never be selected in this repo is a decision, not
|
|
607
644
|
// a line of output.
|
|
608
645
|
if (isJson(flags)) {
|
|
609
|
-
emitJson({ ok: true, command: 'skills why', ...facts }
|
|
646
|
+
emitJson({ ok: true, command: 'skills why', ...facts });
|
|
610
647
|
return EXIT.OK;
|
|
611
648
|
}
|
|
612
649
|
|
|
@@ -632,7 +669,7 @@ export async function whyCommand(args, flags) {
|
|
|
632
669
|
if (facts.variantOf) lines.push('', `variant-of ${facts.variantOf} (sibling group, one body per stack)`);
|
|
633
670
|
if (facts.requires?.length > 0) lines.push(`requires ${facts.requires.join(', ')}`);
|
|
634
671
|
|
|
635
|
-
emit(lines.join('\n')
|
|
672
|
+
emit(lines.join('\n'));
|
|
636
673
|
return EXIT.OK;
|
|
637
674
|
}
|
|
638
675
|
|
|
@@ -738,7 +775,7 @@ export async function evalCommand(positional, flags) {
|
|
|
738
775
|
}
|
|
739
776
|
|
|
740
777
|
lines.push('', okRun ? 'OK the resolver meets the gold corpus' : 'REGRESSION: the resolver no longer meets the gold corpus');
|
|
741
|
-
emit(lines.join('\n')
|
|
778
|
+
emit(lines.join('\n'));
|
|
742
779
|
return okRun ? EXIT.OK : EXIT.FAIL;
|
|
743
780
|
}
|
|
744
781
|
|
|
@@ -20,7 +20,9 @@ import { resolveVaultDir } from '../bench/mine.mjs';
|
|
|
20
20
|
import { locateSource, sourceHeldLocally } from '../resolve/source.mjs';
|
|
21
21
|
import { nearMatches } from '../resolve/near.mjs';
|
|
22
22
|
import { resolveWorkspace, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
23
|
-
import { CLI
|
|
23
|
+
import { CLI } from '../name.mjs';
|
|
24
|
+
import { LOCAL_DIR } from '../state.mjs';
|
|
25
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
24
26
|
|
|
25
27
|
async function readSkillRawText(skill, workspaceRoot, root) {
|
|
26
28
|
const canonical = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
@@ -44,7 +46,7 @@ async function readSkillRawText(skill, workspaceRoot, root) {
|
|
|
44
46
|
|
|
45
47
|
async function loadGoldPathsByTask(root) {
|
|
46
48
|
const map = new Map();
|
|
47
|
-
const tasksDir = join(root,
|
|
49
|
+
const tasksDir = join(root, '.simt', 'bench', 'tasks');
|
|
48
50
|
const vaultRoot = resolveVaultDir(root);
|
|
49
51
|
const entries = (await readdir(tasksDir).catch(() => [])).filter((f) => f.endsWith('.json')).sort();
|
|
50
52
|
for (const entry of entries) {
|
|
@@ -67,7 +69,7 @@ export async function compileCommand(args, flags) {
|
|
|
67
69
|
const { registry, errors, ok } = await loadRegistry(null, flags);
|
|
68
70
|
if (!ok) return fail(errors[0], EXIT.REGISTRY, WORKSPACE_REMEDY);
|
|
69
71
|
|
|
70
|
-
const root = resolve(flags
|
|
72
|
+
const root = resolve(commandRoot(flags));
|
|
71
73
|
const ws = await resolveWorkspace(flags);
|
|
72
74
|
const targetName = args[0] ?? null;
|
|
73
75
|
|
|
@@ -116,7 +118,6 @@ export async function compileCommand(args, flags) {
|
|
|
116
118
|
splitPlan: s.splitPlan,
|
|
117
119
|
})),
|
|
118
120
|
},
|
|
119
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
120
121
|
);
|
|
121
122
|
return EXIT.OK;
|
|
122
123
|
}
|
|
@@ -149,7 +150,7 @@ export async function compileCommand(args, flags) {
|
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
emit(lines.join('\n')
|
|
153
|
+
emit(lines.join('\n'));
|
|
153
154
|
return EXIT.OK;
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -172,26 +173,26 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
172
173
|
triggerStats,
|
|
173
174
|
});
|
|
174
175
|
|
|
175
|
-
const outDir = join(root,
|
|
176
|
-
const proposalPath = join(root,
|
|
176
|
+
const outDir = join(root, LOCAL_DIR, 'compiled', 'skills', skill.name);
|
|
177
|
+
const proposalPath = join(root, LOCAL_DIR, 'proposals', 'skills', `${skill.name}.json`);
|
|
177
178
|
const writtenFiles = [];
|
|
178
179
|
|
|
179
180
|
if (mode.apply) {
|
|
180
181
|
await mkdir(outDir, { recursive: true });
|
|
181
182
|
await writeFile(join(outDir, 'SKILL.md'), compiled.mainText, 'utf8');
|
|
182
|
-
writtenFiles.push(`${
|
|
183
|
+
writtenFiles.push(`${LOCAL_DIR}/compiled/skills/${skill.name}/SKILL.md`);
|
|
183
184
|
|
|
184
185
|
for (const ref of compiled.references) {
|
|
185
186
|
if (!ref.content) continue;
|
|
186
187
|
const refFull = join(outDir, ref.path);
|
|
187
188
|
await mkdir(dirname(refFull), { recursive: true });
|
|
188
189
|
await writeFile(refFull, ref.content, 'utf8');
|
|
189
|
-
writtenFiles.push(`${
|
|
190
|
+
writtenFiles.push(`${LOCAL_DIR}/compiled/skills/${skill.name}/${ref.path}`);
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
await mkdir(dirname(proposalPath), { recursive: true });
|
|
193
194
|
await writeFile(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`, 'utf8');
|
|
194
|
-
writtenFiles.push(`${
|
|
195
|
+
writtenFiles.push(`${LOCAL_DIR}/proposals/skills/${skill.name}.json`);
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
if (isJson(flags)) {
|
|
@@ -222,7 +223,6 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
222
223
|
: null,
|
|
223
224
|
proposal,
|
|
224
225
|
},
|
|
225
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
226
226
|
);
|
|
227
227
|
return EXIT.OK;
|
|
228
228
|
}
|
|
@@ -245,7 +245,7 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
emit(lines.join('\n')
|
|
248
|
+
emit(lines.join('\n'));
|
|
249
249
|
if (!mode.apply) {
|
|
250
250
|
note(`note dry-run nothing written; apply with: ${CLI} skills compile ${skill.name} --yes`);
|
|
251
251
|
}
|
|
@@ -38,6 +38,7 @@ import { resolveWorkspace, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
|
38
38
|
import { CLI } from '../name.mjs';
|
|
39
39
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
40
40
|
import { nameProblems } from '../graph/spec.mjs';
|
|
41
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Where skills live in this repo. The estate already has a convention - a
|
|
@@ -82,13 +83,25 @@ export async function installCommand(args, flags) {
|
|
|
82
83
|
const ws = await resolveWorkspace(flags);
|
|
83
84
|
if (ws.error) return fail(`workspace config: ${ws.error}`, EXIT.CONFIG, 'fix the JSON, or remove the file');
|
|
84
85
|
|
|
85
|
-
const root = flags
|
|
86
|
+
const root = commandRoot(flags);
|
|
86
87
|
const repo = await fingerprint(root);
|
|
87
88
|
const pick = await chooseSource(skill, repo.signals, flags, { root, workspaceRoot: ws.root });
|
|
88
89
|
if (pick.error) return fail(pick.error, EXIT.USAGE, pick.remedy);
|
|
89
90
|
|
|
90
91
|
const found = await locateSource(pick.source, { workspaceRoot: ws.root, root });
|
|
91
92
|
if (!found.path) {
|
|
93
|
+
// A repo-side candidate with the wrong size is a third reason, and it needs
|
|
94
|
+
// its own wording: the file is on the reader's disk, so "this repo does not
|
|
95
|
+
// hold it" would send them looking for a file that is already there. The
|
|
96
|
+
// variant path calls the same failure an impostor (chooseSource).
|
|
97
|
+
const impostor = found.candidates.find((c) => c.how === 'repo');
|
|
98
|
+
if (impostor) {
|
|
99
|
+
return fail(
|
|
100
|
+
`${skill.name}: ${root}/${pick.source.path} exists but is not the declared body (${pick.source.bytes} bytes declared)`,
|
|
101
|
+
EXIT.USAGE,
|
|
102
|
+
`run: ${CLI} graph lint --workspace <path> to see what the registry expects, or delete the local copy`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
92
105
|
// Two reasons land here and they need different wording: no workspace at all,
|
|
93
106
|
// or a workspace that should hold this body and does not. An install that
|
|
94
107
|
// cannot read a body has failed, so this is never a degraded mode - the
|
|
@@ -198,7 +211,6 @@ export async function installCommand(args, flags) {
|
|
|
198
211
|
]
|
|
199
212
|
.filter((l) => l !== null)
|
|
200
213
|
.join('\n'),
|
|
201
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
202
214
|
);
|
|
203
215
|
}
|
|
204
216
|
|