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
|
@@ -15,6 +15,35 @@ one is a mechanical procedure with a fixed order, because every step after it de
|
|
|
15
15
|
on the one before: you cannot review a diff you have not staged, and you cannot claim
|
|
16
16
|
evidence for a test you have not run.
|
|
17
17
|
|
|
18
|
+
## Commit message
|
|
19
|
+
|
|
20
|
+
A message is read by the next agent before they are told what to do. It says what the
|
|
21
|
+
commit is, what it answers, and what it changed, so a reader never reconstructs that
|
|
22
|
+
from the diff.
|
|
23
|
+
|
|
24
|
+
The title is Conventional Commits: imperative, no trailing period, a short scope slug
|
|
25
|
+
that names the subsystem — not a ticket id.
|
|
26
|
+
|
|
27
|
+
feat(dispatcher): skip the test gate on a push that already passed
|
|
28
|
+
|
|
29
|
+
The body is plain sections in this order. Drop any section that has nothing to say; a
|
|
30
|
+
one-line commit needs none of them.
|
|
31
|
+
|
|
32
|
+
What: <one sentence — what this commit is>
|
|
33
|
+
Why: <what it answers — the task, defect, or decision that asked for it>
|
|
34
|
+
Before: <the state this replaces, briefly>
|
|
35
|
+
After: <the state it leaves, briefly>
|
|
36
|
+
|
|
37
|
+
"Before" and "After" are a short state summary, not a diff: enough to see what moved,
|
|
38
|
+
not enough to need the patch. Cite a decision record where one settles the change, and
|
|
39
|
+
never claim a test in the body that was not run on this tree.
|
|
40
|
+
|
|
41
|
+
The message ends with a trailer on its own line, after the body - attribution to the
|
|
42
|
+
tool, in the form git renders as a co-author. It is part of the message, not a
|
|
43
|
+
section, so it stays even when every section is dropped:
|
|
44
|
+
|
|
45
|
+
Co-Authored-By: Simt Dev <noreply@simt.space>
|
|
46
|
+
|
|
18
47
|
## Order
|
|
19
48
|
|
|
20
49
|
1. **Run the focused test the change needs**, through the repository's own runner,
|
|
@@ -24,10 +53,8 @@ evidence for a test you have not run.
|
|
|
24
53
|
alone. Report what you left alone; silence about it reads as "I reverted it".
|
|
25
54
|
3. **Read the staged diff.** Not the working tree — the diff. It is what will be
|
|
26
55
|
committed, and it is the last chance to notice a file that does not belong.
|
|
27
|
-
4. **Write the message.**
|
|
28
|
-
|
|
29
|
-
The subject says what changed; the body says why, and what was deliberately not
|
|
30
|
-
changed.
|
|
56
|
+
4. **Write the message in the format above.** The title names what changed; the body
|
|
57
|
+
says why, and what was deliberately not changed.
|
|
31
58
|
5. **Commit.** `git commit`, non-interactive. Report the SHA.
|
|
32
59
|
6. **Stop.**
|
|
33
60
|
|
|
@@ -44,6 +71,32 @@ Never use `--no-verify`. A hook that fails is telling you something about the ch
|
|
|
44
71
|
skipping it turns a gate into a suggestion and the next person has no way to know it
|
|
45
72
|
was skipped.
|
|
46
73
|
|
|
74
|
+
Once the push is approved, you may push directly without the pre-push gate re-running
|
|
75
|
+
the full suite when you already ran that suite green on the current state and changed
|
|
76
|
+
nothing since: the gate records every run against the tree it ran on, recognises that
|
|
77
|
+
evidence, and skips the re-run, reporting the skip and the record that earned it.
|
|
78
|
+
Cite the already-passing run as the pass — do not claim a re-run that did not happen.
|
|
79
|
+
(ADR-120)
|
|
80
|
+
|
|
81
|
+
## Worktrees
|
|
82
|
+
|
|
83
|
+
A worktree is the unit of isolation for concurrent work. The rules that keep it honest:
|
|
84
|
+
|
|
85
|
+
- **One agent, one worktree, one slice.** Never direct two agents at the same files
|
|
86
|
+
concurrently; give each its own worktree and a slice whose files do not overlap.
|
|
87
|
+
- **Start from the record, not from memory.** `simt slice start <id>` creates the
|
|
88
|
+
worktree for the tracker row and returns its path; work in that path, not the main
|
|
89
|
+
checkout.
|
|
90
|
+
- **Finish where you started.** `simt slice finish <id>` runs the declared gates and
|
|
91
|
+
commits from the worktree; `simt plan done <id> <sha>` records the SHA in the main
|
|
92
|
+
checkout's tracker. A slice is not done when its worktree is green — it is done
|
|
93
|
+
when the tracker row is complete and the SHA is recorded.
|
|
94
|
+
- **Leave the worktree when the slice is done.** A finished worktree that still holds
|
|
95
|
+
a dirty tree is a claim about work that is not committed; remove it, and report what
|
|
96
|
+
you left behind if there was any.
|
|
97
|
+
- **Never rewrite history across worktrees.** A rebase, reset or amend in one worktree
|
|
98
|
+
changes the base every other worktree branched from; coordinate it, or do not do it.
|
|
99
|
+
|
|
47
100
|
## What never belongs in a commit
|
|
48
101
|
|
|
49
102
|
- Failing, partial, or unrun work. A commit is a claim; a partial one is a false one.
|
package/src/advice/advice.mjs
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
// records or a prices table price the tokens, total tokens otherwise. Ordering by a number
|
|
22
22
|
// that does not exist for any run would be nondeterminism, not economics.
|
|
23
23
|
import { classifyTask } from '../brief/kind.mjs';
|
|
24
|
-
import { computeRunCost, isRunResolved, loadPrices
|
|
25
|
-
import {
|
|
24
|
+
import { computeRunCost, isRunResolved, loadPrices } from '../measure/metrics.mjs';
|
|
25
|
+
import { loadRunRecords } from '../measure/records.mjs';
|
|
26
|
+
import { loadTasks } from '../measure/tasks.mjs';
|
|
26
27
|
import { CONFIG_DIR } from '../name.mjs';
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -60,7 +60,7 @@ export async function promptOptIn(root) {
|
|
|
60
60
|
* a minute while a repository is walked — is how the hook gets uninstalled, and the
|
|
61
61
|
* packet is not worth a prompt that cannot be typed.
|
|
62
62
|
*/
|
|
63
|
-
export async function deliverPrompt(row, payload) {
|
|
63
|
+
export async function deliverPrompt(row, payload, { root: flagRoot } = {}) {
|
|
64
64
|
const task = promptText(row, payload);
|
|
65
65
|
if (task === null) return { skipped: 'the payload carries no prompt this harness documents' };
|
|
66
66
|
|
|
@@ -68,7 +68,10 @@ export async function deliverPrompt(row, payload) {
|
|
|
68
68
|
// and resolved to the repository's top level the same way: the manifest, like the
|
|
69
69
|
// lease file, is a repository-relative fact, and a prompt submitted from a
|
|
70
70
|
// subdirectory must find it (review 0003 Q16).
|
|
71
|
-
|
|
71
|
+
// `--root` outranks the payload here as it does on the decision path: a packaged hook
|
|
72
|
+
// runs from the plugin directory, so the payload's cwd would name the wrong repository
|
|
73
|
+
// and find no manifest, which delivered nothing and looked like an opt-out.
|
|
74
|
+
const root = repoRoot(factsFrom(row, payload, { root: flagRoot }).root);
|
|
72
75
|
const optIn = await promptOptIn(root);
|
|
73
76
|
if (!optIn.optedIn) return { skipped: optIn.why };
|
|
74
77
|
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { CLI } from '../name.mjs';
|
|
50
50
|
import { LOADERS } from '../projection/loaders.mjs';
|
|
51
|
+
import { harnessHooks, harnessPayloads } from '../harness/records.mjs';
|
|
51
52
|
import { renderOpencodePlugin } from './opencode.mjs';
|
|
52
53
|
import { EVENTS } from './policy.mjs';
|
|
53
54
|
import { AGENT_HOOK_SHIM } from './shim.mjs';
|
|
@@ -183,188 +184,93 @@ function patchFiles(text) {
|
|
|
183
184
|
return files.filter((f) => f.path !== '');
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
post: {
|
|
218
|
-
event: 'PostToolUse',
|
|
219
|
-
matcher: 'Bash',
|
|
220
|
-
verified: 'code.claude.com/docs/en/hooks, read 2026-10-19',
|
|
221
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
codex: {
|
|
225
|
-
id: 'codex',
|
|
226
|
-
event: 'PreToolUse',
|
|
227
|
-
matcher: 'Bash|apply_patch',
|
|
228
|
-
denyExit: 2,
|
|
229
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-16',
|
|
230
|
-
// Codex is the one harness that will not run a hook until a person has approved the
|
|
231
|
-
// exact definition, keyed by its hash — so an install that does not say this leaves
|
|
232
|
-
// the repository with a hook that never fires and no way to tell why.
|
|
233
|
-
trust: 'Codex skips a hook nobody has trusted: review it with /hooks, or the entry stays inert.',
|
|
234
|
-
root: (p) => p.cwd,
|
|
235
|
-
extract: (p) => ({
|
|
236
|
-
command: p.tool_name === 'Bash' ? (p.tool_input?.command ?? null) : null,
|
|
237
|
-
// apply_patch carries its targets inside the patch text, one header line per file;
|
|
238
|
-
// a move names its destination on the `+++` line after `*** Move File:`, and the
|
|
239
|
-
// destination is the file the move writes (review 0003 Q16).
|
|
240
|
-
writes:
|
|
241
|
-
p.tool_name === 'apply_patch'
|
|
242
|
-
? patchWrites(String(p.tool_input?.command ?? ''))
|
|
243
|
-
: [], // entries carry {path, patch} so the region check maps hunks (Q17)
|
|
244
|
-
}),
|
|
245
|
-
deny: (message) => ({
|
|
246
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: message },
|
|
247
|
-
}),
|
|
248
|
-
prompt: {
|
|
249
|
-
event: 'UserPromptSubmit',
|
|
250
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
251
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text } }),
|
|
252
|
-
},
|
|
253
|
-
post: {
|
|
254
|
-
event: 'PostToolUse',
|
|
255
|
-
matcher: 'Bash',
|
|
256
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
257
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
cursor: {
|
|
261
|
-
id: 'cursor',
|
|
262
|
-
event: 'preToolUse',
|
|
263
|
-
matcher: 'Shell|Write|Edit',
|
|
264
|
-
denyExit: 2,
|
|
265
|
-
verified: 'cursor.com/docs/agent/hooks, read 2026-10-16',
|
|
266
|
-
root: (p) => p.working_directory ?? p.cwd,
|
|
267
|
-
extract: (p) => ({
|
|
268
|
-
command: p.tool_name === 'Shell' ? (p.tool_input?.command ?? null) : null,
|
|
269
|
-
writes: ['Write', 'Edit'].includes(p.tool_name)
|
|
270
|
-
? [writeOf(p.tool_input?.file_path ?? p.tool_input?.path, p.tool_input)].filter(Boolean)
|
|
271
|
-
: [],
|
|
272
|
-
}),
|
|
273
|
-
deny: (message) => ({ permission: 'deny', user_message: message, agent_message: message }),
|
|
274
|
-
// beforeSubmitPrompt is documented as continue/block and nothing else, so there is no
|
|
275
|
-
// key to deliver a packet in. Writing the entry anyway would put a command that reads
|
|
276
|
-
// a repository on the path of every prompt a person types, to say nothing.
|
|
277
|
-
prompt: null,
|
|
278
|
-
post: null,
|
|
279
|
-
},
|
|
280
|
-
antigravity: {
|
|
281
|
-
id: 'antigravity',
|
|
282
|
-
event: 'PreToolUse',
|
|
283
|
-
matcher: 'run_command|write_to_file|replace_file_content|multi_replace_file_content',
|
|
284
|
-
// The page documents the decision JSON and not an exit code, so the denial travels
|
|
285
|
-
// in the JSON only: a non-zero exit whose meaning the vendor has not stated is not
|
|
286
|
-
// a block, it is a guess that happens to be loud.
|
|
287
|
-
denyExit: 0,
|
|
288
|
-
verified: 'antigravity.google/docs/hooks, read 2026-10-16',
|
|
289
|
-
root: (p) => p.workspacePaths?.[0] ?? p.cwd,
|
|
290
|
-
extract: (p) => {
|
|
291
|
-
const call = p.toolCall ?? {};
|
|
292
|
-
const args = call.args ?? {};
|
|
293
|
-
return {
|
|
294
|
-
command: call.name === 'run_command' ? (args.CommandLine ?? null) : null,
|
|
295
|
-
writes: ['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(call.name)
|
|
296
|
-
? [writeOf(args.TargetFile, args)].filter(Boolean)
|
|
297
|
-
: [], // CodeContent/old/new travel with the path for the region check (Q17)
|
|
298
|
-
};
|
|
299
|
-
},
|
|
300
|
-
deny: (message) => ({ decision: 'deny', reason: message }),
|
|
301
|
-
// No prompt event is documented for this harness's hooks file, and a guessed event
|
|
302
|
-
// name is a hook that never fires while its config says it does.
|
|
303
|
-
prompt: null,
|
|
304
|
-
post: null,
|
|
305
|
-
},
|
|
306
|
-
gemini: {
|
|
307
|
-
id: 'gemini',
|
|
308
|
-
event: 'BeforeTool',
|
|
309
|
-
matcher: 'run_shell_command|write_file|replace',
|
|
310
|
-
// The vendor's timeout unit is milliseconds (default 60000), not the seconds every
|
|
311
|
-
// other row uses; a bare 10 here would be a ten-millisecond budget, which is a hook
|
|
312
|
-
// that never finishes. The row carries its own value so the shared constant stays
|
|
313
|
-
// honest for the four vendors that measure in seconds.
|
|
314
|
-
timeout: 10000,
|
|
315
|
-
// The reference documents the decision JSON and an exit-2 system block; the denial
|
|
316
|
-
// travels in the JSON, as antigravity does.
|
|
317
|
-
denyExit: 0,
|
|
318
|
-
verified: 'geminicli.com/docs/hooks/reference/, read 2026-10-26',
|
|
319
|
-
root: (p) => p.cwd,
|
|
320
|
-
extract: (p) => ({
|
|
321
|
-
command: p.tool_name === 'run_shell_command' ? (p.tool_input?.command ?? null) : null,
|
|
322
|
-
writes: ['write_file', 'replace'].includes(p.tool_name)
|
|
323
|
-
? [writeOf(p.tool_input?.file_path, p.tool_input)].filter(Boolean)
|
|
187
|
+
// The payload a hook reads: where the tool name and its input sit, and the keys that
|
|
188
|
+
// name a command and a file. A record cannot carry a function, so the engine reads the
|
|
189
|
+
// record's keys and applies them: a harness that puts a field somewhere new is a new
|
|
190
|
+
// record, not a new branch here (Q71).
|
|
191
|
+
function pick(obj, keys) {
|
|
192
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
193
|
+
for (const k of keys) {
|
|
194
|
+
const v = obj[k];
|
|
195
|
+
if (v !== undefined && v !== null) return v;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function extractOf(spec) {
|
|
201
|
+
return (p) => {
|
|
202
|
+
const payload = p ?? {};
|
|
203
|
+
// antigravity nests the call under `toolCall`; the rest name the tool at the top
|
|
204
|
+
// level. `toolNameKey` is present only on the nested shape, so its presence is
|
|
205
|
+
// the detector.
|
|
206
|
+
const nested = spec.toolNameKey !== undefined;
|
|
207
|
+
const call = nested ? (payload[spec.toolKey] ?? {}) : null;
|
|
208
|
+
const toolName = nested ? call[spec.toolNameKey] : payload[spec.toolKey];
|
|
209
|
+
const input = nested ? (call[spec.inputSubKey] ?? {}) : (payload[spec.inputKey] ?? {});
|
|
210
|
+
return {
|
|
211
|
+
command: (spec.shellTools ?? []).includes(toolName) ? (input[spec.commandKey] ?? null) : null,
|
|
212
|
+
// apply_patch carries its targets inside the patch text (writesFrom: "patch");
|
|
213
|
+
// every other write tool names its file through the path keys.
|
|
214
|
+
writes: (spec.writeTools ?? []).includes(toolName)
|
|
215
|
+
? spec.writesFrom === 'patch'
|
|
216
|
+
? patchWrites(String(input[spec.commandKey] ?? ''))
|
|
217
|
+
: [writeOf(pick(input, spec.pathOverrides?.[toolName] ?? spec.pathKeys), input)].filter(Boolean)
|
|
324
218
|
: [],
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The record's `rootKeys`: a list of accessor paths, first non-null wins. The caller
|
|
224
|
+
// applies its `?? process.cwd()` floor (factsFrom).
|
|
225
|
+
function rootOf(spec) {
|
|
226
|
+
return (p) => {
|
|
227
|
+
for (const path of spec.rootKeys ?? []) {
|
|
228
|
+
let v = p;
|
|
229
|
+
for (const seg of path) v = v?.[seg];
|
|
230
|
+
if (v !== undefined && v !== null) return v;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The `__SIMT_TEXT__` sentinel in a record's deny and delivery templates: the text a
|
|
237
|
+
// decision or a delivery carries is substituted for it, and JSON.stringify downstream
|
|
238
|
+
// escapes whatever it carries.
|
|
239
|
+
const TEXT = '__SIMT_TEXT__';
|
|
240
|
+
|
|
241
|
+
function applyText(value, text) {
|
|
242
|
+
if (typeof value === 'string') return value === TEXT ? text : value;
|
|
243
|
+
if (Array.isArray(value)) return value.map((v) => applyText(v, text));
|
|
244
|
+
if (value && typeof value === 'object') {
|
|
245
|
+
const out = {};
|
|
246
|
+
for (const [k, v] of Object.entries(value)) out[k] = applyText(v, text);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The hooks dialect per harness, as records (Q71): the data a config document and a
|
|
253
|
+
// decision are shaped by, with the page each shape was read from. What a record cannot
|
|
254
|
+
// carry is attached here — `root` and `extract` read the record's payload keys, and
|
|
255
|
+
// `deny`, `prompt.context` and `post.context` apply its templates. Module-internal on
|
|
256
|
+
// purpose: `adapters()` and `adapter()` are the only doors in, and both check the
|
|
257
|
+
// reader table, so an id with no hooks file is no adapter.
|
|
258
|
+
const HOOKS = harnessHooks();
|
|
259
|
+
const PAYLOADS = harnessPayloads();
|
|
260
|
+
|
|
261
|
+
const ADAPTERS = Object.fromEntries(
|
|
262
|
+
Object.entries(HOOKS).map(([id, h]) => [
|
|
263
|
+
id,
|
|
264
|
+
{
|
|
265
|
+
...h,
|
|
266
|
+
root: rootOf(PAYLOADS[id]),
|
|
267
|
+
extract: extractOf(PAYLOADS[id]),
|
|
268
|
+
deny: (message) => applyText(h.deny, message),
|
|
269
|
+
...(h.prompt ? { prompt: { ...h.prompt, context: (text) => applyText(h.prompt.delivery, text) } } : {}),
|
|
270
|
+
...(h.post ? { post: { ...h.post, context: (text) => applyText(h.post.delivery, text) } } : {}),
|
|
364
271
|
},
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
};
|
|
272
|
+
]),
|
|
273
|
+
);
|
|
368
274
|
|
|
369
275
|
/** The six adapters, in the order the reader table lists them. */
|
|
370
276
|
export function adapters() {
|
package/src/agenthooks/shim.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// harness's PATH. A harness config naming the bare `simt` binary therefore
|
|
5
5
|
// runs nothing on Claude (a non-2 exit is silent: no enforcement) and blocks
|
|
6
6
|
// every tool call on Cursor (its hook fails closed). The fix is a shim beside
|
|
7
|
-
// the git shims, resolved the same way: `.simt
|
|
7
|
+
// the git shims, resolved the same way: `.simt/hooks/agent-hook` locates the
|
|
8
8
|
// dispatcher with the shared `renderLocator` block and hands over, so every
|
|
9
9
|
// harness config names the shim and never the bare binary (review 0003 Q21).
|
|
10
10
|
//
|
package/src/agents/render.mjs
CHANGED
|
@@ -25,9 +25,12 @@ import { mkdir, readFile } from 'node:fs/promises';
|
|
|
25
25
|
import { dirname, join } from 'node:path';
|
|
26
26
|
|
|
27
27
|
import { PACKAGE_ROOT } from '../workspace.mjs';
|
|
28
|
-
import {
|
|
28
|
+
import { parseFrontmatter } from '../text/frontmatter.mjs';
|
|
29
29
|
import { LOADERS } from '../projection/loaders.mjs';
|
|
30
30
|
import { journaledWriteFile } from '../journal.mjs';
|
|
31
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
32
|
+
import { readBefore } from '../diff.mjs';
|
|
33
|
+
import { readIfExists } from '../util/fs.mjs';
|
|
31
34
|
|
|
32
35
|
/** The one agent this tool ships, and the filename stem every harness shares. */
|
|
33
36
|
const AGENT_FILE = 'simt-agent.md';
|
|
@@ -149,7 +152,7 @@ function parseSimpleFront(front) {
|
|
|
149
152
|
*/
|
|
150
153
|
export async function loadAgent() {
|
|
151
154
|
const raw = await readFile(join(PACKAGE_ROOT, 'agents', AGENT_FILE), 'utf8');
|
|
152
|
-
const { front, body } =
|
|
155
|
+
const { front, body } = parseFrontmatter(raw);
|
|
153
156
|
const meta = parseSimpleFront(front ?? '');
|
|
154
157
|
if (!meta.name || !meta.description) {
|
|
155
158
|
throw new Error(`${AGENT_FILE} must declare a name and a description in its frontmatter`);
|
|
@@ -213,15 +216,6 @@ export function rendered(adapterRow, agent, existing) {
|
|
|
213
216
|
return { text, action: 'updated' };
|
|
214
217
|
}
|
|
215
218
|
|
|
216
|
-
/** Read a file as UTF-8, or null when it is not there. */
|
|
217
|
-
async function readIfExists(path) {
|
|
218
|
-
try {
|
|
219
|
-
return await readFile(path, 'utf8');
|
|
220
|
-
} catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
219
|
/**
|
|
226
220
|
* The agent files a repository should carry, one per harness that documents an
|
|
227
221
|
* agents directory, plus the companion skill file. Reads each existing file and
|
|
@@ -252,13 +246,16 @@ export async function planAgents(root, { harness } = {}) {
|
|
|
252
246
|
* Skips files that are already unchanged. Returns the paths written.
|
|
253
247
|
*/
|
|
254
248
|
export async function writeAgentPlan(root, journal, plan) {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
249
|
+
const changes = await Promise.all(
|
|
250
|
+
plan
|
|
251
|
+
.filter((x) => x.action !== 'unchanged')
|
|
252
|
+
.map(async (p) => {
|
|
253
|
+
const path = join(root, p.path);
|
|
254
|
+
return change({ path, before: await readBefore(path), after: p.text, owner: 'agents', reason: p.action });
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
await applyPlan(root, changes, { mode: { apply: true }, journal, commit: false });
|
|
258
|
+
return plan.filter((x) => x.action !== 'unchanged').map((p) => p.path);
|
|
262
259
|
}
|
|
263
260
|
|
|
264
261
|
/**
|
package/src/api.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The library surface: the read commands as data (review 0003 Q63).
|
|
2
|
+
//
|
|
3
|
+
// Every function here is the one the CLI and the MCP tools already call. Nothing in
|
|
4
|
+
// this file renders: a caller that wants text, JSON or an MCP result renders these
|
|
5
|
+
// results itself, which is the whole point - a UI, a harness and a terminal asking the
|
|
6
|
+
// same question must not get three answers, and they cannot disagree if they are
|
|
7
|
+
// reading one function's return value.
|
|
8
|
+
//
|
|
9
|
+
// The write half arrives through `run(command, input)`: a writer still renders its plan
|
|
10
|
+
// from the command module, because a person reviews that diff before it is applied
|
|
11
|
+
// (ADR-001), but a library caller now gets that plan back as text and documents instead
|
|
12
|
+
// of having it printed at a terminal it does not own. `run` resolves the same registry
|
|
13
|
+
// and calls the same handler `dispatch` does, so the two surfaces cannot answer
|
|
14
|
+
// differently.
|
|
15
|
+
//
|
|
16
|
+
// The exports map in package.json points `import 'simt-dev'` here, and keeps
|
|
17
|
+
// `./src/*` because the plugin runtime and the harness shims import deep paths.
|
|
18
|
+
// Removing that subpath would break them to make the surface look tidier.
|
|
19
|
+
|
|
20
|
+
export { run, renderText, renderJson } from './run.mjs';
|
|
21
|
+
export { checkRepo, checkTargets, checkTarget, frozenFindings } from './commands/sync.mjs';
|
|
22
|
+
export { diagnose, serialisedFindings } from './commands/doctor.mjs';
|
|
23
|
+
export { rulesCatalogue, rulesWhyFacts } from './commands/rules.mjs';
|
|
24
|
+
export { skillsBriefFacts, skillsFindFacts, skillsShowFacts, skillsWhyFacts } from './commands/skills.mjs';
|
|
25
|
+
export { assembleBrief } from './brief/assemble.mjs';
|
|
26
|
+
export { loadRegistry } from './graph/load.mjs';
|
|
27
|
+
export { discover } from './compare/discover.mjs';
|
|
28
|
+
export { buildRows, resolveConcurrency, summarise } from './compare/row.mjs';
|
|
29
|
+
export { planNext } from './plan/index.mjs';
|
|
30
|
+
export { buildPrBody } from './prbody.mjs';
|
|
31
|
+
export { federationAccess, readFederation } from './federation/config.mjs';
|
package/src/argv.mjs
CHANGED
|
@@ -26,6 +26,11 @@ import { EXIT } from './output/index.mjs';
|
|
|
26
26
|
// mean something, which it could not while three accepted flags had no description.
|
|
27
27
|
|
|
28
28
|
export const BOOL_FLAGS = new Set([
|
|
29
|
+
// bench ablate: compare across harness, model and experiment groups. The command
|
|
30
|
+
// read `flags.crossGroup` while the parser accepted no such flag, so the override
|
|
31
|
+
// existed only in the error message that told the reader to pass it (review 0003
|
|
32
|
+
// Q62, found by the spec-vs-parser drift test).
|
|
33
|
+
'cross-group',
|
|
29
34
|
'json',
|
|
30
35
|
'all',
|
|
31
36
|
'dry-run',
|
|
@@ -39,6 +44,14 @@ export const BOOL_FLAGS = new Set([
|
|
|
39
44
|
'pretty',
|
|
40
45
|
'accept-conflicts',
|
|
41
46
|
'backup',
|
|
47
|
+
// init: opt out of the agent files and the agent-hooks in one flag. The parser's
|
|
48
|
+
// `no-` convention spells the flag as a negation of the positive `agents`, so the
|
|
49
|
+
// positive name is what belongs here and `init` reads `flags.agents === false` -
|
|
50
|
+
// the user-facing flag stays `--no-agents`. They are the two halves of the same
|
|
51
|
+
// decision - which harnesses the adoption speaks to - and the standalone agents /
|
|
52
|
+
// agent-hooks install commands remain for a repo that wants them later. Off by
|
|
53
|
+
// default because the adoption writes them by default.
|
|
54
|
+
'agents',
|
|
42
55
|
'list',
|
|
43
56
|
// tokens: measure every repository under the root, not just the one. Plan 0003 R06
|
|
44
57
|
// names this flag `--workspace`, and that name has been a VALUE flag since plan
|
|
@@ -89,6 +102,10 @@ export const BOOL_FLAGS = new Set([
|
|
|
89
102
|
// bench.json marked unmeasured, and a lint that failed it on arrival would make
|
|
90
103
|
// every new pack red before its author wrote the rule (plan 0003 R45).
|
|
91
104
|
'require-measured',
|
|
105
|
+
// bench mine: restore the old "reflect exactly this history" semantics by
|
|
106
|
+
// deleting task files the current history does not reproduce. Off by default
|
|
107
|
+
// because mining is append-only (review 0003 Q48).
|
|
108
|
+
'replace',
|
|
92
109
|
// distill: propose the summary from the configured local chat model instead of
|
|
93
110
|
// the deterministic outline. Off by default: no model in the default path (P1),
|
|
94
111
|
// and a model that is not asked for should not be opened against (ADR-090).
|
|
@@ -166,6 +183,9 @@ export const VALUE_FLAGS = new Set([
|
|
|
166
183
|
// lease: how long the claim lasts, in seconds. A value, because "a lease is short"
|
|
167
184
|
// is not a number any reader can act on, and an expiry nobody chose is an expiry
|
|
168
185
|
// nobody can plan around (plan 0003 R54, ADR-101).
|
|
186
|
+
// lessons capture: how long a captured candidate stays eligible. A value, because a
|
|
187
|
+
// default would be a deadline nobody chose (review 0003 Q62, found by the drift test).
|
|
188
|
+
'expires',
|
|
169
189
|
'ttl',
|
|
170
190
|
// map: outline (paths), names (exported symbols) or signatures (the line).
|
|
171
191
|
// A boolean would not say which of the three the caller wanted.
|
|
@@ -184,6 +204,10 @@ export const VALUE_FLAGS = new Set([
|
|
|
184
204
|
// name only the author's branch knows, and the default (origin/main, then main)
|
|
185
205
|
// is a guess the report states out loud rather than a question it asks.
|
|
186
206
|
'base',
|
|
207
|
+
// slice start: the id for a prompt slice, overriding the slug derived from the
|
|
208
|
+
// prompt text. A value, because the derived slug is a guess a reader may want to
|
|
209
|
+
// pin, and a flag without a value would not say what to name the slice.
|
|
210
|
+
'id',
|
|
187
211
|
]);
|
|
188
212
|
|
|
189
213
|
/**
|
|
@@ -229,6 +253,11 @@ export function parse(argv) {
|
|
|
229
253
|
const flags = {};
|
|
230
254
|
const positional = [];
|
|
231
255
|
let passthrough = false;
|
|
256
|
+
// `--` is consumed as a separator, but its presence is a fact a command may need:
|
|
257
|
+
// `slice start -- "<text>"` reads the prompt as everything after the marker, and a
|
|
258
|
+
// positional that merely happens to sit there is a different command. Carried as a
|
|
259
|
+
// flag so it reaches the command the way every other parsed fact does.
|
|
260
|
+
let dashdash = false;
|
|
232
261
|
|
|
233
262
|
for (let i = 0; i < argv.length; i += 1) {
|
|
234
263
|
const arg = argv[i];
|
|
@@ -239,6 +268,7 @@ export function parse(argv) {
|
|
|
239
268
|
}
|
|
240
269
|
if (arg === '--') {
|
|
241
270
|
passthrough = true;
|
|
271
|
+
dashdash = true;
|
|
242
272
|
continue;
|
|
243
273
|
}
|
|
244
274
|
|
|
@@ -281,15 +311,30 @@ export function parse(argv) {
|
|
|
281
311
|
flags.reps = n;
|
|
282
312
|
} else {
|
|
283
313
|
const key = camel(raw);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
314
|
+
if (REPEATABLE_FLAGS.has(raw)) {
|
|
315
|
+
flags[key] = [...(Array.isArray(flags[key]) ? flags[key] : []), value];
|
|
316
|
+
} else if (flags[key] !== undefined) {
|
|
317
|
+
// `--root a --root b` kept `b`, so half of what the caller asked for vanished
|
|
318
|
+
// with no error and no way to tell which half. Only a flag declared repeatable
|
|
319
|
+
// accumulates (review 0003 Q62); a second value for any other is a usage error.
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
error: usage(
|
|
323
|
+
`--${raw} was given twice; it takes one value (repeatable: ${[...REPEATABLE_FLAGS]
|
|
324
|
+
.map((name) => `--${name}`)
|
|
325
|
+
.join(', ')})`,
|
|
326
|
+
),
|
|
327
|
+
};
|
|
328
|
+
} else {
|
|
329
|
+
flags[key] = value;
|
|
330
|
+
}
|
|
287
331
|
}
|
|
288
332
|
continue;
|
|
289
333
|
}
|
|
290
334
|
return { ok: false, error: usage(`unknown flag --${raw}`) };
|
|
291
335
|
}
|
|
292
336
|
|
|
337
|
+
if (dashdash) flags.dashdash = true;
|
|
293
338
|
return { ok: true, value: { flags, positional } };
|
|
294
339
|
}
|
|
295
340
|
|
package/src/bench/ablate.mjs
CHANGED
|
@@ -19,15 +19,14 @@ import { plan } from '../projection/project.mjs';
|
|
|
19
19
|
import { isEligible, loadRules, readDeclared, ruleValues, select } from '../projection/rules.mjs';
|
|
20
20
|
import { resolveSurfaces, rulePath, SURFACES } from '../projection/surfaces.mjs';
|
|
21
21
|
import { fingerprint } from '../resolve/fingerprint.mjs';
|
|
22
|
+
import { evaluateRunRecord, loadPrices } from '../measure/metrics.mjs';
|
|
23
|
+
import { loadRunRecords } from '../measure/records.mjs';
|
|
22
24
|
import {
|
|
23
25
|
DEFAULT_BOOTSTRAP_SAMPLES,
|
|
24
26
|
DEFAULT_BOOTSTRAP_SEED,
|
|
25
27
|
compareArms,
|
|
26
|
-
evaluateRunRecord,
|
|
27
28
|
holmAdjust,
|
|
28
|
-
|
|
29
|
-
loadRunRecords,
|
|
30
|
-
} from './report.mjs';
|
|
29
|
+
} from '../measure/stats.mjs';
|
|
31
30
|
|
|
32
31
|
export const DEFAULT_CANARY_SIZE = 10;
|
|
33
32
|
export const DEL_R_TOLERANCE = 0.01;
|