diff --git a/.claude/skills/questions/SKILL.md b/.claude/skills/questions/SKILL.md new file mode 100644 index 0000000000..0243963bfd --- /dev/null +++ b/.claude/skills/questions/SKILL.md @@ -0,0 +1,237 @@ +--- +name: questions +description: Guide for searching, recording, and managing research questions and "how do I?" questions in docs/questions/. Use this skill when the user wants to search for existing questions, record new questions, review old/stale questions, or manage the question knowledge base. Triggers include "search questions", "record a question", "review old questions", or questions about the question management workflow. +--- + +# Questions Skill + +**Use this skill when the user asks to search for questions, record a question, or manage the question knowledge base.** + +## Overview + +This repository maintains a question management system in `docs/questions/` for tracking research questions and "how do I?" questions across work sessions. The system prevents noise accumulation through active lifecycle management. + +## When to Use This Skill + +Invoke this skill when the user: +- Asks to search for existing questions on a topic +- Wants to record a new question +- Requests to review old or stale questions +- Needs to update or deprecate existing questions +- Asks about the question management workflow + +## Available Tools + +### `./record-answer.sh` + +Interactive script for recording questions. Usage: + +```bash +./record-answer.sh +``` + +The script will: +1. Prompt for the question text +2. Search for similar existing questions using ripgrep +3. Offer options: + - Create new question + - Update existing question + - Supersede existing question with new one +4. Generate a file named `YYYY-MM-DD-slug.md` in `docs/questions/` +5. Open the file in the user's editor +6. Update `docs/questions/index.json` + +### `./list-questions.sh` + +List and filter questions. Usage: + +```bash +# List open questions (default) +./list-questions.sh +./list-questions.sh open + +# List answered questions +./list-questions.sh answered + +# List deprecated questions +./list-questions.sh deprecated + +# List questions with age warnings (>6 months) +./list-questions.sh aged + +# List questions by tag +./list-questions.sh tag workflow + +# List all questions +./list-questions.sh all +``` + +Output includes: +- Status indicators (● open, ✓ answered, ✗ deprecated) +- Age warnings (⚠ for questions with age_warning: true) +- File names with age in days (color-coded: green <90d, yellow <180d, red >180d) +- Question titles and tags + +### Direct Search + +Use ripgrep for content search: + +```bash +# Search question content +rg "keyword" docs/questions/ + +# Search by tag +rg "tags: \[.*workflow.*\]" docs/questions/ + +# Search by status +rg "status: open" docs/questions/ +``` + +## Question File Format + +Each question is stored as `YYYY-MM-DD-slug.md`: + +```yaml +--- +date: 2025-11-24 +updated: 2025-11-24 +status: open|answered|deprecated +tags: [workflow, tooling, research] +related: [2025-11-20-similar-question.md] +supersedes: [] +superseded_by: null +age_warning: false +--- + +# Question Title + +## Context +Background and situation that prompted the question + +## Question +The actual question stated clearly + +## Answer +Current thinking or answer (filled in when answered) + +## Notes +Additional context, observations, links +``` + +## Lifecycle Management + +### Status Values +- **open**: Question not yet answered +- **answered**: Question has an answer (may still evolve) +- **deprecated**: Question is obsolete or superseded + +### Age Management +- Questions older than 6 months automatically get `age_warning: true` +- Use `./list-questions.sh aged` to find questions needing review +- Review aged questions to either: + - Update them with new information + - Mark as deprecated if no longer relevant + - Keep as-is if still valuable + +### Superseding Questions +When a question replaces an older one: +1. New question sets `supersedes: [old-question.md]` +2. Old question gets `superseded_by: new-question.md` +3. Old question status becomes `deprecated` + +## Workflow Examples + +### Searching for Existing Questions + +When user asks: "Do we have any questions about pattern deployment?" + +```bash +# Search content +rg -i "pattern.*deploy" docs/questions/ + +# Or search tags +rg "tags: \[.*pattern.*\]" docs/questions/ + +# List all to scan +./list-questions.sh all +``` + +### Recording a New Question + +When user says: "I want to record a question about error handling" + +```bash +./record-answer.sh +# Follow prompts: +# 1. Enter question text +# 2. Review similar questions found +# 3. Choose to create new or update existing +# 4. File opens in editor for detailed content +``` + +### Reviewing Old Questions + +When user asks: "What questions need attention?" + +```bash +# Show aged questions +./list-questions.sh aged + +# Show open questions +./list-questions.sh open + +# Show deprecated questions (candidates for cleanup) +./list-questions.sh deprecated +``` + +### Updating a Question + +When user says: "I found the answer to question X" + +1. Read the question file +2. Update the `status` field to `answered` +3. Update the `updated` field to today's date +4. Fill in the `## Answer` section +5. Add any notes to `## Notes` + +## Best Practices + +1. **Search before creating**: Always search for similar questions first to avoid duplicates +2. **Be specific**: Good question titles and slugs improve discoverability +3. **Tag consistently**: Use consistent tags for better categorization +4. **Link related**: Cross-reference related questions in metadata +5. **Update answers**: When you learn more, update existing questions rather than creating new ones +6. **Deprecate actively**: Don't let obsolete questions accumulate +7. **Preserve context**: Even deprecated questions keep their content for historical reference + +## Integration with Workflow + +- When researching a topic, check for related questions first +- After solving a problem, consider if it's worth recording as a question +- During project reviews, scan aged questions for updates +- When answering questions, link to related questions for context + +## File Locations + +- Questions directory: `docs/questions/` +- Template: `docs/questions/_template.md` +- Index: `docs/questions/index.json` +- README: `docs/questions/README.md` +- Scripts: `./record-answer.sh`, `./list-questions.sh` + +## Troubleshooting + +### Script errors +- Ensure scripts are executable: `chmod +x record-answer.sh list-questions.sh` +- Check ripgrep is installed: `which rg` +- Verify jq is installed: `which jq` + +### Missing questions +- Check `docs/questions/` directory exists +- Verify question files follow `YYYY-MM-DD-*.md` pattern +- Ensure YAML frontmatter is properly formatted + +### Search not finding questions +- Try broader search terms +- Use `./list-questions.sh all` to see all questions +- Check for typos in tags or metadata diff --git a/AGENTS.md b/AGENTS.md index b10e666ba2..e9f1465357 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,12 @@ If you are developing patterns, use Claude Skills (pattern-dev) to do the work. (`bd quickstart` and `bd --help`) - Read `task-management` skill for workflow +**Question Management** + +- Use the `questions` skill to search for, record, and manage research questions + and "how do I?" questions +- See `docs/questions/README.md` for the complete workflow guide + **Reference:** - `packages/patterns/INDEX.md` - Catalog of all pattern examples with summaries, diff --git a/docs/questions/2025-11-24-from-a-built-in-like-wish-or-fetchdata-how-can-i-g.md b/docs/questions/2025-11-24-from-a-built-in-like-wish-or-fetchdata-how-can-i-g.md new file mode 100644 index 0000000000..292e73d123 --- /dev/null +++ b/docs/questions/2025-11-24-from-a-built-in-like-wish-or-fetchdata-how-can-i-g.md @@ -0,0 +1,167 @@ +--- +date: 2025-11-24 +updated: 2025-11-24 +status: answered +tags: [built-ins, runtime, identity, did, signer] +related: [] +supersedes: [] +superseded_by: null +age_warning: false +--- + +# From a built-in like wish() or fetchData() how can I get the public DID of the current user? We have IRuntime which contains IStorageManager which contains ISession... or something like that. But what is the magic line of code to give me the DID (or a storage instance pointing to the space)? + +## Context + +When developing built-in functions, you need to access the user's **identity DID** (their keypair/signer DID) - not the space DID. This is a common source of confusion because there are TWO different DIDs in the system: + +1. **User Identity DID** - The user's keypair/signer identity (what you usually want) +2. **Space DID** - A namespace for data (may be derived from or separate from the user identity) + +## Question + +From a built-in like wish() or fetchData() how can I get the public DID of the current user? We have IRuntime which contains IStorageManager which contains ISession... or something like that. But what is the magic line of code to give me the DID (or a storage instance pointing to the space)? + +## Answer + +### The Correct Answer: User Identity DID + +```typescript +const userIdentityDID = runtime.storageManager.as.did(); +``` + +Built-in functions receive `runtime: IRuntime` as a parameter. The user's identity is accessible via the `storageManager.as` signer: + +```typescript +export function myBuiltin( + inputsCell: Cell, + sendResult: (tx: IExtendedStorageTransaction, result: any) => void, + addCancel: (cancel: () => void) => void, + cause: Cell[], + parentCell: Cell, + runtime: IRuntime, // <-- Runtime is injected +): Action { + return (tx: IExtendedStorageTransaction) => { + // Get the USER's identity DID (their keypair/signer) + const userIdentityDID = runtime.storageManager.as.did(); + + // Get the SPACE's DID (where data is stored - different!) + const spaceDID = parentCell.space; + + // ... + }; +} +``` + +### Architecture Chain + +``` +IRuntime + └─ storageManager: IStorageManager (interface.ts:66-100) + └─ (concrete: StorageManager, cache.ts:1916) + ├─ as: Signer ← USER'S IDENTITY (has .did() method) + ├─ id: string (just a UUID for debugging - NOT a DID!) + └─ spaceIdentity?: Signer (optional derived identity for space) +``` + +From `packages/identity/src/session.ts:4-9`: +```typescript +export type Session = { + spaceName: string; + spaceIdentity?: Identity; // Optional: derived identity for the space + space: DID; // The space's DID + as: Identity; // The USER's identity ← This is what we want! +}; +``` + +### Critical Distinctions + +**User Identity DID vs Space DID:** + +```typescript +// User Identity DID (the person's keypair) +const userDID = runtime.storageManager.as.did(); +// Example: "did:key:z6Mkq..." + +// Space DID (the namespace where data lives) +const spaceDID = parentCell.space; +// Example: "did:key:z6Mkr..." (may be same or different from user DID) + +// WRONG: runtime.id is NOT a DID! +// runtime.id is just crypto.randomUUID() for debugging +``` + +### Real-World Example + +From `packages/shell/src/lib/runtime.ts:106-128`: +```typescript +const session = await createSession({ identity, spaceName }); + +// Log user identity for debugging +identityLogger.log("telemetry", `[Identity] User DID: ${session.as.did()}`); +identityLogger.log( + "telemetry", + `[Identity] Space: ${spaceName} (${session.space})`, +); + +const runtime = new Runtime({ + apiUrl: new URL(apiUrl), + storageManager: StorageManager.open({ + as: session.as, // <-- User identity passed to storage manager + spaceIdentity: session.spaceIdentity, + address: new URL("/api/storage/memory", apiUrl), + }), + // ... +}); +``` + +### Why Spaces Can Be Different from User Identity + +Spaces can have their own derived identities for privacy/security. From `packages/identity/src/session.ts:27-40`: + +```typescript +export const createSession = async ( + { identity, spaceName }: { identity: Identity; spaceName: string }, +): Promise => { + // Derive a space-specific identity from a common root + const spaceIdentity = await (await Identity.fromPassphrase("common user")) + .derive(spaceName); + + return { + spaceName, + spaceIdentity, + space: spaceIdentity.did(), // Space DID (derived) + as: identity, // User identity (original) + }; +}; +``` + +This allows: +- Multiple users to share a space +- A user to have multiple spaces +- Spaces with derived identities for privacy + +## Notes + +### Common Mistakes + +1. **Using `parentCell.space`** - This gives you the SPACE DID, not the user identity DID +2. **Using `runtime.id`** - This is just a random UUID (`crypto.randomUUID()`), not a DID at all +3. **Confusing space with identity** - A space is a namespace owned by an identity; they're different concepts + +### Key Files + +**Type definitions:** +- `packages/identity/src/session.ts:4-9` - Session type showing `as` (user identity) vs `space` +- `packages/runner/src/storage/interface.ts:66-100` - IStorageManager interface +- `packages/runner/src/storage/cache.ts:1916-1949` - StorageManager with `as: Signer` field + +**Real usage examples:** +- `packages/shell/src/lib/runtime.ts:106-128` - Runtime creation with user identity +- `packages/identity/src/session.ts:27-40` - Session creation showing identity vs space + +### The Magic Line + +```typescript +runtime.storageManager.as.did() // ← User's identity DID +``` diff --git a/docs/questions/README.md b/docs/questions/README.md new file mode 100644 index 0000000000..98aae6b2d0 --- /dev/null +++ b/docs/questions/README.md @@ -0,0 +1,151 @@ +# Question Management System + +This directory manages research questions and "how do I?" questions across work sessions, with built-in workflows to prevent noise accumulation over time. + +## Philosophy + +- **Low friction**: Quick to record, fast to search +- **Natural evolution**: Questions can be updated, deprecated, or superseded +- **Context preservation**: Track when/why questions were asked and how answers evolved +- **Prevent noise**: Active lifecycle management keeps the question base fresh + +## Directory Structure + +``` +docs/questions/ +├── README.md # This file +├── _template.md # Template for new questions +├── index.json # Metadata index for fast searching +└── YYYY-MM-DD-slug.md # Individual question files +``` + +## Question File Format + +Each question is stored as `YYYY-MM-DD-slug.md` with YAML frontmatter: + +```yaml +--- +date: 2025-11-24 +updated: 2025-11-24 +status: open|answered|deprecated +tags: [workflow, tooling, research] +related: [2025-11-20-similar-question.md] +supersedes: [] +superseded_by: null +age_warning: false +--- + +# Question Title + +## Context +Why was this question asked? What's the background? + +## Question +The actual question goes here. + +## Answer +Current thinking or answer (for answered questions). + +## Notes +Additional context, links, related thoughts. +``` + +## Metadata Fields + +- **date**: When question was created (YYYY-MM-DD) +- **updated**: Last modification date +- **status**: `open` | `answered` | `deprecated` +- **tags**: Array of topic tags for categorization +- **related**: Links to related question files +- **supersedes**: Questions this one replaces +- **superseded_by**: If deprecated, link to replacement question +- **age_warning**: Auto-set to `true` if >6 months old without updates + +## Workflows + +### Recording a Question + +```bash +./record-answer.sh +``` + +Interactive workflow: +1. Enter your question text +2. Script searches for similar questions +3. If similar found: option to update existing or mark as superseded +4. If new: creates file from template with auto-generated slug +5. Opens in your editor for detailed content entry +6. Updates index.json for fast future searches + +### Listing Questions + +```bash +# List all open questions +./list-questions.sh open + +# List answered questions +./list-questions.sh answered + +# List deprecated questions +./list-questions.sh deprecated + +# List questions by tag +./list-questions.sh tag workflow + +# List questions with age warnings +./list-questions.sh aged +``` + +### Searching Questions + +```bash +# Content search +rg "keyword" docs/questions/ + +# Tag search +rg "tags: \[.*workflow.*\]" docs/questions/ + +# Status search +rg "status: open" docs/questions/ +``` + +## Lifecycle Management + +### Age Warnings +Questions older than 6 months automatically get `age_warning: true` to prompt review. + +### Deprecation +Mark questions as deprecated when: +- The answer is no longer relevant +- The question has been superseded by a better formulation +- The context has changed making the question obsolete + +Always link to replacement via `superseded_by` field. + +### Superseding Questions +When recording a similar question that replaces an old one: +1. Set `supersedes: [old-question.md]` in new question +2. Set `superseded_by: new-question.md` in old question +3. Update old question status to `deprecated` + +### Manual Review +Periodically review: +- Questions with `age_warning: true` +- Deprecated questions (candidates for archival) +- Open questions that may now be answered + +## Best Practices + +1. **Be specific**: Good slugs and titles make future searches easier +2. **Tag thoughtfully**: Consistent tags improve discoverability +3. **Link related**: Cross-reference related questions +4. **Update answers**: When you learn more, update the answer section +5. **Deprecate actively**: Don't let obsolete questions accumulate +6. **Preserve context**: Even deprecated questions keep their content for historical reference + +## Integration with Claude Code + +The `.claude/skills/questions.md` skill guides AI agents on using this system. When working with Claude Code: +- Ask "search questions about X" to find related questions +- Say "record a question" to create a new entry +- Request "review old questions" for maintenance tasks diff --git a/docs/questions/_template.md b/docs/questions/_template.md new file mode 100644 index 0000000000..e205ac2148 --- /dev/null +++ b/docs/questions/_template.md @@ -0,0 +1,28 @@ +--- +date: YYYY-MM-DD +updated: YYYY-MM-DD +status: open +tags: [] +related: [] +supersedes: [] +superseded_by: null +age_warning: false +--- + +# Question Title + +## Context + +Why was this question asked? What's the background or situation that prompted it? + +## Question + +State the actual question clearly and specifically here. + +## Answer + +(Leave empty for open questions, or fill in as you discover the answer) + +## Notes + +Additional context, observations, related thoughts, or links to relevant resources. diff --git a/docs/questions/index.json b/docs/questions/index.json new file mode 100644 index 0000000000..eb36c1ae71 --- /dev/null +++ b/docs/questions/index.json @@ -0,0 +1,12 @@ +{ + "version": "1.0", + "last_updated": "2025-11-24", + "questions": [ + { + "filename": "2025-11-24-from-a-built-in-like-wish-or-fetchdata-how-can-i-g.md", + "date": "2025-11-24", + "title": "From a built-in like wish() or fetchData() how can I get the public DID of the current user? We have IRuntime which contains IStorageManager which contains ISession... or something like that. But what is the magic line of code to give me the DID (or a storage instance pointing to the space)?", + "status": "open" + } + ] +} diff --git a/list-questions.sh b/list-questions.sh new file mode 100755 index 0000000000..ac81e46c8c --- /dev/null +++ b/list-questions.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +set -euo pipefail + +QUESTIONS_DIR="docs/questions" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Get today's date for age calculation +TODAY=$(date +%Y-%m-%d) +TODAY_EPOCH=$(date -j -f "%Y-%m-%d" "$TODAY" "+%s") + +# Calculate age in days +calc_age() { + local date=$1 + local date_epoch=$(date -j -f "%Y-%m-%d" "$date" "+%s" 2>/dev/null || echo "0") + local days=$(( (TODAY_EPOCH - date_epoch) / 86400 )) + echo "$days" +} + +# Format age for display +format_age() { + local days=$1 + if [ "$days" -gt 180 ]; then + echo -e "${RED}${days}d${NC}" + elif [ "$days" -gt 90 ]; then + echo -e "${YELLOW}${days}d${NC}" + else + echo -e "${GREEN}${days}d${NC}" + fi +} + +# Usage +usage() { + echo "Usage: $0 [filter] [value]" + echo "" + echo "Filters:" + echo " open List open questions (default)" + echo " answered List answered questions" + echo " deprecated List deprecated questions" + echo " aged List questions with age warnings (>6 months)" + echo " tag List questions with specific tag" + echo " all List all questions" + echo "" + echo "Examples:" + echo " $0 # List open questions" + echo " $0 answered # List answered questions" + echo " $0 tag workflow # List questions tagged 'workflow'" + exit 1 +} + +# Parse arguments +FILTER="${1:-open}" +VALUE="${2:-}" + +if [ "$FILTER" = "help" ] || [ "$FILTER" = "-h" ] || [ "$FILTER" = "--help" ]; then + usage +fi + +# Find question files +QUESTION_FILES=$(find "$QUESTIONS_DIR" -name "*.md" ! -name "_template.md" ! -name "README.md" | sort -r) + +if [ -z "$QUESTION_FILES" ]; then + echo "No questions found in $QUESTIONS_DIR" + exit 0 +fi + +# Header +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Question List ($FILTER) ${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +COUNT=0 + +# Process each file +while IFS= read -r file; do + # Skip if file doesn't exist + [ -f "$file" ] || continue + + # Extract metadata + DATE=$(rg "^date: " "$file" | head -1 | sed 's/^date: //' || echo "unknown") + UPDATED=$(rg "^updated: " "$file" | head -1 | sed 's/^updated: //' || echo "$DATE") + STATUS=$(rg "^status: " "$file" | head -1 | sed 's/^status: //' || echo "unknown") + TAGS=$(rg "^tags: " "$file" | head -1 | sed 's/^tags: //' || echo "[]") + AGE_WARNING=$(rg "^age_warning: " "$file" | head -1 | sed 's/^age_warning: //' || echo "false") + TITLE=$(rg "^# " "$file" | head -1 | sed 's/^# //' || echo "Untitled") + BASENAME=$(basename "$file") + + # Apply filters + SHOW=false + case $FILTER in + open) + [ "$STATUS" = "open" ] && SHOW=true + ;; + answered) + [ "$STATUS" = "answered" ] && SHOW=true + ;; + deprecated) + [ "$STATUS" = "deprecated" ] && SHOW=true + ;; + aged) + [ "$AGE_WARNING" = "true" ] && SHOW=true + ;; + tag) + if [ -z "$VALUE" ]; then + echo "Error: tag filter requires a value" + exit 1 + fi + echo "$TAGS" | grep -q "$VALUE" && SHOW=true + ;; + all) + SHOW=true + ;; + *) + echo "Unknown filter: $FILTER" + usage + ;; + esac + + if [ "$SHOW" = "true" ]; then + COUNT=$((COUNT + 1)) + + # Calculate age + AGE_DAYS=$(calc_age "$UPDATED") + AGE_DISPLAY=$(format_age "$AGE_DAYS") + + # Status color + case $STATUS in + open) + STATUS_COLOR="${YELLOW}●${NC}" + ;; + answered) + STATUS_COLOR="${GREEN}✓${NC}" + ;; + deprecated) + STATUS_COLOR="${RED}✗${NC}" + ;; + *) + STATUS_COLOR="${CYAN}?${NC}" + ;; + esac + + # Age warning indicator + AGE_IND="" + if [ "$AGE_WARNING" = "true" ]; then + AGE_IND="${RED}⚠${NC} " + fi + + # Display + echo -e "${STATUS_COLOR} ${AGE_IND}${CYAN}$BASENAME${NC} (${AGE_DISPLAY})" + echo -e " ${TITLE}" + + # Show tags if present and not empty + if [ "$TAGS" != "[]" ] && [ -n "$TAGS" ]; then + echo -e " Tags: ${BLUE}$TAGS${NC}" + fi + + echo "" + fi +done <<< "$QUESTION_FILES" + +# Summary +echo -e "${BLUE}────────────────────────────────────────────────────────────────────────────${NC}" +echo -e "${BLUE}Total: $COUNT question(s)${NC}" +echo "" diff --git a/packages/patterns/index.md b/packages/patterns/index.md index f9303c4635..0d704968e4 100644 --- a/packages/patterns/index.md +++ b/packages/patterns/index.md @@ -25,32 +25,29 @@ interface CounterOutput { } ``` -## `chatbot.tsx` +## `todo-list.tsx` -A chatbot demo. +A todo list with AI suggestions. ### Input Schema ```ts -type ChatInput = { - /** The system prompt */ - system: string; -}; +interface TodoItem { + title: string; + done: Default; +} + +interface Input { + items: Cell; +} ``` ### Result Schema ```ts -type ChatOutput = { - messages: Array; - pending: boolean | undefined; - addMessage: Stream; - clearChat: Stream; - cancelGeneration: Stream; - title: string; - attachments: Array; - tools: any; -}; +interface Output { + items: Cell; +} ``` ## `note.tsx` diff --git a/packages/patterns/todo-with-suggestion.tsx b/packages/patterns/todo-list.tsx similarity index 95% rename from packages/patterns/todo-with-suggestion.tsx rename to packages/patterns/todo-list.tsx index 8e8f518ee1..09a5cda046 100644 --- a/packages/patterns/todo-with-suggestion.tsx +++ b/packages/patterns/todo-list.tsx @@ -18,7 +18,8 @@ interface Output { export default pattern(({ items }) => { // AI suggestion based on current todos const suggestion = Suggestion({ - situation: "Based on my todo list, use a pattern to help me.", + situation: + "Based on my todo list, use a pattern to help me. For sub-tasks and additional tasks, use a todo list.", context: { items }, }); diff --git a/record-answer.sh b/record-answer.sh new file mode 100755 index 0000000000..59860fadcc --- /dev/null +++ b/record-answer.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +set -euo pipefail + +QUESTIONS_DIR="docs/questions" +TEMPLATE="$QUESTIONS_DIR/_template.md" +INDEX="$QUESTIONS_DIR/index.json" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get today's date +TODAY=$(date +%Y-%m-%d) + +# Prompt for question +echo -e "${BLUE}Enter your question:${NC}" +read -r QUESTION_TEXT + +if [ -z "$QUESTION_TEXT" ]; then + echo "Error: Question cannot be empty" + exit 1 +fi + +# Search for similar questions using ripgrep +echo -e "\n${BLUE}Searching for similar questions...${NC}" +SIMILAR=$(rg -i -l "$QUESTION_TEXT" "$QUESTIONS_DIR"/*.md 2>/dev/null | grep -v "_template.md" || true) + +if [ -n "$SIMILAR" ]; then + echo -e "${YELLOW}Found potentially similar questions:${NC}" + echo "$SIMILAR" | while read -r file; do + title=$(rg "^# " "$file" | head -1 | sed 's/^# //') + status=$(rg "^status: " "$file" | head -1 | sed 's/^status: //') + echo " - $(basename "$file") [$status]: $title" + done + + echo -e "\n${BLUE}What would you like to do?${NC}" + echo "1) Create new question anyway" + echo "2) Update an existing question" + echo "3) Mark existing as superseded and create new" + echo "4) Cancel" + read -r -p "Choice [1-4]: " CHOICE + + case $CHOICE in + 1) + # Continue with new question + ;; + 2) + echo "Enter the filename to update (e.g., 2025-11-24-example.md):" + read -r UPDATE_FILE + if [ -f "$QUESTIONS_DIR/$UPDATE_FILE" ]; then + # Update the 'updated' field + sed -i '' "s/^updated: .*/updated: $TODAY/" "$QUESTIONS_DIR/$UPDATE_FILE" + ${EDITOR:-vim} "$QUESTIONS_DIR/$UPDATE_FILE" + echo -e "${GREEN}Updated $UPDATE_FILE${NC}" + exit 0 + else + echo "File not found: $UPDATE_FILE" + exit 1 + fi + ;; + 3) + echo "Enter the filename to supersede (e.g., 2025-11-24-example.md):" + read -r OLD_FILE + if [ ! -f "$QUESTIONS_DIR/$OLD_FILE" ]; then + echo "File not found: $OLD_FILE" + exit 1 + fi + # Continue with new question, we'll link them after + ;; + 4) + echo "Cancelled" + exit 0 + ;; + *) + echo "Invalid choice" + exit 1 + ;; + esac +fi + +# Generate slug from question +SLUG=$(echo "$QUESTION_TEXT" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//' | cut -c1-50) +FILENAME="$TODAY-$SLUG.md" +FILEPATH="$QUESTIONS_DIR/$FILENAME" + +# Check if file already exists +if [ -f "$FILEPATH" ]; then + echo -e "${YELLOW}File $FILENAME already exists. Opening for edit...${NC}" + ${EDITOR:-vim} "$FILEPATH" + exit 0 +fi + +# Create new question file from template +cp "$TEMPLATE" "$FILEPATH" + +# Update the metadata +sed -i '' "s/date: YYYY-MM-DD/date: $TODAY/" "$FILEPATH" +sed -i '' "s/updated: YYYY-MM-DD/updated: $TODAY/" "$FILEPATH" + +# Insert question title and text +sed -i '' "s/^# Question Title/# $QUESTION_TEXT/" "$FILEPATH" +sed -i '' "/## Question/,/## Answer/{ + /## Question/a\\ +\\ +$QUESTION_TEXT +}" "$FILEPATH" + +# If superseding an old question, update both files +if [ -n "${OLD_FILE:-}" ]; then + NEW_BASENAME=$(basename "$FILEPATH") + + # Update new file to reference old file + sed -i '' "s/supersedes: \[\]/supersedes: [$OLD_FILE]/" "$FILEPATH" + + # Update old file + sed -i '' "s/status: .*/status: deprecated/" "$QUESTIONS_DIR/$OLD_FILE" + sed -i '' "s/superseded_by: null/superseded_by: $NEW_BASENAME/" "$QUESTIONS_DIR/$OLD_FILE" + sed -i '' "s/updated: .*/updated: $TODAY/" "$QUESTIONS_DIR/$OLD_FILE" + + echo -e "${GREEN}Marked $OLD_FILE as deprecated${NC}" +fi + +# Update index.json +TEMP_INDEX=$(mktemp) +jq --arg filename "$FILENAME" \ + --arg date "$TODAY" \ + --arg title "$QUESTION_TEXT" \ + '.last_updated = $date | .questions += [{"filename": $filename, "date": $date, "title": $title, "status": "open"}]' \ + "$INDEX" > "$TEMP_INDEX" +mv "$TEMP_INDEX" "$INDEX" + +echo -e "${GREEN}Created $FILENAME${NC}" +echo -e "${BLUE}Opening in editor...${NC}" + +# Open in editor +${EDITOR:-vim} "$FILEPATH" + +echo -e "${GREEN}Question recorded successfully!${NC}"