mirror of
https://github.com/Hestia-Homes/agentic-toolkit.git
synced 2026-06-08 11:37:26 +00:00
added tdd
This commit is contained in:
parent
12a885f43b
commit
5c12f8bf43
5 changed files with 175 additions and 3 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -6,3 +6,6 @@ dist
|
||||||
.env.local
|
.env.local
|
||||||
coverage
|
coverage
|
||||||
.vitest-cache
|
.vitest-cache
|
||||||
|
# skills CLI side-effects when running `skills add` locally
|
||||||
|
.agents/
|
||||||
|
.claude/
|
||||||
|
|
|
||||||
135
scripts/generate-lock.sh
Executable file
135
scripts/generate-lock.sh
Executable file
|
|
@ -0,0 +1,135 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Regenerate skills-lock.json from skills.config.json.
|
||||||
|
#
|
||||||
|
# How it works:
|
||||||
|
# 1. Read skills.config.json (list of {source, skills[]}).
|
||||||
|
# 2. In a temp dir, run `npx skills add <source> --skill ... --agent <agent> --copy --yes`
|
||||||
|
# once per source. The `skills` CLI auto-writes/merges skills-lock.json there.
|
||||||
|
# 3. Copy the resulting lock back to the repo root.
|
||||||
|
#
|
||||||
|
# Run from the repo root (or anywhere; paths are resolved relative to the script):
|
||||||
|
# bash scripts/generate-lock.sh
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
CONFIG="$REPO_ROOT/skills.config.json"
|
||||||
|
DEST_LOCK="$REPO_ROOT/skills-lock.json"
|
||||||
|
|
||||||
|
if [[ ! -f "$CONFIG" ]]; then
|
||||||
|
echo "error: $CONFIG not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for bin in node npx; do
|
||||||
|
if ! command -v "$bin" >/dev/null 2>&1; then
|
||||||
|
echo "error: $bin is required (install Node.js >= 20)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
WORK_DIR="$(mktemp -d -t skills-lockgen.XXXXXX)"
|
||||||
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||||
|
|
||||||
|
# Emit "agent\ninstallSource\tcanonicalSource\tskillA,skillB\n..." for the shell.
|
||||||
|
# installSource is what we pass to `skills add` (resolved localPath if set, else
|
||||||
|
# the canonical slug). canonicalSource is what the published lock must record.
|
||||||
|
PLAN_FILE="$WORK_DIR/plan.txt"
|
||||||
|
node --input-type=module -e "
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
||||||
|
const repoRoot = process.argv[3];
|
||||||
|
const agent = cfg.agent || 'claude-code';
|
||||||
|
const lines = [agent];
|
||||||
|
for (const entry of cfg.sources || []) {
|
||||||
|
if (!entry?.source || !Array.isArray(entry.skills) || entry.skills.length === 0) continue;
|
||||||
|
const install = entry.localPath ? path.resolve(repoRoot, entry.localPath) : entry.source;
|
||||||
|
lines.push(install + '\t' + entry.source + '\t' + entry.skills.join(','));
|
||||||
|
}
|
||||||
|
fs.writeFileSync(process.argv[2], lines.join('\n') + '\n');
|
||||||
|
" "$CONFIG" "$PLAN_FILE" "$REPO_ROOT"
|
||||||
|
|
||||||
|
AGENT="$(head -n1 "$PLAN_FILE")"
|
||||||
|
echo "==> Agent target: $AGENT"
|
||||||
|
echo "==> Generating lock in $WORK_DIR"
|
||||||
|
|
||||||
|
# Read plan from fd 3 so the loop body's stdin stays free for npx (the skills
|
||||||
|
# CLI reads stdin even in --yes mode and would otherwise gobble loop lines).
|
||||||
|
while IFS=$'\t' read -r INSTALL_SOURCE CANONICAL_SOURCE SKILLS <&3; do
|
||||||
|
[[ -z "$INSTALL_SOURCE" ]] && continue
|
||||||
|
if [[ "$INSTALL_SOURCE" == "$CANONICAL_SOURCE" ]]; then
|
||||||
|
echo "==> $CANONICAL_SOURCE :: $SKILLS"
|
||||||
|
else
|
||||||
|
echo "==> $CANONICAL_SOURCE (from local $INSTALL_SOURCE) :: $SKILLS"
|
||||||
|
fi
|
||||||
|
SKILL_ARGS=()
|
||||||
|
IFS=',' read -ra _NAMES <<< "$SKILLS"
|
||||||
|
for n in "${_NAMES[@]}"; do
|
||||||
|
[[ -z "$n" ]] && continue
|
||||||
|
SKILL_ARGS+=(--skill "$n")
|
||||||
|
done
|
||||||
|
( cd "$WORK_DIR" && npx --yes skills@latest add "$INSTALL_SOURCE" \
|
||||||
|
"${SKILL_ARGS[@]}" \
|
||||||
|
--agent "$AGENT" \
|
||||||
|
--copy \
|
||||||
|
--yes < /dev/null )
|
||||||
|
done 3< <(tail -n +2 "$PLAN_FILE")
|
||||||
|
|
||||||
|
if [[ ! -f "$WORK_DIR/skills-lock.json" ]]; then
|
||||||
|
echo "error: skills CLI did not produce $WORK_DIR/skills-lock.json." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rewrite any local-path sources in the lock back to their canonical slug, so
|
||||||
|
# consumers of skills-lock.json (e.g. setup.sh in a dev container) fetch from
|
||||||
|
# GitHub instead of a path that only exists on the generator's machine.
|
||||||
|
node --input-type=module -e "
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
||||||
|
const repoRoot = process.argv[2];
|
||||||
|
const lock = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
||||||
|
const remap = new Map();
|
||||||
|
for (const entry of cfg.sources || []) {
|
||||||
|
if (!entry?.source || !entry.localPath) continue;
|
||||||
|
remap.set(path.resolve(repoRoot, entry.localPath), entry.source);
|
||||||
|
}
|
||||||
|
for (const skill of Object.values(lock.skills || {})) {
|
||||||
|
if (!skill?.source) continue;
|
||||||
|
const resolved = path.resolve(skill.source);
|
||||||
|
if (remap.has(resolved)) {
|
||||||
|
skill.source = remap.get(resolved);
|
||||||
|
skill.sourceType = 'github';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.writeFileSync(process.argv[4], JSON.stringify(lock, null, 2) + '\n');
|
||||||
|
" "$CONFIG" "$REPO_ROOT" "$WORK_DIR/skills-lock.json" "$DEST_LOCK"
|
||||||
|
echo "==> Wrote $DEST_LOCK"
|
||||||
|
|
||||||
|
# Cross-check: every (source, skill) requested in config should be present in the lock.
|
||||||
|
node --input-type=module -e "
|
||||||
|
import fs from 'node:fs';
|
||||||
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
||||||
|
const lock = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
||||||
|
const got = new Map();
|
||||||
|
for (const [name, entry] of Object.entries(lock.skills || {})) {
|
||||||
|
if (!entry?.source) continue;
|
||||||
|
if (!got.has(entry.source)) got.set(entry.source, new Set());
|
||||||
|
got.get(entry.source).add(name);
|
||||||
|
}
|
||||||
|
const missing = [];
|
||||||
|
for (const entry of cfg.sources || []) {
|
||||||
|
const have = got.get(entry.source) || new Set();
|
||||||
|
for (const s of entry.skills || []) {
|
||||||
|
if (!have.has(s)) missing.push(entry.source + '::' + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missing.length) {
|
||||||
|
console.error('error: skills missing from generated lock (likely missing SKILL.md frontmatter upstream):');
|
||||||
|
for (const m of missing) console.error(' - ' + m);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('==> Verified: all ' + Object.keys(lock.skills).length + ' configured skills present in lock.');
|
||||||
|
" "$CONFIG" "$DEST_LOCK"
|
||||||
|
|
@ -34,7 +34,6 @@
|
||||||
"ralph-loop": {
|
"ralph-loop": {
|
||||||
"source": "Hestia-Homes/agentic-toolkit",
|
"source": "Hestia-Homes/agentic-toolkit",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "skills/engineering/ralph-loop/SKILL.md",
|
|
||||||
"computedHash": "6d6c256a5145008038d128089fa17ed78ac9350d98efd977624da21d9255988b"
|
"computedHash": "6d6c256a5145008038d128089fa17ed78ac9350d98efd977624da21d9255988b"
|
||||||
},
|
},
|
||||||
"setup-matt-pocock-skills": {
|
"setup-matt-pocock-skills": {
|
||||||
|
|
@ -43,6 +42,11 @@
|
||||||
"skillPath": "skills/engineering/setup-matt-pocock-skills/SKILL.md",
|
"skillPath": "skills/engineering/setup-matt-pocock-skills/SKILL.md",
|
||||||
"computedHash": "0164a8b1ef998abca426056c6ed8a7716a9d4692fc6daa5378f68381a6dafd24"
|
"computedHash": "0164a8b1ef998abca426056c6ed8a7716a9d4692fc6daa5378f68381a6dafd24"
|
||||||
},
|
},
|
||||||
|
"tdd": {
|
||||||
|
"source": "Hestia-Homes/agentic-toolkit",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "b995b2494d7cd0737c7aa2d6a8affcdd8030ac1b29a52c572a3c5c163842c451"
|
||||||
|
},
|
||||||
"to-issues": {
|
"to-issues": {
|
||||||
"source": "mattpocock/skills",
|
"source": "mattpocock/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
|
|
@ -58,7 +62,6 @@
|
||||||
"to-project": {
|
"to-project": {
|
||||||
"source": "Hestia-Homes/agentic-toolkit",
|
"source": "Hestia-Homes/agentic-toolkit",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "skills/engineering/to-project/SKILL.md",
|
|
||||||
"computedHash": "59daf039ac699a44a9416f8ec403b83d4166e05489959e127746231ff8be4e12"
|
"computedHash": "59daf039ac699a44a9416f8ec403b83d4166e05489959e127746231ff8be4e12"
|
||||||
},
|
},
|
||||||
"triage": {
|
"triage": {
|
||||||
|
|
|
||||||
31
skills.config.json
Normal file
31
skills.config.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"$schema_comment": "Source of truth for skills-lock.json. Edit this then run scripts/generate-lock.sh.",
|
||||||
|
"agent": "claude-code",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"source": "mattpocock/skills",
|
||||||
|
"skills": [
|
||||||
|
"diagnose",
|
||||||
|
"grill-with-docs",
|
||||||
|
"improve-codebase-architecture",
|
||||||
|
"setup-matt-pocock-skills",
|
||||||
|
"to-issues",
|
||||||
|
"to-prd",
|
||||||
|
"triage",
|
||||||
|
"zoom-out",
|
||||||
|
"caveman",
|
||||||
|
"grill-me",
|
||||||
|
"write-a-skill"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Hestia-Homes/agentic-toolkit",
|
||||||
|
"localPath": ".",
|
||||||
|
"skills": [
|
||||||
|
"ralph-loop",
|
||||||
|
"to-project",
|
||||||
|
"tdd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: tdd
|
name: tdd
|
||||||
description: Test-driven development using vertical 3A slices (Arrange-Act-Assert, one test → one implementation at a time). Tests verify behavior through public interfaces, not implementation details. Use when adding behavior to a codebase, fixing a bug that lacks a regression test, or any work where "make a failing test pass" is the right unit of progress.
|
description: Test-driven development using vertical 3A slices. Use when adding behavior, fixing a bug that lacks a regression test, or any work where making a failing test pass is the right unit of progress.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Test-Driven Development (3A)
|
# Test-Driven Development (3A)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue