@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,34 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
/**
|
|
3
|
+
* "Croiser CVSS et EPSS (probabilité d'exploitation réelle)." FIRST.org's
|
|
4
|
+
* EPSS API takes a comma-separated CVE list and returns a score per CVE it
|
|
5
|
+
* recognises — a CVE it has no data for is simply absent from the result,
|
|
6
|
+
* not an error.
|
|
7
|
+
*/
|
|
8
|
+
export async function queryEpss(cveIds, options = {}) {
|
|
9
|
+
if (cveIds.length === 0)
|
|
10
|
+
return new Map();
|
|
11
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
12
|
+
const baseUrl = options.baseUrl ?? 'https://api.first.org/data/v1/epss';
|
|
13
|
+
const url = `${baseUrl}?cve=${cveIds.join(',')}`;
|
|
14
|
+
const response = await doFetch(url);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new CogentaError({
|
|
17
|
+
code: 'SECURITY_EPSS_QUERY_FAILED',
|
|
18
|
+
message: `EPSS query failed with status ${response.status}.`,
|
|
19
|
+
hint: 'Check network connectivity and https://www.first.org/epss.',
|
|
20
|
+
details: { status: response.status },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const body = (await response.json());
|
|
24
|
+
const scores = new Map();
|
|
25
|
+
for (const item of body.data ?? []) {
|
|
26
|
+
scores.set(item.cve, {
|
|
27
|
+
cve: item.cve,
|
|
28
|
+
epss: Number.parseFloat(item.epss),
|
|
29
|
+
percentile: Number.parseFloat(item.percentile),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return scores;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=epss-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"epss-client.js","sourceRoot":"","sources":["../../src/security/epss-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAc5C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAyB,EACzB,OAAO,GAAqB,EAAE;IAE9B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,EAAE,CAAA;IAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAA;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,oCAAoC,CAAA;IACvE,MAAM,GAAG,GAAG,GAAG,OAAO,QAAQ,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;IAEhD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,4BAA4B;YAClC,OAAO,EAAE,iCAAiC,QAAQ,CAAC,MAAM,GAAG;YAC5D,IAAI,EAAE,4DAA4D;YAClE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;SACrC,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAA;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAA;IAC3C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;YACnB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;SAC/C,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EpssScore } from './epss-client.js';
|
|
2
|
+
import type { OsvVulnerability } from './osv-client.js';
|
|
3
|
+
export type Urgency = 'low' | 'medium' | 'high' | 'critical';
|
|
4
|
+
export interface ExploitabilityAssessment {
|
|
5
|
+
readonly cvss?: number;
|
|
6
|
+
readonly epss?: number;
|
|
7
|
+
readonly urgency: Urgency;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* "Croiser CVSS et EPSS. Une CVE 9.8 jamais exploitée est moins urgente
|
|
11
|
+
* qu'une 6.5 activement utilisée." EPSS drives urgency once it is known and
|
|
12
|
+
* meaningfully non-zero; a high CVSS score with a near-zero EPSS is
|
|
13
|
+
* downgraded from what its severity alone would suggest, and a merely
|
|
14
|
+
* moderate CVSS with real-world exploitation activity is not.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assessExploitability(vuln: OsvVulnerability, epss?: EpssScore): ExploitabilityAssessment;
|
|
17
|
+
//# sourceMappingURL=exploitability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exploitability.d.ts","sourceRoot":"","sources":["../../src/security/exploitability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAA;AAE5D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAMD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,gBAAgB,EACtB,IAAI,CAAC,EAAE,SAAS,GACf,wBAAwB,CAsB1B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const EPSS_CRITICAL = 0.5;
|
|
2
|
+
const EPSS_HIGH = 0.1;
|
|
3
|
+
const EPSS_DORMANT = 0.01;
|
|
4
|
+
/**
|
|
5
|
+
* "Croiser CVSS et EPSS. Une CVE 9.8 jamais exploitée est moins urgente
|
|
6
|
+
* qu'une 6.5 activement utilisée." EPSS drives urgency once it is known and
|
|
7
|
+
* meaningfully non-zero; a high CVSS score with a near-zero EPSS is
|
|
8
|
+
* downgraded from what its severity alone would suggest, and a merely
|
|
9
|
+
* moderate CVSS with real-world exploitation activity is not.
|
|
10
|
+
*/
|
|
11
|
+
export function assessExploitability(vuln, epss) {
|
|
12
|
+
const cvss = vuln.cvssScore;
|
|
13
|
+
const epssValue = epss?.epss;
|
|
14
|
+
let urgency;
|
|
15
|
+
if (epssValue !== undefined && epssValue >= EPSS_CRITICAL) {
|
|
16
|
+
urgency = 'critical';
|
|
17
|
+
}
|
|
18
|
+
else if (epssValue !== undefined && epssValue >= EPSS_HIGH) {
|
|
19
|
+
urgency = 'high';
|
|
20
|
+
}
|
|
21
|
+
else if (cvss !== undefined && cvss >= 9) {
|
|
22
|
+
urgency = epssValue !== undefined && epssValue < EPSS_DORMANT ? 'medium' : 'high';
|
|
23
|
+
}
|
|
24
|
+
else if (cvss !== undefined && cvss >= 7) {
|
|
25
|
+
urgency = 'medium';
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
urgency = 'low';
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...(cvss === undefined ? {} : { cvss }),
|
|
32
|
+
...(epssValue === undefined ? {} : { epss: epssValue }),
|
|
33
|
+
urgency,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=exploitability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exploitability.js","sourceRoot":"","sources":["../../src/security/exploitability.ts"],"names":[],"mappings":"AAWA,MAAM,aAAa,GAAG,GAAG,CAAA;AACzB,MAAM,SAAS,GAAG,GAAG,CAAA;AACrB,MAAM,YAAY,GAAG,IAAI,CAAA;AAEzB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAsB,EACtB,IAAgB;IAEhB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAA;IAC3B,MAAM,SAAS,GAAG,IAAI,EAAE,IAAI,CAAA;IAE5B,IAAI,OAAgB,CAAA;IACpB,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,aAAa,EAAE,CAAC;QAC1D,OAAO,GAAG,UAAU,CAAA;IACtB,CAAC;SAAM,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;QAC7D,OAAO,GAAG,MAAM,CAAA;IAClB,CAAC;SAAM,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,GAAG,SAAS,KAAK,SAAS,IAAI,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAA;IACnF,CAAC;SAAM,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,GAAG,QAAQ,CAAA;IACpB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,KAAK,CAAA;IACjB,CAAC;IAED,OAAO;QACL,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QACvD,OAAO;KACR,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SbomEntry } from './sbom.js';
|
|
2
|
+
export interface OsvVulnerability {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly summary?: string;
|
|
5
|
+
readonly details?: string;
|
|
6
|
+
readonly aliases?: readonly string[];
|
|
7
|
+
/** Normalised 0–10 CVSS base score, when OSV's response carries one — undefined when only a raw vector string (no parseable base score) is available. */
|
|
8
|
+
readonly cvssScore?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface OsvMatch {
|
|
11
|
+
readonly entry: SbomEntry;
|
|
12
|
+
readonly vulnerabilities: readonly OsvVulnerability[];
|
|
13
|
+
}
|
|
14
|
+
export interface QueryOsvOptions {
|
|
15
|
+
readonly fetchImpl?: typeof fetch;
|
|
16
|
+
readonly baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* "Ne signaler que si le site est réellement concerné : version installée
|
|
20
|
+
* affectée." OSV's `/v1/query` endpoint already does exactly that matching
|
|
21
|
+
* — given one concrete package + version, it returns only the
|
|
22
|
+
* vulnerabilities whose `affected` ranges cover that version, so this
|
|
23
|
+
* module does not re-implement semver-range matching on top of it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function queryOsv(entries: readonly SbomEntry[], options?: QueryOsvOptions): Promise<readonly OsvMatch[]>;
|
|
26
|
+
//# sourceMappingURL=osv-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"osv-client.d.ts","sourceRoot":"","sources":["../../src/security/osv-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,yJAAyJ;IACzJ,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,eAAe,EAAE,SAAS,gBAAgB,EAAE,CAAA;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AA4CD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,SAAS,QAAQ,EAAE,CAAC,CA4B9B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
const QUALITATIVE_SEVERITY_SCORE = {
|
|
3
|
+
CRITICAL: 9.5,
|
|
4
|
+
HIGH: 7.5,
|
|
5
|
+
MODERATE: 5.5,
|
|
6
|
+
MEDIUM: 5.5,
|
|
7
|
+
LOW: 2.5,
|
|
8
|
+
};
|
|
9
|
+
function extractCvssScore(raw) {
|
|
10
|
+
for (const entry of raw.severity ?? []) {
|
|
11
|
+
const asNumber = Number.parseFloat(entry.score);
|
|
12
|
+
if (!Number.isNaN(asNumber) && asNumber >= 0 && asNumber <= 10)
|
|
13
|
+
return asNumber;
|
|
14
|
+
}
|
|
15
|
+
const qualitative = raw.database_specific?.severity?.toUpperCase();
|
|
16
|
+
return qualitative === undefined ? undefined : QUALITATIVE_SEVERITY_SCORE[qualitative];
|
|
17
|
+
}
|
|
18
|
+
function normalize(raw) {
|
|
19
|
+
const cvssScore = extractCvssScore(raw);
|
|
20
|
+
return {
|
|
21
|
+
id: raw.id,
|
|
22
|
+
...(raw.summary === undefined ? {} : { summary: raw.summary }),
|
|
23
|
+
...(raw.details === undefined ? {} : { details: raw.details }),
|
|
24
|
+
...(raw.aliases === undefined ? {} : { aliases: raw.aliases }),
|
|
25
|
+
...(cvssScore === undefined ? {} : { cvssScore }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* "Ne signaler que si le site est réellement concerné : version installée
|
|
30
|
+
* affectée." OSV's `/v1/query` endpoint already does exactly that matching
|
|
31
|
+
* — given one concrete package + version, it returns only the
|
|
32
|
+
* vulnerabilities whose `affected` ranges cover that version, so this
|
|
33
|
+
* module does not re-implement semver-range matching on top of it.
|
|
34
|
+
*/
|
|
35
|
+
export async function queryOsv(entries, options = {}) {
|
|
36
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
37
|
+
const url = options.baseUrl ?? 'https://api.osv.dev/v1/query';
|
|
38
|
+
const matches = [];
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const response = await doFetch(url, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
version: entry.version,
|
|
45
|
+
package: { name: entry.name, ecosystem: entry.ecosystem },
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new CogentaError({
|
|
50
|
+
code: 'SECURITY_OSV_QUERY_FAILED',
|
|
51
|
+
message: `OSV query for "${entry.name}@${entry.version}" failed with status ${response.status}.`,
|
|
52
|
+
hint: 'Check network connectivity and https://status.osv.dev.',
|
|
53
|
+
details: { status: response.status, package: entry.name, version: entry.version },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const body = (await response.json());
|
|
57
|
+
if (body.vulns !== undefined && body.vulns.length > 0) {
|
|
58
|
+
matches.push({ entry, vulnerabilities: body.vulns.map(normalize) });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return matches;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=osv-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"osv-client.js","sourceRoot":"","sources":["../../src/security/osv-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface PrFile {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OpenPrOptions {
|
|
6
|
+
readonly baseBranch: string;
|
|
7
|
+
readonly branchName: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly body: string;
|
|
10
|
+
readonly files: readonly PrFile[];
|
|
11
|
+
}
|
|
12
|
+
export interface PrResult {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly number: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* "Le correctif est une PR avec les tests joués, jamais une modification
|
|
18
|
+
* directe." One structural capability — open a branch, commit the given
|
|
19
|
+
* files, open a PR — hides whichever concrete forge (GitHub, GitLab, a
|
|
20
|
+
* self-hosted one) actually implements it, the same reasoning
|
|
21
|
+
* `ContentServiceLike` (L4 task 5) and `EmbeddingProvider` (L4 task 14) use:
|
|
22
|
+
* `deps.patch` depends on this capability, not on GitHub's REST API shape.
|
|
23
|
+
* A real GitHub-backed implementation is a separate concern from this tool.
|
|
24
|
+
*/
|
|
25
|
+
export interface PrClient {
|
|
26
|
+
open(options: OpenPrOptions): Promise<PrResult>;
|
|
27
|
+
/** The `revert` half — closing the PR without merging it. */
|
|
28
|
+
close(prNumber: number): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=pr-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-client.d.ts","sourceRoot":"","sources":["../../src/security/pr-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/C,6DAA6D;IAC7D,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-client.js","sourceRoot":"","sources":["../../src/security/pr-client.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExploitabilityAssessment } from './exploitability.js';
|
|
2
|
+
import type { OsvVulnerability } from './osv-client.js';
|
|
3
|
+
export interface SecurityFinding {
|
|
4
|
+
readonly package: string;
|
|
5
|
+
readonly version: string;
|
|
6
|
+
readonly vulnerability: OsvVulnerability;
|
|
7
|
+
readonly assessment: ExploitabilityAssessment;
|
|
8
|
+
}
|
|
9
|
+
/** "Le rapport suit un format imposé... Aucun jargon brut." Five plain-language sections, always in this order. */
|
|
10
|
+
export interface SecurityReportEntry {
|
|
11
|
+
readonly finding: SecurityFinding;
|
|
12
|
+
readonly whatIsAffected: string;
|
|
13
|
+
readonly whatAnAttackerCouldDo: string;
|
|
14
|
+
readonly isTheSiteExposed: string;
|
|
15
|
+
readonly whatIsProposed: string;
|
|
16
|
+
readonly whatHappensIfNothingIsDone: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SecurityReport {
|
|
19
|
+
readonly entries: readonly SecurityReportEntry[];
|
|
20
|
+
readonly generatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildSecurityReport(findings: readonly SecurityFinding[], options?: {
|
|
23
|
+
readonly now?: () => number;
|
|
24
|
+
}): SecurityReport;
|
|
25
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/security/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAW,MAAM,qBAAqB,CAAA;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAA;CAC9C;AAED,mHAAmH;AACnH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAA;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAA;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AA2BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAAE,GACxC,cAAc,CAsBhB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function describeExposure(urgency) {
|
|
2
|
+
switch (urgency) {
|
|
3
|
+
case 'critical':
|
|
4
|
+
case 'high':
|
|
5
|
+
return 'Oui — la version installée est concernée, et cette vulnérabilité est activement exploitée dans la nature.';
|
|
6
|
+
case 'medium':
|
|
7
|
+
return 'Probablement — la version installée est concernée ; aucune exploitation active connue à ce jour.';
|
|
8
|
+
default:
|
|
9
|
+
return 'Faible — la version installée est techniquement concernée, mais le risque réel observé est limité.';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function describeInaction(urgency) {
|
|
13
|
+
switch (urgency) {
|
|
14
|
+
case 'critical':
|
|
15
|
+
return 'Le site reste exposé à une vulnérabilité activement exploitée — à traiter immédiatement.';
|
|
16
|
+
case 'high':
|
|
17
|
+
return 'Le risque d’exploitation reste élevé tant que la mise à jour n’est pas appliquée.';
|
|
18
|
+
case 'medium':
|
|
19
|
+
return 'Le risque reste modéré ; une mise à jour lors du prochain cycle de maintenance est raisonnable.';
|
|
20
|
+
default:
|
|
21
|
+
return 'Le risque immédiat est faible ; pas d’urgence particulière à agir.';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function buildSecurityReport(findings, options) {
|
|
25
|
+
const now = options?.now ?? Date.now;
|
|
26
|
+
const entries = findings.map((finding) => {
|
|
27
|
+
const aliases = finding.vulnerability.aliases !== undefined && finding.vulnerability.aliases.length > 0
|
|
28
|
+
? ` (alias ${finding.vulnerability.aliases.join(', ')})`
|
|
29
|
+
: '';
|
|
30
|
+
return {
|
|
31
|
+
finding,
|
|
32
|
+
whatIsAffected: `${finding.package}@${finding.version} — ${finding.vulnerability.id}${aliases}.`,
|
|
33
|
+
whatAnAttackerCouldDo: finding.vulnerability.summary ??
|
|
34
|
+
finding.vulnerability.details ??
|
|
35
|
+
'Le détail n’est pas fourni par la source ; se référer à l’avis de sécurité pour l’impact exact.',
|
|
36
|
+
isTheSiteExposed: describeExposure(finding.assessment.urgency),
|
|
37
|
+
whatIsProposed: `Mettre à jour ${finding.package} au-delà de la version affectée.`,
|
|
38
|
+
whatHappensIfNothingIsDone: describeInaction(finding.assessment.urgency),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
return { entries, generatedAt: new Date(now()).toISOString() };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/security/report.ts"],"names":[],"mappings":"AAyBA,SAAS,gBAAgB,CAAC,OAAgB;IACxC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,UAAU,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,2GAA2G,CAAA;QACpH,KAAK,QAAQ;YACX,OAAO,kGAAkG,CAAA;QAC3G;YACE,OAAO,oGAAoG,CAAA;IAC/G,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAgB;IACxC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,UAAU;YACb,OAAO,0FAA0F,CAAA;QACnG,KAAK,MAAM;YACT,OAAO,mFAAmF,CAAA;QAC5F,KAAK,QAAQ;YACX,OAAO,iGAAiG,CAAA;QAC1G;YACE,OAAO,oEAAoE,CAAA;IAC/E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,QAAoC,EACpC,OAAyC;IAEzC,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAA;IAEpC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAuB,EAAE;QAC5D,MAAM,OAAO,GACX,OAAO,CAAC,aAAa,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YACrF,CAAC,CAAC,WAAW,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACxD,CAAC,CAAC,EAAE,CAAA;QACR,OAAO;YACL,OAAO;YACP,cAAc,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,GAAG,OAAO,GAAG;YAChG,qBAAqB,EACnB,OAAO,CAAC,aAAa,CAAC,OAAO;gBAC7B,OAAO,CAAC,aAAa,CAAC,OAAO;gBAC7B,iGAAiG;YACnG,gBAAgB,EAAE,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC9D,cAAc,EAAE,iBAAiB,OAAO,CAAC,OAAO,kCAAkC;YAClF,0BAA0B,EAAE,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;SACzE,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAA;AAChE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SbomEntry {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly version: string;
|
|
4
|
+
/** OSV's ecosystem identifier (e.g. `npm`) — https://ossf.github.io/osv-schema/#affectedpackage-field */
|
|
5
|
+
readonly ecosystem: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `dependencies` must already be resolved, exact versions (from a lockfile),
|
|
9
|
+
* not the semver ranges a `package.json` declares — OSV's query API matches
|
|
10
|
+
* one concrete version, not a range, and resolving a range to what is
|
|
11
|
+
* actually installed is the caller's job (reading the lockfile), not this
|
|
12
|
+
* pure function's.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildSbom(dependencies: Readonly<Record<string, string>>, ecosystem?: string): readonly SbomEntry[];
|
|
15
|
+
//# sourceMappingURL=sbom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sbom.d.ts","sourceRoot":"","sources":["../../src/security/sbom.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,yGAAyG;IACzG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC9C,SAAS,SAAQ,GAChB,SAAS,SAAS,EAAE,CAEtB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dependencies` must already be resolved, exact versions (from a lockfile),
|
|
3
|
+
* not the semver ranges a `package.json` declares — OSV's query API matches
|
|
4
|
+
* one concrete version, not a range, and resolving a range to what is
|
|
5
|
+
* actually installed is the caller's job (reading the lockfile), not this
|
|
6
|
+
* pure function's.
|
|
7
|
+
*/
|
|
8
|
+
export function buildSbom(dependencies, ecosystem = 'npm') {
|
|
9
|
+
return Object.entries(dependencies).map(([name, version]) => ({ name, version, ecosystem }));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=sbom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sbom.js","sourceRoot":"","sources":["../../src/security/sbom.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,YAA8C,EAC9C,SAAS,GAAG,KAAK;IAEjB,OAAO,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;AAC9F,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AgentDeclaration } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* "Règle : il propose des brouillons, il ne publie jamais." No `content.publish`
|
|
4
|
+
* anywhere in `tools` — the runtime cannot grant what was never listed
|
|
5
|
+
* (L4 task 4's own enforcement point), so this is structural, not a
|
|
6
|
+
* convention the agent merely follows.
|
|
7
|
+
*/
|
|
8
|
+
export declare const seoAgent: AgentDeclaration;
|
|
9
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/seo/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,gBAcrB,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineAgent } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* "Règle : il propose des brouillons, il ne publie jamais." No `content.publish`
|
|
4
|
+
* anywhere in `tools` — the runtime cannot grant what was never listed
|
|
5
|
+
* (L4 task 4's own enforcement point), so this is structural, not a
|
|
6
|
+
* convention the agent merely follows.
|
|
7
|
+
*/
|
|
8
|
+
export const seoAgent = defineAgent({
|
|
9
|
+
name: 'seo',
|
|
10
|
+
identity: './identity.md',
|
|
11
|
+
model: { preferred: 'claude-sonnet', fallback: 'local' },
|
|
12
|
+
tools: ['content.read', 'content.write_draft', 'http.fetch', 'channel.send'],
|
|
13
|
+
skills: ['seo-audit', 'aeo-geo'],
|
|
14
|
+
autonomy: { default: 'propose' },
|
|
15
|
+
budget: { tokensPerDay: 150_000, eurPerMonth: 8, callsPerHour: 30 },
|
|
16
|
+
memory: { episodic: true, semantic: true, scope: 'site' },
|
|
17
|
+
triggers: [
|
|
18
|
+
{ on: 'content.before_publish' },
|
|
19
|
+
{ on: 'schedule', cron: '0 6 * * 1' },
|
|
20
|
+
{ on: 'content.structure_changed' },
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/seo/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAqB,WAAW,CAAC;IACpD,IAAI,EAAE,KAAK;IACX,QAAQ,EAAE,eAAe;IACzB,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE;IACxD,KAAK,EAAE,CAAC,cAAc,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,CAAC;IAC5E,MAAM,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC;IAChC,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IAChC,MAAM,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;IACnE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACzD,QAAQ,EAAE;QACR,EAAE,EAAE,EAAE,wBAAwB,EAAE;QAChC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE;QACrC,EAAE,EAAE,EAAE,2BAA2B,EAAE;KACpC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SeoAuditResult, SeoPageInput } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* "Audit à la publication : titres, méta, structure de titres, alt,
|
|
4
|
+
* maillage interne, canoniques, longueur, lisibilité." Deterministic
|
|
5
|
+
* checks, not a model's judgement call — the acceptance criterion is a
|
|
6
|
+
* measured false-positive rate, which a prompt alone cannot guarantee.
|
|
7
|
+
* JSON-LD, internal-link proposals, cannibalisation and AEO/GEO are task
|
|
8
|
+
* 6's job, not this one's.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately not exposed as a callable tool: the SEO agent's declared
|
|
11
|
+
* tools are only `content.read`/`content.write_draft`/`http.fetch`/
|
|
12
|
+
* `channel.send` (per the lot's own agent spec) — this runs as a
|
|
13
|
+
* deterministic pre-processing step feeding the agent's context, the
|
|
14
|
+
* model never invokes it directly.
|
|
15
|
+
*/
|
|
16
|
+
export declare function auditSeoPage(page: SeoPageInput): SeoAuditResult;
|
|
17
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/seo/audit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAc,YAAY,EAAE,MAAM,YAAY,CAAA;AAoK1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CAY/D"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { countWords, fleschReadingEase } from './readability.js';
|
|
2
|
+
const TITLE_MIN_CHARS = 15;
|
|
3
|
+
const TITLE_MAX_CHARS = 60;
|
|
4
|
+
const META_MIN_CHARS = 50;
|
|
5
|
+
const META_MAX_CHARS = 160;
|
|
6
|
+
const MIN_WORD_COUNT = 300;
|
|
7
|
+
const MIN_READABILITY = 30;
|
|
8
|
+
function checkTitle(page) {
|
|
9
|
+
const title = page.title.trim();
|
|
10
|
+
if (title === '') {
|
|
11
|
+
return [{ check: 'title', severity: 'error', message: 'The page has no title.' }];
|
|
12
|
+
}
|
|
13
|
+
if (title.length < TITLE_MIN_CHARS) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
check: 'title',
|
|
17
|
+
severity: 'warning',
|
|
18
|
+
message: `Title is short (${title.length} characters) — likely too vague for search results.`,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
if (title.length > TITLE_MAX_CHARS) {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
check: 'title',
|
|
26
|
+
severity: 'warning',
|
|
27
|
+
message: `Title is long (${title.length} characters) — may be truncated in search results.`,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
function checkMetaDescription(page) {
|
|
34
|
+
const description = page.metaDescription?.trim();
|
|
35
|
+
if (description === undefined || description === '') {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
check: 'meta_description',
|
|
39
|
+
severity: 'warning',
|
|
40
|
+
message: 'No meta description — search engines will generate one, less control over the snippet.',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
if (description.length < META_MIN_CHARS) {
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
check: 'meta_description',
|
|
48
|
+
severity: 'info',
|
|
49
|
+
message: `Meta description is short (${description.length} characters).`,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (description.length > META_MAX_CHARS) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
check: 'meta_description',
|
|
57
|
+
severity: 'warning',
|
|
58
|
+
message: `Meta description is long (${description.length} characters) — likely truncated in search results.`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
function checkHeadingStructure(page) {
|
|
65
|
+
const findings = [];
|
|
66
|
+
const h1Count = page.headings.filter((heading) => heading.level === 1).length;
|
|
67
|
+
if (h1Count === 0) {
|
|
68
|
+
findings.push({
|
|
69
|
+
check: 'heading_structure',
|
|
70
|
+
severity: 'error',
|
|
71
|
+
message: 'No H1 heading found.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else if (h1Count > 1) {
|
|
75
|
+
findings.push({
|
|
76
|
+
check: 'heading_structure',
|
|
77
|
+
severity: 'warning',
|
|
78
|
+
message: `${h1Count} H1 headings found — a page should have exactly one.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let previousLevel = 0;
|
|
82
|
+
for (const heading of page.headings) {
|
|
83
|
+
if (previousLevel > 0 && heading.level > previousLevel + 1) {
|
|
84
|
+
findings.push({
|
|
85
|
+
check: 'heading_structure',
|
|
86
|
+
severity: 'warning',
|
|
87
|
+
message: `Heading level skips from H${previousLevel} to H${heading.level} ("${heading.text}") — do not skip levels.`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
previousLevel = heading.level;
|
|
91
|
+
}
|
|
92
|
+
return findings;
|
|
93
|
+
}
|
|
94
|
+
function checkAltText(page) {
|
|
95
|
+
const missing = page.images.filter((image) => image.decorative !== true && (image.alt === null || image.alt.trim() === ''));
|
|
96
|
+
if (missing.length === 0)
|
|
97
|
+
return [];
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
check: 'alt_text',
|
|
101
|
+
severity: 'error',
|
|
102
|
+
message: `${missing.length} image(s) missing alt text.`,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
function checkInternalLinking(page) {
|
|
107
|
+
if (page.internalLinks.length === 0) {
|
|
108
|
+
return [
|
|
109
|
+
{
|
|
110
|
+
check: 'internal_linking',
|
|
111
|
+
severity: 'warning',
|
|
112
|
+
message: 'No internal links — this page risks being an orphan.',
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
function checkCanonical(page) {
|
|
119
|
+
if (page.canonicalUrl === undefined || page.canonicalUrl.trim() === '') {
|
|
120
|
+
return [{ check: 'canonical', severity: 'warning', message: 'No canonical URL declared.' }];
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
function checkLength(page) {
|
|
125
|
+
const wordCount = countWords(page.bodyText);
|
|
126
|
+
if (wordCount < MIN_WORD_COUNT) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
check: 'length',
|
|
130
|
+
severity: 'warning',
|
|
131
|
+
message: `Body is thin (${wordCount} words, under ${MIN_WORD_COUNT}).`,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
function checkReadability(page) {
|
|
138
|
+
if (page.bodyText.trim() === '')
|
|
139
|
+
return [];
|
|
140
|
+
const score = fleschReadingEase(page.bodyText);
|
|
141
|
+
if (score < MIN_READABILITY) {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
check: 'readability',
|
|
145
|
+
severity: 'info',
|
|
146
|
+
message: `Text may be hard to read (Flesch score ${score.toFixed(1)}).`,
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* "Audit à la publication : titres, méta, structure de titres, alt,
|
|
154
|
+
* maillage interne, canoniques, longueur, lisibilité." Deterministic
|
|
155
|
+
* checks, not a model's judgement call — the acceptance criterion is a
|
|
156
|
+
* measured false-positive rate, which a prompt alone cannot guarantee.
|
|
157
|
+
* JSON-LD, internal-link proposals, cannibalisation and AEO/GEO are task
|
|
158
|
+
* 6's job, not this one's.
|
|
159
|
+
*
|
|
160
|
+
* Deliberately not exposed as a callable tool: the SEO agent's declared
|
|
161
|
+
* tools are only `content.read`/`content.write_draft`/`http.fetch`/
|
|
162
|
+
* `channel.send` (per the lot's own agent spec) — this runs as a
|
|
163
|
+
* deterministic pre-processing step feeding the agent's context, the
|
|
164
|
+
* model never invokes it directly.
|
|
165
|
+
*/
|
|
166
|
+
export function auditSeoPage(page) {
|
|
167
|
+
const findings = [
|
|
168
|
+
...checkTitle(page),
|
|
169
|
+
...checkMetaDescription(page),
|
|
170
|
+
...checkHeadingStructure(page),
|
|
171
|
+
...checkAltText(page),
|
|
172
|
+
...checkInternalLinking(page),
|
|
173
|
+
...checkCanonical(page),
|
|
174
|
+
...checkLength(page),
|
|
175
|
+
...checkReadability(page),
|
|
176
|
+
];
|
|
177
|
+
return { url: page.url, findings };
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/seo/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CannibalizationCandidatePage {
|
|
2
|
+
readonly url: string;
|
|
3
|
+
readonly bodyText: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CannibalizationPair {
|
|
6
|
+
readonly urlA: string;
|
|
7
|
+
readonly urlB: string;
|
|
8
|
+
readonly score: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DetectCannibalizationOptions {
|
|
11
|
+
readonly threshold?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* "Détection de cannibalisation entre pages" — two *different* pages whose
|
|
15
|
+
* body text is topically near-identical compete for the same search intent
|
|
16
|
+
* instead of ranking for it together. Same embedding approach as
|
|
17
|
+
* `proposeInternalLinks`, but the signal here is a pair scoring above a high
|
|
18
|
+
* threshold, not a ranked list.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectCannibalization(pages: readonly CannibalizationCandidatePage[], options?: DetectCannibalizationOptions): Promise<readonly CannibalizationPair[]>;
|
|
21
|
+
//# sourceMappingURL=cannibalization.d.ts.map
|