P0.6.1 — Step 1 Audit

Inventory of the worktree against the task spec for P0.6.1 ε Skill Schema (Intelligence axis, first of three ε sub-tasks). Scope: the Zod schema that defines a Colibri skill frontmatter shape, plus a parser that reads the SKILL.md files already shipping in .agents/skills/colibri-*/.

Baseline: worktree E:/AMS/.worktrees/claude/p0-6-1-skill-schema/ at commit 3ebbe419 (P0.2.2 SQLite init merged as PR #122, on top of Wave A α server bootstrap at 40cd679d).


§1. Surface being added

Targets this task creates:

  • src/domains/skills/schema.ts — new module. Holds the Zod schema for the SKILL.md frontmatter (SkillFrontmatterSchema), a derived TypeScript type (SkillFrontmatter), a parser function (parseSkillFile(path) → ParsedSkill), and supporting constants (kebab-case regex, capability enum, greek-letter set). Does not yet exist.
  • src/__tests__/skill-schema.test.ts — new test file, co-located with other Phase-0 tests per the Wave A lock (Jest roots: ['<rootDir>/src']). The task spec lists tests/domains/skills/schema.test.ts; this is overridden — see §4b.
  • src/domains/skills/ directory — new directory root. index.ts barrel not in scope (P0.6.2 owns the module wiring).

A worktree scan confirms absence of the authoring targets:

  • ls src/domains/ → “No such file or directory”
  • grep -rn "SkillFrontmatter\|parseSkillFile\|kebab-case\|capabilities.*read.*write" src/ → zero matches
  • No existing import path touches .agents/skills/*/SKILL.md from TypeScript.

This is a greenfield module set. P0.6.1 authors two files (schema + test) under a brand-new src/domains/ tree.


§2. The existing SKILL.md corpus (fixture for the primary acceptance test)

2a. File count

Task prompt claims “22 existing SKILL.md files”. Actual count on 3ebbe419 is 21. See reference_skills_inventory.md in MEMORY for the pre-existing “22 canonical skills” figure — that figure tracked 22 including colibri-memory-context/ which still exists, but one of the other colibri-* directories cited in CLAUDE.md §9.2 never had an individual SKILL.md (ams-* redirect stubs under .claude/skills/ are MIRROR-zone and not part of .agents/skills/).

Enumerating .agents/skills/colibri-*/SKILL.md:

  1. .agents/skills/colibri-audit-memory/SKILL.md
  2. .agents/skills/colibri-audit-proof/SKILL.md
  3. .agents/skills/colibri-autonomous/SKILL.md
  4. .agents/skills/colibri-docs-check/SKILL.md
  5. .agents/skills/colibri-docs-sync/SKILL.md
  6. .agents/skills/colibri-executor/SKILL.md
  7. .agents/skills/colibri-greek-nav/SKILL.md
  8. .agents/skills/colibri-growth-strategy/SKILL.md
  9. .agents/skills/colibri-gsd/SKILL.md
  10. .agents/skills/colibri-gsd-execution/SKILL.md
  11. .agents/skills/colibri-mcp-server/SKILL.md
  12. .agents/skills/colibri-memory-context/SKILL.md
  13. .agents/skills/colibri-observability/SKILL.md
  14. .agents/skills/colibri-obsidian-integration/SKILL.md
  15. .agents/skills/colibri-pm/SKILL.md
  16. .agents/skills/colibri-roadmap-progress/SKILL.md
  17. .agents/skills/colibri-roadmaps-tasks/SKILL.md
  18. .agents/skills/colibri-task-management/SKILL.md
  19. .agents/skills/colibri-tier1-chains/SKILL.md
  20. .agents/skills/colibri-truthing/SKILL.md
  21. .agents/skills/colibri-verification/SKILL.md

Deviation from spec: corpus is 21, not 22. The primary acceptance test parses all 21 and asserts zero schema errors. This deviation is recorded in the packet (§P4) and verification (§V2).

2b. Frontmatter key inventory (observed across the 21 files)

Every file has a YAML block delimited by two --- lines at the top. Keys observed:

Key Present in Shape Notes
name 21 / 21 string, kebab-case (all match /^[a-z][a-z0-9-]+$/) Universal.
description 21 / 21 string (some quoted) Universal. Some files use "…" around multi-sentence descriptions with embedded punctuation; a handwritten parser must handle this correctly.
round 15 / 21 string (e.g. R74.5) Present on the Phase 0 skills authored/refreshed in R74; absent on the older ones (colibri-autonomous, colibri-docs-sync, colibri-executor, colibri-mcp-server, colibri-truthing, colibri-verification).
updated 15 / 21 date string YYYY-MM-DD Co-varies with round.
status 8 / 21 string (heritage) Only on HERITAGE skills — colibri-audit-memory, colibri-gsd, colibri-gsd-execution, colibri-memory-context, colibri-observability, colibri-obsidian-integration, colibri-roadmap-progress, colibri-roadmaps-tasks.
model 0 / 21 The skill tool description block lists model as a typical key but no SKILL.md in this corpus declares it. Absent.

Keys in the P0.6.1 schema spec that are absent from every file in the corpus:

  • version — 0 / 21
  • entrypoint — 0 / 21
  • capabilities — 0 / 21
  • greekLetter — 0 / 21

The Greek: <letter> identifier appears inside the description text for many skills (e.g. Greek: β (beta)), but not as a structured frontmatter key.

2c. Frontmatter parsing hazards

Direct observation of three representative descriptions:

description: "Colibri Project Manager (T2) skill for Phase 0 pipeline management … Trigger phrases: 'PM mode', 'manage pipeline' …"
  • Embedded single quotes inside a double-quoted YAML scalar (colibri-pm, colibri-tier1-chains).
  • Long descriptions with em-dashes, parentheses, backticks, and colons (colibri-memory-context, colibri-roadmap-progress).
  • Unicode Greek letters (β, α, ε, η, π) in unquoted body text (colibri-growth-strategy, colibri-verification).

These are normal for a spec-grade YAML parser. A handwritten regex-only parser will fail on at least colibri-pm, colibri-memory-context, and colibri-roadmap-progress. This audit finding forces the dep decision in §4c.


§3. Adjacent code that the new module must integrate with

3a. src/config.ts (95 lines — P0.1.4, commit 3bd154a7)

Establishes the idiom for Zod-schema modules in the Phase 0 tree:

  • Zod 3 ("zod": "^3.23.8" in package.json) — not Zod 4 despite CLAUDE.md §1 stack line. Schema syntax must be Zod 3 (z.enum([...]) with as const tuple, .optional() for absent, z.object({...}).passthrough() for tolerant extra keys). z.literal.*.or(...) plus z.union([...]) behave as Zod 3 expects.
  • Pure factory patternloadConfig(env) is pure, config is the frozen eager snapshot. The skill parser mirrors this: parseSkillFile(path) is pure (no module-load side-effect), the schema export is a plain constant.
  • Throw on parse failure with a clear error message. config.ts re-wraps Zod errors with .flatten(); the skill parser does the same.

3b. src/modes.ts (185 lines — P0.4.1, commit a64d7349)

Establishes the as const tuple + derived type + frozen record pattern:

  • as const tuple ["FULL", "READONLY", "MINIMAL"]z.enum(RUNTIME_MODES). The skill schema reuses this shape for capabilities (tuple ["read","write","spawn","audit","admin"]) and the greek-letter set (15-element tuple).
  • Frozen exports via Object.freeze(...) for capability matrices. The skill schema does not need a matrix — just an enum — but the idiom is the house style.
  • Test style: one file per source module, organized by describe() per exported symbol. See §3d.

3c. src/db/index.ts (319 lines — P0.2.2, commit 3ebbe419)

Not a direct dependency of this task. Relevant only because:

  • P0.6.2 (next task) builds the skills table and CRUD on top of this. P0.6.1 is the schema layer — pure Zod + parser, no DB access. import of ./db/index.js is forbidden in src/domains/skills/schema.ts.
  • The AuditSink hook in src/server.ts is where P0.6.2 will register skill_list as an MCP tool. Not in scope for P0.6.1.

3d. src/__tests__/config.test.ts (206 lines) and src/__tests__/db-init.test.ts

Test conventions:

  • Filename pattern <module>.test.ts co-located in src/__tests__/. Not tests/. Wave A lock, confirmed in Sigma packet guidance.
  • describe('SkillFrontmatterSchema', () => { ... }) per exported symbol; it('rejects X', () => { ... }) for each acceptance criterion.
  • expect(() => fn(bad)).toThrow(/pattern/) for schema rejection paths.
  • expect(result).toMatchObject({...}) for happy-path parse assertions.
  • File-system reads use node:fs + absolute paths resolved via import.meta.url when the test needs to locate repo root from the compiled src/__tests__/ location.

No jest.isolateModulesAsync is needed — the schema module has no eager side-effect, no env reads, no DB reads. Pure data.

3e. package.json + jest.config.ts + tsconfig.json

  • tsconfig.json has exactOptionalPropertyTypes: true — any optional() field on the Zod schema must produce a TS type where the key is absent or present, not T | undefined. Solution: use z.string().optional() (Zod 3 outputs string | undefined) and accept that consumers do if (meta.greekLetter) checks. This is the same treatment config.ts already uses.
  • tsconfig.json has noUncheckedIndexedAccess: true — array access returns T | undefined. The corpus test iterates an array of paths; each access must be narrowed before use.
  • jest.config.ts collectCoverageFrom already covers src/**/*.ts — the new src/domains/skills/schema.ts will appear in the coverage report automatically.
  • ESLint curly: "all" + eqeqeq: "always" + @typescript-eslint/no-explicit-any: "warn" + consistent-type-imports: "error". Every type import uses import type { … }.

3f. What this task MUST NOT touch

Per the dispatch prompt’s collision avoidance list:

  • src/server.ts — P0.2.3 owns the server-side edit; Wave C sibling.
  • src/db/schema.sql, src/db/migrations/*, src/db/index.ts — the skills table is P0.6.2.
  • src/config.ts, src/modes.ts — unrelated.
  • package.json — editable only to add the frontmatter-parsing dep (see §4c).
  • jest.config.ts, tsconfig.json — no config changes needed for pure data module + pure test.
  • src/domains/tasks/*, src/domains/trail/* — Wave C siblings (P0.3.1, P0.7.1).

§4. Deviations from the spec + open decisions

4a. greekLetter optional (and corpus has zero)

Spec: { name, description, version, entrypoint, capabilities[], greekLetter? }. The ? on greekLetter is explicit in both the task-breakdown acceptance line and in the dispatch prompt.

Reality: no SKILL.md in the 21-file corpus has any of version, entrypoint, capabilities, or greekLetter as a frontmatter key. If the schema declares these as required, the corpus test fails 21/21 immediately.

Resolution (packet §P2 + contract §C1):

  • name required, kebab-case. Corpus: 21/21 ✅
  • description required, non-empty string. Corpus: 21/21 ✅
  • version optional, string (no semver enforcement — extraction does not mandate it). Corpus: 0/21, but the schema accepts it when present. Tests cover the “present + valid” path with a synthetic fixture.
  • entrypoint optional, string (free-form path). Corpus: 0/21, same treatment.
  • capabilities optional, array of enum values (["read","write","spawn","audit","admin"]). Corpus: 0/21, same treatment.
  • greekLetter optional, one of the 15 greek letters. Corpus: 0/21, same treatment.

The contract documents each deviation with the acceptance line it traces back to. The test suite has (a) the corpus test that parses all 21 files with the permissive schema, (b) unit tests that exercise each optional field in isolation with synthetic YAML.

The spec says “preferred” behavior is to add the missing fields to the corpus if trivial — adding version, entrypoint, capabilities to 21 files is not trivial; each requires a real semantic decision (which capabilities does colibri-docs-check expose? what’s the entrypoint of colibri-gsd when the whole point is it’s heritage?). Attempting those assignments would exceed scope and produce 21 placeholder values that P0.6.3 (Capability Index) would then have to rewrite. Option (b) — schema tolerance — is the honest choice.

4b. Test path override

Spec says tests/domains/skills/schema.test.ts. Wave A load-bearing lock says src/__tests__/<module>.test.ts because jest.config.ts line 14 declares roots: ['<rootDir>/src']. Moving to tests/ would require a jest config change (out of scope, see §3f). Actual path: src/__tests__/skill-schema.test.ts.

4c. Dep choice — gray-matter vs depless

The dispatch prompt lists three options. Audit analysis:

Option Verdict Rationale
Depless (handwritten) Reject Per §2c, at least three corpus files use double-quoted YAML strings with embedded single quotes, em-dashes, parens, and colons. A regex split on --- + line-by-line key: value will mis-parse colibri-pm (truncates description at the first ') and colibri-memory-context (line-wraps inside a scalar). Shipping a parser that can’t handle shipped files fails the primary acceptance test.
js-yaml direct Acceptable Full YAML compliance. Small (~40kb). Transitively safe. Requires the caller to split --- delimiters manually.
gray-matter Chosen Wraps js-yaml + delimiter split in one call. matter(content) returns { data, content }. Battle-tested (~15M weekly downloads), <10kb wire, all deps are standard (js-yaml, kind-of, section-matter, strip-bom-string, extend-shallow). The packet includes a dep-justification section.

gray-matter is added as a runtime dependency (not devDependency) because P0.6.2 will use it too (src/domains/skills/repository.ts). Version pinned to ^4.0.3 (current stable).

4d. Primary acceptance test shape

The spec’s acceptance line reads: “Test: parse ALL 22 existing skill files, assert zero schema errors”.

With 21 files in the actual corpus (§2a), the test implementation is:

// src/__tests__/skill-schema.test.ts (excerpt — full shape in packet §P3)
describe('SkillFrontmatterSchema – corpus compatibility', () => {
  const repoRoot = path.resolve(__dirname, '..', '..');
  const skillDir = path.join(repoRoot, '.agents', 'skills');
  const skillFiles = glob('colibri-*/SKILL.md', { cwd: skillDir });

  it('finds the expected corpus count', () => {
    expect(skillFiles.length).toBe(21);
  });

  it.each(skillFiles)('parses %s with zero schema errors', (rel) => {
    const abs = path.join(skillDir, rel);
    expect(() => parseSkillFile(abs)).not.toThrow();
  });

  it('every parsed skill has a name matching kebab-case', () => {
    for (const rel of skillFiles) {
      const parsed = parseSkillFile(path.join(skillDir, rel));
      expect(parsed.frontmatter.name).toMatch(/^[a-z][a-z0-9-]+$/);
    }
  });
});

