simt-dev 0.3.1__tar.gz → 0.4.0__tar.gz
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.
- {simt_dev-0.3.1 → simt_dev-0.4.0}/CHANGELOG.md +109 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/PKG-INFO +15 -5
- {simt_dev-0.3.1 → simt_dev-0.4.0}/README.md +14 -4
- {simt_dev-0.3.1 → simt_dev-0.4.0}/package.json +2 -2
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/core.md +1 -1
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/agenthooks/policy.mjs +10 -2
- simt_dev-0.4.0/src/agenthooks/prompt.mjs +81 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/agenthooks/render.mjs +138 -17
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/argv.mjs +16 -1
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/grade.mjs +3 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/run.mjs +85 -39
- simt_dev-0.4.0/src/bench/variants.mjs +456 -0
- simt_dev-0.4.0/src/brief/assemble.mjs +346 -0
- simt_dev-0.4.0/src/brief/kind.mjs +134 -0
- simt_dev-0.4.0/src/brief/packet.mjs +197 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/agenthook.mjs +84 -5
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/bench.mjs +40 -15
- simt_dev-0.4.0/src/commands/brief.mjs +78 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/compare.mjs +10 -2
- simt_dev-0.4.0/src/commands/decide.mjs +116 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/help.mjs +34 -3
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/index.mjs +17 -1
- simt_dev-0.4.0/src/commands/lessons.mjs +127 -0
- simt_dev-0.4.0/src/commands/local.mjs +97 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/map.mjs +42 -7
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/repoindex.mjs +4 -1
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/rules.mjs +17 -2
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/skills.mjs +3 -17
- simt_dev-0.4.0/src/commands/skillscompile.mjs +255 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/sync.mjs +15 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/tests.mjs +2 -49
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/why.mjs +18 -23
- simt_dev-0.4.0/src/compare/learn.mjs +680 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/decisions/index.mjs +116 -65
- simt_dev-0.4.0/src/federation/client.mjs +188 -0
- simt_dev-0.4.0/src/federation/config.mjs +134 -0
- simt_dev-0.4.0/src/federation/query.mjs +48 -0
- simt_dev-0.4.0/src/graph/compile.mjs +475 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/gate.mjs +1 -13
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/govern.mjs +23 -11
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/map.mjs +251 -17
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/persist.mjs +74 -7
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/store.mjs +222 -8
- simt_dev-0.4.0/src/kernel/variants.mjs +353 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/ledger/index.mjs +36 -1
- simt_dev-0.4.0/src/local/client.mjs +482 -0
- simt_dev-0.4.0/src/local/config.mjs +234 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/mcp/tools.mjs +138 -1
- simt_dev-0.4.0/src/memory/decide.mjs +462 -0
- simt_dev-0.4.0/src/memory/lessons.mjs +858 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/lock.mjs +39 -7
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/rules.mjs +32 -8
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/toolwords.mjs +6 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/.gitignore +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/LICENSE +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/bin/simt-dev.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-001-push-requires-approval.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-002-refactor-blast-radius.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-003-test-breadth.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-004-hook-owns-format.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-005-file-length.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-006-coverage-bar.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-007-section-skeleton.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-008-verdict-vocabulary.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-009-skill-layout.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-010-runner-invocation.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-011-windows-support.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-012-drop-windows-support.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-014-workspace-is-configuration.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-021-one-surface-per-repo.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-044-compatibility-is-not-evidence.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-046-one-rule-one-copy.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-050-a-byte-count-is-not-a-region-and-updated-is-not-a-change-of-rules.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-061-the-skill-registry-is-workspace-data.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-063-the-adoption-escape-hatch-is-opt-in.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-075-nothing-leaves-the-machine.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-083-a-budget-is-met-by-cutting-the-kernel-and-hard-is-the-floor.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-084-a-workspace-carries-its-repeated-rules-once.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-085-the-region-ends-with-the-names-it-kept-out.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-087-a-run-is-evidence-for-one-tree.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-088-a-marked-line-is-enforced-by-a-checker.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-089-the-local-index-is-a-cache-of-git-blobs.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-090-an-optional-adapter-is-off-until-named.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-091-the-map-ranks-files-a-task-mentions.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-092-tests-are-imports-conventions-and-coverage.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-093-why-names-what-governs-a-file.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-094-a-change-runs-the-tests-that-import-it.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-095-gate-output-keeps-the-failures.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/ADR-096-impact-names-who-a-change-reaches.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/docs/decisions/README.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/pyproject.toml +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/python/simt_dev/__init__.py +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/python/simt_dev/cli.py +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/mdc/intake-questions.mdc +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/mdc/preview-postflight.mdc +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/mdc/slice-commit.mdc +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/mdc/test-gate.mdc +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/android.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/cloudflare-workers.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/connectiq.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/container.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/dart.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/deno.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/dotnet.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/elixir.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/go.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/jvm.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/node.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/python.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/react-native.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/react-web.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/ruby.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/rust.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/swift.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/typescript.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/rules/stacks/uv.md +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/agenthooks/decide.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/ablate.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/adapters.json +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/mine.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/probes.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/bench/report.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/ci/dependabot.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/ci/workflow.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/ci.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/evidence.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/gate.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/graph.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/harness.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/hooks.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/impact.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/init.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/mcp.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/plan.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/shared.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/skillsinstall.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/tokens.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/commands/upgrade.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/compare/discover.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/compare/hooksstate.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/compare/row.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/graph/load.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/graph/spec.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/graph/speccheck.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/capability.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/dispatcher.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/gates.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/resolve.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/shim.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/hooks/where.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/identity.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/compact.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/extract.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/impact.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/index/tests.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/kernel/budget.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/kernel/card.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/kernel/checks.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/kernel/inferability.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/locate.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/mcp/resources.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/mcp/schema.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/mcp/server.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/measure/context.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/measure/loaderView.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/measure/session.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/monorepo.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/name.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/output/index.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/package-version.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/packs.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/plan/index.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/plan/template.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/baselines.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/conflicts.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/disclosure.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/loaders.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/manifest.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/markers.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/mdc.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/project.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/shared.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/surfaces.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/projection/targets.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/resolve/eval.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/resolve/fingerprint.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/resolve/near.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/resolve/resolve.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/resolve/source.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/toolchain.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/tsconfig.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/upgrade/plan.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/src/workspace.mjs +0 -0
- {simt_dev-0.3.1 → simt_dev-0.4.0}/templates/plan/tracker.json +0 -0
|
@@ -6,6 +6,114 @@ All notable changes to this package are recorded here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] — 2026-10-20
|
|
10
|
+
|
|
11
|
+
### Hybrid retrieval fuses BM25, the code graph, and the resolver
|
|
12
|
+
|
|
13
|
+
`simt index` indexes full-text tokens across `code`, `docs`, `skills`, `decisions` and
|
|
14
|
+
`lessons` into the SQLite FTS5 `fts` table (with an Okapi BM25 fallback when the index is
|
|
15
|
+
JSON-lines). `simt map` and `simt brief` fuse FTS5 `bm25()`, PageRank graph proximity and
|
|
16
|
+
resolver signals with reciprocal-rank fusion (`RRF_K = 40`), raising gold-file recall@10 on
|
|
17
|
+
the 20 mined tasks here from 54.3% to 61.3% (above the 60% bar in ADR-091). With `--embed`
|
|
18
|
+
and a configured `local.embed` endpoint, `simt index` stores `Float32Array` vector blobs in
|
|
19
|
+
the `embeddings` table and `simt map --embed` adds brute-force cosine similarity; embeddings
|
|
20
|
+
stay off by default unless they raise recall@10 by at least 10 percentage points.
|
|
21
|
+
|
|
22
|
+
### A local model adapter that opens zero sockets until named
|
|
23
|
+
|
|
24
|
+
`simt local doctor` probes the OpenAI-compatible `local.chat` and `local.embed` endpoints
|
|
25
|
+
declared in `simt.json` (`/v1/models`, `/v1/chat/completions`, `/v1/embeddings` for Ollama,
|
|
26
|
+
`llama-server`, LM Studio and MLX servers) and reports reachability, model presence and
|
|
27
|
+
latency. Non-loopback URLs are refused unless `--allow-remote` is given (ADR-075, ADR-090),
|
|
28
|
+
and when `local` is unset nothing opens a socket.
|
|
29
|
+
|
|
30
|
+
### Estate learning and ADR drafts across a workspace
|
|
31
|
+
|
|
32
|
+
`simt compare --learn` aggregates benchmark uplift (`ΔR`, `ΔC`) per rule section across
|
|
33
|
+
adopted sibling repositories, promotes lessons repeated across at least two repositories as
|
|
34
|
+
shared candidates, and flags gates that fail more than 20% of the time as `NOISY-GATE`.
|
|
35
|
+
`simt compare --draft-adr <pattern>` writes a proposed ADR draft under `docs/decisions/`
|
|
36
|
+
carrying `Origin: simt compare --draft-adr` and the workspace evidence behind it.
|
|
37
|
+
|
|
38
|
+
### Rule text variants have to win on the bench
|
|
39
|
+
|
|
40
|
+
A rule may declare candidate wordings under `simt-variants:` in its frontmatter (or supply
|
|
41
|
+
them with `--variants-file`). `simt bench variants <rule-id>` evaluates each candidate on
|
|
42
|
+
paired benchmark runs with McNemar on resolved rate and Wilcoxon signed-rank on cost,
|
|
43
|
+
promotes a wording only when `ΔR > 0` (or `ΔR == 0` and `ΔC < 0`) at `p < 0.05`, and records
|
|
44
|
+
the winning variant and its `p`-value in `simt-dev.lock.json`.
|
|
45
|
+
|
|
46
|
+
### Decision records carry structured supersession frontmatter
|
|
47
|
+
|
|
48
|
+
`simt decide "<title>"` scaffolds the next numbered ADR in `docs/decisions/` with structured
|
|
49
|
+
frontmatter (`Status`, `Date`, `Supersedes`, `Superseded-by`, `Affects`) and updates the
|
|
50
|
+
records it supersedes when `--supersedes` and `--yes` are given. `simt rules lint` reports
|
|
51
|
+
`PARTIAL-SUPERSEDE` when prose claims to supersede part of an earlier record without
|
|
52
|
+
matching frontmatter links.
|
|
53
|
+
|
|
54
|
+
### Lessons are captured from failure-to-fix pairs, recalled on match, and expire
|
|
55
|
+
|
|
56
|
+
When a failing gate or test run in `.simt-dev/ledger.jsonl` is followed by a passing run on
|
|
57
|
+
the same package, `simt lessons` records a deterministic candidate in
|
|
58
|
+
`.simt-dev/lessons-candidates.jsonl`. `simt lessons promote <id> --yes` writes a reviewed
|
|
59
|
+
lesson to `docs/lessons/<id>.md` with `Paths`, `Check`, `Expires` and `ADR` fields, and
|
|
60
|
+
`simt check` reports `LESSON-EXPIRED` once the date passes or the linked ADR is superseded.
|
|
61
|
+
Matching lessons (at most two) are recalled in `simt brief` and by `simt agent-hook
|
|
62
|
+
after-tool` when a tool call fails on the same package or error signature.
|
|
63
|
+
|
|
64
|
+
### Skills compile to the Agent Skills standard and report their triggers
|
|
65
|
+
|
|
66
|
+
`simt skills compile` validates and compiles skill sources against the Agent Skills standard
|
|
67
|
+
(frontmatter `name` and `description`, description under 1024 characters, `SKILL.md` body at
|
|
68
|
+
most 500 lines, and on-demand `references/` and `scripts/` links). `simt skills triggers
|
|
69
|
+
"<prompt>"` previews which skills match a task and the score breakdown behind each match.
|
|
70
|
+
|
|
71
|
+
### A task brief fits in the bytes a prompt can carry
|
|
72
|
+
|
|
73
|
+
`simt brief "<task>"` assembles what a task needs into one packet: the kind of the task
|
|
74
|
+
and the commands that kind needs, the files the map ranks highest, the tests the index
|
|
75
|
+
knows for them, the decisions that govern them, the skills the resolver selects, the
|
|
76
|
+
lessons that name those paths, and the cautions — dirty files to leave alone, generated
|
|
77
|
+
paths, files no test covers. The packet stays inside `--budget` bytes (2048 by default,
|
|
78
|
+
`0` for all of it), ends with the number of items it had to cut, and prints the same bytes
|
|
79
|
+
on the same tree; `--explain` prints every item with the reason it was kept or dropped.
|
|
80
|
+
The accept figure it was designed against — 20% fewer turns, 15% cheaper than the kernel
|
|
81
|
+
arm at no loss of resolved rate — is unmeasured here, because it needs a bench run and an
|
|
82
|
+
installed agent CLI and neither is on this machine. `simt skills brief` keeps its own
|
|
83
|
+
behaviour for now: retiring it changes two accepted decisions and an MCP output schema,
|
|
84
|
+
and this slice carries no decision to do it.
|
|
85
|
+
|
|
86
|
+
### The packet arrives without being asked for
|
|
87
|
+
|
|
88
|
+
A repository that declares a `brief` key in its manifest gets the packet injected as context
|
|
89
|
+
before every prompt: `simt agent-hooks install` writes a `UserPromptSubmit` hook into Claude
|
|
90
|
+
Code's and Codex's own config, and `simt agent-hook prompt-submit --harness <id>` answers in
|
|
91
|
+
that harness's dialect with `hookSpecificOutput.additionalContext`. It never decides and never
|
|
92
|
+
exits 2 — no prompt text, no opt-in, a cold index or an assembly error all print why on stderr
|
|
93
|
+
and exit 0. The injection is capped at 2048 bytes; `brief.bytes` may lower that and may not
|
|
94
|
+
raise it. Cursor and Gemini CLI are not in the table: Cursor's prompt event can only block a
|
|
95
|
+
prompt, and Gemini CLI's replacement is not in the reader table. The pre-tool path pays nothing
|
|
96
|
+
for this — the repository is reached through a dynamic import only a prompt event opens. MCP
|
|
97
|
+
gains a `brief` tool so a client can pull the same packet, assembled by the same code as the
|
|
98
|
+
command.
|
|
99
|
+
|
|
100
|
+
### A brief can cite a code graph it did not build
|
|
101
|
+
|
|
102
|
+
A repository may declare a `federation` block in `simt.json` naming external code-graph
|
|
103
|
+
servers, each with the command that starts it and, optionally, the MCP tool to ask, the
|
|
104
|
+
argument the question goes in, the languages it covers and the timeout to wait. `simt brief`
|
|
105
|
+
and the MCP `brief` tool then ask each declared server one structural question for the task
|
|
106
|
+
and add the answer to a `FEDERATION` section that names its source; a `covers <languages>`
|
|
107
|
+
line records what a server claims without yet skipping the index's own extraction of those
|
|
108
|
+
languages. The list is the repository's own committed declaration, the same trust model as
|
|
109
|
+
`gates.commands`, and nothing is started that the manifest did not name. A malformed entry, a
|
|
110
|
+
spawn failure, a protocol error or a timeout all become a caution and the brief still exits
|
|
111
|
+
`0`; a server that never answers is bounded by its own per-server timeout. MCP also gains a
|
|
112
|
+
`federation_query` tool, which asks a named server (or every declared one) one structural
|
|
113
|
+
question on demand and returns the answer with its source; like the brief's fan-out it goes
|
|
114
|
+
only to the tool and argument the manifest declared. The packet delivered at prompt submit
|
|
115
|
+
does not federate — starting someone else's process is not a wait a 300 ms submit may take.
|
|
116
|
+
|
|
9
117
|
## [0.3.1] — 2026-10-18
|
|
10
118
|
|
|
11
119
|
### Who a change reaches
|
|
@@ -625,6 +733,7 @@ never ran `simt hooks install` again will find nothing until the dependency is r
|
|
|
625
733
|
and `simt hooks install --yes` is re-run. That is a real upgrade step and it is called out
|
|
626
734
|
here rather than discovered.
|
|
627
735
|
|
|
736
|
+
[0.4.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.4.0
|
|
628
737
|
[0.3.1]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.3.1
|
|
629
738
|
[0.3.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.3.0
|
|
630
739
|
[0.2.2]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.2.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simt-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Shared agent rules, skills and git hooks for a repository: one command adopts a repo, and every gate it declares is probed before it is written.
|
|
5
5
|
Project-URL: Homepage, https://github.com/QuazarMedia/simt-dev
|
|
6
6
|
Project-URL: Repository, https://github.com/QuazarMedia/simt-dev
|
|
@@ -30,6 +30,8 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
|
|
31
31
|
# simt-dev
|
|
32
32
|
|
|
33
|
+

|
|
34
|
+
|
|
33
35
|
`simt` adopts a repository for the agents that work in it.
|
|
34
36
|
|
|
35
37
|
It reads what the repository actually runs, projects the rules that are not already
|
|
@@ -111,18 +113,18 @@ this order of purpose:
|
|
|
111
113
|
|
|
112
114
|
## Install
|
|
113
115
|
|
|
114
|
-
Requires **Node >= 22.20.0**. The current published release is **0.
|
|
116
|
+
Requires **Node >= 22.20.0**. The current published release is **0.4.0**. Pin it.
|
|
115
117
|
The tool writes a content digest into the files it generates, and an unpinned upgrade
|
|
116
118
|
changes them under you.
|
|
117
119
|
|
|
118
120
|
```sh
|
|
119
|
-
npm i -D simt-dev@0.
|
|
121
|
+
npm i -D simt-dev@0.4.0
|
|
120
122
|
|
|
121
123
|
# or globally
|
|
122
|
-
npm i -g simt-dev@0.
|
|
124
|
+
npm i -g simt-dev@0.4.0
|
|
123
125
|
|
|
124
126
|
# or once, with no install
|
|
125
|
-
npx -y simt-dev@0.
|
|
127
|
+
npx -y simt-dev@0.4.0 check
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
### From PyPI
|
|
@@ -163,6 +165,14 @@ Node installed.
|
|
|
163
165
|
|
|
164
166
|
Text outside the markers is left alone. A regeneration does not revert it.
|
|
165
167
|
|
|
168
|
+
`simt.json` also carries a `federation` block when the repository names external
|
|
169
|
+
code-graph servers, each with the command that starts it. A brief may then ask one of
|
|
170
|
+
them one structural question for a task and cite the answer's source, and the MCP
|
|
171
|
+
`federation_query` tool asks one on demand, naming the server it asked. The question goes
|
|
172
|
+
to the tool and argument the manifest declared, so no foreign tool is reachable that the
|
|
173
|
+
repository did not name. Nothing is started unless the manifest declared it, and a server
|
|
174
|
+
that does not answer costs the packet a sentence, never the packet.
|
|
175
|
+
|
|
166
176
|
These are caches, gitignored, and rebuildable. They are not repository content:
|
|
167
177
|
|
|
168
178
|
| Path | What |
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# simt-dev
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
`simt` adopts a repository for the agents that work in it.
|
|
4
6
|
|
|
5
7
|
It reads what the repository actually runs, projects the rules that are not already
|
|
@@ -81,18 +83,18 @@ this order of purpose:
|
|
|
81
83
|
|
|
82
84
|
## Install
|
|
83
85
|
|
|
84
|
-
Requires **Node >= 22.20.0**. The current published release is **0.
|
|
86
|
+
Requires **Node >= 22.20.0**. The current published release is **0.4.0**. Pin it.
|
|
85
87
|
The tool writes a content digest into the files it generates, and an unpinned upgrade
|
|
86
88
|
changes them under you.
|
|
87
89
|
|
|
88
90
|
```sh
|
|
89
|
-
npm i -D simt-dev@0.
|
|
91
|
+
npm i -D simt-dev@0.4.0
|
|
90
92
|
|
|
91
93
|
# or globally
|
|
92
|
-
npm i -g simt-dev@0.
|
|
94
|
+
npm i -g simt-dev@0.4.0
|
|
93
95
|
|
|
94
96
|
# or once, with no install
|
|
95
|
-
npx -y simt-dev@0.
|
|
97
|
+
npx -y simt-dev@0.4.0 check
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
### From PyPI
|
|
@@ -133,6 +135,14 @@ Node installed.
|
|
|
133
135
|
|
|
134
136
|
Text outside the markers is left alone. A regeneration does not revert it.
|
|
135
137
|
|
|
138
|
+
`simt.json` also carries a `federation` block when the repository names external
|
|
139
|
+
code-graph servers, each with the command that starts it. A brief may then ask one of
|
|
140
|
+
them one structural question for a task and cite the answer's source, and the MCP
|
|
141
|
+
`federation_query` tool asks one on demand, naming the server it asked. The question goes
|
|
142
|
+
to the tool and argument the manifest declared, so no foreign tool is reachable that the
|
|
143
|
+
repository did not name. Nothing is started unless the manifest declared it, and a server
|
|
144
|
+
that does not answer costs the packet a sentence, never the packet.
|
|
145
|
+
|
|
136
146
|
These are caches, gitignored, and rebuildable. They are not repository content:
|
|
137
147
|
|
|
138
148
|
| Path | What |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simt-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Shared agent rules, skills and git hooks for a repository: one command adopts a repo, and every gate it declares is probed before it is written.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Said Elimam",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"skills"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
-
"test": "node --test test/*.test.mjs",
|
|
40
|
+
"test": "node --test --test-concurrency=4 test/*.test.mjs",
|
|
41
41
|
"lint": "node bin/simt-dev.mjs doctor"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -7,7 +7,7 @@ simt-priority: hard
|
|
|
7
7
|
|
|
8
8
|
# Shared rules
|
|
9
9
|
|
|
10
|
-
Where a rule cites a decision, run `simt
|
|
10
|
+
Where a rule cites a decision, run `simt rules why ADR-NNN` to read it,
|
|
11
11
|
including whether it is still in force.
|
|
12
12
|
|
|
13
13
|
These rules are stack-agnostic. Whatever is specific to your stack, your product or your
|
|
@@ -21,8 +21,16 @@
|
|
|
21
21
|
|
|
22
22
|
import { CLI } from '../name.mjs';
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The events an agent-time hook can answer, normalised across harnesses.
|
|
26
|
+
*
|
|
27
|
+
* `pre-tool-use` is a decision: a payload in, a deny or nothing out, and exit 2 means
|
|
28
|
+
* block. `prompt-submit` is a delivery: the same payload shape in, context out, and it
|
|
29
|
+
* never denies anything — a hook that could stop a person's prompt would be a hook whose
|
|
30
|
+
* own bug is a person who cannot type (R29). `post-tool-use` recalls a lesson as one
|
|
31
|
+
* paragraph when a tool call fails with a signature matching `docs/lessons/*.md` (R33).
|
|
32
|
+
*/
|
|
33
|
+
export const EVENTS = ['pre-tool-use', 'prompt-submit', 'post-tool-use'];
|
|
26
34
|
|
|
27
35
|
/** The environment variable that lifts named policies for one session. */
|
|
28
36
|
export const ALLOW_ENV = 'SIMT_ALLOW';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The prompt-submit surface: the packet delivered where the agent will read it.
|
|
2
|
+
//
|
|
3
|
+
// This module must stay off the tool-call path, and the code's shape is what keeps it
|
|
4
|
+
// there: `src/commands/agenthook.mjs` reaches it through a dynamic `import()` that only a
|
|
5
|
+
// prompt event takes. The pre-tool hook is a latency promise — it answers before every
|
|
6
|
+
// shell command an agent runs — so it imports the policy table, the decision and the
|
|
7
|
+
// renderer and nothing that walks the repository, and `test/agenthooks.test.mjs` asserts
|
|
8
|
+
// exactly that set. A delivery, by contrast, is the one thing here that must read the
|
|
9
|
+
// repository, so it lives behind a door the pre-tool path never opens.
|
|
10
|
+
//
|
|
11
|
+
// Three rules shape what gets delivered:
|
|
12
|
+
//
|
|
13
|
+
// * The repository opts in. The opt-in is a `brief` key in the repository's manifest,
|
|
14
|
+
// not the presence of a hook in a harness config: a repository that declared nothing
|
|
15
|
+
// is not spending its owner's context on a packet they did not ask for, and the
|
|
16
|
+
// manifest is the one file a person reviews before they commit the change.
|
|
17
|
+
// * The packet is capped, at PROMPT_CAP_BYTES. A packet is delivered into a prompt that
|
|
18
|
+
// already exists, and the ceiling is the measured bar of this slice rather than a
|
|
19
|
+
// value a repository is free to raise.
|
|
20
|
+
// * A delivery never blocks and never fails loudly. Everything that could go wrong —
|
|
21
|
+
// no index, no prompt in the payload, a registry that will not load — ends in no
|
|
22
|
+
// injection and a line on stderr. A hook that stopped a person's prompt because this
|
|
23
|
+
// tool could not answer would be worse than a hook that said nothing.
|
|
24
|
+
|
|
25
|
+
import { readManifest } from '../projection/manifest.mjs';
|
|
26
|
+
import { DEFAULT_BRIEF_BYTES, PROMPT_CAP_BYTES } from '../brief/packet.mjs';
|
|
27
|
+
import { assembleBrief } from '../brief/assemble.mjs';
|
|
28
|
+
import { deliveryJson, factsFrom, promptText } from './render.mjs';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether this repository asked for a delivered packet, and the ceiling it asked for.
|
|
32
|
+
*
|
|
33
|
+
* Presence is the opt-in and the value is a preference, so `"brief": {}` means "yes, at
|
|
34
|
+
* whatever you measured" and `"brief": { "bytes": 900 }` means "yes, and keep it small".
|
|
35
|
+
* A declared zero is not honoured as a ceiling: zero is this tool's word for "no ceiling"
|
|
36
|
+
* on the command line, and a delivered packet that meant "the whole repository" would be
|
|
37
|
+
* the one thing the cap exists to prevent. It is read from the manifest rather than from
|
|
38
|
+
* the harness config because the manifest is the file the repository reviews.
|
|
39
|
+
*/
|
|
40
|
+
export async function promptOptIn(root) {
|
|
41
|
+
const { manifest, exists, error } = await readManifest(root);
|
|
42
|
+
if (!exists || error) {
|
|
43
|
+
return { optedIn: false, bytes: DEFAULT_BRIEF_BYTES, why: `${exists ? error : 'no manifest'}` };
|
|
44
|
+
}
|
|
45
|
+
if (manifest?.brief === undefined || manifest.brief === null) {
|
|
46
|
+
return { optedIn: false, bytes: DEFAULT_BRIEF_BYTES, why: 'no `brief` key in the manifest' };
|
|
47
|
+
}
|
|
48
|
+
const declared = manifest.brief?.bytes;
|
|
49
|
+
const asked = Number.isFinite(declared) && declared > 0 ? declared : PROMPT_CAP_BYTES;
|
|
50
|
+
return { optedIn: true, bytes: Math.min(asked, PROMPT_CAP_BYTES) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The context one prompt-submit call should deliver, or the reason it delivers nothing.
|
|
55
|
+
*
|
|
56
|
+
* `build: false` is the latency promise in one argument: a delivery reports a cold index
|
|
57
|
+
* rather than building one, so the first prompt in a fresh clone gets nothing and the
|
|
58
|
+
* second gets the packet. The alternative — a person watching their first prompt sit for
|
|
59
|
+
* a minute while a repository is walked — is how the hook gets uninstalled, and the
|
|
60
|
+
* packet is not worth a prompt that cannot be typed.
|
|
61
|
+
*/
|
|
62
|
+
export async function deliverPrompt(row, payload) {
|
|
63
|
+
const task = promptText(row, payload);
|
|
64
|
+
if (task === null) return { skipped: 'the payload carries no prompt this harness documents' };
|
|
65
|
+
|
|
66
|
+
// The root is the same dialect fact for both events, so it is read the same way.
|
|
67
|
+
const root = factsFrom(row, payload).root;
|
|
68
|
+
const optIn = await promptOptIn(root);
|
|
69
|
+
if (!optIn.optedIn) return { skipped: optIn.why };
|
|
70
|
+
|
|
71
|
+
// Federation is off on this path and on no other: a delivery has 300 ms to answer, and
|
|
72
|
+
// starting a declared server is a wait a prompt submit must not take. The task is the
|
|
73
|
+
// question a federation would ask, and a person is not made to wait for it here.
|
|
74
|
+
const result = await assembleBrief({ root, task, budget: optIn.bytes, build: false, federate: false });
|
|
75
|
+
if (result.error) return { skipped: 'the packet could not be assembled' };
|
|
76
|
+
if (result.cold) return { skipped: 'the index is empty, and a delivered packet never builds one' };
|
|
77
|
+
|
|
78
|
+
const json = deliveryJson(row, result.packed.text);
|
|
79
|
+
if (json === null) return { skipped: `${row.id} has no prompt event that can inject` };
|
|
80
|
+
return { json, root, bytes: result.packed.after };
|
|
81
|
+
}
|
|
@@ -11,6 +11,26 @@
|
|
|
11
11
|
// cursor .cursor/hooks.json preToolUse {"version":1,"hooks":{E:[h]}}
|
|
12
12
|
// antigravity .agents/hooks.json PreToolUse {"<name>":{E:[{matcher,hooks:[h]}]}}
|
|
13
13
|
//
|
|
14
|
+
// The prompt-submit event (R29) is a second table, and it is shorter because the vendors
|
|
15
|
+
// are: two of the four document a prompt event that can put text into the model's context,
|
|
16
|
+
// and a delivery is the only thing this tool does there. Neither takes a matcher — both
|
|
17
|
+
// document that field for tool events, and a matcher a harness ignores is a matcher the
|
|
18
|
+
// next person believes is filtering something.
|
|
19
|
+
//
|
|
20
|
+
// claude .claude/settings.json UserPromptSubmit hookSpecificOutput.additionalContext
|
|
21
|
+
// codex .codex/hooks.json UserPromptSubmit hookSpecificOutput.additionalContext
|
|
22
|
+
// cursor .cursor/hooks.json beforeSubmitPrompt CANNOT INJECT: block-only
|
|
23
|
+
// antigravity .agents/hooks.json (no prompt event documented)
|
|
24
|
+
//
|
|
25
|
+
// Cursor is the one that would be a mistake to write anyway. Its `beforeSubmitPrompt` hook
|
|
26
|
+
// can only continue or block, so the entry would carry no packet and a broken one would
|
|
27
|
+
// stop a person from typing — and its only injection point, `sessionStart.additional_context`,
|
|
28
|
+
// fires once when the session is created, which is a different lifetime from the brief a
|
|
29
|
+
// task needs. Gemini CLI documents `BeforeAgent`, which does inject, but the product was
|
|
30
|
+
// replaced by Antigravity CLI on 2026-06-18 and its config is not in the reader table this
|
|
31
|
+
// module takes its paths from; a harness this tool did not verify is a harness it does not
|
|
32
|
+
// write to.
|
|
33
|
+
//
|
|
14
34
|
// The paths are not written here. They come from the reader table (R14) because that is
|
|
15
35
|
// where each one was verified against its vendor page, and a second copy of a verified
|
|
16
36
|
// fact is a copy that drifts the first time a vendor moves the file.
|
|
@@ -36,8 +56,8 @@ const TIMEOUT_SECONDS = 10;
|
|
|
36
56
|
* harness's dialect when a payload arrived in an unexpected shape, and the harness would
|
|
37
57
|
* read a decision it does not understand as no decision at all.
|
|
38
58
|
*/
|
|
39
|
-
export function hookCommand(id) {
|
|
40
|
-
return `${CLI} agent-hook ${
|
|
59
|
+
export function hookCommand(id, event = EVENTS[0]) {
|
|
60
|
+
return `${CLI} agent-hook ${event} --harness ${id}`;
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
/** The path a config file lives at, from the reader table rather than a second list. */
|
|
@@ -73,6 +93,20 @@ const ADAPTERS = {
|
|
|
73
93
|
deny: (message) => ({
|
|
74
94
|
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: message },
|
|
75
95
|
}),
|
|
96
|
+
// The delivery door. `additionalContext` is documented for this event and is
|
|
97
|
+
// wrapped into the conversation at the point the hook fired, which is why the
|
|
98
|
+
// packet goes here rather than into a system prompt: the cached prefix survives.
|
|
99
|
+
prompt: {
|
|
100
|
+
event: 'UserPromptSubmit',
|
|
101
|
+
verified: 'code.claude.com/docs/en/hooks, read 2026-10-19',
|
|
102
|
+
context: (text) => ({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text } }),
|
|
103
|
+
},
|
|
104
|
+
post: {
|
|
105
|
+
event: 'PostToolUse',
|
|
106
|
+
matcher: 'Bash',
|
|
107
|
+
verified: 'code.claude.com/docs/en/hooks, read 2026-10-19',
|
|
108
|
+
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
109
|
+
},
|
|
76
110
|
},
|
|
77
111
|
codex: {
|
|
78
112
|
id: 'codex',
|
|
@@ -96,6 +130,17 @@ const ADAPTERS = {
|
|
|
96
130
|
deny: (message) => ({
|
|
97
131
|
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: message },
|
|
98
132
|
}),
|
|
133
|
+
prompt: {
|
|
134
|
+
event: 'UserPromptSubmit',
|
|
135
|
+
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
136
|
+
context: (text) => ({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text } }),
|
|
137
|
+
},
|
|
138
|
+
post: {
|
|
139
|
+
event: 'PostToolUse',
|
|
140
|
+
matcher: 'Bash',
|
|
141
|
+
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
142
|
+
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
143
|
+
},
|
|
99
144
|
},
|
|
100
145
|
cursor: {
|
|
101
146
|
id: 'cursor',
|
|
@@ -111,6 +156,11 @@ const ADAPTERS = {
|
|
|
111
156
|
: [],
|
|
112
157
|
}),
|
|
113
158
|
deny: (message) => ({ permission: 'deny', user_message: message, agent_message: message }),
|
|
159
|
+
// beforeSubmitPrompt is documented as continue/block and nothing else, so there is no
|
|
160
|
+
// key to deliver a packet in. Writing the entry anyway would put a command that reads
|
|
161
|
+
// a repository on the path of every prompt a person types, to say nothing.
|
|
162
|
+
prompt: null,
|
|
163
|
+
post: null,
|
|
114
164
|
},
|
|
115
165
|
antigravity: {
|
|
116
166
|
id: 'antigravity',
|
|
@@ -133,6 +183,10 @@ const ADAPTERS = {
|
|
|
133
183
|
};
|
|
134
184
|
},
|
|
135
185
|
deny: (message) => ({ decision: 'deny', reason: message }),
|
|
186
|
+
// No prompt event is documented for this harness's hooks file, and a guessed event
|
|
187
|
+
// name is a hook that never fires while its config says it does.
|
|
188
|
+
prompt: null,
|
|
189
|
+
post: null,
|
|
136
190
|
},
|
|
137
191
|
};
|
|
138
192
|
|
|
@@ -160,6 +214,37 @@ export function decisionJson(row, decision) {
|
|
|
160
214
|
return row.deny(decision.messages.join('\n'));
|
|
161
215
|
}
|
|
162
216
|
|
|
217
|
+
/**
|
|
218
|
+
* The document to print for a delivery, or null when the harness takes none.
|
|
219
|
+
*
|
|
220
|
+
* A delivery is never a decision: nothing here can deny, and the caller exits 0 whatever
|
|
221
|
+
* this returns. That is the whole difference between the two events, and it is why the
|
|
222
|
+
* prompt event has no `denyExit` — there is no exit code that means "here is some context".
|
|
223
|
+
*/
|
|
224
|
+
export function deliveryJson(row, text) {
|
|
225
|
+
return row.prompt ? row.prompt.context(text) : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The document to print for a post-tool lesson recall delivery, or null when the harness takes none. */
|
|
229
|
+
export function postDeliveryJson(row, text) {
|
|
230
|
+
return row.post ? row.post.context(text) : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The submitted text a prompt payload carries, or null when it carries none we recognise.
|
|
235
|
+
*
|
|
236
|
+
* Marked inferred rather than documented: both harnesses that take this event place the
|
|
237
|
+
* prompt at the top level, but only in the example on each page, not as a stated contract.
|
|
238
|
+
* An unrecognised payload therefore delivers nothing and says so on stderr. The failure
|
|
239
|
+
* this must not have is the opposite one — a packet delivered as though it answered a task
|
|
240
|
+
* it never saw, which reads as a confident answer about the wrong question.
|
|
241
|
+
*/
|
|
242
|
+
export function promptText(row, payload) {
|
|
243
|
+
if (!row.prompt) return null;
|
|
244
|
+
const text = payload?.prompt ?? payload?.user_prompt ?? payload?.message;
|
|
245
|
+
return typeof text === 'string' && text.trim().length > 0 ? text : null;
|
|
246
|
+
}
|
|
247
|
+
|
|
163
248
|
// ---------------------------------------------------------------------------
|
|
164
249
|
// The config file: reading ours out of somebody else's JSON, and putting it back.
|
|
165
250
|
// ---------------------------------------------------------------------------
|
|
@@ -180,9 +265,36 @@ function container(row, config, { create = false } = {}) {
|
|
|
180
265
|
return config.hooks ?? null;
|
|
181
266
|
}
|
|
182
267
|
|
|
268
|
+
/** The vendor's name for one of our events on this row, or null when it has none. */
|
|
269
|
+
function vendorEvent(row, event) {
|
|
270
|
+
if (event === EVENTS[0]) return row.event;
|
|
271
|
+
if (event === 'prompt-submit') return row.prompt?.event ?? null;
|
|
272
|
+
if (event === 'post-tool-use') return row.post?.event ?? null;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The events a row answers, and the name each one goes under in its config file. */
|
|
277
|
+
function eventsOf(row, { prompt = false, post = false } = {}) {
|
|
278
|
+
const pairs = [[EVENTS[0], row.event]];
|
|
279
|
+
if (prompt && row.prompt) pairs.push(['prompt-submit', row.prompt.event]);
|
|
280
|
+
if (post && row.post) pairs.push(['post-tool-use', row.post.event]);
|
|
281
|
+
return pairs;
|
|
282
|
+
}
|
|
283
|
+
|
|
183
284
|
/** The group this tool owns for one event, in that harness's shape. */
|
|
184
|
-
function group(row) {
|
|
185
|
-
const command = hookCommand(row.id);
|
|
285
|
+
function group(row, event = EVENTS[0]) {
|
|
286
|
+
const command = hookCommand(row.id, event);
|
|
287
|
+
if (event === 'prompt-submit') {
|
|
288
|
+
// No matcher: both vendors document that field for tool events, and a matcher a
|
|
289
|
+
// harness ignores is a matcher the next reader believes is filtering something.
|
|
290
|
+
return { hooks: [{ type: 'command', command, timeout: TIMEOUT_SECONDS }] };
|
|
291
|
+
}
|
|
292
|
+
if (event === 'post-tool-use') {
|
|
293
|
+
const matcher = row.post?.matcher;
|
|
294
|
+
return matcher
|
|
295
|
+
? { matcher, hooks: [{ type: 'command', command, timeout: TIMEOUT_SECONDS }] }
|
|
296
|
+
: { hooks: [{ type: 'command', command, timeout: TIMEOUT_SECONDS }] };
|
|
297
|
+
}
|
|
186
298
|
if (row.id === 'cursor') {
|
|
187
299
|
// failClosed is set because a hooks file this tool wrote should not silently
|
|
188
300
|
// degrade to fail-open on its own crash: the vendor's default turns a broken hook
|
|
@@ -197,35 +309,44 @@ function ours(entry) {
|
|
|
197
309
|
return JSON.stringify(entry).includes(`${CLI} agent-hook`);
|
|
198
310
|
}
|
|
199
311
|
|
|
200
|
-
function entries(row, config) {
|
|
201
|
-
const
|
|
202
|
-
|
|
312
|
+
function entries(row, config, event = EVENTS[0]) {
|
|
313
|
+
const name = vendorEvent(row, event);
|
|
314
|
+
if (name === null) return [];
|
|
315
|
+
const list = container(row, config)?.[name];
|
|
203
316
|
return Array.isArray(list) ? list : [];
|
|
204
317
|
}
|
|
205
318
|
|
|
206
|
-
/** Our entries in this document, for `status`; the document is not modified. */
|
|
207
|
-
export function installedIn(row, config) {
|
|
208
|
-
return entries(row, config).filter(ours);
|
|
319
|
+
/** Our entries in this document for one event, for `status`; the document is not modified. */
|
|
320
|
+
export function installedIn(row, config, event = EVENTS[0]) {
|
|
321
|
+
return entries(row, config, event).filter(ours);
|
|
209
322
|
}
|
|
210
323
|
|
|
211
324
|
/**
|
|
212
|
-
* The document with this tool's
|
|
325
|
+
* The document with this tool's hooks added, everything else left alone.
|
|
213
326
|
*
|
|
214
327
|
* Written as JSON with two spaces and a trailing newline because that is what a file in
|
|
215
328
|
* a repository reads like; a config that is rewritten with different indentation on
|
|
216
329
|
* every run is a diff nobody can review. Keys this tool does not own are preserved
|
|
217
330
|
* exactly, including their order, because the file belongs to the repository.
|
|
331
|
+
*
|
|
332
|
+
* One file can carry both events — Claude and Codex each hold a tool hook and a prompt
|
|
333
|
+
* hook in the same document — so this writes one group per event the row answers rather
|
|
334
|
+
* than one group per file. `prompt` is the caller's opt-in: a repository that has not
|
|
335
|
+
* asked for a delivered packet gets the tool hook and nothing extra, and the file it never
|
|
336
|
+
* asked to change is a file this tool does not touch.
|
|
218
337
|
*/
|
|
219
|
-
function withHook(row, config) {
|
|
338
|
+
function withHook(row, config, { prompt = false, post = false } = {}) {
|
|
220
339
|
const box = container(row, config, { create: true });
|
|
221
|
-
const
|
|
222
|
-
|
|
340
|
+
for (const [event, name] of eventsOf(row, { prompt, post })) {
|
|
341
|
+
const kept = entries(row, config, event).filter((e) => !ours(e));
|
|
342
|
+
box[name] = [...kept, group(row, event)];
|
|
343
|
+
}
|
|
223
344
|
return config;
|
|
224
345
|
}
|
|
225
346
|
|
|
226
347
|
/** The text `install` would write, or the reason the existing file cannot be read. */
|
|
227
|
-
export function rendered(row, existing) {
|
|
228
|
-
if (existing === null) return { text: `${JSON.stringify(withHook(row, {}), null, 2)}\n`, action: 'created' };
|
|
348
|
+
export function rendered(row, existing, options = {}) {
|
|
349
|
+
if (existing === null) return { text: `${JSON.stringify(withHook(row, {}, options), null, 2)}\n`, action: 'created' };
|
|
229
350
|
let config;
|
|
230
351
|
try {
|
|
231
352
|
config = JSON.parse(existing);
|
|
@@ -235,6 +356,6 @@ export function rendered(row, existing) {
|
|
|
235
356
|
if (config === null || typeof config !== 'object' || Array.isArray(config)) {
|
|
236
357
|
return { error: 'not a JSON object; left alone' };
|
|
237
358
|
}
|
|
238
|
-
const text = `${JSON.stringify(withHook(row, config), null, 2)}\n`;
|
|
359
|
+
const text = `${JSON.stringify(withHook(row, config, options), null, 2)}\n`;
|
|
239
360
|
return { text, action: text === existing ? 'unchanged' : 'updated' };
|
|
240
361
|
}
|
|
@@ -74,6 +74,12 @@ export const BOOL_FLAGS = new Set([
|
|
|
74
74
|
// Off by default because a path is the change the caller named, and the diff
|
|
75
75
|
// is a different question.
|
|
76
76
|
'changed',
|
|
77
|
+
// local doctor: allow non-loopback model URLs. Off by default because nothing leaves
|
|
78
|
+
// the machine unless explicitly overridden (ADR-075, plan 0003 R37).
|
|
79
|
+
'allow-remote',
|
|
80
|
+
// index / map / brief: opt into local vector embeddings configured under local.embed
|
|
81
|
+
// (plan 0003 R28, R37). Off by default unless recall@10 gains at least 10 points.
|
|
82
|
+
'embed',
|
|
77
83
|
]);
|
|
78
84
|
|
|
79
85
|
export const VALUE_FLAGS = new Set([
|
|
@@ -118,6 +124,8 @@ export const VALUE_FLAGS = new Set([
|
|
|
118
124
|
// map: outline (paths), names (exported symbols) or signatures (the line).
|
|
119
125
|
// A boolean would not say which of the three the caller wanted.
|
|
120
126
|
'level',
|
|
127
|
+
// decide: structured supersession spec (ADR-NNN or ADR-NNN:<ruling>[:<kind>], repeatable).
|
|
128
|
+
'supersedes',
|
|
121
129
|
]);
|
|
122
130
|
|
|
123
131
|
/**
|
|
@@ -133,7 +141,7 @@ export const VALUE_FLAGS = new Set([
|
|
|
133
141
|
* is a superset of the old behaviour, not a replacement: `--skill a,b` parsed before it
|
|
134
142
|
* parses now.
|
|
135
143
|
*/
|
|
136
|
-
export const REPEATABLE_FLAGS = new Set(['skill', 'arm', 'compare']);
|
|
144
|
+
export const REPEATABLE_FLAGS = new Set(['skill', 'arm', 'compare', 'supersedes']);
|
|
137
145
|
|
|
138
146
|
/**
|
|
139
147
|
* @param {string[]} argv raw arguments, excluding node and the script path
|
|
@@ -207,6 +215,13 @@ export function parse(argv) {
|
|
|
207
215
|
return { ok: true, value: { flags, positional } };
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
export function parsePathList(value) {
|
|
219
|
+
return String(value ?? '')
|
|
220
|
+
.split(',')
|
|
221
|
+
.map((s) => s.trim())
|
|
222
|
+
.filter(Boolean);
|
|
223
|
+
}
|
|
224
|
+
|
|
210
225
|
/** `--max-bytes` -> `maxBytes`. Callers use camelCase; users type kebab-case. */
|
|
211
226
|
function camel(name) {
|
|
212
227
|
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -173,6 +173,7 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
173
173
|
resolved: false,
|
|
174
174
|
gatesPassed: false,
|
|
175
175
|
diff,
|
|
176
|
+
goldFiles: [...goldFiles],
|
|
176
177
|
agentPatch: diffPatchRes.stdout,
|
|
177
178
|
};
|
|
178
179
|
}
|
|
@@ -183,6 +184,7 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
183
184
|
resolved: false,
|
|
184
185
|
gatesPassed: false,
|
|
185
186
|
diff,
|
|
187
|
+
goldFiles: [...goldFiles],
|
|
186
188
|
agentPatch: diffPatchRes.stdout,
|
|
187
189
|
};
|
|
188
190
|
}
|
|
@@ -224,6 +226,7 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
224
226
|
resolved,
|
|
225
227
|
gatesPassed,
|
|
226
228
|
diff,
|
|
229
|
+
goldFiles: [...goldFiles],
|
|
227
230
|
agentPatch: diffPatchRes.stdout,
|
|
228
231
|
};
|
|
229
232
|
}
|