Schema revisions per user review 2026-04-22 (all 6 open questions resolved — see §Decision log in SUBSTRATE-SCHEMA.md): - #3 side_effects: string tags → structured { op, domain } objects (user: "лучше сразу с запасом") - #4 capabilities.toml: DROPPED entirely (user: "почему не мд?"). SSoT is atoms/*.md. Crate-level metadata moves to Cargo.toml [package.metadata.keisei] — Cargo-native, no drift, no build.rs, no generated files to commit. kei-sage + kei-runtime walk atoms/*.md directly. - #5 atom template: shipped in this PR (user: "ui же параллельно! создавай все!") so Streams B/C/D can scaffold atoms from day 0 without waiting for Stream A (kei-forge UI). - #1/#2/#6 confirmed as drafted (draft-07, `::` separator, per-atom errors). New files: - _templates/atom/ — 5-file template set with placeholder substitution (__CRATE__, __VERB__, __KIND__, __DESCRIPTION__ etc). Covers atoms/<verb>.md, schemas/<verb>-{input,output}.json, src/atoms/<verb>.rs, tests/<verb>_smoke.rs. Each file is a minimal working skeleton. - scripts/new-atom.sh — POSIX bash generator (bash for $'\n' / readonly / trap). Validates verb is lowercase kebab-case, kind is one of command|query|stream|transform. Refuses to overwrite existing files. Rolls back on any failure (trap ERR deletes all generated files so no half-scaffolded state). Tested: produces 5 files, placeholder substitution correct on smoke-test crate. Stream B (atoms refactor) updated to drop the "generates capabilities.toml via build.rs" wording — now just "writes atoms/*.md + updates Cargo.toml [package.metadata.keisei]". Stream D reads atoms/*.md + Cargo.toml, not capabilities.toml. Schema status: revisions applied, decision log complete. Ready for SCHEMA-LOCKED.md marker commit once user signs off on revised doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
3.8 KiB
Bash
Executable file
123 lines
3.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# new-atom.sh — scaffold a new atom per SUBSTRATE-SCHEMA.md
|
|
#
|
|
# Usage:
|
|
# scripts/new-atom.sh <crate> <verb> [kind]
|
|
#
|
|
# Example:
|
|
# scripts/new-atom.sh kei-task add-dependency command
|
|
#
|
|
# Kinds (per schema §Atom kinds): command | query | stream | transform
|
|
# Default kind: command
|
|
|
|
set -euo pipefail
|
|
|
|
CRATE="${1:?usage: new-atom.sh <crate> <verb> [kind]}"
|
|
VERB="${2:?usage: new-atom.sh <crate> <verb> [kind]}"
|
|
KIND="${3:-command}"
|
|
|
|
# Validate kind against schema
|
|
case "$KIND" in
|
|
command|query|stream|transform) ;;
|
|
*) echo "error: kind must be one of: command, query, stream, transform" >&2; exit 1 ;;
|
|
esac
|
|
|
|
# Validate verb naming (kebab-case, lowercase)
|
|
if ! [[ "$VERB" =~ ^[a-z][a-z0-9]*(-[a-z0-9]+)*$ ]]; then
|
|
echo "error: verb must be lowercase kebab-case (got '$VERB')" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Repo root = two dirs up from this script
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
CRATE_DIR="$ROOT/_primitives/_rust/$CRATE"
|
|
TEMPLATE_DIR="$ROOT/_templates/atom"
|
|
|
|
if [ ! -d "$CRATE_DIR" ]; then
|
|
echo "error: crate directory not found: $CRATE_DIR" >&2
|
|
echo "hint: create the crate first (e.g. via 'cargo new --lib $CRATE_DIR')" >&2
|
|
exit 1
|
|
fi
|
|
|
|
VERB_SNAKE="${VERB//-/_}"
|
|
CRATE_SNAKE="${CRATE//-/_}"
|
|
|
|
# Target files
|
|
MD_OUT="$CRATE_DIR/atoms/$VERB.md"
|
|
IN_OUT="$CRATE_DIR/atoms/schemas/$VERB-input.json"
|
|
OUT_OUT="$CRATE_DIR/atoms/schemas/$VERB-output.json"
|
|
RS_OUT="$CRATE_DIR/src/atoms/$VERB_SNAKE.rs"
|
|
TEST_OUT="$CRATE_DIR/tests/${VERB_SNAKE}_smoke.rs"
|
|
|
|
# Refuse to overwrite
|
|
for f in "$MD_OUT" "$IN_OUT" "$OUT_OUT" "$RS_OUT" "$TEST_OUT"; do
|
|
if [ -e "$f" ]; then
|
|
echo "error: file already exists: $f" >&2
|
|
echo "hint: pick a different verb, or delete the existing file first" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Prompt for description (stdin-friendly, non-interactive if piped)
|
|
if [ -t 0 ]; then
|
|
read -rp "One-line description: " DESCRIPTION
|
|
else
|
|
DESCRIPTION="${ATOM_DESCRIPTION:-TODO: add description}"
|
|
fi
|
|
# Escape for sed — forward-slash is our delimiter; strip any the user typed
|
|
DESCRIPTION_ESCAPED="${DESCRIPTION//\//\\/}"
|
|
|
|
mkdir -p "$CRATE_DIR/atoms/schemas" "$CRATE_DIR/src/atoms" "$CRATE_DIR/tests"
|
|
|
|
# Track what we wrote so we can roll back on failure
|
|
CREATED=()
|
|
|
|
substitute() {
|
|
local src="$1" dest="$2"
|
|
sed \
|
|
-e "s/__CRATE__/$CRATE/g" \
|
|
-e "s/__CRATE_SNAKE__/$CRATE_SNAKE/g" \
|
|
-e "s/__VERB__/$VERB/g" \
|
|
-e "s/__VERB_SNAKE__/$VERB_SNAKE/g" \
|
|
-e "s/__KIND__/$KIND/g" \
|
|
-e "s/__DESCRIPTION__/$DESCRIPTION_ESCAPED/g" \
|
|
"$src" > "$dest"
|
|
CREATED+=("$dest")
|
|
}
|
|
|
|
rollback() {
|
|
echo "rolling back — removing ${#CREATED[@]} generated files..." >&2
|
|
for f in "${CREATED[@]}"; do
|
|
rm -f "$f"
|
|
done
|
|
}
|
|
|
|
trap rollback ERR
|
|
|
|
substitute "$TEMPLATE_DIR/atoms/__VERB__.md.template" "$MD_OUT"
|
|
substitute "$TEMPLATE_DIR/atoms/schemas/__VERB__-input.json.template" "$IN_OUT"
|
|
substitute "$TEMPLATE_DIR/atoms/schemas/__VERB__-output.json.template" "$OUT_OUT"
|
|
substitute "$TEMPLATE_DIR/src/atoms/__VERB_SNAKE__.rs.template" "$RS_OUT"
|
|
substitute "$TEMPLATE_DIR/tests/__VERB_SNAKE___smoke.rs.template" "$TEST_OUT"
|
|
|
|
# Registering the atom module in src/atoms/mod.rs is left to Stream B
|
|
# refactor — on a freshly templated crate, src/atoms/mod.rs may not exist
|
|
# yet. The generator refuses to guess where to append.
|
|
|
|
trap - ERR
|
|
|
|
echo ""
|
|
echo "✓ Scaffolded atom $CRATE::$VERB ($KIND)"
|
|
echo ""
|
|
echo "Files created:"
|
|
for f in "${CREATED[@]}"; do
|
|
echo " ${f#$ROOT/}"
|
|
done
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Edit atoms/$VERB.md — fill description, examples, related[] wikilinks"
|
|
echo " 2. Edit atoms/schemas/$VERB-{input,output}.json — declare actual fields"
|
|
echo " 3. Implement src/atoms/$VERB_SNAKE.rs — replace NotImplemented with real logic"
|
|
echo " 4. Register: add 'pub mod $VERB_SNAKE;' to src/atoms/mod.rs"
|
|
echo " 5. cargo check -p $CRATE"
|
|
echo " 6. (once kei-schema-lint ships) kei-schema-lint $CRATE"
|