@starci/eslint-canon-fe 9.5.23 → 9.6.2

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/button-intent.md +1 -1
  3. package/package.json +1 -1
  4. package/runtime/bundle.json +192 -0
  5. package/runtime/engine/by-code-unit.mjs +6 -6
  6. package/runtime/engine/model-config.mjs +98 -76
  7. package/runtime/engine/orca-config.mjs +22 -17
  8. package/runtime/engine/release-config.mjs +22 -18
  9. package/runtime/engine/removed-vocabulary.mjs +21 -20
  10. package/runtime/engine/resources-config.mjs +42 -35
  11. package/runtime/engine/runtime-root.mjs +11 -13
  12. package/runtime/engine/secrets.mjs +170 -102
  13. package/runtime/engine/sonar-config.mjs +16 -13
  14. package/runtime/engine/temp-root.mjs +3 -9
  15. package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
  16. package/runtime/knowledge/hfs/slots.yaml +2 -2
  17. package/runtime/knowledge/sonar-gate.yaml +1 -1
  18. package/runtime/packages/hfs/templates/app/package-scripts/package.json +1 -1
  19. package/runtime/scripts/api/git/cat-file.mjs +0 -1
  20. package/runtime/scripts/api/git/config-get.mjs +1 -2
  21. package/runtime/scripts/api/git/diff.mjs +0 -1
  22. package/runtime/scripts/api/git/lib.mjs +32 -32
  23. package/runtime/scripts/api/git/ls-files.mjs +0 -1
  24. package/runtime/scripts/api/git/ls-tree.mjs +0 -1
  25. package/runtime/scripts/api/git/merge-base.mjs +2 -3
  26. package/runtime/scripts/api/git/remote.mjs +0 -1
  27. package/runtime/scripts/api/git/rev-parse-query.mjs +0 -1
  28. package/runtime/scripts/api/git/show.mjs +0 -1
  29. package/runtime/scripts/hfs/allows.mjs +37 -38
  30. package/runtime/scripts/hfs/architecture/ast-walks.mjs +180 -163
  31. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +80 -73
  32. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +88 -77
  33. package/runtime/scripts/hfs/architecture/backend.mjs +378 -357
  34. package/runtime/scripts/hfs/architecture/background-unowned.mjs +201 -187
  35. package/runtime/scripts/hfs/architecture/canon-clones.mjs +260 -209
  36. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +149 -116
  37. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +72 -68
  38. package/runtime/scripts/hfs/architecture/clones.mjs +212 -217
  39. package/runtime/scripts/hfs/architecture/config-unread.mjs +7 -9
  40. package/runtime/scripts/hfs/architecture/config.mjs +302 -268
  41. package/runtime/scripts/hfs/architecture/connection-map.mjs +258 -225
  42. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +80 -71
  43. package/runtime/scripts/hfs/architecture/context-coupling.mjs +82 -77
  44. package/runtime/scripts/hfs/architecture/context-map.mjs +267 -234
  45. package/runtime/scripts/hfs/architecture/context-owner.mjs +73 -61
  46. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -43
  47. package/runtime/scripts/hfs/architecture/context-transaction.mjs +54 -52
  48. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +113 -104
  49. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +288 -270
  50. package/runtime/scripts/hfs/architecture/contracts.mjs +408 -393
  51. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +50 -51
  52. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +143 -119
  53. package/runtime/scripts/hfs/architecture/dead-exports.mjs +286 -228
  54. package/runtime/scripts/hfs/architecture/default-deny.mjs +129 -110
  55. package/runtime/scripts/hfs/architecture/doc-language.mjs +24 -19
  56. package/runtime/scripts/hfs/architecture/entity-registry.mjs +20 -22
  57. package/runtime/scripts/hfs/architecture/entrypoint.mjs +47 -45
  58. package/runtime/scripts/hfs/architecture/error-codes.mjs +37 -35
  59. package/runtime/scripts/hfs/architecture/error-masked.mjs +55 -59
  60. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +24 -23
  61. package/runtime/scripts/hfs/architecture/feature-shape.mjs +40 -38
  62. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +21 -21
  63. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +106 -98
  64. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +153 -150
  65. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +161 -149
  66. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +343 -305
  67. package/runtime/scripts/hfs/architecture/frontend-world.mjs +340 -294
  68. package/runtime/scripts/hfs/architecture/frontend.mjs +174 -184
  69. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +124 -106
  70. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +52 -48
  71. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +81 -71
  72. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +155 -141
  73. package/runtime/scripts/hfs/architecture/identity.mjs +141 -125
  74. package/runtime/scripts/hfs/architecture/index.mjs +272 -272
  75. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +45 -43
  76. package/runtime/scripts/hfs/architecture/jwt-ast.mjs +195 -176
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +163 -155
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +19 -20
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +177 -172
  80. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +100 -78
  81. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +170 -154
  82. package/runtime/scripts/hfs/architecture/next-data-key.mjs +311 -275
  83. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +17 -17
  84. package/runtime/scripts/hfs/architecture/next-data.mjs +351 -334
  85. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +39 -37
  86. package/runtime/scripts/hfs/architecture/owners.mjs +97 -98
  87. package/runtime/scripts/hfs/architecture/package-shape.mjs +56 -55
  88. package/runtime/scripts/hfs/architecture/presentation.mjs +138 -130
  89. package/runtime/scripts/hfs/architecture/reachability.mjs +258 -241
  90. package/runtime/scripts/hfs/architecture/register-once.mjs +152 -139
  91. package/runtime/scripts/hfs/architecture/registration.mjs +301 -267
  92. package/runtime/scripts/hfs/architecture/required-files.mjs +172 -167
  93. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +85 -77
  94. package/runtime/scripts/hfs/architecture/schema-owner.mjs +206 -181
  95. package/runtime/scripts/hfs/architecture/server-action.mjs +48 -48
  96. package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +261 -259
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +304 -278
  99. package/runtime/scripts/hfs/architecture/sql-keys.mjs +277 -194
  100. package/runtime/scripts/hfs/architecture/sql-owner.mjs +236 -194
  101. package/runtime/scripts/hfs/architecture/sql-returning.mjs +46 -45
  102. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +86 -74
  103. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +456 -341
  104. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +35 -30
  105. package/runtime/scripts/hfs/architecture/surface.mjs +64 -74
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +209 -216
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +165 -155
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +161 -154
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +155 -149
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +110 -92
  113. package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
  114. package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
  115. package/runtime/scripts/hfs/edition-slots.mjs +43 -40
  116. package/runtime/scripts/hfs/linear-text.mjs +20 -20
  117. package/runtime/scripts/hfs/param-names.mjs +6 -11
  118. package/runtime/scripts/hfs/path-findings.mjs +120 -100
  119. package/runtime/scripts/hfs/repo-identity.mjs +55 -42
  120. package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
  121. package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
  122. package/runtime/scripts/hfs/rules/database-migrations.mjs +428 -388
  123. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +37 -36
  124. package/runtime/scripts/hfs/rules/read.mjs +19 -15
  125. package/runtime/scripts/hfs/rules/secrets.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/spec-placement.mjs +12 -13
  127. package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
  128. package/runtime/scripts/hfs/slot-classify.mjs +42 -38
  129. package/runtime/scripts/hfs/slot-errors.mjs +8 -7
  130. package/runtime/scripts/hfs/slot-imports.mjs +70 -63
  131. package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
  132. package/runtime/scripts/hfs/slot-match.mjs +122 -111
  133. package/runtime/scripts/hfs/slot-path.mjs +0 -2
  134. package/runtime/scripts/hfs/slot-required.mjs +65 -59
  135. package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
  136. package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
  137. package/runtime/scripts/hfs/slots.mjs +175 -152
  138. package/runtime/scripts/hfs/test-world-slot.mjs +0 -1
  139. package/runtime/scripts/hfs/trailing-slashes.mjs +4 -4
  140. package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
  141. package/runtime/scripts/hfs/view.mjs +38 -40
  142. package/runtime/scripts/lib/dockerfile.mjs +131 -114
  143. package/runtime/scripts/lib/env.mjs +3 -6
  144. package/runtime/scripts/lib/event-contract.mjs +73 -78
  145. package/runtime/scripts/lib/git.mjs +15 -16
  146. package/runtime/scripts/lib/glob.mjs +23 -14
  147. package/runtime/scripts/lib/grammar-lint.mjs +33 -37
  148. package/runtime/scripts/lib/i18n.mjs +32 -35
  149. package/runtime/scripts/lib/in-order.mjs +57 -38
  150. package/runtime/scripts/lib/json-clone.mjs +45 -36
  151. package/runtime/scripts/lib/json.mjs +26 -14
  152. package/runtime/scripts/lib/language.mjs +133 -129
  153. package/runtime/scripts/lib/list.mjs +9 -14
  154. package/runtime/scripts/lib/mutation-fence.mjs +10 -5
  155. package/runtime/scripts/lib/path-key.mjs +53 -38
  156. package/runtime/scripts/lib/read-text.mjs +30 -17
  157. package/runtime/scripts/lib/regex.mjs +1 -2
  158. package/runtime/scripts/lib/same-text.mjs +0 -1
  159. package/runtime/scripts/lib/sops-envelope.mjs +148 -120
  160. package/runtime/scripts/lib/stack-services.mjs +128 -129
  161. package/runtime/scripts/lib/ts-analysis.mjs +28 -26
  162. package/runtime/scripts/lib/ts-ast.mjs +36 -42
  163. package/runtime/scripts/lib/walk.mjs +33 -27
  164. package/runtime/scripts/lib/yaml-cached.mjs +4 -4
