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/bench.mjs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
// default and write artifacts to `.simt/bench/` only when authorised with
|
|
6
6
|
// `--yes` (and not `--dry-run`). `bench report` is read-only.
|
|
7
7
|
|
|
8
|
-
import { resolve } from 'node:path';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
9
10
|
|
|
10
11
|
import { CLI } from '../name.mjs';
|
|
11
12
|
import { EXIT, emit, emitJson, fail, isJson, writeMode } from '../output/index.mjs';
|
|
12
13
|
import { DEFAULT_MINE_TARGET, mineTasks } from '../bench/mine.mjs';
|
|
14
|
+
import { buildCorpusDocument, verifyCorpusDocument, writeCorpusDocument } from '../bench/corpus.mjs';
|
|
13
15
|
import { regradeRun, runBench } from '../bench/run.mjs';
|
|
14
16
|
import { buildBenchReport } from '../bench/report.mjs';
|
|
15
17
|
import { formatRecall, measureRecall } from '../index/map.mjs';
|
|
@@ -18,14 +20,33 @@ import { runBenchProbes } from '../bench/probes.mjs';
|
|
|
18
20
|
import { selectRuleVariants } from '../bench/variants.mjs';
|
|
19
21
|
import { criticBench } from '../review/critic.mjs';
|
|
20
22
|
import { listFlag } from './skills.mjs';
|
|
23
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Refuse an authorised bench run that names no harness.
|
|
27
|
+
*
|
|
28
|
+
* The default harness is `gold`, which applies the reference patch instead of asking
|
|
29
|
+
* an agent: a `--yes` run with no harness measured the grader and stored the result as
|
|
30
|
+
* an arm's evidence (review 0003 Q49). A default cannot be mistaken for a measurement
|
|
31
|
+
* if there is no default.
|
|
32
|
+
*/
|
|
33
|
+
function requireHarness(flags, command) {
|
|
34
|
+
if (typeof flags.harness === 'string' && flags.harness.length > 0) return null;
|
|
35
|
+
return fail(
|
|
36
|
+
`${command} --yes needs an explicit --harness`,
|
|
37
|
+
EXIT.USAGE,
|
|
38
|
+
`pass --harness <name>: an agent adapter such as \`--harness claude\`, or a calibration harness (\`gold\`, \`noop\`)`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
21
41
|
|
|
22
42
|
export async function benchMineCommand(positional, flags) {
|
|
23
|
-
const root = resolve(flags
|
|
43
|
+
const root = resolve(commandRoot(flags, positional));
|
|
24
44
|
const { dryRun, apply } = writeMode(flags);
|
|
25
45
|
|
|
26
46
|
const result = await mineTasks(root, {
|
|
27
47
|
apply,
|
|
28
48
|
target: DEFAULT_MINE_TARGET,
|
|
49
|
+
replace: flags.replace === true,
|
|
29
50
|
});
|
|
30
51
|
|
|
31
52
|
if (!result.ok) {
|
|
@@ -64,7 +85,7 @@ export async function benchMineCommand(positional, flags) {
|
|
|
64
85
|
for (const task of result.tasks) {
|
|
65
86
|
const subject = task.prompt.split('\n')[0];
|
|
66
87
|
lines.push(
|
|
67
|
-
`task ${task.id} f2p=${task.f2p.length} p2p=${task.
|
|
88
|
+
`task ${task.id} f2p=${task.f2p.length} p2p=${task.p2p.length} files=${task.gold.files} +${task.gold.linesAdded}/-${task.gold.linesRemoved} ${subject}`,
|
|
68
89
|
);
|
|
69
90
|
}
|
|
70
91
|
} else {
|
|
@@ -75,12 +96,12 @@ export async function benchMineCommand(positional, flags) {
|
|
|
75
96
|
lines.push(`remedy run: ${CLI} bench mine --yes`);
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
emit(lines.join('\n')
|
|
99
|
+
emit(lines.join('\n'));
|
|
79
100
|
return EXIT.OK;
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
export async function benchRunCommand(positional, flags) {
|
|
83
|
-
const root = resolve(flags
|
|
104
|
+
const root = resolve(commandRoot(flags, positional));
|
|
84
105
|
const { dryRun, apply } = writeMode(flags);
|
|
85
106
|
|
|
86
107
|
if (flags.regrade !== undefined) {
|
|
@@ -123,10 +144,15 @@ export async function benchRunCommand(positional, flags) {
|
|
|
123
144
|
`run ${run.runId}/${run.task} arm=${run.arm} resolved=${run.resolved} gates=${run.gatesPassed} reproduced=${run.reproduced}`,
|
|
124
145
|
);
|
|
125
146
|
}
|
|
126
|
-
emit(lines.join('\n')
|
|
147
|
+
emit(lines.join('\n'));
|
|
127
148
|
return code;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
if (apply) {
|
|
152
|
+
const missing = requireHarness(flags, 'bench run');
|
|
153
|
+
if (missing !== null) return missing;
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
const arms = listFlag(flags.arm);
|
|
131
157
|
const tasks = listFlag(flags.task);
|
|
132
158
|
const harness = flags.harness ?? 'gold';
|
|
@@ -189,7 +215,7 @@ export async function benchRunCommand(positional, flags) {
|
|
|
189
215
|
lines.push(`remedy run: ${CLI} bench run --yes`);
|
|
190
216
|
}
|
|
191
217
|
|
|
192
|
-
emit(lines.join('\n')
|
|
218
|
+
emit(lines.join('\n'));
|
|
193
219
|
return EXIT.OK;
|
|
194
220
|
}
|
|
195
221
|
|
|
@@ -217,6 +243,7 @@ export async function benchReportCommand(positional, flags) {
|
|
|
217
243
|
harness: flags.harness,
|
|
218
244
|
model: flags.model,
|
|
219
245
|
compare: compareItems.length === 2 ? compareItems : null,
|
|
246
|
+
crossGroup: Boolean(flags.crossGroup),
|
|
220
247
|
});
|
|
221
248
|
} catch (error) {
|
|
222
249
|
return fail(
|
|
@@ -242,7 +269,7 @@ export async function benchReportCommand(positional, flags) {
|
|
|
242
269
|
return EXIT.OK;
|
|
243
270
|
}
|
|
244
271
|
|
|
245
|
-
emit(`${report.markdown}\n\n### Map\n\n${formatRecall(mapRecall)}
|
|
272
|
+
emit(`${report.markdown}\n\n### Map\n\n${formatRecall(mapRecall)}`);
|
|
246
273
|
return EXIT.OK;
|
|
247
274
|
}
|
|
248
275
|
|
|
@@ -255,8 +282,12 @@ async function recallOrUnmeasured(root) {
|
|
|
255
282
|
}
|
|
256
283
|
|
|
257
284
|
export async function benchAblateCommand(positional, flags) {
|
|
258
|
-
const root = resolve(flags
|
|
285
|
+
const root = resolve(commandRoot(flags, positional));
|
|
259
286
|
const { dryRun, apply } = writeMode(flags);
|
|
287
|
+
if (apply) {
|
|
288
|
+
const missing = requireHarness(flags, 'bench ablate');
|
|
289
|
+
if (missing !== null) return missing;
|
|
290
|
+
}
|
|
260
291
|
const harness = flags.harness ?? 'gold';
|
|
261
292
|
const model = flags.model;
|
|
262
293
|
const reps = flags.reps ?? 1;
|
|
@@ -301,13 +332,18 @@ export async function benchAblateCommand(positional, flags) {
|
|
|
301
332
|
lines.push(`remedy run: ${CLI} bench ablate --yes`);
|
|
302
333
|
}
|
|
303
334
|
|
|
304
|
-
emit(lines.join('\n')
|
|
335
|
+
emit(lines.join('\n'));
|
|
305
336
|
return EXIT.OK;
|
|
306
337
|
}
|
|
307
338
|
|
|
308
339
|
export async function benchProbesCommand(positional, flags) {
|
|
309
|
-
const root = resolve(flags
|
|
340
|
+
const root = resolve(commandRoot(flags, positional));
|
|
310
341
|
const { dryRun, apply } = writeMode(flags);
|
|
342
|
+
if (apply) {
|
|
343
|
+
const missing = requireHarness(flags, 'bench probes');
|
|
344
|
+
if (missing !== null) return missing;
|
|
345
|
+
}
|
|
346
|
+
|
|
311
347
|
const arms = listFlag(flags.arm);
|
|
312
348
|
const probes = listFlag(flags.task);
|
|
313
349
|
const harness = flags.harness ?? 'gold';
|
|
@@ -352,13 +388,17 @@ export async function benchProbesCommand(positional, flags) {
|
|
|
352
388
|
lines.push(`remedy run: ${CLI} bench probes --yes`);
|
|
353
389
|
}
|
|
354
390
|
|
|
355
|
-
emit(lines.join('\n')
|
|
391
|
+
emit(lines.join('\n'));
|
|
356
392
|
return EXIT.OK;
|
|
357
393
|
}
|
|
358
394
|
|
|
359
395
|
export async function benchVariantsCommand(positional, flags) {
|
|
360
|
-
const root = resolve(flags
|
|
396
|
+
const root = resolve(commandRoot(flags, positional));
|
|
361
397
|
const { dryRun, apply } = writeMode(flags);
|
|
398
|
+
if (apply) {
|
|
399
|
+
const missing = requireHarness(flags, 'bench variants');
|
|
400
|
+
if (missing !== null) return missing;
|
|
401
|
+
}
|
|
362
402
|
let result;
|
|
363
403
|
try {
|
|
364
404
|
result = await selectRuleVariants(root, {
|
|
@@ -389,7 +429,7 @@ export async function benchVariantsCommand(positional, flags) {
|
|
|
389
429
|
),
|
|
390
430
|
];
|
|
391
431
|
if (!result.applied) lines.push(`remedy run: ${CLI} bench variants --yes`);
|
|
392
|
-
emit(lines.join('\n')
|
|
432
|
+
emit(lines.join('\n'));
|
|
393
433
|
return EXIT.OK;
|
|
394
434
|
}
|
|
395
435
|
|
|
@@ -404,13 +444,13 @@ export async function benchVariantsCommand(positional, flags) {
|
|
|
404
444
|
// the row says precision >= 0.8 on >= 30 labelled diffs promotes it to a gate,
|
|
405
445
|
// and until a bench here shows that, every report calls the critic advisory.
|
|
406
446
|
export async function benchCriticCommand(positional, flags, commands = []) {
|
|
407
|
-
const root = resolve(flags
|
|
408
|
-
const labelsPath = flags.labels ?? '.simt/critic-labels.jsonl';
|
|
447
|
+
const root = resolve(commandRoot(flags, positional));
|
|
448
|
+
const labelsPath = flags.labels ?? '.simt/local/critic-labels.jsonl';
|
|
409
449
|
const result = await criticBench(root, { labelsPath, flags, commands });
|
|
410
450
|
if (typeof result === 'number') return result;
|
|
411
451
|
|
|
412
452
|
if (isJson(flags)) {
|
|
413
|
-
emitJson(result
|
|
453
|
+
emitJson(result);
|
|
414
454
|
return EXIT.OK;
|
|
415
455
|
}
|
|
416
456
|
const lines = [
|
|
@@ -423,6 +463,140 @@ export async function benchCriticCommand(positional, flags, commands = []) {
|
|
|
423
463
|
];
|
|
424
464
|
if (result.labelsNote) lines.push(`labels ${result.labelsNote}`);
|
|
425
465
|
for (const error of result.errors) lines.push(`error ${error}`);
|
|
426
|
-
emit(lines.join('\n')
|
|
466
|
+
emit(lines.join('\n'));
|
|
467
|
+
return EXIT.OK;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// `simt bench export` / `simt bench import` (review 0003 Q48).
|
|
471
|
+
//
|
|
472
|
+
// The vault is keyed on the repository's root commit and lives outside every
|
|
473
|
+
// checkout. That is what makes it safe as a hidden verifier, and it is also
|
|
474
|
+
// why a clone cannot read the corpus mined on another machine: the committed
|
|
475
|
+
// tasks failed with ENOENT from every other checkout. A sealed document
|
|
476
|
+
// carries the descriptors and both patches for every task in one file, so an
|
|
477
|
+
// export on one machine and an import on another reproduce the corpus
|
|
478
|
+
// exactly, and the seal is checked before a single byte is trusted.
|
|
479
|
+
|
|
480
|
+
export async function benchExportCommand(positional, flags) {
|
|
481
|
+
const out = positional[0];
|
|
482
|
+
if (!out) {
|
|
483
|
+
return fail(
|
|
484
|
+
'bench export needs a target file: bench export <corpus.json>',
|
|
485
|
+
EXIT.USAGE,
|
|
486
|
+
`export the sealed corpus: ${CLI} bench export corpus.json --yes`,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const root = resolve(commandRoot(flags));
|
|
490
|
+
const { dryRun, apply } = writeMode(flags);
|
|
491
|
+
|
|
492
|
+
let doc;
|
|
493
|
+
try {
|
|
494
|
+
doc = await buildCorpusDocument(root);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
return fail(
|
|
497
|
+
`${root}: ${error.message ?? error}`,
|
|
498
|
+
EXIT.CONFIG,
|
|
499
|
+
`mine tasks first: ${CLI} bench mine --yes`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (doc.taskCount === 0) {
|
|
504
|
+
return fail(
|
|
505
|
+
`${root}: no mined tasks to export`,
|
|
506
|
+
EXIT.USAGE,
|
|
507
|
+
`mine tasks first: ${CLI} bench mine --yes`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (apply) {
|
|
512
|
+
const absOut = resolve(out);
|
|
513
|
+
await mkdir(dirname(absOut), { recursive: true });
|
|
514
|
+
await writeFile(absOut, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (isJson(flags)) {
|
|
518
|
+
emitJson({
|
|
519
|
+
ok: true,
|
|
520
|
+
command: 'bench export',
|
|
521
|
+
root,
|
|
522
|
+
dryRun,
|
|
523
|
+
applied: apply,
|
|
524
|
+
out: resolve(out),
|
|
525
|
+
taskCount: doc.taskCount,
|
|
526
|
+
seal: doc.seal,
|
|
527
|
+
});
|
|
528
|
+
return EXIT.OK;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const lines = [
|
|
532
|
+
`bench export ${resolve(out)}`,
|
|
533
|
+
`mode ${apply ? 'applied' : dryRun ? 'dry-run (writes nothing)' : 'preview (pass --yes to write)'}`,
|
|
534
|
+
`tasks ${doc.taskCount}`,
|
|
535
|
+
`seal ${doc.seal}`,
|
|
536
|
+
];
|
|
537
|
+
if (!apply) lines.push(`remedy run: ${CLI} bench export ${out} --yes`);
|
|
538
|
+
emit(lines.join('\n'));
|
|
539
|
+
return EXIT.OK;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export async function benchImportCommand(positional, flags) {
|
|
543
|
+
const file = positional[0];
|
|
544
|
+
if (!file) {
|
|
545
|
+
return fail(
|
|
546
|
+
'bench import needs a corpus file: bench import <corpus.json>',
|
|
547
|
+
EXIT.USAGE,
|
|
548
|
+
`import a sealed corpus: ${CLI} bench import corpus.json --yes`,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const root = resolve(commandRoot(flags));
|
|
552
|
+
const { dryRun, apply } = writeMode(flags);
|
|
553
|
+
|
|
554
|
+
let doc;
|
|
555
|
+
try {
|
|
556
|
+
doc = JSON.parse(await readFile(file, 'utf8'));
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return fail(
|
|
559
|
+
`${file}: ${error.message ?? error}`,
|
|
560
|
+
EXIT.USAGE,
|
|
561
|
+
`export one with: ${CLI} bench export ${file} --yes`,
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const problem = verifyCorpusDocument(doc);
|
|
566
|
+
if (problem !== null) {
|
|
567
|
+
return fail(
|
|
568
|
+
`${file}: ${problem}`,
|
|
569
|
+
EXIT.CONFIG,
|
|
570
|
+
`export a fresh corpus: ${CLI} bench export ${file} --yes`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const result = await writeCorpusDocument(root, doc, { apply });
|
|
575
|
+
|
|
576
|
+
if (isJson(flags)) {
|
|
577
|
+
emitJson({
|
|
578
|
+
ok: true,
|
|
579
|
+
command: 'bench import',
|
|
580
|
+
root,
|
|
581
|
+
file: resolve(file),
|
|
582
|
+
dryRun,
|
|
583
|
+
applied: result.applied,
|
|
584
|
+
taskCount: result.tasks,
|
|
585
|
+
tasksDir: result.tasksDir,
|
|
586
|
+
vaultRoot: result.vaultRoot,
|
|
587
|
+
seal: doc.seal,
|
|
588
|
+
});
|
|
589
|
+
return EXIT.OK;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const lines = [
|
|
593
|
+
`bench import ${resolve(file)}`,
|
|
594
|
+
`mode ${apply ? 'applied' : dryRun ? 'dry-run (writes nothing)' : 'preview (pass --yes to write)'}`,
|
|
595
|
+
`imported ${result.tasks} task(s) seal=${doc.seal}`,
|
|
596
|
+
`tasks ${result.tasksDir}`,
|
|
597
|
+
`vault ${result.vaultRoot}`,
|
|
598
|
+
];
|
|
599
|
+
if (!apply) lines.push(`remedy run: ${CLI} bench import ${file} --yes`);
|
|
600
|
+
emit(lines.join('\n'));
|
|
427
601
|
return EXIT.OK;
|
|
428
602
|
}
|
package/src/commands/brief.mjs
CHANGED
|
@@ -19,8 +19,9 @@ import { CLI } from '../name.mjs';
|
|
|
19
19
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
20
20
|
import { DEFAULT_BRIEF_BYTES, explainPacket } from '../brief/packet.mjs';
|
|
21
21
|
import { assembleBrief } from '../brief/assemble.mjs';
|
|
22
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
22
23
|
|
|
23
|
-
export async function briefCommand(args, flags) {
|
|
24
|
+
export async function briefCommand(args, flags, commands = []) {
|
|
24
25
|
if (args.length > 1) {
|
|
25
26
|
return fail(`brief takes one task, not ${args.length}`, EXIT.USAGE, `run: ${CLI} brief "<task>"`);
|
|
26
27
|
}
|
|
@@ -33,11 +34,12 @@ export async function briefCommand(args, flags) {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const result = await assembleBrief({
|
|
36
|
-
root: flags
|
|
37
|
+
root: commandRoot(flags),
|
|
37
38
|
task: String(args[0] ?? flags.task ?? ''),
|
|
38
39
|
paths: flags.paths === undefined ? null : flags.paths,
|
|
39
40
|
budget,
|
|
40
41
|
flags,
|
|
42
|
+
commands,
|
|
41
43
|
});
|
|
42
44
|
if (result.error) return result.error;
|
|
43
45
|
// Unreachable from here — only a delivered brief asks for a cold index to be reported
|
|
@@ -67,12 +69,11 @@ export async function briefCommand(args, flags) {
|
|
|
67
69
|
explain: flags.explain === true ? explainPacket(items, { cut }) : undefined,
|
|
68
70
|
text: packed.text,
|
|
69
71
|
},
|
|
70
|
-
{ pretty: flags.pretty === true, maxBytes: Number(flags.maxBytes ?? 0) || 0 },
|
|
71
72
|
);
|
|
72
73
|
return EXIT.OK;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const body = flags.explain === true ? [packed.text, '', 'EXPLAIN', ...explainPacket(items, { cut })] : [packed.text];
|
|
76
|
-
emit(body.join('\n')
|
|
77
|
+
emit(body.join('\n'));
|
|
77
78
|
return EXIT.OK;
|
|
78
79
|
}
|
package/src/commands/ci.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import { EXIT, emit, emitJson, isJson, note, writeMode } from '../output/index.m
|
|
|
27
27
|
import { planWorkflow, renderWorkflow, WORKFLOW_FILE } from '../ci/workflow.mjs';
|
|
28
28
|
import { planDependabot, renderDependabot, DEPENDABOT_FILE } from '../ci/dependabot.mjs';
|
|
29
29
|
import { CLI } from '../name.mjs';
|
|
30
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* The two plans, with the render and the plan-time data each needs kept attached.
|
|
@@ -43,7 +44,7 @@ function targets() {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export async function ciCommand(positional, flags) {
|
|
46
|
-
const root = flags
|
|
47
|
+
const root = commandRoot(flags, positional);
|
|
47
48
|
const mode = writeMode(flags);
|
|
48
49
|
const checkOnly = flags.check === true;
|
|
49
50
|
|
package/src/commands/compare.mjs
CHANGED
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
import { basename, resolve } from 'node:path';
|
|
23
23
|
|
|
24
|
-
import { EXIT,
|
|
24
|
+
import { EXIT, fail, note, present } from '../output/index.mjs';
|
|
25
25
|
import { provenanceDigest } from '../projection/project.mjs';
|
|
26
26
|
import { GATES } from '../hooks/gates.mjs';
|
|
27
|
-
import {
|
|
27
|
+
import { buildRows, resolveConcurrency, summarise } from '../compare/row.mjs';
|
|
28
28
|
import { discover } from '../compare/discover.mjs';
|
|
29
29
|
import { learnAcrossEstate, renderLearningLines } from '../compare/learn.mjs';
|
|
30
30
|
import { targetRoot, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
@@ -60,12 +60,13 @@ export async function compareCommand(positional, flags) {
|
|
|
60
60
|
const expectedSha = await provenanceDigest();
|
|
61
61
|
const context = { expectedSha, gates: new Set(Object.keys(GATES)), gateKinds: GATE_KINDS };
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
// process;
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// Parallel with a cap, in discovery order. 16 fingerprint+region computations
|
|
64
|
+
// each spawn a git process; unbounded parallelism is a burst of subprocesses
|
|
65
|
+
// with no speedup, and sequential builds are what took 48 s over MCP for 17
|
|
66
|
+
// repos (review 0003 Q38). The cap keeps the burst bounded while overlapping
|
|
67
|
+
// the spawn wait; the rows still come back in input order, so two runs diff
|
|
68
|
+
// cleanly and determinism of the output is untouched.
|
|
69
|
+
const rows = await buildRows(found.repos, context, { concurrency: resolveConcurrency(flags) });
|
|
69
70
|
const learning = await learnAcrossEstate(found.repos);
|
|
70
71
|
|
|
71
72
|
const summary = summarise(rows, {
|
|
@@ -77,50 +78,50 @@ export async function compareCommand(positional, flags) {
|
|
|
77
78
|
directories: found.directories.length,
|
|
78
79
|
});
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
81
|
+
const payload = {
|
|
82
|
+
ok: !rows.some((r) => r.problems.length > 0),
|
|
83
|
+
command: 'compare',
|
|
84
|
+
root: summary.root,
|
|
85
|
+
shape: summary.shape,
|
|
86
|
+
source: summary.source,
|
|
87
|
+
expectedSha: summary.expectedSha,
|
|
88
|
+
// Spread, not a hand-listed copy. A copy is a second place to forget: a field
|
|
89
|
+
// added to `summarise` renders in the text output and silently vanishes from
|
|
90
|
+
// `--json`, which is the combination automation trusts most. That is not
|
|
91
|
+
// hypothetical — `withOwnHooks` and `staleRuleFiles` both did it, and both
|
|
92
|
+
// were caught only because a test asserts the two shapes agree.
|
|
93
|
+
summary: {
|
|
94
|
+
repos: summary.repos,
|
|
95
|
+
adopted: summary.adopted,
|
|
96
|
+
unmanaged: summary.unmanaged,
|
|
97
|
+
broken: summary.broken,
|
|
98
|
+
drifted: summary.drifted,
|
|
99
|
+
current: summary.current,
|
|
100
|
+
noRegion: summary.noRegion,
|
|
101
|
+
malformed: summary.malformed,
|
|
102
|
+
staleRuleFiles: summary.staleRuleFiles,
|
|
103
|
+
packageRegions: summary.packageRegions,
|
|
104
|
+
stalePackages: summary.stalePackages,
|
|
105
|
+
hooksRouted: summary.hooksRouted,
|
|
106
|
+
withProblems: summary.withProblems,
|
|
107
|
+
withOwnHooks: summary.withOwnHooks,
|
|
108
|
+
shas: summary.shas,
|
|
109
|
+
},
|
|
110
|
+
rows,
|
|
111
|
+
learning,
|
|
112
|
+
worktrees: found.worktrees.map((w) => ({
|
|
113
|
+
name: w.name,
|
|
114
|
+
path: w.path,
|
|
115
|
+
owner: w.ownerName,
|
|
116
|
+
ownerMatchesDirName: w.ownerMatches,
|
|
117
|
+
})),
|
|
118
|
+
directories: found.directories.map((d) => basename(d)),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (present(payload, () => render(rows, summary, found, learning), flags) === 'json') {
|
|
120
122
|
return rows.some((r) => r.problems.length > 0) ? EXIT.FAIL : EXIT.OK;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
emit(render(rows, summary, found, learning), { maxBytes: flags.maxBytes ?? 0 });
|
|
124
125
|
if (summary.shape === 'repo') {
|
|
125
126
|
note('note one repository compared; pass --root at a workspace directory to compare its siblings');
|
|
126
127
|
} else if (target.source === 'none') {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `$fpath`. A report line above it would be a report line in the user's shell
|
|
6
6
|
// config.
|
|
7
7
|
|
|
8
|
-
import { EXIT,
|
|
8
|
+
import { EXIT, fail, present } from '../output/index.mjs';
|
|
9
9
|
import { SHELLS, renderCompletions } from '../completions.mjs';
|
|
10
10
|
|
|
11
11
|
export async function completionsCommand(args, flags, commands) {
|
|
@@ -18,10 +18,6 @@ export async function completionsCommand(args, flags, commands) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const script = renderCompletions(shell, commands);
|
|
21
|
-
|
|
22
|
-
emitJson({ ok: true, command: 'completions', shell, script });
|
|
23
|
-
return EXIT.OK;
|
|
24
|
-
}
|
|
25
|
-
emit(script, { maxBytes: flags.maxBytes ?? 0 });
|
|
21
|
+
present({ ok: true, command: 'completions', shell, script }, () => script, flags);
|
|
26
22
|
return EXIT.OK;
|
|
27
23
|
}
|
package/src/commands/decide.mjs
CHANGED
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
// a rulings skeleton, and structured `simt-affects` + `simt-supersedes` frontmatter
|
|
3
3
|
// (plan 0003 R34).
|
|
4
4
|
|
|
5
|
-
import { join
|
|
5
|
+
import { join } from 'node:path';
|
|
6
6
|
import { EXIT, emit, emitJson, fail, isJson, note, writeMode } from '../output/index.mjs';
|
|
7
7
|
import { CLI } from '../name.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import { decisionsCutoff, decisionsDir } from '../decisions/dir.mjs';
|
|
9
9
|
import { lintDecisions, scaffoldDecision } from '../memory/decide.mjs';
|
|
10
10
|
import { commitJournal } from '../journal.mjs';
|
|
11
11
|
import { diffField, emitDiffs } from '../diff.mjs';
|
|
12
12
|
import { parsePathList } from '../argv.mjs';
|
|
13
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
13
14
|
|
|
14
15
|
export async function decideCommand(args, flags) {
|
|
15
|
-
const root = flags
|
|
16
|
+
const root = commandRoot(flags);
|
|
16
17
|
|
|
17
18
|
if (flags.check === true) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
19
|
+
// Q55: one resolution — the manifest key, then the detected directory —
|
|
20
|
+
// and the cutoff from the same config, never a hard-coded number or path.
|
|
21
|
+
const relDir = await decisionsDir(root);
|
|
22
|
+
const res = await lintDecisions(join(root, relDir), { historicalCutoff: await decisionsCutoff(root) });
|
|
21
23
|
|
|
22
24
|
if (isJson(flags)) {
|
|
23
25
|
emitJson({
|
|
@@ -30,7 +32,7 @@ export async function decideCommand(args, flags) {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
if (res.findings.length === 0) {
|
|
33
|
-
emit('decisions ok'
|
|
35
|
+
emit('decisions ok');
|
|
34
36
|
return EXIT.OK;
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -41,7 +43,6 @@ export async function decideCommand(args, flags) {
|
|
|
41
43
|
...(f.remedy ? [` remedy: ${f.remedy}`] : []),
|
|
42
44
|
])
|
|
43
45
|
.join('\n'),
|
|
44
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
45
46
|
);
|
|
46
47
|
return EXIT.FAIL;
|
|
47
48
|
}
|
|
@@ -122,7 +123,7 @@ export async function decideCommand(args, flags) {
|
|
|
122
123
|
: `would write ${res.relPath}; apply with: ${CLI} decide ${JSON.stringify(res.title)} --yes`,
|
|
123
124
|
];
|
|
124
125
|
|
|
125
|
-
emit(lines.join('\n')
|
|
126
|
+
emit(lines.join('\n'));
|
|
126
127
|
// After the report: the report says what, the diff shows it (R50).
|
|
127
128
|
emitDiffs(diffs, flags);
|
|
128
129
|
return EXIT.OK;
|
package/src/commands/distill.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export async function distillCommand(args, flags) {
|
|
|
16
16
|
if (typeof result === 'number') return result;
|
|
17
17
|
|
|
18
18
|
if (isJson(flags)) {
|
|
19
|
-
emitJson(result
|
|
19
|
+
emitJson(result);
|
|
20
20
|
return EXIT.OK;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -27,10 +27,11 @@ export async function distillCommand(args, flags) {
|
|
|
27
27
|
`engine ${result.engine}`,
|
|
28
28
|
`budget ${result.budget} bytes`,
|
|
29
29
|
`citations ${result.citations}`,
|
|
30
|
-
|
|
30
|
+
// A cached proposal predates this field; `?? []` keeps the report from crashing on
|
|
31
|
+
// the file the command wrote one release ago (review 0003 Q52).
|
|
32
|
+
...(result.verified === true ? [] : (result.faults ?? []).map((f) => `unverified ${f}`)),
|
|
31
33
|
`proposal ${result.proposal.path} (${result.proposal.state}, ${result.proposal.bytes} bytes)`,
|
|
32
34
|
].join('\n'),
|
|
33
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
34
35
|
);
|
|
35
36
|
return EXIT.OK;
|
|
36
37
|
}
|