simt-dev 0.7.0 → 0.8.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 +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.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-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/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
package/src/commands/shared.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '../projection/shared.mjs';
|
|
30
30
|
import { buildManifest, declaredShared, manifestPath, manifestText, readManifest, writeManifest } from '../projection/manifest.mjs';
|
|
31
31
|
import { desiredRegion } from '../projection/rules.mjs';
|
|
32
|
+
import { plainNumber } from '../util/fs.mjs';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Everything `plan` and `install` both need, computed once.
|
|
@@ -76,9 +77,9 @@ async function gather(flags) {
|
|
|
76
77
|
*/
|
|
77
78
|
function divergentNote(d, total) {
|
|
78
79
|
if (d.reason === 'coverage') {
|
|
79
|
-
return `${d.id} carried by ${
|
|
80
|
+
return `${d.id} carried by ${plainNumber(d.roots.length)} of ${plainNumber(total)} repositories, so each region keeps its own`;
|
|
80
81
|
}
|
|
81
|
-
return `${d.id} rendered differently in ${
|
|
82
|
+
return `${d.id} rendered differently in ${plainNumber(d.roots.length)} repositories, so each region keeps its own`;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/** Why a selected rule cannot leave any region, in the words its reason deserves. */
|
|
@@ -98,7 +99,7 @@ function excludedNote({ id, why }) {
|
|
|
98
99
|
function capNote(target, bytes) {
|
|
99
100
|
if (target.path === null || target.cap === null || bytes <= target.cap.bytes) return null;
|
|
100
101
|
return [
|
|
101
|
-
`${target.surface} ${
|
|
102
|
+
`${target.surface} ${plainNumber(bytes)} B exceeds the ${plainNumber(target.cap.bytes)} B it allows for ${target.cap.appliesTo}`,
|
|
102
103
|
` ${target.cap.why}`,
|
|
103
104
|
` the rules above that limit are not read; move rules this workspace does not share back into each region, or drop the shared copy for this surface`,
|
|
104
105
|
].join('\n');
|
|
@@ -157,10 +158,10 @@ export async function sharedPlanCommand(_args, flags) {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
const lines = [
|
|
160
|
-
`shared ${target.root} ${
|
|
161
|
-
`rules ${
|
|
161
|
+
`shared ${target.root} ${plainNumber(roots.length)} repositories (${target.source})`,
|
|
162
|
+
`rules ${plainNumber(shared.length)} of ${plainNumber(candidates)} ids carried once ${plainNumber(bytes)} B`,
|
|
162
163
|
];
|
|
163
|
-
for (const r of shared) lines.push(` ${r.id} ${r.priority ?? 'unclassified'} ${
|
|
164
|
+
for (const r of shared) lines.push(` ${r.id} ${r.priority ?? 'unclassified'} ${plainNumber(Buffer.byteLength(r.rendered))} B ${r.source}`);
|
|
164
165
|
if (divergent.length > 0) {
|
|
165
166
|
// Named, not summarised: a reader who expected a python rule here and did not get it
|
|
166
167
|
// is owed the reason, and "divergent: 2" is not a reason.
|
|
@@ -171,9 +172,9 @@ export async function sharedPlanCommand(_args, flags) {
|
|
|
171
172
|
lines.push(`left in every region ${excluded.length} rule(s) no root can hand over:`);
|
|
172
173
|
for (const e of excluded) lines.push(` ${excludedNote(e)}`);
|
|
173
174
|
}
|
|
174
|
-
lines.push(`saves ${
|
|
175
|
+
lines.push(`saves ${plainNumber(save)} B across ${plainNumber(roots.length)} regions`);
|
|
175
176
|
for (const r of rows) {
|
|
176
|
-
lines.push(` ${r.name} ${
|
|
177
|
+
lines.push(` ${r.name} ${plainNumber(r.bytes)} B ${r.mode} ${r.rules.length > 0 ? r.rules.join(', ') : '(carries nothing extra)'}`);
|
|
177
178
|
}
|
|
178
179
|
lines.push('writes');
|
|
179
180
|
for (const t of targets) {
|
|
@@ -189,7 +190,7 @@ export async function sharedPlanCommand(_args, flags) {
|
|
|
189
190
|
if (over !== null) lines.push(`warn ${over}`);
|
|
190
191
|
}
|
|
191
192
|
lines.push(`nothing written; re-run with: ${CLI} shared install --yes`);
|
|
192
|
-
emit(lines.join('\n')
|
|
193
|
+
emit(lines.join('\n'));
|
|
193
194
|
return EXIT.OK;
|
|
194
195
|
}
|
|
195
196
|
|
|
@@ -297,7 +298,7 @@ export async function sharedInstallCommand(_args, flags) {
|
|
|
297
298
|
}
|
|
298
299
|
emit(
|
|
299
300
|
[
|
|
300
|
-
`shared ${
|
|
301
|
+
`shared ${plainNumber(shared.length)} rule(s), ${plainNumber(Buffer.byteLength(sharedBody(shared)))} B`,
|
|
301
302
|
...states.map((s) =>
|
|
302
303
|
s.path === null
|
|
303
304
|
? ` paste ${s.surface} no user-level file is recorded for it`
|
|
@@ -306,7 +307,6 @@ export async function sharedInstallCommand(_args, flags) {
|
|
|
306
307
|
`manifest ${manifests.map((m) => `${m.action}:${basename(m.root)}`).join(' ')}`,
|
|
307
308
|
dryRun ? 'dry-run nothing written' : `nothing written; re-run with: ${CLI} shared install --yes`,
|
|
308
309
|
].join('\n'),
|
|
309
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
310
310
|
);
|
|
311
311
|
// After the report: the report says what, the diff shows it (R50).
|
|
312
312
|
emitDiffs(diffs, flags);
|
|
@@ -327,8 +327,8 @@ export async function sharedInstallCommand(_args, flags) {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
const lines = [
|
|
330
|
-
`shared installed for ${
|
|
331
|
-
`rules ${
|
|
330
|
+
`shared installed for ${plainNumber(roots.length)} repositories`,
|
|
331
|
+
`rules ${plainNumber(shared.length)} carried once ${shared.map((r) => r.id).join(', ')}`,
|
|
332
332
|
];
|
|
333
333
|
for (const w of written) {
|
|
334
334
|
// `inject`'s word for a file with no region in it is "added"; the word for a file
|
|
@@ -354,7 +354,7 @@ export async function sharedInstallCommand(_args, flags) {
|
|
|
354
354
|
if (over !== null) lines.push(`warn ${over}`);
|
|
355
355
|
}
|
|
356
356
|
lines.push(`verify ${CLI} doctor reports the copy as current, and ${CLI} tokens --repos --root . reports what is left`);
|
|
357
|
-
emit(lines.join('\n')
|
|
357
|
+
emit(lines.join('\n'));
|
|
358
358
|
// After the report: the report says what, the diff shows it (R50).
|
|
359
359
|
emitDiffs(diffs, flags);
|
|
360
360
|
return EXIT.OK;
|
|
@@ -418,11 +418,6 @@ function describeShared(state, targets) {
|
|
|
418
418
|
return `${targets.length} user-level file(s) carry every rule this repository handed over`;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
/** `1234567`, never `1,234,567`: two machines must print the same bytes (ADR-013). */
|
|
422
|
-
function plain(n) {
|
|
423
|
-
return String(n);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
421
|
/** The directory name, for a line that has to name several repositories at once. */
|
|
427
422
|
function basename(path) {
|
|
428
423
|
return path.split('/').filter(Boolean).pop() ?? path;
|
package/src/commands/skills.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// black box.
|
|
11
11
|
|
|
12
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
|
|
|
@@ -19,11 +20,14 @@ import { bodyFile, sourcePath } from '../resolve/body.mjs';
|
|
|
19
20
|
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
|
-
import {
|
|
23
|
+
import { rankSkills, closure, selectWithinBudget, resolveAlias, scoreSkill, selectionVerdict, stackCompatible } from '../resolve/resolve.mjs';
|
|
24
|
+
import { tokenize } from '../text/tokens.mjs';
|
|
23
25
|
import { localSkillNodes } from '../resolve/local.mjs';
|
|
24
26
|
import { nearMatches } from '../resolve/near.mjs';
|
|
25
27
|
import { CLI } from '../name.mjs';
|
|
26
28
|
import { parsePathList } from '../argv.mjs';
|
|
29
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
30
|
+
import { gitText } from '../sys/git.mjs';
|
|
27
31
|
|
|
28
32
|
export { parsePathList };
|
|
29
33
|
|
|
@@ -32,11 +36,8 @@ const DEFAULT_BUDGET = 20480;
|
|
|
32
36
|
/** git status paths, so path signals come from the real diff. */
|
|
33
37
|
export async function dirtyPaths(root) {
|
|
34
38
|
try {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const run = promisify(execFile);
|
|
38
|
-
const { stdout } = await run('git', ['-C', root, 'status', '--porcelain=v1'], { maxBuffer: 8 << 20 });
|
|
39
|
-
return stdout
|
|
39
|
+
const stdout = await gitText(root, ['status', '--porcelain=v1'], { maxBuffer: 8 << 20 });
|
|
40
|
+
return (stdout ?? '')
|
|
40
41
|
.split('\n')
|
|
41
42
|
.filter(Boolean)
|
|
42
43
|
.map((line) => line.slice(3).trim())
|
|
@@ -82,11 +83,11 @@ async function skillsRegistry(args) {
|
|
|
82
83
|
* costs a fingerprint plus a directory scan, so it runs only when the registry did
|
|
83
84
|
* not answer.
|
|
84
85
|
*/
|
|
85
|
-
async function findSkill(registry, root, name, verb) {
|
|
86
|
+
async function findSkill(registry, root, name, verb, ctx) {
|
|
86
87
|
if (!name) skillsFailure(`${verb} needs a skill name`, `usage: ${CLI} skills ${verb} <name>`, EXIT.USAGE);
|
|
87
88
|
const skill = registry.skills.find((s) => s.name === name);
|
|
88
89
|
if (skill) return skill;
|
|
89
|
-
const repoStack = (await fingerprint(
|
|
90
|
+
const repoStack = (await ctx.fingerprint()).signals;
|
|
90
91
|
const local = (await localSkillNodes(root, registry, repoStack)).find((s) => s.name === name);
|
|
91
92
|
if (local) return local;
|
|
92
93
|
const near = nearMatches(name, registry.skills.map((s) => s.name));
|
|
@@ -114,7 +115,10 @@ function skillFacts(skill) {
|
|
|
114
115
|
/** Tier 1: the selection the task resolves to, with the text packet's display sets. */
|
|
115
116
|
export async function skillsBriefFacts(root, args = {}) {
|
|
116
117
|
const registry = await skillsRegistry(args);
|
|
117
|
-
|
|
118
|
+
// One context per call: a second question about the same repository inside this
|
|
119
|
+
// command costs nothing (review 0003 Q67).
|
|
120
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
121
|
+
const repo = await ctx.fingerprint();
|
|
118
122
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
119
123
|
const repoStack = repo.signals;
|
|
120
124
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
@@ -246,7 +250,7 @@ export async function skillsFindFacts(root, query, args = {}) {
|
|
|
246
250
|
...registry.skills
|
|
247
251
|
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
248
252
|
.filter((r) => selectionVerdict(r.evidence, r.reasons) === null),
|
|
249
|
-
...(await localSkillNodes(root, registry, (await
|
|
253
|
+
...(await localSkillNodes(root, registry, (await (args.ctx ?? repoContext(root)).fingerprint()).signals))
|
|
250
254
|
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
251
255
|
.filter((r) => r.score > 0),
|
|
252
256
|
].sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
|
|
@@ -259,7 +263,7 @@ export async function skillsFindFacts(root, query, args = {}) {
|
|
|
259
263
|
/** Tier 2: one skill body and the metadata needed to judge it. */
|
|
260
264
|
export async function skillsShowFacts(root, name, args = {}) {
|
|
261
265
|
const registry = await skillsRegistry(args);
|
|
262
|
-
const skill = await findSkill(registry, root, name, 'show');
|
|
266
|
+
const skill = await findSkill(registry, root, name, 'show', args.ctx ?? repoContext(root));
|
|
263
267
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
264
268
|
// The repo is a location for a declared source too, so `show` reads a body the
|
|
265
269
|
// caller already has instead of reporting the body missing.
|
|
@@ -287,8 +291,9 @@ export async function skillsShowFacts(root, name, args = {}) {
|
|
|
287
291
|
/** The signal chain for one skill in one repository. */
|
|
288
292
|
export async function skillsWhyFacts(root, name, args = {}) {
|
|
289
293
|
const registry = await skillsRegistry(args);
|
|
290
|
-
const
|
|
291
|
-
const
|
|
294
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
295
|
+
const skill = await findSkill(registry, root, name, 'why', ctx);
|
|
296
|
+
const repo = await ctx.fingerprint();
|
|
292
297
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
293
298
|
const tokens = tokenize(args.task ?? '');
|
|
294
299
|
const { score, reasons } = scoreSkill(skill, { repoStack: repo.signals, taskTokens: tokens, paths });
|
|
@@ -386,7 +391,6 @@ export async function indexCommand(args, flags) {
|
|
|
386
391
|
'',
|
|
387
392
|
`deferred entries return a pointer, not a body. Fetch one with: ${CLI} skills show <name>`,
|
|
388
393
|
].join('\n'),
|
|
389
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
390
394
|
);
|
|
391
395
|
return EXIT.OK;
|
|
392
396
|
}
|
|
@@ -429,14 +433,14 @@ async function emitAllFacts(facts, flags) {
|
|
|
429
433
|
if (deferred.length > 0) {
|
|
430
434
|
lines.push('', `DEFERRED ${deferred.length}: ${deferred.map((s) => s.name).join(', ')}`);
|
|
431
435
|
}
|
|
432
|
-
emit(lines.join('\n')
|
|
436
|
+
emit(lines.join('\n'));
|
|
433
437
|
return EXIT.OK;
|
|
434
438
|
}
|
|
435
439
|
|
|
436
440
|
// --- find ---------------------------------------------------------------------
|
|
437
441
|
|
|
438
442
|
export async function findCommand(args, flags) {
|
|
439
|
-
const root = flags
|
|
443
|
+
const root = commandRoot(flags);
|
|
440
444
|
let facts;
|
|
441
445
|
try {
|
|
442
446
|
facts = await skillsFindFacts(root, args.join(' '), flags);
|
|
@@ -449,7 +453,6 @@ export async function findCommand(args, flags) {
|
|
|
449
453
|
if (isJson(flags)) {
|
|
450
454
|
emitJson(
|
|
451
455
|
{ ok: true, query, workspace, results },
|
|
452
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
453
456
|
);
|
|
454
457
|
return EXIT.OK;
|
|
455
458
|
}
|
|
@@ -469,17 +472,17 @@ export async function findCommand(args, flags) {
|
|
|
469
472
|
`${r.name.padEnd(26)} ${kb(r.bytes).padStart(7)} ${r.reasons.join(' ')}`,
|
|
470
473
|
),
|
|
471
474
|
].join('\n'),
|
|
472
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
473
475
|
);
|
|
474
476
|
return EXIT.OK;
|
|
475
477
|
}
|
|
476
478
|
|
|
477
479
|
// --- brief: tier 1 ------------------------------------------------------------
|
|
478
480
|
export async function briefCommand(args, flags) {
|
|
479
|
-
const root = flags
|
|
481
|
+
const root = commandRoot(flags);
|
|
482
|
+
const ctx = repoContext(root);
|
|
480
483
|
let facts;
|
|
481
484
|
try {
|
|
482
|
-
facts = await skillsBriefFacts(root, flags);
|
|
485
|
+
facts = await skillsBriefFacts(root, { ...flags, ctx });
|
|
483
486
|
} catch (error) {
|
|
484
487
|
return fail(error.message, error.exitCode ?? EXIT.FAIL, error.remedy);
|
|
485
488
|
}
|
|
@@ -498,21 +501,20 @@ export async function briefCommand(args, flags) {
|
|
|
498
501
|
}
|
|
499
502
|
|
|
500
503
|
if (facts.warning) {
|
|
501
|
-
const repo = await fingerprint(
|
|
504
|
+
const repo = await ctx.fingerprint();
|
|
502
505
|
emit(
|
|
503
506
|
[
|
|
504
507
|
`repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
505
508
|
'',
|
|
506
509
|
facts.warning,
|
|
507
510
|
].join('\n'),
|
|
508
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
509
511
|
);
|
|
510
512
|
return EXIT.OK;
|
|
511
513
|
}
|
|
512
514
|
|
|
513
515
|
const { shown, shownDeferred, shownUsed, pointerOnly, excludedEntries, workspace, workspaceRoot, budget } = facts;
|
|
514
516
|
const lines = [];
|
|
515
|
-
const repo = await fingerprint(
|
|
517
|
+
const repo = await ctx.fingerprint();
|
|
516
518
|
|
|
517
519
|
lines.push(
|
|
518
520
|
`RESOLVED ${shown.length} skills, ${kb(shownUsed)}${flags.all || shownUsed <= budget ? '' : ' (budget capped)'} - repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
@@ -533,7 +535,7 @@ export async function briefCommand(args, flags) {
|
|
|
533
535
|
lines.push('', `NOT SELECTED ${hidden.length}`, '', ` notes: ${CLI} skills why <name> | ${CLI} skills show <name>`);
|
|
534
536
|
}
|
|
535
537
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
536
|
-
emit(lines.join('\n')
|
|
538
|
+
emit(lines.join('\n'));
|
|
537
539
|
return EXIT.OK;
|
|
538
540
|
}
|
|
539
541
|
|
|
@@ -578,14 +580,14 @@ export async function briefCommand(args, flags) {
|
|
|
578
580
|
}
|
|
579
581
|
|
|
580
582
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
581
|
-
emit(lines.join('\n')
|
|
583
|
+
emit(lines.join('\n'));
|
|
582
584
|
return EXIT.OK;
|
|
583
585
|
}
|
|
584
586
|
|
|
585
587
|
// --- show: tier 2 -------------------------------------------------------------
|
|
586
588
|
|
|
587
589
|
export async function showCommand(args, flags) {
|
|
588
|
-
const root = flags
|
|
590
|
+
const root = commandRoot(flags);
|
|
589
591
|
let facts;
|
|
590
592
|
try {
|
|
591
593
|
facts = await skillsShowFacts(root, args[0], flags);
|
|
@@ -595,7 +597,7 @@ export async function showCommand(args, flags) {
|
|
|
595
597
|
const { skill, workspace, body, bodyFrom, bodyText } = facts;
|
|
596
598
|
const source = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
597
599
|
if (isJson(flags)) {
|
|
598
|
-
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText }
|
|
600
|
+
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText });
|
|
599
601
|
return EXIT.OK;
|
|
600
602
|
}
|
|
601
603
|
emit(
|
|
@@ -618,7 +620,6 @@ export async function showCommand(args, flags) {
|
|
|
618
620
|
]
|
|
619
621
|
.filter((l) => l !== null)
|
|
620
622
|
.join('\n'),
|
|
621
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
622
623
|
);
|
|
623
624
|
return EXIT.OK;
|
|
624
625
|
}
|
|
@@ -626,7 +627,7 @@ export async function showCommand(args, flags) {
|
|
|
626
627
|
// --- why ----------------------------------------------------------------------
|
|
627
628
|
|
|
628
629
|
export async function whyCommand(args, flags) {
|
|
629
|
-
const root = flags
|
|
630
|
+
const root = commandRoot(flags);
|
|
630
631
|
let facts;
|
|
631
632
|
try {
|
|
632
633
|
facts = await skillsWhyFacts(root, args[0], flags);
|
|
@@ -641,7 +642,7 @@ export async function whyCommand(args, flags) {
|
|
|
641
642
|
// to scrape: a skill that can never be selected in this repo is a decision, not
|
|
642
643
|
// a line of output.
|
|
643
644
|
if (isJson(flags)) {
|
|
644
|
-
emitJson({ ok: true, command: 'skills why', ...facts }
|
|
645
|
+
emitJson({ ok: true, command: 'skills why', ...facts });
|
|
645
646
|
return EXIT.OK;
|
|
646
647
|
}
|
|
647
648
|
|
|
@@ -667,7 +668,7 @@ export async function whyCommand(args, flags) {
|
|
|
667
668
|
if (facts.variantOf) lines.push('', `variant-of ${facts.variantOf} (sibling group, one body per stack)`);
|
|
668
669
|
if (facts.requires?.length > 0) lines.push(`requires ${facts.requires.join(', ')}`);
|
|
669
670
|
|
|
670
|
-
emit(lines.join('\n')
|
|
671
|
+
emit(lines.join('\n'));
|
|
671
672
|
return EXIT.OK;
|
|
672
673
|
}
|
|
673
674
|
|
|
@@ -773,7 +774,7 @@ export async function evalCommand(positional, flags) {
|
|
|
773
774
|
}
|
|
774
775
|
|
|
775
776
|
lines.push('', okRun ? 'OK the resolver meets the gold corpus' : 'REGRESSION: the resolver no longer meets the gold corpus');
|
|
776
|
-
emit(lines.join('\n')
|
|
777
|
+
emit(lines.join('\n'));
|
|
777
778
|
return okRun ? EXIT.OK : EXIT.FAIL;
|
|
778
779
|
}
|
|
779
780
|
|
|
@@ -14,14 +14,15 @@ import {
|
|
|
14
14
|
evaluateSkillTriggers,
|
|
15
15
|
proposeSkillDescription,
|
|
16
16
|
} from '../graph/compile.mjs';
|
|
17
|
-
import { loadRunRecords } from '../
|
|
18
|
-
import { extractPatchFiles } from '../
|
|
19
|
-
import { resolveVaultDir } from '../
|
|
17
|
+
import { loadRunRecords } from '../measure/records.mjs';
|
|
18
|
+
import { extractPatchFiles } from '../testing/patches.mjs';
|
|
19
|
+
import { resolveVaultDir } from '../testing/vault.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
23
|
import { CLI } from '../name.mjs';
|
|
24
24
|
import { LOCAL_DIR } from '../state.mjs';
|
|
25
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
25
26
|
|
|
26
27
|
async function readSkillRawText(skill, workspaceRoot, root) {
|
|
27
28
|
const canonical = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
@@ -68,7 +69,7 @@ export async function compileCommand(args, flags) {
|
|
|
68
69
|
const { registry, errors, ok } = await loadRegistry(null, flags);
|
|
69
70
|
if (!ok) return fail(errors[0], EXIT.REGISTRY, WORKSPACE_REMEDY);
|
|
70
71
|
|
|
71
|
-
const root = resolve(flags
|
|
72
|
+
const root = resolve(commandRoot(flags));
|
|
72
73
|
const ws = await resolveWorkspace(flags);
|
|
73
74
|
const targetName = args[0] ?? null;
|
|
74
75
|
|
|
@@ -117,7 +118,6 @@ export async function compileCommand(args, flags) {
|
|
|
117
118
|
splitPlan: s.splitPlan,
|
|
118
119
|
})),
|
|
119
120
|
},
|
|
120
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
121
121
|
);
|
|
122
122
|
return EXIT.OK;
|
|
123
123
|
}
|
|
@@ -150,7 +150,7 @@ export async function compileCommand(args, flags) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
emit(lines.join('\n')
|
|
153
|
+
emit(lines.join('\n'));
|
|
154
154
|
return EXIT.OK;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -223,7 +223,6 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
223
223
|
: null,
|
|
224
224
|
proposal,
|
|
225
225
|
},
|
|
226
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
227
226
|
);
|
|
228
227
|
return EXIT.OK;
|
|
229
228
|
}
|
|
@@ -246,7 +245,7 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
246
245
|
}
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
emit(lines.join('\n')
|
|
248
|
+
emit(lines.join('\n'));
|
|
250
249
|
if (!mode.apply) {
|
|
251
250
|
note(`note dry-run nothing written; apply with: ${CLI} skills compile ${skill.name} --yes`);
|
|
252
251
|
}
|
|
@@ -38,6 +38,8 @@ 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';
|
|
42
|
+
import { parseFrontmatter } from '../text/frontmatter.mjs';
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Where skills live in this repo. The estate already has a convention - a
|
|
@@ -82,7 +84,7 @@ export async function installCommand(args, flags) {
|
|
|
82
84
|
const ws = await resolveWorkspace(flags);
|
|
83
85
|
if (ws.error) return fail(`workspace config: ${ws.error}`, EXIT.CONFIG, 'fix the JSON, or remove the file');
|
|
84
86
|
|
|
85
|
-
const root = flags
|
|
87
|
+
const root = commandRoot(flags);
|
|
86
88
|
const repo = await fingerprint(root);
|
|
87
89
|
const pick = await chooseSource(skill, repo.signals, flags, { root, workspaceRoot: ws.root });
|
|
88
90
|
if (pick.error) return fail(pick.error, EXIT.USAGE, pick.remedy);
|
|
@@ -210,7 +212,6 @@ export async function installCommand(args, flags) {
|
|
|
210
212
|
]
|
|
211
213
|
.filter((l) => l !== null)
|
|
212
214
|
.join('\n'),
|
|
213
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
214
215
|
);
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -498,13 +499,13 @@ async function dirEntries(root) {
|
|
|
498
499
|
*/
|
|
499
500
|
export async function wrapFlat(src, name) {
|
|
500
501
|
const text = await readFile(src, 'utf8');
|
|
501
|
-
const
|
|
502
|
-
if (
|
|
503
|
-
const keys =
|
|
502
|
+
const { front, block } = parseFrontmatter(text);
|
|
503
|
+
if (front === null) return `---\nname: ${name}\n---\n\n${text.trim()}\n`;
|
|
504
|
+
const keys = front
|
|
504
505
|
.split('\n')
|
|
505
506
|
.filter((l) => !/^name:/.test(l))
|
|
506
507
|
.join('\n');
|
|
507
|
-
return `---\nname: ${name}\n${keys}\n---\n\n${text.slice(
|
|
508
|
+
return `---\nname: ${name}\n${keys}\n---\n\n${text.slice(block.length).trim()}\n`;
|
|
508
509
|
}
|
|
509
510
|
|
|
510
511
|
async function fileSha(path) {
|