diff --git a/docs/install-for-codex.md b/docs/install-for-codex.md index 2c70a1cc..7e6d3505 100644 --- a/docs/install-for-codex.md +++ b/docs/install-for-codex.md @@ -23,7 +23,7 @@ Or use the unified installer directly: ``` This will: -- Sync `humanize`, `humanize-gen-plan`, `humanize-refine-plan`, and `humanize-rlcr` into `${CODEX_HOME:-~/.codex}/skills` +- Sync `humanize`, `humanize-gen-idea`, `humanize-gen-plan`, `humanize-refine-plan`, and `humanize-rlcr` into `${CODEX_HOME:-~/.codex}/skills` - Copy runtime dependencies into `${CODEX_HOME:-~/.codex}/skills/humanize` - Install/update native Humanize Stop hooks in `${CODEX_HOME:-~/.codex}/hooks.json` - Enable the experimental `codex_hooks` feature in `${CODEX_HOME:-~/.codex}/config.toml` when `codex` is available @@ -41,6 +41,7 @@ ls -la "${CODEX_HOME:-$HOME/.codex}/skills" Expected directories: - `humanize` +- `humanize-gen-idea` - `humanize-gen-plan` - `humanize-refine-plan` - `humanize-rlcr` @@ -55,6 +56,7 @@ Runtime dependencies in `humanize/`: Installed files/directories: - `${CODEX_HOME:-~/.codex}/skills/humanize/SKILL.md` +- `${CODEX_HOME:-~/.codex}/skills/humanize-gen-idea/SKILL.md` - `${CODEX_HOME:-~/.codex}/skills/humanize-gen-plan/SKILL.md` - `${CODEX_HOME:-~/.codex}/skills/humanize-refine-plan/SKILL.md` - `${CODEX_HOME:-~/.codex}/skills/humanize-rlcr/SKILL.md` diff --git a/scripts/install-skill.sh b/scripts/install-skill.sh index 3106201d..4c949945 100755 --- a/scripts/install-skill.sh +++ b/scripts/install-skill.sh @@ -3,7 +3,7 @@ # Install/upgrade Humanize skills for Kimi and/or Codex. # # What this does: -# 1) Sync skills/{humanize,humanize-gen-plan,humanize-rlcr} to target skills dir(s) +# 1) Sync target skills to target skills dir(s) # 2) Copy runtime dependencies into /humanize/{scripts,hooks,prompt-template} # 3) Hydrate SKILL.md command paths with concrete runtime root paths # @@ -43,6 +43,10 @@ SKILL_NAMES=( "humanize-rlcr" ) +CODEX_ONLY_SKILL_NAMES=( + "humanize-gen-idea" +) + usage() { cat <<'EOF' Install Humanize skills for Kimi and/or Codex. @@ -63,6 +67,10 @@ Options: EOF } +target_includes_codex() { + [[ "$TARGET" == "codex" || "$TARGET" == "both" ]] +} + log() { printf '[install-skills] %s\n' "$*" } @@ -82,7 +90,13 @@ validate_repo() { [[ -d "$RUNTIME_SOURCE_ROOT/templates" ]] || die "templates directory not found under runtime source root: $RUNTIME_SOURCE_ROOT" [[ -d "$RUNTIME_SOURCE_ROOT/config" ]] || die "config directory not found under runtime source root: $RUNTIME_SOURCE_ROOT" [[ -d "$RUNTIME_SOURCE_ROOT/agents" ]] || die "agents directory not found under runtime source root: $RUNTIME_SOURCE_ROOT" - for skill in "${SKILL_NAMES[@]}"; do + local required_skills=("${SKILL_NAMES[@]}") + if target_includes_codex; then + required_skills+=("${CODEX_ONLY_SKILL_NAMES[@]}") + fi + + local skill + for skill in "${required_skills[@]}"; do [[ -f "$SKILLS_SOURCE_ROOT/$skill/SKILL.md" ]] || die "missing $SKILLS_SOURCE_ROOT/$skill/SKILL.md" done } @@ -105,9 +119,24 @@ resolve_source_layout() { # /humanize/scripts/install-skill.sh # /humanize-gen-plan/SKILL.md # /humanize-rlcr/SKILL.md + # /humanize-gen-idea/SKILL.md (Codex target only) if [[ -d "$runtime_root/scripts" ]] && [[ -d "$runtime_root/hooks" ]] && [[ -d "$runtime_root/prompt-template" ]]; then skills_root="$(cd "$runtime_root/.." && pwd)" - if [[ -f "$skills_root/humanize/SKILL.md" ]] && [[ -f "$skills_root/humanize-gen-plan/SKILL.md" ]] && [[ -f "$skills_root/humanize-refine-plan/SKILL.md" ]] && [[ -f "$skills_root/humanize-rlcr/SKILL.md" ]]; then + local required_skills=("${SKILL_NAMES[@]}") + if target_includes_codex; then + required_skills+=("${CODEX_ONLY_SKILL_NAMES[@]}") + fi + + local layout_ok="true" + local skill + for skill in "${required_skills[@]}"; do + if [[ ! -f "$skills_root/$skill/SKILL.md" ]]; then + layout_ok="false" + break + fi + done + + if [[ "$layout_ok" == "true" ]]; then SKILLS_SOURCE_ROOT="$skills_root" RUNTIME_SOURCE_ROOT="$runtime_root" return 0 @@ -166,12 +195,18 @@ install_runtime_bundle() { hydrate_skill_runtime_root() { local target_dir="$1" + shift || true + local selected_skills=("$@") local runtime_root="$target_dir/humanize" local skill local skill_file local tmp - for skill in "${SKILL_NAMES[@]}"; do + if [[ ${#selected_skills[@]} -eq 0 ]]; then + selected_skills=("${SKILL_NAMES[@]}") + fi + + for skill in "${selected_skills[@]}"; do skill_file="$target_dir/$skill/SKILL.md" [[ -f "$skill_file" ]] || continue @@ -193,11 +228,17 @@ hydrate_skill_runtime_root() { strip_claude_specific_frontmatter() { local target_dir="$1" + shift || true + local selected_skills=("$@") local skill local skill_file local tmp - for skill in "${SKILL_NAMES[@]}"; do + if [[ ${#selected_skills[@]} -eq 0 ]]; then + selected_skills=("${SKILL_NAMES[@]}") + fi + + for skill in "${selected_skills[@]}"; do skill_file="$target_dir/$skill/SKILL.md" [[ -f "$skill_file" ]] || continue @@ -234,6 +275,10 @@ sync_target() { local target_dir="$2" local selected_skills=("${SKILL_NAMES[@]}") + if [[ "$label" == "codex" ]]; then + selected_skills+=("${CODEX_ONLY_SKILL_NAMES[@]}") + fi + log "target: $label" log "skills dir: $target_dir" @@ -246,8 +291,8 @@ sync_target() { sync_one_skill "$skill" "$target_dir" done install_runtime_bundle "$target_dir" - hydrate_skill_runtime_root "$target_dir" - strip_claude_specific_frontmatter "$target_dir" + hydrate_skill_runtime_root "$target_dir" "${selected_skills[@]}" + strip_claude_specific_frontmatter "$target_dir" "${selected_skills[@]}" } install_codex_native_hooks() { diff --git a/skills/humanize-gen-idea/SKILL.md b/skills/humanize-gen-idea/SKILL.md new file mode 100644 index 00000000..a194bfe2 --- /dev/null +++ b/skills/humanize-gen-idea/SKILL.md @@ -0,0 +1,149 @@ +--- +name: humanize-gen-idea +description: Generate a repo-grounded idea draft from loose input using directed exploration. +type: flow +user-invocable: false +disable-model-invocation: true +--- + +# Humanize Generate Idea + +Transforms loose idea text, or a `.md` file containing an idea, into a repo-grounded draft suitable for `humanize-gen-plan`. + +The installer hydrates this skill with an absolute runtime root path: + +```bash +{{HUMANIZE_RUNTIME_ROOT}} +``` + +## Hard Constraint + +This flow must only produce an idea draft. Do not implement features, modify source code, create commits, or write any file other than the final draft output selected by the validator. + +## Input Requirements + +Required: +- One positional idea input: inline text or path to a `.md` file + +Optional: +- `--n ` - number of directions to explore; default is 6 +- `--output ` - output draft path; default is resolved by the validator under `.humanize/ideas/` + +## Required Sequence + +### 1. Parse Arguments Safely + +Extract `$ARGUMENTS` into: +- `IDEA_INPUT` +- optional `N` +- optional `OUTPUT_FILE` + +Do not pass free-form idea text to the shell unquoted. Inline idea text may contain spaces or shell metacharacters and must be passed as one shell argument. + +### 2. Validate IO + +Run the validator from the installed runtime root: + +```bash +"{{HUMANIZE_RUNTIME_ROOT}}/scripts/validate-gen-idea-io.sh" [--n N] [--output OUTPUT_FILE] "IDEA_INPUT" +``` + +Handle exit codes: + +| Exit Code | Meaning | +|-----------|---------| +| 0 | Success; parse validator stdout and continue | +| 1 | Missing or empty idea input | +| 2 | Input looks like a file path but is missing, unreadable, or not `.md` | +| 3 | Output directory does not exist | +| 4 | Output file already exists | +| 5 | No write permission to output directory | +| 6 | Invalid arguments | +| 7 | Template file missing | + +On success, parse these stdout fields: +- `INPUT_MODE` +- `OUTPUT_FILE` +- `SLUG` +- `TEMPLATE_FILE` +- `N` +- `IDEA_BODY_FILE` when `INPUT_MODE` is `file` + +For inline input, extract `IDEA_BODY` from the validator's sentinel block between `=== IDEA_BODY_BEGIN ===` and `=== IDEA_BODY_END ===`. For file input, read `IDEA_BODY_FILE`. Preserve the idea body byte-identically for the final draft. + +### 3. Generate Orthogonal Directions + +Read repository context before proposing directions: +- `README.md` +- `CLAUDE.md`, if present +- `.claude/CLAUDE.md`, if present +- one-level top-level directory listing + +Generate exactly `N` direction entries. Each entry must have: +- `name`: 2-5 words +- `rationale`: one sentence explaining why this angle is distinct + +Directions must be genuinely orthogonal. If two are near-duplicates, replace one. If the first pass yields fewer than `N` directions, regenerate once. If the second pass still yields fewer than `N` but at least 2, proceed with the reduced count and report a warning. If fewer than 2 directions remain, stop with `direction generation degraded; retry.` + +### 4. Explore Each Direction + +For each direction, gather objective evidence from the repository. Prefer child-agent or parallel exploration only when available and permitted by the current runtime policy; otherwise perform the explorations sequentially in the current session. + +Every exploration must be read-only and must return these fields: + +```text +APPROACH_SUMMARY: +OBJECTIVE_EVIDENCE: +KNOWN_RISKS: +CONFIDENCE: +``` + +Rules for exploration: +- `OBJECTIVE_EVIDENCE` must cite concrete repository paths, prior art, or measurable considerations. +- If no concrete evidence exists, use the literal sentinel `exploratory, no concrete precedent` once. +- Do not fabricate repository references. +- `CONFIDENCE` must be one of `high`, `medium`, or `low`. +- Drop any exploration result missing one of the required fields. +- If fewer than 2 proposals survive, stop with `exploration phase degraded; retry.` + +### 5. Synthesize The Draft + +Choose the primary proposal using this priority: +1. Evidence density +2. Fit with existing repository patterns +3. Smaller implementation surface for otherwise comparable proposals +4. Higher declared confidence + +Generate a 4-10 word Title Case title that captures the primary direction. + +Read `TEMPLATE_FILE` and replace: +- `` +- `<ORIGINAL_IDEA>` +- `<PRIMARY_NAME>` +- `<PRIMARY_RATIONALE>` +- `<PRIMARY_APPROACH_SUMMARY>` +- `<PRIMARY_OBJECTIVE_EVIDENCE>` +- `<PRIMARY_KNOWN_RISKS>` +- `<ALTERNATIVES>` +- `<SYNTHESIS_NOTES>` + +Alternative sections must use this format: + +```markdown +### Alt-<i>: <name> +- Gist: <one-paragraph summary derived from APPROACH_SUMMARY> +- Objective Evidence: + - <bullet from OBJECTIVE_EVIDENCE> +- Why not primary: <one sentence stating the tradeoff vs PRIMARY> +``` + +### 6. Write And Report + +Write the finalized draft to `OUTPUT_FILE` in one operation. Do not write partial output if any prior phase failed. + +Report: +- Path written +- Primary direction name +- Requested `N` and actual direction count +- Any warnings +- Next step: run `humanize-gen-plan` with the draft as `--input` diff --git a/tests/test-codex-hook-install.sh b/tests/test-codex-hook-install.sh index da20fb96..4f07c8ba 100755 --- a/tests/test-codex-hook-install.sh +++ b/tests/test-codex-hook-install.sh @@ -127,6 +127,20 @@ else fail "Codex install keeps humanize-rlcr entrypoint skill" "skills/humanize-rlcr/SKILL.md exists" "missing" fi +if [[ -f "$CODEX_HOME_DIR/skills/humanize-gen-idea/SKILL.md" ]]; then + pass "Codex install includes humanize-gen-idea entrypoint skill" +else + fail "Codex install includes humanize-gen-idea entrypoint skill" "skills/humanize-gen-idea/SKILL.md exists" "missing" +fi + +if grep -qF "$CODEX_HOME_DIR/skills/humanize/scripts/validate-gen-idea-io.sh" "$CODEX_HOME_DIR/skills/humanize-gen-idea/SKILL.md"; then + pass "Codex install hydrates humanize-gen-idea runtime paths" +else + fail "Codex install hydrates humanize-gen-idea runtime paths" \ + "$CODEX_HOME_DIR/skills/humanize/scripts/validate-gen-idea-io.sh" \ + "$(sed -n '1,80p' "$CODEX_HOME_DIR/skills/humanize-gen-idea/SKILL.md" 2>/dev/null || echo missing)" +fi + if [[ -f "$HOOKS_FILE" ]]; then pass "Codex install writes hooks.json" else @@ -253,6 +267,7 @@ PATH="$FAKE_BIN:$PATH" TEST_CODEX_FEATURE_LOG="$FEATURE_LOG" XDG_CONFIG_HOME="$X --target codex \ --codex-config-dir "$CODEX_HOME_DIR" \ --codex-skills-dir "$CODEX_HOME_DIR/skills" \ + --command-bin-dir "$COMMAND_BIN_DIR" \ > "$TEST_DIR/install-2.log" 2>&1 PY_OUTPUT_2="$( @@ -308,7 +323,7 @@ EOF chmod +x "$UNSUPPORTED_BIN/codex" set +e -PATH="$UNSUPPORTED_BIN:$PATH" \ +PATH="$UNSUPPORTED_BIN:$PATH" XDG_CONFIG_HOME="$XDG_CONFIG_HOME_DIR" \ "$INSTALL_SCRIPT" \ --target codex \ --codex-config-dir "$UNSUPPORTED_HOME" \