@@ -51,7 +51,7 @@ versioning:
51
51
  # 3.10.0 is a minor (2026-10-29): it ADDS the lite be.platform.database.entities slot, the entity registry (slot fields role and importedBy), so the
52
52
  # tier matrix and the public-entry rules admit exactly its <c>Entities imports and its deep importers (persistence.md BE-PERSISTENCE-3).
53
53
  # 3.11.0 (ruling:lite-official-postgres-image, 2026-10-29) is, like 3.9.0, a change for lite only: it ADDS the managed app.db.image slot
54
- # (db/Dockerfile, written by `starci app sync --write`), narrows ruleParams.db.extensions to the extensions that image carries, and gives
54
+ # (db/Dockerfile, written by `starci app sync --execute`), narrows ruleParams.db.extensions to the extensions that image carries, and gives
55
55
  # @nestjs/schedule and cron one owner, platform/jobs (R335).
56
56
  # 3.13.0 (rulings lite-pgmq-bus-bullmq-jobs and lite-db-image-in-stacks, 2026-10-29) is a change for lite only: ruleParams.db.extensions
57
57
  # admits pgmq, the event bus, ruleParams.db.queueOutbox is retired with the pg_cron half of R258, the lite event-bus functions replace
@@ -940,7 +940,7 @@ slots:
940
940
 
