@starci/hfs 4.17.0 → 4.17.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.
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +6 -6
- package/runtime/knowledge/hfs/rules.yaml +1 -1
- package/runtime/scripts/hfs/architecture/surface-nesting.d.mts +1 -1
- package/runtime/scripts/hfs/architecture/surface-nesting.mjs +3 -3
- package/runtime/scripts/hfs/architecture/unit-spec-providers.d.mts +2 -1
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +12 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 4.17.2 - 2026-11-02
|
|
6
|
+
|
|
7
|
+
- Changed: bundled pins follow Grammar 0.21.2 with disabled tab items and its coherent canon cohort; existing runtime checks are unchanged.
|
|
8
|
+
|
|
9
|
+
## 4.17.1 - 2026-11-02
|
|
10
|
+
|
|
11
|
+
- Fixed: unit provider contracts recognize the real Nest `Optional()` injection decorator through direct, aliased and namespace imports; TypeScript optional parameters and unrelated decorator names still require their providers.
|
|
12
|
+
- Fixed: cross-file `label-in-grid` judges the nearest peer-card layout (`Grid kind="cards"`), matching the existing one-file rule. Dated week regions keep their day headings; labelled peer tiles still fail, with generic regression coverage for both semantic week layouts and nested layout ownership. Bundled R356 knowledge names the same owner.
|
|
13
|
+
|
|
5
14
|
## 4.17.0 - 2026-11-02
|
|
6
15
|
|
|
7
16
|
- Fixed: the architecture machine run on an app root that has its own tsconfig threw "Cannot destructure property 'lines'" out of `checkClones`: the app resolver has no rule parameters, so the clone threshold (`ruleParams.common.duplicateBlock`, shared by every profile) is read from a side. Spec `architecture-clones-app-root`.
|
package/package.json
CHANGED
|
@@ -31,35 +31,35 @@ pins:
|
|
|
31
31
|
source: packages/event-stream/package.json
|
|
32
32
|
why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
|
|
33
33
|
'@starci/grammar':
|
|
34
|
-
version: 0.21.
|
|
34
|
+
version: 0.21.2
|
|
35
35
|
group: starci
|
|
36
36
|
install: registry
|
|
37
37
|
side: fe
|
|
38
38
|
source: packages/grammar/package.json
|
|
39
39
|
why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
|
|
40
40
|
'@starci/eslint-canon-be':
|
|
41
|
-
version: 3.11.
|
|
41
|
+
version: 3.11.9
|
|
42
42
|
group: starci
|
|
43
43
|
install: registry
|
|
44
44
|
side: be
|
|
45
45
|
source: packages/eslint-canon-be/package.json
|
|
46
46
|
why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
|
|
47
47
|
'@starci/eslint-canon-fe':
|
|
48
|
-
version: 9.9.
|
|
48
|
+
version: 9.9.2
|
|
49
49
|
group: starci
|
|
50
50
|
install: registry
|
|
51
51
|
side: fe
|
|
52
52
|
source: packages/eslint-canon-fe/package.json
|
|
53
53
|
why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
|
|
54
54
|
'@starci/eslint-grammar-core':
|
|
55
|
-
version: 0.21.
|
|
55
|
+
version: 0.21.2
|
|
56
56
|
group: starci
|
|
57
57
|
install: registry
|
|
58
58
|
side: fe
|
|
59
59
|
source: packages/eslint-grammar-core/package.json
|
|
60
60
|
why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
|
|
61
61
|
'@starci/eslint-grammar-offset-pop':
|
|
62
|
-
version: 0.21.
|
|
62
|
+
version: 0.21.2
|
|
63
63
|
group: starci
|
|
64
64
|
install: registry
|
|
65
65
|
side: fe
|
|
@@ -106,7 +106,7 @@ pins:
|
|
|
106
106
|
source: packages/ai-router/package.json
|
|
107
107
|
why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
|
|
108
108
|
'@starci/hfs':
|
|
109
|
-
version: 4.17.
|
|
109
|
+
version: 4.17.2
|
|
110
110
|
group: starci
|
|
111
111
|
install: registry
|
|
112
112
|
side: both
|
|
@@ -6323,7 +6323,7 @@ rules:
|
|
|
6323
6323
|
- {path: packages/eslint-grammar-core/src/surface-card.spec.ts, editions: [lite, full], codes: [FE_SURFACE_CARD_KIND]}
|
|
6324
6324
|
- id: R356
|
|
6325
6325
|
code: "FE_SURFACE_NESTING"
|
|
6326
|
-
law: "Across files, a card is not drawn inside a card, a Pane surface or an overlay, a DataTable is not drawn inside a card, a PaneGrid stands only in Page chrome=\"panes\" or a Pane, a Section or Heading holds more than one card, and a labelled card is not repeated in
|
|
6326
|
+
law: "Across files, a card is not drawn inside a card, a Pane surface or an overlay, a DataTable is not drawn inside a card, a PaneGrid stands only in Page chrome=\"panes\" or a Pane, a Section or Heading holds more than one card, and a labelled card is not repeated in the peer-card layout Grid kind=\"cards\": the render graph replaces every component by what it draws and every children by what its caller handed it."
|
|
6327
6327
|
kinds: [lint]
|
|
6328
6328
|
gates: [pre-commit, settle, land, ci]
|
|
6329
6329
|
failureCodes: ["FE_SURFACE_NESTING"]
|
|
@@ -13,7 +13,7 @@ import type { CheckInput } from './hfs-graph.mjs';
|
|
|
13
13
|
* table-in-card a `DataTable` inside a card (a table stands on the page);
|
|
14
14
|
* grid-outside-panes a `PaneGrid` inside a card or a Page whose chrome is not `panes`;
|
|
15
15
|
* heading-over-card a `Heading`, or a region `Section`, whose only neighbour or child is one card (a card carries its own label);
|
|
16
|
-
* label-in-grid a labelled card repeated by a `.map` in a `Grid` (a repeated tile has no label).
|
|
16
|
+
* label-in-grid a labelled card repeated by a `.map` in a `Grid kind="cards"` (a repeated tile has no label).
|
|
17
17
|
*
|
|
18
18
|
* A finding carries `crossFile: true` when the two elements are drawn in different files, which is what one-file lint cannot see.
|
|
19
19
|
*/
|
|
@@ -11,7 +11,7 @@ import { RenderGraph } from './render-graph.mjs';
|
|
|
11
11
|
* table-in-card a `DataTable` inside a card (a table stands on the page);
|
|
12
12
|
* grid-outside-panes a `PaneGrid` inside a card or a Page whose chrome is not `panes`;
|
|
13
13
|
* heading-over-card a `Heading`, or a region `Section`, whose only neighbour or child is one card (a card carries its own label);
|
|
14
|
-
* label-in-grid a labelled card repeated by a `.map` in a `Grid` (a repeated tile has no label).
|
|
14
|
+
* label-in-grid a labelled card repeated by a `.map` in a `Grid kind="cards"` (a repeated tile has no label).
|
|
15
15
|
*
|
|
16
16
|
* A finding carries `crossFile: true` when the two elements are drawn in different files, which is what one-file lint cannot see.
|
|
17
17
|
*/
|
|
@@ -93,9 +93,9 @@ export function checkSurfaceNesting(input) {
|
|
|
93
93
|
}
|
|
94
94
|
if (element.repeated) {
|
|
95
95
|
const inGrid = [...ancestors].reverse().find(entry => isGrammarIn(entry.element, SURFACE_VOCABULARY.grids));
|
|
96
|
-
const card = inGrid ? cardOf(graph, element) : null;
|
|
96
|
+
const card = inGrid?.element.attributes.get('kind')?.text === 'cards' ? cardOf(graph, element) : null;
|
|
97
97
|
if (inGrid && card && card.attributes.has('label')) {
|
|
98
|
-
report(element, inGrid.element, 'label-in-grid', `${describe(element)} repeats a labelled card in a Grid: a repeated tile has no label`, ancestors, stack);
|
|
98
|
+
report(element, inGrid.element, 'label-in-grid', `${describe(element)} repeats a labelled card in a Grid kind="cards": a repeated tile has no label`, ancestors, stack);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
},
|
|
@@ -4,7 +4,8 @@ import type { MachineContext } from './machine-ast.mjs';
|
|
|
4
4
|
/**
|
|
5
5
|
* R48 `unit-spec-providers` (BE_SPEC_QUALITY), the unit test standard's rule 3: the unit spec of a service builds its
|
|
6
6
|
* subject with `Test.createTestingModule({ providers: [...] })`, and that providers array EQUALS the constructor
|
|
7
|
-
* dependencies of the service under test: the service class first, then one provider for each constructor parameter.
|
|
7
|
+
* dependencies of the service under test: the service class first, then one provider for each required constructor parameter.
|
|
8
|
+
* A parameter decorated by `Optional()` imported from `@nestjs/common` may be omitted to exercise its absent-provider branch.
|
|
8
9
|
*
|
|
9
10
|
* The service under test is the sibling `<name>.service.ts` of every `<name>.service.spec.ts` under src/ or apps/ (a spec is
|
|
10
11
|
* outside the production program, so it is read from disk). A parameter decorated with a custom `Inject<Thing>()` provides
|
|
@@ -9,7 +9,8 @@ import { checkFunctionSubject, functionSubjectExports, keyOf as literalKey, valu
|
|
|
9
9
|
/**
|
|
10
10
|
* R48 `unit-spec-providers` (BE_SPEC_QUALITY), the unit test standard's rule 3: the unit spec of a service builds its
|
|
11
11
|
* subject with `Test.createTestingModule({ providers: [...] })`, and that providers array EQUALS the constructor
|
|
12
|
-
* dependencies of the service under test: the service class first, then one provider for each constructor parameter.
|
|
12
|
+
* dependencies of the service under test: the service class first, then one provider for each required constructor parameter.
|
|
13
|
+
* A parameter decorated by `Optional()` imported from `@nestjs/common` may be omitted to exercise its absent-provider branch.
|
|
13
14
|
*
|
|
14
15
|
* The service under test is the sibling `<name>.service.ts` of every `<name>.service.spec.ts` under src/ or apps/ (a spec is
|
|
15
16
|
* outside the production program, so it is read from disk). A parameter decorated with a custom `Inject<Thing>()` provides
|
|
@@ -80,6 +81,7 @@ function serviceClassFor(ts, rel, serviceRel, serviceFile, imports, list, report
|
|
|
80
81
|
}
|
|
81
82
|
function checkDependencies(kit, rel, service, serviceFile, list, provided, report) {
|
|
82
83
|
const { serviceName, serviceClass } = service;
|
|
84
|
+
const checker = kit.checkerOf(serviceFile.sourceFile);
|
|
83
85
|
const dependencies = constructorDependencies(kit, serviceFile, serviceClass);
|
|
84
86
|
const expected = [];
|
|
85
87
|
for (const dependency of dependencies) {
|
|
@@ -87,18 +89,23 @@ function checkDependencies(kit, rel, service, serviceFile, list, provided, repor
|
|
|
87
89
|
report(list, `${rel}: a constructor dependency of ${serviceName.name} cannot be resolved to a token (${dependency.reason}), so its provider cannot be checked; inject it through an exported Inject<Thing>() decorator or a class type.`);
|
|
88
90
|
continue;
|
|
89
91
|
}
|
|
90
|
-
|
|
92
|
+
const optional = kit.decorators(dependency.parameter).some(decorator => {
|
|
93
|
+
const expression = decorator.expression;
|
|
94
|
+
return kit.ts.isCallExpression(expression) && expression.arguments.length === 0
|
|
95
|
+
&& kit.isImportOf(checker, expression.expression, 'Optional', '@nestjs/common');
|
|
96
|
+
});
|
|
97
|
+
expected.push({ name: dependency.name, optional });
|
|
91
98
|
}
|
|
92
99
|
const remaining = [...expected];
|
|
93
100
|
for (const item of provided.slice(1)) {
|
|
94
|
-
const index = remaining.
|
|
101
|
+
const index = remaining.findIndex(dependency => dependency.name === item.name);
|
|
95
102
|
if (index === -1)
|
|
96
103
|
report(item.node, `${rel}: ${item.name} is provided but is not a constructor dependency of ${serviceName.name}; a unit spec provides exactly what the constructor needs.`);
|
|
97
104
|
else
|
|
98
105
|
remaining.splice(index, 1);
|
|
99
106
|
}
|
|
100
|
-
for (const
|
|
101
|
-
report(list, `${rel}: ${name} is a constructor dependency of ${serviceName.name} but is not provided; add \`{ provide: ${name}, useValue: double }\` (or the class itself) to the providers.`);
|
|
107
|
+
for (const dependency of remaining.filter(item => !item.optional))
|
|
108
|
+
report(list, `${rel}: ${dependency.name} is a constructor dependency of ${serviceName.name} but is not provided; add \`{ provide: ${dependency.name}, useValue: double }\` (or the class itself) to the providers.`);
|
|
102
109
|
}
|
|
103
110
|
function checkTestingModule({ ts, kit, rel, serviceRel, serviceFile, imports, call, report }) {
|
|
104
111
|
const list = providerList(ts, call, rel, report);
|