Skip to content
Draft
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
7 changes: 7 additions & 0 deletions scripts/lib/skill-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,13 @@ async function generateSkill({
}
}

// Copy a sibling checks.json from the skill source dir to the skill root, if present.
// The audit runner reads `.claude/skills/<id>/checks.json` to enroll discoverable specialists' checks via audit_add_checks.
const sourceChecksFile = path.join(configDir, 'skills', ...skill._group.split('/'), 'checks.json');
if (fs.existsSync(sourceChecksFile)) {
fs.copyFileSync(sourceChecksFile, path.join(skillDir, 'checks.json'));
}

// Copy local markdown references from a source references/ directory, if present.
// Group config injects a shared `preamble`; per-file `next_step` frontmatter drives continuation links.
const sourceReferencesDir = path.join(configDir, 'skills', ...skill._group.split('/'), 'references');
Expand Down
44 changes: 44 additions & 0 deletions scripts/lib/tests/skill-config-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,50 @@ describe('loadSkillsConfig', () => {
expect(Object.keys(config)).toEqual(['has-config']);
});

it('exposes audit subagents as separate skills alongside the runner', () => {
createFixture({
skills: {
audit: {
'config.yaml': yaml.dump({
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'PostHog audit' }],
}),
subagents: {
identification: {
'config.yaml': yaml.dump({
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — identification' }],
}),
},
'event-capture': {
'config.yaml': yaml.dump({
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — event capture' }],
}),
},
'web-analytics': {
'config.yaml': yaml.dump({
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — web analytics' }],
}),
},
},
},
},
}, tmpDir);
const config = loadSkillsConfig(tmpDir);
expect(Object.keys(config).sort()).toEqual([
'audit',
'audit/subagents/event-capture',
'audit/subagents/identification',
'audit/subagents/web-analytics',
]);
});

