fix: preserve citation intent and current routing evidence gates

This commit is contained in:
haelyra
2026-10-11 01:56:19 -04:00
3 changed files with 708 additions and 28 deletions
+144 -27
View File
@@ -44,10 +44,138 @@ function hasSuggestionEvidence(candidate) {
const MIN_ANCHOR_TERMS = 1;
// v4: an explicit empty proposal (decline) is honored; the tier-2 fallback no
// longer overrides declines at the launch/selection call sites.
// v5: implicit admission requires a name or trigger anchor term.
const ROUTING_POLICY_VERSION = 5;
// v5: indirect name citations cannot bypass review through exact or scored admission.
// v6: ordered directives and singular named references share the same admission guard.
// v7: independent directives scope negation and imperatives allow question punctuation.
// v8: only recognized request/rejection scopes can authorize coordinated directives.
// v9: neutral task preambles do not suppress a later explicit skill request.
// v10: illustrative instructions do not supply or withdraw a direct request.
// v11: a contrastive current-task instruction ends the preceding example scope.
// v12: preserve current-main evidence floors and anchor checks.
const ROUTING_POLICY_VERSION = 12;
const TASK_KEYS = new Set(['sessionId', 'taskId', 'revision', 'phase', 'query', 'explicitIds', 'proposedIds', 'noWorkflow']);
const DIRECTIVE_VERB = /\b(use|apply|invoke|run|follow|load)\s+(the\s+)?/i;
const DIRECTIVE_REQUEST = /^(?:please )?(?:(?:can|could|would|will) you (?:please )?|do )?(?:use|apply|invoke|run|follow|load)\b/;
const DIRECTIVE_REJECTION = /^(?:please )?(?:(?:(?:do )?not|never) (?:use|apply|invoke|run|follow|load)\b|avoid\b)/;
const DIRECTIVE_NAME_PREFIX = new RegExp(DIRECTIVE_REQUEST.source + '\\s+(?:the\\s+)?(?:skill\\s*)?$');
const QUESTION_START = /^(?!do not\b)(can|could|would|should|shall|will|may|might|must|ought|do|does|did|is|are|why|how|what|when|where|which)\b/;
const REPORTED_INSTRUCTION = /\b(say|says|said|reads|told|mentions?|quoted?|document|states?|stated|recommends?|recommended|asserts?|asserted)\b/;
const EXAMPLE_PREFIX = /^(?:for (?:example|instance)|as an example|to illustrate)\b/;
const CURRENT_TASK_SUFFIX = /\bfor (?:this|the current) task$/;
// Subject-led statements may describe someone else's instructions or intentions.
const SUBJECT_PREFIX = /^(?:the|a|an|i|you|he|she|it|we|they|my|your|his|her|its|our|their|this|that|these|those)\b/;
// Rejections keep their whole list until another instruction/question begins.
const INSTRUCTION_BOUNDARY = /(?:,\s*(?:(?:and|but)\s+)?|\s+(?:and|but)\s+)(?=(?:please\s+)?(?:do\s+(?:not\s+)?|not\s+|never\s+)?(?:use|apply|invoke|run|follow|load)\b|(?:can|could|would|should|shall|will|may|might|must|ought|do|does|did|is|are|why|how|what|when|where|which)\b)/i;
// Admission additionally checks intervening scopes, including unknown wording.
const ADMISSION_BOUNDARY = /(?:,\s*(?:(?:and|but)\s+)?|\s+(?:and|but)\s+)(?=\S)/i;
/** Track example spans without discarding the connectors between instructions.
* A contrast inside an example remains illustrative unless its instruction
* explicitly addresses the current task. Earlier non-example scopes stay intact.
* @param {string} text Normalized clause with comma boundaries preserved.
* @param {string[]} names Known candidate aliases whose internal connectors are names.
* @returns {Array<{start: number, text: string, example: boolean}>} Ordered scopes.
*/
function citationScopes(text, names) {
const mentions = names.flatMap(name => [...text.matchAll(new RegExp('\\b' + name + '\\b', 'g'))]
.map(mention => ({ start: mention.index, end: mention.index + name.length })));
const boundaries = [...text.matchAll(new RegExp(ADMISSION_BOUNDARY.source, 'gi'))]
.filter(boundary => !mentions.some(mention => boundary.index > mention.start && boundary.index < mention.end));
let example = false;
return [0, ...boundaries.map(boundary => boundary.index + boundary[0].length)].map((start, index) => {
const scope = text.slice(start, boundaries[index]?.index ?? text.length);
const currentTask = CURRENT_TASK_SUFFIX.test(scope)
&& (DIRECTIVE_REQUEST.test(scope) || DIRECTIVE_REJECTION.test(scope));
if (currentTask && /\bbut\b/.test(boundaries[index - 1]?.[0] || '')) example = false;
if (EXAMPLE_PREFIX.test(scope)) example = true;
return { start, text: scope, example };
});
}
/** Normalize citation names while preserving contracted negation as a separate word.
* @param {string} text Raw query text or a registry alias.
* @returns {string} Lowercase alphanumeric words with negation preserved.
*/
const normalizedQueryName = text => text.replace(/n['\u2019]t\b/gi, ' not')
.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/_/g, ' ')
.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
/** Return canonical/native aliases plus conservative singular forms. Singular
* multiword aliases only guard indirect references; they never create exact admission.
* @param {object} candidate Ranked skill candidate with its canonical ID and exact alias.
* @param {string} nativeName Registry name that may be absent from exact retrieval.
* @returns {Array<{name: string, exact: boolean}>} Normalized names and admission eligibility.
*/
function citationAliases(candidate, nativeName) {
const names = [...new Set([candidate.exactAlias, candidate.id.slice('skill:'.length), nativeName]
.filter(Boolean).map(normalizedQueryName).filter(Boolean))];
return names.flatMap(name => [{ name, exact: true },
...(/\s\w+[^s]s$/.test(name) ? [{ name: name.slice(0, -1), exact: false }] : [])]);
}
/** Classify a retrieval candidate's references in query order. Genuine later
* directives/rejections replace earlier instructions; questions, quotations and
* reported speech do not withdraw a directive. Returns directive, indirect or none.
* @param {object} candidate Ranked skill candidate with its canonical ID and exact alias.
* @param {string} query Task text to classify without loading skill contents.
* @param {string} nativeName Registry name, including aliases absent from exact retrieval.
* @param {string[]} names All candidate aliases, used to preserve names across scopes.
* @returns {'directive'|'indirect'|'none'} Admission evidence for this skill only.
*/
function citationFor(candidate, query, nativeName, names) {
const aliases = citationAliases(candidate, nativeName);
let citation = 'none';
// Mask quoted instructions before splitting clauses so punctuation inside a
// quotation cannot look like a separate instruction. Quoted names remain usable.
const unquoted = query.replace(/"[^"]*"|\u201c[^\u201d]*\u201d|(?:^|[\s(:])'[^']*'|\u2018[^\u2019]*\u2019|\x60[^\x60]*\x60/g, part => {
const text = normalizedQueryName(part);
const instruction = DIRECTIVE_VERB.test(text);
// Quoting a task qualifier cannot turn an illustrative instruction into a request.
if (!instruction && !CURRENT_TASK_SUFFIX.test(text)) return part;
if (instruction && aliases.some(({ name }) => new RegExp('\\b' + name + '\\b').test(text))) citation = 'indirect';
return part.replace(/[^.!?;\n]/g, ' ');
});
// Keep the illustrative abbreviation together when its periods would split clauses.
const expanded = unquoted.replace(/\be\.g\.(?=\s|[,;:!?]|$)/gi, 'for example');
for (const clause of expanded.match(/[^.!?;\n]+[.!?;\n]*/g) || []) {
// Keep comma boundaries for directive scope without changing alias normalization.
const text = clause.split(',').map(normalizedQueryName).join(', ');
const clauseScopes = citationScopes(text, names);
const mentions = aliases.flatMap(({ name, exact }) => [...text.matchAll(new RegExp('\\b' + name + '\\b', 'g'))]
.map(mention => ({ index: mention.index, exact }))).sort((a, b) => a.index - b.index);
for (const mention of mentions) {
const prefix = text.slice(0, mention.index);
const precedingScopes = clauseScopes.filter(scope => scope.start <= mention.index);
const example = precedingScopes.at(-1)?.example;
const scopes = precedingScopes.filter(scope => !scope.example)
.map(scope => scope.text.slice(0, mention.index - scope.start));
const instructionPrefix = prefix.split(INSTRUCTION_BOUNDARY).at(-1);
// A task preamble such as "fix the bug" does not change a later request.
// Only preceding scopes may be neutral: an embedded instruction, question,
// or subject-led statement still needs review. The final scope must remain
// a direct request; neutral text never supplies admission evidence itself.
const instruction = scopes.every((scope, index) => DIRECTIVE_REQUEST.test(scope) || DIRECTIVE_REJECTION.test(scope)
|| (index < scopes.length - 1 && !DIRECTIVE_VERB.test(scope)
&& !QUESTION_START.test(scope) && !REPORTED_INSTRUCTION.test(scope) && !SUBJECT_PREFIX.test(scope)));
const question = (!DIRECTIVE_REQUEST.test(text) && QUESTION_START.test(text))
|| (!DIRECTIVE_REQUEST.test(instructionPrefix)
&& (QUESTION_START.test(instructionPrefix) || clause.includes('?')));
if (example || question || scopes.some(scope => REPORTED_INSTRUCTION.test(scope)) || /^\s*["'\u201c\u2018\x60]/.test(clause)) {
if (citation === 'none') citation = 'indirect';
} else if (/\b(do not|never|no|not|avoid)\b/.test(instructionPrefix)) {
citation = 'indirect';
} else if (!instruction) {
if (citation === 'none') citation = 'indirect';
} else if (mention.exact && candidate.exact
&& DIRECTIVE_NAME_PREFIX.test(scopes.at(-1))) {
citation = 'directive';
}
}
}
return citation;
}
function validateTask(task) {
if (!task || typeof task !== 'object' || Array.isArray(task)) throw new Error('Task must be an object');
for (const key of Object.keys(task)) if (!TASK_KEYS.has(key)) throw new Error(`Unknown task field: ${key}`);
@@ -196,7 +324,13 @@ function routingEntries({ repoRoot = DEFAULT_REPO_ROOT, profileId = 'lean@1', ta
routingPolicyVersion: ROUTING_POLICY_VERSION, triggersDigest: digestObject(triggers), entries };
}
/** Pure task-scoped resolver. Returned context never invokes a native skill or changes permissions. */
/** Resolve task-scoped skill context without invoking native skills or changing permissions.
* @param {object} [options] Repository, task, profile, and selection constraints.
* @param {object} options.task Task identity, query, and explicit or proposed skill IDs.
* @param {boolean} [options.load=false] Include verified resource contents in the result.
* @param {object|null} [options.previous=null] Receipt reusable only when its binding matches.
* @returns {object} Selection decision, candidates, verified resources, and a bound receipt.
*/
function resolveTaskContext({ repoRoot = DEFAULT_REPO_ROOT, task, profileId = 'lean@1', target = 'codex',
selectionMode = 'auto', include = [], exclude = [], load = false, previous = null, expectedDigest = null } = {}) {
validateTask(task);
@@ -229,28 +363,11 @@ function resolveTaskContext({ repoRoot = DEFAULT_REPO_ROOT, task, profileId = 'l
// questions, negations, reported speech, multiple cited names — never admit
// implicitly. Everything else keeps the bounded-proposal path so the
// primary agent decides ambiguous cases during work it was already doing.
const DIRECTIVE_VERB = /\b(use|apply|invoke|run|follow|load)\s+(the\s+)?/i;
const normalizedQueryName = text => text.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/_/g, ' ')
.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
const directiveCitation = candidate => {
if (!candidate || !candidate.exact) return false;
const text = normalizedQueryName(task.query || '');
const aliases = [...new Set([candidate.exactAlias,
candidate.id.slice('skill:'.length).toLowerCase(),
candidate.id.slice('skill:'.length).toLowerCase().replace(/-/g, ' ')].filter(Boolean))];
for (const name of aliases) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pattern = new RegExp(`${DIRECTIVE_VERB.source}(skill\\s*:?\\s*)?${escaped}(\\s+(skill|workflow|guidance))?\\b`, 'i');
const match = pattern.exec(text);
if (!match) continue;
const window = text.slice(Math.max(0, match.index - 28), match.index);
if (/\b(do not|don't|never|no)\b/.test(window)) return false;
if (/\b(says|said|reads|told|document)\b/i.test(task.query || '')) return false;
return true;
}
return false;
};
const exactAnchors = candidates.filter(directiveCitation);
const names = [...new Set(candidates.flatMap(candidate => citationAliases(candidate, byId.get(candidate.id).name)
.map(alias => alias.name)))];
const citations = new Map(candidates.map(candidate => [candidate.id,
citationFor(candidate, task.query || '', byId.get(candidate.id).name, names)]));
const exactAnchors = candidates.filter(candidate => citations.get(candidate.id) === 'directive');
let autoSelection = null;
if (!task.noWorkflow && selectionMode === 'auto' && !reused && !explicitIds.length && !proposedIds.length && candidates.length) {
if (exactAnchors.length === 1) {
@@ -259,7 +376,7 @@ function resolveTaskContext({ repoRoot = DEFAULT_REPO_ROOT, task, profileId = 'l
} else if (!exactAnchors.length) {
const top = candidates[0];
const second = candidates[1];
if (top.bm25 >= AUTO_ADMIT_MIN_BM25 && top.matchedTerms.length >= AUTO_ADMIT_MIN_TERMS
if (citations.get(top.id) !== 'indirect' && top.bm25 >= AUTO_ADMIT_MIN_BM25 && top.matchedTerms.length >= AUTO_ADMIT_MIN_TERMS
&& top.anchorTerms.length >= MIN_ANCHOR_TERMS
&& (!second || top.bm25 >= AUTO_ADMIT_MARGIN * (second.bm25 || 0))) {
autoSelection = { id: top.id, bm25: top.bm25, matchedTerms: top.matchedTerms.length, exact: false };
@@ -271,7 +388,7 @@ function resolveTaskContext({ repoRoot = DEFAULT_REPO_ROOT, task, profileId = 'l
&& !explicitIds.length && !proposedIds.length && candidates.length && !exactAnchors.length) {
const top = candidates[0];
const second = candidates[1];
if (meetsFallbackBar(top)
if (citations.get(top.id) !== 'indirect' && meetsFallbackBar(top)
&& top.anchorTerms.length >= MIN_ANCHOR_TERMS
&& (!second || top.bm25 >= FALLBACK_MARGIN * (second.bm25 || 0))) {
fallback = { id: top.id, bm25: top.bm25, matchedTerms: top.matchedTerms.length };
+180
View File
@@ -155,3 +155,183 @@ test('spawn failures and timeout signals remain unsuccessful without a native ex
assert.equal(result.taskSuccess, 'unverified');
}
}));
for (const query of ["Don't use the feature skill.", 'Don\u2019t use the feature skill.',
'Can I use the feature skill?', 'The README says use the feature skill.', 'The docs say use feature.',
'The docs say do not use feature and use shared guidance.',
'The docs say do not use feature, use shared guidance, okay?',
'The spec states do not use feature and use shared guidance.',
'The spec stated do not use feature, use shared guidance.',
'The spec recommends we do not use feature and use shared guidance.',
'The spec asserts we should not use feature and use shared guidance.',
'Use standard tools, the spec stipulates do not use feature and use shared guidance.',
'Should we avoid feature and use shared guidance?',
'Should we avoid feature, use shared guidance?',
'Use standard tools, should we use feature?',
'Use standard tools, shall we use feature?',
'Use standard tools, will we use feature?',
'Use standard tools, ought we to use feature?',
'Please use the existing code, can I use feature?',
'Use standard tools and should we use feature?',
'Use standard tools but should we use feature?',
'Fix the bug, shall we use feature?',
'Fix the bug, the spec states do not use feature and use shared guidance.',
'The docs say fix the bug and use feature.',
'Can we fix the bug and use feature?',
'The spec stipulates: fix the bug and use feature.',
'Our policy stipulates repair the bug and use feature.',
'Fix the bug, ought we to repair it and use feature?',
'Fix the bug, must we repair it and use feature?',
'Fix the bug, feature.',
'For example, use feature',
'For instance, use feature.',
'As an example, use feature.',
'To illustrate, use feature.',
'Fix the bug, for example, use feature.',
'For example, use feature and use feature.',
'For example, use feature but use shared guidance.',
"For example, don't use feature but use shared guidance.",
'For example, use feature and use feature for this task.',
'For example, use feature, but should we use feature for this task?',
'For example, use feature, but the docs say use feature for this task.',
'For example, use feature, but "use feature for this task" is an example.',
'The docs say for example, use feature, but use feature for this task.',
'For example, use feature, but use feature "for this task"',
'Should we, for example, use shared guidance, but use feature for this task?',
'Our policy stipulates, for example, use shared guidance, but use feature for this task.',
'Use feature. For example, use shared guidance, but do not use feature for this task.',
'For example, use shared guidance, but use feature for this task and do not use feature.',
'e.g., use feature.',
'E.g. use feature.',
'Do not use feature and shared guidance.',
"Use feature, don't use feature.",
"Use feature and don't use feature.",
"Use feature but don't use feature."]) {
test('indirect citation uses the proposal path and honors decline: ' + query, () => withFixture(repoRoot => {
const phases = [];
const result = launchTaskContext({ repoRoot, task: { ...input, query, explicitIds: [] },
execute(command, args, options) {
phases.push(options.phase);
if (options.phase === 'selection') return { status: 0, stdout: '{"selectedIds":[]}' };
assert.doesNotMatch(options.input, /# feature/);
assert.doesNotMatch(options.input, /# shared/);
return { status: 0, stdout: 'ok' };
} });
assert.deepEqual(phases, ['selection', 'task']);
assert.equal(result.routingCalls, 1);
assert.deepEqual(result.selection.selectedIds, []);
assert.deepEqual(result.selection.loadedIds, []);
assert.equal(result.selection.fallback, null);
assert.equal(result.selection.reason, 'agent-declined-selection');
}));
}
for (const [query, skill] of [
['Fix the bug, use feature', 'feature'],
['Fix the bug and use feature.', 'feature'],
['Please fix the bug, please use feature.', 'feature'],
['Write the regression test, use feature.', 'feature'],
['Investigate the bug but use feature.', 'feature'],
['For this task, use feature.', 'feature'],
["Fix the bug, don't use feature and use shared guidance.", 'shared'],
["Don't use feature, use shared guidance.", 'shared'],
["Don't use feature and use shared guidance.", 'shared'],
["Don't use feature but use shared guidance.", 'shared'],
["Don't use feature, use shared guidance, okay?", 'shared'],
["Don't use feature and use shared guidance, okay?", 'shared'],
["Don't use feature but use shared guidance, okay?", 'shared'],
['Do not use feature, please use shared guidance?', 'shared'],
['Do not use feature and please use shared guidance?', 'shared'],
['Do not use feature but please use shared guidance?', 'shared'],
["Don't use feature, use feature.", 'feature'],
['Use feature?', 'feature'],
['Please use feature?', 'feature'],
['Use feature to fix the bug, okay?', 'feature'],
['Use feature. Shall we use feature?', 'feature'],
['Use feature, shall we use shared guidance?', 'feature'],
['Shall we use shared guidance? Use feature.', 'feature'],
['Use feature. The spec states do not use feature and use shared guidance.', 'feature'],
['The spec states do not use feature and use shared guidance. Use feature.', 'feature'],
['Use feature. For example, use shared guidance.', 'feature'],
['For example, use shared guidance. Use feature.', 'feature'],
["Use feature. For example, don't use feature.", 'feature'],
['Use feature to build an example.', 'feature'],
['Fix the example, use feature.', 'feature'],
['Fix e.g.js, use feature.', 'feature'],
["Use feature. E.g., don't use feature.", 'feature'],
['For example, use feature, but use feature for this task', 'feature'],
['For example, use shared guidance, but please use feature for this task.', 'feature'],
['For instance, use shared guidance but use feature for this task.', 'feature'],
['For example use shared guidance, but use feature for this task.', 'feature'],
['For example, use shared guidance, but use feature for the current task.', 'feature'],
['For example, use shared guidance, but use feature for this task, for instance, do not use feature.', 'feature'],
['For example, the docs say use shared guidance, but use feature for this task.', 'feature'],
]) {
test('an unambiguous final directive injects context without a proposal: ' + query, () => withFixture(repoRoot => {
const phases = [];
const result = launchTaskContext({ repoRoot, task: { ...input, query, explicitIds: [] },
execute(command, args, options) {
phases.push(options.phase);
if (options.phase === 'selection') return { status: 0, stdout: '{"selectedIds":[]}' };
assert.match(options.input, new RegExp('# ' + skill));
assert.doesNotMatch(options.input, new RegExp('# ' + (skill === 'shared' ? 'feature' : 'shared')));
return { status: 0, stdout: 'ok' };
} });
assert.deepEqual(phases, ['task']);
assert.equal(result.routingCalls, 0);
assert.deepEqual(result.selection.loadedIds, ['skill:' + skill]);
assert.equal(result.selection.reason, 'auto-selection');
}));
}
test('a final directive after a question reaches the task with its context', () => withFixture(repoRoot => {
const phases = [];
const result = launchTaskContext({ repoRoot,
task: { ...input, query: 'Should we use feature? Use feature.', explicitIds: [] },
execute(command, args, options) {
phases.push(options.phase);
assert.match(options.input, /# feature/);
return { status: 0, stdout: 'ok' };
} });
assert.deepEqual(phases, ['task']);
assert.equal(result.routingCalls, 0);
assert.deepEqual(result.selection.loadedIds, ['skill:feature']);
}));
for (const [suffix, expectedIds] of [['.', ['skill:feature']], [' and use shared guidance.', []]]) {
test('a conjunction inside a native alias preserves launch scope: ' + suffix, () => withFixture(repoRoot => {
fs.writeFileSync(path.join(repoRoot, 'skills/feature/SKILL.md'),
'---\nname: research-and-development\ndescription: Feature workflow\n---\n# feature');
const query = 'For example, use shared guidance, but use research and development for this task' + suffix;
const phases = [];
const result = launchTaskContext({ repoRoot, task: { ...input, query, explicitIds: [] },
execute(command, args, options) {
phases.push(options.phase);
if (options.phase === 'selection') return { status: 0, stdout: '{"selectedIds":[]}' };
if (expectedIds.length) assert.match(options.input, /# feature/);
else assert.doesNotMatch(options.input, /# feature/);
assert.doesNotMatch(options.input, /# shared/);
return { status: 0, stdout: 'ok' };
} });
assert.deepEqual(phases, expectedIds.length ? ['task'] : ['selection', 'task']);
assert.deepEqual(result.selection.selectedIds, expectedIds);
assert.deepEqual(result.selection.loadedIds, expectedIds);
assert.equal(result.selection.reason, expectedIds.length ? 'auto-selection' : 'agent-declined-selection');
}));
}
test('a singular rejected name cannot inject scored context at launch', () => {
const phases = [];
const query = "Don't use the database migration skill. Review a PostgreSQL migration that adds an indexed nullable column without downtime.";
const result = launchTaskContext({ task: { ...input, query, explicitIds: [] },
execute(command, args, options) {
phases.push(options.phase);
if (options.phase === 'selection') return { status: 0, stdout: '{"selectedIds":[]}' };
assert.doesNotMatch(options.input, /# Database Migration Patterns/);
return { status: 0, stdout: 'ok' };
} });
assert.deepEqual(phases, ['selection', 'task']);
assert.equal(result.routingCalls, 1);
assert.deepEqual(result.selection.loadedIds, []);
assert.equal(result.selection.fallback, null);
});
+384 -1
View File
@@ -167,11 +167,13 @@ test('source changes invalidate reuse and source-bound load preview', () => with
}));
test('bounded search uses canonical IDs and deterministic order', () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query: 'feature' });
const result = resolve(repoRoot, { query: 'feature' }, { load: true });
assert.equal(result.candidates[0].id, 'skill:feature');
// A bare name mention ranks the skill but is not a directive citation.
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
assert.ok(result.candidates.length <= 5);
}));
@@ -201,6 +203,18 @@ test('multiple directive citations defer to an explicit agent proposal', () => w
assert.equal(result.reason, 'agent-selection-required');
}));
for (const [suffix, expectedIds] of [['.', ['skill:feature']], [' and use shared guidance.', []]]) {
test('a conjunction inside a native alias preserves task request scope: ' + suffix, () => withFixture(repoRoot => {
write(repoRoot, 'skills/feature/SKILL.md', '---\nname: research-and-development\ndescription: Feature workflow\n---\n# feature');
const query = 'For example, use shared guidance, but use research and development for this task' + suffix;
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.selectedIds, expectedIds);
assert.deepEqual(result.loadedIds, expectedIds);
assert.equal(result.reason, expectedIds.length ? 'auto-selection' : 'agent-selection-required');
if (!expectedIds.length) assert.equal(result.fallback, null);
}));
}
test('name anchors require complete word boundaries', () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query: 'featurette sharedness' }, { load: true });
assert.deepEqual(result.loadedIds, []);
@@ -344,3 +358,372 @@ test('suggestion evidence requires an exact name or the absolute fallback floor'
assert.equal(hasSuggestionEvidence(candidate({ exact: true, bm25: 1, matchedTerms: ['feature'] })), true);
assert.equal(hasSuggestionEvidence(null), false);
});
for (const query of [
"You shouldn't use feature.",
"You can\u2019t use feature.",
"For feature, don't use the feature skill.",
'For feature, never invoke feature.',
'The phrase "Use feature" is an example.',
'The phrase "Use feature." is an example.',
'The phrase \u201cUse feature\u201d is an example.',
'The phrase `Use feature` is an example.',
"Don't use the feature skill.",
'Don\u2019t use the feature skill.',
'Do not use the feature skill.',
'Never use the feature skill.',
'Can I use the feature skill?',
'Should we use the feature skill?',
'Why use the feature skill?',
'The README says to use the feature skill.',
'The docs say use feature.',
'The docs say to use feature.',
'The docs said use feature.',
'"Use the feature skill" is an example.',
]) {
test('indirect name citation needs an agent decision: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:feature'));
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
}));
}
test('a negated citation does not suppress a separate affirmative directive', () => withFixture(repoRoot => {
for (const query of [
"Don't use feature. Use shared guidance.",
'Don\u2019t use feature; please use shared guidance.',
'The document says use feature. Use shared guidance.',
]) {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.loadedIds, ['skill:shared']);
}
}));
for (const query of [
"Don't use feature, use shared guidance.",
"Don't use feature and use shared guidance.",
"Don't use feature but use shared guidance.",
'Do not use feature, please use shared guidance.',
'Don\u2019t use feature, and please use shared guidance.',
"Don't use feature, do use shared guidance.",
"Don't use feature, use shared guidance, okay?",
"Don't use feature and use shared guidance, okay?",
"Don't use feature but use shared guidance, okay?",
'Do not use feature, please use shared guidance?',
'Do not use feature and please use shared guidance?',
'Do not use feature but please use shared guidance?',
]) {
test('a new directive scopes an earlier rejection: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.selectedIds, ['skill:shared']);
assert.deepEqual(result.loadedIds, ['skill:shared']);
assert.equal(result.reason, 'auto-selection');
assert.equal(result.receipt.autoSelection.exact, true);
}));
}
for (const query of [
'The docs say do not use feature and use shared guidance.',
'The docs say do not use feature, use shared guidance.',
'The docs say do not use feature, use shared guidance, okay?',
'Use standard tools, the spec states do not use feature and use shared guidance.',
'The spec states do not use feature and use shared guidance.',
'The spec stated do not use feature, use shared guidance.',
'The spec recommends we do not use feature and use shared guidance.',
'The spec asserts we should not use feature and use shared guidance.',
'Use standard tools, the spec stipulates do not use feature and use shared guidance.',
'Use the documentation that says do not use feature and use shared guidance.',
'Should we avoid feature and use shared guidance?',
'Should we avoid feature, use shared guidance?',
'Do not use feature and shared guidance.',
'Do not use feature, shared guidance.',
]) {
test('a connector preserves indirect or negated context: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:shared'));
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
}));
}
for (const query of [
'Use standard tools, should we use feature?',
'Use standard tools, shall we use feature?',
'Use standard tools, will we use feature?',
'Use standard tools, ought we to use feature?',
'Please use the existing code, can I use feature?',
'Use standard tools and should we use feature?',
'Use standard tools but should we use feature?',
]) {
test('a generic directive does not authorize a later question: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:feature'));
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
}));
}
for (const query of ['Use feature?', 'Please use feature?', 'Use feature to fix the bug, okay?']) {
test('an imperative with question punctuation remains a directive: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.selectedIds, ['skill:feature']);
assert.deepEqual(result.loadedIds, ['skill:feature']);
assert.equal(result.reason, 'auto-selection');
assert.equal(result.receipt.autoSelection.exact, true);
}));
}
test('ordinary and polite directives preserve automatic and explicit selection', () => withFixture(repoRoot => {
for (const query of ['Please use the feature skill.', 'Can you use the feature skill?',
'Use feature without changing the code.', 'Use feature to document the change.',
'Use "feature".', 'Use `feature`.', 'Do use feature.', 'Please can you use feature?']) {
assert.deepEqual(resolve(repoRoot, { query }, { load: true }).loadedIds, ['skill:feature']);
}
for (const field of ['explicitIds', 'proposedIds']) {
assert.deepEqual(resolve(repoRoot, { query: 'Should we use feature?', [field]: ['skill:feature'] },
{ load: true }).loadedIds, ['skill:feature']);
}
}));
for (const [query, expectedId] of [
['Fix the bug, use feature', 'skill:feature'],
['Fix the bug and use feature.', 'skill:feature'],
['Please fix the bug, please use feature.', 'skill:feature'],
['Write the regression test, use feature.', 'skill:feature'],
['Investigate the bug but use feature.', 'skill:feature'],
['For this task, use feature.', 'skill:feature'],
["Fix the bug, don't use feature and use shared guidance.", 'skill:shared'],
]) {
test('a general task preamble preserves a later directive: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.selectedIds, [expectedId]);
assert.deepEqual(result.loadedIds, [expectedId]);
assert.equal(result.reason, 'auto-selection');
assert.equal(result.receipt.autoSelection.exact, true);
}));
}
for (const query of [
'Fix the bug, shall we use feature?',
'Fix the bug, the spec states do not use feature and use shared guidance.',
'The docs say fix the bug and use feature.',
'Can we fix the bug and use feature?',
'The spec stipulates: fix the bug and use feature.',
'Our policy stipulates repair the bug and use feature.',
'Fix the bug, ought we to repair it and use feature?',
'Fix the bug, must we repair it and use feature?',
'Fix the bug, feature.',
]) {
test('a general task preamble cannot promote an indirect citation: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:feature'));
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
}));
}
for (const prefix of ['database-migrations:', "Don't use database-migrations.", 'Do not use database-migrations.',
'Should we use database-migrations?', 'The README says use database-migrations.']) {
test('strong retrieval cannot bypass an indirect citation: ' + prefix, () => {
const query = prefix + ' Review a PostgreSQL migration that adds an indexed nullable column without downtime.';
const result = resolveTaskContext({ task: task({ query }), load: true });
const candidate = result.candidates.find(value => value.id === 'skill:database-migrations');
assert.ok(candidate && candidate.bm25 >= 20, 'Exercise the BM25 auto-admission threshold');
assert.deepEqual(result.loadedIds, []);
assert.equal(result.fallback, null);
});
}
for (const query of [
'For example, use feature',
'For instance, use feature.',
'As an example, use feature.',
'To illustrate, use feature.',
'Fix the bug, for example, use feature.',
'For example, use feature and use feature.',
'For example, use feature but use shared guidance.',
"For example, don't use feature but use shared guidance.",
'For example, use feature and use feature for this task.',
'For example, use feature, but should we use feature for this task?',
'For example, use feature, but the docs say use feature for this task.',
'For example, use feature, but "use feature for this task" is an example.',
'The docs say for example, use feature, but use feature for this task.',
'For example, use feature, but use feature "for this task"',
'Should we, for example, use shared guidance, but use feature for this task?',
'Our policy stipulates, for example, use shared guidance, but use feature for this task.',
'e.g., use feature.',
'E.g. use feature.',
]) {
test('an illustrative citation needs an agent decision: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:feature'));
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.reason, 'agent-selection-required');
assert.equal(result.fallback, null);
}));
}
for (const [routingPolicyVersion, query] of [
[7, 'Use standard tools, shall we use feature?'],
[9, 'For example, use feature'],
]) {
test('an old policy ' + routingPolicyVersion + ' selected receipt is invalidated after policy changes', () => withFixture(repoRoot => {
const first = resolve(repoRoot, { query, explicitIds: ['skill:feature'] });
const { digestObject } = require('../../scripts/lib/context-profile-support');
const { compileContextProfile } = require('../../scripts/lib/context-profiles');
const plan = compileContextProfile({ repoRoot, selectionMode: 'auto' });
const bindingDigest = digestObject({ sessionId: 'session-1', taskId: 'task-1', revision: 1,
phase: 'implement', planDigest: plan.planDigest, routingPolicyVersion,
triggersDigest: digestObject({}), queryDigest: digestObject(query) });
const { receiptDigest: _receiptDigest, ...receipt } = first.receipt;
const oldReceipt = { ...receipt, bindingDigest, explicitIds: [],
selectionDigest: digestObject({ bindingDigest, selectedIds: ['skill:feature'], explicitIds: [] }) };
const previous = { ...oldReceipt, receiptDigest: digestObject(oldReceipt) };
const result = resolve(repoRoot, { query }, { previous, load: true });
assert.equal(result.reused, false);
assert.notEqual(result.receipt.bindingDigest, previous.bindingDigest);
assert.deepEqual(result.selectedIds, []);
assert.deepEqual(result.loadedIds, []);
assert.equal(result.fallback, null);
}));
}
for (const [routingPolicyVersion, query, expectedId] of [
[6, "Don't use feature, use shared guidance.", 'skill:shared'],
[8, 'Fix the bug, use feature', 'skill:feature'],
[10, 'For example, use feature, but use feature for this task', 'skill:feature'],
]) {
test('an old policy ' + routingPolicyVersion + ' empty selection is reconsidered after policy changes', () => withFixture(repoRoot => {
const first = resolve(repoRoot, { query });
const { digestObject } = require('../../scripts/lib/context-profile-support');
const { compileContextProfile } = require('../../scripts/lib/context-profiles');
const plan = compileContextProfile({ repoRoot, selectionMode: 'auto' });
const bindingDigest = digestObject({ sessionId: 'session-1', taskId: 'task-1', revision: 1,
phase: 'implement', planDigest: plan.planDigest, routingPolicyVersion,
triggersDigest: digestObject({}), queryDigest: digestObject(query) });
const { receiptDigest: _receiptDigest, autoSelection: _autoSelection, ...receipt } = first.receipt;
const oldReceipt = { ...receipt, bindingDigest, decision: 'none', selectedIds: [], explicitIds: [],
loadedIds: [], resources: [], selectionDigest: digestObject({ bindingDigest, selectedIds: [], explicitIds: [] }) };
const previous = { ...oldReceipt, receiptDigest: digestObject(oldReceipt) };
const result = resolve(repoRoot, { query }, { previous, load: true });
assert.equal(result.reused, false);
assert.notEqual(result.receipt.bindingDigest, previous.bindingDigest);
assert.deepEqual(result.selectedIds, [expectedId]);
assert.deepEqual(result.loadedIds, [expectedId]);
assert.equal(result.reason, 'auto-selection');
}));
}
for (const query of [
'Should we use feature? Use feature.',
'Use feature. Should we use feature?',
'Use feature, should we use feature?',
'Use feature. Shall we use feature?',
'Use feature, shall we use shared guidance?',
'Shall we use shared guidance? Use feature.',
'The docs say use feature. Use feature.',
'Use feature. The docs say use feature.',
'Use feature. The spec states do not use feature and use shared guidance.',
'Use feature. Use the warning that says never use feature.',
'The spec states do not use feature and use shared guidance. Use feature.',
'The phrase "Use feature." is an example. Use feature.',
'The docs say "Use feature." Use feature.',
'The docs say "Should we use feature?" Use feature.',
`Use feature. The docs say "don’t use feature."`,
'Use feature. The phrase "Use feature." is an example.',
"Don't use feature. Use feature.",
"Don't use feature, use feature.",
"Don't use feature and use feature.",
"Don't use feature but use feature.",
'The docs say never use feature. Use feature.',
'Use feature. The docs say never use feature.',
'Use feature to inspect the essay.',
'Use feature. For example, use shared guidance.',
'For example, use shared guidance. Use feature.',
"Use feature. For example, don't use feature.",
'Use feature to build an example.',
'Fix the example, use feature.',
'Fix e.g.js, use feature.',
"Use feature. E.g., don't use feature.",
'For example, use feature, but use feature for this task',
'For example, use shared guidance, but please use feature for this task.',
'For instance, use shared guidance but use feature for this task.',
'For example use shared guidance, but use feature for this task.',
'For example, use shared guidance, but use feature for the current task.',
'For example, use shared guidance, but use feature for this task, for instance, do not use feature.',
'For example, the docs say use shared guidance, but use feature for this task.',
]) {
test('a genuine directive survives surrounding discussion: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.loadedIds, ['skill:feature']);
assert.equal(result.reason, 'auto-selection');
assert.equal(result.receipt.autoSelection.exact, true);
}));
}
for (const query of [
"Use feature. Don't use feature.",
'Use feature. Avoid feature.',
'Use feature. You should not use feature.',
'Use feature. You shouldn\u2019t use feature.',
'Use feature, no feature.',
'Use feature. For this task, do not use feature.',
"Use feature, don't use feature.",
"Use feature and don't use feature.",
"Use feature but don't use feature.",
'Use feature. Never use feature.',
"Use feature. Don't use feature. Should we use feature?",
'Use feature. For example, use shared guidance, but do not use feature for this task.',
'For example, use shared guidance, but use feature for this task and do not use feature.',
]) {
test('a later rejection withdraws an earlier directive: ' + query, () => withFixture(repoRoot => {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.loadedIds, []);
assert.equal(result.fallback, null);
}));
}
test('a later grouped rejection withdraws a previously requested skill', () => withFixture(repoRoot => {
for (const query of ["Use shared. Don't use feature and shared guidance.",
'Use shared. Do not use feature and our shared guidance.',
'Use shared. Do not use feature and the existing shared guidance.',
'Use shared. Do not use feature, manual, and shared guidance.',
"Use shared. Don't use feature and the shared skill."]) {
const result = resolve(repoRoot, { query }, { load: true });
assert.deepEqual(result.loadedIds, [], query);
assert.equal(result.fallback, null);
}
}));
for (const prefix of [
"Don't use the database migration skill.",
'Don\u2019t use the database migration skill.',
'Never use database migration.',
'Should we use the database migration skill?',
'The docs say use the database migration skill.',
]) {
test('singular named references cannot bypass citation review: ' + prefix, () => {
const query = prefix + ' Review a PostgreSQL migration that adds an indexed nullable column without downtime.';
const result = resolveTaskContext({ task: task({ query }), load: true });
const candidate = result.candidates.find(value => value.id === 'skill:database-migrations');
assert.ok(candidate && candidate.bm25 >= 20, 'Exercise the BM25 auto-admission threshold');
assert.equal(candidate.exact, false, 'Exercise the non-exact candidate path');
assert.deepEqual(result.loadedIds, []);
assert.equal(result.fallback, null);
});
}
test('singular forms do not create exact directive admission', () => {
const result = resolveTaskContext({ task: task({ query: 'Use the database migration skill.' }), load: true });
assert.ok(result.candidates.some(candidate => candidate.id === 'skill:database-migrations'));
assert.ok(!result.receipt.autoSelection || !result.receipt.autoSelection.exact);
});