@prodero/curator 0.0.0-stage → 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/ARCHITECTURE.md +298 -0
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/SECURITY.md +7 -0
- package/bin/prodero +7 -0
- package/dist/cli.js +228 -0
- package/dist/context-instructions.js +2 -0
- package/dist/contracts.js +50 -0
- package/dist/conversation-presentation.js +262 -0
- package/dist/core/agent-instructions.js +39 -0
- package/dist/core/applicability-label.js +11 -0
- package/dist/core/applicability.js +143 -0
- package/dist/core/bootstrap.js +129 -0
- package/dist/core/consolidation.js +315 -0
- package/dist/core/constants.js +21 -0
- package/dist/core/context-allocation.js +54 -0
- package/dist/core/context-snapshot.js +12 -0
- package/dist/core/context.js +489 -0
- package/dist/core/curation-clauses.js +68 -0
- package/dist/core/curation-evidence.js +98 -0
- package/dist/core/curation-graphical.js +105 -0
- package/dist/core/curation-review-presentation.js +4 -0
- package/dist/core/curation-review-state.js +62 -0
- package/dist/core/curation-review.js +573 -0
- package/dist/core/curation-revision.js +106 -0
- package/dist/core/curation-semantic-contract.js +239 -0
- package/dist/core/curation-semantic-jobs.js +280 -0
- package/dist/core/diagnostics.js +17 -0
- package/dist/core/format.js +114 -0
- package/dist/core/ids.js +6 -0
- package/dist/core/imports.js +169 -0
- package/dist/core/index.js +93 -0
- package/dist/core/instruction-review.js +86 -0
- package/dist/core/integrity-files.js +174 -0
- package/dist/core/intelligence.js +95 -0
- package/dist/core/knowledge-analysis.js +46 -0
- package/dist/core/knowledge-authority.js +143 -0
- package/dist/core/knowledge-curation.js +316 -0
- package/dist/core/knowledge-engine.js +475 -0
- package/dist/core/knowledge-graph.js +191 -0
- package/dist/core/knowledge-lifecycle.js +212 -0
- package/dist/core/knowledge-management.js +113 -0
- package/dist/core/knowledge-migration.js +84 -0
- package/dist/core/knowledge-policy.js +38 -0
- package/dist/core/knowledge-provenance.js +90 -0
- package/dist/core/knowledge-write-policy.js +85 -0
- package/dist/core/language.js +14 -0
- package/dist/core/matching.js +6 -0
- package/dist/core/memory-index.js +239 -0
- package/dist/core/memory-intelligence.js +288 -0
- package/dist/core/memory-link-repair.js +66 -0
- package/dist/core/memory-operations.js +122 -0
- package/dist/core/memory-pressure.js +212 -0
- package/dist/core/memory-sync-curator.js +62 -0
- package/dist/core/memory-sync.js +463 -0
- package/dist/core/memory-transformation-bundle.js +163 -0
- package/dist/core/memory-transformation-proposal.js +135 -0
- package/dist/core/memory-transformation.js +108 -0
- package/dist/core/memory.js +186 -0
- package/dist/core/migrations.js +569 -0
- package/dist/core/path-scope.js +27 -0
- package/dist/core/plugin-files.js +116 -0
- package/dist/core/project-root.js +39 -0
- package/dist/core/project.js +634 -0
- package/dist/core/promotion.js +279 -0
- package/dist/core/reconciliation.js +61 -0
- package/dist/core/secrets.js +67 -0
- package/dist/core/semantic-safety.js +33 -0
- package/dist/core/session-index.js +112 -0
- package/dist/core/session-review.js +150 -0
- package/dist/core/sessions.js +289 -0
- package/dist/core/settings.js +76 -0
- package/dist/core/skill-curation.js +239 -0
- package/dist/core/skill-polish.js +41 -0
- package/dist/core/skills.js +368 -0
- package/dist/core/storage.js +408 -0
- package/dist/core/sync-transaction.js +254 -0
- package/dist/core/telemetry.js +64 -0
- package/dist/core/templates.js +53 -0
- package/dist/core/types.js +2 -0
- package/dist/core/update.js +320 -0
- package/dist/curation-review-adapter.js +92 -0
- package/dist/diagnostic-boundary.js +54 -0
- package/dist/filesystem/permissions.js +39 -0
- package/dist/hosts/adapters.js +119 -0
- package/dist/hosts/files.js +133 -0
- package/dist/hosts/manager.js +150 -0
- package/dist/hosts/public-entry.js +47 -0
- package/dist/hosts/runtime-command.js +66 -0
- package/dist/hosts/runtime.js +139 -0
- package/dist/hosts/wizard.js +114 -0
- package/dist/knowledge-table.js +38 -0
- package/dist/mcp-development.js +69 -0
- package/dist/mcp-workspace.js +141 -0
- package/dist/memory-presentation.js +172 -0
- package/dist/plugins/api.js +2 -0
- package/dist/plugins/context.js +20 -0
- package/dist/plugins/mcp.js +72 -0
- package/dist/plugins/registry.js +106 -0
- package/dist/presentation-cards.js +32 -0
- package/dist/presentation-server.js +79 -0
- package/dist/presentation-trace.js +174 -0
- package/dist/presentation.js +412 -0
- package/dist/providers/mcp-sampling.js +45 -0
- package/dist/server.js +656 -0
- package/dist/settings-definition.js +22 -0
- package/dist/skill-instructions.js +2 -0
- package/dist-ui/index.html +214 -0
- package/docs/APPLICABILITY.md +151 -0
- package/docs/COMPATIBILITY.md +49 -0
- package/docs/CONTEXT-SELECTION-V2.md +61 -0
- package/docs/CURATION-APP-REVIEW.md +156 -0
- package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
- package/docs/IMPORTER-CURATION.md +142 -0
- package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
- package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
- package/docs/MIGRATIONS.md +99 -0
- package/docs/PRIVACY.md +28 -0
- package/docs/RECOVERY.md +56 -0
- package/docs/RELEASING.md +40 -0
- package/docs/hosts.md +134 -0
- package/docs/plugins.md +83 -0
- package/docs/ui-themes.md +43 -0
- package/instructions/AGENTS.md +33 -0
- package/package.json +88 -4
- package/plugins/migrate_ai_studio/README.md +101 -0
- package/plugins/migrate_ai_studio/index.cjs +396 -0
- package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
- package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
- package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
- package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
- package/plugins/migrate_ai_studio/plugin.json +11 -0
- package/plugins/migrate_ai_studio/schema.cjs +20 -0
- package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
- package/prodero.release.json +5 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { readConfig } from './storage.js';
|
|
2
|
+
import { assertCompatibleProject } from "./storage.js";
|
|
3
|
+
import { assertNoSecrets } from "./secrets.js";
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { skillInputSchema, deterministicSkillDraft, candidateRevision, getProjectSkill } from './skills.js';
|
|
8
|
+
import { getContext } from './context.js';
|
|
9
|
+
import { readSession } from './sessions.js';
|
|
10
|
+
import { sessionCandidates } from './consolidation.js';
|
|
11
|
+
import { safeProjectPath } from './sync-transaction.js';
|
|
12
|
+
import { parseKnowledge } from './memory.js';
|
|
13
|
+
import { estimateTokens } from './telemetry.js';
|
|
14
|
+
import { tokens } from './matching.js';
|
|
15
|
+
export const CURATION_LIMITS = Object.freeze({ inputTokens: 6500, outputTokens: 4000, outputBytes: 32000, skillTokens: 2500, observations: 4, sessionBytes: 64000, knowledge: 6, skills: 3, timeoutMs: 45000 });
|
|
16
|
+
const identifier = z.string().regex(/^[a-zA-Z0-9_-]+$/).max(200);
|
|
17
|
+
export const skillDispositionSchema = z.object({ action: z.enum(['create', 'update', 'covered', 'keep-separate']), targetSkillId: identifier.nullable() }).strict();
|
|
18
|
+
export const skillCurationOutputSchema = z.object({
|
|
19
|
+
draft: skillInputSchema.omit({ content: true }).extend({ procedure: z.array(z.string().trim().min(1).max(4000)).min(1).max(24) }).strict(),
|
|
20
|
+
disposition: skillDispositionSchema,
|
|
21
|
+
reasoningSummary: z.string().trim().min(1).max(800),
|
|
22
|
+
grounding: z.array(z.object({ field: z.string().regex(/^(title|description|scope|triggers\.\d+|procedure\.\d+)$/), sources: z.array(z.string().min(1).max(300)).min(1).max(8) }).strict()).min(3).max(60),
|
|
23
|
+
}).strict();
|
|
24
|
+
/** References are project-relative labels, not permission to read arbitrary files. */
|
|
25
|
+
export async function validateProjectReference(root, reference) {
|
|
26
|
+
if (!reference || reference.includes('\\') || reference.includes('\0') || /[\r\n]/.test(reference) || path.isAbsolute(reference) || /^[a-z]+:/i.test(reference) || reference.split('/').includes('..'))
|
|
27
|
+
throw new Error('Reference escapes project root');
|
|
28
|
+
const realRoot = await fs.realpath(root);
|
|
29
|
+
let cursor = realRoot;
|
|
30
|
+
for (const part of reference.split('/')) {
|
|
31
|
+
cursor = path.join(cursor, part);
|
|
32
|
+
try {
|
|
33
|
+
const real = await fs.realpath(cursor);
|
|
34
|
+
if (real !== realRoot && !real.startsWith(realRoot + path.sep))
|
|
35
|
+
throw new Error('Reference escapes project root');
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code !== 'ENOENT')
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function validateSkillDraft(root, input, knownReferences) {
|
|
44
|
+
await assertCompatibleProject(root);
|
|
45
|
+
assertNoSecrets(input);
|
|
46
|
+
const draft = skillInputSchema.strict().parse(input);
|
|
47
|
+
if (/^(?:[\\/]|[a-z]:[\\/])/i.test(draft.scope) || /(?:^|[\\/])\.\.(?:[\\/]|$)/.test(draft.scope) || /[\0\r\n]/.test(draft.scope))
|
|
48
|
+
throw new Error('Invalid applicability scope');
|
|
49
|
+
if (estimateTokens(JSON.stringify(draft)) > CURATION_LIMITS.skillTokens)
|
|
50
|
+
throw new Error('Project Skill size limit exceeded');
|
|
51
|
+
for (const ref of draft.references) {
|
|
52
|
+
await validateProjectReference(root, ref);
|
|
53
|
+
if (knownReferences && !knownReferences.includes(ref))
|
|
54
|
+
throw new Error(`Unknown reference: ${ref}`);
|
|
55
|
+
}
|
|
56
|
+
for (const id of draft.relatedKnowledge) {
|
|
57
|
+
const target = await safeProjectPath(root, `memory/${id}.md`);
|
|
58
|
+
const knowledge = parseKnowledge(await fs.readFile(target, 'utf8'), `${id}.md`, (await readConfig(root))?.knowledgeSchemaVersion);
|
|
59
|
+
if (knowledge.id !== id || knowledge.status !== 'active')
|
|
60
|
+
throw new Error(`Invalid related knowledge: ${id}`);
|
|
61
|
+
}
|
|
62
|
+
return draft;
|
|
63
|
+
}
|
|
64
|
+
export async function sourceObservations(root, skill) {
|
|
65
|
+
await assertCompatibleProject(root);
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const source of skill.derivedFrom.slice(0, CURATION_LIMITS.observations)) {
|
|
68
|
+
const sessionId = source.split('#')[0];
|
|
69
|
+
if (!/^sess_[a-zA-Z0-9_-]+$/.test(sessionId))
|
|
70
|
+
continue;
|
|
71
|
+
const target = await safeProjectPath(root, `sessions/${sessionId}.md`);
|
|
72
|
+
try {
|
|
73
|
+
if ((await fs.stat(target)).size > CURATION_LIMITS.sessionBytes)
|
|
74
|
+
continue;
|
|
75
|
+
const observation = sessionCandidates(await readSession(root, sessionId)).find(o => o.derivedFrom === source);
|
|
76
|
+
if (observation)
|
|
77
|
+
result.push({ id: source, title: observation.title, content: observation.content });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code !== 'ENOENT')
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/** Recover original evidence for an explicitly requested re-curation; do not reset the active record. */
|
|
87
|
+
export async function originalSkillCandidate(root, skill) {
|
|
88
|
+
await assertCompatibleProject(root);
|
|
89
|
+
for (const source of skill.derivedFrom.slice(0, CURATION_LIMITS.observations)) {
|
|
90
|
+
const id = source.split('#')[0];
|
|
91
|
+
if (!/^sess_[a-zA-Z0-9_-]+$/.test(id))
|
|
92
|
+
continue;
|
|
93
|
+
const target = await safeProjectPath(root, `sessions/${id}.md`);
|
|
94
|
+
try {
|
|
95
|
+
if ((await fs.stat(target)).size > CURATION_LIMITS.sessionBytes)
|
|
96
|
+
continue;
|
|
97
|
+
const observation = sessionCandidates(await readSession(root, id)).find(o => o.skill && o.derivedFrom === source);
|
|
98
|
+
if (observation)
|
|
99
|
+
return { ...skill, title: observation.title, description: observation.description ?? observation.title, scope: observation.scopeRef ?? 'project', triggers: observation.triggers ?? [], content: observation.content, references: [], relatedKnowledge: [] };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code !== 'ENOENT')
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return skill;
|
|
107
|
+
}
|
|
108
|
+
export async function buildSkillCurationInput(root, skill) {
|
|
109
|
+
await assertCompatibleProject(root);
|
|
110
|
+
const candidate = skill.status === 'active' ? await originalSkillCandidate(root, skill) : skill;
|
|
111
|
+
const known = deterministicSkillDraft(candidate).references;
|
|
112
|
+
for (const match of `${candidate.description} ${candidate.proposedReason ?? ''}`.match(/[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)+/g) ?? []) {
|
|
113
|
+
const ref = match.replace(/[.,;]+$/, '');
|
|
114
|
+
// Slash-separated prose such as "missing/invalid-cache" is not a file reference.
|
|
115
|
+
if (/\.[a-z0-9]+$/i.test(ref) && !known.includes(ref))
|
|
116
|
+
known.push(ref);
|
|
117
|
+
}
|
|
118
|
+
// A file-shaped observation scope is evidence, not automatically applicability.
|
|
119
|
+
if (/\.[a-z0-9]+$/i.test(candidate.scope) && !known.includes(candidate.scope))
|
|
120
|
+
known.push(candidate.scope);
|
|
121
|
+
const knownReferences = [];
|
|
122
|
+
for (const ref of known.slice(0, 30)) {
|
|
123
|
+
await validateProjectReference(root, ref);
|
|
124
|
+
knownReferences.push(ref);
|
|
125
|
+
}
|
|
126
|
+
const input = {
|
|
127
|
+
candidate, observations: [], provenance: { sourceSessions: candidate.sourceSessions.slice(0, 4), derivedFrom: candidate.derivedFrom.slice(0, 4) }, knownReferences,
|
|
128
|
+
relatedKnowledge: [], activeSkills: [], protectedConstraints: safetyConstraints(candidate.content), truncated: false,
|
|
129
|
+
};
|
|
130
|
+
const fits = () => estimateTokens(JSON.stringify(input)) <= CURATION_LIMITS.inputTokens;
|
|
131
|
+
if (!fits())
|
|
132
|
+
throw new Error('Candidate exceeds curation input limit');
|
|
133
|
+
for (const observation of await sourceObservations(root, candidate)) {
|
|
134
|
+
input.observations.push(observation);
|
|
135
|
+
if (!fits()) {
|
|
136
|
+
input.observations.pop();
|
|
137
|
+
input.truncated = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// The existing context resolver owns ranking, metadata discovery and bounded body loading.
|
|
141
|
+
const context = await getContext(root, `${candidate.title} ${candidate.description} ${candidate.triggers.join(' ')}`, { contextBudgetTokens: 2800, requiredContextBudgetTokens: 2000 });
|
|
142
|
+
for (const item of [...context.required, ...context.relevant].slice(0, CURATION_LIMITS.knowledge)) {
|
|
143
|
+
input.relatedKnowledge.push({ id: item.id, title: item.title, content: item.content });
|
|
144
|
+
if (!fits()) {
|
|
145
|
+
input.relatedKnowledge.pop();
|
|
146
|
+
input.truncated = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const active = skill.status === 'active' ? [skill, ...context.skills.filter(s => s.id !== skill.id)] : context.skills;
|
|
150
|
+
for (const selected of active.slice(0, CURATION_LIMITS.skills)) {
|
|
151
|
+
const item = await getProjectSkill(root, selected.id);
|
|
152
|
+
if (item.status !== 'active')
|
|
153
|
+
continue;
|
|
154
|
+
input.activeSkills.push({ id: item.id, title: item.title, content: item.content, scope: item.scope, description: item.description, triggers: item.triggers, revision: candidateRevision(item) });
|
|
155
|
+
if (!fits()) {
|
|
156
|
+
input.activeSkills.pop();
|
|
157
|
+
input.truncated = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return input;
|
|
161
|
+
}
|
|
162
|
+
// Preserve explicit safety clauses verbatim while allowing surrounding procedure to be rewritten.
|
|
163
|
+
export function safetyConstraints(content) {
|
|
164
|
+
return [...new Set(content.split(/\n|(?<=[.!?;])\s+/).map(s => s.replace(/^\s*\d+[.)]\s*/, '').trim()).filter(s => /\b(?:never|do not|must not|without|only|unchanged|instead of|rather than|no leftover)\b/i.test(s)))];
|
|
165
|
+
}
|
|
166
|
+
export async function validateCurationOutput(root, raw, input) {
|
|
167
|
+
await assertCompatibleProject(root);
|
|
168
|
+
const result = skillCurationOutputSchema.parse(raw);
|
|
169
|
+
const { procedure, ...fields } = result.draft;
|
|
170
|
+
const draft = await validateSkillDraft(root, { ...fields, content: procedure.map((step, i) => `${i + 1}. ${step}`).join('\n') }, input.knownReferences);
|
|
171
|
+
if (input.knownReferences.some(r => !draft.references.includes(r)))
|
|
172
|
+
throw new Error('Source reference was dropped');
|
|
173
|
+
if (draft.relatedKnowledge.some(id => !input.relatedKnowledge.some(k => k.id === id)))
|
|
174
|
+
throw new Error('Related knowledge outside supplied evidence');
|
|
175
|
+
const target = result.disposition.targetSkillId;
|
|
176
|
+
if (['update', 'covered'].includes(result.disposition.action) ? !input.activeSkills.some(s => s.id === target) : target !== null)
|
|
177
|
+
throw new Error('Invalid overlapping skill target');
|
|
178
|
+
const sources = new Map();
|
|
179
|
+
const evidenceSources = [input.candidate, ...input.observations, ...input.relatedKnowledge, ...input.activeSkills];
|
|
180
|
+
for (const source of evidenceSources) {
|
|
181
|
+
// Re-curation has original evidence and the approved revision under the same stable ID.
|
|
182
|
+
const details = `${source.description ?? ''} ${source.scope ?? ''} ${(source.triggers ?? []).join(' ')}`;
|
|
183
|
+
sources.set(source.id, `${sources.get(source.id) ?? ''} ${source.title} ${details} ${source.content}`);
|
|
184
|
+
}
|
|
185
|
+
const fieldsToGround = { title: draft.title, description: draft.description, scope: draft.scope, ...Object.fromEntries(draft.triggers.map((v, i) => [`triggers.${i}`, v])), ...Object.fromEntries(procedure.map((v, i) => [`procedure.${i}`, v])) };
|
|
186
|
+
if (new Set(result.grounding.map(g => g.field)).size !== result.grounding.length || result.grounding.some(g => !(g.field in fieldsToGround)))
|
|
187
|
+
throw new Error('Invalid grounding fields');
|
|
188
|
+
for (const [field, value] of Object.entries(fieldsToGround)) {
|
|
189
|
+
const evidence = result.grounding.find(g => g.field === field);
|
|
190
|
+
if (!evidence || evidence.sources.some(id => !sources.has(id)))
|
|
191
|
+
throw new Error(`Missing field evidence: ${field}`);
|
|
192
|
+
const sourceText = evidence.sources.map(id => sources.get(id)).join(' ');
|
|
193
|
+
const sourceTokens = tokens(sourceText.toLowerCase().replace(/[._/-]/g, ' '));
|
|
194
|
+
const significant = [...tokens(value.toLowerCase().replace(/[._/-]/g, ' '))].filter(t => t.length > 3);
|
|
195
|
+
if (significant.length && !significant.some(t => sourceTokens.has(t)))
|
|
196
|
+
throw new Error(`Ungrounded field: ${field}`);
|
|
197
|
+
const stem = (s) => s.toLowerCase().replace(/(?:ing|ed|es|s)$/, '');
|
|
198
|
+
const vocabulary = new Set([...sourceTokens].map(stem));
|
|
199
|
+
const connective = new Set('verify validate check inspect compare ensure confirm perform follow repeat use run preserve retain before after during when changing changes relevant required existing reusable procedure purpose scope references source evidence safely safe'.split(' ').map(stem));
|
|
200
|
+
if (significant.some(t => !vocabulary.has(stem(t)) && !connective.has(stem(t))))
|
|
201
|
+
throw new Error(`Unsupported concept in ${field}`);
|
|
202
|
+
// New paths/code identifiers or numeric conventions cannot be introduced in paraphrases.
|
|
203
|
+
for (const literal of value.match(/`[^`]+`|\b\d+(?:\.\d+)+\b|[\w.]+\/[\w./-]+/g) ?? [])
|
|
204
|
+
if (!sourceText.includes(literal.replace(/`/g, '')) && !sourceText.includes(literal))
|
|
205
|
+
throw new Error(`Unsupported literal: ${literal}`);
|
|
206
|
+
}
|
|
207
|
+
const overlapConstraints = result.disposition.action === 'update' ? safetyConstraints(input.activeSkills.find(s => s.id === target).content) : [];
|
|
208
|
+
const normalized = (s) => s.replace(/\s+/g, ' ').trim();
|
|
209
|
+
for (const constraint of [...input.protectedConstraints, ...overlapConstraints])
|
|
210
|
+
if (!normalized(draft.content).includes(normalized(constraint)))
|
|
211
|
+
throw new Error('Safety constraint was not preserved');
|
|
212
|
+
return { draft, disposition: result.disposition, reasoningSummary: result.reasoningSummary };
|
|
213
|
+
}
|
|
214
|
+
export async function curateProjectSkill(root, skill, provider) {
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
let timer;
|
|
217
|
+
try {
|
|
218
|
+
const input = await buildSkillCurationInput(root, skill);
|
|
219
|
+
const response = await Promise.race([
|
|
220
|
+
(assertNoSecrets(input), provider.curateSkill(structuredClone(input), controller.signal)),
|
|
221
|
+
new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(new Error('Curation timeout')); }, CURATION_LIMITS.timeoutMs); }),
|
|
222
|
+
]);
|
|
223
|
+
if (Buffer.byteLength(JSON.stringify(response.output) ?? '') > CURATION_LIMITS.outputBytes)
|
|
224
|
+
throw new Error('Curation output limit');
|
|
225
|
+
assertNoSecrets(response.output);
|
|
226
|
+
const validated = await validateCurationOutput(root, response.output, input);
|
|
227
|
+
const selectedTarget = input.activeSkills.find(s => s.id === validated.disposition.targetSkillId);
|
|
228
|
+
if (skill.status === 'active' && (validated.disposition.action !== 'update' || selectedTarget?.id !== skill.id))
|
|
229
|
+
throw new Error('Active re-curation must propose updating this skill');
|
|
230
|
+
return { ...validated, ...(selectedTarget ? { target: { id: selectedTarget.id, title: selectedTarget.title, revision: selectedTarget.revision } } : {}), mode: 'assisted', relatedKnowledge: input.relatedKnowledge.filter(k => validated.draft.relatedKnowledge.includes(k.id)), notice: undefined };
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return { draft: deterministicSkillDraft(skill), mode: 'deterministic', disposition: undefined, reasoningSummary: undefined, relatedKnowledge: [], notice: 'A curated draft could not be prepared safely. Review this source-based draft or use Edit.' };
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
clearTimeout(timer);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=skill-curation.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { assertNoSecrets } from "./secrets.js";
|
|
2
|
+
import { skillInputSchema } from './skills.js';
|
|
3
|
+
import { INTELLIGENCE_LIMITS } from './intelligence.js';
|
|
4
|
+
import { evidenceSentences, exactKnowledge, semanticRisk } from './semantic-safety.js';
|
|
5
|
+
import { estimateTokens } from './telemetry.js';
|
|
6
|
+
/** Existing engine's extractive boundary: polish presentation, never manufacture rules. */
|
|
7
|
+
export async function polishSkillDraft(provider, source) {
|
|
8
|
+
assertNoSecrets(source);
|
|
9
|
+
const fallback = skillInputSchema.parse(source);
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
let timer;
|
|
12
|
+
try {
|
|
13
|
+
if (estimateTokens(JSON.stringify(fallback)) > INTELLIGENCE_LIMITS.distillInputTokens)
|
|
14
|
+
throw new Error('input budget');
|
|
15
|
+
const response = await Promise.race([
|
|
16
|
+
provider.polishSkill(structuredClone(fallback), controller.signal),
|
|
17
|
+
new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(new Error('timeout')); }, INTELLIGENCE_LIMITS.timeoutMs); }),
|
|
18
|
+
]);
|
|
19
|
+
if (Buffer.byteLength(JSON.stringify(response.output) ?? '') > INTELLIGENCE_LIMITS.outputBytes)
|
|
20
|
+
throw new Error('output budget');
|
|
21
|
+
assertNoSecrets(response.output);
|
|
22
|
+
const draft = skillInputSchema.strict().parse(response.output);
|
|
23
|
+
// Titles may be shortened by selecting a contiguous phrase, with capitalization changes.
|
|
24
|
+
// Purpose selects complete existing sentences. Procedure/order/constraints are retained.
|
|
25
|
+
const allowedPurpose = new Set(evidenceSentences(fallback.description));
|
|
26
|
+
if (!fallback.title.toLowerCase().includes(draft.title.toLowerCase()) || semanticRisk(fallback.title, draft.title) || semanticRisk(fallback.description, draft.description) ||
|
|
27
|
+
!evidenceSentences(draft.description).every(s => allowedPurpose.has(s)) ||
|
|
28
|
+
exactKnowledge(draft.content) !== exactKnowledge(fallback.content) ||
|
|
29
|
+
draft.scope !== fallback.scope || JSON.stringify(draft.triggers) !== JSON.stringify(fallback.triggers) ||
|
|
30
|
+
JSON.stringify(draft.references) !== JSON.stringify(fallback.references))
|
|
31
|
+
throw new Error('unsupported revision');
|
|
32
|
+
return { draft, mode: 'assisted' };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { draft: fallback, mode: 'deterministic', notice: 'Automatic polish was unavailable or could not preserve the evidence. Review this source-based draft, or use Edit.' };
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=skill-polish.js.map
|