aidokitwiki

Author a Stack Pack

Audience-Contributor Status-Shipped v0.5 Spec-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):

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):

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 #

Checklist #