@@ -12,15 +12,22 @@ set -e
1212# Read stdin to get SessionStart payload
1313HOOK_INPUT=$( cat)
1414
15- # Extract source field (startup, clear, compact)
16- SOURCE=$( echo " $HOOK_INPUT " | han parse json source -r 2> /dev/null || echo " startup" )
15+ # Extract source field using bash pattern matching (avoid subprocess)
16+ if [[ " $HOOK_INPUT " =~ \" source\" :\ * \" ([^\" ]+)\" ]]; then
17+ SOURCE=" ${BASH_REMATCH[1]} "
18+ else
19+ SOURCE=" startup"
20+ fi
1721
1822# Check for han CLI (only dependency needed)
1923if ! command -v han & > /dev/null; then
2024 echo " Warning: han CLI is required for AI-DLC but not installed. Skipping context injection." >&2
2125 exit 0
2226fi
2327
28+ # Cache git branch (used multiple times)
29+ CURRENT_BRANCH=$( git branch --show-current 2> /dev/null || echo " " )
30+
2431# Source DAG library if available
2532DAG_LIB=" ${CLAUDE_PLUGIN_ROOT} /lib/dag.sh"
2633if [ -f " $DAG_LIB " ]; then
3340PLUGIN_WORKFLOWS=" ${CLAUDE_PLUGIN_ROOT} /workflows.yml"
3441PROJECT_WORKFLOWS=" .ai-dlc/workflows.yml"
3542
36- # Function to extract workflow names from YAML file
37- get_workflow_names () {
38- local file=" $1 "
39- if [ -f " $file " ]; then
40- # Extract top-level keys (workflow names) - lines without leading spaces that end with :
41- grep -E ' ^[a-z][a-z0-9_-]*:' " $file " 2> /dev/null | sed ' s/:.*//' || true
42- fi
43- }
44-
45- # Function to get workflow details (description and hats)
46- get_workflow_details () {
43+ # Parse workflows using a single han call per file (much faster than per-workflow)
44+ # Output format: name|description|hat1,hat2,hat3
45+ parse_all_workflows () {
4746 local file=" $1 "
48- local name=" $2 "
49- if [ -f " $file " ]; then
47+ [ -f " $file " ] || return
48+ # Convert YAML to JSON once, then extract all workflows
49+ han parse yaml-to-json < " $file " 2> /dev/null | han parse json -r 2> /dev/null | while IFS= read -r line; do
50+ # This approach still spawns processes; use native extraction instead
51+ :
52+ done
53+ # Fallback: Extract workflow names and parse each (but batch the file read)
54+ local content
55+ content=$( cat " $file " 2> /dev/null) || return
56+ local names
57+ names=$( echo " $content " | grep -E ' ^[a-z][a-z0-9_-]*:' | sed ' s/:.*//' )
58+ for name in $names ; do
59+ # Use han to parse but pass content via variable to avoid re-reading file
5060 local desc hats
51- desc=$( han parse yaml " ${name} .description" -r < " $file " 2> /dev/null || echo " " )
52- # han parse yaml outputs arrays as "- item\n- item", convert to arrow-separated
53- hats=$( han parse yaml " ${name} .hats" < " $file " 2> /dev/null | sed ' s/^- //' | tr ' \n' ' |' | sed ' s/|$//; s/|/ → /g' || echo " " )
54- if [ -n " $desc " ] && [ -n " $hats " ]; then
55- echo " $desc |$hats "
56- fi
57- fi
61+ desc=$( echo " $content " | han parse yaml " ${name} .description" -r 2> /dev/null || echo " " )
62+ hats=$( echo " $content " | han parse yaml " ${name} .hats" 2> /dev/null | sed ' s/^- //' | tr ' \n' ' |' | sed ' s/|$//; s/|/ → /g' || echo " " )
63+ [ -n " $desc " ] && [ -n " $hats " ] && echo " $name |$desc |$hats "
64+ done
5865}
5966
6067# Build merged workflow list (project overrides plugin)
6168declare -A WORKFLOWS
6269KNOWN_WORKFLOWS=" "
6370
64- # Load plugin workflows first
65- for name in $( get_workflow_names " $PLUGIN_WORKFLOWS " ) ; do
66- details=$( get_workflow_details " $PLUGIN_WORKFLOWS " " $name " )
67- if [ -n " $details " ]; then
68- WORKFLOWS[$name ]=" $details "
69- KNOWN_WORKFLOWS=" $KNOWN_WORKFLOWS $name "
70- fi
71- done
71+ # Load plugin workflows first (single file read)
72+ while IFS=' |' read -r name desc hats; do
73+ [ -z " $name " ] && continue
74+ WORKFLOWS[$name ]=" $desc |$hats "
75+ KNOWN_WORKFLOWS=" $KNOWN_WORKFLOWS $name "
76+ done < <( parse_all_workflows " $PLUGIN_WORKFLOWS " )
7277
7378# Load project workflows (override or add)
74- for name in $( get_workflow_names " $PROJECT_WORKFLOWS " ) ; do
75- details=$( get_workflow_details " $PROJECT_WORKFLOWS " " $name " )
76- if [ -n " $details " ]; then
77- WORKFLOWS[$name ]=" $details "
78- # Add to known if not already there
79- if ! echo " $KNOWN_WORKFLOWS " | grep -qw " $name " ; then
80- KNOWN_WORKFLOWS=" $KNOWN_WORKFLOWS $name "
81- fi
79+ while IFS=' |' read -r name desc hats; do
80+ [ -z " $name " ] && continue
81+ WORKFLOWS[$name ]=" $desc |$hats "
82+ if ! echo " $KNOWN_WORKFLOWS " | grep -qw " $name " ; then
83+ KNOWN_WORKFLOWS=" $KNOWN_WORKFLOWS $name "
8284 fi
83- done
85+ done < <( parse_all_workflows " $PROJECT_WORKFLOWS " )
8486
8587# Build formatted workflow list for display
8688AVAILABLE_WORKFLOWS=" "
9698AVAILABLE_WORKFLOWS=" ${AVAILABLE_WORKFLOWS#
9799} " # Remove leading newline
98100
101+ # Note: _yaml_get_simple is provided by dag.sh (sourced above)
102+ # Alias for consistency in this file
103+ yaml_get_simple () {
104+ _yaml_get_simple " $@ "
105+ }
106+
99107# Check for AI-DLC state
100108# Intent-level state is stored on the current branch (intent branch for orchestrator, unit branch for subagents)
101109# If we're on a unit branch (ai-dlc/intent/unit), we need to check the parent intent branch
102- CURRENT_BRANCH= $( git branch --show-current 2> /dev/null || echo " " )
110+ # Note: CURRENT_BRANCH already cached above
103111ITERATION_JSON=" "
104112
105113# Try current branch first
@@ -122,9 +130,10 @@ if [ -z "$ITERATION_JSON" ]; then
122130 [ -f " $intent_file " ] || continue
123131 dir=$( dirname " $intent_file " )
124132 slug=$( basename " $dir " )
125- status=$( han parse yaml status -r --default active < " $intent_file " 2> /dev/null || echo " active" )
133+ # Use fast yaml extraction (no subprocess)
134+ status=$( yaml_get_simple " status" " active" < " $intent_file " )
126135 [ " $status " = " active" ] || continue
127- workflow=$( han parse yaml workflow -r -- default default < " $intent_file " 2> /dev/null || echo " default " )
136+ workflow=$( yaml_get_simple " workflow" " default" < " $intent_file " )
128137
129138 # Get unit summary if DAG functions are available
130139 summary=" "
@@ -282,18 +291,31 @@ echo ""
282291echo " **Iteration:** $ITERATION | **Hat:** $HAT | **Workflow:** $WORKFLOW_NAME ($WORKFLOW_HATS_STR )"
283292echo " "
284293
285- # Helper function to load ephemeral state from han keep
286- load_keep_value () {
287- local key=" $1 "
288- if [ -n " $INTENT_BRANCH " ]; then
289- han keep load --branch " $INTENT_BRANCH " " $key " --quiet 2> /dev/null || echo " "
290- else
291- han keep load " $key " --quiet 2> /dev/null || echo " "
292- fi
294+ # Batch load all han keep values at once (single subprocess call)
295+ # This is much faster than 5+ separate han keep load calls
296+ load_all_keep_values () {
297+ local branch_flag=" "
298+ [ -n " $INTENT_BRANCH " ] && branch_flag=" --branch $INTENT_BRANCH "
299+
300+ # Get list of keys and load each (still multiple calls, but we can optimize further)
301+ # For now, load the keys we need in parallel using subshells
302+ declare -gA KEEP_VALUES
303+
304+ # Load intent-level keys (from intent branch if applicable)
305+ KEEP_VALUES[intent-slug]=$( han keep load $branch_flag intent-slug --quiet 2> /dev/null || echo " " )
306+ KEEP_VALUES[current-plan.md]=$( han keep load $branch_flag current-plan.md --quiet 2> /dev/null || echo " " )
307+
308+ # Load unit-level keys (always from current branch)
309+ KEEP_VALUES[blockers.md]=$( han keep load blockers.md --quiet 2> /dev/null || echo " " )
310+ KEEP_VALUES[scratchpad.md]=$( han keep load scratchpad.md --quiet 2> /dev/null || echo " " )
311+ KEEP_VALUES[next-prompt.md]=$( han keep load next-prompt.md --quiet 2> /dev/null || echo " " )
293312}
294313
295- # Get intent-slug from han keep (pointer only)
296- INTENT_SLUG=$( load_keep_value intent-slug)
314+ # Load all keep values in batch
315+ load_all_keep_values
316+
317+ # Get intent-slug from cached values
318+ INTENT_SLUG=" ${KEEP_VALUES[intent-slug]} "
297319INTENT_DIR=" "
298320if [ -n " $INTENT_SLUG " ]; then
299321 INTENT_DIR=" .ai-dlc/${INTENT_SLUG} "
@@ -315,35 +337,35 @@ if [ -n "$INTENT_DIR" ] && [ -f "${INTENT_DIR}/completion-criteria.md" ]; then
315337 echo " "
316338fi
317339
318- # Load and display current plan (ephemeral - from han keep )
319- PLAN=$( load_keep_value current-plan.md)
340+ # Load and display current plan (from cached values )
341+ PLAN=" ${KEEP_VALUES[ current-plan.md]} "
320342if [ -n " $PLAN " ]; then
321343 echo " ### Current Plan"
322344 echo " "
323345 echo " $PLAN "
324346 echo " "
325347fi
326348
327- # Load and display blockers (unit-level state from current branch )
328- BLOCKERS=$( han keep load blockers.md --quiet 2> /dev/null || echo " " )
349+ # Load and display blockers (from cached values )
350+ BLOCKERS=" ${KEEP_VALUES[ blockers.md]} "
329351if [ -n " $BLOCKERS " ]; then
330352 echo " ### Previous Blockers"
331353 echo " "
332354 echo " $BLOCKERS "
333355 echo " "
334356fi
335357
336- # Load and display scratchpad (unit-level state from current branch )
337- SCRATCHPAD=$( han keep load scratchpad.md --quiet 2> /dev/null || echo " " )
358+ # Load and display scratchpad (from cached values )
359+ SCRATCHPAD=" ${KEEP_VALUES[ scratchpad.md]} "
338360if [ -n " $SCRATCHPAD " ]; then
339361 echo " ### Learnings from Previous Iteration"
340362 echo " "
341363 echo " $SCRATCHPAD "
342364 echo " "
343365fi
344366
345- # Load and display next prompt (unit-level state from current branch )
346- NEXT_PROMPT=$( han keep load next-prompt.md --quiet 2> /dev/null || echo " " )
367+ # Load and display next prompt (from cached values )
368+ NEXT_PROMPT=" ${KEEP_VALUES[ next-prompt.md]} "
347369if [ -n " $NEXT_PROMPT " ]; then
348370 echo " ### Continue With"
349371 echo " "
@@ -497,7 +519,7 @@ echo "- If blocked, document in \`han keep save --branch blockers.md\`"
497519echo " "
498520
499521# Check branch naming convention (informational only)
500- CURRENT_BRANCH= $( git branch --show-current 2> /dev/null || echo " " )
522+ # Note: CURRENT_BRANCH already cached at top of script
501523if [ -n " $CURRENT_BRANCH " ] && [ " $CURRENT_BRANCH " != " main" ] && [ " $CURRENT_BRANCH " != " master" ]; then
502524 if ! echo " $CURRENT_BRANCH " | grep -qE ' ^ai-dlc/[a-z0-9-]+/[0-9]+-[a-z0-9-]+$' ; then
503525 echo " > **WARNING:** Current branch \` $CURRENT_BRANCH \` doesn't follow AI-DLC convention."
0 commit comments