941
941
  # ----- app root: the database of a lite app (ruling:lite-self-hosted-postgres-passport-minio): a self-hosted PostgreSQL in the app's
942
942
  # stack (the `db` service, built from the managed .starcistacks/images/db/Dockerfile: the official postgres image plus pgvector and pgmq, ruling:lite-db-image-in-stacks), migrated by
943
- # `starci app db migrate --url-env <NAME>`. A full app keeps its TypeORM migrations under be/ and has no such folder.
943
+ # `starci app db migrate --db-url-env <NAME>`. A full app keeps its TypeORM migrations under be/ and has no such folder.
944
944
  - id: app.db
945
945
  profiles: [app]
946
946
  editions: [lite]
@@ -41,7 +41,7 @@ newCode:
41
41
  minReviewedPercent: 100
42
42
  unreviewedMax: 0
43
43
  # The whole code, not only the new code. Every finding of the HFS canon reaches Sonar through ONE entry, `starci app lint`
44
- # (packages/hfs, contract changes hfs-sonar-import and hfs-lint-entry). CI runs it per side with --side and --sonar,
44
+ # (packages/hfs, contract changes hfs-sonar-import and hfs-lint-entry). CI runs it per side with --side and --out,
45
45
  # then merges rules by id and joins issues into ONE file, reports/lint.sonar.json, named by sonar.externalIssuesReportPaths; the gate holds the open-issue count of
