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
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
import { existsSync } from 'node:fs';
|
|
26
26
|
import { mkdir, readFile } from 'node:fs/promises';
|
|
27
27
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
28
|
-
import { commitJournal
|
|
28
|
+
import { commitJournal } from '../journal.mjs';
|
|
29
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
29
30
|
import { AGENT_HOOK_SHIM, renderAgentHookShim } from '../agenthooks/shim.mjs';
|
|
30
31
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
31
32
|
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
installedIn,
|
|
45
46
|
rendered,
|
|
46
47
|
} from '../agenthooks/render.mjs';
|
|
48
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
47
49
|
|
|
48
50
|
export async function agentHookCommand(args, flags) {
|
|
49
51
|
const event = args[0];
|
|
@@ -77,7 +79,7 @@ export async function agentHookCommand(args, flags) {
|
|
|
77
79
|
// dynamic import that only this event opens.
|
|
78
80
|
if (event === 'prompt-submit') {
|
|
79
81
|
const { deliverPrompt } = await import('../agenthooks/prompt.mjs');
|
|
80
|
-
const delivered = await deliverPrompt(row, payload);
|
|
82
|
+
const delivered = await deliverPrompt(row, payload, { root: flags.root });
|
|
81
83
|
if (delivered.skipped) {
|
|
82
84
|
note(`note ${id}: no packet delivered (${delivered.skipped})`);
|
|
83
85
|
return EXIT.OK;
|
|
@@ -167,7 +169,7 @@ export async function agentHookCommand(args, flags) {
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
export async function agentHooksCommand(sub, args, flags) {
|
|
170
|
-
const root = flags
|
|
172
|
+
const root = commandRoot(flags, args);
|
|
171
173
|
if (sub === 'install') return installHooks(root, flags);
|
|
172
174
|
if (sub === 'status') return hooksStatus(root, flags);
|
|
173
175
|
return fail(`unknown agent-hooks subcommand ${sub}`, EXIT.USAGE, `run: ${CLI} agent-hooks status`);
|
|
@@ -178,18 +180,26 @@ export async function agentHooksCommand(sub, args, flags) {
|
|
|
178
180
|
// `skipped` was an adapter the caller did not ask for (ADR-107).
|
|
179
181
|
const OTHER_LABEL = { none: 'NO FILE ', other: 'NOT OURS', skipped: 'SKIPPED ' };
|
|
180
182
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The agent-hooks install, split from `installHooks` into the plan and the apply so
|
|
185
|
+
* `init` can run the same writes inside its own journal (ADR-002: a second real use).
|
|
186
|
+
*
|
|
187
|
+
* The plan is pure: it reads each documented config, renders the entry, and returns the
|
|
188
|
+
* shim the same way, so a preview and an apply describe the same set of files. A config
|
|
189
|
+
* this tool cannot parse lands in `refused` with its action set to `refused`, and the
|
|
190
|
+
* caller decides what that means — this command blocks on it, and `init` inherits the
|
|
191
|
+
* refusal because writing the other configs while one is unreadable would leave the
|
|
192
|
+
* repository in a state where the report it printed is no longer true.
|
|
193
|
+
*/
|
|
194
|
+
export async function planAgentHooks(root, { harness }) {
|
|
183
195
|
// `--harness` narrows the install to one reader (ADR-107). The id is validated
|
|
184
196
|
// against the same table the loop reads, so a typo fails before anything is
|
|
185
197
|
// planned; the remedy names the ids the way `agent-hook` does.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`--harness <id>, one of: ${adapters().map((a) => a.id).join(', ')}`,
|
|
192
|
-
);
|
|
198
|
+
if (harness !== undefined && adapter(harness) === null) {
|
|
199
|
+
return {
|
|
200
|
+
error: `unknown harness \`${harness}\``,
|
|
201
|
+
remedy: `--harness <id>, one of: ${adapters().map((a) => a.id).join(', ')}`,
|
|
202
|
+
};
|
|
193
203
|
}
|
|
194
204
|
// The delivery is a second entry in the same config, and a repository asks for it by
|
|
195
205
|
// declaring a `brief` key rather than by having the entry. Read through a dynamic
|
|
@@ -201,7 +211,7 @@ async function installHooks(root, flags) {
|
|
|
201
211
|
listPromotedLessons(root).catch(() => []),
|
|
202
212
|
]);
|
|
203
213
|
const hasLessons = promotedLessons.length > 0;
|
|
204
|
-
const rows =
|
|
214
|
+
const rows = harness === undefined ? adapters() : adapters().filter((r) => r.id === harness);
|
|
205
215
|
const plan = [];
|
|
206
216
|
for (const row of rows) {
|
|
207
217
|
const path = hookPath(row.id);
|
|
@@ -231,7 +241,7 @@ async function installHooks(root, flags) {
|
|
|
231
241
|
for (const r of hookRows()) {
|
|
232
242
|
if (r.path === null) others.push({ id: r.id, path: null, state: 'none', note: r.note });
|
|
233
243
|
else if (adapter(r.id) === null) others.push({ id: r.id, path: r.path, state: 'other', note: r.note });
|
|
234
|
-
else if (
|
|
244
|
+
else if (harness !== undefined && r.id !== harness) {
|
|
235
245
|
others.push({ id: r.id, path: r.path, state: 'skipped', note: 'not requested; install without --harness to write every harness' });
|
|
236
246
|
}
|
|
237
247
|
}
|
|
@@ -239,13 +249,48 @@ async function installHooks(root, flags) {
|
|
|
239
249
|
const refused = plan.filter((p) => p.error);
|
|
240
250
|
|
|
241
251
|
// The shim every harness config points at: without it the entries above name a
|
|
242
|
-
// binary no harness has on PATH (review 0003 Q21).
|
|
243
|
-
// dependency the same way the git shims' does — a devDependency where the
|
|
244
|
-
// repository has a package.json, the release binary where it does not.
|
|
252
|
+
// binary no harness has on PATH (review 0003 Q21).
|
|
245
253
|
const shimText = renderAgentHookShim({ npm: (await readIfExists(join(root, 'package.json'))) !== null });
|
|
246
254
|
const shimBefore = await readBefore(join(root, AGENT_HOOK_SHIM));
|
|
247
255
|
const shimAction = shimBefore === null ? 'create' : shimBefore === shimText ? 'unchanged' : 'update';
|
|
248
|
-
const
|
|
256
|
+
const shim = { text: shimText, before: shimBefore, action: shimAction, row: { id: 'shim', path: AGENT_HOOK_SHIM, action: shimAction } };
|
|
257
|
+
|
|
258
|
+
return { plan, shim, others, promptLine, refused };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Write the planned agent-hooks entries and the shim into the caller's journal. Nothing
|
|
263
|
+
* is committed here: the caller commits its own journal under its own label, which is
|
|
264
|
+
* what separates this command's run (a standalone `agent-hooks install`) from `init`'s
|
|
265
|
+
* absorbed one (part of the adoption, committed with the rest of it).
|
|
266
|
+
*/
|
|
267
|
+
export async function applyAgentHooks(root, journal, { plan, shim, owner }) {
|
|
268
|
+
const written = [];
|
|
269
|
+
const hookChanges = [];
|
|
270
|
+
for (const p of plan.filter((x) => x.action !== 'unchanged')) {
|
|
271
|
+
const path = join(root, p.path);
|
|
272
|
+
hookChanges.push(change({ path, before: await readBefore(path), after: p.text, owner, reason: p.action }));
|
|
273
|
+
written.push(p);
|
|
274
|
+
}
|
|
275
|
+
// The shim is a file like the configs: unchanged installs leave it alone, and a
|
|
276
|
+
// changed one is journaled so `undo` removes or restores it with the configs.
|
|
277
|
+
let shimWritten = false;
|
|
278
|
+
if (shim.action !== 'unchanged') {
|
|
279
|
+
const shimPath = join(root, AGENT_HOOK_SHIM);
|
|
280
|
+
hookChanges.push(change({ path: shimPath, before: await readBefore(shimPath), after: shim.text, owner, reason: shim.action, mode: 0o755 }));
|
|
281
|
+
shimWritten = true;
|
|
282
|
+
}
|
|
283
|
+
await applyPlan(root, hookChanges, { mode: { apply: true }, journal, commit: false });
|
|
284
|
+
return { written, shimWritten };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function installHooks(root, flags) {
|
|
288
|
+
const mode = writeMode(flags);
|
|
289
|
+
const planned = await planAgentHooks(root, { harness: flags.harness });
|
|
290
|
+
if (planned.error) {
|
|
291
|
+
return fail(planned.error, EXIT.USAGE, planned.remedy);
|
|
292
|
+
}
|
|
293
|
+
const { plan, shim, others, promptLine, refused } = planned;
|
|
249
294
|
|
|
250
295
|
// The `--diff` pairs, read before anything is written so the before side is
|
|
251
296
|
// true in preview and in apply alike (R50).
|
|
@@ -255,7 +300,7 @@ async function installHooks(root, flags) {
|
|
|
255
300
|
plan
|
|
256
301
|
.filter((p) => p.action !== 'unchanged')
|
|
257
302
|
.map(async (p) => ({ path: p.path, before: await readBefore(join(root, p.path)), after: p.text }))
|
|
258
|
-
.concat(
|
|
303
|
+
.concat(shim.action === 'unchanged' ? [] : [{ path: AGENT_HOOK_SHIM, before: shim.before, after: shim.text }]),
|
|
259
304
|
)
|
|
260
305
|
: [];
|
|
261
306
|
|
|
@@ -267,7 +312,7 @@ async function installHooks(root, flags) {
|
|
|
267
312
|
root,
|
|
268
313
|
dryRun: true,
|
|
269
314
|
plan: plan.map(({ text, ...rest }) => rest),
|
|
270
|
-
shim:
|
|
315
|
+
shim: shim.row,
|
|
271
316
|
notWritten: others,
|
|
272
317
|
filesWritten: false,
|
|
273
318
|
...diffField(diffs, flags),
|
|
@@ -278,7 +323,7 @@ async function installHooks(root, flags) {
|
|
|
278
323
|
[
|
|
279
324
|
`repo ${root}`,
|
|
280
325
|
...plan.map((p) => `${p.action.toUpperCase().padEnd(9)} ${p.id.padEnd(12)} ${p.path}${p.error ? ` (${p.error})` : ''}`),
|
|
281
|
-
`${
|
|
326
|
+
`${shim.action.toUpperCase().padEnd(9)} shim ${AGENT_HOOK_SHIM}`,
|
|
282
327
|
...others.map((o) => `${OTHER_LABEL[o.state]} ${o.id.padEnd(12)} ${o.path ?? '(no hooks file is documented)'}`),
|
|
283
328
|
...promptLine,
|
|
284
329
|
'',
|
|
@@ -286,7 +331,6 @@ async function installHooks(root, flags) {
|
|
|
286
331
|
? `REFUSED ${refused.length} config file(s) could not be read; nothing written`
|
|
287
332
|
: `PLAN nothing written; apply with: ${CLI} agent-hooks install --yes`,
|
|
288
333
|
].join('\n'),
|
|
289
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
290
334
|
);
|
|
291
335
|
// After the report: the report says what, the diff shows it (R50).
|
|
292
336
|
emitDiffs(diffs, flags);
|
|
@@ -302,20 +346,7 @@ async function installHooks(root, flags) {
|
|
|
302
346
|
|
|
303
347
|
// Journaled so `undo` restores this run (R49).
|
|
304
348
|
const journal = [];
|
|
305
|
-
const written =
|
|
306
|
-
for (const p of plan.filter((x) => x.action !== 'unchanged')) {
|
|
307
|
-
await mkdir(join(root, dirname(p.path)), { recursive: true });
|
|
308
|
-
await journaledWriteFile(root, journal, join(root, p.path), p.text);
|
|
309
|
-
written.push(p);
|
|
310
|
-
}
|
|
311
|
-
// The shim is a file like the configs: unchanged installs leave it alone, and a
|
|
312
|
-
// changed one is journaled so `undo` removes or restores it with the configs.
|
|
313
|
-
let shimWritten = false;
|
|
314
|
-
if (shimAction !== 'unchanged') {
|
|
315
|
-
await mkdir(join(root, dirname(AGENT_HOOK_SHIM)), { recursive: true });
|
|
316
|
-
await journaledWriteFile(root, journal, join(root, AGENT_HOOK_SHIM), shimText, { mode: 0o755 });
|
|
317
|
-
shimWritten = true;
|
|
318
|
-
}
|
|
349
|
+
const { written, shimWritten } = await applyAgentHooks(root, journal, { plan, shim, owner: 'agent-hooks install' });
|
|
319
350
|
await commitJournal(root, 'agent-hooks install', journal);
|
|
320
351
|
|
|
321
352
|
const notes = [];
|
|
@@ -331,7 +362,7 @@ async function installHooks(root, flags) {
|
|
|
331
362
|
command: 'agent-hooks install',
|
|
332
363
|
root,
|
|
333
364
|
plan: plan.map(({ text, ...rest }) => rest),
|
|
334
|
-
shim: { ...
|
|
365
|
+
shim: { ...shim.row, written: shimWritten },
|
|
335
366
|
notWritten: others,
|
|
336
367
|
written: written.map((p) => p.path),
|
|
337
368
|
filesWritten: true,
|
|
@@ -344,13 +375,12 @@ async function installHooks(root, flags) {
|
|
|
344
375
|
[
|
|
345
376
|
`repo ${root}`,
|
|
346
377
|
...plan.map((p) => `${p.action.toUpperCase().padEnd(9)} ${p.id.padEnd(12)} ${p.path}`),
|
|
347
|
-
`${
|
|
378
|
+
`${shim.action.toUpperCase().padEnd(9)} shim ${AGENT_HOOK_SHIM}`,
|
|
348
379
|
...others.map((o) => `${OTHER_LABEL[o.state]} ${o.id.padEnd(12)} ${o.path ?? '(no hooks file is documented)'}`),
|
|
349
380
|
...promptLine,
|
|
350
381
|
'',
|
|
351
382
|
`WROTE ${written.length} hooks file(s)${shimWritten ? ' and the shim' : ''}; each one runs the ${AGENT_HOOK_SHIM} shim before the tool call`,
|
|
352
383
|
].join('\n'),
|
|
353
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
354
384
|
);
|
|
355
385
|
// After the report: the report says what, the diff shows it (R50).
|
|
356
386
|
emitDiffs(diffs, flags);
|
|
@@ -448,7 +478,7 @@ async function hooksStatus(root, flags) {
|
|
|
448
478
|
lines.push('', 'problems:');
|
|
449
479
|
for (const p of problems) lines.push(` ${p}`);
|
|
450
480
|
}
|
|
451
|
-
emit(lines.join('\n')
|
|
481
|
+
emit(lines.join('\n'));
|
|
452
482
|
return problems.length === 0 ? EXIT.OK : EXIT.FAIL;
|
|
453
483
|
}
|
|
454
484
|
|
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 '../measure/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
|
|
|
@@ -207,7 +233,7 @@ export async function benchReportCommand(positional, flags) {
|
|
|
207
233
|
);
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
const root = resolve(flags
|
|
236
|
+
const root = resolve(commandRoot(flags, pos));
|
|
211
237
|
const arms = listFlag(flags.arm);
|
|
212
238
|
|
|
213
239
|
let report;
|
|
@@ -243,7 +269,7 @@ export async function benchReportCommand(positional, flags) {
|
|
|
243
269
|
return EXIT.OK;
|
|
244
270
|
}
|
|
245
271
|
|
|
246
|
-
emit(`${report.markdown}\n\n### Map\n\n${formatRecall(mapRecall)}
|
|
272
|
+
emit(`${report.markdown}\n\n### Map\n\n${formatRecall(mapRecall)}`);
|
|
247
273
|
return EXIT.OK;
|
|
248
274
|
}
|
|
249
275
|
|
|
@@ -256,8 +282,12 @@ async function recallOrUnmeasured(root) {
|
|
|
256
282
|
}
|
|
257
283
|
|
|
258
284
|
export async function benchAblateCommand(positional, flags) {
|
|
259
|
-
const root = resolve(flags
|
|
285
|
+
const root = resolve(commandRoot(flags, positional));
|
|
260
286
|
const { dryRun, apply } = writeMode(flags);
|
|
287
|
+
if (apply) {
|
|
288
|
+
const missing = requireHarness(flags, 'bench ablate');
|
|
289
|
+
if (missing !== null) return missing;
|
|
290
|
+
}
|
|
261
291
|
const harness = flags.harness ?? 'gold';
|
|
262
292
|
const model = flags.model;
|
|
263
293
|
const reps = flags.reps ?? 1;
|
|
@@ -302,13 +332,18 @@ export async function benchAblateCommand(positional, flags) {
|
|
|
302
332
|
lines.push(`remedy run: ${CLI} bench ablate --yes`);
|
|
303
333
|
}
|
|
304
334
|
|
|
305
|
-
emit(lines.join('\n')
|
|
335
|
+
emit(lines.join('\n'));
|
|
306
336
|
return EXIT.OK;
|
|
307
337
|
}
|
|
308
338
|
|
|
309
339
|
export async function benchProbesCommand(positional, flags) {
|
|
310
|
-
const root = resolve(flags
|
|
340
|
+
const root = resolve(commandRoot(flags, positional));
|
|
311
341
|
const { dryRun, apply } = writeMode(flags);
|
|
342
|
+
if (apply) {
|
|
343
|
+
const missing = requireHarness(flags, 'bench probes');
|
|
344
|
+
if (missing !== null) return missing;
|
|
345
|
+
}
|
|
346
|
+
|
|
312
347
|
const arms = listFlag(flags.arm);
|
|
313
348
|
const probes = listFlag(flags.task);
|
|
314
349
|
const harness = flags.harness ?? 'gold';
|
|
@@ -353,13 +388,17 @@ export async function benchProbesCommand(positional, flags) {
|
|
|
353
388
|
lines.push(`remedy run: ${CLI} bench probes --yes`);
|
|
354
389
|
}
|
|
355
390
|
|
|
356
|
-
emit(lines.join('\n')
|
|
391
|
+
emit(lines.join('\n'));
|
|
357
392
|
return EXIT.OK;
|
|
358
393
|
}
|
|
359
394
|
|
|
360
395
|
export async function benchVariantsCommand(positional, flags) {
|
|
361
|
-
const root = resolve(flags
|
|
396
|
+
const root = resolve(commandRoot(flags, positional));
|
|
362
397
|
const { dryRun, apply } = writeMode(flags);
|
|
398
|
+
if (apply) {
|
|
399
|
+
const missing = requireHarness(flags, 'bench variants');
|
|
400
|
+
if (missing !== null) return missing;
|
|
401
|
+
}
|
|
363
402
|
let result;
|
|
364
403
|
try {
|
|
365
404
|
result = await selectRuleVariants(root, {
|
|
@@ -390,7 +429,7 @@ export async function benchVariantsCommand(positional, flags) {
|
|
|
390
429
|
),
|
|
391
430
|
];
|
|
392
431
|
if (!result.applied) lines.push(`remedy run: ${CLI} bench variants --yes`);
|
|
393
|
-
emit(lines.join('\n')
|
|
432
|
+
emit(lines.join('\n'));
|
|
394
433
|
return EXIT.OK;
|
|
395
434
|
}
|
|
396
435
|
|
|
@@ -405,13 +444,13 @@ export async function benchVariantsCommand(positional, flags) {
|
|
|
405
444
|
// the row says precision >= 0.8 on >= 30 labelled diffs promotes it to a gate,
|
|
406
445
|
// and until a bench here shows that, every report calls the critic advisory.
|
|
407
446
|
export async function benchCriticCommand(positional, flags, commands = []) {
|
|
408
|
-
const root = resolve(flags
|
|
447
|
+
const root = resolve(commandRoot(flags, positional));
|
|
409
448
|
const labelsPath = flags.labels ?? '.simt/local/critic-labels.jsonl';
|
|
410
449
|
const result = await criticBench(root, { labelsPath, flags, commands });
|
|
411
450
|
if (typeof result === 'number') return result;
|
|
412
451
|
|
|
413
452
|
if (isJson(flags)) {
|
|
414
|
-
emitJson(result
|
|
453
|
+
emitJson(result);
|
|
415
454
|
return EXIT.OK;
|
|
416
455
|
}
|
|
417
456
|
const lines = [
|
|
@@ -424,6 +463,140 @@ export async function benchCriticCommand(positional, flags, commands = []) {
|
|
|
424
463
|
];
|
|
425
464
|
if (result.labelsNote) lines.push(`labels ${result.labelsNote}`);
|
|
426
465
|
for (const error of result.errors) lines.push(`error ${error}`);
|
|
427
|
-
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'));
|
|
428
601
|
return EXIT.OK;
|
|
429
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
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
// commands owns which file would be learning about ownership twice to learn nothing
|
|
19
19
|
// new, and the next .github file would arrive with a third command to remember.
|
|
20
20
|
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { commitJournal, journaledWriteFile } from '../journal.mjs';
|
|
21
|
+
import { readFile } from 'node:fs/promises';
|
|
22
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
24
23
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
25
24
|
|
|
26
25
|
import { EXIT, emit, emitJson, isJson, note, writeMode } from '../output/index.mjs';
|
|
27
26
|
import { planWorkflow, renderWorkflow, WORKFLOW_FILE } from '../ci/workflow.mjs';
|
|
28
27
|
import { planDependabot, renderDependabot, DEPENDABOT_FILE } from '../ci/dependabot.mjs';
|
|
29
28
|
import { CLI } from '../name.mjs';
|
|
29
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* The two plans, with the render and the plan-time data each needs kept attached.
|
|
@@ -38,12 +38,12 @@ import { CLI } from '../name.mjs';
|
|
|
38
38
|
function targets() {
|
|
39
39
|
return [
|
|
40
40
|
{ key: 'workflow', file: WORKFLOW_FILE, plan: planWorkflow, render: () => renderWorkflow({ own: true }) },
|
|
41
|
-
{ key: 'dependabot', file: DEPENDABOT_FILE, plan: planDependabot, render: (plan) => renderDependabot(plan.facts ?? [], { stack: plan.stack ?? [], own: true }) },
|
|
41
|
+
{ key: 'dependabot', file: DEPENDABOT_FILE, plan: planDependabot, render: (plan) => renderDependabot(plan.facts ?? [], { stack: plan.stack ?? [], own: true, root: plan.root }) },
|
|
42
42
|
];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export async function ciCommand(positional, flags) {
|
|
46
|
-
const root = flags
|
|
46
|
+
const root = commandRoot(flags, positional);
|
|
47
47
|
const mode = writeMode(flags);
|
|
48
48
|
const checkOnly = flags.check === true;
|
|
49
49
|
|
|
@@ -152,19 +152,18 @@ export async function ciCommand(positional, flags) {
|
|
|
152
152
|
return EXIT.OK;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// Journaled so `undo` restores this run (R49).
|
|
156
|
-
|
|
155
|
+
// Journaled so `undo` restores this run (R49). The renderer takes the plan because
|
|
156
|
+
// detection-driven content is a function of this repo's facts: a dependabot config
|
|
157
|
+
// rendered without them would watch nothing, which is the same silent-nothing the
|
|
158
|
+
// detection ruling exists to stop.
|
|
159
|
+
const changes = [];
|
|
157
160
|
for (const { key, plan, render } of targets()) {
|
|
158
161
|
const p = plans.find((x) => x.key === key);
|
|
159
162
|
if (p.action === 'unchanged') continue;
|
|
160
|
-
await
|
|
161
|
-
// The renderer takes the plan because detection-driven content is a function of
|
|
162
|
-
// this repo's facts: a dependabot config rendered without them would watch
|
|
163
|
-
// nothing, which is the same silent-nothing the detection ruling exists to stop.
|
|
164
|
-
await journaledWriteFile(root, journal, p.path, render(p));
|
|
165
|
-
note(`wrote ${p.file} (${p.action}; the template and this copy now agree)`);
|
|
163
|
+
changes.push(change({ path: p.path, before: await readBefore(p.path), after: render(p), owner: 'ci', reason: 'the template and this copy now agree' }));
|
|
166
164
|
}
|
|
167
|
-
await
|
|
165
|
+
await applyPlan(root, changes, { mode, label: 'ci' });
|
|
166
|
+
for (const c of changes) note(`wrote ${c.path.replace(`${root}/`, '')} (${c.action}; ${c.reason})`);
|
|
168
167
|
if (isJson(flags)) {
|
|
169
168
|
emitJson(
|
|
170
169
|
{
|