@cogenta/agents-builtin 0.6.6 → 0.7.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/accessibility/agent.d.ts +10 -0
- package/dist/accessibility/agent.d.ts.map +1 -0
- package/dist/accessibility/agent.js +19 -0
- package/dist/accessibility/agent.js.map +1 -0
- package/dist/accessibility/audit.d.ts +36 -0
- package/dist/accessibility/audit.d.ts.map +1 -0
- package/dist/accessibility/audit.js +142 -0
- package/dist/accessibility/audit.js.map +1 -0
- package/dist/analytics/agent.d.ts +9 -0
- package/dist/analytics/agent.d.ts.map +1 -0
- package/dist/analytics/agent.js +18 -0
- package/dist/analytics/agent.js.map +1 -0
- package/dist/analytics/signals.d.ts +35 -0
- package/dist/analytics/signals.d.ts.map +1 -0
- package/dist/analytics/signals.js +70 -0
- package/dist/analytics/signals.js.map +1 -0
- package/dist/compliance/agent.d.ts +9 -0
- package/dist/compliance/agent.d.ts.map +1 -0
- package/dist/compliance/agent.js +18 -0
- package/dist/compliance/agent.js.map +1 -0
- package/dist/compliance/audit.d.ts +44 -0
- package/dist/compliance/audit.d.ts.map +1 -0
- package/dist/compliance/audit.js +64 -0
- package/dist/compliance/audit.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/media/agent.d.ts +10 -0
- package/dist/media/agent.d.ts.map +1 -0
- package/dist/media/agent.js +26 -0
- package/dist/media/agent.js.map +1 -0
- package/dist/media/audit.d.ts +38 -0
- package/dist/media/audit.d.ts.map +1 -0
- package/dist/media/audit.js +56 -0
- package/dist/media/audit.js.map +1 -0
- package/dist/migration/agent.d.ts +10 -0
- package/dist/migration/agent.d.ts.map +1 -0
- package/dist/migration/agent.js +26 -0
- package/dist/migration/agent.js.map +1 -0
- package/dist/migration/residue.d.ts +48 -0
- package/dist/migration/residue.d.ts.map +1 -0
- package/dist/migration/residue.js +92 -0
- package/dist/migration/residue.js.map +1 -0
- package/dist/moderation/agent.d.ts +9 -0
- package/dist/moderation/agent.d.ts.map +1 -0
- package/dist/moderation/agent.js +18 -0
- package/dist/moderation/agent.js.map +1 -0
- package/dist/moderation/triage.d.ts +24 -0
- package/dist/moderation/triage.d.ts.map +1 -0
- package/dist/moderation/triage.js +26 -0
- package/dist/moderation/triage.js.map +1 -0
- package/dist/translation/agent.d.ts +9 -0
- package/dist/translation/agent.d.ts.map +1 -0
- package/dist/translation/agent.js +24 -0
- package/dist/translation/agent.js.map +1 -0
- package/dist/translation/gaps.d.ts +31 -0
- package/dist/translation/gaps.d.ts.map +1 -0
- package/dist/translation/gaps.js +44 -0
- package/dist/translation/gaps.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export { accessibilityAgent } from './accessibility/agent.js';
|
|
2
|
+
export { auditAccessibility, auditContrast, contrastRatio, relativeLuminance, } from './accessibility/audit.js';
|
|
3
|
+
export { analyticsAgent } from './analytics/agent.js';
|
|
4
|
+
export { readAudienceSignals } from './analytics/signals.js';
|
|
5
|
+
export { complianceAgent } from './compliance/agent.js';
|
|
6
|
+
export { auditCompliance } from './compliance/audit.js';
|
|
1
7
|
export { contentAgent } from './content/agent.js';
|
|
2
8
|
export { createContentDraftTool } from './content/provenance.js';
|
|
3
9
|
export { checkTerminology } from './content/terminology.js';
|
|
@@ -6,6 +12,12 @@ export { designerAgent } from './designer/agent.js';
|
|
|
6
12
|
export { developerAgent } from './developer/agent.js';
|
|
7
13
|
export { createCodePatchTool } from './developer/patch-tool.js';
|
|
8
14
|
export { imageCreatorAgent } from './image-creator/agent.js';
|
|
15
|
+
export { mediaAgent } from './media/agent.js';
|
|
16
|
+
export { auditMediaLibrary } from './media/audit.js';
|
|
17
|
+
export { migrationAgent } from './migration/agent.js';
|
|
18
|
+
export { findMigrationResidue, redirectsFor } from './migration/residue.js';
|
|
19
|
+
export { moderationAgent } from './moderation/agent.js';
|
|
20
|
+
export { triageComments } from './moderation/triage.js';
|
|
9
21
|
export { performanceAgent } from './performance/agent.js';
|
|
10
22
|
export { compareToBudget } from './performance/budget.js';
|
|
11
23
|
export { queryCrux } from './performance/crux-client.js';
|
|
@@ -40,4 +52,6 @@ export { createProposeThemeTool } from './theme-creator/propose-theme-tool.js';
|
|
|
40
52
|
export { createListSandboxFilesTool, createReadSandboxFileTool, } from './theme-creator/read-sandbox-tool.js';
|
|
41
53
|
export { COGENTA_THEME_SPECIFICATION } from './theme-creator/theme-spec.js';
|
|
42
54
|
export { createWriteSandboxFileTool } from './theme-creator/write-sandbox-file-tool.js';
|
|
55
|
+
export { translationAgent } from './translation/agent.js';
|
|
56
|
+
export { findTranslationGaps } from './translation/gaps.js';
|
|
43
57
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAO3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAMrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAS9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAU9D,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,gCAAgC,GACjC,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAMlE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAMnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAErD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAEnE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAM7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAA;AAOhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAEhE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAStD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAK5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAMhF,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAA;AAMtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAMlF,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAA;AAQ9E,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAA;AAM3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AgentDeclaration } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. Its findings come from `auditMediaLibrary`, never
|
|
4
|
+
* from the model: the model writes alt text for the images the audit named,
|
|
5
|
+
* and nothing else. No delete anywhere in `tools` — "no longer referenced"
|
|
6
|
+
* is not "no longer wanted", and a file removal is not reversible by the
|
|
7
|
+
* content trash.
|
|
8
|
+
*/
|
|
9
|
+
export declare const mediaAgent: AgentDeclaration;
|
|
10
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/media/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iBAAiB,CAAA;AAEpE;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,gBAgBvB,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineAgent } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. Its findings come from `auditMediaLibrary`, never
|
|
4
|
+
* from the model: the model writes alt text for the images the audit named,
|
|
5
|
+
* and nothing else. No delete anywhere in `tools` — "no longer referenced"
|
|
6
|
+
* is not "no longer wanted", and a file removal is not reversible by the
|
|
7
|
+
* content trash.
|
|
8
|
+
*/
|
|
9
|
+
export const mediaAgent = defineAgent({
|
|
10
|
+
name: 'media',
|
|
11
|
+
identity: './identity.md',
|
|
12
|
+
model: { preferred: 'claude-sonnet', fallback: 'local' },
|
|
13
|
+
tools: [
|
|
14
|
+
'media.list',
|
|
15
|
+
'media.read',
|
|
16
|
+
'media.write',
|
|
17
|
+
'content.collections',
|
|
18
|
+
'content.list',
|
|
19
|
+
'content.read',
|
|
20
|
+
],
|
|
21
|
+
autonomy: { default: 'propose' },
|
|
22
|
+
budget: { tokensPerDay: 60_000, eurPerMonth: 4, callsPerHour: 15 },
|
|
23
|
+
memory: { episodic: true, scope: 'site' },
|
|
24
|
+
triggers: [{ on: 'schedule', cron: '0 5 * * 1' }],
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/media/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAEpE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqB,WAAW,CAAC;IACtD,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,eAAe;IACzB,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE;IACxD,KAAK,EAAE;QACL,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,qBAAqB;QACrB,cAAc;QACd,cAAc;KACf;IACD,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IAChC,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;IAClE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACzC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;CAClD,CAAC,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is wrong with a media library, computed — never guessed (L5 task 10).
|
|
3
|
+
*
|
|
4
|
+
* The rule the priority-1 agents established holds here: the model never
|
|
5
|
+
* decides *that* something is a problem, only how to word the fix. Every
|
|
6
|
+
* finding below comes out of arithmetic on real rows, so the same library
|
|
7
|
+
* always produces the same list, and a test can pin it.
|
|
8
|
+
*/
|
|
9
|
+
export type MediaIssue = 'missing-alt' | 'unused' | 'oversized';
|
|
10
|
+
export interface MediaAuditAsset {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly kind: 'image' | 'video' | 'audio' | 'file';
|
|
13
|
+
readonly filename: string;
|
|
14
|
+
readonly size: number;
|
|
15
|
+
readonly width: number | null;
|
|
16
|
+
readonly height: number | null;
|
|
17
|
+
readonly alt: string;
|
|
18
|
+
readonly decorative: boolean;
|
|
19
|
+
readonly createdAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface MediaFinding {
|
|
22
|
+
readonly issue: MediaIssue;
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly filename: string;
|
|
25
|
+
/** One sentence, in English like every other agent finding, for the report to translate. */
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MediaAuditOptions {
|
|
29
|
+
/** Ids referenced by at least one entry. Anything outside it is a candidate for `unused`. */
|
|
30
|
+
readonly referenced: ReadonlySet<string>;
|
|
31
|
+
/** How old an unreferenced file must be before it counts as unused. A file uploaded an hour ago is simply not placed yet. */
|
|
32
|
+
readonly unusedAfterDays?: number;
|
|
33
|
+
/** Bytes per displayed pixel above which an image is oversized. 1 byte/px is already generous for a photograph. */
|
|
34
|
+
readonly maxBytesPerPixel?: number;
|
|
35
|
+
readonly now?: Date;
|
|
36
|
+
}
|
|
37
|
+
export declare function auditMediaLibrary(assets: readonly MediaAuditAsset[], options: MediaAuditOptions): readonly MediaFinding[];
|
|
38
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/media/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,QAAQ,GAAG,WAAW,CAAA;AAE/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,6FAA6F;IAC7F,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC,6HAA6H;IAC7H,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,mHAAmH;IACnH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CACpB;AAYD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,OAAO,EAAE,iBAAiB,GACzB,SAAS,YAAY,EAAE,CAyCzB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is wrong with a media library, computed — never guessed (L5 task 10).
|
|
3
|
+
*
|
|
4
|
+
* The rule the priority-1 agents established holds here: the model never
|
|
5
|
+
* decides *that* something is a problem, only how to word the fix. Every
|
|
6
|
+
* finding below comes out of arithmetic on real rows, so the same library
|
|
7
|
+
* always produces the same list, and a test can pin it.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_UNUSED_AFTER_DAYS = 30;
|
|
10
|
+
const DEFAULT_MAX_BYTES_PER_PIXEL = 1;
|
|
11
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
function daysBetween(from, now) {
|
|
13
|
+
const at = Date.parse(from);
|
|
14
|
+
if (Number.isNaN(at))
|
|
15
|
+
return 0;
|
|
16
|
+
return (now.getTime() - at) / DAY_MS;
|
|
17
|
+
}
|
|
18
|
+
export function auditMediaLibrary(assets, options) {
|
|
19
|
+
const now = options.now ?? new Date();
|
|
20
|
+
const unusedAfter = options.unusedAfterDays ?? DEFAULT_UNUSED_AFTER_DAYS;
|
|
21
|
+
const maxBytesPerPixel = options.maxBytesPerPixel ?? DEFAULT_MAX_BYTES_PER_PIXEL;
|
|
22
|
+
const findings = [];
|
|
23
|
+
for (const asset of assets) {
|
|
24
|
+
// A decorative image is *meant* to have an empty alt (WCAG): flagging it
|
|
25
|
+
// would be the false positive that gets an agent muted.
|
|
26
|
+
if (asset.kind === 'image' && !asset.decorative && asset.alt.trim() === '') {
|
|
27
|
+
findings.push({
|
|
28
|
+
issue: 'missing-alt',
|
|
29
|
+
id: asset.id,
|
|
30
|
+
filename: asset.filename,
|
|
31
|
+
detail: 'An image with no alt text: a screen reader announces nothing for it.',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (!options.referenced.has(asset.id) && daysBetween(asset.createdAt, now) >= unusedAfter) {
|
|
35
|
+
findings.push({
|
|
36
|
+
issue: 'unused',
|
|
37
|
+
id: asset.id,
|
|
38
|
+
filename: asset.filename,
|
|
39
|
+
detail: `No published entry references it, ${Math.floor(daysBetween(asset.createdAt, now))} days after it was uploaded.`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (asset.kind === 'image' && asset.width !== null && asset.height !== null) {
|
|
43
|
+
const pixels = asset.width * asset.height;
|
|
44
|
+
if (pixels > 0 && asset.size / pixels > maxBytesPerPixel) {
|
|
45
|
+
findings.push({
|
|
46
|
+
issue: 'oversized',
|
|
47
|
+
id: asset.id,
|
|
48
|
+
filename: asset.filename,
|
|
49
|
+
detail: `${Math.round(asset.size / 1024)} kB for ${asset.width}×${asset.height} pixels — heavier than the picture needs.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/media/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkCH,MAAM,yBAAyB,GAAG,EAAE,CAAA;AACpC,MAAM,2BAA2B,GAAG,CAAC,CAAA;AACrC,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,SAAS,WAAW,CAAC,IAAY,EAAE,GAAS;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAAkC,EAClC,OAA0B;IAE1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAA;IACrC,MAAM,WAAW,GAAG,OAAO,CAAC,eAAe,IAAI,yBAAyB,CAAA;IACxE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,2BAA2B,CAAA;IAChF,MAAM,QAAQ,GAAmB,EAAE,CAAA;IAEnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,yEAAyE;QACzE,wDAAwD;QACxD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,aAAa;gBACpB,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,MAAM,EAAE,sEAAsE;aAC/E,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,WAAW,EAAE,CAAC;YAC1F,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,QAAQ;gBACf,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,MAAM,EAAE,qCAAqC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,8BAA8B;aACzH,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;YACzC,IAAI,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,MAAM,GAAG,gBAAgB,EAAE,CAAC;gBACzD,QAAQ,CAAC,IAAI,CAAC;oBACZ,KAAK,EAAE,WAAW;oBAClB,EAAE,EAAE,KAAK,CAAC,EAAE;oBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,2CAA2C;iBAC1H,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AgentDeclaration } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 3. Works only on entries an import wrote
|
|
4
|
+
* (`provenance: 'imported'`) — `findMigrationResidue` filters on that — and
|
|
5
|
+
* never touches a slug: changing an imported URL breaks exactly the links the
|
|
6
|
+
* migration set out to keep. A redirect it creates is reversible and audited,
|
|
7
|
+
* like the Site Monitor's (L22 task 3).
|
|
8
|
+
*/
|
|
9
|
+
export declare const migrationAgent: AgentDeclaration;
|
|
10
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/migration/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iBAAiB,CAAA;AAEpE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,gBAgB3B,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineAgent } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 3. Works only on entries an import wrote
|
|
4
|
+
* (`provenance: 'imported'`) — `findMigrationResidue` filters on that — and
|
|
5
|
+
* never touches a slug: changing an imported URL breaks exactly the links the
|
|
6
|
+
* migration set out to keep. A redirect it creates is reversible and audited,
|
|
7
|
+
* like the Site Monitor's (L22 task 3).
|
|
8
|
+
*/
|
|
9
|
+
export const migrationAgent = defineAgent({
|
|
10
|
+
name: 'migration',
|
|
11
|
+
identity: './identity.md',
|
|
12
|
+
model: { preferred: 'claude-sonnet', fallback: 'local' },
|
|
13
|
+
tools: [
|
|
14
|
+
'content.collections',
|
|
15
|
+
'content.schema',
|
|
16
|
+
'content.list',
|
|
17
|
+
'content.read',
|
|
18
|
+
'content.write_draft',
|
|
19
|
+
'redirects.create',
|
|
20
|
+
],
|
|
21
|
+
autonomy: { default: 'propose' },
|
|
22
|
+
budget: { tokensPerDay: 60_000, eurPerMonth: 4, callsPerHour: 15 },
|
|
23
|
+
memory: { episodic: true, scope: 'site' },
|
|
24
|
+
triggers: [{ on: 'schedule', cron: '0 5 1 * *' }],
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/migration/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAEpE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAqB,WAAW,CAAC;IAC1D,IAAI,EAAE,WAAW;IACjB,QAAQ,EAAE,eAAe;IACzB,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE;IACxD,KAAK,EAAE;QACL,qBAAqB;QACrB,gBAAgB;QAChB,cAAc;QACd,cAAc;QACd,qBAAqB;QACrB,kBAAkB;KACnB;IACD,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IAChC,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;IAClE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACzC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;CAClD,CAAC,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an import left behind (L5 task 10, Migration).
|
|
3
|
+
*
|
|
4
|
+
* `@cogenta/import` (L9) produces a coherent site, not a finished one: links
|
|
5
|
+
* still point at the old domain, images are still referenced by URL rather
|
|
6
|
+
* than by media id, excerpts are missing. All four findings below are read
|
|
7
|
+
* off the stored values — nothing here asks a model whether a link is
|
|
8
|
+
* internal.
|
|
9
|
+
*/
|
|
10
|
+
export type MigrationIssue = 'absolute-internal-link' | 'orphan-media-url' | 'missing-excerpt' | 'empty-common-field';
|
|
11
|
+
export interface MigrationEntry {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly collection: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
readonly path: string | null;
|
|
16
|
+
readonly provenance: string;
|
|
17
|
+
/** Flattened text of the entry — body, blocks, rich text — as the caller extracted it. */
|
|
18
|
+
readonly text: string;
|
|
19
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
export interface MigrationFinding {
|
|
22
|
+
readonly issue: MigrationIssue;
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly collection: string;
|
|
25
|
+
readonly title: string;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
/** For a link finding: the exact URL found, so a redirect can be proposed without re-scanning. */
|
|
28
|
+
readonly url?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface MigrationOptions {
|
|
31
|
+
/** Domains the site used to live on. A URL on one of them is an internal link written the old way. */
|
|
32
|
+
readonly previousDomains: readonly string[];
|
|
33
|
+
/** Fields most entries of a collection fill; one left empty on an imported entry is a gap worth naming. */
|
|
34
|
+
readonly excerptFields?: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export declare function findMigrationResidue(entries: readonly MigrationEntry[], options: MigrationOptions): readonly MigrationFinding[];
|
|
37
|
+
/**
|
|
38
|
+
* The redirects an import's own links imply: one per distinct old URL whose
|
|
39
|
+
* path differs from where the entry now lives.
|
|
40
|
+
*
|
|
41
|
+
* Mechanical on purpose — a model has nothing to add to "this address moved
|
|
42
|
+
* there", and `redirects.create` is reversible and audited (L22 task 3).
|
|
43
|
+
*/
|
|
44
|
+
export declare function redirectsFor(findings: readonly MigrationFinding[], entries: readonly MigrationEntry[]): readonly {
|
|
45
|
+
readonly from: string;
|
|
46
|
+
readonly to: string;
|
|
47
|
+
}[];
|
|
48
|
+
//# sourceMappingURL=residue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"residue.d.ts","sourceRoot":"","sources":["../../src/migration/residue.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GACtB,wBAAwB,GACxB,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,CAAA;AAExB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,kGAAkG;IAClG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,sGAAsG;IACtG,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,2GAA2G;IAC3G,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3C;AAaD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,OAAO,EAAE,gBAAgB,GACxB,SAAS,gBAAgB,EAAE,CA2C7B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,SAAS,gBAAgB,EAAE,EACrC,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,CAqB3D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an import left behind (L5 task 10, Migration).
|
|
3
|
+
*
|
|
4
|
+
* `@cogenta/import` (L9) produces a coherent site, not a finished one: links
|
|
5
|
+
* still point at the old domain, images are still referenced by URL rather
|
|
6
|
+
* than by media id, excerpts are missing. All four findings below are read
|
|
7
|
+
* off the stored values — nothing here asks a model whether a link is
|
|
8
|
+
* internal.
|
|
9
|
+
*/
|
|
10
|
+
const URL_PATTERN = /https?:\/\/[^\s"'<>)]+/gu;
|
|
11
|
+
const IMAGE_SUFFIX = /\.(?:jpe?g|png|gif|webp|avif|svg)$/iu;
|
|
12
|
+
function hostOf(url) {
|
|
13
|
+
try {
|
|
14
|
+
return new URL(url).host.toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function findMigrationResidue(entries, options) {
|
|
21
|
+
const previous = new Set(options.previousDomains.map((domain) => domain.toLowerCase()));
|
|
22
|
+
const excerptFields = options.excerptFields ?? ['excerpt', 'summary', 'description'];
|
|
23
|
+
const findings = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
// Only what an import wrote. An entry someone typed here is not residue,
|
|
26
|
+
// whatever it contains.
|
|
27
|
+
if (entry.provenance !== 'imported')
|
|
28
|
+
continue;
|
|
29
|
+
for (const url of entry.text.match(URL_PATTERN) ?? []) {
|
|
30
|
+
const host = hostOf(url);
|
|
31
|
+
if (host === null || !previous.has(host))
|
|
32
|
+
continue;
|
|
33
|
+
findings.push({
|
|
34
|
+
issue: IMAGE_SUFFIX.test(new URL(url).pathname)
|
|
35
|
+
? 'orphan-media-url'
|
|
36
|
+
: 'absolute-internal-link',
|
|
37
|
+
id: entry.id,
|
|
38
|
+
collection: entry.collection,
|
|
39
|
+
title: entry.title,
|
|
40
|
+
url,
|
|
41
|
+
detail: IMAGE_SUFFIX.test(new URL(url).pathname)
|
|
42
|
+
? 'An image is still loaded from the old site rather than from this media library.'
|
|
43
|
+
: 'A link still points at the old domain rather than at this site.',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const excerpt = excerptFields
|
|
47
|
+
.map((field) => entry.values[field])
|
|
48
|
+
.find((value) => typeof value === 'string' && value.trim() !== '');
|
|
49
|
+
if (excerpt === undefined && excerptFields.some((field) => field in entry.values)) {
|
|
50
|
+
findings.push({
|
|
51
|
+
issue: 'missing-excerpt',
|
|
52
|
+
id: entry.id,
|
|
53
|
+
collection: entry.collection,
|
|
54
|
+
title: entry.title,
|
|
55
|
+
detail: 'No excerpt: listings and search results will show the first words of the body instead.',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The redirects an import's own links imply: one per distinct old URL whose
|
|
63
|
+
* path differs from where the entry now lives.
|
|
64
|
+
*
|
|
65
|
+
* Mechanical on purpose — a model has nothing to add to "this address moved
|
|
66
|
+
* there", and `redirects.create` is reversible and audited (L22 task 3).
|
|
67
|
+
*/
|
|
68
|
+
export function redirectsFor(findings, entries) {
|
|
69
|
+
const pathOf = new Map(entries.map((entry) => [entry.id, entry.path]));
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
const redirects = [];
|
|
72
|
+
for (const finding of findings) {
|
|
73
|
+
if (finding.issue !== 'absolute-internal-link' || finding.url === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
const to = pathOf.get(finding.id);
|
|
76
|
+
if (to === null || to === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
let from;
|
|
79
|
+
try {
|
|
80
|
+
from = new URL(finding.url).pathname;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (from === to || seen.has(from))
|
|
86
|
+
continue;
|
|
87
|
+
seen.add(from);
|
|
88
|
+
redirects.push({ from, to });
|
|
89
|
+
}
|
|
90
|
+
return redirects;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=residue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"residue.js","sourceRoot":"","sources":["../../src/migration/residue.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH,MAAM,WAAW,GAAG,0BAA0B,CAAA;AAC9C,MAAM,YAAY,GAAG,sCAAsC,CAAA;AAE3D,SAAS,MAAM,CAAC,GAAW;IACzB,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,OAAkC,EAClC,OAAyB;IAEzB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IACvF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAC,CAAA;IACpF,MAAM,QAAQ,GAAuB,EAAE,CAAA;IAEvC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,yEAAyE;QACzE,wBAAwB;QACxB,IAAI,KAAK,CAAC,UAAU,KAAK,UAAU;YAAE,SAAQ;QAE7C,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;YACxB,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAQ;YAClD,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;oBAC7C,CAAC,CAAC,kBAAkB;oBACpB,CAAC,CAAC,wBAAwB;gBAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,GAAG;gBACH,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;oBAC9C,CAAC,CAAC,iFAAiF;oBACnF,CAAC,CAAC,iEAAiE;aACtE,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,aAAa;aAC1B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACnC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QACpE,IAAI,OAAO,KAAK,SAAS,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAClF,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,iBAAiB;gBACxB,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EACJ,wFAAwF;aAC3F,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,QAAqC,EACrC,OAAkC;IAElC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,SAAS,GAAmC,EAAE,CAAA;IAEpD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,KAAK,KAAK,wBAAwB,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS;YAAE,SAAQ;QACrF,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACjC,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,SAAS;YAAE,SAAQ;QAC7C,IAAI,IAAY,CAAA;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC3C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACd,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AgentDeclaration } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. `triageComments` settles what the site's own
|
|
4
|
+
* anti-spam pass already settled; the agent explains the rest and leaves it
|
|
5
|
+
* pending. `comments.decide` cannot delete anything — refusing a comment is
|
|
6
|
+
* a status a human can reverse.
|
|
7
|
+
*/
|
|
8
|
+
export declare const moderationAgent: AgentDeclaration;
|
|
9
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/moderation/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,gBAS5B,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineAgent } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. `triageComments` settles what the site's own
|
|
4
|
+
* anti-spam pass already settled; the agent explains the rest and leaves it
|
|
5
|
+
* pending. `comments.decide` cannot delete anything — refusing a comment is
|
|
6
|
+
* a status a human can reverse.
|
|
7
|
+
*/
|
|
8
|
+
export const moderationAgent = defineAgent({
|
|
9
|
+
name: 'moderation',
|
|
10
|
+
identity: './identity.md',
|
|
11
|
+
model: { preferred: 'claude-sonnet', fallback: 'local' },
|
|
12
|
+
tools: ['comments.list', 'comments.decide', 'content.read'],
|
|
13
|
+
autonomy: { default: 'observe' },
|
|
14
|
+
budget: { tokensPerDay: 60_000, eurPerMonth: 4, callsPerHour: 15 },
|
|
15
|
+
memory: { episodic: true, scope: 'site' },
|
|
16
|
+
triggers: [{ on: 'schedule', cron: '0 * * * *' }],
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/moderation/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAqB,WAAW,CAAC;IAC3D,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,eAAe;IACzB,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE;IACxD,KAAK,EAAE,CAAC,eAAe,EAAE,iBAAiB,EAAE,cAAc,CAAC;IAC3D,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IAChC,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;IAClE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACzC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;CAClD,CAAC,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CommentSummary, ModeratableStatus } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* Which pending comments are already settled by the site's own checks, and
|
|
4
|
+
* which are a judgement call (L5 task 10, Modération).
|
|
5
|
+
*
|
|
6
|
+
* The anti-spam pass of `@cogenta/comments` runs at submission and stores its
|
|
7
|
+
* verdict. This turns that verdict into decisions an agent may carry out —
|
|
8
|
+
* and, just as importantly, into the list it must leave alone. A model is
|
|
9
|
+
* never asked whether something is spam; it is asked to explain the ones the
|
|
10
|
+
* code could not settle.
|
|
11
|
+
*/
|
|
12
|
+
export interface ModerationDecision {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly status: ModeratableStatus;
|
|
15
|
+
readonly reason: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ModerationTriage {
|
|
18
|
+
/** Safe to act on: the deterministic verdict is unambiguous. */
|
|
19
|
+
readonly decided: readonly ModerationDecision[];
|
|
20
|
+
/** Left pending on purpose — a human (or a model's opinion in the report) decides. */
|
|
21
|
+
readonly undecided: readonly CommentSummary[];
|
|
22
|
+
}
|
|
23
|
+
export declare function triageComments(comments: readonly CommentSummary[]): ModerationTriage;
|
|
24
|
+
//# sourceMappingURL=triage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage.d.ts","sourceRoot":"","sources":["../../src/moderation/triage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExE;;;;;;;;;GASG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC/C,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAA;CAC9C;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,gBAAgB,CA0BpF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function triageComments(comments) {
|
|
2
|
+
const decided = [];
|
|
3
|
+
const undecided = [];
|
|
4
|
+
for (const comment of comments) {
|
|
5
|
+
if (comment.flagged && comment.severity === 'high') {
|
|
6
|
+
decided.push({
|
|
7
|
+
id: comment.id,
|
|
8
|
+
status: 'spam',
|
|
9
|
+
reason: `The site's own checks flagged it: ${comment.reason}`,
|
|
10
|
+
});
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (!comment.flagged) {
|
|
14
|
+
decided.push({
|
|
15
|
+
id: comment.id,
|
|
16
|
+
status: 'approved',
|
|
17
|
+
reason: "The site's own checks found nothing wrong with it.",
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
// Flagged, but not squarely: exactly the case a moderator exists for.
|
|
22
|
+
undecided.push(comment);
|
|
23
|
+
}
|
|
24
|
+
return { decided, undecided };
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=triage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage.js","sourceRoot":"","sources":["../../src/moderation/triage.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,cAAc,CAAC,QAAmC;IAChE,MAAM,OAAO,GAAyB,EAAE,CAAA;IACxC,MAAM,SAAS,GAAqB,EAAE,CAAA;IAEtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACnD,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,qCAAqC,OAAO,CAAC,MAAM,EAAE;aAC9D,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,oDAAoD;aAC7D,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QACD,sEAAsE;QACtE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACzB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AAC/B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AgentDeclaration } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. `findTranslationGaps` decides what is missing or
|
|
4
|
+
* behind; the model only translates what it was handed, into a draft. No
|
|
5
|
+
* `content.publish`: which language a site speaks in public is an editorial
|
|
6
|
+
* decision, and ADR-0014 makes each translation a real entry someone owns.
|
|
7
|
+
*/
|
|
8
|
+
export declare const translationAgent: AgentDeclaration;
|
|
9
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/translation/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,gBAe7B,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineAgent } from '@cogenta/agents';
|
|
2
|
+
/**
|
|
3
|
+
* L5 task 10, priority 2. `findTranslationGaps` decides what is missing or
|
|
4
|
+
* behind; the model only translates what it was handed, into a draft. No
|
|
5
|
+
* `content.publish`: which language a site speaks in public is an editorial
|
|
6
|
+
* decision, and ADR-0014 makes each translation a real entry someone owns.
|
|
7
|
+
*/
|
|
8
|
+
export const translationAgent = defineAgent({
|
|
9
|
+
name: 'translation',
|
|
10
|
+
identity: './identity.md',
|
|
11
|
+
model: { preferred: 'claude-sonnet', fallback: 'local' },
|
|
12
|
+
tools: [
|
|
13
|
+
'content.collections',
|
|
14
|
+
'content.schema',
|
|
15
|
+
'content.list',
|
|
16
|
+
'content.read',
|
|
17
|
+
'content.write_draft',
|
|
18
|
+
],
|
|
19
|
+
autonomy: { default: 'propose' },
|
|
20
|
+
budget: { tokensPerDay: 60_000, eurPerMonth: 4, callsPerHour: 15 },
|
|
21
|
+
memory: { episodic: true, scope: 'site' },
|
|
22
|
+
triggers: [{ on: 'schedule', cron: '0 5 * * 2' }],
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/translation/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAqB,WAAW,CAAC;IAC5D,IAAI,EAAE,aAAa;IACnB,QAAQ,EAAE,eAAe;IACzB,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE;IACxD,KAAK,EAAE;QACL,qBAAqB;QACrB,gBAAgB;QAChB,cAAc;QACd,cAAc;QACd,qBAAqB;KACtB;IACD,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IAChC,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE;IAClE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACzC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;CAClD,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which translations are missing, and which have fallen behind (L5 task 10).
|
|
3
|
+
*
|
|
4
|
+
* ADR-0014 makes a translation a real entry of its own, linked by
|
|
5
|
+
* `translationOf`. That is what makes both questions answerable by arithmetic:
|
|
6
|
+
* a family is a set of entries with a locale each, and a member is stale when
|
|
7
|
+
* it was last written before its source was.
|
|
8
|
+
*/
|
|
9
|
+
export type TranslationIssue = 'missing' | 'stale';
|
|
10
|
+
export interface TranslationMember {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly locale: string;
|
|
13
|
+
readonly updatedAt: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TranslationFamily {
|
|
17
|
+
readonly collection: string;
|
|
18
|
+
/** The entry the others are translations of. */
|
|
19
|
+
readonly source: TranslationMember;
|
|
20
|
+
readonly members: readonly TranslationMember[];
|
|
21
|
+
}
|
|
22
|
+
export interface TranslationGap {
|
|
23
|
+
readonly issue: TranslationIssue;
|
|
24
|
+
readonly collection: string;
|
|
25
|
+
readonly sourceId: string;
|
|
26
|
+
readonly title: string;
|
|
27
|
+
readonly locale: string;
|
|
28
|
+
readonly detail: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function findTranslationGaps(families: readonly TranslationFamily[], locales: readonly string[]): readonly TranslationGap[];
|
|
31
|
+
//# sourceMappingURL=gaps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gaps.d.ts","sourceRoot":"","sources":["../../src/translation/gaps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,SAAS,cAAc,EAAE,CAoC3B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which translations are missing, and which have fallen behind (L5 task 10).
|
|
3
|
+
*
|
|
4
|
+
* ADR-0014 makes a translation a real entry of its own, linked by
|
|
5
|
+
* `translationOf`. That is what makes both questions answerable by arithmetic:
|
|
6
|
+
* a family is a set of entries with a locale each, and a member is stale when
|
|
7
|
+
* it was last written before its source was.
|
|
8
|
+
*/
|
|
9
|
+
export function findTranslationGaps(families, locales) {
|
|
10
|
+
const gaps = [];
|
|
11
|
+
for (const family of families) {
|
|
12
|
+
const byLocale = new Map(family.members.map((member) => [member.locale, member]));
|
|
13
|
+
byLocale.set(family.source.locale, family.source);
|
|
14
|
+
for (const locale of locales) {
|
|
15
|
+
if (locale === family.source.locale)
|
|
16
|
+
continue;
|
|
17
|
+
const member = byLocale.get(locale);
|
|
18
|
+
if (member === undefined) {
|
|
19
|
+
gaps.push({
|
|
20
|
+
issue: 'missing',
|
|
21
|
+
collection: family.collection,
|
|
22
|
+
sourceId: family.source.id,
|
|
23
|
+
title: family.source.title,
|
|
24
|
+
locale,
|
|
25
|
+
detail: `No ${locale} version of this entry exists.`,
|
|
26
|
+
});
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Strictly before: same instant means translated in the same edit.
|
|
30
|
+
if (Date.parse(member.updatedAt) < Date.parse(family.source.updatedAt)) {
|
|
31
|
+
gaps.push({
|
|
32
|
+
issue: 'stale',
|
|
33
|
+
collection: family.collection,
|
|
34
|
+
sourceId: family.source.id,
|
|
35
|
+
title: family.source.title,
|
|
36
|
+
locale,
|
|
37
|
+
detail: `The ${locale} version was last written before the source was changed.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return gaps;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=gaps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gaps.js","sourceRoot":"","sources":["../../src/translation/gaps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA2BH,MAAM,UAAU,mBAAmB,CACjC,QAAsC,EACtC,OAA0B;IAE1B,MAAM,IAAI,GAAqB,EAAE,CAAA;IAEjC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;QACjF,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAEjD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM;gBAAE,SAAQ;YAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC;oBACR,KAAK,EAAE,SAAS;oBAChB,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE;oBAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;oBAC1B,MAAM;oBACN,MAAM,EAAE,MAAM,MAAM,gCAAgC;iBACrD,CAAC,CAAA;gBACF,SAAQ;YACV,CAAC;YACD,mEAAmE;YACnE,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACvE,IAAI,CAAC,IAAI,CAAC;oBACR,KAAK,EAAE,OAAO;oBACd,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE;oBAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;oBAC1B,MAAM;oBACN,MAAM,EAAE,OAAO,MAAM,0DAA0D;iBAChF,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|