46
46
  # the whole project at zero. The file carries three engines:
47
47
  # starci-hfs the repository findings of `starci app check` (managed files, tree, pins, CI, contracts; the rule id is the finding code)
@@ -8,7 +8,7 @@
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
9
  "test:e2e": "npm run typecheck:tests && cd be && jest --selectProjects e2e",
10
10
  "test:contract": "npm run typecheck:tests && cd be && jest --selectProjects contract",
11
- "test:stack": "starci app stack",
11
+ "test:world": "starci app world up",
12
12
  {{supabaseScripts}}
13
13
  "contract:emit": "starci app emit"
14
14
  }
@@ -1,5 +1,4 @@
1
1
  // cat-file.mjs — `git cat-file <args>`: the type, size or content of repository objects (-e, -p, --batch).
2
2
  import { runGit } from './lib.mjs';
3
-
4
3
  /** The spawn result {status, stdout, stderr, error} of `git cat-file ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
4
  export const catFile = (args = [], options = {}) => runGit(['cat-file', ...args], options);
@@ -1,10 +1,9 @@
1
1
  // config-get.mjs — `git config [--local] --get <key>`: one configuration value of a checkout (empty when unset).
2
2
  import { gitRunner } from './lib.mjs';
3
-
4
3
  /**
5
4
  * {ok, stdout, stderr}; `local` reads the repository's own config only; `env` replaces the process env (a caller drops
6
5
  * git's repository-local variables). git: the caller's runner or null.
7
6
  */
8
7
  export function configGet(cwd, key, { local = false, env = undefined, git = null } = {}) {
9
- return gitRunner(git)(['config', ...(local ? ['--local'] : []), '--get', key], env ? { cwd, env } : { cwd });
8
+ return gitRunner(git)(['config', ...(local ? ['--local'] : []), '--get', key], env ? { cwd, env } : { cwd });
10
9
  }
@@ -1,5 +1,4 @@
1
1
  // diff.mjs — `git diff <args>`: the changes between commits, the index and the working tree.
2
2
  import { runGit } from './lib.mjs';
3
-
4
3
  /** The spawn result {status, stdout, stderr, error} of `git diff ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
4
  export const diff = (args = [], options = {}) => runGit(['diff', ...args], options);
@@ -13,7 +13,6 @@ import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
14
  import { withoutGitLocalEnv } from '../../lib/git.mjs';
15
15
  import { withTempEnv } from '../../../engine/temp-root.mjs';
16
-
17
16
  /**
18
17
  * Spawn `file` (the git binary) once with `args`: utf8 text, a hidden window, never a shell.
19
18
  * Options pass through last, so cwd, timeout, input, env or maxBuffer land as given. encoding:'buffer'
@@ -21,46 +20,46 @@ import { withTempEnv } from '../../../engine/temp-root.mjs';
21
20
  * ERR_UNKNOWN_ENCODING, so it reaches spawnSync as encoding:null (Buffer output either way).
22
21
  */
23
22
  const gitSpawn = (file, args, options = {}) => {
24
- assertMutationFence({ kind: 'git-effect', args });
25
- const spawn = { encoding: 'utf8', windowsHide: true, ...options };
26
- if (spawn.encoding === 'buffer') spawn.encoding = null;
27
- // tempChildEnv copies the env (node adds NODE_V8_COVERAGE to the env object it is given: a frozen caller env must not throw under coverage) and points TEMP/TMP/TMPDIR at the temp root
28
- return spawnSync(file, args, withTempEnv(spawn));
23
+ assertMutationFence({ kind: 'git-effect', args });
24
+ const spawn = { encoding: 'utf8', windowsHide: true, ...options };
25
+ if (spawn.encoding === 'buffer')
26
+ spawn.encoding = null;
27
+ // tempChildEnv copies the env (node adds NODE_V8_COVERAGE to the env object it is given: a frozen caller env must not throw under coverage) and points TEMP/TMP/TMPDIR at the temp root
28
+ // reason: encoding:null (the former 'buffer') returns Buffers; callers of that mode read the result as raw bytes
29
+ const spawnOptions = withTempEnv(spawn);
30
+ return spawnSync(file, args, spawnOptions);
29
31
  };
