Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ function trimText(text, max = 220) {
function getSkillId(agentId) {
const id = String(agentId || '').trim();
if (id.startsWith('aiox-')) return id;
if (id.startsWith('aios-')) return id.replace(/^aios-/, 'aiox-');
return `aiox-${id}`;
}

function getLegacySkillId(agentId) {
const id = String(agentId || '').trim();
if (!id) return 'aios-unknown';
if (id.startsWith('aios-')) return id;
if (id.startsWith('aiox-')) return id.replace(/^aiox-/, 'aios-');
return `aios-${id}`;
}
Expand Down
177 changes: 176 additions & 1 deletion .aiox-core/infrastructure/scripts/codex-skills-sync/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,141 @@ function isGeneratedSquadSkill(content, projectRoot) {
return fs.existsSync(path.join(projectRoot, sourcePath));
}

function readTextIfExists(filePath) {
try {
return fs.readFileSync(filePath, 'utf8');
} catch (_error) {
return '';
}
}

function hasFullActivationPayload(content) {
const value = String(content || '');
if (!value.trim()) {
return false;
}

const frontmatter = parseSkillFrontmatter(value);
if (frontmatter.error) {
return false;
}

const canonicalAgentPath = extractCanonicalAgentPath(value);
if (!canonicalAgentPath) {
return false;
}

return (
value.includes('## Activation Protocol') ||
value.includes('generate-greeting.js') ||
value.includes('source of truth')
);
}

function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function hasLegacyAliasIntent(content, canonicalSkillId) {
const value = String(content || '');
const normalized = value.toLowerCase();
return (
value.includes('AIOX-CODEX-LEGACY-ALIAS') ||
(
normalized.includes('legacy alias') &&
normalized.includes('canonical') &&
value.includes(canonicalSkillId)
)
);
}

function getThinLegacyRedirectIssues(content, canonicalSkillId, legacySkillId) {
const lines = String(content || '')
.split(/\r?\n/)
.map(line => line.trim())
.filter(Boolean);
const canonical = escapeRegExp(canonicalSkillId);
const legacy = escapeRegExp(legacySkillId);
const allowedLinePatterns = [
/^<!--\s*AIOX-CODEX-LEGACY-ALIAS:\s*redirect\s*-->$/,
new RegExp(`^#\\s+${legacy}$`),
new RegExp(`^This legacy alias redirects to canonical skill \`${canonical}\`\\.$`),
new RegExp(`^Use \`\\$?${canonical}\` instead\\.$`),
];
const issues = [];

if (!lines.some(line => /^<!--\s*AIOX-CODEX-LEGACY-ALIAS:\s*redirect\s*-->$/.test(line))) {
issues.push('missing exact legacy redirect marker');
}

if (!lines.some(line => new RegExp(`^This legacy alias redirects to canonical skill \`${canonical}\`\\.$`).test(line))) {
issues.push(`missing exact canonical redirect sentence for "${canonicalSkillId}"`);
}

const invalidLines = lines.filter(line => !allowedLinePatterns.some(pattern => pattern.test(line)));
if (invalidLines.length > 0) {
issues.push('contains non-redirect content');
}

return issues;
}

function isIntentionalLegacyAlias(content, canonicalSkillId, legacySkillId) {
return getThinLegacyRedirectIssues(content, canonicalSkillId, legacySkillId).length === 0;
}

function classifyLegacySkillAlias(content, item, relativeSkillPath) {
if (!String(content || '').trim()) {
return {
dir: item.legacySkillId,
canonicalSkillId: item.skillId,
classification: 'missing-skill-file',
fatal: true,
message: `Legacy skill alias directory missing SKILL.md: ${relativeSkillPath}`,
};
}

if (hasFullActivationPayload(content)) {
const canonicalAgentPath = extractCanonicalAgentPath(content);
return {
dir: item.legacySkillId,
canonicalSkillId: item.skillId,
classification: 'duplicate-full-payload',
fatal: true,
message: `Legacy skill alias duplicates full activation payload: ${relativeSkillPath} -> ${item.skillId} (${canonicalAgentPath})`,
};
}

if (isIntentionalLegacyAlias(content, item.skillId, item.legacySkillId)) {
return {
dir: item.legacySkillId,
canonicalSkillId: item.skillId,
classification: 'intentional-redirect',
fatal: false,
message: `Intentional legacy skill alias directory: ${relativeSkillPath} -> ${item.skillId}`,
};
}

if (hasLegacyAliasIntent(content, item.skillId)) {
const issues = getThinLegacyRedirectIssues(content, item.skillId, item.legacySkillId);
return {
dir: item.legacySkillId,
canonicalSkillId: item.skillId,
classification: 'non-thin-legacy-alias',
fatal: true,
message: `Legacy skill alias is not a thin redirect: ${relativeSkillPath} -> ${item.skillId} (${issues.join('; ')})`,
};
}

return {
dir: item.legacySkillId,
canonicalSkillId: item.skillId,
classification: 'unclassified-legacy-alias',
fatal: true,
message: `Unclassified legacy skill alias directory: ${relativeSkillPath}`,
};
}

function validateCodexSkills(options = {}) {
const defaults = getDefaultOptions();
const projectRoot = options.projectRoot || defaults.projectRoot;
Expand All @@ -270,6 +405,8 @@ function validateCodexSkills(options = {}) {
missing: [],
orphaned: [],
legacy: [],
legacyAliases: [],
duplicatePayloads: [],
ignored: [],
selfTests: [],
};
Expand Down Expand Up @@ -307,17 +444,34 @@ function validateCodexSkills(options = {}) {

const expectedIds = new Set(expected.map(item => item.skillId));
const legacyIds = new Set(expected.map(item => item.legacySkillId));
const expectedByLegacyId = new Map(expected.map(item => [item.legacySkillId, item]));
const expectedByCanonicalPath = new Map(expected.map(item => [
`.aiox-core/development/agents/${item.filename}`,
item,
]));
const orphaned = [];
const legacy = [];
const legacyAliases = [];
const duplicatePayloads = [];
const ignored = [];
if (resolved.strict) {
const dirs = fs.readdirSync(resolved.skillsDir, { withFileTypes: true })
.filter(entry => entry.isDirectory() && (entry.name.startsWith('aiox-') || entry.name.startsWith('aios-')))
.map(entry => entry.name);
for (const dir of dirs) {
if (legacyIds.has(dir)) {
const item = expectedByLegacyId.get(dir);
const skillPath = path.join(resolved.skillsDir, dir, 'SKILL.md');
const relativeSkillPath = path.join(path.relative(resolved.projectRoot, resolved.skillsDir), dir, 'SKILL.md');
const classification = classifyLegacySkillAlias(readTextIfExists(skillPath), item, relativeSkillPath);

legacy.push(dir);
errors.push(`Legacy skill alias directory: ${path.join(path.relative(resolved.projectRoot, resolved.skillsDir), dir)}`);
legacyAliases.push(classification);
if (classification.fatal) {
errors.push(classification.message);
} else {
warnings.push(classification.message);
}
continue;
}
if (dir.startsWith('aiox-') && !expectedIds.has(dir)) {
Expand All @@ -332,6 +486,20 @@ function validateCodexSkills(options = {}) {
content = '';
}

const canonicalAgentPath = extractCanonicalAgentPath(content);
const duplicateOf = canonicalAgentPath ? expectedByCanonicalPath.get(canonicalAgentPath) : null;
if (duplicateOf && hasFullActivationPayload(content)) {
duplicatePayloads.push({
dir,
canonicalSkillId: duplicateOf.skillId,
canonicalAgentPath,
});
errors.push(
`Duplicate full skill payload: ${path.join(path.relative(resolved.projectRoot, resolved.skillsDir), dir, 'SKILL.md')} -> ${duplicateOf.skillId} (${canonicalAgentPath})`,
);
continue;
}

if (isGeneratedSquadSkill(content, resolved.projectRoot)) {
ignored.push(dir);
continue;
Expand Down Expand Up @@ -370,6 +538,8 @@ function validateCodexSkills(options = {}) {
missing,
orphaned,
legacy,
legacyAliases,
duplicatePayloads,
ignored,
selfTests,
};
Expand Down Expand Up @@ -424,6 +594,11 @@ module.exports = {
runSkillSelfTests,
extractGeneratedSquadSource,
isGeneratedSquadSkill,
hasFullActivationPayload,
hasLegacyAliasIntent,
getThinLegacyRedirectIssues,
isIntentionalLegacyAlias,
classifyLegacySkillAlias,
parseArgs,
getDefaultOptions,
};
10 changes: 5 additions & 5 deletions .aiox-core/install-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - File types for categorization
#
version: 5.1.17
generated_at: "2026-05-09T09:07:47.281Z"
generated_at: "2026-05-10T01:14:38.554Z"
generator: scripts/generate-install-manifest.js
file_count: 1128
files:
Expand Down Expand Up @@ -3093,17 +3093,17 @@ files:
type: script
size: 22355
- path: infrastructure/scripts/codex-skills-sync/index.js
hash: sha256:caf8e4f6fad8d5b5c123bf47561a632bdb2cfacb5f3a80265de81ba0dea3f6b9
hash: sha256:2dc8637ba0095921e3cb5e99791dc05da61a12e375407b05f199696bcce7ea61
type: script
size: 5533
size: 5642
- path: infrastructure/scripts/codex-skills-sync/README.md
hash: sha256:2e4366ccc31b617a9be8a8b19c0379e1fd3ae64c4d5cfbb4d233860f9db0f949
type: script
size: 4072
- path: infrastructure/scripts/codex-skills-sync/validate.js
hash: sha256:5b8cb91ddcfda2b6b66328c12129e71665e4e1cb6f13b1792746e9d761d1ebcf
hash: sha256:6c8f98f49e433fc3aa648034457d5273a2ca667b8c7e492157a89ed6d0582c96
type: script
size: 12185
size: 17947
- path: infrastructure/scripts/collect-tool-usage.js
hash: sha256:8a739b79182dc41e28b7e02aeb9ec1dde5ec49f3ca534399acc59711b3b92bbf
type: script
Expand Down
4 changes: 2 additions & 2 deletions .codex/skills/aiox-master/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ Use when you need comprehensive expertise across all domains, framework componen
- `*kb` - Toggle KB mode (loads AIOX Method knowledge)
- `*status` - Show current context and progress
- `*guide` - Show comprehensive usage guide for this agent
- `*exit` - Exit agent mode
- `*create` - Create new AIOX component (agent, task, workflow, template, checklist)
- `*modify` - Modify existing AIOX component
- `*update-manifest` - Update team manifest
- `*task` - Execute specific task (or list available)
- `*workflow` - Start workflow (guided=manual, engine=real subagent spawning)

## Non-Negotiables
- Follow `.aiox-core/constitution.md`.
Expand Down
86 changes: 86 additions & 0 deletions docs/migration/PRO-14.5-legacy-slash-command-shim-retirement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# PRO-14.5 Legacy Slash-Command Shim Retirement Gate

Story: `STORY-PRO-14.5`
Date: 2026-05-09
Repo: `aiox-core`
Status: implementation gate

## Purpose

This document defines the retirement gate for legacy AIOS-era slash-command shims and `aios-*` skill aliases. It does not authorize deletion. It makes deletion possible later only after generated surfaces prove canonical AIOX skills are unique, validation blocks duplicate payloads, supported IDE projections pass, and the compatibility window is approved.

## Inventory

| Surface | Current classification | Evidence | Retirement posture |
|---|---|---|---|
| `.aiox-core/infrastructure/scripts/codex-skills-sync/index.js` | canonical payload generator + legacy id normalizer | Generates `aiox-*` skill ids and now normalizes `aios-*` inputs to `aiox-*`. | Keep. This is the source of canonical Codex skill ids. |
| `.aiox-core/infrastructure/scripts/codex-skills-sync/validate.js` | strict validator + legacy alias detector | Strict mode reports legacy aliases, rejects full duplicate payloads and rejects duplicate orphaned canonical payloads. | Keep as blocking gate before any alias removal. |
| `.codex/skills/` | generated projection | Observed 13 `aiox-*` skill dirs and 0 `aios-*` dirs in this worktree. | Canonical projection. Regenerate with `npm run sync:skills:codex`. |
| `.claude/commands/AIOX/agents/*.md` | legacy slash-command shims | 12 command files contain `ACORE-CLAUDE-AGENT-COMMAND: legacy-shim` and redirect to `.claude/skills/AIOX/agents/*/SKILL.md`. | Keep until usage cutoff or telemetry evidence approves removal. |
| `.claude/skills/AIOX/agents/*/SKILL.md` | canonical Claude skill payloads | 12 skill dirs observed. | Canonical Claude activation payloads. |
| `.claude/skills/AIOX/agents/aiox-master/SKILL.md` | canonical payload with current double-prefix frontmatter | Current generated frontmatter contains `name: aiox-aiox-master`. | Classify as existing contract; do not change without a separate focused compatibility story. |
| `.aiox-core/infrastructure/scripts/ide-sync/transformers/kimi.js` | canonical Kimi generator + legacy id normalizer | `aios-*` preferred aliases normalize to `aiox-*`; tests cover this. | Keep. |
| `.kimi/skills/` | generated projection | Observed 12 `aiox-*` skill dirs and 0 `aios-*` dirs. | Canonical projection. |
| `.aiox-core/infrastructure/scripts/ide-sync/gemini-commands.js` | canonical Gemini command generator | Generates `aiox-{slug}.toml` and `aiox-menu.toml`. | Keep. |
| `.gemini/commands/` | generated projection | Observed 13 `aiox-*` command files, including `aiox-menu.toml`, and 0 `aios-*` files. | Canonical projection. |
| `sinkra-hub/apps/gateway-ai/hooks/aios-command` | external legacy consumer | Existing package/hook name and logs still use `aios-command`. | Do not change in this story; open an owning-repo story before migration. |

## Blocking Rules

Strict Codex skill validation must fail when any of these conditions appears:

- a legacy `aios-*` directory contains a full activation payload instead of a classified redirect;
- an orphaned canonical `aiox-*` directory duplicates the full payload of a known source agent;
- an unexpected `aiox-*` generated skill directory is orphaned from source agents, unless it is an explicitly detected generated squad skill.

Strict validation may pass with an intentional legacy alias only when the alias is a thin redirect with the exact `AIOX-CODEX-LEGACY-ALIAS: redirect` marker and an explicit canonical redirect sentence. Any extra non-redirect content is fatal. Passing aliases are still reported as warnings so the migration remains visible.

## Removal Criteria

Legacy slash-command shims or `aios-*` alias dirs may only be removed after all criteria below are true:

1. `npm run sync:skills:codex` produces no unexpected generated diff.
2. `npm run validate:codex-skills:self-test` passes.
3. `npm run validate:codex-sync` and `npm run validate:codex-integration` pass.
4. `npm run sync:ide:check` passes for enabled IDE targets.
5. `npm run validate:gemini-sync` and `npm run validate:gemini-integration` pass.
6. The generated-surface inventory shows one canonical payload per supported agent per IDE surface.
7. Legacy shim usage is either measured as zero for one full minor release or covered by a documented support cutoff.
8. `@architect`, `@qa` and `@po` approve the removal story before deletion.

## Compatibility Window

Default window: keep legacy command shims and legacy alias recognition for at least one full minor release after this validation gate lands. If no telemetry exists for a specific shim surface, the removal story must provide a dated support cutoff and rollback plan before deletion.

## Validation Evidence

Validated on 2026-05-09 from branch `feat/pro-14-5-legacy-shim-retirement`:

| Command | Result | Notes |
|---|---|---|
| `npm ci` | Pass | Installed dependencies cleanly; transient executable mode drift from install was reverted before commit. |
| `npm run sync:skills:codex` | Pass | Generated 12 Codex skills. |
| `npm run validate:codex-skills:self-test` | Pass | 12 skills checked; 12 self-tests passed. |
| `npm run validate:codex-sync` | Pass | expected 12, synced 12, missing 0, drift 0, orphaned 0. |
| `npm run validate:codex-integration` | Pass with warning | Warns that generated squad skill `aiox-claude-mastery-chief` makes Codex skill count 13/12; existing known generated extra, not a duplicate legacy payload. |
| `npm run sync:ide:check` | Pass | expected 109, synced 109, missing 0, drift 0, orphaned 0. |
| `npm run validate:gemini-sync` | Pass | expected 25, synced 25, missing 0, drift 0, orphaned 0. |
| `npm run validate:gemini-integration` | Pass with warning | Warns `.gemini/rules.md` is not present; existing integration warning. |
| `npm run lint` | Pass | Re-run after full tests released temporary fixture state. |
| `npm run typecheck` | Pass | No TypeScript errors. |
| `npm test -- --runInBand --forceExit` | Pass | 341 suites passed, 12 skipped; 8433 tests passed, 172 skipped. `--forceExit` used because the exact command kept open handles after completion. |
| `npm run build` | Unavailable | Package has no `build` script; `npm run validate:publish` was run as the package publication gate instead. |
| `npm run validate:publish` | Pass | Package contents, dependency completeness and publish safety gate passed. |
| `git diff --check` | Pass | No whitespace errors. |
| `rg -n 'aios-\|aiox-\|slash-command\|aios-command\|legacy-shim' .aiox-core .claude .codex .gemini .kimi packages tests docs -S` | Pass | Inventory command completed; expected references are documented above. |

## Rollback

If removal later breaks activation, rollback is:

1. Restore `.claude/commands/AIOX/agents/*.md` from the previous generator output or git history.
2. Restore any removed legacy `aios-*` alias dirs as thin redirects, not full duplicated payloads.
3. Run `npm run sync:skills:codex`.
4. Run `npm run sync:ide`.
5. Run all validation commands listed in the removal story.
6. Publish a patch release only after validation passes.
Loading
Loading