@prmichaelsen/acp-visualizer 0.1.0

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.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,985 @@
1
+ #!/bin/bash
2
+ # Generic YAML Parser with AST
3
+ # Pure POSIX shell implementation
4
+ # Version: 1.0.0
5
+ # Created: 2026-02-21
6
+
7
+ # ============================================================================
8
+ # PORTABILITY HELPERS
9
+ # ============================================================================
10
+
11
+ # Portable in-place sed (works on both GNU and BSD/macOS sed)
12
+ # Usage: _yaml_sed_i "expression" "file"
13
+ _yaml_sed_i() {
14
+ if [ "$(uname)" = "Darwin" ]; then
15
+ sed -i '' "$@"
16
+ else
17
+ sed -i "$@"
18
+ fi
19
+ }
20
+
21
+ # ============================================================================
22
+ # GLOBAL STATE
23
+ # ============================================================================
24
+
25
+ # Prevent variable reset on re-sourcing
26
+ if [ -z "${YAML_PARSER_LOADED:-}" ]; then
27
+ YAML_PARSER_LOADED=1
28
+ AST_FILE=""
29
+ AST_ROOT_ID=0
30
+ YAML_CURRENT_FILE=""
31
+ fi
32
+
33
+ # ============================================================================
34
+ # UTILITY FUNCTIONS
35
+ # ============================================================================
36
+
37
+ init_ast() {
38
+ AST_FILE=$(mktemp)
39
+ echo "0|map||root|-1|" > "$AST_FILE"
40
+ AST_ROOT_ID=0
41
+ }
42
+
43
+ cleanup_ast() {
44
+ if [ -n "$AST_FILE" ] && [ -f "$AST_FILE" ]; then
45
+ rm -f "$AST_FILE"
46
+ fi
47
+ }
48
+
49
+ get_next_node_id() {
50
+ wc -l < "$AST_FILE"
51
+ }
52
+
53
+ create_node() {
54
+ local type="$1"
55
+ local key="$2"
56
+ local value="$3"
57
+ local parent_id="$4"
58
+
59
+ key=$(echo "$key" | sed 's/|/\\|/g')
60
+ value=$(echo "$value" | sed 's/|/\\|/g')
61
+
62
+ local node_id
63
+ node_id=$(get_next_node_id)
64
+
65
+ echo "$node_id|$type|$key|$value|$parent_id|" >> "$AST_FILE"
66
+ echo "$node_id"
67
+ }
68
+
69
+ get_node() {
70
+ local node_id="$1"
71
+ sed -n "$((node_id + 1))p" "$AST_FILE"
72
+ }
73
+
74
+ get_node_field() {
75
+ local node_id="$1"
76
+ local field_num="$2"
77
+ get_node "$node_id" | cut -d'|' -f"$field_num"
78
+ }
79
+
80
+ add_child() {
81
+ local parent_id="$1"
82
+ local child_id="$2"
83
+
84
+ local node
85
+ node=$(get_node "$parent_id")
86
+
87
+ local id type key value parent children
88
+ id=$(echo "$node" | cut -d'|' -f1)
89
+ type=$(echo "$node" | cut -d'|' -f2)
90
+ key=$(echo "$node" | cut -d'|' -f3)
91
+ value=$(echo "$node" | cut -d'|' -f4)
92
+ parent=$(echo "$node" | cut -d'|' -f5)
93
+ children=$(echo "$node" | cut -d'|' -f6)
94
+
95
+ if [ -z "$children" ]; then
96
+ children="$child_id"
97
+ else
98
+ children="$children,$child_id"
99
+ fi
100
+
101
+ local updated="$id|$type|$key|$value|$parent|$children"
102
+ _yaml_sed_i "$((parent_id + 1))s@.*@$updated@" "$AST_FILE"
103
+ }
104
+
105
+ update_node_type() {
106
+ local node_id="$1"
107
+ local new_type="$2"
108
+
109
+ local node
110
+ node=$(get_node "$node_id")
111
+
112
+ local id type key value parent children
113
+ id=$(echo "$node" | cut -d'|' -f1)
114
+ key=$(echo "$node" | cut -d'|' -f3)
115
+ value=$(echo "$node" | cut -d'|' -f4)
116
+ parent=$(echo "$node" | cut -d'|' -f5)
117
+ children=$(echo "$node" | cut -d'|' -f6)
118
+
119
+ local updated="$id|$new_type|$key|$value|$parent|$children"
120
+ _yaml_sed_i "$((node_id + 1))s@.*@$updated@" "$AST_FILE"
121
+ }
122
+
123
+ # ============================================================================
124
+ # PARSER
125
+ # ============================================================================
126
+
127
+ yaml_parse() {
128
+ local file="$1"
129
+
130
+ if [ ! -f "$file" ]; then
131
+ echo "Error: File not found: $file" >&2
132
+ return 1
133
+ fi
134
+
135
+ cleanup_ast
136
+ init_ast
137
+ YAML_CURRENT_FILE="$file"
138
+
139
+ # State tracking
140
+ local parent_stack="0"
141
+ local indent_stack="-1"
142
+ local current_parent=0
143
+ local prev_indent=-1
144
+ local last_key_node=-1
145
+
146
+ while IFS= read -r line || [ -n "$line" ]; do
147
+ # Skip empty lines
148
+ [ -z "$line" ] && continue
149
+
150
+ # Skip comment lines
151
+ case "$line" in \#*) continue ;; esac
152
+
153
+ # Strip inline comments
154
+ line=$(echo "$line" | sed 's/#.*$//')
155
+
156
+ # Calculate indentation
157
+ local indent=0
158
+ local trimmed="$line"
159
+ while [ "$trimmed" != "${trimmed# }" ]; do
160
+ indent=$((indent + 1))
161
+ trimmed="${trimmed# }"
162
+ done
163
+
164
+ # Skip empty after trim
165
+ [ -z "$trimmed" ] && continue
166
+
167
+ # Handle dedent - pop stack
168
+ while [ "$prev_indent" -ge 0 ] && [ "$indent" -le "$prev_indent" ]; do
169
+ # Pop one level
170
+ parent_stack=$(echo "$parent_stack" | sed 's/,[^,]*$//')
171
+ indent_stack=$(echo "$indent_stack" | sed 's/,[^,]*$//')
172
+
173
+ # Get new current parent
174
+ current_parent=$(echo "$parent_stack" | awk -F',' '{print $NF}')
175
+ prev_indent=$(echo "$indent_stack" | awk -F',' '{print $NF}')
176
+
177
+ # Handle empty stack
178
+ [ -z "$current_parent" ] && current_parent=0
179
+ [ -z "$prev_indent" ] && prev_indent=-1
180
+
181
+ last_key_node=-1
182
+ done
183
+
184
+ # Parse line content
185
+ if echo "$trimmed" | grep -q '^-[[:space:]]'; then
186
+ # Array item
187
+ local item_content
188
+ item_content=$(echo "$trimmed" | sed 's/^-[[:space:]]*//')
189
+
190
+ # Convert last key node to array if needed
191
+ if [ "$last_key_node" -ge 0 ]; then
192
+ update_node_type "$last_key_node" "array"
193
+ current_parent="$last_key_node"
194
+ last_key_node=-1
195
+ fi
196
+
197
+ # Check if inline object (has colon on same line)
198
+ if echo "$item_content" | grep -q ':'; then
199
+ # Inline object: - name: value
200
+ local obj_node
201
+ obj_node=$(create_node "map" "" "" "$current_parent")
202
+ add_child "$current_parent" "$obj_node"
203
+
204
+ # Parse first field
205
+ local key value
206
+ key=$(echo "$item_content" | cut -d':' -f1 | sed 's/[[:space:]]*$//')
207
+ value=$(echo "$item_content" | cut -d':' -f2- | sed 's/^[[:space:]]*//')
208
+
209
+ local field_node
210
+ field_node=$(create_node "scalar" "$key" "$value" "$obj_node")
211
+ add_child "$obj_node" "$field_node"
212
+
213
+ # Push object onto stack for potential additional fields
214
+ parent_stack="$parent_stack,$obj_node"
215
+ indent_stack="$indent_stack,$indent"
216
+ current_parent="$obj_node"
217
+ prev_indent="$indent"
218
+ else
219
+ # Simple array item: - value
220
+ local item_node
221
+ item_node=$(create_node "scalar" "" "$item_content" "$current_parent")
222
+ add_child "$current_parent" "$item_node"
223
+ fi
224
+ elif echo "$trimmed" | grep -q ':'; then
225
+ # Key-value pair
226
+ local key value
227
+ key=$(echo "$trimmed" | cut -d':' -f1 | sed 's/[[:space:]]*$//')
228
+ value=$(echo "$trimmed" | cut -d':' -f2- | sed 's/^[[:space:]]*//')
229
+
230
+ if [ -z "$value" ]; then
231
+ # Key with no value - map or array follows
232
+ local node_id
233
+ node_id=$(create_node "map" "$key" "" "$current_parent")
234
+ add_child "$current_parent" "$node_id"
235
+
236
+ # Push onto stack
237
+ parent_stack="$parent_stack,$node_id"
238
+ indent_stack="$indent_stack,$indent"
239
+ current_parent="$node_id"
240
+ prev_indent="$indent"
241
+ last_key_node="$node_id"
242
+ else
243
+ # Check for empty array [] or empty map {}
244
+ if [ "$value" = "[]" ]; then
245
+ # Empty array
246
+ local node_id
247
+ node_id=$(create_node "array" "$key" "" "$current_parent")
248
+ add_child "$current_parent" "$node_id"
249
+ elif [ "$value" = "{}" ]; then
250
+ # Empty map
251
+ local node_id
252
+ node_id=$(create_node "map" "$key" "" "$current_parent")
253
+ add_child "$current_parent" "$node_id"
254
+ else
255
+ # Scalar value
256
+ local node_id
257
+ node_id=$(create_node "scalar" "$key" "$value" "$current_parent")
258
+ add_child "$current_parent" "$node_id"
259
+ fi
260
+ fi
261
+ fi
262
+ done < "$file"
263
+
264
+ return 0
265
+ }
266
+
267
+ # ============================================================================
268
+ # QUERY ENGINE
269
+ # ============================================================================
270
+
271
+ find_child_by_key() {
272
+ local parent_id="$1"
273
+ local key="$2"
274
+
275
+ local children
276
+ children=$(get_node_field "$parent_id" 6)
277
+
278
+ [ -z "$children" ] && return 1
279
+
280
+ local IFS=','
281
+ for child_id in $children; do
282
+ local child_key
283
+ child_key=$(get_node_field "$child_id" 3)
284
+
285
+ if [ "$child_key" = "$key" ]; then
286
+ echo "$child_id"
287
+ return 0
288
+ fi
289
+ done
290
+
291
+ return 1
292
+ }
293
+
294
+ find_child_by_index() {
295
+ local parent_id="$1"
296
+ local index="$2"
297
+
298
+ local children
299
+ children=$(get_node_field "$parent_id" 6)
300
+
301
+ [ -z "$children" ] && return 1
302
+
303
+ local child_id
304
+ child_id=$(echo "$children" | tr ',' '\n' | sed -n "$((index + 1))p")
305
+
306
+ if [ -n "$child_id" ]; then
307
+ echo "$child_id"
308
+ return 0
309
+ fi
310
+
311
+ return 1
312
+ }
313
+
314
+ yaml_query() {
315
+ local path="$1"
316
+
317
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
318
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
319
+ return 1
320
+ fi
321
+
322
+ path=$(echo "$path" | sed 's/^\.//')
323
+
324
+ local current_node="$AST_ROOT_ID"
325
+
326
+ local IFS='.'
327
+ for segment in $path; do
328
+ if echo "$segment" | grep -q '\['; then
329
+ local key index
330
+ key=$(echo "$segment" | sed 's/\[.*//')
331
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
332
+
333
+ current_node=$(find_child_by_key "$current_node" "$key")
334
+ [ -z "$current_node" ] && return 1
335
+
336
+ current_node=$(find_child_by_index "$current_node" "$index")
337
+ [ -z "$current_node" ] && return 1
338
+ else
339
+ current_node=$(find_child_by_key "$current_node" "$segment")
340
+ [ -z "$current_node" ] && return 1
341
+ fi
342
+ done
343
+
344
+ # Check node type
345
+ local node_type
346
+ node_type=$(get_node_field "$current_node" 2)
347
+
348
+ # For map or array nodes, return children keys in YAML format
349
+ if [ "$node_type" = "map" ] || [ "$node_type" = "array" ]; then
350
+ local children
351
+ children=$(get_node_field "$current_node" 6)
352
+
353
+ if [ -n "$children" ]; then
354
+ # Split by comma and iterate
355
+ local IFS=','
356
+ for child_id in $children; do
357
+ local child_key
358
+ child_key=$(get_node_field "$child_id" 3)
359
+ echo "${child_key}:"
360
+ done
361
+ fi
362
+ else
363
+ # For scalar nodes, return the value
364
+ get_node_field "$current_node" 4
365
+ fi
366
+ }
367
+
368
+ # Create a new node in the AST (used by yaml_set for auto-creation)
369
+ # Usage: node_id=$(create_node_and_link "type" "key" "value" "parent_id")
370
+ # Returns: new node ID
371
+ # NOTE: This version adds the node as a child of parent (for yaml_set)
372
+ create_node_and_link() {
373
+ local type="$1"
374
+ local key="$2"
375
+ local value="$3"
376
+ local parent_id="$4"
377
+
378
+ # Get next node ID
379
+ local next_id
380
+ next_id=$(wc -l < "$AST_FILE")
381
+
382
+ # Create node: id|type|key|value|parent|children
383
+ echo "${next_id}|${type}|${key}|${value}|${parent_id}|" >> "$AST_FILE"
384
+
385
+ # Add this node to parent's children list
386
+ if [ "$parent_id" != "-1" ]; then
387
+ # Read current parent node
388
+ local parent_line
389
+ parent_line=$(sed -n "$((parent_id + 1))p" "$AST_FILE")
390
+
391
+ # Extract parent fields
392
+ local parent_children
393
+ parent_children=$(echo "$parent_line" | cut -d'|' -f6)
394
+
395
+ # Append new child ID
396
+ if [ -z "$parent_children" ]; then
397
+ parent_children="$next_id"
398
+ else
399
+ parent_children="${parent_children},${next_id}"
400
+ fi
401
+
402
+ # Update parent node with new children list
403
+ local parent_prefix
404
+ parent_prefix=$(echo "$parent_line" | cut -d'|' -f1-5)
405
+ _yaml_sed_i "$((parent_id + 1))s@.*@${parent_prefix}|${parent_children}@" "$AST_FILE"
406
+ fi
407
+
408
+ echo "$next_id"
409
+ }
410
+
411
+ # Original create_node for backward compatibility (does NOT link to parent)
412
+ create_node() {
413
+ local type="$1"
414
+ local key="$2"
415
+ local value="$3"
416
+ local parent_id="$4"
417
+
418
+ # Get next node ID
419
+ local next_id
420
+ next_id=$(wc -l < "$AST_FILE")
421
+
422
+ # Create node: id|type|key|value|parent|children
423
+ echo "${next_id}|${type}|${key}|${value}|${parent_id}|" >> "$AST_FILE"
424
+
425
+ echo "$next_id"
426
+ }
427
+
428
+ yaml_set() {
429
+ local path="$1"
430
+ local new_value="$2"
431
+
432
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
433
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
434
+ return 1
435
+ fi
436
+
437
+ path=$(echo "$path" | sed 's/^\.//')
438
+
439
+ local current_node="$AST_ROOT_ID"
440
+ local IFS='.'
441
+ local segments=($path)
442
+ local last_index=$((${#segments[@]} - 1))
443
+
444
+ # Traverse path, creating missing nodes
445
+ local i=0
446
+ for segment in "${segments[@]}"; do
447
+ local is_last=$((i == last_index))
448
+
449
+ if echo "$segment" | grep -q '\['; then
450
+ local key index
451
+ key=$(echo "$segment" | sed 's/\[.*//')
452
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
453
+
454
+ local child_node
455
+ child_node=$(find_child_by_key "$current_node" "$key")
456
+ if [ -z "$child_node" ]; then
457
+ # Create missing array node
458
+ child_node=$(create_node_and_link "array" "$key" "" "$current_node")
459
+ fi
460
+ current_node="$child_node"
461
+
462
+ child_node=$(find_child_by_index "$current_node" "$index")
463
+ if [ -z "$child_node" ]; then
464
+ echo "Error: Cannot create array index $index (not supported yet)" >&2
465
+ return 1
466
+ fi
467
+ current_node="$child_node"
468
+ else
469
+ local child_node
470
+ child_node=$(find_child_by_key "$current_node" "$segment")
471
+
472
+ if [ -z "$child_node" ]; then
473
+ # Create missing node
474
+ if [ "$is_last" -eq 1 ]; then
475
+ # Last segment - check for empty array/map
476
+ if [ "$new_value" = "[]" ]; then
477
+ # Create empty array
478
+ child_node=$(create_node_and_link "array" "$segment" "" "$current_node")
479
+ elif [ "$new_value" = "{}" ]; then
480
+ # Create empty map
481
+ child_node=$(create_node_and_link "map" "$segment" "" "$current_node")
482
+ else
483
+ # Create scalar with value
484
+ child_node=$(create_node_and_link "scalar" "$segment" "$new_value" "$current_node")
485
+ fi
486
+ return 0
487
+ else
488
+ # Intermediate segment - create map
489
+ child_node=$(create_node_and_link "map" "$segment" "" "$current_node")
490
+ fi
491
+ fi
492
+ current_node="$child_node"
493
+ fi
494
+
495
+ i=$((i + 1))
496
+ done
497
+
498
+ # Update existing node value
499
+ local node
500
+ node=$(get_node "$current_node")
501
+
502
+ local id type key value parent children
503
+ id=$(echo "$node" | cut -d'|' -f1)
504
+ type=$(echo "$node" | cut -d'|' -f2)
505
+ key=$(echo "$node" | cut -d'|' -f3)
506
+ parent=$(echo "$node" | cut -d'|' -f5)
507
+ children=$(echo "$node" | cut -d'|' -f6)
508
+
509
+ # Check if converting to empty array
510
+ if [ "$new_value" = "[]" ]; then
511
+ # Convert node to array type and clear children
512
+ local updated="$id|array|$key||$parent|"
513
+ _yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
514
+ else
515
+ new_value=$(echo "$new_value" | sed 's/|/\\|/g')
516
+ local updated="$id|$type|$key|$new_value|$parent|$children"
517
+ _yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
518
+ fi
519
+ }
520
+
521
+ yaml_write() {
522
+ local output_file="$1"
523
+
524
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
525
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
526
+ return 1
527
+ fi
528
+
529
+ serialize_node "$AST_ROOT_ID" 0 > "$output_file"
530
+ }
531
+
532
+ serialize_node() {
533
+ local node_id="$1"
534
+ local indent_level="$2"
535
+ local parent_type="${3:-}"
536
+
537
+ local node
538
+ node=$(get_node "$node_id")
539
+
540
+ local type key value children parent_id
541
+ type=$(echo "$node" | cut -d'|' -f2)
542
+ key=$(echo "$node" | cut -d'|' -f3)
543
+ value=$(echo "$node" | cut -d'|' -f4)
544
+ parent_id=$(echo "$node" | cut -d'|' -f5)
545
+ children=$(echo "$node" | cut -d'|' -f6)
546
+
547
+ # Determine parent type if not provided
548
+ if [ -z "$parent_type" ] && [ "$parent_id" -ge 0 ]; then
549
+ parent_type=$(get_node_field "$parent_id" 2)
550
+ fi
551
+
552
+ local indent=""
553
+ local i=0
554
+ while [ "$i" -lt "$indent_level" ]; do
555
+ indent="$indent "
556
+ i=$((i + 1))
557
+ done
558
+
559
+ case "$type" in
560
+ scalar)
561
+ if [ -n "$key" ]; then
562
+ echo "$indent$key: $value"
563
+ else
564
+ echo "$indent- $value"
565
+ fi
566
+ ;;
567
+
568
+ map)
569
+ # If this map is in an array, first child gets dash prefix
570
+ local is_first_child=true
571
+
572
+ if [ "$node_id" -ne 0 ] && [ -n "$key" ]; then
573
+ echo "$indent$key:"
574
+ fi
575
+
576
+ if [ -n "$children" ]; then
577
+ local IFS=','
578
+ local next_indent
579
+ # Root node (id=0) doesn't add indentation
580
+ if [ "$node_id" -eq 0 ]; then
581
+ next_indent="$indent_level"
582
+ else
583
+ next_indent="$((indent_level + 1))"
584
+ fi
585
+
586
+ for child_id in $children; do
587
+ # If parent is array and this is first child, use dash
588
+ if [ "$parent_type" = "array" ] && [ "$is_first_child" = true ]; then
589
+ # Serialize first field with dash
590
+ local child_node
591
+ child_node=$(get_node "$child_id")
592
+ local child_type child_key child_value
593
+ child_type=$(echo "$child_node" | cut -d'|' -f2)
594
+ child_key=$(echo "$child_node" | cut -d'|' -f3)
595
+ child_value=$(echo "$child_node" | cut -d'|' -f4)
596
+
597
+ if [ "$child_type" = "scalar" ] && [ -n "$child_key" ]; then
598
+ echo "$indent- $child_key: $child_value"
599
+ fi
600
+ is_first_child=false
601
+ else
602
+ serialize_node "$child_id" "$next_indent" "$type"
603
+ fi
604
+ done
605
+ fi
606
+ ;;
607
+
608
+ array)
609
+ if [ -n "$key" ]; then
610
+ echo "$indent$key:"
611
+ fi
612
+
613
+ if [ -n "$children" ]; then
614
+ local IFS=','
615
+ # Array children need to be indented
616
+ for child_id in $children; do
617
+ serialize_node "$child_id" "$((indent_level + 1))" "array"
618
+ done
619
+ fi
620
+ ;;
621
+ esac
622
+ }
623
+
624
+ # ============================================================================
625
+ # BACKWARD COMPATIBILITY
626
+ # ============================================================================
627
+
628
+ yaml_get() {
629
+ local file="$1"
630
+ local key="$2"
631
+
632
+ if [ "$YAML_CURRENT_FILE" != "$file" ]; then
633
+ yaml_parse "$file" || return 1
634
+ fi
635
+
636
+ yaml_query ".$key"
637
+ }
638
+
639
+ yaml_get_nested() {
640
+ local file="$1"
641
+ local path="$2"
642
+
643
+ if [ "$YAML_CURRENT_FILE" != "$file" ]; then
644
+ yaml_parse "$file" || return 1
645
+ fi
646
+
647
+ yaml_query ".$path"
648
+ }
649
+
650
+ # Check if key exists (checks if node exists, not if it has a value)
651
+ yaml_has_key() {
652
+ local file="$1"
653
+ local key="$2"
654
+
655
+ if [ "$YAML_CURRENT_FILE" != "$file" ]; then
656
+ yaml_parse "$file" || return 1
657
+ fi
658
+
659
+ # Try to find the node (returns empty string on failure, but exit code tells us)
660
+ path=$(echo "$key" | sed 's/^\.//')
661
+ local current_node="$AST_ROOT_ID"
662
+
663
+ local IFS='.'
664
+ for segment in $path; do
665
+ if echo "$segment" | grep -q '\['; then
666
+ local k index
667
+ k=$(echo "$segment" | sed 's/\[.*//')
668
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
669
+
670
+ current_node=$(find_child_by_key "$current_node" "$k" 2>/dev/null)
671
+ [ -z "$current_node" ] && return 1
672
+
673
+ current_node=$(find_child_by_index "$current_node" "$index" 2>/dev/null)
674
+ [ -z "$current_node" ] && return 1
675
+ else
676
+ current_node=$(find_child_by_key "$current_node" "$segment" 2>/dev/null)
677
+ [ -z "$current_node" ] && return 1
678
+ fi
679
+ done
680
+
681
+ # Node exists
682
+ return 0
683
+ }
684
+
685
+ # Get array count (for object arrays)
686
+ # Usage: yaml_get_array file.yaml "contents.commands"
687
+ # Returns: count of array elements
688
+ yaml_get_array() {
689
+ local file="$1"
690
+ local path="$2"
691
+
692
+ if [ "$YAML_CURRENT_FILE" != "$file" ]; then
693
+ yaml_parse "$file" || return 1
694
+ fi
695
+
696
+ # Find the array node
697
+ path=$(echo "$path" | sed 's/^\.//')
698
+ local current_node="$AST_ROOT_ID"
699
+
700
+ local IFS='.'
701
+ for segment in $path; do
702
+ current_node=$(find_child_by_key "$current_node" "$segment")
703
+ [ -z "$current_node" ] && return 1
704
+ done
705
+
706
+ # Get children count
707
+ local children
708
+ children=$(get_node_field "$current_node" 6)
709
+
710
+ if [ -z "$children" ]; then
711
+ echo "0"
712
+ else
713
+ echo "$children" | tr ',' '\n' | wc -l
714
+ fi
715
+ }
716
+
717
+ # Append scalar item to array
718
+ # Usage: yaml_array_append ".path.to.array" "value"
719
+ # Returns: node_id of new item
720
+ yaml_array_append() {
721
+ local path="$1"
722
+ local value="$2"
723
+
724
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
725
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
726
+ return 1
727
+ fi
728
+
729
+ # Find the array node
730
+ path=$(echo "$path" | sed 's/^\.//')
731
+ local current_node="$AST_ROOT_ID"
732
+
733
+ local IFS='.'
734
+ for segment in $path; do
735
+ if echo "$segment" | grep -q '\['; then
736
+ local key index
737
+ key=$(echo "$segment" | sed 's/\[.*//')
738
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
739
+
740
+ current_node=$(find_child_by_key "$current_node" "$key")
741
+ [ -z "$current_node" ] && return 1
742
+
743
+ current_node=$(find_child_by_index "$current_node" "$index")
744
+ [ -z "$current_node" ] && return 1
745
+ else
746
+ current_node=$(find_child_by_key "$current_node" "$segment")
747
+ [ -z "$current_node" ] && return 1
748
+ fi
749
+ done
750
+
751
+ # Verify it's an array
752
+ local node_type
753
+ node_type=$(get_node_field "$current_node" 2)
754
+
755
+ if [ "$node_type" != "array" ]; then
756
+ echo "Error: Path does not point to an array" >&2
757
+ return 1
758
+ fi
759
+
760
+ # Create new scalar node
761
+ local new_node
762
+ new_node=$(create_node "scalar" "" "$value" "$current_node")
763
+
764
+ # Add as child
765
+ add_child "$current_node" "$new_node"
766
+
767
+ echo "$new_node"
768
+ }
769
+
770
+ # Delete a node at the specified path
771
+ # Usage: yaml_delete ".path.to.node"
772
+ # Returns: 0 on success, 1 on failure
773
+ yaml_delete() {
774
+ local path="$1"
775
+
776
+ if [ -z "$path" ]; then
777
+ echo "Error: Path is required" >&2
778
+ return 1
779
+ fi
780
+
781
+ # Remove leading dot
782
+ path="${path#.}"
783
+
784
+ # Navigate to parent and get the key to delete
785
+ local parent_path=""
786
+ local key_to_delete=""
787
+
788
+ # Split path into parent and key
789
+ if echo "$path" | grep -q '\.'; then
790
+ parent_path=$(echo "$path" | sed 's/\.[^.]*$//')
791
+ key_to_delete=$(echo "$path" | sed 's/.*\.//')
792
+ else
793
+ parent_path=""
794
+ key_to_delete="$path"
795
+ fi
796
+
797
+ # Find parent node
798
+ local current_node=0
799
+ if [ -n "$parent_path" ]; then
800
+ local IFS='.'
801
+ for segment in $parent_path; do
802
+ if echo "$segment" | grep -q '\['; then
803
+ local key index
804
+ key=$(echo "$segment" | sed 's/\[.*//')
805
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
806
+
807
+ current_node=$(find_child_by_key "$current_node" "$key")
808
+ [ -z "$current_node" ] && return 1
809
+
810
+ current_node=$(find_child_by_index "$current_node" "$index")
811
+ [ -z "$current_node" ] && return 1
812
+ else
813
+ current_node=$(find_child_by_key "$current_node" "$segment")
814
+ [ -z "$current_node" ] && return 1
815
+ fi
816
+ done
817
+ fi
818
+
819
+ # Find the child node to delete
820
+ local node_to_delete
821
+ node_to_delete=$(find_child_by_key "$current_node" "$key_to_delete")
822
+
823
+ if [ -z "$node_to_delete" ]; then
824
+ echo "Error: Node not found: $path" >&2
825
+ return 1
826
+ fi
827
+
828
+ # Remove child from parent's children list
829
+ local parent_node
830
+ parent_node=$(get_node "$current_node")
831
+
832
+ local id type key value parent children
833
+ id=$(echo "$parent_node" | cut -d'|' -f1)
834
+ type=$(echo "$parent_node" | cut -d'|' -f2)
835
+ key=$(echo "$parent_node" | cut -d'|' -f3)
836
+ value=$(echo "$parent_node" | cut -d'|' -f4)
837
+ parent=$(echo "$parent_node" | cut -d'|' -f5)
838
+ children=$(echo "$parent_node" | cut -d'|' -f6)
839
+
840
+ # Remove node_to_delete from children list
841
+ local new_children=""
842
+ local IFS=','
843
+ for child_id in $children; do
844
+ if [ "$child_id" != "$node_to_delete" ]; then
845
+ if [ -z "$new_children" ]; then
846
+ new_children="$child_id"
847
+ else
848
+ new_children="$new_children,$child_id"
849
+ fi
850
+ fi
851
+ done
852
+
853
+ # Update parent node
854
+ local updated="$id|$type|$key|$value|$parent|$new_children"
855
+ _yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
856
+
857
+ return 0
858
+ }
859
+
860
+ # Append object to array
861
+ # Usage: yaml_array_append_object ".path.to.array"
862
+ # Returns: node_id of new object (use yaml_object_set to add fields)
863
+ yaml_array_append_object() {
864
+ local path="$1"
865
+
866
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
867
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
868
+ return 1
869
+ fi
870
+
871
+ # Find the node
872
+ path=$(echo "$path" | sed 's/^\.//')
873
+ local current_node="$AST_ROOT_ID"
874
+
875
+ local IFS='.'
876
+ for segment in $path; do
877
+ if echo "$segment" | grep -q '\['; then
878
+ local key index
879
+ key=$(echo "$segment" | sed 's/\[.*//')
880
+ index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
881
+
882
+ current_node=$(find_child_by_key "$current_node" "$key")
883
+ [ -z "$current_node" ] && return 1
884
+
885
+ current_node=$(find_child_by_index "$current_node" "$index")
886
+ [ -z "$current_node" ] && return 1
887
+ else
888
+ current_node=$(find_child_by_key "$current_node" "$segment")
889
+ [ -z "$current_node" ] && return 1
890
+ fi
891
+ done
892
+
893
+ # Check node type
894
+ local node_type
895
+ node_type=$(get_node_field "$current_node" 2)
896
+
897
+ # If it's a map with no children, convert to array
898
+ if [ "$node_type" = "map" ]; then
899
+ local children
900
+ children=$(get_node_field "$current_node" 6)
901
+ if [ -z "$children" ]; then
902
+ # Empty map - convert to array
903
+ update_node_type "$current_node" "array"
904
+ else
905
+ echo "Error: Path points to non-empty map, not array" >&2
906
+ return 1
907
+ fi
908
+ elif [ "$node_type" != "array" ]; then
909
+ echo "Error: Path does not point to an array" >&2
910
+ return 1
911
+ fi
912
+
913
+ # Create new map node (object)
914
+ local new_node
915
+ new_node=$(create_node "map" "" "" "$current_node")
916
+
917
+ # Add as child
918
+ add_child "$current_node" "$new_node"
919
+
920
+ echo "$new_node"
921
+ }
922
+
923
+ # Set field on object (for building objects in arrays)
924
+ # Usage: yaml_object_set node_id "field_name" "value"
925
+ yaml_object_set() {
926
+ local object_node="$1"
927
+ local field_name="$2"
928
+ local field_value="$3"
929
+
930
+ if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
931
+ echo "Error: No AST loaded. Call yaml_parse first." >&2
932
+ return 1
933
+ fi
934
+
935
+ # Create scalar field
936
+ local field_node
937
+ field_node=$(create_node "scalar" "$field_name" "$field_value" "$object_node")
938
+
939
+ # Add as child
940
+ add_child "$object_node" "$field_node"
941
+
942
+ echo "$field_node"
943
+ }
944
+
945
+ # ============================================================================
946
+ # MAIN
947
+ # ============================================================================
948
+
949
+ trap cleanup_ast EXIT INT TERM
950
+
951
+ # Only run main if script is executed directly (not sourced)
952
+ if [ -n "${1:-}" ] && [ "${1:-}" != "-" ] && [ "${BASH_SOURCE[0]}" = "${0}" ]; then
953
+ case "$1" in
954
+ parse)
955
+ yaml_parse "$2"
956
+ echo "✓ Parsed $2 ($(get_next_node_id) nodes)"
957
+ ;;
958
+ query)
959
+ yaml_parse "$2"
960
+ yaml_query "$3"
961
+ ;;
962
+ set)
963
+ yaml_parse "$2"
964
+ yaml_set "$3" "$4"
965
+ yaml_write "$2"
966
+ echo "✓ Updated $2"
967
+ ;;
968
+ debug)
969
+ yaml_parse "$2"
970
+ echo "AST Contents:"
971
+ cat "$AST_FILE"
972
+ ;;
973
+ *)
974
+ echo "Usage: $0 {parse|query|set|debug} file.yaml [path] [value]"
975
+ echo ""
976
+ echo "Examples:"
977
+ echo " $0 parse file.yaml"
978
+ echo " $0 query file.yaml .name"
979
+ echo " $0 query file.yaml .tags[0]"
980
+ echo " $0 set file.yaml .version 2.0.0"
981
+ echo " $0 debug file.yaml"
982
+ exit 1
983
+ ;;
984
+ esac
985
+ fi