simt-dev 0.2.1__tar.gz → 0.3.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.2.1 → simt_dev-0.3.0}/.gitignore +7 -1
- {simt_dev-0.2.1 → simt_dev-0.3.0}/CHANGELOG.md +239 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/PKG-INFO +2 -2
- simt_dev-0.3.0/bin/simt-dev.mjs +47 -0
- simt_dev-0.3.0/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
- simt_dev-0.3.0/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
- simt_dev-0.3.0/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
- simt_dev-0.3.0/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
- simt_dev-0.3.0/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
- simt_dev-0.3.0/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
- simt_dev-0.3.0/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
- simt_dev-0.3.0/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
- simt_dev-0.3.0/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
- simt_dev-0.3.0/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
- simt_dev-0.3.0/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
- simt_dev-0.3.0/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
- simt_dev-0.3.0/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
- simt_dev-0.3.0/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
- simt_dev-0.3.0/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
- simt_dev-0.3.0/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
- simt_dev-0.3.0/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
- simt_dev-0.3.0/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
- simt_dev-0.3.0/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/package.json +2 -1
- {simt_dev-0.2.1 → simt_dev-0.3.0}/pyproject.toml +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/core.md +1 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/android.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/cloudflare-workers.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/connectiq.md +1 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/container.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/dart.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/deno.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/dotnet.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/elixir.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/go.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/jvm.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/node.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/python.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/react-native.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/react-web.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/ruby.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/rust.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/swift.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/typescript.md +2 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/stacks/uv.md +2 -0
- simt_dev-0.3.0/src/agenthooks/decide.mjs +123 -0
- simt_dev-0.3.0/src/agenthooks/policy.mjs +78 -0
- simt_dev-0.3.0/src/agenthooks/render.mjs +240 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/argv.mjs +55 -1
- simt_dev-0.3.0/src/bench/ablate.mjs +630 -0
- simt_dev-0.3.0/src/bench/adapters.json +149 -0
- simt_dev-0.3.0/src/bench/grade.mjs +229 -0
- simt_dev-0.3.0/src/bench/mine.mjs +811 -0
- simt_dev-0.3.0/src/bench/probes.mjs +1350 -0
- simt_dev-0.3.0/src/bench/report.mjs +899 -0
- simt_dev-0.3.0/src/bench/run.mjs +1002 -0
- simt_dev-0.3.0/src/commands/agenthook.mjs +282 -0
- simt_dev-0.3.0/src/commands/bench.mjs +359 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/compare.mjs +15 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/graph.mjs +45 -21
- simt_dev-0.3.0/src/commands/harness.mjs +301 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/help.mjs +73 -2
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/index.mjs +67 -25
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/init.mjs +12 -8
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/mcp.mjs +20 -5
- simt_dev-0.3.0/src/commands/plan.mjs +99 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/rules.mjs +42 -57
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/skills.mjs +67 -92
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/skillsinstall.mjs +17 -2
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/sync.mjs +62 -6
- simt_dev-0.3.0/src/commands/tokens.mjs +169 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/compare/row.mjs +73 -2
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/decisions/index.mjs +23 -5
- simt_dev-0.3.0/src/graph/spec.mjs +227 -0
- simt_dev-0.3.0/src/graph/speccheck.mjs +68 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/dispatcher.mjs +35 -3
- simt_dev-0.3.0/src/kernel/card.mjs +254 -0
- simt_dev-0.3.0/src/kernel/inferability.mjs +188 -0
- simt_dev-0.3.0/src/mcp/resources.mjs +157 -0
- simt_dev-0.3.0/src/mcp/schema.mjs +139 -0
- simt_dev-0.3.0/src/mcp/server.mjs +375 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/mcp/tools.mjs +372 -1
- simt_dev-0.3.0/src/measure/context.mjs +542 -0
- simt_dev-0.3.0/src/measure/loaderView.mjs +435 -0
- simt_dev-0.3.0/src/measure/session.mjs +267 -0
- simt_dev-0.3.0/src/monorepo.mjs +339 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/output/index.mjs +78 -2
- simt_dev-0.3.0/src/packs.mjs +182 -0
- simt_dev-0.3.0/src/plan/index.mjs +650 -0
- simt_dev-0.3.0/src/plan/template.mjs +331 -0
- simt_dev-0.3.0/src/projection/loaders.mjs +689 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/manifest.mjs +114 -3
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/project.mjs +140 -9
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/rules.mjs +365 -22
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/surfaces.mjs +155 -23
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/targets.mjs +37 -0
- simt_dev-0.3.0/src/toolwords.mjs +69 -0
- simt_dev-0.3.0/templates/plan/tracker.json +36 -0
- simt_dev-0.2.1/bin/simt-dev.mjs +0 -28
- simt_dev-0.2.1/src/mcp/server.mjs +0 -167
- {simt_dev-0.2.1 → simt_dev-0.3.0}/LICENSE +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/README.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-001-push-requires-approval.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-002-refactor-blast-radius.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-003-test-breadth.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-004-hook-owns-format.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-005-file-length.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-006-coverage-bar.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-007-section-skeleton.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-008-verdict-vocabulary.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-009-skill-layout.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-010-runner-invocation.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-011-windows-support.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-012-drop-windows-support.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-014-workspace-is-configuration.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-021-one-surface-per-repo.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.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.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.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.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-044-compatibility-is-not-evidence.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-046-one-rule-one-copy.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.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.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-061-the-skill-registry-is-workspace-data.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/ADR-063-the-adoption-escape-hatch-is-opt-in.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/docs/decisions/README.md +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/python/simt_dev/__init__.py +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/python/simt_dev/cli.py +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/mdc/intake-questions.mdc +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/mdc/preview-postflight.mdc +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/mdc/slice-commit.mdc +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/rules/mdc/test-gate.mdc +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/ci/dependabot.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/ci/workflow.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/ci.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/hooks.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/commands/upgrade.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/compare/discover.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/compare/hooksstate.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/graph/load.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/capability.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/gates.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/resolve.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/shim.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/hooks/where.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/identity.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/locate.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/name.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/package-version.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/baselines.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/conflicts.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/markers.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/projection/mdc.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/resolve/eval.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/resolve/fingerprint.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/resolve/near.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/resolve/resolve.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/resolve/source.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/toolchain.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/tsconfig.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/upgrade/plan.mjs +0 -0
- {simt_dev-0.2.1 → simt_dev-0.3.0}/src/workspace.mjs +0 -0
|
@@ -7,4 +7,10 @@
|
|
|
7
7
|
git.config
|
|
8
8
|
|
|
9
9
|
.pybuild/
|
|
10
|
-
dist/
|
|
10
|
+
dist/
|
|
11
|
+
|
|
12
|
+
# Local index, evidence ledger and benchmark run outputs (plan 0003 §4).
|
|
13
|
+
# Task descriptors in .simt-dev/bench/tasks/*.json are committed; runs are not.
|
|
14
|
+
.simt-dev/index.sqlite
|
|
15
|
+
.simt-dev/ledger.jsonl
|
|
16
|
+
.simt-dev/bench/runs/
|
|
@@ -6,6 +6,242 @@ All notable changes to this package are recorded here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] — 2026-10-17
|
|
10
|
+
|
|
11
|
+
### The rules and the decisions are readable as MCP resources
|
|
12
|
+
|
|
13
|
+
A client that wants to browse this tool's rules and rulings had no way to: every answer came back from a tool call with an argument in it. They are addressable now, as one URI per rule and one per decision record, with the byte size each will cost stated up front — the one number a client cannot get without spending the read.
|
|
14
|
+
|
|
15
|
+
Both resource revisions of the specification were read rather than recalled, and they disagree about the behaviour that decides whether a client retries or gives up: a missing resource is `-32602` on the newest revision and `-32002` on the two older ones, and neither lets a server answer with an empty body. A decision record is redacted on the way out, because a resource travels further than the CLI did (ADR-074).
|
|
16
|
+
|
|
17
|
+
### A plan is resumed one slice at a time
|
|
18
|
+
|
|
19
|
+
`simt plan next` returns the next ready slice and only its section. `simt plan done` rewrites that one row, and previews the change unless `--yes` is given. The directories and the table format come from the `plans` key in `simt.json` when a repository sets one; otherwise the tracker template delivered with the tool is used, which reads `docs/plans` and `docs/reviews` (ADR-081, ADR-082). `init` keeps a manifest key it does not write, which is what lets `plans` survive a re-init.
|
|
20
|
+
|
|
21
|
+
### The rules are enforced at the tool call
|
|
22
|
+
|
|
23
|
+
Three rules that text does not hold — a `git push`, a `--no-verify`, an edit inside a generated region — are now a decision before the tool call. `simt agent-hooks install` writes one policy into the harnesses that read a hooks file, and keeps every key it does not own. `simt agent-hook` is the entry point those harnesses call (ADR-079).
|
|
24
|
+
|
|
25
|
+
### A line the repository already proves does not ship
|
|
26
|
+
|
|
27
|
+
A rule line may say when the repository already proves it. `simt sync` drops that line, and `--explain` names the line and the fact, so a removal is visible rather than silent (ADR-078).
|
|
28
|
+
|
|
29
|
+
### The region leads with this repository's commands
|
|
30
|
+
|
|
31
|
+
The projected region opens with the commands this repository actually runs, derived from its committed gates and runner, in both the full projection and kernel mode (ADR-077).
|
|
32
|
+
|
|
33
|
+
### A change to what an agent reads can be measured
|
|
34
|
+
|
|
35
|
+
`simt bench mine`, `run`, `report`, `ablate` and `probes` measure a change to projected context against tasks taken from this repository's own history. A change that cannot be run says `unmeasured` in its record rather than implying a result (ADR-076).
|
|
36
|
+
|
|
37
|
+
### What each reader loads, and a transcript split
|
|
38
|
+
|
|
39
|
+
`simt harness loaders` reports what each reader loads at session start, in bytes, and what its size cap drops. DeepSeek is one of those readers. `simt tokens --session` splits a local Claude Code or Codex transcript into rules, file reads and tool output.
|
|
40
|
+
|
|
41
|
+
### A name this tool owns is not reported as a repository
|
|
42
|
+
|
|
43
|
+
`simt rules why` keeps this tool's own names (`simt-priority`, `simt-hooks`, and the rest) and still redacts a repository the reader cannot open (ADR-080).
|
|
44
|
+
|
|
45
|
+
## [0.2.2] — 2026-10-14
|
|
46
|
+
|
|
47
|
+
### A repository can ask for a smaller projection
|
|
48
|
+
|
|
49
|
+
`simt tokens` measured the cost and `check` now holds the line the repository draws: `budget.bytes`
|
|
50
|
+
in `simt.json` is how many always-on bytes that repository has agreed to carry, and both the meter and
|
|
51
|
+
the gate report the overflow with the two numbers in them. It sits beside `gates` rather than inside
|
|
52
|
+
`projection`, because that key belongs to this tool and an upgrade replaces it.
|
|
53
|
+
|
|
54
|
+
`mode: kernel` is the other half. It keeps the rules a reader cannot do without — the ones a hard
|
|
55
|
+
constraint or a command depends on — and defers the rest behind a `## Deferred rules` section that
|
|
56
|
+
names each one and the command that shows it. A Python fixture projects 16% smaller. `full` is still
|
|
57
|
+
the default, because nobody has measured yet whether the smaller projection loses compliance, and a
|
|
58
|
+
tool that made that trade silently would be deciding it on the reader's behalf (ADR-073).
|
|
59
|
+
|
|
60
|
+
### A repository can declare how many always-on bytes it is willing to carry
|
|
61
|
+
|
|
62
|
+
`simt tokens` has reported that number since the meter landed, against a constant nobody
|
|
63
|
+
could argue with. A repository can now say what it will carry — `budget: { bytes: N }`
|
|
64
|
+
beside `limits` and `gates` in the manifest — and both `simt tokens` and `simt-dev check`
|
|
65
|
+
measure against that line. One declaration, one meter, one function that words the finding.
|
|
66
|
+
|
|
67
|
+
The declaration is deliberately *not* inside `projection`, which is the one block this tool
|
|
68
|
+
owns and `upgrade` is allowed to replace: a number written there would be deleted silently
|
|
69
|
+
on the next upgrade and never reported as drift. And a repository that declares nothing is
|
|
70
|
+
reported as declaring nothing, rather than being quietly handed the default as though it
|
|
71
|
+
had chosen it.
|
|
72
|
+
|
|
73
|
+
Over budget is reported and never fails the gate. Nothing was cut — the projection is
|
|
74
|
+
still full mode, every rule loaded as written — so the honest thing to say is "you are
|
|
75
|
+
over your line", not to fail a repository over a ceiling it wrote down itself. Which rules
|
|
76
|
+
move behind skills stays with the evaluation that has to decide it (ADR-072).
|
|
77
|
+
|
|
78
|
+
### A repository can declare the always-on bytes it intends to carry
|
|
79
|
+
|
|
80
|
+
`simt tokens` measured the number; it could not be held to one. A manifest now carries
|
|
81
|
+
`budget.bytes` — a declaration beside `limits` and `gates`, not inside `projection`,
|
|
82
|
+
because that block is regenerated on every upgrade and nothing in the repair would notice
|
|
83
|
+
its budget disappearing. A repository that declares nothing is measured against the
|
|
84
|
+
review's 30 KB and reports `declared: false` rather than looking like it chose it.
|
|
85
|
+
|
|
86
|
+
Over budget is reported, never enforced, and the report says what was not done: nothing
|
|
87
|
+
was cut, because the projection is still in full mode. Which rules move behind skills is a
|
|
88
|
+
measurement that has not been taken yet, and a budget that quietly dropped rules would be
|
|
89
|
+
enforcing a decision nobody made (ADR-072).
|
|
90
|
+
|
|
91
|
+
### A stack profile loads only when a matching file is in play
|
|
92
|
+
|
|
93
|
+
A stack profile used to be always-on everywhere, because profiles reached a reader through
|
|
94
|
+
the generated region in AGENTS.md and through nothing else. A profile can now declare
|
|
95
|
+
`simt-scope`, and a repository on Cursor, Copilot or Antigravity gets it as a file the
|
|
96
|
+
surface attaches on patterns: `.cursor/rules/python.mdc` with `globs` and
|
|
97
|
+
`alwaysApply: false`, an Antigravity rule with `trigger: glob`, a Copilot
|
|
98
|
+
`.instructions.md` with `applyTo`. AGENTS.md keeps one line naming the profile, the
|
|
99
|
+
patterns and the file it moved to — a profile that vanishes from the region reads as one
|
|
100
|
+
that was deleted.
|
|
101
|
+
|
|
102
|
+
The narrowing happens only when every selected surface can do it. Claude Code's `paths:`
|
|
103
|
+
key is documented and silently fails to lazy-load for quoted values, and opencode has no
|
|
104
|
+
per-rule channel at all, so a repository carrying either keeps its profiles in the region
|
|
105
|
+
and is told which surface stopped it. A rule that never loads is worse than a rule that
|
|
106
|
+
loads eagerly.
|
|
107
|
+
|
|
108
|
+
Reading the two vendors' docs changed the shape of this: Antigravity requires patterns
|
|
109
|
+
starting with `*` to be quoted or YAML reads them as an alias anchor and the rule is
|
|
110
|
+
discarded with no message, and it documents a 20,000-token budget for always_on rules past
|
|
111
|
+
which they are demoted to pointers — so eager profiles cost the reader a real budget. A
|
|
112
|
+
`simt-scope` that matches everything is now refused, since that is the same behaviour with
|
|
113
|
+
a narrowing-looking spelling (ADR-071).
|
|
114
|
+
|
|
115
|
+
### Every `--json` payload says which version it is
|
|
116
|
+
|
|
117
|
+
An agent that parses `--json` had no way to notice that a field had moved: the parse
|
|
118
|
+
succeeds, the lookup returns `undefined`, and the answer is quietly wrong. Every payload now
|
|
119
|
+
opens with `schemaVersion`, and a version in a comment is not a version, so it travels in
|
|
120
|
+
the document every reader is guaranteed to see.
|
|
121
|
+
|
|
122
|
+
The same document is now one line rather than four columns of whitespace, because the reader
|
|
123
|
+
of `--json` is a program and it pays for every byte. `--pretty` asks for the other form, and
|
|
124
|
+
a terminal gets it without being asked, because there the reader is a person.
|
|
125
|
+
|
|
126
|
+
Writing the contract test found two things the review that asked for this missed. Eight
|
|
127
|
+
commands — `doctor`, `mcp --list`, `graph lint`, `rules list`, and the `skills` ones — had a
|
|
128
|
+
private `emit(JSON.stringify(..., null, 2))` instead of the shared helper, so `--json` meant
|
|
129
|
+
something different depending on which command you asked. And a command that *failed* under
|
|
130
|
+
`--json` wrote only to stderr, so a scripted caller got an empty document and a non-zero
|
|
131
|
+
exit and no way to learn what went wrong. It now answers in the shape it was asked in (ADR-070).
|
|
132
|
+
|
|
133
|
+
### `graph lint` now checks skills against the published specification
|
|
134
|
+
|
|
135
|
+
The Agent Skills specification sets limits nobody here was checking: a name of lowercase
|
|
136
|
+
letters, digits and single hyphens that equals the directory it lives in; a description
|
|
137
|
+
under 1,024 characters; a compatibility note under 500; a body recommended under 5,000
|
|
138
|
+
tokens because an agent loads all of it once the skill activates. `graph lint --spec
|
|
139
|
+
agentskills` checks every skill against those, and the specification was fetched rather
|
|
140
|
+
than recalled — reading it turned up four rules the review that asked for this did not
|
|
141
|
+
list, including the one that breaks installs.
|
|
142
|
+
|
|
143
|
+
What the specification calls a must fails the exit code; what it calls a recommendation
|
|
144
|
+
warns. A lint that fails on advice is a lint that gets skipped. `skills install` now asks
|
|
145
|
+
the same name rules, since the directory is created from the name and an unaddressable
|
|
146
|
+
skill would be the last thing anyone notices.
|
|
147
|
+
|
|
148
|
+
### The MCP surface speaks three protocol revisions, and answers in the shape each asks for
|
|
149
|
+
|
|
150
|
+
The server was pinned to one revision and returned pretty JSON in a text block, so a
|
|
151
|
+
client could read the answer but could not validate it, could not cache it, and could not
|
|
152
|
+
tell a read-only tool from one that writes. All three published revisions were read rather
|
|
153
|
+
than guessed at, and each is answered in its own shape: the newest drops the handshake for
|
|
154
|
+
a version carried on every request, adds a discovery method servers must implement,
|
|
155
|
+
requires a result-type field and a cache lifetime on list results, and deleted the liveness
|
|
156
|
+
method — which older clients still get, and newest clients are told is gone by name.
|
|
157
|
+
|
|
158
|
+
Every tool now declares what it returns and says it is a reader, and every call returns
|
|
159
|
+
the answer in both shapes: the text block for a client that predates structured content,
|
|
160
|
+
the structured block for one that validates against the declared schema. The byte ceiling
|
|
161
|
+
bounds the text only, so the block a client can check still checks. The schema checker
|
|
162
|
+
that makes this testable lives in the package rather than in a dependency.
|
|
163
|
+
|
|
164
|
+
### Which harnesses a repository is set up for is now answerable
|
|
165
|
+
|
|
166
|
+
`simt-dev harness detect` looks at the configuration directories that exist and says
|
|
167
|
+
which harnesses this repository — and, with `--user`, its reader — is set up for. It
|
|
168
|
+
claims presence and nothing else: a directory found is not a claim about what its owner
|
|
169
|
+
reads, because the tool cannot answer that without a vendor document, and a guess here
|
|
170
|
+
would be the reader's problem the first time it was wrong. A harness that reads
|
|
171
|
+
AGENTS.md is served by the standard surface whatever else it reads, and every surface row
|
|
172
|
+
now records that in three values rather than two, because "reads AGENTS.md" and "is
|
|
173
|
+
covered for this harness" are not the same sentence — Copilot's CLI reads it and its
|
|
174
|
+
editor, code review and cloud agent do not.
|
|
175
|
+
|
|
176
|
+
No new harness rows ship with it. The candidates are recorded as directory names, which
|
|
177
|
+
claim presence and not coverage; a row still requires the vendor document it cites, and
|
|
178
|
+
one that reads nothing has to be added on purpose.
|
|
179
|
+
|
|
180
|
+
### A monorepo gets rules for each of its packages
|
|
181
|
+
|
|
182
|
+
One repository, one projection: every package under the root was given the root's rules,
|
|
183
|
+
so a React application inside a node workspace was handed the node profile and nothing
|
|
184
|
+
else, and the profile that should have applied to it was applied by nobody. The rule that
|
|
185
|
+
package needed was not wrong — it was missing.
|
|
186
|
+
|
|
187
|
+
Packages are now discovered from the declarations that already list them (npm and pnpm
|
|
188
|
+
workspaces, turbo, Cargo members, `go.work`, Gradle includes) rather than by walking the
|
|
189
|
+
tree, because a declaration states which directories are members and a marker file only
|
|
190
|
+
says there is a build here. The root keeps what every package shares; each package that
|
|
191
|
+
selects something new gets a generated region carrying exactly its own delta, and a
|
|
192
|
+
package that selects nothing new gets no file at all. `check` reports every package
|
|
193
|
+
region and fails on any one of them, and `compare` counts the drifted regions rather than
|
|
194
|
+
the repositories that hold them.
|
|
195
|
+
|
|
196
|
+
A gate can also name the directory it runs in, which is what a monorepo with no root test
|
|
197
|
+
script needs: `gates.cwd` maps a gate to a directory, a directory that does not exist or
|
|
198
|
+
resolves outside the repository fails the event and is reported by `check` before the
|
|
199
|
+
commit rather than at it, and a directory named for a gate that is not declared is
|
|
200
|
+
reported as the inert entry it is.
|
|
201
|
+
|
|
202
|
+
### One rule set, shared by the repositories that agreed on it
|
|
203
|
+
|
|
204
|
+
Several repositories want the same rules and each kept its own copy, so a policy change
|
|
205
|
+
was several edits that could be several-minus-one, and the repository that missed it
|
|
206
|
+
carried instructions nobody there wrote. A pack is that shared set: `packs` in the manifest
|
|
207
|
+
declares it, the bytes live in the repository, and a digest of them is recorded so nobody
|
|
208
|
+
has to trust that the two agree.
|
|
209
|
+
|
|
210
|
+
A pack's rules are selected by the same matcher as any profile and appear in the region's
|
|
211
|
+
source list under the pack's own directory. Precedence is the repository, then a pack it
|
|
212
|
+
declares, then what the tool ships, and every replacement is reported rather than silent.
|
|
213
|
+
|
|
214
|
+
A pack that disagrees with its pin still projects — the gate blocks and prints both
|
|
215
|
+
digests, because dropping it would let the repair delete the rules it is reporting. One
|
|
216
|
+
that is missing blocks with a remedy that restores it, and one with no digest is reported
|
|
217
|
+
without failing anything, since its rules render correctly. `compare` lists every pack and
|
|
218
|
+
the digest it is pinned to, so a repository behind a shared change is visible before
|
|
219
|
+
anyone follows stale instructions. Nothing is fetched: the bytes are in the repository,
|
|
220
|
+
which is what makes the rendered region reproducible from what the reader has (ADR-065).
|
|
221
|
+
|
|
222
|
+
### `simt tokens` counts what a reader loads
|
|
223
|
+
|
|
224
|
+
Review 0002 filed the estate's always-on context at 90 KB per turn, 72% of it a paragraph
|
|
225
|
+
an earlier repository had already loaded — a number produced by reading seven
|
|
226
|
+
repositories by hand, which ages the moment a rule is edited and cannot be re-derived by
|
|
227
|
+
the reader who has to act on it.
|
|
228
|
+
|
|
229
|
+
`simt tokens` is that reading, as a command. It reports the bytes this tool wrote per
|
|
230
|
+
channel (region, rule files, skill metadata), an approximate token figure, and — with
|
|
231
|
+
`--repos` — the same figure across a workspace, with every repeated paragraph attributed
|
|
232
|
+
to the first root that carries it. A region that ends past a harness's read limit is
|
|
233
|
+
reported with its byte offset, using the same numbers the existing check uses.
|
|
234
|
+
|
|
235
|
+
Three choices keep the figure honest, and all three are recorded in ADR-064: the meter
|
|
236
|
+
counts what the projection wrote and reports hand-written bytes beside it rather than
|
|
237
|
+
folding them into the budget; a region is the span between its two markers, so the blank
|
|
238
|
+
line a person left above the marker is billed to nobody; and an over-budget repository is
|
|
239
|
+
reported rather than failed, because a meter that went red would train people to ignore
|
|
240
|
+
the number.
|
|
241
|
+
|
|
242
|
+
S38 stays open — this is the instrument. Whether the estate gets under 30 KB is R07's
|
|
243
|
+
kernel, R08's budget field and R11's dedupe, measured with this.
|
|
244
|
+
|
|
9
245
|
## [0.2.1] — 2026-10-13
|
|
10
246
|
|
|
11
247
|
### `init` writes in place; the backup tag and adopt branch are opt-in
|
|
@@ -327,6 +563,9 @@ never ran `simt hooks install` again will find nothing until the dependency is r
|
|
|
327
563
|
and `simt hooks install --yes` is re-run. That is a real upgrade step and it is called out
|
|
328
564
|
here rather than discovered.
|
|
329
565
|
|
|
566
|
+
[0.3.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.3.0
|
|
567
|
+
[0.2.2]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.2.2
|
|
568
|
+
[0.2.1]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.2.1
|
|
330
569
|
[0.2.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.2.0
|
|
331
570
|
[0.1.1]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.1.1
|
|
332
571
|
[0.1.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: simt-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// simt-dev - developer and agent tooling for the SIMT ecosystem.
|
|
3
|
+
//
|
|
4
|
+
// POSIX only (ADR-012). package.json declares os: [darwin, linux], so npm refuses
|
|
5
|
+
// the install elsewhere rather than letting a hook fail obscurely.
|
|
6
|
+
|
|
7
|
+
import { parse } from '../src/argv.mjs';
|
|
8
|
+
import { EXIT, fail, jsonFailMode, jsonIndentDefault } from '../src/output/index.mjs';
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
const parsed = parse(argv);
|
|
13
|
+
if (!parsed.ok) {
|
|
14
|
+
process.exit(parsed.code);
|
|
15
|
+
}
|
|
16
|
+
const { flags, positional } = parsed.value;
|
|
17
|
+
|
|
18
|
+
// No arguments at all is a usage error, not a silent help dump: an agent that
|
|
19
|
+
// ran the wrong thing should be told so rather than handed 40 lines of prose.
|
|
20
|
+
if (positional.length === 0 && !flags.help && !flags.version) {
|
|
21
|
+
process.exit(fail('no command given', EXIT.USAGE));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
jsonIndentDefault(flags.pretty === true);
|
|
25
|
+
jsonFailMode(flags.json === true);
|
|
26
|
+
|
|
27
|
+
// The one command that runs on somebody else's hot path, and therefore the one that
|
|
28
|
+
// does not load the registry. `agent-hook` is called before every tool call an agent
|
|
29
|
+
// makes, so its latency is a product requirement rather than a nicety (plan 0003
|
|
30
|
+
// R15), and the registry's imports - detection, projection, the bench corpus - cost
|
|
31
|
+
// tens of milliseconds before the command itself starts. Measured on the machine
|
|
32
|
+
// this was written on: 127.7 ms through the registry against 91.5 ms with only this
|
|
33
|
+
// module's imports, where the bare runtime is 86.9 ms of it.
|
|
34
|
+
//
|
|
35
|
+
// Only the exact spelling the renderers write takes this path. Anything else -
|
|
36
|
+
// including `agent-hook --help` - loads the registry, so help stays the one list of
|
|
37
|
+
// every command and this shortcut cannot become a second, thinner command surface.
|
|
38
|
+
if (positional[0] === 'agent-hook' && !flags.help && !flags.version) {
|
|
39
|
+
const { agentHookCommand } = await import('../src/commands/agenthook.mjs');
|
|
40
|
+
process.exit((await agentHookCommand(positional.slice(1), flags)) ?? EXIT.OK);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { dispatch } = await import('../src/commands/index.mjs');
|
|
44
|
+
process.exit((await dispatch(argv)) ?? EXIT.OK);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
process.exit(fail(error?.message ?? String(error), EXIT.FAIL));
|
|
47
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# ADR-064 — The context meter counts what the tool wrote, and says what it did not
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed after review 0002 S38
|
|
4
|
+
|
|
5
|
+
**Affects:** the `tokens` command, the new context meter in `src/measure/context.mjs`, and
|
|
6
|
+
the structure field `readLimitFindings` now returns.
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Review 0002 S38 measured the estate by hand and filed a number: 90,042 bytes of always-on
|
|
11
|
+
context per turn across seven workspace roots, of which 64,738 bytes — 72% — were a
|
|
12
|
+
paragraph an earlier root had already loaded. It also found a repository whose region
|
|
13
|
+
ended past the 32,768 bytes Codex reads before it stops.
|
|
14
|
+
|
|
15
|
+
Two things are wrong with a finding that arrives as a hand count. The count ages: the
|
|
16
|
+
moment a rule is edited, the number is a description of a version nobody has any more.
|
|
17
|
+
And it cannot be re-derived by the reader who has to act on it — it was produced by
|
|
18
|
+
reading seven repositories, which is the work the tool exists to do.
|
|
19
|
+
|
|
20
|
+
Plan 0003 R06 asks for the same measurement as a command, with an acceptance criterion
|
|
21
|
+
that names the figures it must reproduce: the 90,042 bytes, the 64,738 redundant, and a
|
|
22
|
+
repository beyond Codex's limit. Reproducing a hand count is only possible if the meter
|
|
23
|
+
counts what the hand counted — which means settling what "always-on" means before
|
|
24
|
+
writing the code, because every choice here changes the number the criterion will be
|
|
25
|
+
checked against.
|
|
26
|
+
|
|
27
|
+
The answer is the hardest question in the command, and it is not "bytes".
|
|
28
|
+
|
|
29
|
+
## Rulings
|
|
30
|
+
|
|
31
|
+
1. **The meter counts what this tool wrote, and reports hand-written bytes beside it
|
|
32
|
+
rather than folding them in.** The budget being defended is the projection's: a
|
|
33
|
+
repository's own 40 KB of prose in AGENTS.md is an authoring decision the tool did not
|
|
34
|
+
make and cannot shrink. Billing it to a projection budget would make the budget
|
|
35
|
+
unachievable for reasons outside the projection, which is how a budget stops being
|
|
36
|
+
read. So every channel reports `generatedBytes` and `readerBytes` separately, and the
|
|
37
|
+
headline figure is the generated one.
|
|
38
|
+
|
|
39
|
+
2. **A token count is an approximation, and it is labelled as one every time it is
|
|
40
|
+
printed.** Four bytes per token, stated in the payload and in the text, with a `~`
|
|
41
|
+
in front of the number. Nobody has measured this corpus, and a figure that looks exact
|
|
42
|
+
invites the reader to spend it as though it were.
|
|
43
|
+
|
|
44
|
+
3. **A region is the span between its two markers, and nothing outside it counts as
|
|
45
|
+
generated — not even the blank line above the marker.** This is the defect the first
|
|
46
|
+
test run caught: subtracting the hand-written prefix from the file billed the
|
|
47
|
+
projection for a newline a person typed. In a command whose whole claim is that it
|
|
48
|
+
counts exactly, that byte is the whole argument.
|
|
49
|
+
|
|
50
|
+
4. **Redundancy is byte-exact, blank-line-paragraph by blank-line-paragraph, measured
|
|
51
|
+
across roots in sorted path order.** That is the method review 0002 used by hand, and
|
|
52
|
+
it is reproducible: the same corpus measured twice gives the same figure, which is
|
|
53
|
+
what lets the number appear in a commit message. Order is therefore load-bearing —
|
|
54
|
+
`discover` sorts, and the first root to carry a paragraph owns it. Reordering the walk
|
|
55
|
+
would move bytes between roots, so nothing downstream re-sorts them.
|
|
56
|
+
|
|
57
|
+
5. **The budget is reported, not enforced, and the command exits 0 for it.** A
|
|
58
|
+
repository over budget is a fact about the repository. A meter that went red for it
|
|
59
|
+
would train people to ignore the number it exists to print. Enforcement is R08's
|
|
60
|
+
business, and it is a manifest field rather than a constant so a repository can argue
|
|
61
|
+
with its own budget.
|
|
62
|
+
|
|
63
|
+
6. **A pointer is counted once, as the file it points at.** One reader, one copy of the
|
|
64
|
+
text; enumerating the target as well would double every rule the projection replaced
|
|
65
|
+
with a pointer, which is precisely the saving the pointer was made for.
|
|
66
|
+
|
|
67
|
+
7. **MCP `tools/list` bytes are reported once, outside every repository total.** The
|
|
68
|
+
same tool list is loaded whatever the repository, so summing it into each root would
|
|
69
|
+
multiply one number by the number of roots and call the result a workspace cost.
|
|
70
|
+
|
|
71
|
+
8. **The flag is `--repos`, not `--workspace`.** Plan 0003 R06 wrote `--workspace`, but
|
|
72
|
+
`--workspace <path>` has been a value flag since plan 0002, where it names a
|
|
73
|
+
directory of skill sources. One flag cannot mean two things depending on which
|
|
74
|
+
command read it — that is the silent-ignore defect the argument parser's own header
|
|
75
|
+
refuses — so the new one is `--repos`, and it says what it measures: every repository
|
|
76
|
+
under the root, in sorted order.
|
|
77
|
+
|
|
78
|
+
9. **No MCP tool exposes this yet.** The byte counts are already in the payload, but a
|
|
79
|
+
tool that returns a per-repository context cost is a second public surface to keep
|
|
80
|
+
honest. It arrives when there is a second caller for the logic to be wrong in.
|
|
81
|
+
|
|
82
|
+
## Consequences
|
|
83
|
+
|
|
84
|
+
- `simt tokens` answers "what does a turn pay for this repository", and
|
|
85
|
+
`simt tokens --repos` answers "what does a turn pay for the workspace". Both print
|
|
86
|
+
bytes and a labelled approximation; `--json` carries the same figures, because two
|
|
87
|
+
renderings of one question must not be able to disagree (ADR-050).
|
|
88
|
+
- The command refuses two shapes of path — a directory holding repositories when
|
|
89
|
+
`--repos` was not passed, and a directory holding none — with remedies that are
|
|
90
|
+
commands rather than locations.
|
|
91
|
+
- `readLimitFindings` grew structured fields (`reader`, `limitBytes`, `endByte`,
|
|
92
|
+
`overBy`) beside its prose, so the meter and the check that already reported the
|
|
93
|
+
Codex limit are reading the same numbers rather than each re-deriving them.
|
|
94
|
+
- S38 stays open. What landed is the instrument. The 30 KB figure it wants needs R07's
|
|
95
|
+
kernel, R08's budget field, R11's dedupe, and R01–R05's evaluation of whether moving
|
|
96
|
+
the process rules behind skills costs compliance — and this meter's job is to report
|
|
97
|
+
whether that work succeeded.
|
|
98
|
+
|
|
99
|
+
## Rejected
|
|
100
|
+
|
|
101
|
+
- **Failing the run when a repository is over budget.** The measurement would become
|
|
102
|
+
something people route around, and the number would go with it.
|
|
103
|
+
- **Treating a pointer as free rather than counted once.** Its target is still loaded;
|
|
104
|
+
calling that zero is the sort of accounting that makes a report unfalsifiable.
|
|
105
|
+
- **`--workspace` for the new flag, on the grounds that the plan says so.** The plan
|
|
106
|
+
wrote that before `--workspace` was a value flag for something else, and a plan is a
|
|
107
|
+
record of an intention, not a reason to ship two meanings for one name.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ADR-065 — A pack is one shared rule set, vendored into the repository and pinned by digest
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S39
|
|
4
|
+
**Affects:** `src/packs.mjs`, `src/projection/rules.mjs` (`loadRules`, `select`), `src/projection/manifest.mjs` (`packs` field), `src/commands/sync.mjs` (`check`), `src/compare/row.mjs` (`packs`, `problems`), `test/packs.test.mjs`
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
S39 found estate product policy shipping as if it were universal: a profile that named
|
|
9
|
+
one company's domain layer, a rule file that named its Firebase environment variables, and
|
|
10
|
+
a `gender` sentinel in a map profile. Fourteen repositories shared one set of those rules
|
|
11
|
+
and each carried its own copy, so a policy change was fourteen edits that could be thirteen.
|
|
12
|
+
|
|
13
|
+
Two answers were available. The first is a remote: a pack published somewhere, a `source`
|
|
14
|
+
in the manifest, and the renderer fetching it. The second is a directory in the repository
|
|
15
|
+
holding the bytes, with a digest recorded so nobody has to trust that the two agree.
|
|
16
|
+
|
|
17
|
+
The remote is the one that reads better in a design document and is worse for the reader
|
|
18
|
+
this tool is for. A region rendered from a fetched pack is a region whose bytes depend on
|
|
19
|
+
something the receiving repository does not contain: a reader who clones the repo, deletes
|
|
20
|
+
their network, and runs the gate gets a different answer, and a reviewer approving a diff
|
|
21
|
+
is approving text they cannot produce from what is in front of them. That is the single
|
|
22
|
+
failure this repository exists to prevent, and a shared-rules feature is exactly where it
|
|
23
|
+
would arrive.
|
|
24
|
+
|
|
25
|
+
The vendored answer has one obvious weakness: updating a pack is a commit in every
|
|
26
|
+
repository that declares it, not one push somewhere. That is the cost of the property
|
|
27
|
+
above, and it is the right trade — a rule a reader cannot account for is worth less than a
|
|
28
|
+
rule that is one merge behind.
|
|
29
|
+
|
|
30
|
+
ADR-061 already settled half of P4. A pack may have carried the skill registry, and the
|
|
31
|
+
review's fix line says it should; ADR-061 ruled the registry to be workspace data that the
|
|
32
|
+
package no longer ships. A second source for it would reopen that ruling by accident, from
|
|
33
|
+
a feature that is not about skills at all.
|
|
34
|
+
|
|
35
|
+
## Rulings
|
|
36
|
+
|
|
37
|
+
1. **A pack's bytes live in the adopting repository, and the manifest records a digest of
|
|
38
|
+
them.** The manifest gains a `packs` list of `{name, source, version, digest}`; the
|
|
39
|
+
directory the tool reads is a fixed one, and `source` and `version` record where the
|
|
40
|
+
bytes came from for a human and for a future fetch. Nothing in a projection reads a
|
|
41
|
+
path outside the repository it is rendering into.
|
|
42
|
+
|
|
43
|
+
2. **Precedence is ranked, not special-cased: the repository's own profile, then a pack it
|
|
44
|
+
declares, then what the tool ships.** The ranking is one table read by the selector, so
|
|
45
|
+
the precedence and the order the files are collected in cannot disagree. A replacement
|
|
46
|
+
is reported in the existing `shadowed` list, because a profile that silently replaced
|
|
47
|
+
another leaves a reader unable to tell which of two texts they are being held to.
|
|
48
|
+
|
|
49
|
+
3. **A pack that is present but disagrees with its pin still projects.** What is
|
|
50
|
+
questioned is the pin, and the gate blocks and prints both digests. Dropping the pack
|
|
51
|
+
instead would let the remedy the finding names — `sync` — delete every rule the pack
|
|
52
|
+
carries, which is the opposite of what a reader who ran it meant. A pack that is absent
|
|
53
|
+
has nothing to project and blocks with a remedy that restores it; a pack with no digest
|
|
54
|
+
is reported and does not block, because its rules render correctly and only the record of
|
|
55
|
+
which bytes they were is missing.
|
|
56
|
+
|
|
57
|
+
4. **A pack carries rule files, not a skill registry.** A profile it ships is selected by
|
|
58
|
+
the same matcher as any other and appears in the region's source list under its pack's
|
|
59
|
+
directory. A registry inside a pack is read by nothing, and that is the ruling rather
|
|
60
|
+
than an oversight: ADR-061 makes the registry workspace data, and a second source for
|
|
61
|
+
it needs its own decision about which of two registries answers.
|
|
62
|
+
|
|
63
|
+
5. **`compare` reports every pack and the digest it is pinned to, per repository.** The
|
|
64
|
+
pinned digest, not the computed one — it answers "which bytes was this declared at", and
|
|
65
|
+
a row reporting the new digest would hide the mismatch from anything reading the JSON.
|
|
66
|
+
A pack that cannot be used is a problem on the row; one that is merely unpinned is a
|
|
67
|
+
note, which is the same division the gate draws.
|
|
68
|
+
|
|
69
|
+
6. **`packs` is repository-owned content, not a generated field.** Which packs a repository
|
|
70
|
+
uses is its decision, so a re-init must not empty it, and the drift check does not
|
|
71
|
+
compare it. What is compared is the directory's digest against the pin.
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
|
|
75
|
+
- A group of repositories sharing one rule set declares the same pack and vendors the same
|
|
76
|
+
bytes; a policy change is a commit per repository, and the digest is what makes an
|
|
77
|
+
unmerged one visible before a reader follows stale instructions.
|
|
78
|
+
- Nothing fetches anything. A pack cannot be pulled by name, cannot be published by this
|
|
79
|
+
tool, and cannot be refreshed by `upgrade`. That is the deliberate half of the answer,
|
|
80
|
+
and it leaves the fetch as a later ruling rather than a missing feature.
|
|
81
|
+
- The rule provenance digest does not cover pack bytes, so `projection.simtDevSha` still
|
|
82
|
+
answers "what did the tool render" and the pack digest answers "which bytes were these".
|
|
83
|
+
Two answers to two questions, both in the manifest.
|
|
84
|
+
|
|
85
|
+
## Rejected
|
|
86
|
+
|
|
87
|
+
- **Fetching a pack from a remote during projection.** It makes the rendered bytes depend
|
|
88
|
+
on something the receiving repository does not carry, and it puts the network inside a
|
|
89
|
+
command whose defining property is that it is deterministic and offline.
|
|
90
|
+
- **Refusing to project a pack whose digest does not match.** The remedy would delete the
|
|
91
|
+
rules it reports, so the tool would cause the damage it exists to report.
|
|
92
|
+
- **One flag per pack state**, so a reader could choose what blocks the gate. Three flags
|
|
93
|
+
for one directory's worth of state is a configuration surface nobody will read twice, and
|
|
94
|
+
the division that matters — does the region account for its rules — has one defensible
|
|
95
|
+
answer.
|
|
96
|
+
- **A pack carrying the skill registry**, which proposal P4 asked for before ADR-061
|
|
97
|
+
settled where the registry comes from.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# ADR-066 — A monorepo projects once per package
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S41
|
|
4
|
+
**Affects:** `init`, `sync`, `check`, `compare`, `hooks run`, `src/monorepo.mjs`,
|
|
5
|
+
`src/projection/rules.mjs`, `src/projection/project.mjs`, `src/hooks/dispatcher.mjs`,
|
|
6
|
+
the `gates.cwd` manifest field
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
A monorepo received exactly one projection: the root's `AGENTS.md`, fingerprinted from
|
|
11
|
+
the root. Everything below the root was invisible to the detector, so a React
|
|
12
|
+
application inside a node workspace was handed the node profile and nothing else, while
|
|
13
|
+
a Workers service beside it was handed the same. Neither the repository's rules nor the
|
|
14
|
+
tool's output said another package existed with different rules.
|
|
15
|
+
|
|
16
|
+
The estate's own monorepos made this expensive rather than merely wrong. Fourteen
|
|
17
|
+
repositories hold between them hundreds of packages, and one rule set per repository
|
|
18
|
+
means every package is told the union of what the root can see and nothing about what it
|
|
19
|
+
is. The profile that should have applied to a package is applied by nobody, so the rule
|
|
20
|
+
that package needed was simply missing — not wrong, missing.
|
|
21
|
+
|
|
22
|
+
Three shapes of answer were available, and the cheapest is the one that was not taken.
|
|
23
|
+
Writing every package's full profile set into the root duplicates text the reader loads
|
|
24
|
+
once per turn (review 0002 S38 measured that duplication at 72% of the workspace's
|
|
25
|
+
always-on bytes). Writing the root's set into each package does the same and worse.
|
|
26
|
+
Projecting the root once and each package once, with each package carrying only what the
|
|
27
|
+
root does not, adds bytes proportional to the number of packages that actually differ,
|
|
28
|
+
and nothing at all to the ones that do not.
|
|
29
|
+
|
|
30
|
+
The harder half is that `check` is the gate. A projection the tool writes but does not
|
|
31
|
+
check is a projection nobody is held to, and a monorepo that drifts in one package would
|
|
32
|
+
report `current` while CI is green and the reader is wrong.
|
|
33
|
+
|
|
34
|
+
## Rulings
|
|
35
|
+
|
|
36
|
+
1. **A workspace is read from its declarations, never from a directory scan.** npm
|
|
37
|
+
workspaces, pnpm's package list, turbo's workspace list, Cargo members, a go.work use
|
|
38
|
+
list and Gradle includes are all read, because each one is a file that already states
|
|
39
|
+
which directories are members and why. Finding a marker file by walking the tree is
|
|
40
|
+
the alternative, and it is the one this repository's rules forbid: a heuristic that
|
|
41
|
+
cannot be explained by a declaration is a heuristic a reader has to reverse-engineer
|
|
42
|
+
from its output, and the only way to find out why a directory was or was not given
|
|
43
|
+
rules is to read this tool's source. A declaration this tool cannot read is a
|
|
44
|
+
directory it does not discover, which is an honest absence rather than a guess.
|
|
45
|
+
|
|
46
|
+
2. **The root keeps the shared rules; a package carries its delta.** The root is
|
|
47
|
+
fingerprinted as before and renders the core plus every profile the root's own signals
|
|
48
|
+
select. A package is fingerprinted the same way, and renders only the profiles the
|
|
49
|
+
root did not already select. A reader opening the package file is therefore never
|
|
50
|
+
shown a rule the root already gave them, and a reader of the root file is never shown
|
|
51
|
+
a rule that belongs to one package. The duplication the workspace reader pays for is
|
|
52
|
+
one sentence of orientation per package, and the bytes it pays for scale with the
|
|
53
|
+
packages that differ rather than with the repository count.
|
|
54
|
+
|
|
55
|
+
3. **An empty delta writes nothing.** Two node packages under one workspace select the
|
|
56
|
+
same profiles, so the delta is empty and there is no file. A file whose entire content
|
|
57
|
+
is a claim about rules it does not contain is always-on bytes spent on nothing, which
|
|
58
|
+
is the cost S38 exists to measure, and the reader is left looking for rules that are
|
|
59
|
+
not there.
|
|
60
|
+
|
|
61
|
+
4. **A gate names its directory in a map, not in a new shape for the command.** The
|
|
62
|
+
manifest's gate commands stay name-to-command-line strings, and an added map gives
|
|
63
|
+
each gate a directory to run in. Re-reading a command string to discover where it runs
|
|
64
|
+
would mean the answer to "where does anything run" lives inside a value whose purpose
|
|
65
|
+
is something else, and a manifest written by an earlier release would have to be
|
|
66
|
+
rewritten to gain the capability. A directory that does not exist, or that resolves
|
|
67
|
+
outside the repository, fails the event with a message naming which of the two things
|
|
68
|
+
is wrong, because they are repaired by different edits. Containment is checked on the
|
|
69
|
+
resolved path rather than on the text: a relative traversal and a symlink both pass a
|
|
70
|
+
prefix test applied to a string.
|
|
71
|
+
|
|
72
|
+
5. **`check` reports a bad directory before a commit, not at one.** The dispatcher fails
|
|
73
|
+
closed on the same condition, so a reader would otherwise find out by being unable to
|
|
74
|
+
commit — after writing a commit, which is the wrong order for the news. The same
|
|
75
|
+
applies to a directory entry naming a gate that is not declared at all: the entry is
|
|
76
|
+
inert, the reader believes the gate runs where it says, and nothing anywhere says
|
|
77
|
+
otherwise.
|
|
78
|
+
|
|
79
|
+
6. **`compare` counts findings, not repositories.** One monorepo with three drifted
|
|
80
|
+
packages is one repository with three findings. A summary that said one problem would
|
|
81
|
+
understate the work by a factor of three, and a reader comparing two runs would see a
|
|
82
|
+
single count that moves by three when the underlying state changed by three. The
|
|
83
|
+
repository still counts once among repositories with problems, because that count is
|
|
84
|
+
about how much work a person has.
|
|
85
|
+
|
|
86
|
+
## Consequences
|
|
87
|
+
|
|
88
|
+
- A workspace whose members are declared in a form this tool does not read gets the root
|
|
89
|
+
projection only, exactly as it did before. That is a gap to close by adding a row to
|
|
90
|
+
the declaration table, not a behaviour to document.
|
|
91
|
+
- Nested regions are checked, and their findings are printed with their paths. A reader
|
|
92
|
+
who edits a package file by hand is told which file, which is the same information the
|
|
93
|
+
root's own drift finding carries.
|
|
94
|
+
- The package file is a generated region like any other: the markers, the source list and
|
|
95
|
+
the repair command all behave identically, so there is no second kind of generated file
|
|
96
|
+
for a reader to learn.
|
|
97
|
+
|
|
98
|
+
## Rejected
|
|
99
|
+
|
|
100
|
+
- **Scanning for marker files instead of reading declarations.** Rejected for ruling 1.
|
|
101
|
+
A marker file answers "is there a build here", which is not the question; a declaration
|
|
102
|
+
answers "is this a member of the workspace", which is.
|
|
103
|
+
- **Folding every package's rules into the root region.** Rejected for ruling 2. It is
|
|
104
|
+
the smallest change to make and it makes every reader in the workspace carry every
|
|
105
|
+
package's rules, which is the cost S38 measured and S38's kernel exists to remove.
|
|
106
|
+
- **A directory field on each declared command.** Rejected for ruling 4, for the reason
|
|
107
|
+
recorded there: it puts where a gate runs inside the value that says what it runs, and
|
|
108
|
+
it makes an old manifest unreadable rather than merely older.
|
|
109
|
+
- **Skipping the directory check because the dispatcher already fails closed.**
|
|
110
|
+
Rejected for ruling 5. The dispatcher's refusal is correct but late; the reader who
|
|
111
|
+
learns about it from a failed commit has already done the work the commit would have
|
|
112
|
+
saved them.
|
|
113
|
+
- **Refusing to project a monorepo at all until every package is understood.** Rejected:
|
|
114
|
+
a root-only projection is a strictly smaller claim than it was, and refusing to improve
|
|
115
|
+
it because the improvement is incomplete makes the reader worse off than the tool.
|