diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 4e3eec1..a50565e 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -30,7 +30,7 @@ {"id":"skills-8d4","title":"Compare CLI_REFERENCE.md with upstream","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T20:15:53.268324087-08:00","updated_at":"2025-12-03T20:17:26.552616779-08:00","closed_at":"2025-12-03T20:17:26.552616779-08:00","dependencies":[{"issue_id":"skills-8d4","depends_on_id":"skills-ebh","type":"discovered-from","created_at":"2025-12-03T20:15:53.27265681-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"skills-8d9","title":"Add conversational patterns to orch skill","description":"## Context\nThe orch skill currently documents consensus and single-shot chat, but doesn't\nteach agents how to use orch for multi-turn conversations with external AIs.\n\n## Goal\nAdd documentation and patterns for agent-driven conversations where the calling\nagent (Claude Code) orchestrates multi-turn dialogues using orch primitives.\n\n## Patterns to document\n\n### Session-based multi-turn\n```bash\n# Initial query\nRESPONSE=$(orch chat \"Analyze this\" --model claude --format json)\nSESSION=$(echo \"$RESPONSE\" | jq -r .session_id)\n\n# Continue conversation\norch chat \"Elaborate on X\" --model claude --session $SESSION\n\n# Inspect state\norch sessions info $SESSION\norch sessions show $SESSION --last 2 --format text\n```\n\n### Cross-model dialogue\n```bash\n# Get one model's take\nCLAUDE=$(orch chat \"Review this\" --model claude --format json)\nCLAUDE_SAYS=$(echo \"$CLAUDE\" | jq -r '.responses[0].content')\n\n# Ask another model to respond\norch chat \"Claude said: $CLAUDE_SAYS\n\nWhat's your perspective?\" --model gemini\n```\n\n### When to use conversations vs consensus\n- Consensus: quick parallel opinions on a decision\n- Conversation: deeper exploration, follow-up questions, iterative refinement\n\n## Files\n- skills/orch/SKILL.md\n\n## Related\n- orch-c3r: Design: Session introspection for agent-driven conversations (in orch repo)","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-18T19:57:28.201494288-08:00","updated_at":"2025-12-29T15:34:16.254181578-05:00","closed_at":"2025-12-29T15:34:16.254181578-05:00","close_reason":"Added conversational patterns section to orch SKILL.md: sessions, cross-model dialogue, iterative refinement, consensus vs chat guidance."} {"id":"skills-8ma","title":"worklog skill: remove org-mode references, use markdown instead","description":"The worklog skill currently references org-mode format (.org files) in the template and instructions. Update to use markdown (.md) instead:\n\n1. Update ~/.claude/skills/worklog/templates/worklog-template.org → worklog-template.md\n2. Convert org-mode syntax to markdown (#+TITLE → # Title, * → ##, etc.)\n3. Update skill instructions to reference .md files\n4. Update suggest-filename.sh to output .md extension\n\nContext: org-mode is less widely supported than markdown in tooling and editors.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T08:43:55.761429693-05:00","created_by":"dan","updated_at":"2026-01-02T00:13:05.338810905-05:00","closed_at":"2026-01-02T00:13:05.338810905-05:00","close_reason":"Migrated worklog skill from org-mode to markdown. Template, scripts, and SKILL.md updated. Backward compatible with existing .org files."} -{"id":"skills-8v0","title":"Consolidate skill list definitions (flake.nix + ai-skills.nix)","description":"Skill list duplicated in:\n- flake.nix (lines 15-27)\n- modules/ai-skills.nix (lines 8-18)\n\nIssues:\n- Manual sync required when adding skills\n- No validation that referenced skills exist\n\nFix:\n- Single source of truth for skill list\n- Consider generating one from the other\n\nSeverity: MEDIUM","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-24T02:51:14.432158871-05:00","updated_at":"2025-12-24T02:51:14.432158871-05:00"} +{"id":"skills-8v0","title":"Consolidate skill list definitions (flake.nix + ai-skills.nix)","description":"Skill list duplicated in:\n- flake.nix (lines 15-27)\n- modules/ai-skills.nix (lines 8-18)\n\nIssues:\n- Manual sync required when adding skills\n- No validation that referenced skills exist\n\nFix:\n- Single source of truth for skill list\n- Consider generating one from the other\n\nSeverity: MEDIUM","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-24T02:51:14.432158871-05:00","updated_at":"2026-01-03T12:06:23.731969973-08:00","closed_at":"2026-01-03T12:06:23.731969973-08:00","close_reason":"Created skills.nix as single source of truth for skill names and descriptions. Updated flake.nix and Home Manager module to use it."} {"id":"skills-8y6","title":"Define skill versioning strategy","description":"Git SHA alone is insufficient. Need tuple approach:\n\n- skill_source_rev: git SHA (if available)\n- skill_content_hash: hash of SKILL.md + scripts\n- runtime_ref: flake.lock hash or Nix store path\n\nQuestions to resolve:\n- Do Protos pin to versions (stable but maintenance) or float on latest (risky)?\n- How to handle breaking changes in skills?\n- Record in wisp trace vs proto definition?\n\nFrom consensus: both models flagged versioning instability as high severity.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-23T19:49:30.839064445-05:00","updated_at":"2025-12-23T20:55:04.439779336-05:00","closed_at":"2025-12-23T20:55:04.439779336-05:00","close_reason":"ADRs revised with orch consensus feedback"} {"id":"skills-9af","title":"spec-review: Add spike/research task handling","description":"Tasks like 'Investigate X' can linger without clear outcomes.\n\nAdd to REVIEW_TASKS:\n- Flag research/spike tasks\n- Require timebox and concrete outputs (decision record, prototype, risks)\n- Pattern for handling unknowns","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-15T00:23:26.887719136-08:00","updated_at":"2025-12-15T14:08:13.441095034-08:00","closed_at":"2025-12-15T14:08:13.441095034-08:00"} {"id":"skills-9bc","title":"Investigate pre-compression hook for worklogs","description":"## Revised Understanding\n\nClaude Code already persists full conversation history in `~/.claude/projects/\u003cproject\u003e/\u003csession-id\u003e.jsonl`. Pre-compact hooks aren't needed for data capture.\n\n## Question\nWhat's the ideal workflow for generating worklogs from session data?\n\n## Options\n\n### 1. Post-session script\n- Run after exiting Claude Code\n- Reads most recent session JSONL\n- Generates worklog from conversation content\n- Pro: Async, doesn't interrupt flow\n- Con: May forget to run it\n\n### 2. On-demand slash command\n- `/worklog-from-session` or similar\n- Reads current session's JSONL file\n- Generates worklog with full context\n- Pro: Explicit control\n- Con: Still need to remember\n\n### 3. Pre-compact reminder\n- Hook prints reminder: \"Consider running /worklog\"\n- Doesn't automate, just nudges\n- Pro: Simple, non-intrusive\n- Con: Easy to dismiss\n\n### 4. Async batch processing\n- Process old sessions whenever\n- All data persists in JSONL files\n- Pro: No urgency, can do later\n- Con: Context may be stale\n\n## Data Format\nSession files contain:\n- User messages with timestamp\n- Assistant responses with model info\n- Tool calls and results\n- Git branch, cwd, version info\n\n## Next Steps\n- Decide preferred workflow\n- Build script to parse session JSONL → worklog format","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-17T14:32:32.568430817-08:00","updated_at":"2025-12-17T15:56:38.864916015-08:00","closed_at":"2025-12-17T15:56:38.864916015-08:00","close_reason":"Pivoted: worklogs may be redundant given full conversation persistence. New approach: make conversations searchable directly."} diff --git a/.specify/scripts/bash/update-agent-context.sh b/.specify/scripts/bash/update-agent-context.sh index c218467..3fbe542 100755 --- a/.specify/scripts/bash/update-agent-context.sh +++ b/.specify/scripts/bash/update-agent-context.sh @@ -327,29 +327,37 @@ create_new_agent_file() { fi local substitutions=( - "s|\[PROJECT NAME\]|$project_name|" - "s|\[DATE\]|$current_date|" - "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" - "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" - "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" - "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" - "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" + "-e" "s|\[PROJECT NAME\]|$project_name|" + "-e" "s|\[DATE\]|$current_date|" + "-e" "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" + "-e" "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" + "-e" "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" + "-e" "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" + "-e" "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" ) - for substitution in "${substitutions[@]}"; do - if ! sed -i.bak -e "$substitution" "$temp_file"; then - log_error "Failed to perform substitution: $substitution" - rm -f "$temp_file" "$temp_file.bak" - return 1 - fi - done + # Perform all substitutions in one pass to a second temp file + local temp_file_final + temp_file_final="${temp_file}.final" + if ! sed "${substitutions[@]}" "$temp_file" > "$temp_file_final"; then + log_error "Failed to perform substitutions" + rm -f "$temp_file_final" + return 1 + fi + + # Replace the working temp file + mv "$temp_file_final" "$temp_file" + # Convert \n sequences to actual newlines newline=$(printf '\n') - sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" - - # Clean up backup files - rm -f "$temp_file.bak" "$temp_file.bak2" + temp_file_nl="${temp_file}.nl" + if ! sed "s/\\\\n/${newline}/g" "$temp_file" > "$temp_file_nl"; then + log_error "Failed to convert newlines" + rm -f "$temp_file_nl" + return 1 + fi + mv "$temp_file_nl" "$temp_file" return 0 } diff --git a/bin/deploy-skill.sh b/bin/deploy-skill.sh index 1901994..8fdd134 100755 --- a/bin/deploy-skill.sh +++ b/bin/deploy-skill.sh @@ -123,15 +123,45 @@ inject_nix_config() { echo "ℹ️ Config already present in $(basename "$target_file")" else echo "Injecting config into $(basename "$target_file")..." - # Create backup - cp "$target_file" "${target_file}.bak" + + # Create a secure temporary file + local temp_file + temp_file=$(mktemp "${target_file}.XXXXXX") + # Ensure cleanup on exit or error + trap 'rm -f "$temp_file"' EXIT # Insert before the last line (assuming it is '}') - # We use a temporary file to construct the new content - head -n -1 "$target_file" > "${target_file}.tmp" - echo "$config_block" >> "${target_file}.tmp" - tail -n 1 "$target_file" >> "${target_file}.tmp" - mv "${target_file}.tmp" "$target_file" + if ! head -n -1 "$target_file" > "$temp_file"; then + echo "Error: failed to read $target_file" >&2 + return 1 + fi + + echo "$config_block" >> "$temp_file" + + if ! tail -n 1 "$target_file" >> "$temp_file"; then + echo "Error: failed to append to $temp_file" >&2 + return 1 + fi + + # Validate: temp file should be larger than original (since we're adding) + local orig_size + orig_size=$(stat -c%s "$target_file") + local new_size + new_size=$(stat -c%s "$temp_file") + + if [[ $new_size -le $orig_size ]]; then + echo "Error: Validation failed, new file is not larger than original" >&2 + return 1 + fi + + # Atomic move + if ! mv "$temp_file" "$target_file"; then + echo "Error: Failed to replace $target_file" >&2 + return 1 + fi + + # Clear trap after successful move + trap - EXIT echo "✓ Updated $(basename "$target_file")" fi } diff --git a/skills/update-opencode/scripts/update-nix-file.sh b/skills/update-opencode/scripts/update-nix-file.sh index 37bd7c0..4278510 100755 --- a/skills/update-opencode/scripts/update-nix-file.sh +++ b/skills/update-opencode/scripts/update-nix-file.sh @@ -113,13 +113,34 @@ if [[ "$DRY_RUN" == true ]]; then exit 0 fi -# Perform atomic update using sed -sed -i \ +# Perform atomic update +TEMP_FILE=$(mktemp "${NIX_FILE}.XXXXXX") +trap 'rm -f "$TEMP_FILE"' EXIT + +if ! sed \ -e "s/version = \"[^\"]*\"/version = \"$VERSION\"/" \ -e "s/sha256 = \"[^\"]*\"/sha256 = \"$SHA256\"/" \ - "$NIX_FILE" + "$NIX_FILE" > "$TEMP_FILE"; then + echo "Error: failed to generate updated Nix file" >&2 + exit 1 +fi -# Verify update succeeded +# Basic validation: check if it's not empty and contains expected values +if [[ ! -s "$TEMP_FILE" ]]; then + echo "Error: Generated file is empty" >&2 + exit 1 +fi + +if ! grep -q "version = \"$VERSION\"" "$TEMP_FILE" || ! grep -q "sha256 = \"$SHA256\"" "$TEMP_FILE"; then + echo "Error: Generated file validation failed (expected patterns not found)" >&2 + exit 1 +fi + +# Atomic move +mv "$TEMP_FILE" "$NIX_FILE" +trap - EXIT + +# Verify update succeeded (extra safety check) UPDATED_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' "$NIX_FILE") UPDATED_SHA256=$(grep -oP 'sha256\s*=\s*"\K[^"]+' "$NIX_FILE")