@starci/hfs 4.7.32 → 4.7.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +13 -6
- package/emit/contracts.mjs +4 -3
- package/emit/form-bounds-reader.ts.partial +58 -0
- package/emit/form-bounds.mjs +155 -0
- package/emit/graphql-input-bounds.mjs +97 -0
- package/emit/input-bounds.mjs +44 -0
- package/emit/operation-schemas.mjs +137 -0
- package/emit/operations-worker.mjs +2 -2
- package/emit/operations.mjs +52 -16
- package/emit/schema-worker.mjs +17 -4
- package/emit/type-schema.mjs +17 -5
- package/package.json +9 -2
- package/release/gate.mjs +18 -23
- package/release/index.mjs +1 -1
- package/release/push.mjs +4 -5
- package/runtime/engine/admission.mjs +331 -0
- package/runtime/engine/canonical-json.mjs +14 -0
- package/runtime/engine/db/blob.mjs +380 -0
- package/runtime/engine/db/event-compact.mjs +73 -0
- package/runtime/engine/db/ledger-open.mjs +38 -0
- package/runtime/engine/db/ledger-paths.mjs +87 -0
- package/runtime/engine/db/ledger.mjs +1068 -0
- package/runtime/engine/db/machine-connection.mjs +150 -0
- package/runtime/engine/db/machine-open.mjs +60 -0
- package/runtime/engine/db/machine-schema.mjs +105 -0
- package/runtime/engine/db/machine.mjs +1317 -0
- package/runtime/engine/db/provider-reservations.mjs +127 -0
- package/runtime/engine/db/provision-authority.mjs +76 -0
- package/runtime/engine/db/provision-lock.mjs +29 -0
- package/runtime/engine/db/provision-operations.mjs +146 -0
- package/runtime/engine/db/ref-value.mjs +67 -0
- package/runtime/engine/db/schema/machine.sql +920 -0
- package/runtime/engine/db/schema/runtime.sql +1113 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
- package/runtime/knowledge/hfs/design-truths.yaml +149 -0
- package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
- package/runtime/knowledge/hfs/rules.yaml +269 -48
- package/runtime/knowledge/hfs/slots.yaml +113 -53
- package/runtime/knowledge/patterns/be/api.yaml +46 -20
- package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
- package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
- package/runtime/knowledge/patterns/be/projections.yaml +2 -2
- package/runtime/knowledge/patterns/be/queues.yaml +57 -22
- package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
- package/runtime/knowledge/patterns/be/saga.yaml +2 -2
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +249 -0
- package/runtime/modules/kernel/failure-codes.yaml +211 -52
- package/runtime/modules/kernel/hook-input.yaml +6 -0
- package/runtime/modules/kernel/protected-zone.yaml +81 -0
- package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
- package/runtime/modules/kernel/roles.yaml +405 -0
- package/runtime/modules/models/registry.yaml +3 -1
- package/runtime/modules/models/runtimes.yaml +2 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
- package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
- package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
- package/runtime/scripts/agent/codex-app-server.mjs +79 -0
- package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
- package/runtime/scripts/agent/host-agents.mjs +80 -0
- package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
- package/runtime/scripts/agent/trust-launch.mjs +191 -0
- package/runtime/scripts/agent/trust.mjs +523 -0
- package/runtime/scripts/api/fs/rename-over.mjs +21 -0
- package/runtime/scripts/api/fs/temp-path.mjs +8 -0
- package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
- package/runtime/scripts/api/git/diff-names.mjs +8 -0
- package/runtime/scripts/api/git/head-time.mjs +10 -0
- package/runtime/scripts/api/git/index-lock.mjs +78 -0
- package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
- package/runtime/scripts/api/git/merge-tree.mjs +5 -0
- package/runtime/scripts/api/git/rev-list.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse.mjs +13 -0
- package/runtime/scripts/api/git/status-query.mjs +5 -0
- package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
- package/runtime/scripts/api/npm/lib.mjs +32 -0
- package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
- package/runtime/scripts/api/npm/run-npm.mjs +6 -0
- package/runtime/scripts/api/npm/view.mjs +30 -0
- package/runtime/scripts/api/npm/whoami.mjs +8 -0
- package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
- package/runtime/scripts/api/process/kill-tree.mjs +22 -0
- package/runtime/scripts/api/process/lib.mjs +125 -0
- package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
- package/runtime/scripts/api/process/process-list.mjs +28 -0
- package/runtime/scripts/api/process/system-tool.mjs +37 -0
- package/runtime/scripts/gates/canon-digest.mjs +81 -0
- package/runtime/scripts/gates/canon-pins.mjs +16 -0
- package/runtime/scripts/gates/commit-proof.mjs +53 -0
- package/runtime/scripts/gates/gate-input.mjs +98 -0
- package/runtime/scripts/gates/gate.mjs +669 -0
- package/runtime/scripts/gates/grammar-dist.mjs +206 -0
- package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
- package/runtime/scripts/gates/test-world-run.mjs +254 -0
- package/runtime/scripts/gates/type-impact.mjs +277 -0
- package/runtime/scripts/gates/type-program.mjs +113 -0
- package/runtime/scripts/guards/assigned-command.mjs +12 -0
- package/runtime/scripts/guards/call-rights.mjs +47 -0
- package/runtime/scripts/guards/command-guard.mjs +468 -0
- package/runtime/scripts/guards/command-policy.mjs +342 -0
- package/runtime/scripts/guards/critic-reach.mjs +82 -0
- package/runtime/scripts/guards/deps-guard.mjs +150 -0
- package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
- package/runtime/scripts/guards/git-pathspec.mjs +225 -0
- package/runtime/scripts/guards/git-policy.mjs +355 -0
- package/runtime/scripts/guards/guards-root.mjs +27 -0
- package/runtime/scripts/guards/hook-install.mjs +343 -0
- package/runtime/scripts/guards/hook-io.mjs +76 -0
- package/runtime/scripts/guards/hook-verdict.mjs +12 -0
- package/runtime/scripts/guards/install-verdict.mjs +41 -0
- package/runtime/scripts/guards/kernel-seat.mjs +63 -0
- package/runtime/scripts/guards/launch-verdict.mjs +74 -0
- package/runtime/scripts/guards/op-starci.mjs +16 -0
- package/runtime/scripts/guards/op-write-scope.mjs +27 -0
- package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
- package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
- package/runtime/scripts/guards/protected-zone.mjs +56 -0
- package/runtime/scripts/guards/refusals.mjs +27 -0
- package/runtime/scripts/guards/release-definition.mjs +185 -0
- package/runtime/scripts/guards/release-push-gate.mjs +72 -0
- package/runtime/scripts/guards/release-record.mjs +58 -0
- package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
- package/runtime/scripts/guards/rights.mjs +268 -0
- package/runtime/scripts/guards/seat-tools.mjs +47 -0
- package/runtime/scripts/guards/shell-commands.mjs +328 -0
- package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
- package/runtime/scripts/guards/verdict-process.mjs +81 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
- package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +6 -4
- package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
- package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
- package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
- package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
- package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
- package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
- package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
- package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
- package/runtime/scripts/hfs/rules/edition.mjs +56 -12
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
- package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
- package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
- package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
- package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
- package/runtime/scripts/hfs/sealed-members.mjs +3 -3
- package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
- package/runtime/scripts/hfs/slots.mjs +2 -1
- package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
- package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
- package/runtime/scripts/hfs/view.mjs +2 -1
- package/runtime/scripts/lib/app-data.mjs +11 -0
- package/runtime/scripts/lib/bypass-markers.mjs +272 -75
- package/runtime/scripts/lib/check-format.mjs +7 -0
- package/runtime/scripts/lib/cli-arg.mjs +120 -0
- package/runtime/scripts/lib/css-rules.mjs +220 -0
- package/runtime/scripts/lib/dom-tree.mjs +4 -0
- package/runtime/scripts/lib/escape.mjs +16 -0
- package/runtime/scripts/lib/event-items.mjs +22 -0
- package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
- package/runtime/scripts/lib/git-dir.mjs +120 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
- package/runtime/scripts/lib/guard-command.mjs +24 -0
- package/runtime/scripts/lib/hook-limits.mjs +5 -0
- package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
- package/runtime/scripts/lib/js-lex.mjs +200 -0
- package/runtime/scripts/lib/json-formats.mjs +0 -1
- package/runtime/scripts/lib/language.mjs +94 -1
- package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
- package/runtime/scripts/lib/loosening-config.mjs +361 -0
- package/runtime/scripts/lib/loosening-state.mjs +223 -0
- package/runtime/scripts/lib/menu-parts.mjs +11 -0
- package/runtime/scripts/lib/normalize.mjs +25 -0
- package/runtime/scripts/lib/op-gate.mjs +15 -0
- package/runtime/scripts/lib/redact.mjs +208 -0
- package/runtime/scripts/lib/runtime-location.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
- package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
- package/runtime/scripts/lib/seat-env.mjs +43 -0
- package/runtime/scripts/lib/secret-patterns.mjs +58 -9
- package/runtime/scripts/lib/shell-source.mjs +4 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -0
- package/runtime/scripts/lib/swarm-stack.mjs +2 -1
- package/runtime/scripts/lib/tar-files.mjs +39 -0
- package/runtime/scripts/lib/ts-analysis.mjs +10 -0
- package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
- package/runtime/scripts/lib/worktree-record.mjs +14 -0
- package/runtime/scripts/machine/artifact-hold.mjs +52 -0
- package/runtime/scripts/machine/ask-receipts.mjs +113 -0
- package/runtime/scripts/machine/evidence-store.mjs +223 -0
- package/runtime/scripts/machine/home.mjs +216 -0
- package/runtime/scripts/machine/host-lock.mjs +184 -0
- package/runtime/scripts/machine/lock-recovery.mjs +55 -0
- package/runtime/scripts/machine/roles-contract.mjs +90 -0
- package/runtime/scripts/machine/runtime-change.mjs +20 -0
- package/runtime/scripts/machine/workflow-tree.mjs +122 -0
- package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
- package/runtime/scripts/reconciler/guard-row.mjs +25 -0
- package/runtime/scripts/work/asset-slot.mjs +284 -0
- package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
- package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
- package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
- package/runtime/scripts/work/brand/brand.mjs +892 -0
- package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
- package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
- package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
- package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
- package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
- package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
- package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
- package/runtime/scripts/work/draw/draw-source.mjs +412 -0
- package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
- package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
- package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
- package/runtime/scripts/work/draw-grammar.mjs +175 -0
- package/runtime/scripts/work/draw-render-page.mjs +188 -0
- package/runtime/scripts/work/draw-render.mjs +626 -0
- package/runtime/scripts/work/grammar-proposal.mjs +279 -0
- package/runtime/scripts/work/png.mjs +237 -0
- package/runtime/scripts/work/render-tools.mjs +64 -0
- package/runtime/scripts/work/work-io.mjs +119 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-plan.mjs +40 -13
- package/scaffold/add-queue-lite.mjs +372 -0
- package/scaffold/add.mjs +5 -2
- package/scaffold/app.mjs +1 -2
- package/scaffold/edition-gate.mjs +11 -7
- package/scaffold/lite-exports.mjs +10 -0
- package/scaffold/pattern-file-slots.mjs +33 -0
- package/scaffold/test-world.mjs +1 -1
- package/scaffold/yaml-block-edit.mjs +117 -0
- package/src/main.mjs +5 -4
- package/stack/deploy.mjs +7 -3
- package/stack/post-deploy.mjs +48 -0
- package/sync/index.mjs +10 -0
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/codegen-graphql.mjs.partial +59 -12
- package/templates/app/hooks/husky/pre-commit +2 -2
- package/templates/app/hooks/husky/pre-push +10 -3
- package/templates/app/package-scripts/redis-dev.json +3 -0
- package/templates/app/package-scripts-lite/package.json +1 -0
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
- package/templates/app/skeleton/README.md +2 -0
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton-lite/README.md +1 -1
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
- package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
- package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
- package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/inbox/platform/migration.sql.tpl +1 -0
- package/templates/be/inbox/service.spec.ts.tpl +4 -4
- package/templates/be/inbox/service.ts.tpl +3 -1
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
- package/templates/be/inbox/webhook-door.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
- package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
- package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
- package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
- package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
- package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
- package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
- package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
- package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
- package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
- package/upgrade/check.mjs +12 -135
- package/upgrade/file-state.mjs +1 -16
- package/upgrade/index.mjs +34 -361
- package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
- package/runtime/scripts/lib/gate-loosening.mjs +0 -292
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
- package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
- package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// grammar-vendor-parity.mjs - GRAMMAR_VENDOR_PARITY: a Grammar component that wraps HeroUI keeps HeroUI's anatomy and defaults.
|
|
2
|
+
//
|
|
3
|
+
// Grammar is a wrapper of HeroUI. The vendor reference (knowledge/vendor/heroui/<dir>.yaml, measured from the installed @heroui packages by
|
|
4
|
+
// scripts/work/ui/vendor-heroui.mjs) states what HeroUI draws; the wrapper manifest (knowledge/vendor/heroui/wrappers.yaml) states what each
|
|
5
|
+
// Grammar file wraps, which parts it hands to its caller (`delegates`) and which vendor slots it restyles on purpose (`overrides`, empty by
|
|
6
|
+
// default, pinned by an owner ruling: scripts/checks/check-grammar-vendor-parity.mjs). This library is pure: the caller hands the parsed
|
|
7
|
+
// reference, the parsed manifest, the Grammar sources and stylesheets.
|
|
8
|
+
//
|
|
9
|
+
// Two layers. The UNIVERSAL layer binds every Grammar built on HeroUI: use the HeroUI component for each role (no hand-rolled menu, list
|
|
10
|
+
// box, dialog or switch: `role`), keep its anatomy and parts (`anatomy`, `undeclared`). The PER-GRAMMAR layer is what a Grammar declares in the
|
|
11
|
+
// manifest (`grammars: [{id, paths, vendor: {name: heroui, parity}}]`, never inferred from its code): `strict` keeps HeroUI's default CSS and
|
|
12
|
+
// arrangement (`slot`, `style`, `default`, `stale`; the pinned overrides are the only deviations), `custom` may re-skin and re-arrange
|
|
13
|
+
// (tokens, radius, padding, colour, where the indicator sits, separators, icon size) and is judged by the universal layer only.
|
|
14
|
+
// Grammar StarCi is strict.
|
|
15
|
+
//
|
|
16
|
+
// A finding is red when
|
|
17
|
+
// anatomy a wrapper renders a part the vendor does not have, omits a part the vendor requires, or omits the vendor's embedded
|
|
18
|
+
// divider (`Separator`) while it renders a `Section`;
|
|
19
|
+
// undeclared a Grammar file wraps a HeroUI component its manifest row does not declare (or the row names a file that is gone);
|
|
20
|
+
// slot Grammar CSS targets a vendor class or `data-slot` no override declares;
|
|
21
|
+
// style (rows with `styleParity: true`) Grammar CSS restyles a paint family (padding, radius, colour, size, ...) of a vendor part
|
|
22
|
+
// through a class or `data-grammar-*` stamp that wrapper puts on that part, and no override declares it (accessibility
|
|
23
|
+
// media - forced colors, the touch target floor - is not judged);
|
|
24
|
+
// default a wrapper passes a vendor part a prop value other than the vendor default (the dot indicator in place of the check);
|
|
25
|
+
// role a Grammar file hand-rolls an ARIA role HeroUI already has a component for (unless a pinned `role` override declares it);
|
|
26
|
+
// grammar a Grammar file belongs to no declared Grammar, or a declaration is malformed;
|
|
27
|
+
// stale an override nothing needs any more.
|
|
28
|
+
// A StarCi canon decision that is not drift (the glyph size of the icon rule) is declared as a `canon` override: justified, pinned, and it
|
|
29
|
+
// permits nothing. Family skins (`offset-pop`, `heritage`) are brand directions that re-skin HeroUI on purpose and are not judged; the
|
|
30
|
+
// base look (`common`, `core`) is.
|
|
31
|
+
import { createHash } from 'node:crypto';
|
|
32
|
+
import { cssRules, ruleFamilies, selectorAttributes, selectorClasses, subjectCompound } from './css-rules.mjs';
|
|
33
|
+
import { byCodeUnit } from './list.mjs';
|
|
34
|
+
|
|
35
|
+
export const CODE = 'GRAMMAR_VENDOR_PARITY';
|
|
36
|
+
export const WRAPPERS_SCHEMA = 'starci/vendor-heroui-wrappers@1';
|
|
37
|
+
|
|
38
|
+
/** The sha256 of a pinned file's text, line endings normalised. */
|
|
39
|
+
export const manifestDigest = (text) => createHash('sha256').update(String(text ?? '').replaceAll('\r\n', '\n')).digest('hex');
|
|
40
|
+
|
|
41
|
+
const finding = (rule, file, line, message) => ({ code: CODE, rule, file, line, message: `${rule}: ${message}` });
|
|
42
|
+
const lineOf = (text, index) => text.slice(0, index).split('\n').length;
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Reading JSX without a parser: the opening tags of the aliases a file imports from @heroui/react.
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/** The index after the quoted string that starts at `start`. */
|
|
49
|
+
function skipQuoted(text, start) {
|
|
50
|
+
const quote = text[start];
|
|
51
|
+
let index = start + 1;
|
|
52
|
+
while (index < text.length && text[index] !== quote) index += text[index] === '\\' ? 2 : 1;
|
|
53
|
+
return index + 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The index after the template literal that starts at `start`, `${...}` substitutions included. */
|
|
57
|
+
function skipTemplate(text, start) {
|
|
58
|
+
let index = start + 1;
|
|
59
|
+
while (index < text.length && text[index] !== '`') {
|
|
60
|
+
if (text[index] === '\\') index += 2;
|
|
61
|
+
else if (text[index] === '$' && text[index + 1] === '{') index = skipBraces(text, index + 1);
|
|
62
|
+
else index += 1;
|
|
63
|
+
}
|
|
64
|
+
return index + 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The index after the balanced `{...}` that starts at `start`. */
|
|
68
|
+
function skipBraces(text, start) {
|
|
69
|
+
let depth = 0;
|
|
70
|
+
let index = start;
|
|
71
|
+
while (index < text.length) {
|
|
72
|
+
const char = text[index];
|
|
73
|
+
if (char === '"' || char === "'") index = skipQuoted(text, index);
|
|
74
|
+
else if (char === '`') index = skipTemplate(text, index);
|
|
75
|
+
else {
|
|
76
|
+
if (char === '{') depth += 1;
|
|
77
|
+
if (char === '}') depth -= 1;
|
|
78
|
+
index += 1;
|
|
79
|
+
if (depth === 0 && char === '}') return index;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return index;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The index after the value (a string, a template or a braced expression) that starts at `start`. */
|
|
86
|
+
function skipValue(text, start) {
|
|
87
|
+
const char = text[start];
|
|
88
|
+
if (char === '"' || char === "'") return skipQuoted(text, start);
|
|
89
|
+
if (char === '`') return skipTemplate(text, start);
|
|
90
|
+
return char === '{' ? skipBraces(text, start) : start + 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Attributes of the opening tag whose name ended at `from`: `{name, raw}` (a spread is named `...`). */
|
|
94
|
+
function readAttributes(text, from) {
|
|
95
|
+
const attrs = [];
|
|
96
|
+
let index = from;
|
|
97
|
+
while (index < text.length) {
|
|
98
|
+
while (/\s/.test(text[index] ?? '')) index += 1;
|
|
99
|
+
if (text[index] === '>' || text.startsWith('/>', index)) break;
|
|
100
|
+
if (text[index] === '{') {
|
|
101
|
+
const end = skipBraces(text, index);
|
|
102
|
+
attrs.push({ name: '...', raw: text.slice(index + 1, end - 1) });
|
|
103
|
+
index = end;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const name = /^[\w:-]+/.exec(text.slice(index, index + 80))?.[0];
|
|
107
|
+
if (!name) break;
|
|
108
|
+
index += name.length;
|
|
109
|
+
if (text[index] !== '=') {
|
|
110
|
+
attrs.push({ name, raw: 'true' });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
index += 1;
|
|
114
|
+
const end = skipValue(text, index);
|
|
115
|
+
const raw = text.slice(index, end);
|
|
116
|
+
attrs.push({ name, raw: raw.startsWith('{') ? raw.slice(1, -1) : raw });
|
|
117
|
+
index = end;
|
|
118
|
+
}
|
|
119
|
+
return attrs;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The opening tags of the given import aliases: `{alias, part, attrs, line}` (`part` is `Root` for the bare alias). */
|
|
123
|
+
export function heroTags(text, aliases) {
|
|
124
|
+
if (!aliases.length) return [];
|
|
125
|
+
const pattern = new RegExp(String.raw`<(${aliases.join('|')})(?:\.(\w+))?(?=[\s/>])`, 'g');
|
|
126
|
+
return [...text.matchAll(pattern)].map((match) => ({
|
|
127
|
+
alias: match[1],
|
|
128
|
+
part: match[2] ?? 'Root',
|
|
129
|
+
attrs: readAttributes(text, match.index + match[0].length),
|
|
130
|
+
line: lineOf(text, match.index),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The string literals of an expression, without their quotes. */
|
|
135
|
+
const stringLiterals = (raw) => (String(raw).match(/"[^"\n]*"|'[^'\n]*'|`[^`$]*`/g) ?? []).map((literal) => literal.slice(1, -1));
|
|
136
|
+
|
|
137
|
+
/** The class tokens an element carries: the literals of its className, with a bare constant resolved one level in the file. */
|
|
138
|
+
function classTokens(text, attrs) {
|
|
139
|
+
const raw = attrs.find((attr) => attr.name === 'className')?.raw;
|
|
140
|
+
if (raw === undefined) return [];
|
|
141
|
+
const names = String(raw).match(/\b[A-Za-z_]\w*\b/g) ?? [];
|
|
142
|
+
const resolved = names.flatMap((name) => {
|
|
143
|
+
const declaration = new RegExp(String.raw`\bconst\s+${name}\s*=\s*([^\n]*(?:\n[^\n;]*)?)`).exec(text);
|
|
144
|
+
return declaration ? stringLiterals(declaration[1]) : [];
|
|
145
|
+
});
|
|
146
|
+
return [...stringLiterals(raw), ...resolved].flatMap((value) => value.split(/\s+/)).filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// The judgment.
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
/** Accessibility adaptations of the user's input or display (forced colours, contrast, the touch target floor) are not restyles of the vendor look. */
|
|
154
|
+
const ACCESSIBILITY_MEDIA = /forced-colors|prefers-contrast|pointer:\s*coarse/;
|
|
155
|
+
const OVERRIDE_KINDS = ['slot', 'style', 'default', 'role', 'canon'];
|
|
156
|
+
const PARITY_MODES = ['strict', 'custom'];
|
|
157
|
+
/** The ARIA roles HeroUI has a component for: a Grammar file that writes one by hand has hand-rolled that component. */
|
|
158
|
+
const HEROUI_ROLES = new Set(['menu', 'menuitem', 'menuitemradio', 'menuitemcheckbox', 'listbox', 'option', 'combobox', 'dialog', 'alertdialog', 'tablist', 'tab', 'tabpanel', 'switch', 'slider', 'checkbox', 'radio', 'radiogroup']);
|
|
159
|
+
const overrideKey = (kind, target) => `${kind}:${target}`;
|
|
160
|
+
|
|
161
|
+
/** The vendor imports of a source: alias -> vendor component name. */
|
|
162
|
+
export function importAliases(text) {
|
|
163
|
+
const aliases = new Map();
|
|
164
|
+
for (const match of text.matchAll(/import\s*\{([^}]*)\}\s*from\s*["']@heroui\/react["']/g)) {
|
|
165
|
+
for (const entry of match[1].split(',').map((part) => part.trim()).filter(Boolean)) {
|
|
166
|
+
const found = /^(?!type\s)(\w+)(?:\s+as\s+(\w+))?$/.exec(entry);
|
|
167
|
+
if (found) aliases.set(found[2] ?? found[1], found[1]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return aliases;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The declared overrides as a ledger: `allowed(kind, target)` answers and remembers the use, so an override nothing needs is found stale.
|
|
175
|
+
* A `canon` override records a StarCi canon decision that is not vendor drift (the glyph size of the icon rule) for the owner; it permits nothing.
|
|
176
|
+
*/
|
|
177
|
+
function declaredOverrides(wrappers) {
|
|
178
|
+
const declared = new Map();
|
|
179
|
+
const used = new Set();
|
|
180
|
+
const problems = [];
|
|
181
|
+
for (const row of wrappers) {
|
|
182
|
+
for (const entry of row.overrides ?? []) {
|
|
183
|
+
if (OVERRIDE_KINDS.includes(entry?.kind) && entry.target && entry.reason) declared.set(overrideKey(entry.kind, entry.target), row.file);
|
|
184
|
+
else problems.push(finding('override', row.file, 0, `an override names its kind (${OVERRIDE_KINDS.join(', ')}), its target and its reason`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const allowed = (kind, target) => {
|
|
188
|
+
const key = overrideKey(kind, target);
|
|
189
|
+
used.add(key);
|
|
190
|
+
return declared.has(key);
|
|
191
|
+
};
|
|
192
|
+
const stale = () => [...declared]
|
|
193
|
+
.filter(([key]) => !key.startsWith('canon:') && !used.has(key))
|
|
194
|
+
.map(([key, file]) => finding('stale', file, 0, `the override ${key} is declared but nothing needs it: remove it`));
|
|
195
|
+
return { allowed, stale, problems };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The Grammar a file belongs to (the longest declared path prefix), or undefined. */
|
|
199
|
+
const grammarOf = (file, grammars) => grammars
|
|
200
|
+
.flatMap((grammar) => (grammar.paths ?? []).filter((prefix) => file.startsWith(prefix)).map((prefix) => ({ grammar, length: prefix.length })))
|
|
201
|
+
.sort((a, b) => b.length - a.length)[0]?.grammar;
|
|
202
|
+
|
|
203
|
+
/** The malformed Grammar declarations and the files that belong to no declared Grammar. */
|
|
204
|
+
function grammarFindings(grammars, files) {
|
|
205
|
+
const found = grammars
|
|
206
|
+
.filter((grammar) => !grammar.id || !Array.isArray(grammar.paths) || grammar.vendor?.name !== 'heroui' || !PARITY_MODES.includes(grammar.vendor?.parity))
|
|
207
|
+
.map((grammar) => finding('grammar', 'knowledge/vendor/heroui/wrappers.yaml', 0, `the Grammar ${grammar.id ?? '?'} declares an id, its paths and vendor: {name: heroui, parity: ${PARITY_MODES.join(' | ')}}`));
|
|
208
|
+
return [...found, ...files.filter((file) => grammarOf(file, grammars) === undefined).map((file) => finding('grammar', file, 0, 'belongs to no declared Grammar: declare it in the grammars of the wrapper manifest'))];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The ARIA roles a file writes by hand in JSX (comments excluded) that HeroUI has a component for. */
|
|
212
|
+
function roleFindings({ file, text, allowed }) {
|
|
213
|
+
const code = text.replaceAll(/\/\*[\s\S]*?\*\//g, (comment) => comment.replaceAll(/[^\n]/g, ' ')).replaceAll(/\/\/[^\n]*/g, '');
|
|
214
|
+
const roles = [...code.matchAll(/\srole=["']([a-z]+)["']/g)].map((match) => match[1]).filter((role) => HEROUI_ROLES.has(role));
|
|
215
|
+
return [...new Set(roles)].filter((role) => !allowed('role', `${file}:${role}`)).map((role) => finding('role', file, 0, `hand-rolls role="${role}", which HeroUI has a component for: use it (override target role:${file}:${role})`));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The part findings of one wrapped component: parts that are not the vendor's, required parts left out, the divider between sections. */
|
|
219
|
+
function anatomyFindings({ file, name, reference, mine, row, tags, aliases }) {
|
|
220
|
+
const found = [];
|
|
221
|
+
for (const tag of mine) {
|
|
222
|
+
if (tag.part !== 'Root' && !reference.anatomy.parts.includes(tag.part)) {
|
|
223
|
+
found.push(finding('anatomy', file, tag.line, `<${tag.alias}.${tag.part}> is not a part of HeroUI ${name} (parts: ${reference.anatomy.parts.join(', ') || 'none'})`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const delegates = new Set(row.delegates?.[name] ?? []);
|
|
227
|
+
const rendered = new Set(mine.map((tag) => tag.part));
|
|
228
|
+
for (const part of reference.anatomy.required.filter((candidate) => !rendered.has(candidate) && !delegates.has(candidate))) {
|
|
229
|
+
found.push(finding('anatomy', file, 1, `omits ${name}.${part}, which HeroUI ${name} requires (render it, or declare it under delegates when the caller supplies it)`));
|
|
230
|
+
}
|
|
231
|
+
if (!rendered.has('Section')) return found;
|
|
232
|
+
for (const embed of reference.anatomy.embeds.filter((entry) => entry.slot === 'separator')) {
|
|
233
|
+
const separatorAliases = new Set([...aliases].filter(([, exported]) => exported === embed.component).map(([local]) => local));
|
|
234
|
+
if (!tags.some((tag) => separatorAliases.has(tag.alias))) {
|
|
235
|
+
found.push(finding('anatomy', file, 1, `renders ${name}.Section without HeroUI's ${embed.component} between sections (HeroUI styles its separator inside the menu)`));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return found;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The default findings: a prop of a part given a value of its closed set other than the vendor default. */
|
|
242
|
+
function defaultFindings({ file, name, reference, mine, allowed }) {
|
|
243
|
+
const found = [];
|
|
244
|
+
for (const tag of mine) {
|
|
245
|
+
const part = reference.parts[tag.part];
|
|
246
|
+
for (const attr of part ? tag.attrs : []) {
|
|
247
|
+
const closedSet = part.values?.[attr.name];
|
|
248
|
+
const fallback = part.defaults?.[attr.name];
|
|
249
|
+
if (!closedSet || fallback === undefined) continue;
|
|
250
|
+
const odd = stringLiterals(attr.raw).filter((value) => closedSet.includes(value) && value !== fallback);
|
|
251
|
+
const target = [name, tag.part, attr.name].join('.');
|
|
252
|
+
if (odd.length && !allowed('default', target)) {
|
|
253
|
+
found.push(finding('default', file, tag.line, `${name}.${tag.part} ${attr.name}=${odd.map((value) => JSON.stringify(value)).join('|')} replaces the vendor default "${fallback}" (override target default:${target})`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return found;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function wrapperFindings({ file, text, row, references, byName, allowed, strict }) {
|
|
261
|
+
const aliases = importAliases(text);
|
|
262
|
+
const components = [...new Set([...aliases.values()].filter((name) => byName.has(name)))].sort(byCodeUnit);
|
|
263
|
+
if (!row) return components.length ? [finding('undeclared', file, 1, `wraps ${components.join(', ')} but the wrapper manifest has no row for it`)] : [];
|
|
264
|
+
const found = [];
|
|
265
|
+
const declared = [...(row.wraps ?? [])].sort(byCodeUnit);
|
|
266
|
+
if (components.join(',') !== declared.join(',')) found.push(finding('undeclared', file, 1, `wraps ${components.join(', ') || 'nothing'} but its manifest row declares ${declared.join(', ') || 'nothing'}`));
|
|
267
|
+
const tags = heroTags(text, [...aliases.keys()]);
|
|
268
|
+
for (const name of components) {
|
|
269
|
+
const reference = references[byName.get(name)];
|
|
270
|
+
const locals = new Set([...aliases].filter(([, exported]) => exported === name).map(([local]) => local));
|
|
271
|
+
const mine = tags.filter((tag) => locals.has(tag.alias));
|
|
272
|
+
found.push(...anatomyFindings({ file, name, reference, mine, row, tags, aliases }), ...(strict ? defaultFindings({ file, name, reference, mine, allowed }) : []));
|
|
273
|
+
}
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** The stamps (class tokens and `data-grammar-*` names) each wrapped vendor part carries: stamp -> [{name, part, dir, file}]. */
|
|
278
|
+
function stampsOf(sources, rows, byName, references, strictOf) {
|
|
279
|
+
const stamps = new Map();
|
|
280
|
+
const add = (key, entry) => stamps.set(key, [...(stamps.get(key) ?? []), entry]);
|
|
281
|
+
for (const { file, text } of sources.filter((source) => rows.get(source.file)?.styleParity && strictOf(source.file))) {
|
|
282
|
+
const aliases = importAliases(text);
|
|
283
|
+
for (const tag of heroTags(text, [...aliases.keys()])) {
|
|
284
|
+
const name = aliases.get(tag.alias);
|
|
285
|
+
const dir = byName.get(name);
|
|
286
|
+
if (dir === undefined || !references[dir].parts[tag.part]) continue;
|
|
287
|
+
const entry = { name, part: tag.part, dir, file };
|
|
288
|
+
classTokens(text, tag.attrs).forEach((token) => add(`.${token}`, entry));
|
|
289
|
+
tag.attrs.filter((attr) => attr.name.startsWith('data-grammar-')).forEach((attr) => add(`[${attr.name}]`, entry));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return stamps;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** The vendor classes and data-slots Grammar CSS targets, as slot findings. */
|
|
296
|
+
function slotFindings({ file, selector, vendor, allowed }) {
|
|
297
|
+
const found = [];
|
|
298
|
+
for (const cls of selectorClasses(selector).filter((candidate) => vendor.classes.has(candidate) && !allowed('slot', `.${candidate}`))) {
|
|
299
|
+
found.push(finding('slot', file, 0, `${selector} targets the vendor class .${cls} (override target slot:.${cls})`));
|
|
300
|
+
}
|
|
301
|
+
for (const attribute of selectorAttributes(selector)) {
|
|
302
|
+
if (attribute.name === 'data-slot' && vendor.slots.has(attribute.value) && !allowed('slot', attribute.value)) {
|
|
303
|
+
found.push(finding('slot', file, 0, `${selector} targets the vendor data-slot "${attribute.value}" (override target slot:${attribute.value})`));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return found;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** The paint families Grammar CSS repaints on a vendor part through a stamp of the subject compound, as style findings. */
|
|
310
|
+
function styleFindings({ file, selector, families, stamps, references, allowed }) {
|
|
311
|
+
const found = [];
|
|
312
|
+
const subject = subjectCompound(selector);
|
|
313
|
+
const tokens = [...selectorClasses(subject).map((cls) => `.${cls}`), ...selectorAttributes(subject).map((attribute) => `[${attribute.name}]`)];
|
|
314
|
+
for (const token of tokens) {
|
|
315
|
+
for (const entry of stamps.get(token) ?? []) {
|
|
316
|
+
const paints = references[entry.dir].parts[entry.part].paints;
|
|
317
|
+
for (const family of families.filter((candidate) => paints.includes(candidate))) {
|
|
318
|
+
const target = `${entry.name}.${entry.part}:${family}`;
|
|
319
|
+
if (!allowed('style', target)) found.push(finding('style', file, 0, `${selector} restyles ${family} of HeroUI ${entry.name}.${entry.part}, which the vendor decides (stamp ${token} on ${entry.file}; override target style:${target})`));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return found;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function cssFindings({ styles, references, stamps, allowed, strictOf }) {
|
|
327
|
+
const vendor = {
|
|
328
|
+
classes: new Set(Object.values(references).flatMap((reference) => reference.css.classes)),
|
|
329
|
+
slots: new Set(Object.values(references).flatMap((reference) => reference.css.dataSlots)),
|
|
330
|
+
};
|
|
331
|
+
const found = [];
|
|
332
|
+
for (const { file, text } of styles.filter((sheet) => strictOf(sheet.file))) {
|
|
333
|
+
for (const rule of cssRules(text)) {
|
|
334
|
+
const families = rule.wraps.some((wrap) => ACCESSIBILITY_MEDIA.test(wrap)) ? [] : ruleFamilies(rule);
|
|
335
|
+
for (const selector of rule.selectors) {
|
|
336
|
+
found.push(...slotFindings({ file, selector, vendor, allowed }), ...(families.length ? styleFindings({ file, selector, families, stamps, references, allowed }) : []));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return found;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Every GRAMMAR_VENDOR_PARITY finding.
|
|
345
|
+
* references { <dir>: parsed vendor reference }
|
|
346
|
+
* manifest the parsed wrapper manifest ({schema, wrappers: [{file, wraps, delegates, styleParity, overrides}]})
|
|
347
|
+
* sources [{file, text}] Grammar .ts/.tsx (stories excluded), file relative to packages/grammar/src
|
|
348
|
+
* styles [{file, text}] every Grammar .css; a sheet is judged only when its Grammar declares `parity: strict`
|
|
349
|
+
*/
|
|
350
|
+
export function parityFindings({ references, manifest, sources, styles }) {
|
|
351
|
+
if (manifest?.schema !== WRAPPERS_SCHEMA) return [finding('manifest', 'knowledge/vendor/heroui/wrappers.yaml', 0, `schema must be ${WRAPPERS_SCHEMA}`)];
|
|
352
|
+
const wrappers = Array.isArray(manifest.wrappers) ? manifest.wrappers : [];
|
|
353
|
+
const byName = new Map(Object.entries(references).map(([dir, reference]) => [reference.component, dir]));
|
|
354
|
+
const rows = new Map(wrappers.map((row) => [row.file, row]));
|
|
355
|
+
const known = new Set(sources.map((source) => source.file));
|
|
356
|
+
const { allowed, stale, problems } = declaredOverrides(wrappers);
|
|
357
|
+
const grammars = Array.isArray(manifest.grammars) ? manifest.grammars : [];
|
|
358
|
+
const strictOf = (file) => grammarOf(file, grammars)?.vendor?.parity === 'strict';
|
|
359
|
+
return [
|
|
360
|
+
...grammarFindings(grammars, [...sources, ...styles].map((source) => source.file)),
|
|
361
|
+
...wrappers.filter((row) => !known.has(row.file)).map((row) => finding('undeclared', row.file, 0, 'the manifest names a file that does not exist')),
|
|
362
|
+
...problems,
|
|
363
|
+
...sources.flatMap(({ file, text }) => [
|
|
364
|
+
...wrapperFindings({ file, text, row: rows.get(file), references, byName, allowed, strict: strictOf(file) }),
|
|
365
|
+
...roleFindings({ file, text, allowed }),
|
|
366
|
+
]),
|
|
367
|
+
...cssFindings({ styles, references, stamps: stampsOf(sources, rows, byName, references, strictOf), allowed, strictOf }),
|
|
368
|
+
...stale(),
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Pin findings: the manifest differs from the digest the latest owner ruling that pins it records. */
|
|
373
|
+
export function pinFindings({ file, text, pinned }) {
|
|
374
|
+
if (pinned === undefined) return [finding('pin', file, 0, 'no owner ruling pins this manifest: an owner ruling records its sha256')];
|
|
375
|
+
const digest = manifestDigest(text);
|
|
376
|
+
return digest === pinned ? [] : [finding('pin', file, 0, `its digest ${digest.slice(0, 12)} is not the one the owner pinned (${String(pinned).slice(0, 12)}): only an owner ruling may add an override or change a row`)];
|
|
377
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// guard-command.mjs — the one spelling of the hook command that registers a StarCi guard verb with an agent host.
|
|
2
|
+
//
|
|
3
|
+
// A host chooses the shell that runs a hook. The linked runtime supplies the absolute CLI path shared by launch trust
|
|
4
|
+
// and preflight. Hook deadlines belong to the guard process; shell termination does not bound native children on Windows.
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { readRuntimeRecord } from './runtime-location.mjs';
|
|
8
|
+
|
|
9
|
+
const GUARD_COMMAND_END = /starci(?:\.mjs|\.cmd)?"? guard command$/;
|
|
10
|
+
const quotedPath = (file) => `"${String(file).replaceAll('\\', '/')}"`;
|
|
11
|
+
|
|
12
|
+
/** The command guard hook for the linked runtime; a missing or stale runtime record refuses registration. */
|
|
13
|
+
export function toolGuardCommand({ home, nodePath = process.execPath, cliPath = null } = {}) {
|
|
14
|
+
if (!cliPath) {
|
|
15
|
+
const skipped = [];
|
|
16
|
+
const installed = readRuntimeRecord({ home, skipped });
|
|
17
|
+
if (!installed) throw new Error(skipped.join('; ') || 'no linked runtime; run starci runtime link');
|
|
18
|
+
cliPath = path.join(installed.root, 'packages', 'cli', 'bin', 'starci.mjs');
|
|
19
|
+
}
|
|
20
|
+
return `${quotedPath(nodePath)} ${quotedPath(cliPath)} guard command`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Recognize the direct Node hook and launcher spellings so trust preparation replaces an older registered guard. */
|
|
24
|
+
export const isGuardCommand = (command) => GUARD_COMMAND_END.test(String(command ?? ''));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { parseYaml } from '../../engine/yaml.mjs';
|
|
3
|
+
|
|
4
|
+
/** The shared hook lifetime and input limits, read from their protected contract. */
|
|
5
|
+
export const HOOK_INPUT_LIMITS = Object.freeze(parseYaml(fs.readFileSync(new URL('../../modules/kernel/hook-input.yaml', import.meta.url), 'utf8')));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// interrupt-cleanup.mjs - the one owner of "what a long verb leaves behind when it is interrupted". A verb that creates something outside its own return path (a lock, a temp file,
|
|
2
|
+
// a container) registers the undo with `onInterrupt(undo)`; the undo runs when the process receives SIGINT, SIGTERM, SIGBREAK or SIGHUP (then the process exits with 128 + the signal number)
|
|
3
|
+
// and when the process exits while the registration is still open, and never twice. The returned disposer removes the registration once the verb released the thing itself.
|
|
4
|
+
// The process handlers are installed with the first registration and removed with the last. The registry takes the process as a seam (`proc`: on, off, exit).
|
|
5
|
+
const SIGNALS = Object.freeze({ SIGHUP: 1, SIGINT: 2, SIGTERM: 15, SIGBREAK: 21 });
|
|
6
|
+
|
|
7
|
+
/** A registry of undo functions bound to `proc`. */
|
|
8
|
+
function createInterruptRegistry({ proc = process } = {}) {
|
|
9
|
+
const undos = new Set();
|
|
10
|
+
const handlers = new Map();
|
|
11
|
+
|
|
12
|
+
const runAll = () => {
|
|
13
|
+
for (const undo of [...undos].reverse()) {
|
|
14
|
+
undos.delete(undo);
|
|
15
|
+
try { undo(); } catch { /* one failing undo never stops the others */ }
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const uninstall = () => {
|
|
19
|
+
for (const [event, handler] of handlers) proc.off(event, handler);
|
|
20
|
+
handlers.clear();
|
|
21
|
+
};
|
|
22
|
+
const install = () => {
|
|
23
|
+
if (handlers.size) return;
|
|
24
|
+
for (const [signal, number] of Object.entries(SIGNALS)) handlers.set(signal, () => { runAll(); uninstall(); proc.exit(128 + number); });
|
|
25
|
+
handlers.set('exit', runAll);
|
|
26
|
+
for (const [event, handler] of handlers) proc.on(event, handler);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Register `undo`; returns the disposer that removes it (and the process handlers when it was the last). */
|
|
30
|
+
const onInterrupt = (undo) => {
|
|
31
|
+
undos.add(undo);
|
|
32
|
+
install();
|
|
33
|
+
return () => { undos.delete(undo); if (!undos.size) uninstall(); };
|
|
34
|
+
};
|
|
35
|
+
return { onInterrupt, pending: () => undos.size };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const { onInterrupt } = createInterruptRegistry();
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// js-lex.mjs - reads a JavaScript, TypeScript or stylesheet source as three layers: its code, its comments and its string contents.
|
|
2
|
+
// A scanner that looks for a spelling in text (a bypass marker, a command in a string) needs to know which layer the spelling is in: a marker
|
|
3
|
+
// quoted inside a string or a template literal is data, a marker at the start of a comment is a directive, a marker in code is a call.
|
|
4
|
+
// One pass, no dependency, no AST: strings (single, double, template with nested `${}`), line and block comments and regular expression
|
|
5
|
+
// literals are told apart by the previous significant character; a `)` that closes an `if`, `while`, `for` or `with` head is followed by a
|
|
6
|
+
// statement, so a `/` after it opens a regular expression. A line ends at \n, \r, U+2028 and U+2029, as in JavaScript. A quote with no closing
|
|
7
|
+
// quote on its line is not a string (an apostrophe in JSX text or prose), so it cannot swallow a comment behind it.
|
|
8
|
+
const REGEX_AFTER = new Set(['', '(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>', '~', '^']);
|
|
9
|
+
const REGEX_KEYWORDS = new Set(['return', 'typeof', 'case', 'delete', 'void', 'throw', 'in', 'of', 'else', 'yield', 'await']);
|
|
10
|
+
const HEAD_KEYWORDS = new Set(['if', 'while', 'for', 'with']);
|
|
11
|
+
const LINE_SEPARATOR = String.fromCodePoint(0x2028);
|
|
12
|
+
const PARAGRAPH_SEPARATOR = String.fromCodePoint(0x2029);
|
|
13
|
+
const LINE_BREAKS = new Set(['\n', '\r', LINE_SEPARATOR, PARAGRAPH_SEPARATOR]);
|
|
14
|
+
const LINE_SPLIT = new RegExp(String.raw`\r\n|[\n\r${LINE_SEPARATOR}${PARAGRAPH_SEPARATOR}]`);
|
|
15
|
+
const isBreak = (char) => LINE_BREAKS.has(char);
|
|
16
|
+
|
|
17
|
+
/** What `kind` of source this is: `css` has block comments only, `scss` and `less` add line comments, anything else is script. */
|
|
18
|
+
export const langOf = (file) => {
|
|
19
|
+
if (file.endsWith('.css')) return 'css';
|
|
20
|
+
if (/\.(?:scss|less)$/.test(file)) return 'scss';
|
|
21
|
+
return 'script';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const NOT_A_BREAK = new RegExp(String.raw`[^\n\r${LINE_SEPARATOR}${PARAGRAPH_SEPARATOR}]`, 'g');
|
|
25
|
+
const blank = (text) => text.replace(NOT_A_BREAK, ' ');
|
|
26
|
+
|
|
27
|
+
/** The index right after the last non-blank character before `end` (a scan back over blanks only, so a long source stays linear). */
|
|
28
|
+
function contentEnd(code, end) {
|
|
29
|
+
let stop = end;
|
|
30
|
+
while (stop > 0 && /\s/.test(code[stop - 1])) stop -= 1;
|
|
31
|
+
return stop;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The word that ends at `end` of `code` (letters, digits, `$`, `_`). */
|
|
35
|
+
function wordBefore(code, end) {
|
|
36
|
+
let start = end;
|
|
37
|
+
while (start > 0 && /[\w$]/.test(code[start - 1])) start -= 1;
|
|
38
|
+
return code.slice(start, end);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class Reader {
|
|
42
|
+
constructor(text, lang) {
|
|
43
|
+
this.text = text;
|
|
44
|
+
this.lang = lang;
|
|
45
|
+
this.at = 0;
|
|
46
|
+
this.line = 1;
|
|
47
|
+
this.code = '';
|
|
48
|
+
this.comments = [];
|
|
49
|
+
this.strings = [];
|
|
50
|
+
this.lastSignificant = '';
|
|
51
|
+
this.heads = [];
|
|
52
|
+
this.headClosed = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Consumes `count` characters as non-code (blanked in `code`). */
|
|
56
|
+
skip(count) {
|
|
57
|
+
const piece = this.text.slice(this.at, this.at + count);
|
|
58
|
+
this.code += blank(piece);
|
|
59
|
+
this.advanceLines(piece);
|
|
60
|
+
this.at += count;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
advanceLines(piece) {
|
|
64
|
+
for (let index = 0; index < piece.length; index += 1) {
|
|
65
|
+
if (piece[index] === '\r' && piece[index + 1] === '\n') continue;
|
|
66
|
+
if (isBreak(piece[index])) this.line += 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Consumes one code character. */
|
|
71
|
+
take() {
|
|
72
|
+
const char = this.text[this.at];
|
|
73
|
+
this.code += char;
|
|
74
|
+
if (isBreak(char)) { if (!(char === '\r' && this.text[this.at + 1] === '\n')) this.line += 1; } else if (!/\s/.test(char)) this.trackParens(char);
|
|
75
|
+
this.at += 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Remembers which parenthesis closes the head of an if, while, for or with: a statement, and so possibly a regular expression, follows it. */
|
|
79
|
+
trackParens(char) {
|
|
80
|
+
this.headClosed = false;
|
|
81
|
+
if (char === '(') this.heads.push(HEAD_KEYWORDS.has(wordBefore(this.code, contentEnd(this.code, this.code.length - 1))));
|
|
82
|
+
else if (char === ')') this.headClosed = this.heads.pop() === true;
|
|
83
|
+
this.lastSignificant = char;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
regexAllowed() {
|
|
87
|
+
if (this.lastSignificant === ')') return this.headClosed;
|
|
88
|
+
if (!REGEX_AFTER.has(this.lastSignificant)) return REGEX_KEYWORDS.has(wordBefore(this.code, contentEnd(this.code, this.code.length)));
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lineEnd(from) {
|
|
93
|
+
let end = from;
|
|
94
|
+
while (end < this.text.length && !isBreak(this.text[end])) end += 1;
|
|
95
|
+
return end;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lineComment() {
|
|
99
|
+
const stop = this.lineEnd(this.at);
|
|
100
|
+
this.comments.push({ line: this.line, text: this.text.slice(this.at + 2, stop), first: true, last: true, block: false });
|
|
101
|
+
this.skip(stop - this.at);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
blockComment() {
|
|
105
|
+
const close = this.text.indexOf('*/', this.at + 2);
|
|
106
|
+
const stop = close < 0 ? this.text.length : close + 2;
|
|
107
|
+
const body = this.text.slice(this.at + 2, close < 0 ? stop : close);
|
|
108
|
+
const pieces = body.split(LINE_SPLIT);
|
|
109
|
+
pieces.forEach((piece, index) => this.comments.push({ line: this.line + index, text: piece, first: index === 0, last: index === pieces.length - 1, block: true }));
|
|
110
|
+
this.skip(stop - this.at);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
quoted(quote) {
|
|
114
|
+
let end = this.at + 1;
|
|
115
|
+
while (end < this.text.length && this.text[end] !== quote && !isBreak(this.text[end])) end += this.text[end] === '\\' ? 2 : 1;
|
|
116
|
+
if (this.text[end] !== quote) { this.take(); return; }
|
|
117
|
+
this.strings.push({ line: this.line, at: this.at + 1, text: this.text.slice(this.at + 1, end) });
|
|
118
|
+
this.skip(end + 1 - this.at);
|
|
119
|
+
this.lastSignificant = quote;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
regex() {
|
|
123
|
+
let end = this.at + 1;
|
|
124
|
+
let inClass = false;
|
|
125
|
+
while (end < this.text.length && !isBreak(this.text[end])) {
|
|
126
|
+
const char = this.text[end];
|
|
127
|
+
if (char === '\\') end += 1;
|
|
128
|
+
else if (char === '[') inClass = true;
|
|
129
|
+
else if (char === ']') inClass = false;
|
|
130
|
+
else if (char === '/' && !inClass) break;
|
|
131
|
+
end += 1;
|
|
132
|
+
}
|
|
133
|
+
if (this.text[end] !== '/') { this.take(); return; }
|
|
134
|
+
end += 1;
|
|
135
|
+
while (/[a-z]/i.test(this.text[end] ?? '')) end += 1;
|
|
136
|
+
this.skip(end - this.at);
|
|
137
|
+
this.lastSignificant = '/';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A template literal: the text pieces are strings, each `${ ... }` is code again. */
|
|
141
|
+
template() {
|
|
142
|
+
this.skip(1);
|
|
143
|
+
let piece = '';
|
|
144
|
+
let from = this.at;
|
|
145
|
+
const flush = () => { this.strings.push({ line: this.line, at: from, text: piece }); piece = ''; };
|
|
146
|
+
while (this.at < this.text.length && this.text[this.at] !== '`') {
|
|
147
|
+
if (this.text[this.at] === '\\') { piece += this.text.slice(this.at, this.at + 2); this.skip(2); continue; }
|
|
148
|
+
if (this.text[this.at] === '$' && this.text[this.at + 1] === '{') {
|
|
149
|
+
flush();
|
|
150
|
+
this.skip(2);
|
|
151
|
+
this.expression();
|
|
152
|
+
from = this.at;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
piece += this.text[this.at];
|
|
156
|
+
this.skip(1);
|
|
157
|
+
}
|
|
158
|
+
flush();
|
|
159
|
+
if (this.text[this.at] === '`') this.skip(1);
|
|
160
|
+
this.lastSignificant = '`';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Code up to the `}` that closes the current `${`. */
|
|
164
|
+
expression() {
|
|
165
|
+
let depth = 0;
|
|
166
|
+
while (this.at < this.text.length) {
|
|
167
|
+
const char = this.text[this.at];
|
|
168
|
+
if (char === '}' && depth === 0) { this.skip(1); return; }
|
|
169
|
+
if (char === '{') depth += 1;
|
|
170
|
+
else if (char === '}') depth -= 1;
|
|
171
|
+
this.step();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
step() {
|
|
176
|
+
const char = this.text[this.at];
|
|
177
|
+
const next = this.text[this.at + 1];
|
|
178
|
+
if (char === '/' && next === '*') this.blockComment();
|
|
179
|
+
else if (char === '/' && next === '/' && this.lang !== 'css') this.lineComment();
|
|
180
|
+
else if (char === '"' || char === "'") this.quoted(char);
|
|
181
|
+
else if (char === '`' && this.lang === 'script') this.template();
|
|
182
|
+
else if (char === '/' && this.lang === 'script' && this.regexAllowed()) this.regex();
|
|
183
|
+
else this.take();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
run() {
|
|
187
|
+
while (this.at < this.text.length) this.step();
|
|
188
|
+
return { code: this.code, comments: this.comments, strings: this.strings };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* `text` as { code, comments, strings }. `code` is the source with every comment, string and regular expression blanked (same length, same
|
|
194
|
+
* lines); `comments` are { line, text, first, last } per physical line of each comment (the text without its opener; `first` and `last`
|
|
195
|
+
* mark the ends of a block comment); `strings` are { line, at, text } per string or template piece, the content without its quotes and its offset.
|
|
196
|
+
*/
|
|
197
|
+
export const lexSource = (text, lang = 'script') => new Reader(String(text ?? ''), lang).run();
|
|
198
|
+
|
|
199
|
+
/** `text` as its physical lines (a line ends at \n, \r\n, \r, U+2028 and U+2029). */
|
|
200
|
+
export const splitLines = (text) => String(text ?? '').split(LINE_SPLIT);
|
|
@@ -26,7 +26,6 @@ export const JSON_FORMATS = Object.freeze([
|
|
|
26
26
|
{ id: 'package-scripts-fragment', tool: 'npm (scripts rendered into package.json)', match: (rel) => /^packages\/hfs\/templates\/app\/package-scripts[^/]*\/[^/]+\.json$/.test(rel) },
|
|
27
27
|
{ id: 'async-event-contract', tool: 'starci app emit', match: (rel) => /(^|\/)be\/contracts\/[^/]+\/events(\.pin)?\.json$/.test(rel) },
|
|
28
28
|
{ id: 'operation-contract', tool: 'starci app emit', match: (rel) => /(^|\/)be\/contracts\/[^/]+\/openapi\.json$/.test(rel) },
|
|
29
|
-
{ id: 'model-scorecard-snapshot', tool: 'scripts/agent/benchmark-snapshot.mjs', match: (rel) => /^benchmark\/snapshots\/[^/]+\.json$/.test(rel) },
|
|
30
29
|
]);
|
|
31
30
|
|
|
32
31
|
/** The declared format that fixes `rel`, or null: the file is authored JSON the check must refuse. */
|