技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
sample-data-scaffolding【数据与研究】Use during bob's Step 2.5 (once per component, before implementation WPs run) to generate realistic, seeded fixtures for each component in the frozen contract map. Reads inputs declared in progress/contract-map.yaml and produces tests/fixtures/<component>/<input>/<index>.json plus a manifest.yaml with hashes and semantic types used. Requires a bob-is…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Turn the frozen contract map into realistic test fixtures. For each component's declared inputs, generate deterministic, semantically-typed sample data (Faker/Mimesis) and hash it into a manifest. Three fixture variants per field: happy, boundary, adversarial.
Bob invokes this skill once per component after G1/G2/G3 pass and after bob has issued a claim token. The skill does NOT gate itself — bob runs the gates and hands a pre-approved opaque claim UUID to the skill. The skill heartbeats the claim, generates fixtures, emits a transition request, and exits. Bob applies the transition atomically.
The skill GENERATES fixture files. Bob EXECUTES the runner (spec section 11.2 CB3 fix). The skill NEVER writes to the ledger directly.
Bob's Step 2.5 (spec section 14.2) calls this skill with:
component_id — the component to scaffoldclaim_uuid — opaque claim token bob issued via claims.issue_claim(wp_id, "sample-data-scaffolding")contract_map_path — progress/contract-map.yaml (read-only)project_root — project rootBob has already:
gates.py G1 with ledger binding — passedgates.py G2 — passedclaims.issue_claim(...) — returned the claim UUIDThe skill's job is narrow: generate fixtures, heartbeat, emit a request.
The first thing the skill does is verify the claim actually exists on disk and is owned by bob:
import yaml
from pathlib import Path
claim_path = Path(f".ledger/claims/{claim_uuid}.claim.yaml")
if not claim_path.is_file():
raise SystemExit("CLAIM_MISSING: bob has not issued a claim for this skill invocation")
claim = yaml.safe_load(claim_path.read_text())
if claim.get("issued_by") != "bob" or claim.get("skill") != "sample-data-scaffolding":
raise SystemExit("CLAIM_OWNERSHIP_VIOLATION: claim is not a bob-issued sample-data-scaffolding claim")
If either check fails, the skill HALTs. It never writes claim files — that would violate CB4.
component_id and claim_uuid from bobExpected CLI invocation (reference pattern):
python3 -m sample_data_scaffolding scaffold \
--component auth-service \
--claim <UUID> \
--contract-map progress/contract-map.yaml \
--project-root .
The skill is typically invoked by bob as a Python module; the CLI form above is a reference.
from _meta import claims
state = claims.heartbeat_claim(claim_uuid) # 'ok' | 'stale' | 'expired'
if state != "ok":
# STOP IMMEDIATELY. Report to bob. Do not emit a transition request.
raise SystemExit(f"CLAIM_{state.upper()}: re-run G3 and reissue claim")
Heartbeat runs every 60 seconds in a background thread while the generator works. If any heartbeat returns stale or expired, STOP all work immediately and exit cleanly — bob will reissue a fresh claim and re-invoke the skill.
import yaml
map_yaml = yaml.safe_load(Path("progress/contract-map.yaml").read_text())
component = next((c for c in map_yaml["components"] if c["id"] == component_id), None)
if component is None:
raise SystemExit(f"COMPONENT_NOT_IN_MAP: {component_id}")
Do NOT mutate the map. It is frozen.
Load the v1 semantic type registry from
~/.claude/skills/sample-data-scaffolding/semantic-types.yaml
and the project-local override at .contract/semantic-types.yaml if present.
For each input field:
| Input shape | Strategy |
|---|---|
kind: primitive + semantic_type: X | Look up X in the merged registry. Use the declared Faker/Mimesis strategy key. |
kind: primitive + semantic_type: technical + technical: Y | Generate per the technical closed-list rules (ids from uuid4, timestamps from now, hashes from sha256 of random bytes). |
kind: record | Recurse into each field. |
kind: collection | Generate N items (N = example_count or default 4). |
kind: opaque | DO NOT generate. Copy opaque_fixture_source into the fixtures path as-is. |
$ref: TypeName | Look up the type in map_yaml["types"] and recurse on its shape. |
NEVER invent a field the input does not declare. NEVER generate for a field without a semantic type unless it is kind: opaque with a fixture source.
Determinism is mandatory. Re-running with the same seed MUST produce the same fixtures — this makes tests reproducible and lets the audit bundle be hashed reliably.
Seed derivation:
import hashlib
seed_input = f"{component_id}::{input_name}::{contract_map_hash}".encode()
seed = int.from_bytes(hashlib.sha256(seed_input).digest()[:8], "big")
faker = Faker()
faker.seed_instance(seed)
Variants per field (spec section 17 R2):
happy — typical valueboundary — empty-string, zero, max-length, unicode edgesadversarial — SQL injection marker, XSS marker, null byte, overflowProduce example_count samples per variant (default 3; max from contract map).
Write to tests/fixtures/<component_id>/<input_name>/<variant>/<index>.json via tmp+rename:
fixtures_dir = Path(f"tests/fixtures/{component_id}/{input_name}/{variant}")
fixtures_dir.mkdir(parents=True, exist_ok=True)
for i, sample in enumerate(samples):
tmp = fixtures_dir / f".{i}.json.tmp"
tmp.write_text(json.dumps(sample, sort_keys=True))
tmp.rename(fixtures_dir / f"{i}.json")
Write the manifest:
# tests/fixtures/<component_id>/manifest.yaml
schema_version: "1.0.0"
component_id: auth-service
contract_map_hash: sha256:<hash>
generated_at: <iso8601>
generated_by: sample-data-scaffolding@1.0.0
seed_salt: <component_id + contract_map_hash>
inputs:
session_token:
semantic_type: session_token
strategy: hex_random_32
fixtures:
happy:
- path: tests/fixtures/auth-service/session_token/happy/0.json
hash: sha256:<hash>
boundary: [...]
adversarial: [...]
Write .ledger/requests/<request_id>.request.yaml (do NOT touch the ledger or claim files directly):
request_id: <UUID> # for idempotent apply
claim_uuid: <UUID> # bob verifies this
wp: WP-NNN
component_id: auth-service
requester: sample-data-scaffolding
target_stage: SCAFFOLDED
evidence:
- type: fixture_manifest
path: tests/fixtures/auth-service/manifest.yaml
hash: sha256:<hash>
produced_by: skill:sample-data-scaffolding
- type: semantic_validation
command: "python3 -m sample_data_scaffolding validate tests/fixtures/auth-service/"
exit_code: 0
at: <iso8601>
Bob picks up the request file, runs verify_claim_on_transition, and either:
The skill does NOT poll forever. If bob has not applied within the claim's lease TTL (default 10 min), the claim expires and the skill exits with CLAIM_EXPIRED. Bob's recovery logic handles the retry.
See Step 4. The rule is: follow the shape declared in the contract map. Never guess structure. Never inline fields that the shape does not declare.
The registry lives at ~/.claude/skills/sample-data-scaffolding/semantic-types.yaml and is derived from ~/.claude/skills/_meta/gates.py V1_SEMANTIC_TYPES by construction. A parity test (spec section 20 criterion 9 and this skill's test suite) runs on any change — drift fails the build.
The 18 types are:
user_id, session_token, api_keyemail, phone_e164, address_line, country_iso2full_name, first_name, last_name, date_of_birthiso_8601_datetime, iso_8601_date, unix_timestampcurrency_amount, currency_iso4217, ibanurl_httpTo add a project-specific type, create .contract/semantic-types.yaml in the project root:
semantic_types:
internal_product_sku:
strategy: faker.bothify
args: { text: "PRD-####-???" }
validator: "^PRD-\\d{4}-[A-Z]{3}quot;
G2 loads both and namespaces them. Project-local wins on collision with a warning in the design-doc callout.
Same inputs → same bytes. The contract_map_hash is part of the seed so that a re-signing of the map (freeze-the-world revision bump) produces fresh fixtures for affected components only. Unaffected components keep their fixtures because the re-run produces bit-identical output.
stale or expired: stop work immediately, do NOT emit a transition request, exit with CLAIM_STALE or CLAIM_EXPIRED.ok: continue.Per-WP budgets: S=10min, M=25min, L=60min. If the generator exceeds its budget (determined by bob), bob revokes the claim, the skill's next heartbeat sees expired, and the skill exits cleanly.
progress/integration-ledger.md directly — emit a transition request file..ledger/claims/ — claims are bob-only (CB4).semantic_type (unless kind: opaque with fixture source).--regenerate flag from bob.v1 scope: sync request/response, the 18 registry types, pytest + jest fixtures only. Report scope violations to bob, do not solve them.