@starci/eslint-canon-be 3.0.8 → 3.0.9

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 (147) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +12 -6
  3. package/ambient-id.mjs +90 -0
  4. package/cli.mjs +181 -0
  5. package/comments.mjs +18 -21
  6. package/config-owner.mjs +20 -4
  7. package/connections.mjs +12 -12
  8. package/cookies.mjs +106 -0
  9. package/cqrs.mjs +22 -26
  10. package/data-access.mjs +9 -28
  11. package/default-deny.mjs +2 -1
  12. package/e2e-flow.mjs +4 -4
  13. package/event-bus.mjs +237 -0
  14. package/feature-thin.mjs +237 -0
  15. package/idempotency.mjs +3 -1
  16. package/index.mjs +22 -1
  17. package/infra-owner.mjs +1 -1
  18. package/injection.mjs +67 -22
  19. package/input-bounds.mjs +2 -1
  20. package/jobs.mjs +214 -0
  21. package/lib/ast.mjs +7 -0
  22. package/lib/config.mjs +1 -1
  23. package/lib/db-writes.mjs +1 -1
  24. package/lib/doors.mjs +81 -0
  25. package/lib/edition.mjs +45 -0
  26. package/lib/entity-writes.mjs +35 -0
  27. package/lib/path.mjs +0 -6
  28. package/lib/persistence.mjs +11 -18
  29. package/lib/ports.mjs +5 -2
  30. package/lib/specifier.mjs +27 -0
  31. package/lib/transactions.mjs +37 -0
  32. package/lib/transport-slots.mjs +8 -3
  33. package/lib/unit-spec.mjs +63 -2
  34. package/module-layering.mjs +3 -24
  35. package/module-shape.mjs +2 -2
  36. package/naming.mjs +2 -2
  37. package/package.json +3 -1
  38. package/project-graph.mjs +5 -2
  39. package/projections.mjs +117 -0
  40. package/query-safety.mjs +1 -1
  41. package/realtime.mjs +181 -0
  42. package/runtime/engine/config.mjs +494 -0
  43. package/runtime/engine/invalid-config.mjs +15 -0
  44. package/runtime/engine/orca-config.mjs +27 -0
  45. package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
  46. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  47. package/runtime/knowledge/hfs/slots.yaml +912 -120
  48. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  49. package/runtime/knowledge/sonar-gate.yaml +52 -18
  50. package/runtime/modules/models/registry.yaml +591 -0
  51. package/runtime/modules/models/runtimes.yaml +701 -0
  52. package/runtime/packages/hfs/templates/app/package-scripts/package.json +3 -10
  53. package/runtime/scripts/api/git/config-get.mjs +10 -0
  54. package/runtime/scripts/api/git/lib.mjs +16 -26
  55. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  56. package/runtime/scripts/api/git/remote.mjs +5 -0
  57. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  58. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  59. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  60. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  61. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  62. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  63. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  64. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  65. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  66. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  67. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  68. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  69. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  70. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  71. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  72. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  73. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  74. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
  76. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  77. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  78. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  79. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  80. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  81. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  82. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  83. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  84. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  85. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  86. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  87. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  88. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  89. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  90. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  91. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  92. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  93. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  94. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  95. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  96. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  97. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  98. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  99. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  100. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  101. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  102. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  103. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  104. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  105. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  106. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  107. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  108. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  109. package/runtime/scripts/hfs/param-names.mjs +18 -0
  110. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  111. package/runtime/scripts/hfs/project-graph.mjs +3 -3
  112. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  113. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  114. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  115. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  116. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  117. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  118. package/runtime/scripts/hfs/slots.mjs +78 -108
  119. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  120. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  121. package/runtime/scripts/hfs/view.mjs +3 -2
  122. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  123. package/runtime/scripts/lib/git.mjs +36 -0
  124. package/runtime/scripts/lib/i18n.mjs +86 -0
  125. package/runtime/scripts/lib/json.mjs +13 -3
  126. package/runtime/scripts/lib/language.mjs +24 -5
  127. package/runtime/scripts/lib/list.mjs +20 -0
  128. package/runtime/scripts/lib/path-key.mjs +57 -2
  129. package/runtime/scripts/lib/read-text.mjs +34 -0
  130. package/runtime/scripts/lib/regex.mjs +4 -0
  131. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  132. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  133. package/runtime/scripts/lib/stack-services.mjs +7 -31
  134. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  135. package/runtime/scripts/lib/walk.mjs +4 -3
  136. package/schema-authority.mjs +14 -12
  137. package/service-deps.mjs +7 -13
  138. package/service-isolation.mjs +72 -0
  139. package/size-budget.mjs +1 -1
  140. package/test-world.mjs +5 -4
  141. package/testing.mjs +27 -24
  142. package/transport.mjs +12 -9
  143. package/unit-spec.mjs +1 -1
  144. package/user-copy.mjs +5 -4
  145. package/webhooks.mjs +173 -0
  146. package/runtime/scripts/api/sops/decrypt.mjs +0 -16
  147. package/runtime/scripts/lib/test-secrets.mjs +0 -116