30
-
31
32
  /**
32
33
  * `git args` in `cwd`, or `git -C dir args` when `dir` is given. `git` overrides the binary; `config` ({key: value})
33
34
  * prefixes one `-c key=value` per entry (core.quotepath=off, a committer identity). Scoped calls discard ambient
34
35
  * hook repository variables; an explicit env remains authoritative, including an intentional temporary index.
35
36
  */
36
- export const runGit = (args, { cwd = null, dir = null, git = 'git', config = null, ...options } = {}) =>
37
- gitSpawn(git, [...(dir ? ['-C', dir] : []), ...Object.entries(config ?? {}).flatMap(([key, value]) => ['-c', `${key}=${value}`]), ...args], {
37
+ export const runGit = (args, { cwd = null, dir = null, git = 'git', config = null, ...options } = {}) => gitSpawn(git, [...(dir ? ['-C', dir] : []), ...Object.entries(config ?? {}).flatMap(([key, value]) => ['-c', `${key}=${value}`]), ...args], {
38
38
  ...(cwd ? { cwd } : {}), ...options,
39
39
  ...((cwd || dir) && options.env === undefined ? { env: withoutGitLocalEnv(process.env) } : {}),
40
- });
41
-
40
+ });
42
41
  /**
43
42
  * A caller's git runner folded to (args, opts) -> {ok, stdout, stderr}. `git`: the caller's runner (args, {cwd}) ->
44
43
  * {ok|status, stdout|out, stderr|err} (a spec's fake); null: runGit with a 5-minute timeout and a 64 MB buffer.
45
44
  */
46
45
  export const gitRunner = (git = null) => (args, opts = {}) => {
47
- const r = git ? git(args, opts) : runGit(args, { timeout: 300_000, maxBuffer: 64 * 1024 * 1024, ...opts });
48
- return { ok: r?.ok ?? (!r?.error && r?.status === 0), stdout: String(r?.stdout ?? r?.out ?? '').trim(), stderr: String(r?.stderr ?? r?.err ?? r?.error?.message ?? r?.error ?? '').trim() };
46
+ const r = git ? git(args, opts) : runGit(args, { timeout: 300_000, maxBuffer: 64 * 1024 * 1024, ...opts });
47
+ return { ok: r?.ok ?? (!r?.error && r?.status === 0), stdout: String(r?.stdout ?? r?.out ?? '').trim(), stderr: String(r?.stderr ?? r?.err ?? (typeof r?.error === 'object' ? r.error?.message : undefined) ?? r?.error ?? '').trim() };
49
48
  };
50
-
51
49
  let cachedGit;
52
50
  const gitPathOf = ({ env, platform, home, exists }) => {
53
- const p = platform === 'win32' ? path.win32 : path.posix;
54
- const pathKey = Object.keys(env).find((name) => name.toLowerCase() === 'path');
55
- const shim = p.join(home, '.starci', 'bin');
56
- const same = (a, b) => (platform === 'win32' ? p.resolve(a).toLowerCase() === p.resolve(b).toLowerCase() : p.resolve(a) === p.resolve(b));
57
- const binary = platform === 'win32' ? 'git.exe' : 'git';
58
- for (const raw of String(pathKey ? env[pathKey] : '').split(platform === 'win32' ? ';' : ':')) {
59
- const dir = raw.replace(/^"(.*)"$/, '$1');
60
- const file = dir && p.isAbsolute(dir) && !same(dir, shim) ? p.join(dir, binary) : null;
61
- if (file && exists(file)) return file;
62
- }
63
- return null;
51
+ const p = platform === 'win32' ? path.win32 : path.posix;
52
+ const pathKey = Object.keys(env).find((name) => name.toLowerCase() === 'path');
53
+ const shim = p.join(home, '.starci', 'bin');
54
+ const same = (a, b) => (platform === 'win32' ? p.resolve(a).toLowerCase() === p.resolve(b).toLowerCase() : p.resolve(a) === p.resolve(b));
55
+ const binary = platform === 'win32' ? 'git.exe' : 'git';
56
+ for (const raw of String(pathKey ? env[pathKey] : '').split(platform === 'win32' ? ';' : ':')) {
57
+ const dir = raw.replace(/^"(.*)"$/, '$1');
58
+ const file = dir && p.isAbsolute(dir) && !same(dir, shim) ? p.join(dir, binary) : null;
59
+ if (file && exists(file))
60
+ return file;
61
+ }
62
+ return null;
64
63
  };
