@starci/hfs 4.0.9 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/LICENSE +21 -0
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +82 -8
- package/package.json +3 -2
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +187 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +247 -273
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +134 -0
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +18 -18
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +102 -69
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -1
- package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
- package/runtime/modules/models/registry.yaml +25 -397
- package/runtime/modules/models/runtimes.yaml +92 -262
- package/runtime/modules/models/tiers.yaml +65 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/lib.mjs +6 -0
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +36 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +52 -0
- package/runtime/scripts/api/process/run-program.mjs +8 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -16
- package/runtime/scripts/api/sops/lib.mjs +309 -11
- package/runtime/scripts/api/sops/seal.mjs +8 -4
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +3 -2
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +158 -102
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/check.mjs +61 -70
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +5 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +90 -45
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +72 -693
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/fs-kind.mjs +14 -4
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/mutation-fence.mjs +15 -0
- package/runtime/scripts/lib/path-key.mjs +42 -9
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +103 -2
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 4.1.0 - 2026-10-08
|
|
4
4
|
|
|
5
5
|
- Breaking (runtime alpha.4 CLI unification): remove the `hfs` bin and expose `main(argv, io)` for the sole `starci app ...` grammar; the app flags `--repo` and `--root` are replaced by the global `--cwd` flag.
|
|
6
6
|
- Changed (contract change `coverage-modules-logic`): the coverage scope is derived once from the slot manifest (`coverage: required|none` on every tracked be slot, `ruleParams.be.logicRoles`; `scripts/hfs/coverage-scope.mjs`). `hfs sync` renders it into `be/jest.config.js` (`starciJestConfig({ coverage })`), `sonar.coverage.exclusions` and `codecov.yml` (the measured roots, plus one Codecov component per service app and one `platform`, each at 100); `coverageScope`, `coverageExclusions` and `HFS_SYNC_COVERAGE_SCOPE` are gone. New R204 `HFS_COVERAGE_SCOPE_DRIFT`: a hand edit of any of the three coverage statements is drift. `hfs scaffold app` renders with the source it just wrote.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) StarCi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/emit/operations.mjs
CHANGED
|
@@ -70,7 +70,7 @@ export function readOperations({ ts, program, file }) {
|
|
|
70
70
|
const output = guard('output', member('output'));
|
|
71
71
|
const refusalType = member('refusal');
|
|
72
72
|
let refusal = [];
|
|
73
|
-
if (
|
|
73
|
+
if ((refusalType.flags & ts.TypeFlags.Never) === 0) {
|
|
74
74
|
const members = refusalType.isUnion() ? refusalType.types : [refusalType];
|
|
75
75
|
if (!members.every((each) => each.isStringLiteral())) fail(`refusal must be a closed union of string literals, found ${checker.typeToString(refusalType)}`);
|
|
76
76
|
refusal = members.map((each) => each.value).sort();
|
package/emit/type-schema.mjs
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* object type with no members and no index signature, and a union with `undefined` where a member could not simply be absent.
|
|
8
8
|
* Pure: takes the TypeScript module and a checker.
|
|
9
9
|
*/
|
|
10
|
+
import { byCodeUnit } from '../report/order.mjs';
|
|
10
11
|
|
|
11
12
|
/** Sorts object keys at every depth, so equal schemas print equal text. */
|
|
12
13
|
export function stable(value) {
|
|
13
14
|
if (Array.isArray(value)) return value.map(stable);
|
|
14
|
-
if (value && typeof value === 'object') return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
15
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.keys(value).sort(byCodeUnit).map((key) => [key, stable(value[key])]));
|
|
15
16
|
return value;
|
|
16
17
|
}
|
|
17
18
|
|
package/lint/run.mjs
CHANGED
|
@@ -19,10 +19,12 @@ import fs from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { createRequire } from 'node:module';
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
import { linterReport, mergeReports, sonarReport, sourceRootsOf } from '../report/sonar.mjs';
|
|
24
24
|
import { SIDES, appRelativeMessages, loadSlotManifest, readRepoDeclaration } from '../runtime/scripts/hfs/slots.mjs';
|
|
25
25
|
import { STYLE_GLOB } from '../sync/index.mjs';
|
|
26
|
+
import { byCodeUnit } from '../report/order.mjs';
|
|
27
|
+
import { braceVariants, globExpression } from '../runtime/scripts/lib/glob.mjs';
|
|
26
28
|
|
|
27
29
|
export const LINT_SCHEMA = 'starci/lint@1';
|
|
28
30
|
/** The side whose stylesheets stylelint judges. */
|
|
@@ -75,12 +77,14 @@ function runLinter({ cwd, pkg, bin, args, bound = null }) {
|
|
|
75
77
|
return { error: `${pkg} is not installed for ${cwd}` };
|
|
76
78
|
}
|
|
77
79
|
const run = spawnSync(process.execPath, [...(bound ? linterBoundArgs() : []), entry, ...args], { cwd, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024, ...(bound ? { env: { ...process.env, [BOUND_ENV]: bound } } : {}) });
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
+
// ESLint prints its report on stdout; stylelint uses stderr. JSON does not prove a completed child: only exits 0/1
|
|
81
|
+
// without a spawn error or signal are measurable, and exit 1 must carry findings.
|
|
80
82
|
let results;
|
|
81
83
|
try { results = JSON.parse(run.stdout.trim() || run.stderr); } catch { return { error: `${pkg} produced no json report in ${path.basename(cwd)}/ (exit ${run.status}): ${String(run.stderr || run.stdout).trim().split('\n')[0]}` }; }
|
|
82
84
|
if (!Array.isArray(results)) return { error: `${pkg} produced a report that is not a result list` };
|
|
83
|
-
|
|
85
|
+
const completed = !run.error && !run.signal && [0, 1].includes(run.status)
|
|
86
|
+
&& (run.status === 0 || results.some((result) => result?.messages?.length || result?.warnings?.length));
|
|
87
|
+
return { results, ...(completed ? {} : { error: `${pkg} could not complete in ${path.basename(cwd)}/ (exit ${run.status}, signal ${run.signal ?? 'none'}): ${String(run.error?.message || run.stderr || run.stdout || '').trim().split('\n')[0]}` }) };
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
const eslintFindings = (results, appRoot) => results.flatMap((result) => (result.messages ?? []).map((message) => ({
|
|
@@ -98,6 +102,69 @@ const byLocation = (a, b) => `${a.path ?? ''}:${String(a.line ?? 0).padStart(7,
|
|
|
98
102
|
/** The files of `changed` (app-relative) below `side`/, relative to the side folder. */
|
|
99
103
|
const onSide = (changed, side) => changed.filter((file) => file.startsWith(`${side}/`)).map((file) => file.slice(side.length + 1));
|
|
100
104
|
|
|
105
|
+
const fileKey = (file) => process.platform === 'win32' ? path.resolve(file).toLowerCase() : path.resolve(file);
|
|
106
|
+
const inside = (root, file) => { const rel = path.relative(root, file); return rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel); };
|
|
107
|
+
|
|
108
|
+
/** Read the installed canon's source selectors, then enumerate that scope; unavailable or unsupported selectors cannot prove coverage. */
|
|
109
|
+
async function sourceFiles({ repoRoot, cwd, side, scope }) {
|
|
110
|
+
const entry = createRequire(path.join(cwd, 'package.json')).resolve(`@starci/eslint-canon-${side}`);
|
|
111
|
+
if (!inside(fs.realpathSync(repoRoot), fs.realpathSync(entry))) throw new Error('the installed canon is outside the app root');
|
|
112
|
+
const canon = await import(pathToFileURL(entry).href);
|
|
113
|
+
const factory = side === 'be' ? canon.starciBeConfig : canon.starciFeConfig;
|
|
114
|
+
if (typeof factory !== 'function' || typeof canon.loadHfs !== 'function') throw new Error('the installed canon has no supported config factory');
|
|
115
|
+
const config = await factory({ hfs: canon.loadHfs(pathToFileURL(path.join(cwd, 'eslint.config.mjs')).href) });
|
|
116
|
+
if (!Array.isArray(config)) throw new Error('the installed canon did not return a flat config');
|
|
117
|
+
const selectors = [], ignored = [];
|
|
118
|
+
for (const block of config) {
|
|
119
|
+
if (!block || typeof block !== 'object') throw new Error('the installed canon returned an invalid config block');
|
|
120
|
+
for (const [field, target] of [['files', selectors], ['ignores', ignored]]) {
|
|
121
|
+
if (block[field] === undefined) continue;
|
|
122
|
+
if (!Array.isArray(block[field]) || block[field].some((glob) => typeof glob !== 'string' || glob.startsWith('!'))) throw new Error(`unsupported canon ${field} selectors`);
|
|
123
|
+
if (field === 'ignores' && Object.keys(block).some((key) => !['ignores', 'name'].includes(key))) throw new Error('unsupported non-global canon ignores');
|
|
124
|
+
target.push(...block[field].flatMap((glob) => braceVariants(glob).map(globExpression)));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!selectors.length) throw new Error('the installed canon has no source selectors');
|
|
128
|
+
const matches = (patterns, file) => patterns.some((pattern) => pattern.test(file));
|
|
129
|
+
const files = [];
|
|
130
|
+
const visit = (folder) => {
|
|
131
|
+
for (const child of fs.readdirSync(path.join(cwd, folder), { withFileTypes: true })) {
|
|
132
|
+
const file = posix(path.join(folder, child.name));
|
|
133
|
+
// Ask the canon's global ignore patterns before entering output or installed-package trees.
|
|
134
|
+
if (matches(ignored, file) || matches(ignored, `${file}/__starci_scope_probe__`)) continue;
|
|
135
|
+
if (child.isSymbolicLink()) throw new Error(`cannot enumerate symlink ${file}`);
|
|
136
|
+
if (child.isDirectory()) visit(file);
|
|
137
|
+
else if (child.isFile() && matches(selectors, file)) files.push(file);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
visit(scope ?? '');
|
|
141
|
+
return files.sort(byCodeUnit);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Every requested source needs an explicit, well-formed result; suppressed findings and foreign paths cannot witness a completed lint. */
|
|
145
|
+
function checkedEslintResults({ results, expected, cwd, scope, changed }) {
|
|
146
|
+
const required = new Set(expected.map((file) => fileKey(path.join(cwd, file))));
|
|
147
|
+
const seen = new Set(), valid = [], errors = [];
|
|
148
|
+
for (const result of results) {
|
|
149
|
+
if (!result || typeof result.filePath !== 'string' || !path.isAbsolute(result.filePath) || !Array.isArray(result.messages)
|
|
150
|
+
|| result.messages.some((message) => !message || typeof message.message !== 'string')
|
|
151
|
+
|| (result.suppressedMessages !== undefined && !Array.isArray(result.suppressedMessages))) {
|
|
152
|
+
errors.push('eslint produced a malformed file result'); continue;
|
|
153
|
+
}
|
|
154
|
+
const key = fileKey(result.filePath);
|
|
155
|
+
if (!inside(cwd, result.filePath) || (scope && !inside(path.join(cwd, scope), result.filePath)) || (changed && !required.has(key))) {
|
|
156
|
+
errors.push(`eslint produced a result outside the requested scope: ${result.filePath}`); continue;
|
|
157
|
+
}
|
|
158
|
+
if (seen.has(key)) errors.push(`eslint produced duplicate results for ${result.filePath}`);
|
|
159
|
+
seen.add(key);
|
|
160
|
+
if (result.suppressedMessages?.length) errors.push(`eslint suppressed findings for ${result.filePath}`);
|
|
161
|
+
valid.push(result);
|
|
162
|
+
}
|
|
163
|
+
const missing = expected.filter((file) => !seen.has(fileKey(path.join(cwd, file))));
|
|
164
|
+
if (missing.length) errors.push(`eslint produced no result for requested source files: ${missing.join(', ')}`);
|
|
165
|
+
return { results: valid, errors };
|
|
166
|
+
}
|
|
167
|
+
|
|
101
168
|
/**
|
|
102
169
|
* Run the whole lint of the app at `repoRoot`. `hfsCheck(repoRoot)` returns the `starci app check` result (`{ findings, tracked }`); the CLI
|
|
103
170
|
* injects it. Returns `{ report, sonar, exit }`; `sonar` is the merged Generic Issue Import document.
|
|
@@ -130,9 +197,16 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
|
|
|
130
197
|
if (workspace !== null && side !== STYLE_SIDE) { engines.eslint.sides[side] = { files: 0, skipped: `outside --workspace ${workspace}` }; continue; }
|
|
131
198
|
const sources = onSide(existing, side).filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file));
|
|
132
199
|
if (changed !== null && !sources.length) { engines.eslint.sides[side] = { files: 0, skipped: 'no changed source file' }; continue; }
|
|
133
|
-
const
|
|
200
|
+
const cwd = path.join(repoRoot, side);
|
|
201
|
+
let expected;
|
|
202
|
+
try { expected = changed ? sources : await sourceFiles({ repoRoot, cwd, side, scope: onFe }); }
|
|
203
|
+
catch (error) { errors.push(`eslint source scope is unavailable in ${side}/: ${String(error?.message ?? error)}`); engines.eslint.sides[side] = { files: 0 }; continue; }
|
|
204
|
+
const linted = runLinter({ cwd, pkg: 'eslint', bin: 'eslint', args: ['--format', 'json', ...(opts.fix ? ['--fix'] : []), ...(changed ? sources : [onFe ?? '.'])], bound: repoRoot });
|
|
134
205
|
if (linted.error) errors.push(linted.error);
|
|
135
|
-
|
|
206
|
+
if (linted.results) {
|
|
207
|
+
const checked = checkedEslintResults({ results: linted.results, expected, cwd, scope: onFe, changed: changed !== null });
|
|
208
|
+
errors.push(...checked.errors);
|
|
209
|
+
linted.results = checked.results;
|
|
136
210
|
// The side canon names side-relative paths in its messages; the report names every path from the app root.
|
|
137
211
|
const appRelative = appRelativeMessages(side, path.join(repoRoot, side));
|
|
138
212
|
for (const result of linted.results) for (const message of result.messages ?? []) message.message = appRelative(message.message);
|
|
@@ -148,7 +222,7 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
|
|
|
148
222
|
if (changed === null || styles.length) {
|
|
149
223
|
const linted = runLinter({ cwd: path.join(repoRoot, STYLE_SIDE), pkg: 'stylelint', bin: 'stylelint', args: [...(changed ? styles : [onFe ? `${onFe}/src/**/*.css` : STYLE_GLOB]), '--formatter', 'json', ...(opts.fix ? ['--fix'] : [])] });
|
|
150
224
|
if (linted.error) errors.push(linted.error);
|
|
151
|
-
|
|
225
|
+
if (linted.results) {
|
|
152
226
|
const appRelative = appRelativeMessages(STYLE_SIDE, path.join(repoRoot, STYLE_SIDE));
|
|
153
227
|
for (const result of linted.results) for (const warning of result.warnings ?? []) warning.text = appRelative(warning.text);
|
|
154
228
|
raw.stylelint = linted.results;
|
|
@@ -183,7 +257,7 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
|
|
|
183
257
|
linterReport('eslint', raw.eslint, { root: repoRoot, sourceRoots, tracked }),
|
|
184
258
|
linterReport('stylelint', raw.stylelint, { root: repoRoot, sourceRoots, tracked }),
|
|
185
259
|
]);
|
|
186
|
-
const report = { schema: LINT_SCHEMA, ok: findings.length === 0 && errors.length === 0, repoRoot, changed: changed ? [...changed].sort() : null, workspace, counts: { error: findings.length }, engines, errors, findings };
|
|
260
|
+
const report = { schema: LINT_SCHEMA, ok: findings.length === 0 && errors.length === 0, repoRoot, changed: changed ? [...changed].sort(byCodeUnit) : null, workspace, counts: { error: findings.length }, engines, errors, findings };
|
|
187
261
|
return { report, sonar, exit: errors.length ? 2 : findings.length ? 1 : 0 };
|
|
188
262
|
}
|
|
189
263
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/hfs",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/main.mjs",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"smol-toml": "1.9.0"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
+
"LICENSE",
|
|
18
19
|
"src/main.mjs",
|
|
19
20
|
"emit/**",
|
|
20
21
|
"lint/**",
|
package/report/order.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// order.mjs - the one code-unit comparator of @starci/hfs: the order Array.prototype.sort gives without a compare function (UTF-16 code units), named so the digests, emitted files and reports that depend on it stay byte-identical.
|
|
2
|
+
export const byCodeUnit = (a, b) => { const l = String(a), r = String(b); return l < r ? -1 : l > r ? 1 : 0; };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# config.example.yaml — seed for the per-project owner config.
|
|
2
|
+
# The installer copies this file to the gitignored `config.yaml` next to it;
|
|
3
|
+
# owners edit that copy. Everything below is read at runtime — no key here is
|
|
4
|
+
# decorative. Kernel route precedence: explicit `--agent` flag >
|
|
5
|
+
# `kernel` below > the tier chain (modules/models/tiers.yaml).
|
|
6
|
+
language: vi
|
|
7
|
+
model: null
|
|
8
|
+
effort: medium
|
|
9
|
+
# Explicit current owner adoption is required; shipped defaults grant no trust or purge authority.
|
|
10
|
+
# launchTrust: {profile: automatic, approvedBy: owner, approvalRef: '<current approval>', roots: ['<exact absolute repository root>']}
|
|
11
|
+
# profile: declined preserves an explicit decision; provider-side declines also remain authoritative.
|
|
12
|
+
launchTrust: null
|
|
13
|
+
# retention: {workflowPurge: {approvedBy: owner, approvalRef: '<current approval>', repos: ['<exact absolute ledger-owner repo>']}}
|
|
14
|
+
retention: null
|
|
15
|
+
# kernel — the long-lived [Kernel] seat. It takes the `high` tier of modules/models/tiers.yaml (an ordered chain of
|
|
16
|
+
# members; the picker takes the first member with tokens after your bias and the balance step; docs/config-format.md).
|
|
17
|
+
# {agent?, model?, effort?}: agent and/or model PIN the seat — the bias `only`, keeping the tier's model of that agent
|
|
18
|
+
# (an explicit `--agent` flag is the same bias and wins). effort: the seat's effort; null takes the tier member's effort.
|
|
19
|
+
kernel: {effort: high}
|
|
20
|
+
# parallel — the one knob for how wide the workers run. `gear` indexes the agent
|
|
21
|
+
# table in modules/models/runtimes.yaml allocation.slicing.size: a long (`l`)
|
|
22
|
+
# operation asks for the `l.agents[gear]` agents, an extra-long (`xl`) one for
|
|
23
|
+
# `xl.agents[gear]`; `s` and `m` operations are always one agent. Declared gears
|
|
24
|
+
# are allocation.slicing.gears — an undeclared value fails closed. Absent means
|
|
25
|
+
# the first declared gear. A gear raises only what `starci kernel estimate` requests; it
|
|
26
|
+
# never raises a pool's maxParallel, maxParallelOps or budgets.maxOps below.
|
|
27
|
+
parallel: {gear: 1}
|
|
28
|
+
# supervisor — the optional chat digest cadence. pollIntervalMs controls
|
|
29
|
+
# scripts/supervisor/poll.mjs when run continuously (its floor is the config
|
|
30
|
+
# validation of engine/config.mjs); null uses scripts/machine/home.mjs
|
|
31
|
+
# DEFAULTS, and --interval-ms overrides this key.
|
|
32
|
+
# The reconciler engine owns host runtime cadence and restores seats after reboot.
|
|
33
|
+
# repos: managed product ledger-owner repositories for the reconciler and Supervisor tools,
|
|
34
|
+
# relative to the source root (the directory holding this skill) or absolute.
|
|
35
|
+
# --repo on the poll command line can add another. stallMinutes (optional,
|
|
36
|
+
# integer >= 5) marks a running workflow STALLED in scripts/supervisor/stall.mjs;
|
|
37
|
+
# the Workflow controller turns actionable findings into Decision Items. The [Supervisor]
|
|
38
|
+
# kernel (docs/supervisor.md) also takes, optional, defaults in home.mjs DEFAULTS:
|
|
39
|
+
# kernel: {agent, model, effort} its agent pin (default: the kernel pin below)
|
|
40
|
+
# workers: {base, max} the adaptive [Worker] cap, 0 to 10 (0 = the Supervisor runs no fix worker)
|
|
41
|
+
# landGate: {mode: shared} shared lets running lanes commit directly; exclusive = gate only. A land
|
|
42
|
+
# fast-forwards LOCAL main only: the remote main moves with a release (starci release cut)
|
|
43
|
+
supervisor:
|
|
44
|
+
# mode: chat (default) - the owner's desktop chat session is the Supervisor: it owns channel 'main'
|
|
45
|
+
# and reads Decision Items; nothing starts a [Supervisor] kernel. kernel - the optional [Supervisor]
|
|
46
|
+
# Orca kernel (scripts/supervisor/start-supervisor.mjs), kept alive by the reconciler Host controller.
|
|
47
|
+
# See docs/supervisor.md.
|
|
48
|
+
# mode: chat
|
|
49
|
+
pollIntervalMs: null
|
|
50
|
+
repos: []
|
|
51
|
+
# frozenMinutes: <n> # Host seat check: a busy frame unchanged this long is frozen, not busy
|
|
52
|
+
# landGate: {mode: shared|exclusive}
|
|
53
|
+
# delegation — while the owner is away, a named delegate (e.g. the supervisor chat) may
|
|
54
|
+
# answer owner asks until the given time; excluded classes stay owner-only. null = none.
|
|
55
|
+
# delegation: {asks: supervisor, until: 2026-09-23T09:00:00+07:00,
|
|
56
|
+
# excludes: [credentials, oauth-consent, payments, legal, payment-provider]}
|
|
57
|
+
delegation: null
|
|
58
|
+
# budgets — the owner's ceiling, surfaced by every plan before `ok`. Null = unbounded.
|
|
59
|
+
budgets:
|
|
60
|
+
maxOps: null # max concurrent dispatched ops one workflow may hold open;
|
|
61
|
+
# route/dispatch refuse `max-ops` at min(this, maxParallelOps)
|
|
62
|
+
# allocation — the owner's default grant for a pool whose capacity is an explicit grant (Devin).
|
|
63
|
+
# grants: '<pool>=<slots>@<role>+<role>' — the default grant every workflow gets. Once a grants list is
|
|
64
|
+
# declared it is the whole set: a pool whose registry.yaml capacityAuthority is explicit-workflow-quota
|
|
65
|
+
# (Devin) routes only for the roles and up to the running slots a grant names ([] closes it).
|
|
66
|
+
allocation:
|
|
67
|
+
grants: [devin-agent=10@implement+verify+write]
|
|
68
|
+
# models — optional overrides of the shipped model tiers (modules/models/tiers.yaml owns the defaults). Every key is optional:
|
|
69
|
+
# tiers: {<tier>: [{agent, model, effort?}, ...]} replaces or adds one tier's ordered member chain
|
|
70
|
+
# seats: {<seat>: <tier>} remaps a seat (supervisor, kernel, worker, planner, validator, kernelManager)
|
|
71
|
+
# balance: {maxStreak, maxSharePercent} a head member picked maxStreak times in a row, or holding more than
|
|
72
|
+
# maxSharePercent of the tier's running seats, yields to the next member
|
|
73
|
+
# usage: {reservePercent, biasPercent} a member at reservePercent of its tokens is skipped; an owner bias
|
|
74
|
+
# naming it keeps it usable up to biasPercent; never above it
|
|
75
|
+
models: {}
|
|
76
|
+
# connectors — the owner's public ask channel (docs/connectors.md). Everything is off by default.
|
|
77
|
+
# One local gateway (scripts/connectors/ask-gateway.mjs) fronts every serve-ask form; a Cloudflare
|
|
78
|
+
# tunnel (scripts/connectors/tunnel.mjs) publishes the gateway; when serve-ask binds a form, the
|
|
79
|
+
# kernel's ask path sends the owner one Telegram message (scripts/connectors/telegram.mjs) with the
|
|
80
|
+
# question, its options and the public link. Telegram carries owner asks only.
|
|
81
|
+
# SECRETS NEVER GO IN THIS FILE: tokenEnv/botTokenEnv NAME an environment variable holding the token
|
|
82
|
+
# (or <NAME>_FILE pointing at a custody file); a value that is not an UPPER_SNAKE env var name is refused.
|
|
83
|
+
# Host credentials use the canonical runtime root secret.env; docs/host-secrets.md owns setup.
|
|
84
|
+
# Supplied environment values win, including blank values. Missing local file adds no values.
|
|
85
|
+
# repos: ledger-owner repository roots whose asks go public, relative to the source root (the
|
|
86
|
+
# directory holding this skill) or absolute. [] = the source root plus every .workspaces project owner.
|
|
87
|
+
# gateway.port: the one fixed local port the tunnel points at (outside the 6969..7069 form band).
|
|
88
|
+
# cloudflare.mode: off | quick | named.
|
|
89
|
+
# quick — `cloudflared tunnel --url`, a random https://*.trycloudflare.com host, no account, no auth,
|
|
90
|
+
# a new host on every restart. Good for decisions only.
|
|
91
|
+
# named — a fixed `hostname`, authenticated one of two ways: `tunnel` (UUID) + `credentialsFile`
|
|
92
|
+
# (the JSON `cloudflared tunnel create` wrote; the manager writes its own ingress config mapping
|
|
93
|
+
# hostname -> the gateway and never reads ~/.cloudflared/config.yml), or a remotely managed tunnel
|
|
94
|
+
# token in `tokenEnv` (its public hostname is routed to http://127.0.0.1:<gateway.port> in the
|
|
95
|
+
# Cloudflare dashboard).
|
|
96
|
+
# cloudflare.access: whether a Cloudflare Access application guards `hostname` (informational; false
|
|
97
|
+
# prints a warning). Recommended for anything beyond yes/no decisions.
|
|
98
|
+
# telegram.enabled / chatId: the owner's chat with the bot (BotFather token in botTokenEnv). Send the
|
|
99
|
+
# bot /start, then `starci connect telegram discover-chat` prints the chat id.
|
|
100
|
+
# telegram.exposeCredentialAsks: false keeps a credential/secret ask off the public link — the message
|
|
101
|
+
# says to answer it on this machine and carries only the localhost link, and the gateway refuses it.
|
|
102
|
+
connectors:
|
|
103
|
+
repos: []
|
|
104
|
+
gateway: {port: 7070}
|
|
105
|
+
cloudflare: {mode: off, tunnel: null, credentialsFile: null, tokenEnv: CLOUDFLARE_TUNNEL_TOKEN, hostname: null, access: false}
|
|
106
|
+
telegram:
|
|
107
|
+
enabled: false
|
|
108
|
+
botTokenEnv: TELEGRAM_BOT_TOKEN
|
|
109
|
+
chatId: null
|
|
110
|
+
exposeCredentialAsks: false
|
|
111
|
+
# asks — owner asks that carry a recommended option (question.recommended, or exactly one option
|
|
112
|
+
# marked "(recommended)" or its Vietnamese equivalents). autoAcceptRecommended: true answers such an
|
|
113
|
+
# ask with its recommendation instead of serving the form: the ledger records ask-answered with
|
|
114
|
+
# answeredBy auto-recommended, the kernel is woken, and Telegram gets one plain message naming the
|
|
115
|
+
# pick. A later owner answer supersedes it. excludes lists the ask classes that always reach the owner:
|
|
116
|
+
# credential — any ask with secret fields, or of kind credential, account, access or consent;
|
|
117
|
+
# handover — every handover.review ask (always excluded, even when removed from this list);
|
|
118
|
+
# draw-review — the opt-out for drawings (interface.draw, scripts/work/draw-review.mjs): unlisted, a
|
|
119
|
+
# drawing the owner did not ask to review is accepted without the owner (its accept option is the
|
|
120
|
+
# recommendation); a drawing the owner asked for (a prior owner redraw of the record, the owner
|
|
121
|
+
# opened its form, or question.ownerRequested) always reaches the owner;
|
|
122
|
+
# or any ask kind: information, authority, business-decision, irreversible-confirmation, ...
|
|
123
|
+
asks:
|
|
124
|
+
autoAcceptRecommended: false
|
|
125
|
+
excludes: [credential, irreversible-confirmation, handover]
|
|
126
|
+
# uat — the machine-wide UAT ceiling. At most maxConcurrent UAT runs (a browser session, a player, the
|
|
127
|
+
# app it drives) execute at once on this host, across every workflow; the rest wait queued for a slot
|
|
128
|
+
# (scripts/uat/uat-slots.mjs, lock files under <runtime>/uat-slots). A slot whose holder died is
|
|
129
|
+
# reclaimed. STARCI_UAT_MAX_CONCURRENT overrides it for one process tree.
|
|
130
|
+
uat:
|
|
131
|
+
maxConcurrent: 10
|
|
132
|
+
# orca — the owner's Orca app settings the runtime must know (engine/orca-config.mjs orcaSettings). maxWorkerDepth MUST equal
|
|
133
|
+
# the worker depth set in the Orca app: the deepest worker Orca starts under the owner's chat (chat 0, Kernel or
|
|
134
|
+
# Supervisor 1, op or [Worker] 2, draw critic 3). Orca exposes no read of it, so the runtime trusts this value and refuses
|
|
135
|
+
# a deeper launch before worker-start (worker-depth-exceeded). `start --check` with STARCI_ORCA_LIVE=1 measures Orca's
|
|
136
|
+
# real limit and turns red when the two differ.
|
|
137
|
+
orca:
|
|
138
|
+
maxWorkerDepth: 4
|
|
139
|
+
# debugLoop — the debug watcher. Workflow DEBUG is a /loop of the chat that started the workflow (the /starci skill sets it up in
|
|
140
|
+
# Claude Code and Codex; no seat, no background agent, nothing in the runtime schedules it). Each tick runs the read-only
|
|
141
|
+
# `starci debug digest`. interval is the loop cadence (<n>s | <n>m | <n>h, default `10m` = `debugLoop.interval`); worktreeLimit is the
|
|
142
|
+
# per-repository core-watch alert threshold (default `40` = `debugLoop.worktreeLimit`). Both keys are optional (engine/config.mjs DEBUG_LOOP_DEFAULTS).
|
|
143
|
+
debugLoop:
|
|
144
|
+
interval: 10m
|
|
145
|
+
worktreeLimit: 40
|
|
146
|
+
# reconciler — the host's one reconciler engine (scripts/reconciler/engine.mjs; contract
|
|
147
|
+
# modules/reconciler/reconciler.yaml). Each controller runs off | shadow (reads, records what it would do as
|
|
148
|
+
# reconciler.would rows, runs nothing) | active (acts; the old loop yields that duty via scripts/reconciler/owns.mjs).
|
|
149
|
+
# profile — the operational default of every controller, so a host is never left all-shadow by accident:
|
|
150
|
+
# operational job, host, workflow, resource active (they settle jobs, start services and seats, wake stalled
|
|
151
|
+
# workflows, throttle); gc, workers, learning shadow. The `start` skill applies and checks it.
|
|
152
|
+
# observe every controller shadow (read-only; a debugging or audit host).
|
|
153
|
+
# (absent) only the explicit controllers.<name>.mode entries count; an unnamed controller is off.
|
|
154
|
+
# controllers.<name>.mode overrides the profile for that one controller (e.g. gc: {mode: active}).
|
|
155
|
+
# Rollback of a controller: set its mode to off (the old loops take the duty back within
|
|
156
|
+
# allocation.reconciler.heartbeatStaleMs). Numbers: modules/models/runtimes.yaml allocation.reconciler.
|
|
157
|
+
reconciler:
|
|
158
|
+
enabled: true
|
|
159
|
+
profile: operational
|
|
160
|
+
controllers: {}
|
|
161
|
+
# specs.unit / specs.e2e in workflows (the `specs` map near the top; owner 2026-09-28 "speed up development;
|
|
162
|
+
# test later when asked"; scripts/route/spec-deferral.mjs). Each op brief's policy.specsToggle says what it does:
|
|
163
|
+
# unit: false - backend.implement, interface.implement and code.refactor run no jest/vitest/test:ci/coverage
|
|
164
|
+
# and demand no changed-line coverage (Sonar still runs: bugs, smells, security gate);
|
|
165
|
+
# test.author legs are never dispatched; review/handover gates do not count unit tests or coverage.
|
|
166
|
+
# e2e: false - e2e.verify legs and e2e test authoring are never dispatched; uat.verify is not e2e.
|
|
167
|
+
# A leg that is not dispatched settles `deferred: specs.<class>=false` with no attempt spent and the workflow
|
|
168
|
+
# proceeds past it; starci kernel status lists testsDeferred and `starci kernel run-deferred-tests --workflow <id> [--kind unit|e2e]`
|
|
169
|
+
# runs them later. Read on every wake: flipping a switch needs no restart.
|
|
170
|
+
# roots — where this host keeps the archive, the lane worktrees and its temporary files, when the owner moves them off the defaults.
|
|
171
|
+
# All keys are optional absolute directories; env STARCI_ARCHIVE_ROOT / STARCI_LANES_ROOT / STARCI_TEMP_ROOT win over them, and an
|
|
172
|
+
# absent key means <starciLocalRoot>/archive (<runtime root>/.runtime/archive), the per-user profile lanes directory, kept out of the checkout (scripts/machine/home.mjs lanesRoot),
|
|
173
|
+
# and the OS temp directory (engine/temp-root.mjs tempRoot; the processes the runtime starts get it as TEMP/TMP/TMPDIR). No host location lives in
|
|
174
|
+
# a tracked file: set the real directories only in the gitignored config.yaml copy, e.g.
|
|
175
|
+
# roots: {archive: <absolute directory>, lanes: <absolute directory>, temp: <absolute directory>}
|
|
176
|
+
# sonar — the SonarCloud organization the release's Sonar proof of the example apps analyses in (scripts/supervisor/release-sonarcloud.mjs). It is configuration, not a secret,
|
|
177
|
+
# so it lives here and not in secret.env; the project keys are <organization>_starci-example-<app>. The environment variable SONAR_ORGANIZATION wins over it (CI reads the
|
|
178
|
+
# repository variable of the same name). Empty until the owner sets it in the gitignored config.yaml copy, e.g.
|
|
179
|
+
# sonar: {organization: <the organization key shown on sonarcloud.io>}
|
|
180
|
+
sonar: {organization: null}
|
|
181
|
+
# resources — the host capacity floors the dispatch gate enforces, over the shipped numbers of modules/models/runtimes.yaml allocation.resources
|
|
182
|
+
# (scripts/machine/host-resources.mjs; docs/config-format.md "Host resource floors"). Every key is optional (a number above 0, or null):
|
|
183
|
+
# minFreeDiskGb free GB a drive holding the temp root or the repository must keep
|
|
184
|
+
# minFreeDiskPct the same floor as a percentage of the drive's size; with minFreeDiskGb the larger requirement applies
|
|
185
|
+
# minFreeRamPct free-RAM percentage below which no new heavy op starts
|
|
186
|
+
# set them only in the gitignored config.yaml copy, e.g.
|
|
187
|
+
# resources: {minFreeDiskGb: 5, minFreeDiskPct: 1, minFreeRamPct: 10}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// by-code-unit.mjs - the comparator of a bare `Array.prototype.sort()`, owned beside the engine files that digest and canonicalize (scripts/lib/list.mjs re-exports it).
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The comparator of a bare `Array.prototype.sort()`: values compared as strings by UTF-16 code unit. Pass it to `sort` and
|
|
5
|
+
* `toSorted` so the order is stated, and stays byte-identical to the default order that digests and canonical output depend on.
|
|
6
|
+
*/
|
|
7
|
+
export const byCodeUnit = (a, b) => {
|
|
8
|
+
const left = String(a);
|
|
9
|
+
const right = String(b);
|
|
10
|
+
if (left < right) return -1;
|
|
11
|
+
return left > right ? 1 : 0;
|
|
12
|
+
};
|