Files
Claude-Code-Workflow/.claude/commands/workflow/spec/load.md
catlog22 663620955c chore: update commands, specs, and ccw tools
Update DDD commands (doc-generate, doc-refresh, sync), workflow commands
(session/sync, spec/add, spec/setup, spec/load), ccw specs, personal
preferences, and add generate-ddd-docs tool.
2026-03-09 23:20:39 +08:00

11 KiB

name, description, argument-hint, examples
name description argument-hint examples
load Interactive spec loader - ask what user needs, then load relevant specs by keyword routing [--all] [--type <bug|pattern|decision|rule>] [--tag <tag>] ["keyword query"]
/workflow:spec:load
/workflow:spec:load "api routing"
/workflow:spec:load --type bug
/workflow:spec:load --all
/workflow:spec:load --tag security

Spec Load Command (/workflow:spec:load)

Overview

Interactive entry point for loading and browsing project specs. Asks the user what they need, then routes to the appropriate spec content based on keywords, type filters, or tag filters.

Design: Menu-driven → keyword match → load & display. No file modifications.

Note: This command may be called by other workflow commands. Upon completion, return immediately to continue the calling workflow.

Usage

/workflow:spec:load                    # Interactive menu
/workflow:spec:load "api routing"      # Direct keyword search
/workflow:spec:load --type bug         # Filter by knowledge type
/workflow:spec:load --tag security     # Filter by domain tag
/workflow:spec:load --all              # Load all specs

Execution Process

Input Parsing:
   ├─ Parse --all flag → loadAll = true | false
   ├─ Parse --type (bug|pattern|decision|rule)
   ├─ Parse --tag (domain tag)
   └─ Parse keyword query (positional text)

Decision:
   ├─ --all → Load all specs (Path C)
   ├─ --type or --tag or keyword → Direct filter (Path B)
   └─ No args → Interactive menu (Path A)

Path A: Interactive Menu
   ├─ Step A1: Ask user intent
   ├─ Step A2: Route to action
   └─ Step A3: Display results

Path B: Direct Filter
   ├─ Step B1: Build filter from args
   ├─ Step B2: Search specs
   └─ Step B3: Display results

Path C: Load All
   └─ Display all spec contents

Output:
   └─ Formatted spec entries matching user query

Implementation

Step 1: Parse Input

const args = $ARGUMENTS
const argsLower = args.toLowerCase()

const loadAll = argsLower.includes('--all')
const hasType = argsLower.includes('--type')
const hasTag = argsLower.includes('--tag')

let type = hasType ? args.match(/--type\s+(\w+)/i)?.[1]?.toLowerCase() : null
let tag = hasTag ? args.match(/--tag\s+([\w-]+)/i)?.[1]?.toLowerCase() : null