Using it.each gives per-file assertion rows — if one breaks, the output names the file.

4e. No SKILL.md corpus edits

Per §4a, the audit concludes that amending 21 files to add placeholder version/entrypoint/capabilities/greekLetter is out-of-scope and would produce low-signal values. This PR edits zero SKILL.md files. The contract (§C2) records this decision.

4f. Parser return shape

The dispatch prompt specifies parseSkillFile(path): SkillMeta (abstractly). The audit resolves this to:

interface ParsedSkill {
  frontmatter: SkillFrontmatter;   // Zod-validated frontmatter
  body: string;                    // the Markdown after the closing `---`
  path: string;                    // absolute path the file was read from
}

function parseSkillFile(absPath: string): ParsedSkill;

path on the return keeps errors easy to attribute in aggregate scans (P0.6.2 callsite).


§5. Acceptance criteria traceability

Spec line How it’s satisfied Where
{ name, description, version, entrypoint, capabilities[], greekLetter? } Zod schema Exported SkillFrontmatterSchema with name + description required, rest optional (§4a) schema.ts
name kebab-case /^[a-z][a-z0-9-]+$/ z.string().regex(KEBAB_CASE); test with 3 invalid names + corpus sweep schema.ts + test
capabilities enum ["read","write","spawn","audit","admin"] z.array(z.enum(CAPABILITIES)); test with valid array + invalid member rejection schema.ts + test
greekLetter one of α β γ δ ε ζ η θ ι κ λ μ ν ξ π z.enum(GREEK_LETTERS) with the exact 15-char tuple; optional; test rejects non-greek chars and upper-case schema.ts + test
SKILL.md parser: reads frontmatter + body parseSkillFile(abs) via gray-matter; returns { frontmatter, body, path } schema.ts + test
Corpus test: parse all existing files with zero errors it.each(skillFiles) with corpus iteration; 21 rows test

§6. Out of scope

  • Loading SKILL.md files into a DB — P0.6.2.
  • skill_list MCP tool registration — P0.6.2.
  • Capability-based filtering — P0.6.3.
  • Hot-reload, skill_get, skill_reload — Phase 1 per extraction heritage note.
  • Adding the skills table to src/db/migrations/ — P0.6.2.
  • δ Model Router, agent spawning via skills — Phase 1.5 per ADR-005.

Audit complete. Step 2 (Contract) follows immediately, grounded in §4’s deviations + §5’s traceability matrix.


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.