65
64
  /**
66
65
  * The absolute path of the real git binary, or null: the first PATH directory holding git (git.exe on Windows) that is not
@@ -69,12 +68,13 @@ const gitPathOf = ({ env, platform, home, exists }) => {
69
68
  * platform, home, exists) are for a spec and bypass the cache.
70
69
  */
71
70
  export const gitExecutable = (options = null) => {
72
- if (!options && cachedGit !== undefined) return cachedGit;
73
- const { env = process.env, platform = process.platform, home = os.homedir(), exists = (file) => fs.statSync(file, { throwIfNoEntry: false })?.isFile() === true } = options ?? {};
74
- const found = gitPathOf({ env, platform, home, exists });
75
- if (!options) cachedGit = found;
76
- return found;
71
+ if (!options && cachedGit !== undefined)
72
+ return cachedGit;
73
+ const { env = process.env, platform = process.platform, home = os.homedir(), exists = (file) => fs.statSync(file, { throwIfNoEntry: false })?.isFile() === true } = options ?? {};
74
+ const found = gitPathOf({ env, platform, home, exists });
75
+ if (!options)
76
+ cachedGit = found;
77
+ return found;
77
78
  };
78
-
79
79
  /** The output ceiling of the whole-diff and `cat-file --batch` calls batch (a release diff is tens of megabytes). */
80
- const BATCH_MAX_BUFFER = 1024 * 1024 * 1024;
80
+ export const BATCH_MAX_BUFFER = 1024 * 1024 * 1024;
@@ -1,6 +1,5 @@
1
1
  // ls-files.mjs — `git ls-files <args>`: the paths the index tracks (--cached), or the untracked / deleted / ignored ones
2
2
  // the flags ask for (-z keeps any file name intact).
3
3
  import { runGit } from './lib.mjs';
4
-
5
4
  /** The spawn result {status, stdout, stderr, error} of `git ls-files ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
6
5
  export const lsFiles = (args = [], options = {}) => runGit(['ls-files', ...args], options);
@@ -1,5 +1,4 @@
1
1
  // ls-tree.mjs — `git ls-tree <args>`: the entries of a tree object.
2
2
  import { runGit } from './lib.mjs';
3
-
4
3
  /** The spawn result {status, stdout, stderr, error} of `git ls-tree ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
4
  export const lsTree = (args = [], options = {}) => runGit(['ls-tree', ...args], options);
@@ -1,9 +1,8 @@
1
1
  // merge-base.mjs — `git merge-base <a> <b>`: the best common ancestor of two commits. Whether one commit is in another
2
2
  // is is-ancestor.mjs.
3
3
  import { gitRunner } from './lib.mjs';
4
-
5
4
  /** The best common ancestor of commits `a` and `b` (a sha), or null when they share none or a ref does not resolve. */
6
5
  export const mergeBase = (cwd, a, b, { git = null } = {}) => {
7
- const r = gitRunner(git)(['merge-base', a, b], { cwd });
8
- return r.ok && r.stdout ? r.stdout : null;
6
+ const r = gitRunner(git)(['merge-base', a, b], { cwd });
7
+ return r.ok && r.stdout ? r.stdout : null;
9
8
  };
@@ -1,5 +1,4 @@
1
1
  // remote.mjs — `git remote <args>`: the configured remotes (get-url <name>).
2
2
  import { runGit } from './lib.mjs';
