stylegallery 0.1.3 → 0.1.6
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/AGENTS.md +2 -1
- package/DOMAINS.md +13 -9
- package/GOVERNANCE.md +9 -1
- package/README.md +26 -5
- package/consumer-reference/agent-native/README.md +12 -1
- package/consumer-reference/agent-native/v2/admission-policy.json +176 -1
- package/consumer-reference/agent-native/v2/material-registry.json +1 -1
- package/consumer-reference/agent-native/v2/schema/admission-policy.schema.json +3 -3
- package/consumer-reference/agent-native/v2/schema/material-record.schema.json +1 -1
- package/design-engineering/component-contract.md +98 -0
- package/design-engineering/consumer-migration-readiness.md +124 -0
- package/design-engineering/decision-tree.md +69 -0
- package/design-engineering/index.md +12 -2
- package/design-engineering/interface-craft.md +107 -0
- package/design-engineering/worked-examples.md +127 -0
- package/design-terminology/comparison-workflow.md +98 -0
- package/design-terminology/concept-families.md +78 -0
- package/design-terminology/conflict-cases.md +109 -0
- package/design-terminology/index.md +73 -0
- package/design-terminology/relation-types.md +109 -0
- package/design-terminology/source-kinds.md +77 -0
- package/design-terminology/source-vocabularies.md +93 -0
- package/game-ui/classification.md +82 -0
- package/game-ui/decision-tree.md +81 -0
- package/game-ui/index.md +11 -1
- package/game-ui/reference-record.md +62 -0
- package/game-ui/screen-hierarchy.md +107 -0
- package/game-ui/screen-recipes.md +101 -0
- package/game-ui/unity/architecture.md +166 -0
- package/game-ui/unity/cli-loop.md +84 -0
- package/game-ui/unity/org-wiki.md +146 -0
- package/game-ui/unity/repository-map.md +102 -0
- package/game-ui/unity/ui-systems.md +85 -0
- package/game-ui/verification-workflow.md +100 -0
- package/guides/vocabulary.md +1 -1
- package/index.md +10 -1
- package/log.md +43 -0
- package/motion/decision-tree.md +70 -0
- package/motion/index.md +14 -2
- package/motion/interaction-recipes.md +164 -0
- package/motion/motion-brief.md +125 -0
- package/motion/practice-reference.md +145 -0
- package/motion/review-workflow.md +94 -0
- package/motion/vocabulary.md +82 -0
- package/package.json +36 -1
- package/platform-guides/adaptation-workflow.md +76 -0
- package/platform-guides/android-interaction.md +74 -0
- package/platform-guides/apple-interaction.md +97 -0
- package/platform-guides/compatibility-matrix.md +97 -0
- package/platform-guides/index.md +13 -2
- package/platform-guides/windows-interaction.md +75 -0
- package/quality/claim-records/design-terminology-domain-scope.md +57 -0
- package/quality/evidence/executable-evidence.md +1 -1
- package/quality/index.md +2 -2
- package/recipes/homepage.md +8 -0
- package/scripts/agent-native/v2/material-admission.mjs +1 -1
- package/scripts/agent-native/v2/material-identity.mjs +2 -2
- package/scripts/agent-native/v2/material-mcp-adapter.mjs +1 -1
- package/scripts/agent-native/v2/material-queries.mjs +60 -12
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision Record
|
|
3
|
+
title: Design Terminology Domain Scope Decision
|
|
4
|
+
description: Decision to add Design Terminology as the sixth StyleGallery domain, with four placement alternatives compared and the domain's independent decision surface defined.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Design Terminology Domain Scope Decision
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
|
|
11
|
+
StyleGallery adds Design Terminology as a sixth explicit domain. The domain's independent decision surface, in one sentence:
|
|
12
|
+
|
|
13
|
+
> Design Terminology binds design terms to their named sources, classifies them by source kind and concept family, and judges typed semantic relations and conflicts between terms from different sources.
|
|
14
|
+
|
|
15
|
+
Concretely, this domain and only this domain decides: which named source a term belongs to, what kind of source that is, which concept family the term serves, and which typed relation with which boundary holds between two terms. The five existing domains keep their boundaries unchanged.
|
|
16
|
+
|
|
17
|
+
The domain does not own: StyleGallery's internal canonical naming (the [Controlled Vocabulary](../../guides/vocabulary.md) keeps it), actual UI behavior or implementation methods, accessibility rules, platform-specific interaction adaptation (Platform Guides keeps it), and any universal prescription about which term is generally better.
|
|
18
|
+
|
|
19
|
+
## Alternatives Compared
|
|
20
|
+
|
|
21
|
+
| Candidate | Strength | Weakness | Disposition |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| Extend `guides/vocabulary.md` | Close to existing terminology stewardship | Mixes StyleGallery's internal canonical word set with external systems' term meanings on one authoritative page; the vocabulary would then compete with external definitions it is supposed to route around | Rejected: internal canonical control and external comparative meaning are different decisions with different evidence and freshness rules |
|
|
24
|
+
| `platform-guides/terminology/` | Reuses an existing comparative domain | Tools (Figma), specifications (DTCG Format), and pattern libraries are not platforms; hosting them under Platform Guides would erase the source-kind axis this domain depends on | Rejected: source kind, not platform comparison, is the primary axis |
|
|
25
|
+
| Shared non-domain `terminology/` | Easy to reference across domains | No named owner, lifecycle, validator, or review authority; consumer-reference already carries the no-second-authority rule for shared infrastructure | Rejected: ungoverned placement reproduces the silent-collision problem the content exists to solve |
|
|
26
|
+
| New sixth domain `design-terminology/` | Owns an independent lifecycle, review owner, validator coverage, and experimental-to-stable promotion path | Widest infrastructure blast radius: manifest, validators, schemas, sealed material cardinality, and package inventory all change | Adopted, with the conditions below |
|
|
27
|
+
|
|
28
|
+
Adoption conditions: content starts `experimental`; a machine-readable term/concept/source/relation registry is a promotion prerequisite; every external meaning claim carries an official source and review date; typed relations replace plain term-pair tables.
|
|
29
|
+
|
|
30
|
+
## Naming
|
|
31
|
+
|
|
32
|
+
Terminology, not topology: topology suggests a formal relation graph or ontology that this domain does not yet provide; v0.1 compares definitions, usage scope, and conflicts. Terminology, not vocabulary: the [Controlled Vocabulary](../../guides/vocabulary.md) owns StyleGallery's canonical internal word set; this domain compares the meanings and relations of terms used by external named sources.
|
|
33
|
+
|
|
34
|
+
## Context
|
|
35
|
+
|
|
36
|
+
Named design systems, platform guidelines, specifications, and design tools define overlapping terms differently, and handoffs between them fail on silent term collisions. Recording these definitions inside Layout would violate the pattern boundary; the controlled vocabulary page is for internal canonical terms; Platform Guides compares interaction conventions rather than terminology.
|
|
37
|
+
|
|
38
|
+
## Warrant
|
|
39
|
+
|
|
40
|
+
A separate domain lets terminology comparison name its own evidence boundary: cited official sources, dated structural readings, typed relations with boundaries, and working translations that never override repository policy.
|
|
41
|
+
|
|
42
|
+
## Consequences
|
|
43
|
+
|
|
44
|
+
- `design-terminology/**` begins `experimental` with a named review owner and validator coverage.
|
|
45
|
+
- The domain manifest, root routes, quality scope, vocabulary domain list, and governance rows record six domains; Consumer Reference remains shared infrastructure and still cannot add a domain row.
|
|
46
|
+
- Record consistency is machine-enforced from v0.1 by `scripts/validate-design-terminology.mjs` (closed relation types, recorded terms on both sides, non-trivial boundaries, contradiction pairs, date and status shape, no orphan terms). Human semantic review is required at stable promotion only, per the repository promotion rules; it is not a merge gate for `experimental` content.
|
|
47
|
+
- Domain identity and cardinality are currently sealed in multiple surfaces (closed schema enums, identity sets, discover cardinality, package inventory, snapshot tests) requiring synchronized manual edits when a domain is added. This is recorded technical debt, not tamper evidence alone: one author changing all copies in one commit weakens duplication as independent evidence while keeping drift risk. Revisit triggers: a seventh domain id is added, manual-change sites exceed twelve, a cardinality drift incident occurs, or registry generation changes. Target structure when triggered: one canonical domain registry generating enums, lists, and package inventory, with an independently reviewed sealed digest as the tamper check. Named review owner for this debt: the Design Terminology domain owner until a governance owner is designated.
|
|
48
|
+
- Term relations recorded by the domain are working judgments, not repository policy for other domains.
|
|
49
|
+
|
|
50
|
+
## Boundary Or Limitation
|
|
51
|
+
|
|
52
|
+
This decision establishes ownership and governance only. It does not verify that any cited external vocabulary is current beyond each record's review date, does not make any external definition authoritative for product implementation, and does not claim the typed relation model is complete beyond the recorded cases.
|
|
53
|
+
|
|
54
|
+
## IA Navigation
|
|
55
|
+
|
|
56
|
+
Parent: [Claim Records](index.md) in [Quality Gates](../index.md).
|
|
57
|
+
Next: [StyleGallery Domains](../../DOMAINS.md) for the active domain manifest.
|
|
@@ -32,7 +32,7 @@ Executable evidence identifies which claims are machine-enforced, which are only
|
|
|
32
32
|
| Markdown links resolve inside the repository. | `scripts/validate-links.mjs` | `node scripts/validate-links.mjs --json` | All checked Markdown links resolve to local files or are intentionally external or anchors. | A local link that escapes the repository or points at a missing file must fail. | Proves link targets exist, not that the target content supports a claim. |
|
|
33
33
|
| Catalog and generated pattern indexes match the pattern data source. | `scripts/validate-catalog.mjs` | `node scripts/validate-catalog.mjs --json` | `CATALOG.md`, category indexes, and expected pattern files match `scripts/pattern-data.mjs`. | Missing, unexpected, or unlisted pattern files must fail. | Proves catalog consistency, not pattern usefulness. |
|
|
34
34
|
| Governance, lifecycle, generated-file, ownership, and stale-content policy remain discoverable and CI-enforced. | `scripts/validate-governance.mjs` and `scripts/test-validate-governance.mjs` | `node scripts/validate-governance.mjs --json`; `node scripts/test-validate-governance.mjs --json` | `GOVERNANCE.md`, `.github/CODEOWNERS`, generated warnings, generated metadata, root link targets, lifecycle states, `scheduled_stale_audit` metadata, and CI wiring are present. | Missing governance file, generated warning, generated metadata, CODEOWNERS coverage, or stale policy fixtures must fail. Preferred governance prose and link labels emit warnings. | Proves governance policy is present and linked, not that CODEOWNERS users have verified repository write access. |
|
|
35
|
-
| Domain topology, metadata, provenance, scope boundaries, and root routes remain enforced. | `scripts/validate-domains.mjs` and `scripts/test-validate-domains.mjs` | `node scripts/validate-domains.mjs --json`; `node scripts/test-validate-domains.mjs` |
|
|
35
|
+
| Domain topology, metadata, provenance, scope boundaries, and root routes remain enforced. | `scripts/validate-domains.mjs` and `scripts/test-validate-domains.mjs` | `node scripts/validate-domains.mjs --json`; `node scripts/test-validate-domains.mjs` | Six governed domains and their declared leaves are reachable and attributed. | Domain metadata, immutable provenance, scope boundaries, and root-route fixtures must fail. | A full SHA proves content identity syntax, not publisher authenticity, source ownership, or local quality. |
|
|
36
36
|
| Consumer-reference handoffs, schema/runtime parity, repository-local record paths, lifecycle separation, ownership truth, and dependency direction remain enforced. | `scripts/validate-consumer-reference.mjs` and `scripts/test-validate-consumer-reference.mjs` | `node scripts/validate-consumer-reference.mjs --json`; `node scripts/test-validate-consumer-reference.mjs --json` | Declared and reasoned-not-applicable fixtures exit zero, every shipped schema rule has accepted/rejected parity coverage, all repository handoffs declare applicability, fixtures remain related, and ownership discloses placeholder single-account review. | Unknown top-level or nested properties, invalid schema fields, missing handoff or reason, stable with ended support, boolean independence, unresolved/absolute/scheme/network/parent/item-or-record-symlink/non-JSON paths, scaffold success, sixth-domain classification, literal or supported computed reverse imports, and repository handoff omissions must fail with named finding codes and non-zero child exit. | Proves receiver-contract consistency, bounded static dependency detection, and path containment, not general JavaScript data-flow safety, visual quality, independent adoption, verified owner permissions, or consumer implementation conformance. |
|
|
37
37
|
| Promotion governance remains a closed JSON-only, invariant-scoped, deferred example contract. | `scripts/validate-promotion-rfc.mjs` and `scripts/test-validate-promotion-rfc.mjs` | `node scripts/validate-promotion-rfc.mjs --json`; `node scripts/test-validate-promotion-rfc.mjs --json` | The manifest-declared policy and two canonical examples pass with deferred decisions, zero attestations, exact support commitment, and no promotion claim. | Manifest-declared invalid fixtures plus forged, unregistered, duplicate, path, extension, support, evidence, canonical-drift, and lifecycle mutations exit non-zero with named findings. | Synthetic validation does not authenticate adoption, organizations, owners, support capacity, provenance, or a promotion decision; the current canonical inventory supplies no real independent consumers. |
|
|
38
38
|
| Portable token source and generated CSS agree through the pinned restricted adapter. | `scripts/build-reference-artifacts.mjs`, `scripts/validate-reference-artifacts.mjs`, and `scripts/test-reference-adapters.mjs` | Build with `--fail-on-warning`, validate the generated manifest, then run the adapter fixture harness. | Dimension, color, duration, tested border values, and whole-token curly aliases emit nonzero CSS declarations with matching counts and hashes; output references remain CSS variable references. | `$extends`, JSON Pointer, resolver/modifier/theme documents, unknown reserved fields, non-string descriptions, adapter-unsafe path segments, untested type/unit, partial/dangling/cyclic/type-mismatched aliases, missing output, zero count, warning, scaffold/unknown manifest fields, missing/duplicate declaration, object sentinel, unresolved or forged value, and count drift must fail with named findings and non-zero child exit. | Proves the pinned adapter preserves this tested subset and that committed artifacts independently rebuild from their canonical source, not that tokens are product defaults, portable beyond the tested adapters, visually suitable, or adopted by a consumer. |
|
package/quality/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Quality Gates
|
|
2
2
|
|
|
3
|
-
`quality/` is shared StyleGallery infrastructure for deciding whether Layout, Motion, Design Engineering, Game UI,
|
|
3
|
+
`quality/` is shared StyleGallery infrastructure for deciding whether Layout, Motion, Design Engineering, Game UI, Platform Guides, and Design Terminology claims are admissible.
|
|
4
4
|
|
|
5
5
|
This shared layer does not make the repository a visual design system. Layout pattern files still own spatial structure only; domain guidance cannot bypass these gates. Quality gates explain how claims are framed, what evidence can support them, and when review or debt is required.
|
|
6
6
|
|
|
@@ -48,7 +48,7 @@ Use this script for lightweight tree tests:
|
|
|
48
48
|
| Name an observed motion effect. | [README](../README.md) | [Motion Vocabulary](../motion/vocabulary.md) | The first selected route is Motion and the term reference is reached within two hops. |
|
|
49
49
|
| Review product-level interface craft. | [README](../README.md) | [Interface Craft Decisions](../design-engineering/interface-craft.md) | The first selected route is Design Engineering and shared quality gates remain visible. |
|
|
50
50
|
| Compare an Apple interaction convention. | [README](../README.md) | [Apple Interaction](../platform-guides/apple-interaction.md) | The first selected route is Platform Guides and source/version limits are reached within two hops. |
|
|
51
|
-
| Declare whether a consumer reference applies. | [README](../README.md) | [Consumer Reference](../consumer-reference/index.md) | The handoff reaches the shared contract without classifying it as a
|
|
51
|
+
| Declare whether a consumer reference applies. | [README](../README.md) | [Consumer Reference](../consumer-reference/index.md) | The handoff reaches the shared contract without classifying it as a seventh domain. |
|
|
52
52
|
| Prove a consumer migration preserves declared behavior. | [README](../README.md) | [Consumer Migration Readiness](../design-engineering/consumer-migration-readiness.md) | The reader reaches the Design Engineering method and its evidence gate within two hops. |
|
|
53
53
|
| Find the authority route for uGUI, UI Toolkit, or NGUI. | [README](../README.md) | [Unity UI Systems](../game-ui/unity/ui-systems.md) | The first selected route is Game UI, and the system-specific source and version boundary is reached within three hops. |
|
|
54
54
|
|
package/recipes/homepage.md
CHANGED
|
@@ -37,8 +37,16 @@ Reject this recipe when the page is primarily a document, dashboard, form, comma
|
|
|
37
37
|
|
|
38
38
|
Order sections by the visitor's decision path, not by visual symmetry. Put the primary heading, explanation, proof, and action in semantic order before layout classes. Repeated proof should be a list when each item has the same role.
|
|
39
39
|
|
|
40
|
+
## Scroll Story Composition
|
|
41
|
+
|
|
42
|
+
When a section explains a product through changing visual chapters, compose existing containment, stacking, overlay, and sticky responsibilities before proposing a new spatial pattern. Name the sticky containing block, stage height, document scroll track, offset, release boundary, and long-content fallback. Prevent clipping when copy is taller than the stage.
|
|
43
|
+
|
|
44
|
+
Motion owns continuous progress and scene changes through the [Scroll-driven Story recipe](../motion/interaction-recipes.md#scroll-driven-story). The [Scroll Story Lab](../examples/scroll-story/README.md) demonstrates this composition with two products. Photography, device mockups, transforms, opacity, and timing remain in those consuming examples.
|
|
45
|
+
|
|
40
46
|
## Scroll Ownership
|
|
41
47
|
|
|
48
|
+
A short full-page showcase can compose a viewport shell and [overlay-stack](../patterns/overlay-exception/overlay-stack.md) responsibilities with the [Full-viewport Scene Navigation contract](../motion/interaction-recipes.md#full-viewport-scene-navigation). Declare the document as fixed in scene mode, the chapter copy as the native overflow owner, and the document as the scroll owner in reading mode. The failure to prevent is a clipped or unreachable chapter when the viewport or text size changes. The [worked example](../examples/scene-navigation/README.md) uses a shrinkable stage between header and footer; it introduces no new reusable Layout pattern.
|
|
49
|
+
|
|
42
50
|
Prefer normal document scrolling. Avoid nested scroll regions unless a section has an explicit independent task, such as a horizontally scrolling media reel with named keyboard and overflow behavior.
|
|
43
51
|
|
|
44
52
|
## Accessibility Expectations
|
|
@@ -17,7 +17,7 @@ const MANIFEST_REF = "sg:manifest/material-admission-v2";
|
|
|
17
17
|
const MATERIAL_REF_PATTERN = /^sg:material\/path-sha256-[a-f0-9]{64}$/;
|
|
18
18
|
const MATERIAL_VERSION_PATTERN = /^sg:material\/path-sha256-[a-f0-9]{64}@sha256:[a-f0-9]{64}$/;
|
|
19
19
|
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
20
|
-
const DOMAINS = new Set(["layout", "motion", "design-engineering", "game-ui", "platform-guides", "shared"]);
|
|
20
|
+
const DOMAINS = new Set(["layout", "motion", "design-engineering", "game-ui", "platform-guides", "design-terminology", "shared"]);
|
|
21
21
|
const LIFECYCLES = new Set(["draft", "stable", "deprecated", "experimental", "generated"]);
|
|
22
22
|
const RECORD_KEYS = new Set(["schema_version", "record_kind", "stable_ref", "version_id", "repository_path", "media_type", "source_sha256", "byte_length", "lifecycle", "domain"]);
|
|
23
23
|
const MANIFEST_KEYS = new Set(["schema_version", "record_kind", "stable_ref", "version_id", "admission_policy_ref", "admission_policy_version_id", "materials"]);
|
|
@@ -6,7 +6,7 @@ export const MATERIAL_STABLE_REF_KINDS = Object.freeze(["domain", "page", "patte
|
|
|
6
6
|
const KIND_SET = new Set(MATERIAL_STABLE_REF_KINDS);
|
|
7
7
|
const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
8
|
const PATH_ID = /^path-sha256-[a-f0-9]{64}$/;
|
|
9
|
-
const DOMAIN_IDS = new Set(["layout", "motion", "design-engineering", "game-ui", "platform-guides"]);
|
|
9
|
+
const DOMAIN_IDS = new Set(["layout", "motion", "design-engineering", "game-ui", "platform-guides", "design-terminology"]);
|
|
10
10
|
const MATERIAL_ID = PATH_ID;
|
|
11
11
|
|
|
12
12
|
export class MaterialIdentityError extends TypeError {
|
|
@@ -56,7 +56,7 @@ export function materialIdentityForRecord(record) {
|
|
|
56
56
|
if (typeof repositoryPath !== "string") fail("material_record_identity_invalid", "record has no bound source path");
|
|
57
57
|
let kind = "page";
|
|
58
58
|
let id = `path-sha256-${sha256(Buffer.from(repositoryPath, "utf8"))}`;
|
|
59
|
-
const domainMatch = /^(layout|motion|design-engineering|game-ui|platform-guides)\/index\.md$/.exec(repositoryPath);
|
|
59
|
+
const domainMatch = /^(layout|motion|design-engineering|game-ui|platform-guides|design-terminology)\/index\.md$/.exec(repositoryPath);
|
|
60
60
|
if (domainMatch) { kind = "domain"; id = domainMatch[1]; }
|
|
61
61
|
else if (/^patterns\/[^/]+\/[^/]+\.md$/.test(repositoryPath)) kind = "pattern";
|
|
62
62
|
return deepFreeze({
|
|
@@ -16,7 +16,7 @@ const manifestPath = path.join(repositoryRoot, "consumer-reference", "agent-nati
|
|
|
16
16
|
const exposedNames = new Set(["material-context", "material-discover", "material-get", "material-search"]);
|
|
17
17
|
const descriptions = Object.freeze({
|
|
18
18
|
"material-context": "Build a deterministic, provenance-bound material context within a token budget.",
|
|
19
|
-
"material-discover": "Discover the
|
|
19
|
+
"material-discover": "Discover the six governed StyleGallery domain entries.",
|
|
20
20
|
"material-get": "Read bounded UTF-8 bytes for one canonical v2 material StableRef.",
|
|
21
21
|
"material-search": "Search admitted material deterministically, optionally returning only repository-relative paths.",
|
|
22
22
|
});
|
|
@@ -310,11 +310,36 @@ function titleOf(text) {
|
|
|
310
310
|
return /^#{1,6}\s+(.+?)\s*$/m.exec(text)?.[1] ?? "";
|
|
311
311
|
}
|
|
312
312
|
function fieldMembership(fieldTokens, queryTokens) {
|
|
313
|
-
const members = new Set(fieldTokens);
|
|
314
313
|
let count = 0;
|
|
315
|
-
for (const token of queryTokens) if (
|
|
314
|
+
for (const token of queryTokens) if (fieldTokens.has(token)) count += 1;
|
|
316
315
|
return count;
|
|
317
316
|
}
|
|
317
|
+
function queryTokenMembership(value, queryTokenSet) {
|
|
318
|
+
const matches = new Set();
|
|
319
|
+
for (const token of tokenizeMaterialText(value)) {
|
|
320
|
+
if (queryTokenSet.has(token)) matches.add(token);
|
|
321
|
+
}
|
|
322
|
+
return matches;
|
|
323
|
+
}
|
|
324
|
+
function rarityWeightsFor(documents, queryTokens) {
|
|
325
|
+
return queryTokens.map((token) => {
|
|
326
|
+
const documentFrequency = documents.reduce((count, document) => count + Number(
|
|
327
|
+
document.titleTokens.has(token) || document.pathTokens.has(token) || document.bodyTokens.has(token),
|
|
328
|
+
), 0);
|
|
329
|
+
return {
|
|
330
|
+
document_frequency: documentFrequency,
|
|
331
|
+
multiplier: Math.ceil(documents.length / Math.max(1, documentFrequency)),
|
|
332
|
+
token,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function weightedFieldMembership(fieldTokens, rarityByToken, fieldWeight) {
|
|
337
|
+
let score = 0;
|
|
338
|
+
for (const [token, multiplier] of rarityByToken) {
|
|
339
|
+
if (fieldTokens.has(token)) score += multiplier * fieldWeight;
|
|
340
|
+
}
|
|
341
|
+
return score;
|
|
342
|
+
}
|
|
318
343
|
function sourceIdentity(record) {
|
|
319
344
|
return { stable_ref: record.stable_ref, version_id: record.version_id, source_sha256: record.source_sha256, byte_length: record.byte_length };
|
|
320
345
|
}
|
|
@@ -325,7 +350,7 @@ export function materialDiscover({ repositoryRoot, fileSystem = fs }) {
|
|
|
325
350
|
.filter(({ identity }) => identity.kind === "domain")
|
|
326
351
|
.map(({ record, identity }) => ({ identity, source: sourceIdentity(record), lifecycle: record.lifecycle }))
|
|
327
352
|
.sort((left, right) => compare(left.identity.stable_ref, right.identity.stable_ref));
|
|
328
|
-
if (domains.length !==
|
|
353
|
+
if (domains.length !== 6) fail("material_domain_set_invalid", "material manifest does not contain the six governed domains");
|
|
329
354
|
return deepFreeze({ schema_version: "2.0", manifest_version_id: manifest.version_id, domains });
|
|
330
355
|
}
|
|
331
356
|
|
|
@@ -334,20 +359,19 @@ export function materialSearch({ repositoryRoot, input, fileSystem = fs, sourceR
|
|
|
334
359
|
const state = snapshot ? requireSnapshot(snapshot) : loadMetadata(repositoryRoot, fileSystem);
|
|
335
360
|
const { root, manifest } = state;
|
|
336
361
|
const beforeInventory = snapshot ? state.inventory : trackedInventory(repositoryRoot, gitRunner);
|
|
337
|
-
const
|
|
362
|
+
const queryTokenSet = new Set(normalized.scoringTokens);
|
|
363
|
+
const documents = [];
|
|
338
364
|
for (const record of manifest.materials) {
|
|
339
365
|
const bytes = readRecord({ repositoryRoot, root, record, fileSystem, sourceReader, gitRunner, inventory: beforeInventory });
|
|
340
366
|
let body;
|
|
341
367
|
try { body = utf8Fatal.decode(bytes); } catch { fail("material_source_utf8_invalid", "material source is not valid UTF-8"); }
|
|
342
368
|
const title = titleOf(body);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
repositoryPath: record.repository_path,
|
|
350
|
-
result: { identity: materialIdentityForRecord(record), source: sourceIdentity(record), title, score, match_counts: { title: titleCount, path: pathCount, body: bodyCount } },
|
|
369
|
+
documents.push({
|
|
370
|
+
bodyTokens: queryTokenMembership(body, queryTokenSet),
|
|
371
|
+
pathTokens: queryTokenMembership(record.repository_path, queryTokenSet),
|
|
372
|
+
record,
|
|
373
|
+
title,
|
|
374
|
+
titleTokens: queryTokenMembership(title, queryTokenSet),
|
|
351
375
|
});
|
|
352
376
|
}
|
|
353
377
|
const afterInventory = trackedInventory(repositoryRoot, gitRunner);
|
|
@@ -355,6 +379,28 @@ export function materialSearch({ repositoryRoot, input, fileSystem = fs, sourceR
|
|
|
355
379
|
assertTracked(record, afterInventory);
|
|
356
380
|
if (canonicalize(beforeInventory.get(record.repository_path)) !== canonicalize(afterInventory.get(record.repository_path))) fail("material_source_race", "material source tracking changed during read");
|
|
357
381
|
}
|
|
382
|
+
const rarityWeights = rarityWeightsFor(documents, normalized.scoringTokens);
|
|
383
|
+
const rarityByToken = new Map(rarityWeights.map(({ token, multiplier }) => [token, multiplier]));
|
|
384
|
+
const ranked = [];
|
|
385
|
+
for (const document of documents) {
|
|
386
|
+
const titleCount = fieldMembership(document.titleTokens, normalized.scoringTokens);
|
|
387
|
+
const pathCount = fieldMembership(document.pathTokens, normalized.scoringTokens);
|
|
388
|
+
const bodyCount = fieldMembership(document.bodyTokens, normalized.scoringTokens);
|
|
389
|
+
const score = weightedFieldMembership(document.titleTokens, rarityByToken, MATERIAL_SEARCH_WEIGHTS.title)
|
|
390
|
+
+ weightedFieldMembership(document.pathTokens, rarityByToken, MATERIAL_SEARCH_WEIGHTS.path)
|
|
391
|
+
+ weightedFieldMembership(document.bodyTokens, rarityByToken, MATERIAL_SEARCH_WEIGHTS.body);
|
|
392
|
+
if (score === 0) continue;
|
|
393
|
+
ranked.push({
|
|
394
|
+
repositoryPath: document.record.repository_path,
|
|
395
|
+
result: {
|
|
396
|
+
identity: materialIdentityForRecord(document.record),
|
|
397
|
+
source: sourceIdentity(document.record),
|
|
398
|
+
title: document.title,
|
|
399
|
+
score,
|
|
400
|
+
match_counts: { title: titleCount, path: pathCount, body: bodyCount },
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
}
|
|
358
404
|
ranked.sort((left, right) => right.result.score - left.result.score || compare(left.result.identity.stable_ref, right.result.identity.stable_ref));
|
|
359
405
|
const selected = ranked.slice(0, normalized.limit);
|
|
360
406
|
const envelope = {
|
|
@@ -362,6 +408,8 @@ export function materialSearch({ repositoryRoot, input, fileSystem = fs, sourceR
|
|
|
362
408
|
normalization: "Unicode NFKC; ECMAScript locale-independent lowercase; Letter/Number-led Unicode words retaining attached Marks; unique query-token field membership",
|
|
363
409
|
query: normalized.query.normalize("NFKC").toLowerCase(), tokens: normalized.tokens,
|
|
364
410
|
scoring_tokens: normalized.scoringTokens, weights: MATERIAL_SEARCH_WEIGHTS,
|
|
411
|
+
rarity_weighting: "ceil(material_count / max(1, token_document_frequency))",
|
|
412
|
+
rarity_weights: rarityWeights,
|
|
365
413
|
};
|
|
366
414
|
if (normalized.pathsOnly) return deepFreeze({ ...envelope, paths_only: true, paths: selected.map(({ repositoryPath }) => repositoryPath), total_matches: ranked.length });
|
|
367
415
|
return deepFreeze({ ...envelope, results: selected.map(({ result }) => result), total_matches: ranked.length });
|