it('handles flat and nested siblings', () => {
createFixture({
skills: {
Expand Down
78 changes: 78 additions & 0 deletions scripts/lib/tests/skill-generator-references-folder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,82 @@ describe('generateSkill local references', () => {
expect(readFileSync(generatedRef, 'utf8')).toBe('# Product analytics best practices\n\nDetails');
expect(readFileSync(generatedSkill, 'utf8')).toContain('references/product-analytics.md');
});

it('copies a sibling checks.json into the generated skill root', async () => {
const checksContent = JSON.stringify([
{ id: 'sample-check', area: 'Sample', label: 'Sample check' },
], null, 2);
createFixture({
skills: {
'audit-subagent': {
'description.md': '# {display_name}',
'checks.json': checksContent,
},
},
}, tmpDir);

const config = {
'audit-subagent': {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit subagent' }],
},
};

const skill = expandSkillGroups(config, tmpDir)[0];
const outputDir = join(tmpDir, 'out');

await generateSkill({
skill,
version: 'test',
repoRoot: tmpDir,
configDir: tmpDir,
outputDir,
skipPatterns: { global: [], examples: {} },
commandmentsConfig: { commandments: {} },
skillTemplate: skill._template,
sharedDocs: skill._sharedDocs || [],
workflows: [],
});

const generatedChecks = join(outputDir, 'audit-subagent', 'checks.json');
expect(existsSync(generatedChecks)).toBe(true);
expect(readFileSync(generatedChecks, 'utf8')).toBe(checksContent);
});

it('omits checks.json when source skill has none', async () => {
createFixture({
skills: {
integration: {
'description.md': '# {display_name}',
},
},
}, tmpDir);

const config = {
integration: {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Integration' }],
},
};

const skill = expandSkillGroups(config, tmpDir)[0];
const outputDir = join(tmpDir, 'out');

await generateSkill({
skill,
version: 'test',
repoRoot: tmpDir,
configDir: tmpDir,
outputDir,
skipPatterns: { global: [], examples: {} },
commandmentsConfig: { commandments: {} },
skillTemplate: skill._template,
sharedDocs: skill._sharedDocs || [],
workflows: [],
});

expect(existsSync(join(outputDir, 'integration', 'checks.json'))).toBe(false);
});
});
51 changes: 51 additions & 0 deletions scripts/lib/tests/skill-group-expander.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,57 @@ describe('expandSkillGroups', () => {
expect(skills[0]._examplePaths).toEqual(['basics/django']);
});

it('produces audit + audit-subagents-* ids for the runner+specialists layout', () => {
createFixture({
skills: {
audit: {
'description.md': '# {display_name}',
subagents: {
identification: { 'description.md': '# {display_name}' },
'event-capture': { 'description.md': '# {display_name}' },
'web-analytics': { 'description.md': '# {display_name}' },
},
},
},
}, tmpDir);
const config = {
audit: {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'PostHog audit' }],
},
'audit/subagents/identification': {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — identification' }],
},
'audit/subagents/event-capture': {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — event capture' }],
},
'audit/subagents/web-analytics': {
type: 'docs-only',
template: 'description.md',
variants: [{ id: 'all', display_name: 'Audit — web analytics' }],
},
};
const skills = expandSkillGroups(config, tmpDir);
expect(skills.map(s => s.id).sort()).toEqual([
'audit',
'audit-subagents-event-capture',
'audit-subagents-identification',
'audit-subagents-web-analytics',
]);
const runner = skills.find(s => s.id === 'audit');
expect(runner._category).toBe('audit');
expect(runner._topic).toBeNull();
const ident = skills.find(s => s.id === 'audit-subagents-identification');
expect(ident._category).toBe('audit');
expect(ident._topic).toBe('subagents/identification');
expect(ident._group).toBe('audit/subagents/identification');
});

it('defaults _examplePaths to empty array when not specified', () => {
createFixture({
skills: {
Expand Down
2 changes: 1 addition & 1 deletion transformation-config/skills/audit/config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type: docs-only
template: description.md
description: Audit an existing PostHog integration for correctness and best practices
description: Audit an existing PostHog integration for correctness and best practices, dispatching specialist subagents in parallel
tags: [best-practices]
references:
preamble: "**Read ONLY this file.** Do not read any other reference file until this one tells you to."
Expand Down
58 changes: 48 additions & 10 deletions transformation-config/skills/audit/description.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,57 @@
# PostHog Audit

This skill audits an existing PostHog integration for **data integrity** in event capture and identification. **Read-only** — the only file you create is the final audit report.
This skill audits an existing PostHog integration for **data integrity** in installation, identification, event capture, and (when applicable) deeper areas like web analytics, feature flags, experiments, LLM analytics, and error tracking. **Read-only** — the only file you create is the final audit report.

Perform the checks described in the referenced skills and only the events referenced in the skills.
Perform the checks described in the referenced step files and only the events referenced there.

## Workflow

The audit runs as a 5-step chain: Installation (SDK + version) → init correctness → identification → event capture → report. Each step file ends with a pointer to the next. Follow them in the order they are written. You must resolve them in order before any source-tree exploration.
The audit runs as a 6-step chain:

The audit ledger is already seeded with the 10 pending checks. Use `mcp__wizard-tools__audit_resolve_checks` to patch each one as you finish it.
1. SDK + version (`references/1-version.md`)
2. Init correctness (`references/2-init.md`)
3. Identification — dispatches `audit-subagents-identification`, waits for it to resolve all 4 identification checks (`references/3-identification.md`)
4. Event capture — dispatches `audit-subagents-event-capture`, waits for it to resolve all 3 event-capture checks (`references/4-event-capture.md`)
5. Dispatch agent + discoverable specialists — the dispatch agent picks which discoverable specialists to run, then the runner enrolls their checks via `audit_add_checks` and fans them out (`references/5-discoverable-dispatch.md`)
6. Read ledger + write report (`references/6-report.md`)

Each step file ends with a pointer to the next. Follow them in the order they are written. Resolve each step's checks before moving to the next. Identification (Step 3) must be fully resolved before Event Capture (Step 4) is dispatched, and both must be fully resolved before the dispatch agent runs.

The audit ledger is seeded by the wizard with 10 pending checks (3 install/init + 4 identification + 3 event-capture). Step 5 may add more checks via `audit_add_checks` for discoverable specialists. Use `mcp__wizard-tools__audit_resolve_checks` to patch each check as it's evaluated. Specialists you dispatch own their own checks — they call `audit_resolve_checks` themselves; you do not patch on their behalf.

**Start by reading the path relative to this file at `references/1-version.md`.** Do not Glob, ls, or find the skill directory. Do not preload future steps. Do not re-read a step file once you've moved past it. Do not re-read SKILL.md.

`ToolSearch` is only for loading a tool by exact name when the SDK has it deferred (e.g. `select:Grep`). Do **not** use it to browse for other tools — every tool the audit needs (`Glob`, `Grep`, `Read`, `Write`, `Bash`, and the named `mcp__wizard-tools__audit_*` tools) is already named in this skill.
`ToolSearch` is only for loading a tool by exact name when the SDK has it deferred (e.g. `select:Grep`). Do **not** use it to browse for other tools — every tool the audit needs (`Glob`, `Grep`, `Read`, `Write`, `Bash`, `Task`, and the named `mcp__wizard-tools__*` tools) is already named in this skill or its step files.

**Do not call `TodoWrite`.** The audit doesn't track its own task list — progress comes from the audit ledger plus `[STATUS]` lines.

## Specialist registries

### Always-on (Steps 3 + 4, pre-seeded by wizard)

| Specialist | Step | Skill ID |
|---|---|---|
| identification | 3 | `audit-subagents-identification` |
| event-capture | 4 | `audit-subagents-event-capture` |

### Discoverable (Step 5, gated by the dispatch agent)

| Area | Skill ID |
|---|---|
| Web Analytics | `audit-subagents-web-analytics` |
| Feature Flags | `audit-subagents-feature-flags` |
| Experiments | `audit-subagents-experiments` |
| LLM Analytics | `audit-subagents-llm-analytics` |
| Error Tracking | `audit-subagents-error-tracking` |

The dispatch agent itself: `audit-subagents-dispatch`.

### Selection overrides

The wizard's `--only` and `--skip` flags inject a "Specialist selection (override defaults):" block into this prompt:
- `--skip` removes specific basic specialists from Step 3 or Step 4 (their pre-seeded checks must still be patched — mirror them as `{ status: "warning", details: "skipped: suppressed by --skip" }` in that step).
- `--only=identification,event-capture` (or any selection block that excludes the dispatch agent) **suppresses Step 5 entirely** — no dispatch agent spawn, no second wave.

## Live activity — `[STATUS]`

The "Working on …" banner reads from `[STATUS]` lines you emit in plain text. Whenever you start a new sub-step, write a line like:
Expand All @@ -30,14 +66,15 @@ The wizard intercepts these and updates the spinner. Use them freely — they ar

The ledger lives at `.posthog-audit-checks.json` and is rendered live in the "Audit plan" tab. It is owned by MCP tools — **never `Write` this file directly**:

- `mcp__wizard-tools__audit_resolve_checks({ updates })` — patch one or more checks by `id`. Each `update` is `{ id, status, file?, details? }`. Batch updates from the same step into a single call.
- `mcp__wizard-tools__audit_resolve_checks({ updates })` — patch one or more checks by `id`. Each `update` is `{ id, status, file?, details? }`. Batch updates from the same step into a single call where possible.
- `mcp__wizard-tools__audit_add_checks(<checks>)` — Step 4 only, to enroll discoverable specialists' checks before dispatching them.

All audit ledger calls are atomic and serialize internally — **concurrent calls from parallel subagents cannot lose updates**, so feel free to fan out runtime checks across `Task` subagents when a step says so.
All audit ledger calls are atomic and serialize internally — **concurrent calls from parallel subagents cannot lose updates**, so feel free to fan out runtime checks across `Task` subagents.

### Check entry shape

- `id` — stable kebab-case slug. Reuse the existing seeded ids exactly when calling `audit_resolve_checks`.
- `area` — short group name. The current core workflow uses `Installation`, `Identification`, and `Event Capture`.
- `area` — short group name. The current core workflow uses `Installation`, `Identification`, `Event Capture`, plus whatever areas Step 4 adds (`Web Analytics`, `Feature Flags`, etc.).
- `label` — short human name.
- `status` — `pending` | `pass` | `error` | `warning` | `suggestion`.
- `file` — optional `path:line` for findings tied to a location.
Expand All @@ -54,13 +91,14 @@ After the report is written (Step 5), delete `.posthog-audit-checks.json`.
## Key principles

- **Read-only**: Do not edit project source files. The only file you create is the audit report.
- **Evidence-based**: Reference specific `file:line` for every non-pass finding.
- **Evidence-based**: Reference specific `file:line` for every non-pass finding (or hosts/evidence for query-driven specialists).
- **Actionable**: Every finding states what to fix and how.
- **One report**: Specialists do not write reports of their own. They write to the ledger, the runner reads the ledger and writes the single report in Step 5.

## Abort statuses

Report abort states with `[ABORT]` prefixed messages. The wizard catches these and terminates the run — do not halt yourself.
- No PostHog SDK found
- `[ABORT] No PostHog SDK found`

## Framework guidelines

Expand Down
Loading
Loading