3
-
4
3
  /** The spawn result {status, stdout, stderr, error} of `git remote ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
4
  export const remote = (args = [], options = {}) => runGit(['remote', ...args], options);
@@ -1,5 +1,4 @@
1
1
  // rev-parse-query.mjs — `git rev-parse <args>`: one rev-parse query (--show-toplevel, --git-common-dir, --git-path, a ref): the text it prints.
2
2
  import { runGit } from './lib.mjs';
3
-
4
3
  /** The spawn result {status, stdout, stderr, error} of `git rev-parse ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
4
  export const revParseQuery = (args = [], options = {}) => runGit(['rev-parse', ...args], options);
@@ -1,6 +1,5 @@
1
1
  // show.mjs — `git show <args>`: an object of a repository - a file at a revision (`<rev>:<path>`), or a commit's
2
2
  // header and message (-s --format=...).
3
3
  import { runGit } from './lib.mjs';
4
-
5
4
  /** The spawn result {status, stdout, stderr, error} of `git show ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
6
5
  export const show = (args = [], options = {}) => runGit(['show', ...args], options);
@@ -1,7 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { braceVariants, globExpression } from '../lib/glob.mjs';
3
3
  import { declaredLogicOf } from './manifest-shape.mjs';
4
-
5
4
  /**
6
5
  * What a slot's `requires`, `allows` and `forbids` entries (knowledge/hfs/slots.yaml) say about a file, read through the
7
6
  * resolver of scripts/hfs/slots.mjs. The machine checks that judge "this folder holds only what its slot allows"
@@ -14,65 +13,65 @@ import { declaredLogicOf } from './manifest-shape.mjs';
14
13
  */
15
14
  const VAR = /<([A-Za-z][A-Za-z0-9-]*)>/g;
16
15
  const SPEC = /\.spec\.ts$/;
17
-
18
16
  /**
19
17
  * Whether `relative` is the unit spec `<stem>.spec.ts` of a measured file beside it that the slot names: a file the slot declares in
20
18
  * `logic`, or a `requires`/`allows` file of a logic role (ruleParams.be.logicRoles), in a slot whose coverage is required and whose tests
21
19
  * are unit-beside. The slot admits that spec without listing it, as unit-test-colocated (R47) accepts it.
22
20
  */
23
21
  function isMeasuredSpec(resolver, classified, slot, relative, named) {
24
- if (!SPEC.test(relative) || slot.coverage !== 'required' || slot.tests !== 'unit-beside') return false;
25
- const subject = relative.replace(SPEC, '.ts');
26
- if (declaredLogicOf(slot).includes(subject)) return true;
27
- const role = /\.([a-z0-9-]+)\.ts$/.exec(subject)?.[1];
28
- // An app resolver keeps no rule parameters of its own: a path on a side reads that side's.
29
- const params = classified.side ? resolver.sides?.[classified.side]?.ruleParams() : resolver.ruleParams?.();
30
- return (params?.logicRoles ?? []).includes(role) && named.some(matcher => matcher.expression.test(subject));
22
+ if (!SPEC.test(relative) || slot.coverage !== 'required' || slot.tests !== 'unit-beside')
23
+ return false;
24
+ const subject = relative.replace(SPEC, '.ts');
25
+ if (declaredLogicOf(slot).includes(subject))
26
+ return true;
27
+ const role = /\.([a-z0-9-]+)\.ts$/.exec(subject)?.[1];
28
+ // An app resolver keeps no rule parameters of its own: a path on a side reads that side's.
29
+ const params = classified.side ? resolver.sides?.[classified.side]?.ruleParams() : resolver.ruleParams?.();
30
+ return (params?.logicRoles ?? []).includes(role) && named.some((matcher) => matcher.expression.test(subject));
31
31
  }