@@ -4,7 +4,7 @@
4
4
  // Both @starci/eslint-canon-be and @starci/eslint-canon-fe ship a byte copy of this file (and of the machine it imports) in their
5
5
  // runtime/ bundle (scripts/hfs/sync-runtime.mjs). There is no second graph: `projectGraph` runs `checkArchitecture`
6
6
  // (scripts/hfs/architecture/index.mjs) with `surface: 'lint'`, which judges exactly the checks whose findings attach to a
7
- // TypeScript file, and indexes the findings by repository-relative path. `hfs check` runs the other surface ('check') and never
7
+ // TypeScript file, and indexes the findings by repository-relative path. `starci app check` runs the other surface ('check') and never
8
8
  // the lint one, so no rule has two enforcers.
9
9
  //
10
10
  // The per-path judgements of the slot manifest (hfs-path-findings.mjs: slot ownership, source file name, spec placement) over the
@@ -16,7 +16,7 @@ import fs from 'node:fs';
16
16
  import { checkArchitecture } from './architecture/index.mjs';
17
17
  import { openHfs } from './slots.mjs';
18
18
  import { pathFindings } from './path-findings.mjs';
19
- import { runGit } from '../api/git/lib.mjs';
19
+ import { lsFiles } from '../api/git/ls-files.mjs';
20
20
  import { posixPath } from '../lib/path-key.mjs';
21
21
  import { onLintSurface } from './architecture/surface.mjs';
22
22
 
@@ -34,7 +34,7 @@ export function projectGraph({ repoRoot, runtimeRoot, injectedTypeScript }) {
34
34
  const opened = openHfs({ root: runtimeRoot, repoRoot: key });
35
35
  const report = checkArchitecture({ repositoryRoot: key, hfs: opened, injectedTypeScript, surface: 'lint' });
36
36
  if (report.errors.length) throw new Error(`the project graph of ${key} cannot be built: ${report.errors.map((e) => `${e.ruleId}: ${e.message}`).join('; ')}`);
37
- const listed = runGit(['ls-files', '-z', '--cached', '--exclude-standard'], { dir: key, maxBuffer: 256 * 1024 * 1024 });
37
+ const listed = lsFiles(['-z', '--cached', '--exclude-standard'], { dir: key, maxBuffer: 256 * 1024 * 1024 });
38
38
  if (listed.error || listed.status !== 0) throw new Error(`the project graph of ${key} needs a Git work tree (git ls-files failed)`);
39
39
  const tracked = listed.stdout.split('\0').filter(Boolean).map(posixPath);
40
40
  const repoFindings = pathFindings({ files: tracked, resolver: opened, profile: opened.repo.profile }).filter((finding) => onLintSurface(key, finding));
@@ -1,11 +1,14 @@
1
1
  // repo-identity.mjs - who a repository is, independent of the folder it is checked out in.
2
- // A git worktree (D:/starci-lanes/<lane>/<name>) and the main checkout of the same repository are ONE repository:
2
+ // A git worktree (<lanes root>/<lane>/<name>) and the main checkout of the same repository are ONE repository:
3
3
  // they carry the same README title, the same stack-declaration project name and the same sibling repositories.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { gitOutput } from '../api/git/lib.mjs';
6
+ import { revParseQuery } from '../api/git/rev-parse-query.mjs';
7
+ import { remote as gitRemote } from '../api/git/remote.mjs';
8
+ import { gitOutputOf } from '../lib/git.mjs';
7
9
 
8
- const git = (root, args) => gitOutput(args, { cwd: root }).trim();
10
+ /** One git call (a scripts/api/git call file) in `root`: its trimmed stdout; throws unless git exits 0. */
11
+ const git = (call, root, args) => gitOutputOf(call(args, { cwd: root })).trim();
9
12
  const real = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
10
13
 
11
14
  /** { repositoryRoot, inWorkTree, home } for `root`: home is the main checkout folder when root is a repository top level. */
@@ -13,9 +16,9 @@ function identityOf(root) {
13
16
  let inWorkTree = false, repositoryRoot = false, home = null;
14
17
  try {
15
18
  // Identity comes from Git only when root IS a repository (or worktree) top level, never a folder inside one.
16
- repositoryRoot = real(git(root, ['rev-parse', '--show-toplevel'])) === real(root);
19
+ repositoryRoot = real(git(revParseQuery, root, ['--show-toplevel'])) === real(root);
17
20
  inWorkTree = true;
18
- const common = path.resolve(root, git(root, ['rev-parse', '--git-common-dir']));
21
+ const common = path.resolve(root, git(revParseQuery, root, ['--git-common-dir']));
19
22
  if (repositoryRoot && path.basename(common) === '.git') home = path.dirname(common);
20
23
  } catch { /* Not a Git work tree; the caller falls back to the folder and package name. */ }
21
24
  return { inWorkTree, repositoryRoot, home };
@@ -31,7 +34,7 @@ export function repositoryName(root) {
31
34
  if (home) return path.basename(home);
32
35
  if (repositoryRoot) {
33
36
  try {
34
- const remote = git(root, ['remote', 'get-url', 'origin']).replace(/[\/]+$/u, '').replace(/\.git$/iu, '');
37
+ const remote = git(gitRemote, root, ['get-url', 'origin']).replace(/[\/]+$/u, '').replace(/\.git$/iu, '');
35
38
  const name = remote.split(/[\/:]/u).pop();
36
39
  if (name) return name;
37
40
  } catch { /* No origin remote. */ }
@@ -0,0 +1,50 @@
1
+ // rule-params-shape.mjs - the shape of `ruleParams` of an app slot manifest (knowledge/hfs/slots.yaml): the parameters both sides
2
+ // share (`common`), the back end's own (`be`) and the front end's overrides (`fe`). Pure: a parsed manifest in, a list of
3
+ // problems out, in the words of modules/schemas/hfs-slots.schema.yaml. scripts/hfs/slots.mjs (loadSlotManifest) is the reader.
4
+ import { isPlainObject } from '../../engine/plain-object.mjs';
5
+ import { paramNamesOk } from './param-names.mjs';
6
+ import { kindParamProblems, scenarioProblem } from './declaration-slots.mjs';
7
+ import { roleListProblems, SCHEMA_AUTHORITIES, unitRolesProblems } from './manifest-shape.mjs';
8
+
9
+ const PROFILES = ['be', 'fe'];
10
+
11
+ /** The shape problems of `m.ruleParams`. */
12
+ export function ruleParamsProblems(m) {
13
+ const bad = [];
14
+ const blockOk = (v) => isPlainObject(v) && Number.isInteger(v.lines) && v.lines >= 2 && Number.isInteger(v.tokens) && v.tokens >= 1 && Object.keys(v).length === 2;
15
+ const fileLinesOk = (v) => isPlainObject(v) && Number.isInteger(v.soft) && v.soft >= 1 && typeof v.hardGrowth === 'boolean' && Object.keys(v).length === 2;
16
+ const rp = m.ruleParams;
17
+ if (!isPlainObject(rp) || Object.keys(rp).some((k) => ![...PROFILES, 'common'].includes(k)) || !isPlainObject(rp.common) || !isPlainObject(rp.be) || (rp.fe !== undefined && !isPlainObject(rp.fe))) bad.push('ruleParams must be a map with common, be and optionally fe');
18
+ else {
19
+ // common holds the parameters both sides share; a side may restate one of them (a valid value) as an override, nothing else.
20
+ const sharedParam = (key, v) => (key === 'fileLines' ? fileLinesOk(v) : blockOk(v));
21
+ const sideOk = (side, own, extra = []) => own.every((k) => side[k] !== undefined) && Object.entries(side).every(([k, v]) => own.includes(k) || extra.includes(k) || (['fileLines', 'duplicateBlock'].includes(k) && sharedParam(k, v)));
22
+ // Edition lite: a side's optional `lite` map overrides keys of the same side (never `lite` itself) when the app declares edition lite.
23
+ const liteOk = (lite, keys) => lite === undefined || (isPlainObject(lite) && Object.keys(lite).length > 0 && Object.keys(lite).every((k) => k !== 'lite' && keys.includes(k)));
24
+ const BE_OWN = ['infraOwners', 'eventBus', 'specDoubles', 'paramNames', 'suffixes', 'bannedSuffixes', 'contractShape', 'unitRoles', 'logicRoles', 'thinRoles', 'patternScenarios', 'kindPatterns', 'addKinds'];
25
+ if (Object.keys(rp.common).length !== 2 || !fileLinesOk(rp.common.fileLines) || !blockOk(rp.common.duplicateBlock)) bad.push('ruleParams.common needs exactly fileLines {soft, hardGrowth} and duplicateBlock {lines >= 2, tokens >= 1}');
26
+ if (!sideOk(rp.be, BE_OWN, ['schemaAuthority', 'lite']) || (rp.be.schemaAuthority !== undefined && !SCHEMA_AUTHORITIES.includes(rp.be.schemaAuthority)) || !liteOk(rp.be.lite, [...BE_OWN, 'fileLines', 'duplicateBlock', 'schemaAuthority'])) bad.push('ruleParams.be needs infraOwners, eventBus, specDoubles, paramNames, suffixes, bannedSuffixes, contractShape {helper}, unitRoles, logicRoles, thinRoles, patternScenarios, kindPatterns and addKinds (fileLines and duplicateBlock live in ruleParams.common, a side may override them); optional: schemaAuthority (' + SCHEMA_AUTHORITIES.join(' | ') + ') and lite (overrides of the same keys)'); else bad.push(...unitRolesProblems(rp.be), ...roleListProblems(rp.be));
27
+ bad.push(...scenarioProblem(rp.be.patternScenarios), ...kindParamProblems(rp.be, m.triggerKinds));
28
+ if (!isPlainObject(rp.be.contractShape) || Object.keys(rp.be.contractShape).length !== 1 || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(String(rp.be.contractShape.helper))) bad.push('ruleParams.be.contractShape must be {helper: <identifier>}');
29
+ const owners = rp.be.infraOwners;
30
+ const ownerId = /^(platform|integrations)\/[a-z][a-z0-9-]*$/;
31
+ if (!isPlainObject(owners) || !Object.keys(owners).length || !Object.entries(owners).every(([key, list]) => key && Array.isArray(list) && list.every((o) => ownerId.test(String(o))) && new Set(list).size === list.length)) bad.push('ruleParams.be.infraOwners must map a non-empty specifier to a list of unique platform/<capability> or integrations/<provider> owners ([] means nowhere)');
32
+ const className = /^[A-Z][A-Za-z0-9]*$/;
33
+ const memberMap = (v, allowEmpty) => isPlainObject(v) && Object.keys(v).length > 0 && Object.values(v).every((list) => Array.isArray(list) && (allowEmpty || list.length > 0) && list.every((x) => className.test(String(x))) && new Set(list).size === list.length);
34
+ const eventBus = rp.be.eventBus;
35
+ if (!isPlainObject(eventBus) || Object.keys(eventBus).sort().join() !== 'classes,imports' || !memberMap(eventBus.imports, true) || !memberMap(eventBus.classes, false)) bad.push('ruleParams.be.eventBus must be {imports: {<module>: [unique PascalCase members]}, classes: {<module>: [non-empty unique PascalCase classes]}}');
36
+ if (!paramNamesOk(rp.be.paramNames)) bad.push('ruleParams.be.paramNames must be a non-empty list of unique {type | typeSuffix, names, nameSuffix?} entries');
37
+ const roleList = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => /^[a-z][a-z0-9-]*$/.test(String(x))) && new Set(v).size === v.length;
38
+ if (!roleList(rp.be.suffixes)) bad.push('ruleParams.be.suffixes must be a non-empty list of unique kebab-case role suffixes');
39
+ if (!roleList(rp.be.bannedSuffixes)) bad.push('ruleParams.be.bannedSuffixes must be a non-empty list of unique kebab-case suffixes');
40
+ else if (roleList(rp.be.suffixes) && rp.be.suffixes.some((x) => rp.be.bannedSuffixes.includes(x))) bad.push('ruleParams.be.suffixes and bannedSuffixes must be disjoint');
41
+ const formNames = ['call', 'new', 'curried', 'object', 'primitive', 'array'];
42
+ const formsOk = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => formNames.includes(x)) && new Set(v).size === v.length;
43
+ const doubleOk = (v) => isPlainObject(v) && /^[A-Za-z][A-Za-z0-9]*$/.test(String(v.double)) && formsOk(v.forms);
44
+ const regexOk = (v) => { try { return typeof v === 'string' && v.length > 0 && Boolean(new RegExp(v)); } catch { return false; } };
45
+ const sd = rp.be.specDoubles;
46
+ if (!isPlainObject(sd) || Object.keys(sd).sort().join() !== 'doubles,fallback,kit' || typeof sd.kit !== 'string' || !sd.kit || !Array.isArray(sd.doubles) || !sd.doubles.length || !sd.doubles.every((e) => doubleOk(e) && regexOk(e.token) && Object.keys(e).length === 3) || !doubleOk(sd.fallback) || Object.keys(sd.fallback).length !== 2) bad.push('ruleParams.be.specDoubles must be {kit, doubles: [{token: regex, double, forms}], fallback: {double, forms}} with forms drawn from call, new, curried, object, primitive, array');
47
+ if (rp.fe !== undefined && (!sideOk(rp.fe, [], ['lite']) || !liteOk(rp.fe.lite, ['fileLines', 'duplicateBlock']))) bad.push('ruleParams.fe may only restate fileLines and duplicateBlock of ruleParams.common, and carry a lite override of them');
48
+ }
49
+ return bad;
50
+ }
@@ -12,11 +12,12 @@ import { found, readJson } from './read.mjs';
12
12
  export const FE_NO_TESTS = 'FE_NO_TESTS';
13
13
  const SPEC_FILE = /(?:\.(?:spec|test)|-spec)\.[cm]?[jt]sx?$/;
14
14
  const TEST_DIRECTORY = /(?:^|\/)(?:e2e|__tests__|__mocks__|test-support)\//;
15
- const TEST_TOOL_FILE = /(?:^|\/)(?:(?:vitest|jest|playwright)\.[^/]+|cypress\.config\.[^/]+|tsconfig\.e2e\.json|e2e\.ya?ml)$/;
16
- const TEST_SCRIPT_NAME = /^(?:pre|post)?test(?::|$)/;
17
- const TEST_RUNNER_COMMAND = /(?:^|[\s&|;(])(?:npx\s+)?(?:vitest|jest|playwright|cypress|mocha)(?=$|[\s&|;)])|\bnode\s+(?:--\S+\s+)*--test\b/;
18
- const TEST_DEPENDENCY = /^(?:vitest|@vitest\/.+|playwright|playwright-core|@playwright\/.+|jest|@jest\/.+|ts-jest|babel-jest|@types\/jest|jest-[\w-]+|mocha|@types\/mocha|cypress|@testing-library\/.+|jsdom|@types\/jsdom|happy-dom|axe-core|@axe-core\/.+|vitest-axe|@starci\/(?:vitest|jest|playwright)-preset)$/;
19
- const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
15
+ export const TEST_TOOL_FILE = /(?:^|\/)(?:(?:vitest|jest|playwright)\.[^/]+|cypress\.config\.[^/]+|tsconfig\.e2e\.json|e2e\.ya?ml)$/;
16
+ // The edition rule (rules/edition.mjs, L01) applies the same lists to the whole app tree in lite: they are the test vocabulary.
17
+ export const TEST_SCRIPT_NAME = /^(?:pre|post)?test(?::|$)/;
18
+ export const TEST_RUNNER_COMMAND = /(?:^|[\s&|;(])(?:npx\s+)?(?:vitest|jest|playwright|cypress|mocha)(?=$|[\s&|;)])|\bnode\s+(?:--\S+\s+)*--test\b/;
19
+ export const TEST_DEPENDENCY = /^(?:vitest|@vitest\/.+|playwright|playwright-core|@playwright\/.+|jest|@jest\/.+|ts-jest|babel-jest|@types\/jest|jest-[\w-]+|mocha|@types\/mocha|cypress|@testing-library\/.+|jsdom|@types\/jsdom|happy-dom|axe-core|@axe-core\/.+|vitest-axe|@starci\/(?:vitest|jest|playwright)-preset)$/;
20
+ export const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
20
21
 
21
22
  /** True for a path FE_NO_TESTS owns: a spec file, a test directory or a test-tool file. */
22
23
  export const isFeTestPath = (file) => SPEC_FILE.test(file) || TEST_DIRECTORY.test(file) || TEST_TOOL_FILE.test(file);
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
5
  /** Files above this size are not read by a tree check (a tracked binary or a generated dump is no source of these laws). */
6
- export const MAX_READ_BYTES = 1024 * 1024;
6
+ const MAX_READ_BYTES = 1024 * 1024;
7
7
 
8
8
  /** The text of `rel` under `repoRoot`, or null when it is absent, unreadable, over MAX_READ_BYTES or binary. */
9
9
  export function readText(repoRoot, rel) {
@@ -6,10 +6,10 @@
6
6
  // - a line of any other tracked text file that matches a secret pattern.
7
7
  // A finding names the file, the line and the pattern, never the value.
8
8
  import { FORBIDDEN_FILES, secretHits } from '../../lib/secret-patterns.mjs';
9
- import { isSopsEnvelope } from '../../lib/test-secrets.mjs';
9
+ import { isSopsEnvelope } from '../../lib/sops-envelope.mjs';
10
10
  import { found, readText } from './read.mjs';
11
11
 
12
- export const PLAINTEXT_SECRET = 'HFS_PLAINTEXT_SECRET';
12
+ const PLAINTEXT_SECRET = 'HFS_PLAINTEXT_SECRET';
13
13
  /** Files whose lines are not secrets: a lockfile carries integrity hashes, never a credential. */
14
14
  const NOT_SCANNED = /(^|\/)package-lock\.json$/;
15
15
 
@@ -19,7 +19,7 @@ export const slotOwnsSecrets = (slot) => slot?.rules?.includes(PLAINTEXT_SECRET)
19
19
  /**
20
20
  * The findings of R06 for one tracked file that no forbidden slot claims: a secret by being (an env file, a key file), an `.enc` that is
21
21
  * no sops envelope, or a line that matches a secret pattern. `text` is the file's text, or null when it cannot be read (binary, too
22
- * large, absent). The whole-tree check reads the work tree and the commit hook (`hfs work-hygiene`) reads the staged blob: one judgement.
22
+ * large, absent). The whole-tree check reads the work tree and the commit hook (`starci app hygiene`) reads the staged blob: one judgement.
23
23
  */
24
24
  export function secretFileFindings({ file, text }) {
25
25
  const being = FORBIDDEN_FILES.find((rule) => rule.test(file));
@@ -7,7 +7,7 @@
7
7
  // finding names the rule rather than only the missing slot. Which folder is a layer is read from the slot manifest, never spelled here.
8
8
  import { found } from './read.mjs';
9
9
 
10
- export const SPEC_PLACEMENT = 'BE_SPEC_PLACEMENT';
10
+ const SPEC_PLACEMENT = 'BE_SPEC_PLACEMENT';
11
11
  const SPEC_FILE = /(?:\.(?:spec|test)|-spec)\.[cm]?[jt]sx?$/;
12
12
  const TEST_SLOT_TESTS = new Set(['unit-beside', 'e2e']);
13
13