@leeovery/claude-technical-workflows 2.0.29 → 2.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leeovery/claude-technical-workflows",
3
- "version": "2.0.29",
3
+ "version": "2.0.30",
4
4
  "description": "Technical workflow skills & commands for Claude Code",
5
5
  "license": "MIT",
6
6
  "author": "Lee Overy <me@leeovery.com>",
@@ -20,6 +20,7 @@
20
20
  "skills",
21
21
  "commands",
22
22
  "agents",
23
- "hooks"
23
+ "hooks",
24
+ "scripts"
24
25
  ]
25
26
  }
@@ -0,0 +1,210 @@
1
+ #!/bin/bash
2
+ #
3
+ # discover-spec-state.sh
4
+ #
5
+ # Discovers the current state of discussions, specifications, and cache
6
+ # for the workflow:start-specification command.
7
+ #
8
+ # Outputs structured YAML that the command can consume directly.
9
+ #
10
+
11
+ set -eo pipefail
12
+
13
+ DISCUSSION_DIR="docs/workflow/discussion"
14
+ SPEC_DIR="docs/workflow/specification"
15
+ CACHE_FILE="docs/workflow/.cache/discussion-consolidation-analysis.md"
16
+
17
+ # Helper: Extract a frontmatter field value from a file
18
+ # Usage: extract_field <file> <field_name>
19
+ extract_field() {
20
+ local file="$1"
21
+ local field="$2"
22
+ # Read until --- (end of frontmatter), find field, extract value
23
+ # Use grep -m1 and || true to handle no match gracefully
24
+ sed -n '/^---$/,/^---$/p' "$file" 2>/dev/null | \
25
+ grep -m1 "^${field}:" | \
26
+ sed "s/^${field}:[[:space:]]*//" || true
27
+ }
28
+
29
+ # Helper: Extract array field from frontmatter (returns space-separated values)
30
+ # Usage: extract_array_field <file> <field_name>
31
+ extract_array_field() {
32
+ local file="$1"
33
+ local field="$2"
34
+ local result
35
+ # Look for field followed by array items (- item), excluding --- delimiters
36
+ result=$(sed -n '/^---$/,/^---$/p' "$file" 2>/dev/null | \
37
+ grep -v "^---$" | \
38
+ sed -n "/^${field}:/,/^[a-z_]*:/p" | \
39
+ grep "^[[:space:]]*-" | \
40
+ sed 's/^[[:space:]]*-[[:space:]]*//' | \
41
+ tr '\n' ' ' | \
42
+ sed 's/[[:space:]]*$//' || true)
43
+ echo "$result"
44
+ }
45
+
46
+ # Start YAML output
47
+ echo "# Specification Command State Discovery"
48
+ echo "# Generated: $(date -Iseconds)"
49
+ echo ""
50
+
51
+ #
52
+ # DISCUSSIONS
53
+ #
54
+ echo "discussions:"
55
+
56
+ if [ -d "$DISCUSSION_DIR" ] && [ -n "$(ls -A "$DISCUSSION_DIR" 2>/dev/null)" ]; then
57
+ for file in "$DISCUSSION_DIR"/*.md; do
58
+ [ -f "$file" ] || continue
59
+
60
+ name=$(basename "$file" .md)
61
+ status=$(extract_field "$file" "status")
62
+ status=${status:-"unknown"}
63
+
64
+ # Check if this discussion has a corresponding individual spec
65
+ has_individual_spec="false"
66
+ if [ -f "$SPEC_DIR/${name}.md" ]; then
67
+ has_individual_spec="true"
68
+ fi
69
+
70
+ echo " - name: \"$name\""
71
+ echo " status: \"$status\""
72
+ echo " has_individual_spec: $has_individual_spec"
73
+ done
74
+ else
75
+ echo " [] # No discussions found"
76
+ fi
77
+
78
+ echo ""
79
+
80
+ #
81
+ # SPECIFICATIONS
82
+ #
83
+ echo "specifications:"
84
+
85
+ if [ -d "$SPEC_DIR" ] && [ -n "$(ls -A "$SPEC_DIR" 2>/dev/null)" ]; then
86
+ for file in "$SPEC_DIR"/*.md; do
87
+ [ -f "$file" ] || continue
88
+
89
+ name=$(basename "$file" .md)
90
+ status=$(extract_field "$file" "status")
91
+ status=${status:-"active"}
92
+
93
+ superseded_by=$(extract_field "$file" "superseded_by")
94
+ sources=$(extract_array_field "$file" "sources")
95
+
96
+ echo " - name: \"$name\""
97
+ echo " status: \"$status\""
98
+
99
+ if [ -n "$superseded_by" ]; then
100
+ echo " superseded_by: \"$superseded_by\""
101
+ fi
102
+
103
+ if [ -n "$sources" ]; then
104
+ echo " sources:"
105
+ for src in $sources; do
106
+ echo " - \"$src\""
107
+ done
108
+ fi
109
+ done
110
+ else
111
+ echo " [] # No specifications found"
112
+ fi
113
+
114
+ echo ""
115
+
116
+ #
117
+ # CACHE STATE
118
+ #
119
+ echo "cache:"
120
+
121
+ if [ -f "$CACHE_FILE" ]; then
122
+ echo " exists: true"
123
+
124
+ cached_checksum=$(extract_field "$CACHE_FILE" "checksum")
125
+ cached_date=$(extract_field "$CACHE_FILE" "generated")
126
+
127
+ echo " cached_checksum: \"${cached_checksum:-unknown}\""
128
+ echo " cached_date: \"${cached_date:-unknown}\""
129
+
130
+ # Extract anchored names (groupings that have existing specs)
131
+ # These are the grouping names from the cache that have corresponding specs
132
+ echo " anchored_names:"
133
+
134
+ # Parse the cache file for grouping names (### Name format)
135
+ # and check if a spec exists for each
136
+ anchored_found=false
137
+ while IFS= read -r grouping_name; do
138
+ # Clean the name (remove any trailing annotations, lowercase, spaces to hyphens)
139
+ clean_name=$(echo "$grouping_name" | sed 's/[[:space:]]*(.*)//' | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
140
+ if [ -f "$SPEC_DIR/${clean_name}.md" ]; then
141
+ echo " - \"$clean_name\""
142
+ anchored_found=true
143
+ fi
144
+ done < <(grep "^### " "$CACHE_FILE" 2>/dev/null | sed 's/^### //' || true)
145
+
146
+ if [ "$anchored_found" = false ]; then
147
+ echo " [] # No anchored names found"
148
+ fi
149
+ else
150
+ echo " exists: false"
151
+ echo " cached_checksum: null"
152
+ echo " cached_date: null"
153
+ echo " anchored_names: []"
154
+ fi
155
+
156
+ echo ""
157
+
158
+ #
159
+ # CURRENT CHECKSUM
160
+ #
161
+ echo "current_state:"
162
+
163
+ if [ -d "$DISCUSSION_DIR" ] && [ -n "$(ls -A "$DISCUSSION_DIR" 2>/dev/null)" ]; then
164
+ # Compute checksum of all discussion files (deterministic via sorted glob)
165
+ current_checksum=$(cat "$DISCUSSION_DIR"/*.md 2>/dev/null | md5sum | cut -d' ' -f1)
166
+ echo " discussions_checksum: \"$current_checksum\""
167
+
168
+ # Count concluded discussions
169
+ concluded_count=0
170
+ for file in "$DISCUSSION_DIR"/*.md; do
171
+ [ -f "$file" ] || continue
172
+ status=$(extract_field "$file" "status")
173
+ if [ "$status" = "concluded" ]; then
174
+ concluded_count=$((concluded_count + 1))
175
+ fi
176
+ done
177
+ echo " concluded_discussion_count: $concluded_count"
178
+ else
179
+ echo " discussions_checksum: null"
180
+ echo " concluded_discussion_count: 0"
181
+ fi
182
+
183
+ echo ""
184
+
185
+ #
186
+ # CHECKSUM COMPARISON
187
+ #
188
+ echo "cache_validity:"
189
+
190
+ if [ -f "$CACHE_FILE" ]; then
191
+ cached_checksum=$(extract_field "$CACHE_FILE" "checksum")
192
+
193
+ if [ -d "$DISCUSSION_DIR" ] && [ -n "$(ls -A "$DISCUSSION_DIR" 2>/dev/null)" ]; then
194
+ current_checksum=$(cat "$DISCUSSION_DIR"/*.md 2>/dev/null | md5sum | cut -d' ' -f1)
195
+
196
+ if [ "$cached_checksum" = "$current_checksum" ]; then
197
+ echo " is_valid: true"
198
+ echo " reason: \"checksums match\""
199
+ else
200
+ echo " is_valid: false"
201
+ echo " reason: \"discussions have changed since cache was generated\""
202
+ fi
203
+ else
204
+ echo " is_valid: false"
205
+ echo " reason: \"no discussions to compare\""
206
+ fi
207
+ else
208
+ echo " is_valid: false"
209
+ echo " reason: \"no cache exists\""
210
+ fi