@starci/hfs 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1 - 2026-09-30
4
+
5
+ - Fixed: `package.json` now has `exports` for `./runtime/*` and `./package.json`, so other published packages can resolve the runtime copy it carries (`@starci/hfs/runtime/knowledge/hfs/slots.yaml`, `@starci/hfs/runtime/engine/yaml.mjs`) with `import.meta.resolve` or `require.resolve`, wherever the package is installed. `@starci/eslint-canon-be` 1.7.1 reads the slot manifest this way.
6
+ - Changed: `CHANGELOG.md` ships in the package (`files`).
7
+ - The bin is unchanged; the runtime copy is resynced (`canon-pins.yaml` states the new pins).
8
+
9
+ ## 1.0.0 - 2026-09-30
10
+
11
+ - First registry publication: the `hfs` command line with its runtime bundle, sync files and templates.
package/README.md CHANGED
@@ -36,3 +36,9 @@ except `hfs init`, which writes `hfs.json` only when none exists.
36
36
  check can emit). After changing `scripts/lib/hfs-check.mjs`, `scripts/lib/hfs-slots.mjs`, `knowledge/hfs/slots.yaml`,
37
37
  `knowledge/hfs/canon-pins.yaml` or the catalog entries of those codes, run `node packages/hfs/scripts/sync-runtime.mjs`;
38
38
  `tests/hfs-cli.spec.mjs` fails on a stale copy. Bump `version` here and in the pin when the behaviour changes.
39
+
40
+ ## Serving knowledge to other packages
41
+
42
+ `package.json` `exports` opens `./runtime/*`, so a package that needs a runtime file resolves it from the installed copy
43
+ (`import.meta.resolve("@starci/hfs/runtime/knowledge/hfs/slots.yaml")`), never from the product repository or a link path.
44
+ `@starci/eslint-canon-be` does this and lists `@starci/hfs` in `dependencies` at the exact version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "The HFS command line of a StarCi product repository: hfs check, init, explain, sync and work-hygiene. Self-contained: it carries the runtime files it reads.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -8,12 +8,17 @@
8
8
  "bin": {
9
9
  "hfs": "./bin/hfs.mjs"
10
10
  },
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ "./runtime/*": "./runtime/*"
14
+ },
11
15
  "files": [
12
16
  "bin/hfs.mjs",
13
17
  "runtime/**",
14
18
  "sync/**",
15
19
  "templates/**",
16
- "README.md"
20
+ "README.md",
21
+ "CHANGELOG.md"
17
22
  ],
