@profile-as-code/core 0.0.0-stage → 0.1.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/dist/byte-order.d.ts +17 -0
  4. package/dist/byte-order.d.ts.map +1 -0
  5. package/dist/byte-order.js +19 -0
  6. package/dist/byte-order.js.map +1 -0
  7. package/dist/cite.d.ts +91 -0
  8. package/dist/cite.d.ts.map +1 -0
  9. package/dist/cite.js +440 -0
  10. package/dist/cite.js.map +1 -0
  11. package/dist/compile.d.ts +40 -0
  12. package/dist/compile.d.ts.map +1 -0
  13. package/dist/compile.js +120 -0
  14. package/dist/compile.js.map +1 -0
  15. package/dist/completeness.d.ts +81 -0
  16. package/dist/completeness.d.ts.map +1 -0
  17. package/dist/completeness.js +300 -0
  18. package/dist/completeness.js.map +1 -0
  19. package/dist/drift.d.ts +43 -0
  20. package/dist/drift.d.ts.map +1 -0
  21. package/dist/drift.js +277 -0
  22. package/dist/drift.js.map +1 -0
  23. package/dist/generated/ai-policy.gen.d.ts +36 -0
  24. package/dist/generated/ai-policy.gen.d.ts.map +1 -0
  25. package/dist/generated/ai-policy.gen.js +31 -0
  26. package/dist/generated/ai-policy.gen.js.map +1 -0
  27. package/dist/generated/platforms.gen.d.ts +34 -0
  28. package/dist/generated/platforms.gen.d.ts.map +1 -0
  29. package/dist/generated/platforms.gen.js +253 -0
  30. package/dist/generated/platforms.gen.js.map +1 -0
  31. package/dist/generated/schema.zod.d.ts +595 -0
  32. package/dist/generated/schema.zod.d.ts.map +1 -0
  33. package/dist/generated/schema.zod.js +36 -0
  34. package/dist/generated/schema.zod.js.map +1 -0
  35. package/dist/geo.d.ts +42 -0
  36. package/dist/geo.d.ts.map +1 -0
  37. package/dist/geo.js +614 -0
  38. package/dist/geo.js.map +1 -0
  39. package/dist/index.d.ts +60 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +41 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/ledger.d.ts +238 -0
  44. package/dist/ledger.d.ts.map +1 -0
  45. package/dist/ledger.js +429 -0
  46. package/dist/ledger.js.map +1 -0
  47. package/dist/load.d.ts +51 -0
  48. package/dist/load.d.ts.map +1 -0
  49. package/dist/load.js +138 -0
  50. package/dist/load.js.map +1 -0
  51. package/dist/media.d.ts +82 -0
  52. package/dist/media.d.ts.map +1 -0
  53. package/dist/media.js +170 -0
  54. package/dist/media.js.map +1 -0
  55. package/dist/normalize.d.ts +57 -0
  56. package/dist/normalize.d.ts.map +1 -0
  57. package/dist/normalize.js +133 -0
  58. package/dist/normalize.js.map +1 -0
  59. package/dist/presence.d.ts +94 -0
  60. package/dist/presence.d.ts.map +1 -0
  61. package/dist/presence.js +217 -0
  62. package/dist/presence.js.map +1 -0
  63. package/dist/registry.d.ts +79 -0
  64. package/dist/registry.d.ts.map +1 -0
  65. package/dist/registry.js +214 -0
  66. package/dist/registry.js.map +1 -0
  67. package/dist/selection.d.ts +98 -0
  68. package/dist/selection.d.ts.map +1 -0
  69. package/dist/selection.js +242 -0
  70. package/dist/selection.js.map +1 -0
  71. package/dist/structured-data.d.ts +83 -0
  72. package/dist/structured-data.d.ts.map +1 -0
  73. package/dist/structured-data.js +330 -0
  74. package/dist/structured-data.js.map +1 -0
  75. package/dist/types.d.ts +327 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +34 -0
  78. package/dist/types.js.map +1 -0
  79. package/package.json +59 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Luciani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,81 @@
