@starci/hfs 4.7.26 → 4.7.28
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 +16 -0
- package/README.md +2 -3
- package/package.json +7 -1
- package/release/candidate-ci.mjs +3 -21
- package/runtime/engine/yaml.mjs +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +4 -4
- package/runtime/knowledge/hfs/rules.yaml +913 -637
- package/runtime/knowledge/hfs/slots.yaml +21 -14
- package/runtime/modules/kernel/failure-codes.yaml +10 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +130 -54
- package/runtime/scripts/api/git/check-ignore.mjs +5 -0
- package/runtime/scripts/gates/runtime-host.mjs +83 -0
- package/runtime/scripts/gates/sonar-gate.mjs +251 -0
- package/runtime/scripts/gates/starcistacks-ci.mjs +55 -0
- package/runtime/scripts/gates/starcistacks-form.mjs +26 -0
- package/runtime/scripts/gates/starcistacks-runtime.mjs +2 -0
- package/runtime/scripts/gates/starcistacks-value-file.mjs +17 -0
- package/runtime/scripts/gates/starcistacks.mjs +499 -0
- package/runtime/scripts/hfs/app-wiring.mjs +2 -2
- package/runtime/scripts/hfs/architecture/supabase.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +4 -1
- package/runtime/scripts/hfs/coverage-from.mjs +1 -1
- package/runtime/scripts/hfs/manifest-shape.mjs +1 -3
- package/runtime/scripts/hfs/rule-catalog.mjs +7 -10
- package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
- package/runtime/scripts/hfs/rules/assets.mjs +1 -1
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +24 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +2 -2
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
- package/runtime/scripts/lib/bypass-markers.mjs +116 -0
- package/runtime/scripts/lib/check-code-loosening.mjs +128 -0
- package/runtime/scripts/lib/example-refs.mjs +205 -0
- package/runtime/scripts/lib/failure-code-reads.mjs +100 -0
- package/runtime/scripts/lib/gate-loosening.mjs +52 -8
- package/runtime/scripts/lib/json-formats.mjs +34 -0
- package/runtime/scripts/lib/managed-json.mjs +1 -1
- package/runtime/scripts/lib/package-at.mjs +1 -1
- package/runtime/scripts/lib/properties.mjs +16 -0
- package/runtime/scripts/lib/provision-control-failure.mjs +32 -0
- package/runtime/scripts/lib/sonar-new-code.mjs +58 -0
- package/runtime/scripts/lib/starciwork-boundary.mjs +164 -0
- package/runtime/scripts/lib/work-edition.mjs +40 -0
- package/sync/format.mjs +1 -1
- package/sync/hygiene.mjs +16 -4
- package/templates/app/hooks/husky/pre-commit +8 -7
|
@@ -538,6 +538,7 @@ slots:
|
|
|
538
538
|
profiles: [app]
|
|
539
539
|
exemplifiedBy:
|
|
540
540
|
- packages/hfs/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml
|
|
541
|
+
- {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: full}
|
|
541
542
|
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages its Supabase database deploy workflow; full owns an optional provider-specific workflow."
|
|
542
543
|
# The database deploy workflow: workflow_dispatch only (like app.ci-e2e); starci app sync renders it under the managed ci group.
|
|
543
544
|
path: ".github/workflows/db-deploy.yml"
|
|
@@ -548,7 +549,6 @@ slots:
|
|
|
548
549
|
liteManagedBy: ci-workflows-lite
|
|
549
550
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
550
551
|
since: 2.1.0
|
|
551
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
552
552
|
- id: app.ci-stack-deploy
|
|
553
553
|
since: 3.3.0
|
|
554
554
|
profiles: [app]
|
|
@@ -687,7 +687,8 @@ slots:
|
|
|
687
687
|
tier: none
|
|
688
688
|
tests: none
|
|
689
689
|
rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
|
|
690
|
-
|
|
690
|
+
exemplifiedBy:
|
|
691
|
+
- {path: packages/hfs/src/hfs-slot-ownership.spec.mjs, edition: lite}
|
|
691
692
|
- id: app.sides
|
|
692
693
|
since: 3.1.0
|
|
693
694
|
profiles: [app]
|
|
@@ -810,6 +811,7 @@ slots:
|
|
|
810
811
|
profiles: [app]
|
|
811
812
|
exemplifiedBy:
|
|
812
813
|
- packages/hfs/templates/app/skeleton-lite/supabase/
|
|
814
|
+
- {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: full}
|
|
813
815
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase is required in lite; full enables it only through a declared connection."
|
|
814
816
|
# The one Supabase project directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
|
|
815
817
|
path: "supabase/"
|
|
@@ -820,11 +822,11 @@ slots:
|
|
|
820
822
|
tier: none
|
|
821
823
|
tests: none
|
|
822
824
|
since: 2.1.0
|
|
823
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
824
825
|
- id: app.supabase.config
|
|
825
826
|
profiles: [app]
|
|
826
827
|
exemplifiedBy:
|
|
827
828
|
- packages/hfs/templates/app/skeleton-lite/supabase/config.toml
|
|
829
|
+
- {path: packages/hfs/src/db-config-policy.spec.mjs, edition: full}
|
|
828
830
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires Supabase configuration; full requires it only when Supabase is declared."
|
|
829
831
|
# The project configuration: auth keys are env(NAME) references, never literals (L08 judges its policy keys).
|
|
830
832
|
path: "supabase/config.toml"
|
|
@@ -835,11 +837,11 @@ slots:
|
|
|
835
837
|
tier: none
|
|
836
838
|
tests: none
|
|
837
839
|
since: 2.1.0
|
|
838
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
839
840
|
- id: app.supabase.migrations
|
|
840
841
|
profiles: [app]
|
|
841
842
|
exemplifiedBy:
|
|
842
843
|
- packages/hfs/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql
|
|
844
|
+
- {path: packages/hfs/src/db-migration-shape.spec.mjs, edition: full}
|
|
843
845
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite uses Supabase schema authority and requires a migration; full opts into that provider."
|
|
844
846
|
# The schema authority of an app on Supabase: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql,
|
|
845
847
|
# one file per change, immutable once on origin/main (L02-L08 judge them).
|
|
@@ -852,7 +854,6 @@ slots:
|
|
|
852
854
|
tier: none
|
|
853
855
|
tests: none
|
|
854
856
|
since: 2.1.0
|
|
855
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
856
857
|
- id: app.supabase.migrations.archive
|
|
857
858
|
profiles: [app]
|
|
858
859
|
# The migrations an existing app retired at its cutover (owner ruling 2026-10-13, migration baseline policy): the live schema is
|
|
@@ -888,7 +889,8 @@ slots:
|
|
|
888
889
|
tier: none
|
|
889
890
|
tests: none
|
|
890
891
|
since: 2.1.0
|
|
891
|
-
|
|
892
|
+
exemplifiedBy:
|
|
893
|
+
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, edition: full}
|
|
892
894
|
- id: app.supabase.templates
|
|
893
895
|
profiles: [app]
|
|
894
896
|
path: "supabase/templates/*.html"
|
|
@@ -1025,7 +1027,8 @@ slots:
|
|
|
1025
1027
|
tests: none
|
|
1026
1028
|
coverage: none
|
|
1027
1029
|
rules: [HFS_UNUSED_SYMBOL]
|
|
1028
|
-
|
|
1030
|
+
exemplifiedBy:
|
|
1031
|
+
- {path: packages/hfs/src/hfs-unused-symbol-ledger.spec.mjs, editions: [lite, full]}
|
|
1029
1032
|
- id: repo.packages
|
|
1030
1033
|
since: 3.1.0
|
|
1031
1034
|
profiles: [be, fe]
|
|
@@ -1183,8 +1186,9 @@ slots:
|
|
|
1183
1186
|
tests: none
|
|
1184
1187
|
coverage: none
|
|
1185
1188
|
why: the MCP tool catalog an app's MCP server publishes at one contract major (`<server>@<major>`), the `tools/list` result its clients rely on, committed as a cross-repo interface; the canon emits no MCP catalog, so its shape is judged (R302 HFS_MCP_TOOLS_SNAPSHOT)
|
|
1186
|
-
exampleNa: "owner:mcp-tools-snapshot-no-example (2026-10-21): no example app serves an MCP server; the package spec judges the shape"
|
|
1187
1189
|
rules: [HFS_MCP_TOOLS_SNAPSHOT]
|
|
1190
|
+
exemplifiedBy:
|
|
1191
|
+
- {path: packages/hfs/src/mcp-tools-snapshot.spec.mjs, editions: [lite, full]}
|
|
1188
1192
|
|
|
1189
1193
|
# ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
|
|
1190
1194
|
# The files of every app (owner ruling 2026-10-14, R245 and R246, since 3.1.0), each with a line budget that
|
|
@@ -1449,7 +1453,8 @@ slots:
|
|
|
1449
1453
|
coverage: none
|
|
1450
1454
|
rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
|
|
1451
1455
|
since: 1.0.0
|
|
1452
|
-
|
|
1456
|
+
exemplifiedBy:
|
|
1457
|
+
- {path: packages/hfs/src/hfs-stacks-swarm.spec.mjs, edition: lite}
|
|
1453
1458
|
- id: be.feature.realtime.graphql
|
|
1454
1459
|
profiles: [be]
|
|
1455
1460
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite uses only the in-process socket.io gateway; the GraphQL subscription door remains full-only."
|
|
@@ -1653,7 +1658,8 @@ slots:
|
|
|
1653
1658
|
coverage: required
|
|
1654
1659
|
budget: {indexExports: 60}
|
|
1655
1660
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_INFRA_OWNER]
|
|
1656
|
-
|
|
1661
|
+
exemplifiedBy:
|
|
1662
|
+
- {path: packages/eslint/be/webhook-handoff-inbox.spec.mjs, edition: lite}
|
|
1657
1663
|
- id: be.platform.queue
|
|
1658
1664
|
since: 3.1.0
|
|
1659
1665
|
profiles: [be]
|
|
@@ -1792,7 +1798,8 @@ slots:
|
|
|
1792
1798
|
tests: unit-beside # a measured slot: <name>.projection.spec.ts beside its projection is legal (the file owes 100 per file; a spec of its own is optional)
|
|
1793
1799
|
coverage: required
|
|
1794
1800
|
rules: [BE_PROJECTION_WRITE_OWNER, BE_PROJECTION_SHAPE, BE_TIER_DIRECTION, BE_CONTEXT_COUPLING]
|
|
1795
|
-
|
|
1801
|
+
exemplifiedBy:
|
|
1802
|
+
- {path: packages/eslint/be/jobs-projections-coverage.spec.mjs, edition: lite}
|
|
1796
1803
|
- id: be.integrations
|
|
1797
1804
|
since: 3.1.0
|
|
1798
1805
|
profiles: [be]
|
|
@@ -1822,6 +1829,7 @@ slots:
|
|
|
1822
1829
|
profiles: [be]
|
|
1823
1830
|
exemplifiedBy:
|
|
1824
1831
|
- packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/
|
|
1832
|
+
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, edition: full}
|
|
1825
1833
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires the Supabase Auth and Storage integration; full opts in through a connection."
|
|
1826
1834
|
# The only be import of @supabase/* (L10): the Auth admin and Storage admin client, plus the JWKS-based verification of
|
|
1827
1835
|
# the Supabase access token the guard chain reads (L16). The connection itself is ordinary Postgres (the shared
|
|
@@ -1839,7 +1847,6 @@ slots:
|
|
|
1839
1847
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
|
|
1840
1848
|
coverage: required
|
|
1841
1849
|
since: 2.1.0
|
|
1842
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
1843
1850
|
|
|
1844
1851
|
# ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
|
|
1845
1852
|
# lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
|
|
@@ -1872,7 +1879,6 @@ slots:
|
|
|
1872
1879
|
overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
|
|
1873
1880
|
call migrate/runMigrations/synchronize, or write process.env.
|
|
1874
1881
|
rules: [BE_TEST_TOPOLOGY, BE_TEST_CONSTRAINTS_ON]
|
|
1875
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
1876
1882
|
|
|
1877
1883
|
- id: be.tests.world.kit
|
|
1878
1884
|
since: 3.1.0
|
|
@@ -2269,7 +2275,8 @@ slots:
|
|
|
2269
2275
|
tests: none
|
|
2270
2276
|
why: the canonical GraphQL client output of codegen is local and never committed; this specific slot overrides the tracked API parent
|
|
2271
2277
|
rules: [FE_WIRE_GENERATED]
|
|
2272
|
-
|
|
2278
|
+
exemplifiedBy:
|
|
2279
|
+
- {path: packages/eslint/fe/r52-wire-generated.spec.mjs, editions: [lite, full]}
|
|
2273
2280
|
- id: fe.transport.client
|
|
2274
2281
|
since: 3.1.0
|
|
2275
2282
|
profiles: [fe]
|
|
@@ -3044,6 +3044,16 @@ HFS_AUDIT_STEP_MISSING:
|
|
|
3044
3044
|
owner: op-retry
|
|
3045
3045
|
kind: check-finding
|
|
3046
3046
|
|
|
3047
|
+
HFS_BYPASS_MARKER:
|
|
3048
|
+
title: "A product repository holds no bypass of a check"
|
|
3049
|
+
title_vi: "Kho có đoạn bỏ qua một bước kiểm tra"
|
|
3050
|
+
meaning_vi: "Kho có đoạn làm cho một bước kiểm tra qua mà không thỏa nó ở `<f>`: chú thích tắt luật (eslint, stylelint, prettier, biome), `ts-ignore`/`ts-expect-error`/`ts-nocheck`, `NOSONAR`, bỏ qua độ phủ istanbul/c8/v8, bài test bị bỏ qua hoặc chỉ chạy riêng, hoặc lỗi bị nuốt trong hook, script hay workflow (`|| true`, `continue-on-error`, `set +e`, `HUSKY=0`, `--no-verify`, `fail_ci_if_error: false`)."
|
|
3051
|
+
causes_vi:
|
|
3052
|
+
- "Vi phạm luật R303: một lần bỏ qua kiểm tra luôn là lỗi (phán quyết của chủ sở hữu loosening-always-red); kiểm tra được thỏa bằng cách sửa nguyên nhân, không phải bỏ qua. Giao diện (FE) không có test nhưng mã nguồn vẫn bị soát các chú thích tắt luật."
|
|
3053
|
+
nextStep_vi: "Op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra: xóa đoạn bỏ qua và sửa nguyên nhân thật (mã, kiểu dữ liệu, test, bước chạy)."
|
|
3054
|
+
owner: op-retry
|
|
3055
|
+
kind: check-finding
|
|
3056
|
+
|
|
3047
3057
|
HFS_CANON_COPY_DRIFT:
|
|
3048
3058
|
title: "A hash-sensitive canon file differs from its template"
|
|
3049
3059
|
title_vi: "Tệp canon nhạy hash lệch khỏi template"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GENERATED by scripts/cli/gen-catalog.mjs — do not edit
|
|
2
2
|
export const CATALOG = {
|
|
3
3
|
"schema": "starci/cli-catalog@1",
|
|
4
|
-
"hash": "
|
|
4
|
+
"hash": "466ffd40fe4a003c976ac306bcb14639b1b57c947112e7899ef717a819e571f5",
|
|
5
5
|
"global": [
|
|
6
6
|
{
|
|
7
7
|
"name": "json",
|
|
@@ -1248,7 +1248,7 @@ export const CATALOG = {
|
|
|
1248
1248
|
"requireCi true: push --execute sends HEAD to refs/heads/release/candidate-<version> first and waits for its ci.yml run",
|
|
1249
1249
|
"green moves the branch and the tag in one atomic push; red refuses RELEASE_CANDIDATE_CI_RED naming the failing jobs",
|
|
1250
1250
|
"no run, or no end in time, refuses RELEASE_CANDIDATE_CI_TIMEOUT; unreadable GitHub refuses RELEASE_CI_UNKNOWN; nothing reaches the branch",
|
|
1251
|
-
"
|
|
1251
|
+
"a red candidate never passes: there is no override and no --no-ci-check flag",
|
|
1252
1252
|
"gate refuses a dirty tree, HEAD other than --source, a moved HEAD and a tree dirtied by the gates; record and tag only when all gates are green",
|
|
1253
1253
|
"a gate with summary jest is red on failed tests and on a coverage threshold miss even when the process exits 0",
|
|
1254
1254
|
"push needs the annotated tag and L4 record of HEAD, runs git push --dry-run --atomic first and never uses --no-verify"
|
|
@@ -8466,56 +8466,6 @@ export const CATALOG = {
|
|
|
8466
8466
|
},
|
|
8467
8467
|
"runtimeSide": "tree"
|
|
8468
8468
|
},
|
|
8469
|
-
"ci-override": {
|
|
8470
|
-
"summary": "the owner accepts a red candidate CI for one SHA (emergency path) and records who and why",
|
|
8471
|
-
"owner": "runtime",
|
|
8472
|
-
"flags": [
|
|
8473
|
-
{
|
|
8474
|
-
"name": "sha",
|
|
8475
|
-
"type": "string",
|
|
8476
|
-
"summary": "required full 40 lowercase hex SHA of the red CI run"
|
|
8477
|
-
},
|
|
8478
|
-
{
|
|
8479
|
-
"name": "reason",
|
|
8480
|
-
"type": "string",
|
|
8481
|
-
"summary": "required nonempty owner reason, recorded verbatim after trimming"
|
|
8482
|
-
},
|
|
8483
|
-
{
|
|
8484
|
-
"name": "repo",
|
|
8485
|
-
"type": "string",
|
|
8486
|
-
"summary": "repository receiving the override (default current directory)"
|
|
8487
|
-
}
|
|
8488
|
-
],
|
|
8489
|
-
"exit": {
|
|
8490
|
-
"0": "the override record and runtime owner ruling were written",
|
|
8491
|
-
"2": "RIGHTS_OWNER_ONLY (a bound role or non-TTY stdin), bad usage, confirmation mismatch, or write failure"
|
|
8492
|
-
},
|
|
8493
|
-
"json": "none",
|
|
8494
|
-
"examples": [
|
|
8495
|
-
"starci release ci-override --sha 0123456789abcdef0123456789abcdef01234567 --reason \"Owner accepts this CI failure for the release\"",
|
|
8496
|
-
"starci release ci-override --repo ../my-app --sha 0123456789abcdef0123456789abcdef01234567 --reason \"Owner accepts the failed run\""
|
|
8497
|
-
],
|
|
8498
|
-
"editions": [
|
|
8499
|
-
"full"
|
|
8500
|
-
],
|
|
8501
|
-
"impl": {
|
|
8502
|
-
"script": "scripts/supervisor/release-ci-override-cli.mjs"
|
|
8503
|
-
},
|
|
8504
|
-
"conventions": [
|
|
8505
|
-
"OWNER RULING candidate-ci (2026-10-18): a release reaches main only when the candidate CI of its own sha is green;",
|
|
8506
|
-
"this verb is the one documented EMERGENCY path for a red candidate.",
|
|
8507
|
-
"The override is owner-only, records who and why, and agents cannot run it (OWNER RULING alpha20-ci-override-owner-only).",
|
|
8508
|
-
"Every bound role is refused by the command guard and by this script. The owner must use a terminal and type the first 12 SHA characters exactly.",
|
|
8509
|
-
"Writes <git-common-dir>/starci-release/<sha>.ci-override.json, schema starci/ci-override@1,",
|
|
8510
|
-
"with sha, reason, by (git user.name and OS user), at, and runUrl (empty if GitHub is unavailable).",
|
|
8511
|
-
"release cut and app release push read that record for the exact candidate sha only; the release record logs its use.",
|
|
8512
|
-
"For the runtime it also appends ci-override-<sha12> under rulings in modules/kernel/owner-rulings.yaml as the audit trail.",
|
|
8513
|
-
"App repositories receive only the record.",
|
|
8514
|
-
"An override applies only to its exact SHA. It never permits a release whose CI is unknown or timed out.",
|
|
8515
|
-
"Existing ruling IDs with another reason refuse."
|
|
8516
|
-
],
|
|
8517
|
-
"runtimeSide": "tree"
|
|
8518
|
-
},
|
|
8519
8469
|
"ci-status": {
|
|
8520
8470
|
"summary": "the verdict of the GitHub ci workflow, Codecov and Sonar for a release; red is recorded for Debug",
|
|
8521
8471
|
"owner": "runtime",
|
|
@@ -8662,8 +8612,7 @@ export const CATALOG = {
|
|
|
8662
8612
|
"No run within the grace, or no end within the wait, refuses RELEASE_CANDIDATE_CI_TIMEOUT.",
|
|
8663
8613
|
"A red candidate keeps its ref for diagnosis; after a cut the candidate ref is left on the remote, naming the released sha.",
|
|
8664
8614
|
"Only on green is the annotated tag created and main plus the tag pushed atomically; the release record logs the candidate verdict.",
|
|
8665
|
-
"
|
|
8666
|
-
"The release record logs its use."
|
|
8615
|
+
"There is no override: a red candidate never passes."
|
|
8667
8616
|
],
|
|
8668
8617
|
"runtimeSide": "tree"
|
|
8669
8618
|
},
|
|
@@ -8986,6 +8935,133 @@ export const CATALOG = {
|
|
|
8986
8935
|
"summary": "resolve runtime operations from structured routing inputs",
|
|
8987
8936
|
"owner": "runtime",
|
|
8988
8937
|
"verbs": {
|
|
8938
|
+
"model": {
|
|
8939
|
+
"summary": "resolve which tier members may take a workload from the declarative model-selection rules",
|
|
8940
|
+
"owner": "runtime",
|
|
8941
|
+
"flags": [
|
|
8942
|
+
{
|
|
8943
|
+
"name": "kind",
|
|
8944
|
+
"type": "string",
|
|
8945
|
+
"summary": "operation kind the workload runs"
|
|
8946
|
+
},
|
|
8947
|
+
{
|
|
8948
|
+
"name": "role",
|
|
8949
|
+
"type": "enum",
|
|
8950
|
+
"enum": [
|
|
8951
|
+
"implement",
|
|
8952
|
+
"verify",
|
|
8953
|
+
"decide",
|
|
8954
|
+
"plan",
|
|
8955
|
+
"write"
|
|
8956
|
+
],
|
|
8957
|
+
"summary": "role of the workload (default is the role of the kind in kinds.yaml)"
|
|
8958
|
+
},
|
|
8959
|
+
{
|
|
8960
|
+
"name": "domain",
|
|
8961
|
+
"type": "string",
|
|
8962
|
+
"summary": "domain of the workload"
|
|
8963
|
+
},
|
|
8964
|
+
{
|
|
8965
|
+
"name": "risk",
|
|
8966
|
+
"type": "enum",
|
|
8967
|
+
"enum": [
|
|
8968
|
+
"low",
|
|
8969
|
+
"medium",
|
|
8970
|
+
"high",
|
|
8971
|
+
"critical"
|
|
8972
|
+
],
|
|
8973
|
+
"summary": "risk of the workload"
|
|
8974
|
+
},
|
|
8975
|
+
{
|
|
8976
|
+
"name": "floor",
|
|
8977
|
+
"type": "enum",
|
|
8978
|
+
"enum": [
|
|
8979
|
+
"probation",
|
|
8980
|
+
"standard",
|
|
8981
|
+
"high",
|
|
8982
|
+
"critical"
|
|
8983
|
+
],
|
|
8984
|
+
"summary": "lowest qualification a pick may carry"
|
|
8985
|
+
},
|
|
8986
|
+
{
|
|
8987
|
+
"name": "tools",
|
|
8988
|
+
"type": "list",
|
|
8989
|
+
"summary": "tool the workload needs; comma list or repeatable"
|
|
8990
|
+
},
|
|
8991
|
+
{
|
|
8992
|
+
"name": "context-tokens",
|
|
8993
|
+
"type": "string",
|
|
8994
|
+
"summary": "context size in tokens the workload needs"
|
|
8995
|
+
},
|
|
8996
|
+
{
|
|
8997
|
+
"name": "external",
|
|
8998
|
+
"type": "boolean",
|
|
8999
|
+
"summary": "the workload has external effects and probation is forbidden"
|
|
9000
|
+
},
|
|
9001
|
+
{
|
|
9002
|
+
"name": "unapproved",
|
|
9003
|
+
"type": "boolean",
|
|
9004
|
+
"summary": "the workflow is not approved and probation is forbidden"
|
|
9005
|
+
},
|
|
9006
|
+
{
|
|
9007
|
+
"name": "no-review",
|
|
9008
|
+
"type": "boolean",
|
|
9009
|
+
"summary": "no fresh independent review is planned"
|
|
9010
|
+
},
|
|
9011
|
+
{
|
|
9012
|
+
"name": "no-checks",
|
|
9013
|
+
"type": "boolean",
|
|
9014
|
+
"summary": "the op declares no machine checks"
|
|
9015
|
+
},
|
|
9016
|
+
{
|
|
9017
|
+
"name": "difficulty",
|
|
9018
|
+
"type": "enum",
|
|
9019
|
+
"enum": [
|
|
9020
|
+
"easy",
|
|
9021
|
+
"medium",
|
|
9022
|
+
"hard",
|
|
9023
|
+
"insane"
|
|
9024
|
+
],
|
|
9025
|
+
"summary": "measured difficulty (default medium, required with --plan)"
|
|
9026
|
+
},
|
|
9027
|
+
{
|
|
9028
|
+
"name": "plan",
|
|
9029
|
+
"type": "boolean",
|
|
9030
|
+
"summary": "what-if view that walks the tier chain and annotates missing or stale qualification evidence instead of refusing"
|
|
9031
|
+
},
|
|
9032
|
+
{
|
|
9033
|
+
"name": "repo",
|
|
9034
|
+
"type": "string",
|
|
9035
|
+
"summary": "repository whose ledger provider-health circuit marks a model unavailable"
|
|
9036
|
+
},
|
|
9037
|
+
{
|
|
9038
|
+
"name": "models-dir",
|
|
9039
|
+
"type": "string",
|
|
9040
|
+
"summary": "models directory (default is modules/models)"
|
|
9041
|
+
},
|
|
9042
|
+
{
|
|
9043
|
+
"name": "verbose",
|
|
9044
|
+
"type": "boolean",
|
|
9045
|
+
"summary": "print the per-candidate rejection reasons"
|
|
9046
|
+
}
|
|
9047
|
+
],
|
|
9048
|
+
"exit": {
|
|
9049
|
+
"0": "a member is picked",
|
|
9050
|
+
"1": "no eligible model",
|
|
9051
|
+
"2": "bad usage"
|
|
9052
|
+
},
|
|
9053
|
+
"json": "flag",
|
|
9054
|
+
"examples": [
|
|
9055
|
+
"starci route model --kind interface.draw --plan --json"
|
|
9056
|
+
],
|
|
9057
|
+
"editions": [
|
|
9058
|
+
"full"
|
|
9059
|
+
],
|
|
9060
|
+
"impl": {
|
|
9061
|
+
"script": "scripts/route/route-model.mjs"
|
|
9062
|
+
},
|
|
9063
|
+
"runtimeSide": "host"
|
|
9064
|
+
},
|
|
8989
9065
|
"op": {
|
|
8990
9066
|
"summary": "resolve the operation that serves a structured routing request",
|
|
8991
9067
|
"owner": "runtime",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// check-ignore.mjs — `git check-ignore <args>`: whether paths are ignored (-q exits 0 for an ignored path).
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git check-ignore ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const checkIgnore = (args = [], options = {}) => runGit(['check-ignore', ...args], options);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// runtime-host.mjs - the repository that hosts this runtime and how a stack declaration's `repository` names resolve on
|
|
2
|
+
// this machine. One home for scripts/gates/starcistacks.mjs (the services contract) and scripts/gates/sonar-local.mjs
|
|
3
|
+
// (the Sonar helper), so both read a declaration's `{repository, path}` the same way.
|
|
4
|
+
//
|
|
5
|
+
// The runtime host is the repository whose checkout holds the runtime's MAIN checkout (`<host>/.claude`), found from git
|
|
6
|
+
// identity, never from the folder this runtime tree happens to sit in: a lane worktree of the runtime
|
|
7
|
+
// (<lanes root>/<lane>/<name>) resolves to the same host as the main checkout. STARCI_SOURCE_ROOT overrides it.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { skillRoot, starciSourceRoot } from '../../engine/runtime-root.mjs';
|
|
11
|
+
import { isDir } from '../lib/fs-kind.mjs';
|
|
12
|
+
import { repositoryHome, repositoryName } from '../hfs/repo-identity.mjs';
|
|
13
|
+
import { secretEnv } from '../../engine/secrets.mjs';
|
|
14
|
+
import { revParseQuery } from '../api/git/rev-parse-query.mjs';
|
|
15
|
+
import { gitOutputOf } from '../lib/git.mjs';
|
|
16
|
+
|
|
17
|
+
/** The repository hosting this runtime: STARCI_SOURCE_ROOT, else the folder holding the runtime's main checkout. */
|
|
18
|
+
export function runtimeHostRoot(env = process.env) {
|
|
19
|
+
if (env.STARCI_SOURCE_ROOT) return starciSourceRoot(env);
|
|
20
|
+
return path.dirname(repositoryHome(skillRoot));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The canonical runtime main root of a runtime checkout or lane (its secret.env lives there); throws when the root is not a verified Git top level. */
|
|
24
|
+
export function verifiedRuntimeMain(runtimeRoot, env) {
|
|
25
|
+
if (typeof runtimeRoot !== 'string' || !path.isAbsolute(runtimeRoot))
|
|
26
|
+
throw new TypeError('runtimeSecretEnv requires an absolute runtime root');
|
|
27
|
+
const stat = fs.lstatSync(runtimeRoot);
|
|
28
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('runtime root must be a regular directory');
|
|
29
|
+
const root = fs.realpathSync.native(runtimeRoot), marker = path.join(root, '.git');
|
|
30
|
+
let gitMarker;
|
|
31
|
+
try { gitMarker = fs.lstatSync(marker); } catch (error) {
|
|
32
|
+
if (error?.code === 'ENOENT') return root;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
if (gitMarker.isSymbolicLink() || (!gitMarker.isFile() && !gitMarker.isDirectory()))
|
|
36
|
+
throw new Error('runtime Git identity is unavailable');
|
|
37
|
+
const topLevel = cwd => fs.realpathSync.native(path.resolve(cwd,
|
|
38
|
+
gitOutputOf(revParseQuery(['--show-toplevel'], { cwd, env })).trim()));
|
|
39
|
+
if (!same(topLevel(root), root)) throw new Error('runtime must be its Git top level');
|
|
40
|
+
const main = fs.realpathSync.native(repositoryHome(root)), mainMarker = fs.lstatSync(path.join(main, '.git'));
|
|
41
|
+
if (!mainMarker.isDirectory() || mainMarker.isSymbolicLink() || !same(topLevel(main), main))
|
|
42
|
+
throw new Error('runtime main Git identity is unavailable');
|
|
43
|
+
return main;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load host-local credentials from this runtime's verified main tree, or its installed physical tree.
|
|
48
|
+
* A lane never reads its own alternate file. The actual environment wins; no process state is mutated.
|
|
49
|
+
*/
|
|
50
|
+
export function runtimeSecretEnv(env = process.env, runtimeRoot = skillRoot) {
|
|
51
|
+
return secretEnv(verifiedRuntimeMain(runtimeRoot, env), env);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const same = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The checkout of the repository a declaration names: the declaring repository itself, the runtime host, or a sibling
|
|
58
|
+
* checkout of either (by repository identity, so a worktree and its main checkout are one repository). Null when the
|
|
59
|
+
* repository is not checked out on this machine.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveDeclaredRepository(name, { fromRepo } = {}) {
|
|
62
|
+
const wanted = typeof name === 'string' && name.trim() ? name.trim() : null;
|
|
63
|
+
if (!wanted) return null;
|
|
64
|
+
const candidates = [...(fromRepo ? [path.resolve(fromRepo)] : []), runtimeHostRoot()];
|
|
65
|
+
for (const dir of candidates) if (same(repositoryName(dir), wanted) && isDir(dir)) return dir;
|
|
66
|
+
for (const dir of candidates) {
|
|
67
|
+
const sibling = path.join(path.dirname(repositoryHome(dir)), wanted);
|
|
68
|
+
if (isDir(sibling)) return sibling;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A custody path of a declared repository as a file on this machine. A path under `.claude/` of the runtime host is a member of
|
|
75
|
+
* the runtime tree in use (this checkout, a lane worktree included), the same rule a host-owned stack root `.claude/ext/<service>`
|
|
76
|
+
* follows; any other path is joined to the repository's checkout. A path with a `..` segment is refused (null).
|
|
77
|
+
*/
|
|
78
|
+
export function resolveCustodyFile(repo, rel) {
|
|
79
|
+
if (!repo || !rel || String(rel).split(/[\\/]/).includes('..')) return null;
|
|
80
|
+
const posix = String(rel).replaceAll('\\', '/');
|
|
81
|
+
if (posix.startsWith('.claude/') && same(path.resolve(repo), path.resolve(runtimeHostRoot()))) return path.join(skillRoot, posix.slice('.claude/'.length));
|
|
82
|
+
return path.join(repo, rel);
|
|
83
|
+
}
|