18
23
  "engines": {
19
24
  "node": ">=22.13.0"
@@ -17,12 +17,12 @@ pins:
17
17
  source: packages/grammar/package.json
18
18
  why: nivo-fe pins 0.4.11 and 0.5.0 in one workspace, starci-next-fe 0.5.1, miamia-fe 0.5.0; the runtime source is 0.7.1.
19
19
  '@starci/eslint-canon-be':
20
- version: 1.7.0
20
+ version: 1.7.1
21
21
  group: starci
22
22
  install: registry
23
23
  side: be
24
24
  source: packages/eslint/be/package.json
25
- why: 'back-end repositories carry 1.2.1 and 1.2.2 while the runtime source is 1.7.0: the round-3 catch-up landed the nest code-pattern profile''s version and content digest, left unbumped when R78-R82 landed, plus the inbox-dedupe-required shape-detection fix; 1.7.0 adds named-entity-manager-only (R83).'
25
+ why: 'back-end repositories carry 1.2.1 and 1.2.2 while the runtime source is 1.7.0: the round-3 catch-up landed the nest code-pattern profile''s version and content digest, left unbumped when R78-R82 landed, plus the inbox-dedupe-required shape-detection fix; 1.7.0 adds named-entity-manager-only (R83); 1.7.1 reads the slot manifest from the installed @starci/hfs runtime copy instead of the product repository.'
26
26
  '@starci/eslint-canon-fe':
27
27
  version: 5.1.2
28
28
  group: starci
@@ -69,12 +69,12 @@ pins:
69
69
  side: fe
70
70
  source: packages/playwright-preset/package.json
71
71
  '@starci/hfs':
72
- version: 1.0.0
72
+ version: 1.0.1
73
73
  group: starci
74
74
  install: registry
75
75
  side: both
76
76
  source: packages/hfs/package.json
77
- why: the hfs command line (check, init, explain) of every product repository, installed from the npm registry.
77
+ why: the hfs command line (check, init, explain) of every product repository, installed from the npm registry; 1.0.1 exports runtime/* so the eslint canons resolve the knowledge it carries.
78
78
  # --- tooling
79
79
  typescript:
80
80
  version: 5.9.3
@@ -92,14 +92,15 @@ ruleParams:
92
92
  globalModules: [src/modules/platform/config/, src/modules/platform/logging/, src/modules/platform/database/]
93
93
  # HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft.
94
94
  fileLines: {soft: 500, hardGrowth: true}
95
- # HFS_DUPLICATE_BLOCK: a token-normalised block of at least this many source lines that appears in two owners.
96
- duplicateBlockLines: 25
95
+ # R21 HFS_DUPLICATE_CODE: the ONE threshold of duplicate code (the architecture machine reads it here; no other file states it):
96
+ # a token-normalised block of at least `lines` source lines and `tokens` tokens that appears twice.
97
+ duplicateBlock: {lines: 8, tokens: 60}
97
98
  fe:
98
99
  # Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
99
100
  fileLines: {soft: 500, hardGrowth: true}
100
101
  # FE_TRANSPORT_OWNER: the one module per app that may call fetch.
101
102
  clientModule: "apps/<app>/src/modules/api/client.ts"
102
- duplicateBlockLines: 25
103
+ duplicateBlock: {lines: 8, tokens: 60}
103
104
 
104
105
  slots:
105
106
 
@@ -144,13 +144,13 @@ function manifestShapeProblems(m) {
144
144
  for (const key of Object.keys(def ?? {})) if (!['mayImport', 'acyclic', 'lowerLayerOnly'].includes(key)) bad.push(`tiers.${profile}.${tier}.${key} is not a tier field`);
145
145
  }
146
146
  }
147
- const blockLinesOk = (v) => Number.isInteger(v) && v >= 2;
147
+ const blockOk = (v) => isMap(v) && Number.isInteger(v.lines) && v.lines >= 2 && Number.isInteger(v.tokens) && v.tokens >= 1 && Object.keys(v).length === 2;
148
148
  const fileLinesOk = (v) => isMap(v) && Number.isInteger(v.soft) && v.soft >= 1 && typeof v.hardGrowth === 'boolean' && Object.keys(v).length === 2;
149
149
  const rp = m.ruleParams;
150
150
  if (!isMap(rp) || Object.keys(rp).some((k) => !PROFILES.includes(k)) || !PROFILES.every((p) => isMap(rp[p]))) bad.push('ruleParams must be a map with be and fe');
151
151
  else {
152
- if (!(strList(rp.be.globalModules) && new Set(rp.be.globalModules).size === rp.be.globalModules.length) || !fileLinesOk(rp.be.fileLines) || !blockLinesOk(rp.be.duplicateBlockLines) || Object.keys(rp.be).length !== 3) bad.push('ruleParams.be needs globalModules (unique paths), fileLines {soft, hardGrowth} and duplicateBlockLines (integer >= 2)');
153
- if (!fileLinesOk(rp.fe.fileLines) || typeof rp.fe.clientModule !== 'string' || !rp.fe.clientModule || !blockLinesOk(rp.fe.duplicateBlockLines) || Object.keys(rp.fe).length !== 3) bad.push('ruleParams.fe needs fileLines {soft, hardGrowth}, clientModule and duplicateBlockLines (integer >= 2)');
152
+ if (!(strList(rp.be.globalModules) && new Set(rp.be.globalModules).size === rp.be.globalModules.length) || !fileLinesOk(rp.be.fileLines) || !blockOk(rp.be.duplicateBlock) || Object.keys(rp.be).length !== 3) bad.push('ruleParams.be needs globalModules (unique paths), fileLines {soft, hardGrowth} and duplicateBlock {lines >= 2, tokens >= 1}');
153
+ if (!fileLinesOk(rp.fe.fileLines) || typeof rp.fe.clientModule !== 'string' || !rp.fe.clientModule || !blockOk(rp.fe.duplicateBlock) || Object.keys(rp.fe).length !== 3) bad.push('ruleParams.fe needs fileLines {soft, hardGrowth}, clientModule and duplicateBlock {lines >= 2, tokens >= 1}');
154
154
  }
155
155
  if (!Array.isArray(m.slots) || !m.slots.length) { bad.push('slots must be a non-empty list'); return bad; }
156
156
  const slotKeys = new Set(['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'allows', 'forbids', 'layers', 'budget', 'managedBy', 'rules', 'goesTo', 'why', 'since', 'retiredIn', 'successor']);
@@ -535,7 +535,7 @@ export function createSlotResolver(manifest, repo) {
535
535
  });
536
536
  }
537
537
 
538
- /** The rule parameters of one profile (be: globalModules, fileLines, duplicateBlockLines; fe: fileLines, clientModule, duplicateBlockLines), as a frozen deep copy. */
538
+ /** The rule parameters of one profile (be: globalModules, fileLines, duplicateBlock; fe: fileLines, clientModule, duplicateBlock), as a frozen deep copy. */
539
539
  export function ruleParams(manifest, profile) {
540
540
  if (!PROFILES.includes(profile)) fail('HFS_MANIFEST_INVALID', `ruleParams has no profile ${profile}`, { profile });
541
541
  const deepFreeze = (v) => { if (v && typeof v === 'object') Object.values(v).forEach(deepFreeze); return Object.freeze(v); };
@@ -662,6 +662,12 @@ export function loadRuleCatalog({ root = skillRoot, file = path.join(root, HFS_R
662
662
  byCode: (code) => byCode.get(code) ?? null,
663
663
  /** The rules that run at a gate. */
664
664
  forGate: (gate) => list.filter((r) => r.gates.includes(gate)),
665
+ /** The catalogued why code of a lint finding's rule id (`starci-be/<id>`, `starci-fe/<id>`), or undefined. */
666
+ lintCode: (ruleId) => {
667
+ const [plugin, id] = String(ruleId ?? '').split('/');
668
+ const kind = plugin === 'starci-be' ? 'eslint-be' : plugin === 'starci-fe' ? 'eslint-fe' : null;
669
+ return kind ? list.find((r) => r.enforcers.some((e) => e.kind === kind && e.id === id))?.code : undefined;
670
+ },
665
671
  /** The rules one enforcer judges, e.g. forEnforcer('eslint-be', 'error-home'). */
666
672
  forEnforcer: (kind, id) => list.filter((r) => r.enforcers.some((e) => e.kind === kind && e.id === id)),
667
673
  /** Every enforcer still owed, as {rule, kind, id}. */
@@ -671,5 +677,5 @@ export function loadRuleCatalog({ root = skillRoot, file = path.join(root, HFS_R
671
677
  });
672
678
  }
673
679
 
674
- /** The 77 rules of this runtime's catalog, frozen, in id order. */
680
+ /** The rules of this runtime's catalog, frozen, in id order. */
675
681
  export const rules = (options) => loadRuleCatalog(options).rules;