32
-
33
32
  function matchersOf(entries, bindings) {
34
- return (entries ?? []).flatMap(entry => {
35
- const directory = entry.endsWith('/');
36
- const body = directory ? entry.slice(0, -1) : entry;
37
- const filled = body.replace(VAR, (whole, name) => bindings[name] ?? '*');
38
- return braceVariants(filled).map(variant => ({ entry, directory, expression: globExpression(directory ? `${variant}/**` : variant) }));
39
- });
33
+ return (entries ?? []).flatMap((entry) => {
34
+ const directory = entry.endsWith('/');
35
+ const body = directory ? entry.slice(0, -1) : entry;
36
+ const filled = body.replace(VAR, (_whole, name) => bindings[name] ?? '*');
37
+ return braceVariants(filled).map((variant) => ({ entry, directory, expression: globExpression(directory ? `${variant}/**` : variant) }));
38
+ });
40
39
  }
41
-
42
40
  /** The path of `file` below the instance root of the slot that owns it. */
43
41
  export function relativeToRoot(file, root) {
44
- return root && file.startsWith(`${root}/`) ? file.slice(root.length + 1) : path.posix.basename(file);
42
+ return root && file.startsWith(`${root}/`) ? file.slice(root.length + 1) : path.posix.basename(file);
45
43
  }
46
-
47
44
  /**
48
45
  * Whether the slot that owns `file` names it in `requires` or `allows` (and does not forbid it); null when no enabled slot
49
46
  * owns it. Unlike allowsFile it judges a slot that lists only `requires` too, so a folder closed by its slot (an app's
50
47
  * src/) is read from the slot and never from a check's own list.
51
48
  */
52
49
  export function slotAdmitsFile(resolver, file) {
53
- const classified = resolver.classifyPath(file);
54
- if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
55
- const slot = resolver.slot(classified.slot);
56
- const relative = relativeToRoot(classified.path, classified.root);
57
- const named = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings);
58
- const hit = named.some(matcher => matcher.expression.test(relative)) || isMeasuredSpec(resolver, classified, slot, relative, named);
59
- const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
60
- return hit && !forbidden;
50
+ const classified = resolver.classifyPath(file);
51
+ if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot)
52
+ return null;
53
+ const slot = resolver.slot(classified.slot);
54
+ const relative = relativeToRoot(classified.path, classified.root);
55
+ const named = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings);
56
+ const hit = named.some(matcher => matcher.expression.test(relative)) || isMeasuredSpec(resolver, classified, slot, relative, named);
57
+ const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
58
+ return hit && !forbidden;
61
59
  }
62
-
63
60
  /**
64
61
  * {slot, root, relative, allowed, entry?, forbiddenBy?} for a repository-relative file, or null when no enabled slot owns
65
62
  * it or the slot names no `allows`. `allowed` is true when the relative path matches a `requires` or `allows` entry.
66
63
  */
67
64
  export function allowsFile(resolver, file) {
68
- const classified = resolver.classifyPath(file);
69
- if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
70
- const slot = resolver.slot(classified.slot);
71
- if (!slot?.allows?.length) return null;
72
- const relative = relativeToRoot(classified.path, classified.root);
73
- const named = matchersOf([...(slot.requires ?? []), ...slot.allows], classified.bindings);
74
- const hit = named.find(matcher => matcher.expression.test(relative));
75
- const spec = !hit && isMeasuredSpec(resolver, classified, slot, relative, named);
76
- const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
77
- return { slot: slot.id, root: classified.root, relative, allowed: (Boolean(hit) || spec) && !forbidden, entry: hit?.entry, forbiddenBy: forbidden?.entry, allows: slot.allows };
65
+ const classified = resolver.classifyPath(file);
66
+ if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot)
67
+ return null;
68
+ const slot = resolver.slot(classified.slot);
69
+ if (!slot?.allows?.length)
70
+ return null;
71
+ const relative = relativeToRoot(classified.path, classified.root);
72
+ const named = matchersOf([...(slot.requires ?? []), ...slot.allows], classified.bindings);
73
+ const hit = named.find(matcher => matcher.expression.test(relative));
74
+ const spec = !hit && isMeasuredSpec(resolver, classified, slot, relative, named);
75
+ const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
76
+ return { slot: slot.id, root: classified.root, relative, allowed: (Boolean(hit) || spec) && !forbidden, entry: hit?.entry, forbiddenBy: forbidden?.entry, allows: slot.allows };
78
77
  }