@cogenta/agents-builtin 0.1.0
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/dist/content/agent.d.ts +18 -0
- package/dist/content/agent.d.ts.map +1 -0
- package/dist/content/agent.js +28 -0
- package/dist/content/agent.js.map +1 -0
- package/dist/content/provenance.d.ts +28 -0
- package/dist/content/provenance.d.ts.map +1 -0
- package/dist/content/provenance.js +48 -0
- package/dist/content/provenance.js.map +1 -0
- package/dist/content/terminology.d.ts +12 -0
- package/dist/content/terminology.d.ts.map +1 -0
- package/dist/content/terminology.js +24 -0
- package/dist/content/terminology.js.map +1 -0
- package/dist/content/topic-gaps.d.ts +29 -0
- package/dist/content/topic-gaps.d.ts.map +1 -0
- package/dist/content/topic-gaps.js +58 -0
- package/dist/content/topic-gaps.js.map +1 -0
- package/dist/content/types.d.ts +15 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +2 -0
- package/dist/content/types.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/performance/agent.d.ts +9 -0
- package/dist/performance/agent.d.ts.map +1 -0
- package/dist/performance/agent.js +19 -0
- package/dist/performance/agent.js.map +1 -0
- package/dist/performance/budget.d.ts +8 -0
- package/dist/performance/budget.d.ts.map +1 -0
- package/dist/performance/budget.js +29 -0
- package/dist/performance/budget.js.map +1 -0
- package/dist/performance/crux-client.d.ts +21 -0
- package/dist/performance/crux-client.d.ts.map +1 -0
- package/dist/performance/crux-client.js +56 -0
- package/dist/performance/crux-client.js.map +1 -0
- package/dist/performance/diagnosis.d.ts +18 -0
- package/dist/performance/diagnosis.d.ts.map +1 -0
- package/dist/performance/diagnosis.js +48 -0
- package/dist/performance/diagnosis.js.map +1 -0
- package/dist/performance/median.d.ts +11 -0
- package/dist/performance/median.d.ts.map +1 -0
- package/dist/performance/median.js +34 -0
- package/dist/performance/median.js.map +1 -0
- package/dist/performance/regression.d.ts +16 -0
- package/dist/performance/regression.d.ts.map +1 -0
- package/dist/performance/regression.js +39 -0
- package/dist/performance/regression.js.map +1 -0
- package/dist/performance/types.d.ts +39 -0
- package/dist/performance/types.d.ts.map +1 -0
- package/dist/performance/types.js +2 -0
- package/dist/performance/types.js.map +1 -0
- package/dist/security/agent.d.ts +14 -0
- package/dist/security/agent.d.ts.map +1 -0
- package/dist/security/agent.js +36 -0
- package/dist/security/agent.js.map +1 -0
- package/dist/security/bump-version.d.ts +8 -0
- package/dist/security/bump-version.d.ts.map +1 -0
- package/dist/security/bump-version.js +22 -0
- package/dist/security/bump-version.js.map +1 -0
- package/dist/security/deps-patch-tool.d.ts +34 -0
- package/dist/security/deps-patch-tool.d.ts.map +1 -0
- package/dist/security/deps-patch-tool.js +61 -0
- package/dist/security/deps-patch-tool.js.map +1 -0
- package/dist/security/deps-scan-tool.d.ts +56 -0
- package/dist/security/deps-scan-tool.d.ts.map +1 -0
- package/dist/security/deps-scan-tool.js +89 -0
- package/dist/security/deps-scan-tool.js.map +1 -0
- package/dist/security/epss-client.d.ts +18 -0
- package/dist/security/epss-client.d.ts.map +1 -0
- package/dist/security/epss-client.js +34 -0
- package/dist/security/epss-client.js.map +1 -0
- package/dist/security/exploitability.d.ts +17 -0
- package/dist/security/exploitability.d.ts.map +1 -0
- package/dist/security/exploitability.js +36 -0
- package/dist/security/exploitability.js.map +1 -0
- package/dist/security/osv-client.d.ts +26 -0
- package/dist/security/osv-client.d.ts.map +1 -0
- package/dist/security/osv-client.js +63 -0
- package/dist/security/osv-client.js.map +1 -0
- package/dist/security/pr-client.d.ts +30 -0
- package/dist/security/pr-client.d.ts.map +1 -0
- package/dist/security/pr-client.js +2 -0
- package/dist/security/pr-client.js.map +1 -0
- package/dist/security/report.d.ts +25 -0
- package/dist/security/report.d.ts.map +1 -0
- package/dist/security/report.js +43 -0
- package/dist/security/report.js.map +1 -0
- package/dist/security/sbom.d.ts +15 -0
- package/dist/security/sbom.d.ts.map +1 -0
- package/dist/security/sbom.js +11 -0
- package/dist/security/sbom.js.map +1 -0
- package/dist/seo/agent.d.ts +9 -0
- package/dist/seo/agent.d.ts.map +1 -0
- package/dist/seo/agent.js +23 -0
- package/dist/seo/agent.js.map +1 -0
- package/dist/seo/audit.d.ts +17 -0
- package/dist/seo/audit.d.ts.map +1 -0
- package/dist/seo/audit.js +179 -0
- package/dist/seo/audit.js.map +1 -0
- package/dist/seo/cannibalization.d.ts +21 -0
- package/dist/seo/cannibalization.d.ts.map +1 -0
- package/dist/seo/cannibalization.js +32 -0
- package/dist/seo/cannibalization.js.map +1 -0
- package/dist/seo/internal-linking.d.ts +26 -0
- package/dist/seo/internal-linking.d.ts.map +1 -0
- package/dist/seo/internal-linking.js +35 -0
- package/dist/seo/internal-linking.js.map +1 -0
- package/dist/seo/json-ld.d.ts +29 -0
- package/dist/seo/json-ld.d.ts.map +1 -0
- package/dist/seo/json-ld.js +64 -0
- package/dist/seo/json-ld.js.map +1 -0
- package/dist/seo/llms-txt.d.ts +11 -0
- package/dist/seo/llms-txt.d.ts.map +1 -0
- package/dist/seo/llms-txt.js +40 -0
- package/dist/seo/llms-txt.js.map +1 -0
- package/dist/seo/readability.d.ts +10 -0
- package/dist/seo/readability.d.ts.map +1 -0
- package/dist/seo/readability.js +44 -0
- package/dist/seo/readability.js.map +1 -0
- package/dist/seo/redirects.d.ts +13 -0
- package/dist/seo/redirects.d.ts.map +1 -0
- package/dist/seo/redirects.js +30 -0
- package/dist/seo/redirects.js.map +1 -0
- package/dist/seo/similarity.d.ts +8 -0
- package/dist/seo/similarity.d.ts.map +1 -0
- package/dist/seo/similarity.js +23 -0
- package/dist/seo/similarity.js.map +1 -0
- package/dist/seo/types.d.ts +37 -0
- package/dist/seo/types.d.ts.map +1 -0
- package/dist/seo/types.js +2 -0
- package/dist/seo/types.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cannibalization.d.ts","sourceRoot":"","sources":["../../src/seo/cannibalization.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAID;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,4BAA4B,EAAE,EAC9C,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAqBzC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHashingEmbeddingProvider } from '@cogenta/agents';
|
|
2
|
+
import { cosineSimilarity } from './similarity.js';
|
|
3
|
+
const DEFAULT_THRESHOLD = 0.85;
|
|
4
|
+
/**
|
|
5
|
+
* "Détection de cannibalisation entre pages" — two *different* pages whose
|
|
6
|
+
* body text is topically near-identical compete for the same search intent
|
|
7
|
+
* instead of ranking for it together. Same embedding approach as
|
|
8
|
+
* `proposeInternalLinks`, but the signal here is a pair scoring above a high
|
|
9
|
+
* threshold, not a ranked list.
|
|
10
|
+
*/
|
|
11
|
+
export async function detectCannibalization(pages, options = {}) {
|
|
12
|
+
if (pages.length < 2)
|
|
13
|
+
return [];
|
|
14
|
+
const provider = createHashingEmbeddingProvider();
|
|
15
|
+
const vectors = await provider.embed(pages.map((page) => page.bodyText));
|
|
16
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
17
|
+
const pairs = [];
|
|
18
|
+
for (let i = 0; i < pages.length; i++) {
|
|
19
|
+
for (let j = i + 1; j < pages.length; j++) {
|
|
20
|
+
const score = cosineSimilarity(vectors[i] ?? [], vectors[j] ?? []);
|
|
21
|
+
if (score >= threshold) {
|
|
22
|
+
const pageA = pages[i];
|
|
23
|
+
const pageB = pages[j];
|
|
24
|
+
if (pageA !== undefined && pageB !== undefined) {
|
|
25
|
+
pairs.push({ urlA: pageA.url, urlB: pageB.url, score });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return pairs.sort((a, b) => b.score - a.score);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=cannibalization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cannibalization.js","sourceRoot":"","sources":["../../src/seo/cannibalization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAiBlD,MAAM,iBAAiB,GAAG,IAAI,CAAA;AAE9B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA8C,EAC9C,OAAO,GAAiC,EAAE;IAE1C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IAE/B,MAAM,QAAQ,GAAG,8BAA8B,EAAE,CAAA;IACjD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IACxE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iBAAiB,CAAA;IAExD,MAAM,KAAK,GAA0B,EAAE,CAAA;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;YAClE,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;gBACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;gBACtB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC/C,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;gBACzD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;AAChD,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface InternalLinkSourcePage {
|
|
2
|
+
readonly url: string;
|
|
3
|
+
readonly bodyText: string;
|
|
4
|
+
readonly internalLinks: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface InternalLinkCandidatePage {
|
|
7
|
+
readonly url: string;
|
|
8
|
+
readonly bodyText: string;
|
|
9
|
+
}
|
|
10
|
+
export interface InternalLinkProposal {
|
|
11
|
+
readonly url: string;
|
|
12
|
+
readonly score: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ProposeInternalLinksOptions {
|
|
15
|
+
readonly limit?: number;
|
|
16
|
+
readonly minScore?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* "Propositions de liens internes" — ranks candidate pages by topical
|
|
20
|
+
* similarity to the current page's body text, using the same hashing-trick
|
|
21
|
+
* `EmbeddingProvider` the RAG index already uses (L4 task 14) rather than a
|
|
22
|
+
* bespoke similarity metric. A page already linked from the current one, or
|
|
23
|
+
* the current page itself, is never proposed again.
|
|
24
|
+
*/
|
|
25
|
+
export declare function proposeInternalLinks(currentPage: InternalLinkSourcePage, candidatePages: readonly InternalLinkCandidatePage[], options?: ProposeInternalLinksOptions): Promise<readonly InternalLinkProposal[]>;
|
|
26
|
+
//# sourceMappingURL=internal-linking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-linking.d.ts","sourceRoot":"","sources":["../../src/seo/internal-linking.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1C;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAKD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,SAAS,yBAAyB,EAAE,EACpD,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAyB1C"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHashingEmbeddingProvider } from '@cogenta/agents';
|
|
2
|
+
import { cosineSimilarity } from './similarity.js';
|
|
3
|
+
const DEFAULT_LIMIT = 5;
|
|
4
|
+
const DEFAULT_MIN_SCORE = 0.05;
|
|
5
|
+
/**
|
|
6
|
+
* "Propositions de liens internes" — ranks candidate pages by topical
|
|
7
|
+
* similarity to the current page's body text, using the same hashing-trick
|
|
8
|
+
* `EmbeddingProvider` the RAG index already uses (L4 task 14) rather than a
|
|
9
|
+
* bespoke similarity metric. A page already linked from the current one, or
|
|
10
|
+
* the current page itself, is never proposed again.
|
|
11
|
+
*/
|
|
12
|
+
export async function proposeInternalLinks(currentPage, candidatePages, options = {}) {
|
|
13
|
+
const alreadyLinked = new Set(currentPage.internalLinks);
|
|
14
|
+
const eligible = candidatePages.filter((page) => page.url !== currentPage.url && !alreadyLinked.has(page.url));
|
|
15
|
+
if (eligible.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const provider = createHashingEmbeddingProvider();
|
|
18
|
+
const vectors = await provider.embed([
|
|
19
|
+
currentPage.bodyText,
|
|
20
|
+
...eligible.map((page) => page.bodyText),
|
|
21
|
+
]);
|
|
22
|
+
const currentVector = vectors[0];
|
|
23
|
+
if (currentVector === undefined)
|
|
24
|
+
return [];
|
|
25
|
+
const minScore = options.minScore ?? DEFAULT_MIN_SCORE;
|
|
26
|
+
const scored = eligible
|
|
27
|
+
.map((page, index) => ({
|
|
28
|
+
url: page.url,
|
|
29
|
+
score: cosineSimilarity(currentVector, vectors[index + 1] ?? []),
|
|
30
|
+
}))
|
|
31
|
+
.filter((entry) => entry.score >= minScore)
|
|
32
|
+
.sort((a, b) => b.score - a.score);
|
|
33
|
+
return scored.slice(0, options.limit ?? DEFAULT_LIMIT);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=internal-linking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-linking.js","sourceRoot":"","sources":["../../src/seo/internal-linking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAuBlD,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,iBAAiB,GAAG,IAAI,CAAA;AAE9B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,WAAmC,EACnC,cAAoD,EACpD,OAAO,GAAgC,EAAE;IAEzC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAA;IACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CACpC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CACvE,CAAA;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAEpC,MAAM,QAAQ,GAAG,8BAA8B,EAAE,CAAA;IACjD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC;QACnC,WAAW,CAAC,QAAQ;QACpB,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;KACzC,CAAC,CAAA;IACF,MAAM,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IAE1C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAA;IACtD,MAAM,MAAM,GAAG,QAAQ;SACpB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,KAAK,EAAE,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;KACjE,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAC;SAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;IAEpC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC,CAAA;AACxD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SeoFinding } from './types.js';
|
|
2
|
+
export type ArticleJsonLdType = 'Article' | 'BlogPosting' | 'NewsArticle';
|
|
3
|
+
export interface ArticleJsonLdInput {
|
|
4
|
+
readonly url: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly authorName: string;
|
|
7
|
+
readonly datePublished: string;
|
|
8
|
+
readonly description?: string;
|
|
9
|
+
readonly dateModified?: string;
|
|
10
|
+
readonly imageUrl?: string;
|
|
11
|
+
readonly type?: ArticleJsonLdType;
|
|
12
|
+
}
|
|
13
|
+
export interface JsonLd {
|
|
14
|
+
readonly '@context': string;
|
|
15
|
+
readonly '@type': string;
|
|
16
|
+
readonly [field: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** "Génération... du JSON-LD" — schema.org Article/BlogPosting/NewsArticle, the three types the required-field table in `validateJsonLd` below actually knows about. */
|
|
19
|
+
export declare function buildArticleJsonLd(input: ArticleJsonLdInput): JsonLd;
|
|
20
|
+
/**
|
|
21
|
+
* "...et vérification du JSON-LD" — the other half: given a JSON-LD object
|
|
22
|
+
* (this agent's own output, or one already on the page), report what
|
|
23
|
+
* schema.org's minimum expectations for its `@type` are missing. Only the
|
|
24
|
+
* three article-like types this module generates have a known required-field
|
|
25
|
+
* table; an unrecognised `@type` is reported as unverifiable, not silently
|
|
26
|
+
* passed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateJsonLd(jsonLd: Readonly<Record<string, unknown>>): readonly SeoFinding[];
|
|
29
|
+
//# sourceMappingURL=json-ld.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-ld.d.ts","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,CAAA;AAEzE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAA;CAClC;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAClC;AAED,wKAAwK;AACxK,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAYpE;AAYD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,UAAU,EAAE,CAmC/F"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** "Génération... du JSON-LD" — schema.org Article/BlogPosting/NewsArticle, the three types the required-field table in `validateJsonLd` below actually knows about. */
|
|
2
|
+
export function buildArticleJsonLd(input) {
|
|
3
|
+
return {
|
|
4
|
+
'@context': 'https://schema.org',
|
|
5
|
+
'@type': input.type ?? 'Article',
|
|
6
|
+
headline: input.title,
|
|
7
|
+
url: input.url,
|
|
8
|
+
datePublished: input.datePublished,
|
|
9
|
+
author: { '@type': 'Person', name: input.authorName },
|
|
10
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
11
|
+
...(input.dateModified === undefined ? {} : { dateModified: input.dateModified }),
|
|
12
|
+
...(input.imageUrl === undefined ? {} : { image: input.imageUrl }),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const REQUIRED_FIELDS_BY_TYPE = {
|
|
16
|
+
Article: ['headline', 'author', 'datePublished'],
|
|
17
|
+
BlogPosting: ['headline', 'author', 'datePublished'],
|
|
18
|
+
NewsArticle: ['headline', 'author', 'datePublished'],
|
|
19
|
+
};
|
|
20
|
+
function isBlank(value) {
|
|
21
|
+
return value === undefined || value === null || value === '';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* "...et vérification du JSON-LD" — the other half: given a JSON-LD object
|
|
25
|
+
* (this agent's own output, or one already on the page), report what
|
|
26
|
+
* schema.org's minimum expectations for its `@type` are missing. Only the
|
|
27
|
+
* three article-like types this module generates have a known required-field
|
|
28
|
+
* table; an unrecognised `@type` is reported as unverifiable, not silently
|
|
29
|
+
* passed.
|
|
30
|
+
*/
|
|
31
|
+
export function validateJsonLd(jsonLd) {
|
|
32
|
+
const type = jsonLd['@type'];
|
|
33
|
+
if (typeof type !== 'string' || type === '') {
|
|
34
|
+
return [{ check: 'json_ld', severity: 'error', message: 'JSON-LD has no "@type".' }];
|
|
35
|
+
}
|
|
36
|
+
const findings = [];
|
|
37
|
+
if (jsonLd['@context'] !== 'https://schema.org') {
|
|
38
|
+
findings.push({
|
|
39
|
+
check: 'json_ld',
|
|
40
|
+
severity: 'warning',
|
|
41
|
+
message: 'JSON-LD "@context" is not "https://schema.org".',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const required = REQUIRED_FIELDS_BY_TYPE[type];
|
|
45
|
+
if (required === undefined) {
|
|
46
|
+
findings.push({
|
|
47
|
+
check: 'json_ld',
|
|
48
|
+
severity: 'info',
|
|
49
|
+
message: `No required-field rules known for "@type": "${type}".`,
|
|
50
|
+
});
|
|
51
|
+
return findings;
|
|
52
|
+
}
|
|
53
|
+
for (const field of required) {
|
|
54
|
+
if (isBlank(jsonLd[field])) {
|
|
55
|
+
findings.push({
|
|
56
|
+
check: 'json_ld',
|
|
57
|
+
severity: 'error',
|
|
58
|
+
message: `JSON-LD is missing required field "${field}" for "@type": "${type}".`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return findings;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=json-ld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-ld.js","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"AAqBA,wKAAwK;AACxK,MAAM,UAAU,kBAAkB,CAAC,KAAyB;IAC1D,OAAO;QACL,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;QAChC,QAAQ,EAAE,KAAK,CAAC,KAAK;QACrB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE;QACrD,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9E,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;QACjF,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;KACnE,CAAA;AACH,CAAC;AAED,MAAM,uBAAuB,GAAgD;IAC3E,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC;IAChD,WAAW,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC;IACpD,WAAW,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC;CACrD,CAAA;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAA;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,MAAyC;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAC5C,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAA;IACtF,CAAC;IAED,MAAM,QAAQ,GAAiB,EAAE,CAAA;IACjC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,oBAAoB,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,iDAAiD;SAC3D,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,+CAA+C,IAAI,IAAI;SACjE,CAAC,CAAA;QACF,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,SAAS;gBAChB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,sCAAsC,KAAK,mBAAmB,IAAI,IAAI;aAChF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SeoFinding } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* "Vérification de llms.txt" (the AEO/GEO differentiator) — checks the
|
|
4
|
+
* shape https://llmstxt.org defines: an H1 title first, then H2 sections
|
|
5
|
+
* that group markdown links for an AI answer engine to crawl. Not a full
|
|
6
|
+
* grammar-level parser — the two things worth flagging are "no title at
|
|
7
|
+
* all" (the file is useless without one) and "no organised link sections"
|
|
8
|
+
* (the file has no links a crawler could actually follow).
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateLlmsTxt(content: string): readonly SeoFinding[];
|
|
11
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.d.ts","sourceRoot":"","sources":["../../src/seo/llms-txt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAI5C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,CAiCtE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const LINK_PATTERN = /\[[^\]]+\]\([^)]+\)/;
|
|
2
|
+
/**
|
|
3
|
+
* "Vérification de llms.txt" (the AEO/GEO differentiator) — checks the
|
|
4
|
+
* shape https://llmstxt.org defines: an H1 title first, then H2 sections
|
|
5
|
+
* that group markdown links for an AI answer engine to crawl. Not a full
|
|
6
|
+
* grammar-level parser — the two things worth flagging are "no title at
|
|
7
|
+
* all" (the file is useless without one) and "no organised link sections"
|
|
8
|
+
* (the file has no links a crawler could actually follow).
|
|
9
|
+
*/
|
|
10
|
+
export function validateLlmsTxt(content) {
|
|
11
|
+
const lines = content.split('\n');
|
|
12
|
+
const firstNonEmpty = lines.find((line) => line.trim() !== '');
|
|
13
|
+
if (firstNonEmpty === undefined || !firstNonEmpty.trim().startsWith('# ')) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
check: 'llms_txt',
|
|
17
|
+
severity: 'error',
|
|
18
|
+
message: 'llms.txt must start with an H1 title ("# Title").',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
const findings = [];
|
|
23
|
+
const hasH2Section = lines.some((line) => line.trim().startsWith('## '));
|
|
24
|
+
if (!hasH2Section) {
|
|
25
|
+
findings.push({
|
|
26
|
+
check: 'llms_txt',
|
|
27
|
+
severity: 'warning',
|
|
28
|
+
message: 'No H2 sections found — llms.txt should organise links under headed sections.',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!LINK_PATTERN.test(content)) {
|
|
32
|
+
findings.push({
|
|
33
|
+
check: 'llms_txt',
|
|
34
|
+
severity: 'warning',
|
|
35
|
+
message: 'No markdown links found — llms.txt should list links to the site’s key resources.',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../../src/seo/llms-txt.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,qBAAqB,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAE9D,IAAI,aAAa,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1E,OAAO;YACL;gBACE,KAAK,EAAE,UAAU;gBACjB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,mDAAmD;aAC7D;SACF,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAiB,EAAE,CAAA;IACjC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IACxE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,UAAU;YACjB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,8EAA8E;SACxF,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,UAAU;YACjB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,mFAAmF;SAC7F,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flesch Reading Ease, standard formula — higher is easier to read (roughly
|
|
3
|
+
* 0–100, though the formula can technically exceed those bounds). No
|
|
4
|
+
* dictionary or NLP dependency: syllables are counted heuristically, which
|
|
5
|
+
* is exactly why this is a "readability may be off" signal for the audit,
|
|
6
|
+
* not a hard fact.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fleschReadingEase(text: string): number;
|
|
9
|
+
export declare function countWords(text: string): number;
|
|
10
|
+
//# sourceMappingURL=readability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.d.ts","sourceRoot":"","sources":["../../src/seo/readability.ts"],"names":[],"mappings":"AAwBA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function splitSentences(text) {
|
|
2
|
+
return text
|
|
3
|
+
.split(/[.!?]+/)
|
|
4
|
+
.map((sentence) => sentence.trim())
|
|
5
|
+
.filter((sentence) => sentence.length > 0);
|
|
6
|
+
}
|
|
7
|
+
function splitWords(text) {
|
|
8
|
+
return text
|
|
9
|
+
.split(/\s+/)
|
|
10
|
+
.map((word) => word.trim())
|
|
11
|
+
.filter((word) => word.length > 0);
|
|
12
|
+
}
|
|
13
|
+
/** A heuristic vowel-group count — not a dictionary lookup (R9: no new dependency for this), imprecise on edge cases but stable and dependency-free. */
|
|
14
|
+
function countSyllables(word) {
|
|
15
|
+
const normalized = word.toLowerCase().replace(/[^a-z]/g, '');
|
|
16
|
+
if (normalized.length === 0)
|
|
17
|
+
return 0;
|
|
18
|
+
const vowelGroups = normalized.match(/[aeiouy]+/g);
|
|
19
|
+
let count = vowelGroups === null ? 1 : vowelGroups.length;
|
|
20
|
+
if (normalized.endsWith('e') && count > 1)
|
|
21
|
+
count -= 1;
|
|
22
|
+
return Math.max(count, 1);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Flesch Reading Ease, standard formula — higher is easier to read (roughly
|
|
26
|
+
* 0–100, though the formula can technically exceed those bounds). No
|
|
27
|
+
* dictionary or NLP dependency: syllables are counted heuristically, which
|
|
28
|
+
* is exactly why this is a "readability may be off" signal for the audit,
|
|
29
|
+
* not a hard fact.
|
|
30
|
+
*/
|
|
31
|
+
export function fleschReadingEase(text) {
|
|
32
|
+
const sentences = splitSentences(text);
|
|
33
|
+
const words = splitWords(text);
|
|
34
|
+
if (sentences.length === 0 || words.length === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
const syllables = words.reduce((sum, word) => sum + countSyllables(word), 0);
|
|
37
|
+
const wordsPerSentence = words.length / sentences.length;
|
|
38
|
+
const syllablesPerWord = syllables / words.length;
|
|
39
|
+
return 206.835 - 1.015 * wordsPerSentence - 84.6 * syllablesPerWord;
|
|
40
|
+
}
|
|
41
|
+
export function countWords(text) {
|
|
42
|
+
return splitWords(text).length;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=readability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.js","sourceRoot":"","sources":["../../src/seo/readability.ts"],"names":[],"mappings":"AAAA,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,KAAK,CAAC,QAAQ,CAAC;SACf,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;SAClC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI;SACR,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,wJAAwJ;AACxJ,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IAC5D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrC,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IAClD,IAAI,KAAK,GAAG,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAA;IACzD,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,KAAK,IAAI,CAAC,CAAA;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAE1D,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5E,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;IACxD,MAAM,gBAAgB,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,CAAA;IAEjD,OAAO,OAAO,GAAG,KAAK,GAAG,gBAAgB,GAAG,IAAI,GAAG,gBAAgB,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Redirect {
|
|
2
|
+
readonly from: string;
|
|
3
|
+
readonly to: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* "Suivi des redirections orphelines" — a redirect is orphaned when its
|
|
7
|
+
* target never reaches a live URL, whether directly, through a chain of
|
|
8
|
+
* other redirects, or in a cycle. Following the chain (bounded, so a cycle
|
|
9
|
+
* cannot loop forever) is what makes A→B→C(live) correctly *not* orphaned
|
|
10
|
+
* even though B itself is not a live URL.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findOrphanedRedirects(redirects: readonly Redirect[], liveUrls: readonly string[]): readonly Redirect[];
|
|
13
|
+
//# sourceMappingURL=redirects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects.d.ts","sourceRoot":"","sources":["../../src/seo/redirects.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAID;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,SAAS,QAAQ,EAAE,CAmBrB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const MAX_CHAIN_DEPTH = 10;
|
|
2
|
+
/**
|
|
3
|
+
* "Suivi des redirections orphelines" — a redirect is orphaned when its
|
|
4
|
+
* target never reaches a live URL, whether directly, through a chain of
|
|
5
|
+
* other redirects, or in a cycle. Following the chain (bounded, so a cycle
|
|
6
|
+
* cannot loop forever) is what makes A→B→C(live) correctly *not* orphaned
|
|
7
|
+
* even though B itself is not a live URL.
|
|
8
|
+
*/
|
|
9
|
+
export function findOrphanedRedirects(redirects, liveUrls) {
|
|
10
|
+
const live = new Set(liveUrls);
|
|
11
|
+
const targetByFrom = new Map(redirects.map((redirect) => [redirect.from, redirect.to]));
|
|
12
|
+
function resolvesToLive(startTarget) {
|
|
13
|
+
const visited = new Set();
|
|
14
|
+
let current = startTarget;
|
|
15
|
+
for (let depth = 0; depth < MAX_CHAIN_DEPTH; depth++) {
|
|
16
|
+
if (live.has(current))
|
|
17
|
+
return true;
|
|
18
|
+
if (visited.has(current))
|
|
19
|
+
return false;
|
|
20
|
+
visited.add(current);
|
|
21
|
+
const next = targetByFrom.get(current);
|
|
22
|
+
if (next === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
current = next;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return redirects.filter((redirect) => !resolvesToLive(redirect.to));
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=redirects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects.js","sourceRoot":"","sources":["../../src/seo/redirects.ts"],"names":[],"mappings":"AAKA,MAAM,eAAe,GAAG,EAAE,CAAA;AAE1B;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAA8B,EAC9B,QAA2B;IAE3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC9B,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAEvF,SAAS,cAAc,CAAC,WAAmB;QACzC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;QACjC,IAAI,OAAO,GAAG,WAAW,CAAA;QACzB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC;YACrD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAA;YAClC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,KAAK,CAAA;YACtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACtC,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAA;YACpC,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AACrE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Not re-exported from `@cogenta/agents`' own `vector-rank.ts` — that one
|
|
3
|
+
* operates on `IndexedChunk` (the RAG index's own shape, L4 task 16); the
|
|
4
|
+
* SEO checks below only ever compare two bare vectors, so this is the
|
|
5
|
+
* smaller, undecorated version of the same formula.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cosineSimilarity(a: readonly number[], b: readonly number[]): number;
|
|
8
|
+
//# sourceMappingURL=similarity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../src/seo/similarity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAcnF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Not re-exported from `@cogenta/agents`' own `vector-rank.ts` — that one
|
|
3
|
+
* operates on `IndexedChunk` (the RAG index's own shape, L4 task 16); the
|
|
4
|
+
* SEO checks below only ever compare two bare vectors, so this is the
|
|
5
|
+
* smaller, undecorated version of the same formula.
|
|
6
|
+
*/
|
|
7
|
+
export function cosineSimilarity(a, b) {
|
|
8
|
+
let dot = 0;
|
|
9
|
+
let magnitudeA = 0;
|
|
10
|
+
let magnitudeB = 0;
|
|
11
|
+
const length = Math.min(a.length, b.length);
|
|
12
|
+
for (let i = 0; i < length; i++) {
|
|
13
|
+
const valueA = a[i] ?? 0;
|
|
14
|
+
const valueB = b[i] ?? 0;
|
|
15
|
+
dot += valueA * valueB;
|
|
16
|
+
magnitudeA += valueA * valueA;
|
|
17
|
+
magnitudeB += valueB * valueB;
|
|
18
|
+
}
|
|
19
|
+
if (magnitudeA === 0 || magnitudeB === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
return dot / (Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=similarity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.js","sourceRoot":"","sources":["../../src/seo/similarity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAoB,EAAE,CAAoB;IACzE,IAAI,GAAG,GAAG,CAAC,CAAA;IACX,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACxB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACxB,GAAG,IAAI,MAAM,GAAG,MAAM,CAAA;QACtB,UAAU,IAAI,MAAM,GAAG,MAAM,CAAA;QAC7B,UAAU,IAAI,MAAM,GAAG,MAAM,CAAA;IAC/B,CAAC;IACD,IAAI,UAAU,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAClD,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;AAC9D,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface SeoHeading {
|
|
2
|
+
/** 1–6, matching h1–h6. */
|
|
3
|
+
readonly level: number;
|
|
4
|
+
readonly text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SeoImage {
|
|
7
|
+
readonly alt: string | null;
|
|
8
|
+
/** A decorative image legitimately has no alt text — content.ts's own `alt`/`decorative` distinction (L4 task 5), reproduced here. */
|
|
9
|
+
readonly decorative?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deliberately not `@cogenta/blocks`' own entry/block types — the same
|
|
13
|
+
* narrow-structural-interface reasoning `ContentServiceLike` (L4 task 5)
|
|
14
|
+
* documents. Whatever flattens a real entry into this shape is the
|
|
15
|
+
* runtime's job, not this audit's.
|
|
16
|
+
*/
|
|
17
|
+
export interface SeoPageInput {
|
|
18
|
+
readonly url: string;
|
|
19
|
+
readonly title: string;
|
|
20
|
+
readonly metaDescription?: string;
|
|
21
|
+
readonly canonicalUrl?: string;
|
|
22
|
+
readonly headings: readonly SeoHeading[];
|
|
23
|
+
readonly images: readonly SeoImage[];
|
|
24
|
+
readonly internalLinks: readonly string[];
|
|
25
|
+
readonly bodyText: string;
|
|
26
|
+
}
|
|
27
|
+
export type SeoIssueSeverity = 'error' | 'warning' | 'info';
|
|
28
|
+
export interface SeoFinding {
|
|
29
|
+
readonly check: string;
|
|
30
|
+
readonly severity: SeoIssueSeverity;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SeoAuditResult {
|
|
34
|
+
readonly url: string;
|
|
35
|
+
readonly findings: readonly SeoFinding[];
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/seo/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,sIAAsI;IACtI,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAA;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAA;IACpC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;AAE3D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAA;CACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/seo/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cogenta/agents-builtin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The built-in agents shipped with Cogenta — Security, SEO, Content, Performance.",
|
|
5
|
+
"license": "MPL-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.11.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"provenance": true
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/cogenta-cms/cogenta.git",
|
|
27
|
+
"directory": "packages/agents-builtin"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
32
|
+
"test": "vitest run --passWithNoTests"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@cogenta/agents": "workspace:*",
|
|
36
|
+
"@cogenta/core": "workspace:*",
|
|
37
|
+
"zod": "catalog:"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "catalog:",
|
|
41
|
+
"vitest": "catalog:"
|
|
42
|
+
}
|
|
43
|
+
}
|