1
- # Temporary Holding Version
1
+ # @profile-as-code/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The core engine of **profile-as-code** (M1): load a profile-data workspace,
4
+ validate it against the contract, and run the drift / GEO / compile layers.
5
+
6
+ The contract source is `schema/profile.schema.json` (D3) — Zod schemas are
7
+ **generated** from it, never hand-written (see `scripts/gen-zod.ts`).
8
+
9
+ ## API
10
+
11
+ ```ts
12
+ import { load, driftCheck, geoCheck, compile } from '@profile-as-code/core';
13
+
14
+ const profile = load('/path/to/profile-data'); // parse content/*.yaml + validate (FAIL on schema, D12)
15
+ const issues = driftCheck(profile); // Issue[] — WARN only, never throws (D12)
16
+ const geo = geoCheck(profile); // GeoReport — 0..100 score, not a gate (§8)
17
+ const slice = compile(profile, 'linkedin', 'es'); // PlatformProfile — language-aware renderer slice
18
+ ```
19
+
20
+ - **`load(workspace)`** — reads `content/*.yaml` (file name = root property:
21
+ `bios.yaml` → `bios[]`, `identity.yaml` → `identity`, …), merges, validates
22
+ against the generated Zod, normalizes (collections default to `[]`).
23
+ Throws `LoadError` (workspace problems) / `SchemaValidationError` (contract
24
+ violations with one precise `path` + `message` per error — the fail-on-schema
25
+ side of D12). Unknown files fail validation via `additionalProperties: false`.
26
+ - **`driftCheck(profile)`** — factual contradictions across surfaces, all WARN
27
+ (users may have different intents per platform, but they must SEE them):
28
+ uncanonicalized platform URLs, `start > end`, duplicated bios/headlines,
29
+ same role/org with different dates, same title with different year/venue,
30
+ ORCID conflicts/mismatches vs `identity.links`, active entries without a
31
+ curated base, unknown active platform keys, skill level conflicts.
32
+ *Out of scope (v1):* free-text mining of bio prose, DOI resolvability
33
+ (network), the variants store (only the platform+lang base is validated).
34
+ - **`geoCheck(profile)`** — weighted report (14 checks since v1.10, 0..100):
35
+ **fact consistency** (conflicts + per-platform bio/headline coverage),
36
+ **structured-data readiness** (`schema.org` Person JSON-LD with `sameAs`,
37
+ ready for the web renderer), **semantic associations** (the 3–5
38
+ `positioning.geo.semanticAssociations` anchors and how well each is
39
+ reinforced across statement/skills/keywords/projects), **citability**
40
+ (DOI/ORCID on publications, `evidence[]` on projects) plus the v1.10 citability
41
+ codes — canonical definition, FAQ coverage, freshness, sameAs coverage, name
42
+ collision. Missing data is neutral for the fact checks (never penalized);
43
+ readiness/citability checks (structured data, semantic anchors, the v1.10 codes)
44
+ can score 0 when the signal is absent — a reported gap, not a penalty (SPEC §8).
45
+ - **`compile(profile, platform, lang)`** — facts pass through untouched
46
+ (identical on every platform = GEO-citable); text (bios, headlines, statement,
47
+ audiences, citationsGoal) is filtered by platform+lang and falls back to `en`
48
+ (always required by the schema), recording each fallback in `fallbacks`.
49
+
50
+ ## Contract fidelity (generator)
51
+
52
+ `scripts/gen-zod.ts` regenerates `src/generated/schema.zod.ts` from
53
+ `schema/profile.schema.json`. Because `json-schema-to-zod@2.8.1` does not resolve
54
+ `$defs`, ignore `propertyNames`, and bloats `oneOf`, the generator:
55
+
56
+ 1. inlines `#/$defs/*` references (fails on unknown/circular defs),
57
+ 2. rewrites the lone `oneOf` (`check.value`) to `anyOf` (clean `z.union`),
58
+ 3. materializes `langMap.en` as a real property (keeps `required: ['en']`), and
59
+ 4. wraps every langMap with a `langMapKeys` superRefine enforcing the
60
+ `en/es/fr` key enum (`propertyNames`).
61
+
62
+ Safety nets fail CI loudly if the output ever contains `z.any()` or loses the
63
+ langMap guard.
64
+
65
+ ## Test
66
+
67
+ ```sh
68
+ pnpm install
69
+ pnpm test # vitest (load, drift, geo, compile)
70
+ pnpm build # strict tsc → dist/
71
+ pnpm gen:zod # regenerate src/generated/schema.zod.ts (must produce no diff)
72
+ ```
73
+
74
+ Fixtures live in `tests/fixtures/`: `minimal/` (drift-free, GEO-rich — the
75
+ reference fixture) and `drifting/` (schema-valid but contradicts itself on
76
+ every drift rule).
77
+
78
+ Requires Node ≥ 22 (the `engines` field of this manifest). Full source and docs
79
+ live in the [public repository](https://github.com/ChristianLuciani/profile-as-code) —
80
+ `scripts/gen-zod.ts` and `tests/fixtures/` above are package-relative paths there
81
+ (`packages/core/…`); the contract source is the repository's `schema/profile.schema.json`.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * byte-order.ts — the ONE home of every text-order comparison in the engine
3
+ * (D19 byte-parity, issue #305).
4
+ *
5
+ * The comparator is UTF-16 code UNITS — JS native `a < b` string order:
6
+ * deterministic on every environment, ICU-free. It is NOT codepoint order on
7
+ * the astral edge, and SPEC §15.3 names that choice exactly. A `localeCompare`
8
+ * call-site would depend on the ICU build and can tie (`0`), leaking
9
+ * environment-specific order through a stable sort — the class #257 fixed for
10
+ * the featured slice and #305 closes workspace-wide.
11
+ *
12
+ * The discriminating probe (`apple` vs `APPLE`: ICU collation reads lowercase
13
+ * first, code units read uppercase first) lives at
14
+ * packages/core/tests/byte-order.test.ts.
15
+ */
16
+ export declare function byteOrder(a: string, b: string): number;
17
+ //# sourceMappingURL=byte-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byte-order.d.ts","sourceRoot":"","sources":["../src/byte-order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * byte-order.ts — the ONE home of every text-order comparison in the engine
3
+ * (D19 byte-parity, issue #305).
4
+ *
5
+ * The comparator is UTF-16 code UNITS — JS native `a < b` string order:
6
+ * deterministic on every environment, ICU-free. It is NOT codepoint order on
7
+ * the astral edge, and SPEC §15.3 names that choice exactly. A `localeCompare`
8
+ * call-site would depend on the ICU build and can tie (`0`), leaking
9
+ * environment-specific order through a stable sort — the class #257 fixed for
10
+ * the featured slice and #305 closes workspace-wide.
11
+ *
12
+ * The discriminating probe (`apple` vs `APPLE`: ICU collation reads lowercase
13
+ * first, code units read uppercase first) lives at
14
+ * packages/core/tests/byte-order.test.ts.
15
+ */
16
+ export function byteOrder(a, b) {
17
+ return a < b ? -1 : a > b ? 1 : 0;
18
+ }
19
+ //# sourceMappingURL=byte-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byte-order.js","sourceRoot":"","sources":["../src/byte-order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC5C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC"}
package/dist/cite.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * cite.ts — the deterministic citation matcher (SPEC §7.4, issue #291).
3
+ *
4
+ * The engine answers ONE question about an answer-engine record: did this saved
5
+ * answer cite this entity? It is a pure function over the SSOT and the answer
6
+ * bytes — no network, no model, no clock (D19) — because the alternative
7
+ * (substring matching) is why GEO reporting overreports, and the only fix is a
8
+ * matcher that says exactly which text it matched, why, and when it refuses to
9
+ * guess.
10
+ *
11
+ * The vocabulary is the workspace's DECLARED data, never world knowledge:
12
+ * name identity.name
13
+ * alias identity.orgName (the one declared alternate name the schema has)
14
+ * handle identity.handle (its final token, '@' stripped) + socialHandles
15
+ * rows with status: claimed
16
+ * hosts the role: identity URL and the sameAs set (structured-data.ts —
17
+ * one derivation, never a second)
18
+ * partial a contiguous multi-token prefix of the name (low confidence)
19
+ *
20
+ * Normalization and word-boundary matching live in `normalize.ts`. It is
21
+ * `cite-check`'s fold, not `geoCheck`'s: `geoCheck` keeps its own case-only
22
+ * comparison so this matcher cannot move a published GEO score (§8).
23
+ *
24
+ * Guards make a text hit NOT a citation (`cited: false`): a shorter term
25
+ * contained in a longer declared one (`longer-name`), a one-word name with no
26
+ * declared disambiguator in its sentence (`ambiguous-term` + `no-qualifier`), and
27
+ * a mention inside a contrast clause (`contrast-context`). `competing:<phrase>`
28
+ * never decides on its own — it only names the longer reading the text carries
29
+ * when the hit is already ambiguous, so ordinary prose cannot become a
30
+ * competitor through an adjacent capital. `confidence: low` is never citable
31
+ * downstream (declared in §7.4).
32
+ */
33
+ import type { Profile } from "./types.js";
34
+ /** The rung a span was matched by — one kind per ladder rung (SPEC §7.4). */
35
+ export type CiteSpanKind = "citation-url" | "name" | "alias" | "handle" | "partial";
36
+ /** The best surviving rung. `low` is never a citation in a downstream report. */
37
+ export type CiteConfidence = "high" | "medium" | "low";
38
+ /** One citation row of the runner's input contract. */
39
+ export interface CiteCitation {
40
+ url: string;
41
+ title?: string;
42
+ }
43
+ /**
44
+ * One saved answer (the runner's contract, never produced here). The matcher
45
+ * reads `answer` + `citations`; the rest is carried through for a report.
46
+ */
47
+ export interface CiteAnswer {
48
+ engine?: string | null;
49
+ askedAt?: string | null;
50
+ question?: string | null;
51
+ answer: string;
52
+ citations: CiteCitation[];
53
+ }
54
+ /** A matched span: a character range in `answer`, or a citation pointer. */
55
+ export interface CiteSpan {
56
+ /** Original offset (inclusive). 0 for a `citation-url` span (not in the text). */
57
+ start: number;
58
+ /** Original offset (exclusive). 0 for a `citation-url` span. */
59
+ end: number;
60
+ kind: CiteSpanKind;
61
+ /** The citation URL — only on `citation-url` spans. */
62
+ url?: string;
63
+ }
64
+ /** Per-answer result. Never aggregated by the engine (SPEC §7.4). */
65
+ export interface CiteMatch {
66
+ cited: boolean;
67
+ confidence: CiteConfidence;
68
+ spans: CiteSpan[];
69
+ reasons: string[];
70
+ }
71
+ /**
72
+ * The vocabulary the matcher resolved from the workspace. Exposed in `--json`
73
+ * so a report can say WHAT was matched without re-reading the SSOT.
74
+ */
75
+ export interface CiteVocabulary {
76
+ name: string;
77
+ aliases: string[];
78
+ handles: string[];
79
+ partials: string[];
80
+ hosts: string[];
81
+ }
82
+ /** The public vocabulary of a workspace (the `entity` block of `--json`). */
83
+ export declare function citeVocabulary(profile: Profile): CiteVocabulary;
84
+ /**
85
+ * Match one saved answer against the workspace's declared entity (SPEC §7.4).
86
+ * Pure: the same profile + answer bytes always produce the same result.
87
+ */
88
+ export declare function citeMatch(profile: Profile, answer: CiteAnswer): CiteMatch;
89
+ /** Exported for tests: the declared host of a URL under the matcher's rule. */
90
+ export declare function citationHost(url: string): string | undefined;
91
+ //# sourceMappingURL=cite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cite.d.ts","sourceRoot":"","sources":["../src/cite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpF,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEvD,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,cAAc,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAsND,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAE/D;AAgFD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CA0JzE;AAaD,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D"}