// Extract keyword query (everything that's not a flag)
let keyword = args
  .replace(/--type\s+\w+/gi, '')
  .replace(/--tag\s+[\w-]+/gi, '')
  .replace(/--all/gi, '')
  .replace(/^["']|["']$/g, '')
  .trim()

// Validate type
if (type && !['bug', 'pattern', 'decision', 'rule'].includes(type)) {
  console.log("Invalid type. Use 'bug', 'pattern', 'decision', or 'rule'.")
  return
}

Step 2: Determine Mode

const useInteractive = !loadAll && !hasType && !hasTag && !keyword

Path A: Interactive Menu

if (useInteractive) {
  const answer = AskUserQuestion({
    questions: [{
      question: "What specs would you like to load?",
      header: "Action",
      multiSelect: false,
      options: [
        {
          label: "Browse all specs",
          description: "Load and display all project spec entries"
        },
        {
          label: "Search by keyword",
          description: "Find specs matching a keyword (e.g., api, security, routing)"
        },
        {
          label: "View bug experiences",
          description: "Load all [bug:*] debugging experience entries"
        },
        {
          label: "View code patterns",
          description: "Load all [pattern:*] reusable code pattern entries"
        }
      ]
    }]
  })

  const choice = answer.answers["Action"]

  if (choice === "Browse all specs") {
    loadAll = true
  } else if (choice === "View bug experiences") {
    type = "bug"
  } else if (choice === "View code patterns") {
    type = "pattern"
  } else if (choice === "Search by keyword") {
    // Ask for keyword
    const kwAnswer = AskUserQuestion({
      questions: [{
        question: "Enter keyword(s) to search for:",
        header: "Keyword",
        multiSelect: false,
        options: [
          { label: "api", description: "API endpoints, HTTP, REST, routing" },
          { label: "security", description: "Authentication, authorization, input validation" },
          { label: "arch", description: "Architecture, design patterns, module structure" },
          { label: "perf", description: "Performance, caching, optimization" }
        ]
      }]
    })
    keyword = kwAnswer.answers["Keyword"].toLowerCase()
  } else {
    // "Other" — user typed custom input, use as keyword
    keyword = choice.toLowerCase()
  }
}

Step 3: Load Spec Files

// Discover all spec files
const specFiles = [
  '.ccw/specs/coding-conventions.md',
  '.ccw/specs/architecture-constraints.md',
  '.ccw/specs/learnings.md',
  '.ccw/specs/quality-rules.md'
]

// Also check personal specs
const personalFiles = [
  '~/.ccw/personal/conventions.md',
  '~/.ccw/personal/constraints.md',
  '~/.ccw/personal/learnings.md',
  '.ccw/personal/conventions.md',
  '.ccw/personal/constraints.md',
  '.ccw/personal/learnings.md'
]

// Read all existing spec files
const allEntries = []

for (const file of [...specFiles, ...personalFiles]) {
  if (!file_exists(file)) continue
  const content = Read(file)

  // Extract entries using unified format regex
  // Entry line: - [type:tag] summary (date)
  // Extended:       - key: value
  const lines = content.split('\n')
  let currentEntry = null

  for (const line of lines) {
    const entryMatch = line.match(/^- \[(\w+):([\w-]+)\] (.*?)(?:\s+\((\d{4}-\d{2}-\d{2})\))?$/)
    if (entryMatch) {
      if (currentEntry) allEntries.push(currentEntry)
      currentEntry = {
        type: entryMatch[1],
        tag: entryMatch[2],
        summary: entryMatch[3],
        date: entryMatch[4] || null,
        extended: {},
        source: file,
        raw: line
      }
    } else if (currentEntry && /^\s{4}- ([\w-]+):\s?(.*)/.test(line)) {
      const fieldMatch = line.match(/^\s{4}- ([\w-]+):\s?(.*)/)
      currentEntry.extended[fieldMatch[1]] = fieldMatch[2]
    } else if (currentEntry && !/^\s{4}/.test(line) && line.trim() !== '') {
      // Non-indented non-empty line = end of current entry
      allEntries.push(currentEntry)
      currentEntry = null
    }

    // Also handle legacy format: - [tag] text (learned: date)
    const legacyMatch = line.match(/^- \[([\w-]+)\] (.+?)(?:\s+\(learned: (\d{4}-\d{2}-\d{2})\))?$/)
    if (!entryMatch && legacyMatch) {
      if (currentEntry) allEntries.push(currentEntry)
      currentEntry = {
        type: 'rule',
        tag: legacyMatch[1],
        summary: legacyMatch[2],
        date: legacyMatch[3] || null,
        extended: {},
        source: file,
        raw: line,
        legacy: true
      }
    }
  }
  if (currentEntry) allEntries.push(currentEntry)
}

Step 4: Filter Entries

let filtered = allEntries

// Filter by type
if (type) {
  filtered = filtered.filter(e => e.type === type)
}

// Filter by tag
if (tag) {
  filtered = filtered.filter(e => e.tag === tag)
}

// Filter by keyword (search in tag, summary, and extended fields)
if (keyword) {
  const kw = keyword.toLowerCase()
  const kwTerms = kw.split(/\s+/)

  filtered = filtered.filter(e => {
    const searchText = [
      e.type, e.tag, e.summary,
      ...Object.values(e.extended)
    ].join(' ').toLowerCase()

    return kwTerms.every(term => searchText.includes(term))
  })
}

// If --all, keep everything (no filter)

Step 5: Display Results

if (filtered.length === 0) {
  const filterDesc = []
  if (type) filterDesc.push(`type=${type}`)
  if (tag) filterDesc.push(`tag=${tag}`)
  if (keyword) filterDesc.push(`keyword="${keyword}"`)

  console.log(`
No specs found matching: ${filterDesc.join(', ') || '(all)'}

Available spec files:
${specFiles.filter(f => file_exists(f)).map(f => `  - ${f}`).join('\n') || '  (none)'}

Suggestions:
- Use /workflow:spec:setup to initialize specs
- Use /workflow:spec:add to add new entries
- Use /workflow:spec:load --all to see everything
`)
  return
}

// Group by source file
const grouped = {}
for (const entry of filtered) {
  if (!grouped[entry.source]) grouped[entry.source] = []
  grouped[entry.source].push(entry)
}

// Display
console.log(`
## Specs Loaded (${filtered.length} entries)
${type ? `Type: ${type}` : ''}${tag ? ` Tag: ${tag}` : ''}${keyword ? ` Keyword: "${keyword}"` : ''}
`)

for (const [source, entries] of Object.entries(grouped)) {
  console.log(`### ${source}`)
  console.log('')

  for (const entry of entries) {
    // Render entry
    const datePart = entry.date ? ` (${entry.date})` : ''
    console.log(`- [${entry.type}:${entry.tag}] ${entry.summary}${datePart}`)

    // Render extended fields
    for (const [key, value] of Object.entries(entry.extended)) {
      console.log(`    - ${key}: ${value}`)
    }
  }
  console.log('')
}

// Summary footer
const typeCounts = {}
for (const e of filtered) {
  typeCounts[e.type] = (typeCounts[e.type] || 0) + 1
}
const typeBreakdown = Object.entries(typeCounts)
  .map(([t, c]) => `${t}: ${c}`)
  .join(', ')

console.log(`---`)
console.log(`Total: ${filtered.length} entries (${typeBreakdown})`)
console.log(`Sources: ${Object.keys(grouped).join(', ')}`)

Examples

Interactive Browse

/workflow:spec:load
# → Menu: "What specs would you like to load?"
# → User selects "Browse all specs"
# → Displays all entries grouped by file
/workflow:spec:load "api routing"
# → Filters entries where tag/summary/extended contains "api" AND "routing"
# → Displays matching entries

Type Filter

/workflow:spec:load --type bug
# → Shows all [bug:*] entries from learnings.md

Tag Filter

/workflow:spec:load --tag security
# → Shows all [*:security] entries across all spec files

Combined Filters

/workflow:spec:load --type rule --tag api
# → Shows all [rule:api] entries

Load All

/workflow:spec:load --all
# → Displays every entry from every spec file

Error Handling

Error Resolution
No spec files found Suggest /workflow:spec:setup to initialize
No matching entries Show available files and suggest alternatives
Invalid type Exit with valid type list
Corrupt entry format Skip unparseable lines, continue loading
  • /workflow:spec:setup - Initialize project with specs scaffold
  • /workflow:spec:add - Add knowledge entries (bug/pattern/decision/rule) with unified [type:tag] format
  • /workflow:session:sync - Quick-sync session work to specs and project-tech
  • ccw spec list - View spec file index
  • ccw spec load - CLI-level spec loading (used by hooks)