Author a Stack Pack
Purpose #
Walk through adding a minimal stack pack — manifest, detect, suggestSkills, suggestMCPs, suggestValidationCommands — with a conformance test that passes.
Big Picture #
Follow the contract at .docs/docs/specs/stack-pack-contract.md and use packages/stack-pack-node-ts/ as the canonical reference implementation. The cardinal rule: never emit files directly — return data; the adapter emits.
This tutorial uses a hypothetical elixir-phoenix pack as the example. Replace the name with whatever you're actually building.
How It Works #
1. Scaffold the package directory #
mkdir -p packages/stack-pack-elixir-phoenix/src
cd packages/stack-pack-elixir-phoenix
Copy package.json, tsconfig.json, and the scripts/copy-files.mjs script from packages/stack-pack-node-ts/ and adapt the name. Add it to the pnpm-workspace.yaml (already covers packages/*).
2. Write the manifest #
packages/stack-pack-elixir-phoenix/src/manifest.ts:
import type { StackPackManifest } from '@aidokit/core';
export const manifest: StackPackManifest = {
name: 'elixir-phoenix',
displayName: 'Elixir + Phoenix',
languages: ['elixir'],
specVersion: '2.0',
sdkVersion: 'workspace:*',
conformance: 'standard',
packVersion: '0.1.0',
maintainer: { name: 'your name' },
detectionSignals: {
files: ['mix.exs'],
manifestDeps: ['phoenix'],
},
composesWith: [],
};
3. Write detect #
src/detect.ts:
import type { DetectContext, DetectionResult } from '@aidokit/core';
export async function detect(ctx: DetectContext): Promise<DetectionResult> {
const hasMixExs = await ctx.fileExists('mix.exs');
if (!hasMixExs) return { matched: false, confidence: 'none' };
const mix = await ctx.readFile('mix.exs');
const hasPhoenix = /(:phoenix,|:phoenix\s*,)/.test(mix);
if (hasPhoenix) return { matched: true, confidence: 'high', reason: 'mix.exs declares :phoenix' };
return { matched: true, confidence: 'medium', reason: 'mix.exs present, no :phoenix declared' };
}
Rules (stack-pack-contract §8.1):
- Use only
DetectContextmethods. - Never throw on missing files — return
matched: false, confidence: 'none'. confidence: 'high'requires at least two independent signals.
4. Write suggestSkills #
src/skills.ts:
import type { ProjectContext, SkillTemplate } from '@aidokit/core';
export function suggestSkills(_ctx: ProjectContext): SkillTemplate[] {
return [
{
id: 'phoenix-conventions',
name: 'Phoenix conventions',
source: 'stack-pack',
sourcePackId: 'elixir-phoenix',
content: `# Phoenix conventions
## Contexts <REVIEW REQUIRED>
This project organises business logic into Phoenix contexts under \`lib/<app>/\`.
...
`,
autoLoadTriggers: ['lib/**/*.ex'],
preloadedBy: ['builder', 'tester-reviewer'],
required: false,
},
// ... 2-7 more skills
];
}
Rules (stack-pack-contract §11):
source: 'stack-pack',sourcePackIdmatchesmanifest.name.- Mark opinionated assertions with
<REVIEW REQUIRED>. - 3–8 skills (more suggests the pack should split).
- No hardcoded paths, no credentials, no adapter-specific markup.
- IDs cannot collide with core base skills or other active packs.
5. Write suggestMCPs and suggestValidationCommands #
// src/mcps.ts
export function suggestMCPs(_ctx: ProjectContext): string[] {
return ['context7'];
}
// src/validation.ts
import type { ProjectContext, ValidationCommand } from '@aidokit/core';
export function suggestValidationCommands(_ctx: ProjectContext): ValidationCommand[] {
return [
{ id: 'lint', label: 'Credo', command: 'mix credo', kind: 'lint', ciSafe: true, speed: 'fast' },
{ id: 'typecheck', label: 'Dialyzer', command: 'mix dialyzer', kind: 'typecheck', ciSafe: true, speed: 'slow' },
{ id: 'test', label: 'ExUnit', command: 'mix test', kind: 'test', ciSafe: true, speed: 'medium' },
];
}
Rules: ids exist in the catalog (mcp-catalog reference); commands suitable for CI (ciSafe: true when deterministic); use the project's own scripts where possible (consume ctx.parsedManifests per ADR-0014).
6. Compose into the export #
src/index.ts:
import type { StackPack } from '@aidokit/core';
import { manifest } from './manifest.js';
import { detect } from './detect.js';
import { suggestSkills } from './skills.js';
import { suggestMCPs } from './mcps.js';
import { suggestValidationCommands } from './validation.js';
export const stackPack: StackPack = {
manifest,
detect,
suggestSkills,
suggestMCPs,
suggestValidationCommands,
};
7. Write the conformance test #
test/conformance.test.ts:
import { test, expect } from 'vitest';
import { runStackPackConformance } from '@aidokit/core';
import { stackPack } from '../src/index.js';
test(`${stackPack.manifest.name} conforms to declared level`, async () => {
const report = await runStackPackConformance(stackPack, stackPack.manifest.conformance);
if (report.overall !== 'pass') {
console.error(JSON.stringify(report.results.filter(r => r.status === 'fail'), null, 2));
}
expect(report.overall).toBe('pass');
});
8. Register and run #
Add a Vitest unit test for detect against synthetic fixtures, then:
pnpm --filter @aidokit/stack-pack-elixir-phoenix build
pnpm --filter @aidokit/stack-pack-elixir-phoenix test
9. Add a changeset #
pnpm changeset
# choose: new minor for @aidokit/stack-pack-elixir-phoenix
10. CLI wiring (if first-party) #
If you intend this pack to be detected automatically by aidokit init, register it in the CLI's static extension table (packages/cli/src/init/selections.ts and adjacent — see how @aidokit/stack-pack-node-ts is wired). Until v2.0 brings runtime discovery, extensions are statically imported.
Example: a minimal detect-only Minimum pack #
If your stack is niche and you only want detection + a single skill, declare conformance: 'minimum' and skip suggestMCPs / suggestValidationCommands (return []). The harness will only check Minimum-level requirements.
Common Mistakes #
- Calling
child_processindetect—stack-pack.detect.no-shell-callswill fail. - Hardcoded absolute paths in skill content —
stack-pack.skills.no-hard-coded-pathswarns. - Suggesting
filesystemor anothersecuritySensitive: trueMCP —stack-pack.mcp.no-sensitive-suggestionsfails. - Defining a new MCP inline — MCPs come from the catalog. PR a catalog entry first.
- Skill content that asserts conventions without
<REVIEW REQUIRED>—stack-pack.skills.has-review-markersis heuristic but will flag.
Checklist #
- [ ] Manifest declares level + spec version + languages.
- [ ]
detectreturns categorical confidence; handles missing files; uses onlyDetectContext. - [ ] ≥1
SkillTemplatewithsource: 'stack-pack',sourcePackIdset, opinionated content marked. - [ ]
suggestMCPsreturns catalog ids only; no sensitive entries. - [ ]
suggestValidationCommandsare well-formed withciSafe. - [ ] Conformance test passes in CI.
- [ ] Changeset filed.
- [ ] Co-located unit tests cover detect against synthetic project trees.