@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,1781 @@
1
+ #!/bin/sh
2
+ # Common utilities for ACP scripts
3
+ # POSIX-compliant for maximum portability
4
+
5
+ # Portable in-place sed (works on both GNU and BSD/macOS sed)
6
+ # Usage: _sed_i "expression" "file"
7
+ _sed_i() {
8
+ if [ "$(uname)" = "Darwin" ]; then
9
+ sed -i '' "$@"
10
+ else
11
+ sed -i "$@"
12
+ fi
13
+ }
14
+
15
+ # Initialize colors using tput (more reliable than ANSI codes)
16
+ init_colors() {
17
+ if command -v tput >/dev/null 2>&1 && [ -t 1 ]; then
18
+ RED=$(tput setaf 1)
19
+ GREEN=$(tput setaf 2)
20
+ YELLOW=$(tput setaf 3)
21
+ BLUE=$(tput setaf 4)
22
+ BOLD=$(tput bold)
23
+ NC=$(tput sgr0)
24
+ else
25
+ RED=''
26
+ GREEN=''
27
+ YELLOW=''
28
+ BLUE=''
29
+ BOLD=''
30
+ NC=''
31
+ fi
32
+ }
33
+
34
+ # Calculate file checksum (SHA-256)
35
+ # Usage: calculate_checksum "path/to/file"
36
+ # Returns: checksum string (without "sha256:" prefix)
37
+ calculate_checksum() {
38
+ local file="$1"
39
+ if [ ! -f "$file" ]; then
40
+ echo "Error: File not found: $file" >&2
41
+ return 1
42
+ fi
43
+ if command -v sha256sum >/dev/null 2>&1; then
44
+ sha256sum "$file" 2>/dev/null | cut -d' ' -f1
45
+ elif command -v shasum >/dev/null 2>&1; then
46
+ shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1
47
+ else
48
+ echo "unknown"
49
+ fi
50
+ }
51
+
52
+ # Get current timestamp in ISO 8601 format (UTC)
53
+ # Usage: timestamp=$(get_timestamp)
54
+ # Returns: YYYY-MM-DDTHH:MM:SSZ
55
+ get_timestamp() {
56
+ date -u +"%Y-%m-%dT%H:%M:%SZ"
57
+ }
58
+
59
+ # Validate URL format
60
+ # Usage: if validate_url "$url"; then ...
61
+ # Returns: 0 if valid, 1 if invalid
62
+ validate_url() {
63
+ local url="$1"
64
+ case "$url" in
65
+ http://*|https://*)
66
+ return 0
67
+ ;;
68
+ *)
69
+ return 1
70
+ ;;
71
+ esac
72
+ }
73
+
74
+ # Get script directory (portable way)
75
+ # Usage: script_dir=$(get_script_dir)
76
+ get_script_dir() {
77
+ # Get the directory of the calling script
78
+ dirname "$0"
79
+ }
80
+
81
+ # Source YAML parser
82
+ # Usage: source_yaml_parser
83
+ source_yaml_parser() {
84
+ # Check if already loaded (don't re-source to preserve AST_FILE)
85
+ if [ -n "${YAML_PARSER_LOADED:-}" ]; then
86
+ return 0
87
+ fi
88
+
89
+ # Try to find acp.yaml-parser.sh in multiple locations
90
+ local parser_locations=(
91
+ "$(dirname "${BASH_SOURCE[0]}")/acp.yaml-parser.sh"
92
+ "agent/scripts/acp.yaml-parser.sh"
93
+ "./agent/scripts/acp.yaml-parser.sh"
94
+ "../agent/scripts/acp.yaml-parser.sh"
95
+ )
96
+
97
+ for parser_path in "${parser_locations[@]}"; do
98
+ if [ -f "$parser_path" ]; then
99
+ . "$parser_path"
100
+ return 0
101
+ fi
102
+ done
103
+
104
+ echo "${RED}Error: acp.yaml-parser.sh not found${NC}" >&2
105
+ return 1
106
+ }
107
+
108
+ # Initialize manifest file if it doesn't exist
109
+ # Usage: init_manifest
110
+ init_manifest() {
111
+ if [ ! -f "agent/manifest.yaml" ]; then
112
+ cat > agent/manifest.yaml << 'EOF'
113
+ # ACP Package Manifest
114
+ # Tracks installed packages and their versions
115
+
116
+ packages: {}
117
+
118
+ manifest_version: 1.0.0
119
+ last_updated: null
120
+ EOF
121
+ echo "${GREEN}✓${NC} Created agent/manifest.yaml"
122
+ fi
123
+ }
124
+
125
+ # Validate manifest structure
126
+ # Usage: if validate_manifest; then ...
127
+ # Returns: 0 if valid, 1 if invalid
128
+ validate_manifest() {
129
+ local manifest="agent/manifest.yaml"
130
+
131
+ if [ ! -f "$manifest" ]; then
132
+ echo "${RED}Error: Manifest not found${NC}" >&2
133
+ return 1
134
+ fi
135
+
136
+ # Source YAML parser if not already loaded
137
+ if ! command -v yaml_get >/dev/null 2>&1; then
138
+ source_yaml_parser || return 1
139
+ fi
140
+
141
+ # Check required fields
142
+ local manifest_version
143
+ manifest_version=$(yaml_get "$manifest" "manifest_version" 2>/dev/null)
144
+
145
+ if [ -z "$manifest_version" ] || [ "$manifest_version" = "null" ]; then
146
+ echo "${RED}Error: manifest_version missing${NC}" >&2
147
+ return 1
148
+ fi
149
+
150
+ echo "${GREEN}✓${NC} Manifest valid"
151
+ return 0
152
+ }
153
+
154
+ # Update manifest last_updated timestamp
155
+ # Usage: update_manifest_timestamp
156
+ update_manifest_timestamp() {
157
+ local manifest="agent/manifest.yaml"
158
+ local timestamp
159
+ timestamp=$(get_timestamp)
160
+
161
+ # Update timestamp using sed
162
+ _sed_i "s/^last_updated: .*/last_updated: $timestamp/" "$manifest"
163
+ }
164
+
165
+ # Check if package exists in manifest
166
+ # Usage: if package_exists "package-name" ["manifest-path"]; then ...
167
+ # Returns: 0 if exists, 1 if not
168
+ package_exists() {
169
+ local package_name="$1"
170
+ local manifest="${2:-agent/manifest.yaml}"
171
+
172
+ # Source YAML parser if not already loaded
173
+ if ! command -v yaml_has_key >/dev/null 2>&1; then
174
+ source_yaml_parser || return 1
175
+ fi
176
+
177
+ yaml_has_key "$manifest" "packages.${package_name}.source"
178
+ }
179
+
180
+ # ============================================================================
181
+ # Global Manifest Functions
182
+ # ============================================================================
183
+
184
+ # Get global manifest path
185
+ # Usage: manifest_path=$(get_global_manifest_path)
186
+ # Returns: Path to global manifest
187
+ get_global_manifest_path() {
188
+ echo "$HOME/.acp/agent/manifest.yaml"
189
+ }
190
+
191
+ # Check if global manifest exists
192
+ # Usage: if global_manifest_exists; then ...
193
+ # Returns: 0 if exists, 1 if not
194
+ global_manifest_exists() {
195
+ local manifest_path
196
+ manifest_path=$(get_global_manifest_path)
197
+ [ -f "$manifest_path" ]
198
+ }
199
+
200
+ # Initialize global manifest if it doesn't exist
201
+ # Usage: init_global_manifest
202
+ init_global_manifest() {
203
+ local manifest_path
204
+ manifest_path=$(get_global_manifest_path)
205
+
206
+ if [ -f "$manifest_path" ]; then
207
+ return 0
208
+ fi
209
+
210
+ # Create ~/.acp directory if needed
211
+ mkdir -p "$HOME/.acp/projects"
212
+
213
+ # Create manifest
214
+ local timestamp
215
+ timestamp=$(get_timestamp)
216
+
217
+ cat > "$manifest_path" << EOF
218
+ # Global ACP Package Manifest
219
+ # This file tracks all globally installed ACP packages
220
+
221
+ version: 1.0.0
222
+ updated: $timestamp
223
+
224
+ packages: {}
225
+ EOF
226
+
227
+ success "Initialized global manifest at $manifest_path"
228
+ }
229
+
230
+ # Read global manifest (returns full content)
231
+ # Usage: content=$(read_global_manifest)
232
+ read_global_manifest() {
233
+ local manifest_path
234
+ manifest_path=$(get_global_manifest_path)
235
+
236
+ if [ ! -f "$manifest_path" ]; then
237
+ echo "Error: Global manifest not found at $manifest_path" >&2
238
+ return 1
239
+ fi
240
+
241
+ cat "$manifest_path"
242
+ }
243
+
244
+ # Update global manifest timestamp
245
+ # Usage: update_global_manifest_timestamp
246
+ update_global_manifest_timestamp() {
247
+ local manifest_path
248
+ manifest_path=$(get_global_manifest_path)
249
+
250
+ if [ ! -f "$manifest_path" ]; then
251
+ echo "Error: Global manifest not found" >&2
252
+ return 1
253
+ fi
254
+
255
+ # Update timestamp using sed
256
+ local timestamp
257
+ timestamp=$(get_timestamp)
258
+ _sed_i "s/^updated: .*/updated: $timestamp/" "$manifest_path"
259
+ }
260
+
261
+ # Check if package exists in global manifest
262
+ # Usage: if global_package_exists "package-name"; then ...
263
+ # Returns: 0 if exists, 1 if not
264
+ global_package_exists() {
265
+ local package_name="$1"
266
+ local manifest_path
267
+ manifest_path=$(get_global_manifest_path)
268
+
269
+ if [ ! -f "$manifest_path" ]; then
270
+ return 1
271
+ fi
272
+
273
+ # Check if package exists in manifest
274
+ grep -q "^ $package_name:" "$manifest_path"
275
+ }
276
+
277
+ # Get global package location
278
+ # Usage: location=$(get_global_package_location "package-name")
279
+ # Returns: Package installation path
280
+ get_global_package_location() {
281
+ local package_name="$1"
282
+ local manifest_path
283
+ manifest_path=$(get_global_manifest_path)
284
+
285
+ if [ ! -f "$manifest_path" ]; then
286
+ return 1
287
+ fi
288
+
289
+ # Extract location using awk
290
+ awk -v pkg="$package_name" '
291
+ $0 ~ "^ " pkg ":" { in_package=1; next }
292
+ in_package && /^ location:/ { print $2; exit }
293
+ /^ [a-z]/ && in_package { exit }
294
+ ' "$manifest_path"
295
+ }
296
+
297
+ # Initialize global ACP infrastructure if it doesn't exist
298
+ # This function is idempotent - safe to call multiple times
299
+ # Usage: init_global_acp
300
+ # Returns: 0 on success, 1 on failure
301
+ init_global_acp() {
302
+ local global_dir="$HOME/.acp"
303
+
304
+ # Check if already initialized
305
+ if [ -d "$global_dir/agent" ] && [ -f "$global_dir/AGENT.md" ]; then
306
+ return 0 # Already initialized, nothing to do
307
+ fi
308
+
309
+ echo "${BLUE}Initializing global ACP infrastructure at ~/.acp/...${NC}"
310
+ echo ""
311
+
312
+ # Create ~/.acp directory
313
+ mkdir -p "$global_dir"
314
+
315
+ # Create .gitignore for global ACP directory
316
+ if [ ! -f "$global_dir/.gitignore" ]; then
317
+ cat > "$global_dir/.gitignore" << 'GITIGNORE'
318
+ # Project repos have their own git
319
+ projects/
320
+
321
+ # Claude Code session data
322
+ .claude/
323
+
324
+ # Common noise
325
+ *.log
326
+ node_modules/
327
+ GITIGNORE
328
+ fi
329
+
330
+ # Get the directory where this script is located
331
+ local script_dir
332
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
333
+
334
+ # Run standard ACP installation in ~/.acp/
335
+ # This installs all templates, scripts, and schemas
336
+ if [ -f "$script_dir/acp.install.sh" ]; then
337
+ # Use local install script
338
+ (
339
+ cd "$global_dir" || exit 1
340
+ bash "$script_dir/acp.install.sh"
341
+ ) || {
342
+ echo "${RED}Error: Failed to initialize global ACP infrastructure${NC}" >&2
343
+ return 1
344
+ }
345
+ else
346
+ # Fallback: Download from repository
347
+ (
348
+ cd "$global_dir" || exit 1
349
+ curl -fsSL https://raw.githubusercontent.com/prmichaelsen/agent-context-protocol/mainline/agent/scripts/acp.install.sh | bash
350
+ ) || {
351
+ echo "${RED}Error: Failed to initialize global ACP infrastructure${NC}" >&2
352
+ return 1
353
+ }
354
+ fi
355
+
356
+ # Create additional global directories
357
+ mkdir -p "$global_dir/projects"
358
+
359
+ # Initialize global manifest if it doesn't exist
360
+ if [ ! -f "$global_dir/agent/manifest.yaml" ]; then
361
+ init_global_manifest
362
+ fi
363
+
364
+ # Initialize projects registry
365
+ if [ ! -f "$HOME/.acp/projects.yaml" ]; then
366
+ init_projects_registry
367
+ echo "${GREEN}✓${NC} Initialized projects registry"
368
+ fi
369
+
370
+ # Append global installation notes to AGENT.md
371
+ if [ -f "$global_dir/AGENT.md" ] && ! grep -q "## Global Installation" "$global_dir/AGENT.md"; then
372
+ cat >> "$global_dir/AGENT.md" << 'EOF'
373
+
374
+ ---
375
+
376
+ ## Global Installation
377
+
378
+ This is a global ACP installation located at `~/.acp/`.
379
+
380
+ ### Purpose
381
+
382
+ This installation provides:
383
+ - **Global packages** in `~/.acp/agent/` - Packages installed with `@acp.package-install --global`
384
+ - **Project workspace** in `~/.acp/projects/` - Optional location for package development
385
+ - **Global manifest** in `~/.acp/agent/manifest.yaml` - Tracks globally installed packages
386
+ - **Templates and scripts** in `~/.acp/agent/` - All ACP templates and utilities
387
+
388
+ ### Usage
389
+
390
+ **Install packages globally**:
391
+ ```bash
392
+ @acp.package-install --global https://github.com/user/acp-package.git
393
+ ```
394
+
395
+ **Create packages**:
396
+ ```bash
397
+ cd ~/.acp/projects
398
+ @acp.package-create
399
+ ```
400
+
401
+ **List global packages**:
402
+ ```bash
403
+ @acp.package-list --global
404
+ ```
405
+
406
+ ### Discovery
407
+
408
+ Agents can discover globally installed packages by reading `~/.acp/agent/manifest.yaml`. Local packages always take precedence over global packages.
409
+ EOF
410
+ fi
411
+
412
+ echo ""
413
+ echo "${GREEN}✓ Global ACP infrastructure initialized${NC}"
414
+ echo ""
415
+ echo "Location: $global_dir"
416
+ echo "Templates: $global_dir/agent/"
417
+ echo "Projects: $global_dir/projects/"
418
+ echo ""
419
+ }
420
+
421
+ # Print error message and exit
422
+ # Usage: die "Error message"
423
+ die() {
424
+ echo "${RED}Error: $1${NC}" >&2
425
+ exit 1
426
+ }
427
+
428
+ # Print warning message
429
+ # Usage: warn "Warning message"
430
+ warn() {
431
+ echo "${YELLOW}Warning: $1${NC}" >&2
432
+ }
433
+
434
+ # Print success message
435
+ # Usage: success "Success message"
436
+ success() {
437
+ echo "${GREEN}✓${NC} $1"
438
+ }
439
+
440
+ # Print info message
441
+ # Usage: info "Info message"
442
+ info() {
443
+ echo "${BLUE}ℹ${NC} $1"
444
+ }
445
+
446
+ # Remove deprecated script files (from versions < 2.0.0)
447
+ # Usage: cleanup_deprecated_scripts
448
+ cleanup_deprecated_scripts() {
449
+ local deprecated_scripts=(
450
+ "check-for-updates.sh"
451
+ "common.sh"
452
+ "install.sh"
453
+ "package-install.sh"
454
+ "uninstall.sh"
455
+ "update.sh"
456
+ "version.sh"
457
+ "yaml.sh"
458
+ )
459
+
460
+ local removed_count=0
461
+ for script in "${deprecated_scripts[@]}"; do
462
+ if [ -f "agent/scripts/$script" ]; then
463
+ rm "agent/scripts/$script"
464
+ warn "Removed deprecated script: $script"
465
+ removed_count=$((removed_count + 1))
466
+ fi
467
+ done
468
+
469
+ if [ $removed_count -gt 0 ]; then
470
+ success "Cleaned up $removed_count deprecated script(s)"
471
+ fi
472
+ }
473
+
474
+ # Parse package.yaml from repository
475
+ # Usage: parse_package_metadata "repo_dir"
476
+ # Sets global variables: PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DESCRIPTION
477
+ parse_package_metadata() {
478
+ local repo_dir="$1"
479
+ local package_yaml="${repo_dir}/package.yaml"
480
+
481
+ if [ ! -f "$package_yaml" ]; then
482
+ warn "package.yaml not found in repository"
483
+ PACKAGE_NAME="unknown"
484
+ PACKAGE_VERSION="0.0.0"
485
+ PACKAGE_DESCRIPTION="No description"
486
+ return 1
487
+ fi
488
+
489
+ # Source YAML parser if not already loaded
490
+ if ! command -v yaml_get >/dev/null 2>&1; then
491
+ source_yaml_parser || return 1
492
+ fi
493
+
494
+ # Extract metadata
495
+ PACKAGE_NAME=$(yaml_get "$package_yaml" "name" 2>/dev/null || echo "unknown")
496
+ PACKAGE_VERSION=$(yaml_get "$package_yaml" "version" 2>/dev/null || echo "0.0.0")
497
+ PACKAGE_DESCRIPTION=$(yaml_get "$package_yaml" "description" 2>/dev/null || echo "No description")
498
+
499
+ info "Package: $PACKAGE_NAME"
500
+ info "Version: $PACKAGE_VERSION"
501
+ info "Description: $PACKAGE_DESCRIPTION"
502
+
503
+ return 0
504
+ }
505
+
506
+ # Get file version from package.yaml
507
+ # Usage: get_file_version "package.yaml" "patterns" "filename.md"
508
+ # Returns: version string or "0.0.0" if not found
509
+ get_file_version() {
510
+ local package_yaml="$1"
511
+ local file_type="$2"
512
+ local file_name="$3"
513
+
514
+ if [ ! -f "$package_yaml" ]; then
515
+ echo "0.0.0"
516
+ return 0
517
+ fi
518
+
519
+ # Use awk to parse YAML array (acp.yaml.sh doesn't support array queries)
520
+ local version
521
+ version=$(awk -v type="$file_type" -v name="$file_name" '
522
+ BEGIN { in_section=0; in_item=0 }
523
+ /^ [a-z_]+:/ { in_section=0 }
524
+ $0 ~ "^ " type ":" { in_section=1; next }
525
+ in_section && /^ - name:/ {
526
+ if ($3 == name) { in_item=1 }
527
+ else { in_item=0 }
528
+ next
529
+ }
530
+ in_section && in_item && /^ version:/ {
531
+ print $2
532
+ exit
533
+ }
534
+ ' "$package_yaml")
535
+
536
+ if [ -z "$version" ]; then
537
+ echo "0.0.0"
538
+ else
539
+ echo "$version"
540
+ fi
541
+
542
+ return 0
543
+ }
544
+
545
+ # Add package to manifest
546
+ # Usage: add_package_to_manifest "package_name" "source_url" "version" "commit_hash"
547
+ add_package_to_manifest() {
548
+ local package_name="$1"
549
+ local source_url="$2"
550
+ local package_version="$3"
551
+ local commit_hash="$4"
552
+ local timestamp
553
+ timestamp=$(get_timestamp)
554
+
555
+ local manifest="agent/manifest.yaml"
556
+
557
+ # Add package metadata using direct YAML appending (new parser doesn't support yaml_set for new keys)
558
+ # Check if package already exists
559
+ if grep -q "^ ${package_name}:" "$manifest" 2>/dev/null; then
560
+ # Update existing package
561
+ _sed_i "/^ ${package_name}:/,/^ [a-z]/ {
562
+ s|source: .*|source: $source_url|
563
+ s|package_version: .*|package_version: $package_version|
564
+ s|commit: .*|commit: $commit_hash|
565
+ s|updated_at: .*|updated_at: $timestamp|
566
+ }" "$manifest"
567
+ else
568
+ # Add new package entry
569
+ # Find the packages: line and append after it
570
+ awk -v pkg="$package_name" -v src="$source_url" -v ver="$package_version" -v commit="$commit_hash" -v ts="$timestamp" '
571
+ /^packages:/ {
572
+ if ($2 == "{}") {
573
+ # Empty packages - replace {} with just "packages:"
574
+ print "packages:"
575
+ } else {
576
+ print
577
+ }
578
+ print " " pkg ":"
579
+ print " source: " src
580
+ print " package_version: " ver
581
+ print " commit: " commit
582
+ print " installed_at: " ts
583
+ print " updated_at: " ts
584
+ print " files:"
585
+ print " patterns: []"
586
+ print " commands: []"
587
+ print " designs: []"
588
+ print " scripts: []"
589
+ print " files: []"
590
+ print " indices: []"
591
+ next
592
+ }
593
+ { print }
594
+ ' "$manifest" > "$manifest.tmp" && mv "$manifest.tmp" "$manifest"
595
+ fi
596
+
597
+ # Update manifest timestamp
598
+ update_manifest_timestamp
599
+
600
+ success "Added package $package_name to manifest"
601
+ }
602
+
603
+ # Add file to manifest
604
+ # Usage: add_file_to_manifest "package_name" "file_type" "filename" "version" "file_path"
605
+ # file_type: patterns, commands, designs
606
+ add_file_to_manifest() {
607
+ local package_name="$1"
608
+ local file_type="$2"
609
+ local filename="$3"
610
+ local file_version="$4"
611
+ local file_path="$5"
612
+ local package_yaml_path="$6" # Optional: path to package.yaml for experimental checking
613
+ local timestamp
614
+ timestamp=$(get_timestamp)
615
+
616
+ local manifest="agent/manifest.yaml"
617
+
618
+ # Calculate checksum
619
+ local checksum
620
+ checksum=$(calculate_checksum "$file_path")
621
+
622
+ if [ $? -ne 0 ]; then
623
+ warn "Failed to calculate checksum for $filename"
624
+ checksum="unknown"
625
+ fi
626
+
627
+ # Check if experimental (if package.yaml provided)
628
+ local is_experimental=""
629
+ if [ -n "$package_yaml_path" ] && [ -f "$package_yaml_path" ]; then
630
+ is_experimental=$(grep -A 1000 "^ ${file_type}:" "$package_yaml_path" 2>/dev/null | grep -A 2 "name: ${filename}" | grep "^ *experimental: true" | grep -v "^[[:space:]]*#" | head -1)
631
+ fi
632
+
633
+ # Source YAML parser
634
+ if ! command -v yaml_parse >/dev/null 2>&1; then
635
+ source_yaml_parser || return 1
636
+ fi
637
+
638
+ # Convert empty arrays [] to proper format first (workaround for parser limitation)
639
+ _sed_i "s/^ ${file_type}: \\[\\]$/ ${file_type}:/" "$manifest"
640
+
641
+ # Parse manifest
642
+ yaml_parse "$manifest"
643
+
644
+ # Append object to array
645
+ local obj_node
646
+ obj_node=$(yaml_array_append_object ".packages.${package_name}.files.${file_type}")
647
+
648
+ # Set object fields
649
+ yaml_object_set "$obj_node" "name" "$filename" >/dev/null
650
+ yaml_object_set "$obj_node" "version" "$file_version" >/dev/null
651
+ yaml_object_set "$obj_node" "installed_at" "$timestamp" >/dev/null
652
+ yaml_object_set "$obj_node" "modified" "false" >/dev/null
653
+ yaml_object_set "$obj_node" "checksum" "sha256:$checksum" >/dev/null
654
+
655
+ # Add experimental field if marked
656
+ if [ -n "$is_experimental" ]; then
657
+ yaml_object_set "$obj_node" "experimental" "true" >/dev/null
658
+ fi
659
+
660
+ # Write back
661
+ yaml_write "$manifest"
662
+
663
+ return 0
664
+ }
665
+
666
+ # Get package commit hash from git repository
667
+ # Usage: get_commit_hash "repo_dir"
668
+ # Returns: commit hash
669
+ get_commit_hash() {
670
+ local repo_dir="$1"
671
+
672
+ if [ ! -d "$repo_dir/.git" ]; then
673
+ echo "unknown"
674
+ return 0
675
+ fi
676
+
677
+ (cd "$repo_dir" && git rev-parse HEAD 2>/dev/null) || echo "unknown"
678
+ }
679
+
680
+ # Compare semantic versions
681
+ # Usage: compare_versions "1.2.3" "1.3.0"
682
+ # Returns: "newer" if remote > current, "same" if equal, "older" if remote < current
683
+ compare_versions() {
684
+ local current="$1"
685
+ local remote="$2"
686
+
687
+ if [ "$current" = "$remote" ]; then
688
+ echo "same"
689
+ return 0
690
+ fi
691
+
692
+ # Use sort -V for version comparison
693
+ local older
694
+ older=$(printf '%s\n%s\n' "$current" "$remote" | sort -V | head -n1)
695
+
696
+ if [ "$older" = "$current" ]; then
697
+ echo "newer"
698
+ else
699
+ echo "older"
700
+ fi
701
+ }
702
+
703
+ # Check if file was modified locally
704
+ # Usage: is_file_modified "package_name" "file_type" "filename"
705
+ # Returns: 0 if modified, 1 if not modified
706
+ is_file_modified() {
707
+ local package_name="$1"
708
+ local file_type="$2"
709
+ local file_name="$3"
710
+ local manifest="agent/manifest.yaml"
711
+
712
+ # Get stored checksum from manifest
713
+ local stored_checksum
714
+ stored_checksum=$(awk -v pkg="$package_name" -v type="$file_type" -v name="$file_name" '
715
+ BEGIN { in_pkg=0; in_type=0; in_file=0 }
716
+ $0 ~ "^ " pkg ":" { in_pkg=1; next }
717
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
718
+ in_pkg && $0 ~ "^ " type ":" { in_type=1; next }
719
+ in_type && /^ [a-z]/ && !/^ / { in_type=0 }
720
+ in_type && /^ - name:/ {
721
+ if ($3 == name) { in_file=1 }
722
+ else { in_file=0 }
723
+ next
724
+ }
725
+ in_file && /^ checksum:/ {
726
+ gsub(/sha256:/, "", $2)
727
+ print $2
728
+ exit
729
+ }
730
+ ' "$manifest")
731
+
732
+ if [ -z "$stored_checksum" ]; then
733
+ warn "No checksum found in manifest for $file_type/$file_name"
734
+ return 1
735
+ fi
736
+
737
+ # Calculate current checksum
738
+ # Map manifest key to filesystem directory (they differ for some types)
739
+ local file_dir="$file_type"
740
+ case "$file_type" in
741
+ indices) file_dir="index" ;;
742
+ esac
743
+ local current_checksum
744
+ current_checksum=$(calculate_checksum "agent/${file_dir}/${file_name}")
745
+
746
+ if [ "$stored_checksum" != "$current_checksum" ]; then
747
+ return 0 # Modified
748
+ else
749
+ return 1 # Not modified
750
+ fi
751
+ }
752
+
753
+ # Update file entry in manifest
754
+ # Usage: update_file_in_manifest "package_name" "file_type" "filename" "new_version" "new_checksum"
755
+ update_file_in_manifest() {
756
+ local package_name="$1"
757
+ local file_type="$2"
758
+ local file_name="$3"
759
+ local new_version="$4"
760
+ local new_checksum="$5"
761
+ local timestamp
762
+ timestamp=$(get_timestamp)
763
+
764
+ local manifest="agent/manifest.yaml"
765
+
766
+ # Update using awk to modify in place
767
+ # This is complex with acp.yaml.sh, so we'll use a temp file approach
768
+ local temp_file
769
+ temp_file=$(mktemp)
770
+
771
+ awk -v pkg="$package_name" -v type="$file_type" -v name="$file_name" \
772
+ -v ver="$new_version" -v chk="sha256:$new_checksum" -v ts="$timestamp" '
773
+ BEGIN { in_pkg=0; in_type=0; in_file=0 }
774
+ $0 ~ "^ " pkg ":" { in_pkg=1; print; next }
775
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0; print; next }
776
+ in_pkg && $0 ~ "^ " type ":" { in_type=1; print; next }
777
+ in_type && /^ [a-z]/ && !/^ / { in_type=0; print; next }
778
+ in_type && /^ - name:/ {
779
+ if ($3 == name) { in_file=1 }
780
+ else { in_file=0 }
781
+ print
782
+ next
783
+ }
784
+ in_file && /^ version:/ {
785
+ print " version: " ver
786
+ next
787
+ }
788
+ in_file && /^ checksum:/ {
789
+ print " checksum: " chk
790
+ next
791
+ }
792
+ in_file && /^ modified:/ {
793
+ print " modified: false"
794
+ next
795
+ }
796
+ { print }
797
+ ' "$manifest" > "$temp_file"
798
+
799
+ mv "$temp_file" "$manifest"
800
+ }
801
+
802
+ # ============================================================================
803
+ # Template File Manifest Functions
804
+ # ============================================================================
805
+
806
+ # Check if a template file was modified locally (uses target path, not agent/ path)
807
+ # Usage: is_template_file_modified "package_name" "filename" "target_path"
808
+ # Returns: 0 if modified, 1 if not modified
809
+ is_template_file_modified() {
810
+ local package_name="$1"
811
+ local file_name="$2"
812
+ local target_path="$3"
813
+ local manifest="agent/manifest.yaml"
814
+
815
+ # Get stored checksum from manifest
816
+ local stored_checksum
817
+ stored_checksum=$(awk -v pkg="$package_name" -v name="$file_name" '
818
+ BEGIN { in_pkg=0; in_files=0; in_file=0 }
819
+ $0 ~ "^ " pkg ":" { in_pkg=1; next }
820
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
821
+ in_pkg && /^ files:/ { in_files=1; next }
822
+ in_files && /^ [a-z]/ && !/^ / { in_files=0 }
823
+ in_files && /^ - name:/ {
824
+ if ($3 == name) { in_file=1 }
825
+ else { in_file=0 }
826
+ next
827
+ }
828
+ in_file && /^ checksum:/ {
829
+ gsub(/sha256:/, "", $2)
830
+ print $2
831
+ exit
832
+ }
833
+ ' "$manifest")
834
+
835
+ if [ -z "$stored_checksum" ]; then
836
+ warn "No checksum found in manifest for files/$file_name"
837
+ return 1
838
+ fi
839
+
840
+ # Calculate current checksum from target path
841
+ if [ ! -f "$target_path" ]; then
842
+ warn "Target file not found: $target_path"
843
+ return 0 # Missing = modified (deleted)
844
+ fi
845
+
846
+ local current_checksum
847
+ current_checksum=$(calculate_checksum "$target_path")
848
+
849
+ if [ "$stored_checksum" != "$current_checksum" ]; then
850
+ return 0 # Modified
851
+ else
852
+ return 1 # Not modified
853
+ fi
854
+ }
855
+
856
+ # Get target path for a template file from manifest
857
+ # Usage: target=$(get_template_file_target "package_name" "filename")
858
+ get_template_file_target() {
859
+ local package_name="$1"
860
+ local file_name="$2"
861
+ local manifest="agent/manifest.yaml"
862
+
863
+ awk -v pkg="$package_name" -v name="$file_name" '
864
+ BEGIN { in_pkg=0; in_files=0; in_file=0 }
865
+ $0 ~ "^ " pkg ":" { in_pkg=1; next }
866
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
867
+ in_pkg && /^ files:/ { in_files=1; next }
868
+ in_files && /^ [a-z]/ && !/^ / { in_files=0 }
869
+ in_files && /^ - name:/ {
870
+ if ($3 == name) { in_file=1 }
871
+ else { in_file=0 }
872
+ next
873
+ }
874
+ in_file && /^ target:/ {
875
+ $1=""
876
+ gsub(/^ +/, "")
877
+ print
878
+ exit
879
+ }
880
+ ' "$manifest"
881
+ }
882
+
883
+ # Get stored variable values for a template file from manifest
884
+ # Usage: vars=$(get_template_file_variables "package_name" "filename")
885
+ # Returns: KEY=VALUE lines (one per line)
886
+ get_template_file_variables() {
887
+ local package_name="$1"
888
+ local file_name="$2"
889
+ local manifest="agent/manifest.yaml"
890
+
891
+ awk -v pkg="$package_name" -v name="$file_name" '
892
+ BEGIN { in_pkg=0; in_files=0; in_file=0; in_vars=0 }
893
+ $0 ~ "^ " pkg ":" { in_pkg=1; next }
894
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
895
+ in_pkg && /^ files:/ { in_files=1; next }
896
+ in_files && /^ [a-z]/ && !/^ / { in_files=0 }
897
+ in_files && /^ - name:/ {
898
+ if ($3 == name) { in_file=1 }
899
+ else { in_file=0; in_vars=0 }
900
+ next
901
+ }
902
+ in_file && /^ variables:/ { in_vars=1; next }
903
+ in_vars && /^ [A-Z]/ {
904
+ key=$1
905
+ gsub(/:$/, "", key)
906
+ $1=""
907
+ gsub(/^ +/, "")
908
+ print key "=" $0
909
+ next
910
+ }
911
+ in_vars && /^ [a-z]/ { in_vars=0 }
912
+ in_vars && /^ -/ { in_vars=0; in_file=0 }
913
+ ' "$manifest"
914
+ }
915
+
916
+ # Update template file entry in manifest
917
+ # Usage: update_template_file_in_manifest "package_name" "filename" "new_version" "new_checksum"
918
+ update_template_file_in_manifest() {
919
+ local package_name="$1"
920
+ local file_name="$2"
921
+ local new_version="$3"
922
+ local new_checksum="$4"
923
+ local timestamp
924
+ timestamp=$(get_timestamp)
925
+
926
+ local manifest="agent/manifest.yaml"
927
+
928
+ local temp_file
929
+ temp_file=$(mktemp)
930
+
931
+ awk -v pkg="$package_name" -v name="$file_name" \
932
+ -v ver="$new_version" -v chk="sha256:$new_checksum" -v ts="$timestamp" '
933
+ BEGIN { in_pkg=0; in_files=0; in_file=0 }
934
+ $0 ~ "^ " pkg ":" { in_pkg=1; print; next }
935
+ in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0; print; next }
936
+ in_pkg && /^ files:/ { in_files=1; print; next }
937
+ in_files && /^ [a-z]/ && !/^ / { in_files=0; print; next }
938
+ in_files && /^ - name:/ {
939
+ if ($3 == name) { in_file=1 }
940
+ else { in_file=0 }
941
+ print
942
+ next
943
+ }
944
+ in_file && /^ version:/ {
945
+ print " version: " ver
946
+ next
947
+ }
948
+ in_file && /^ checksum:/ {
949
+ print " checksum: " chk
950
+ next
951
+ }
952
+ in_file && /^ modified:/ {
953
+ print " modified: false"
954
+ next
955
+ }
956
+ { print }
957
+ ' "$manifest" > "$temp_file"
958
+
959
+ mv "$temp_file" "$manifest"
960
+ }
961
+
962
+ # ============================================================================
963
+ # Dependency Checking Functions
964
+ # ============================================================================
965
+
966
+ # Detect project package manager
967
+ # Usage: detect_package_manager
968
+ # Returns: npm, pip, cargo, go, or unknown
969
+ detect_package_manager() {
970
+ if [ -f "package.json" ]; then
971
+ echo "npm"
972
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
973
+ echo "pip"
974
+ elif [ -f "Cargo.toml" ]; then
975
+ echo "cargo"
976
+ elif [ -f "go.mod" ]; then
977
+ echo "go"
978
+ else
979
+ echo "unknown"
980
+ fi
981
+ }
982
+
983
+ # Check npm dependency
984
+ # Usage: check_npm_dependency "dep_name" "required_version"
985
+ # Returns: installed version or "not-installed"
986
+ check_npm_dependency() {
987
+ local dep_name="$1"
988
+ local required_version="$2"
989
+
990
+ if [ ! -f "package.json" ]; then
991
+ echo "not-installed"
992
+ return 1
993
+ fi
994
+
995
+ # Check if jq is available
996
+ if ! command -v jq >/dev/null 2>&1; then
997
+ warn "jq not found, skipping npm dependency check"
998
+ echo "unknown"
999
+ return 0
1000
+ fi
1001
+
1002
+ # Get installed version
1003
+ local installed_version
1004
+ installed_version=$(jq -r ".dependencies.\"${dep_name}\" // .devDependencies.\"${dep_name}\" // \"not-installed\"" package.json 2>/dev/null)
1005
+
1006
+ if [ "$installed_version" = "not-installed" ] || [ "$installed_version" = "null" ]; then
1007
+ echo "not-installed"
1008
+ return 1
1009
+ fi
1010
+
1011
+ # Remove ^ ~ >= etc for display
1012
+ installed_version=$(echo "$installed_version" | sed 's/[\^~>=<]//g')
1013
+
1014
+ echo "$installed_version"
1015
+ return 0
1016
+ }
1017
+
1018
+ # Check pip dependency
1019
+ # Usage: check_pip_dependency "dep_name" "required_version"
1020
+ # Returns: installed version or "not-installed"
1021
+ check_pip_dependency() {
1022
+ local dep_name="$1"
1023
+ local required_version="$2"
1024
+
1025
+ # Check requirements.txt
1026
+ if [ -f "requirements.txt" ]; then
1027
+ local version
1028
+ version=$(grep "^${dep_name}" requirements.txt 2>/dev/null | cut -d'=' -f2 | head -n1)
1029
+ if [ -n "$version" ]; then
1030
+ echo "$version"
1031
+ return 0
1032
+ fi
1033
+ fi
1034
+
1035
+ # Check pyproject.toml
1036
+ if [ -f "pyproject.toml" ]; then
1037
+ local version
1038
+ version=$(grep "${dep_name}" pyproject.toml 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -n1)
1039
+ if [ -n "$version" ]; then
1040
+ echo "$version"
1041
+ return 0
1042
+ fi
1043
+ fi
1044
+
1045
+ echo "not-installed"
1046
+ return 1
1047
+ }
1048
+
1049
+ # Check cargo dependency
1050
+ # Usage: check_cargo_dependency "dep_name" "required_version"
1051
+ # Returns: installed version or "not-installed"
1052
+ check_cargo_dependency() {
1053
+ local dep_name="$1"
1054
+ local required_version="$2"
1055
+
1056
+ if [ ! -f "Cargo.toml" ]; then
1057
+ echo "not-installed"
1058
+ return 1
1059
+ fi
1060
+
1061
+ local version
1062
+ version=$(grep "^${dep_name}" Cargo.toml 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -n1)
1063
+
1064
+ if [ -n "$version" ]; then
1065
+ echo "$version"
1066
+ return 0
1067
+ fi
1068
+
1069
+ echo "not-installed"
1070
+ return 1
1071
+ }
1072
+
1073
+ # Check go dependency
1074
+ # Usage: check_go_dependency "dep_name" "required_version"
1075
+ # Returns: installed version or "not-installed"
1076
+ check_go_dependency() {
1077
+ local dep_name="$1"
1078
+ local required_version="$2"
1079
+
1080
+ if [ ! -f "go.mod" ]; then
1081
+ echo "not-installed"
1082
+ return 1
1083
+ fi
1084
+
1085
+ local version
1086
+ version=$(grep "${dep_name}" go.mod 2>/dev/null | grep -oP 'v\d+\.\d+\.\d+' | sed 's/^v//' | head -n1)
1087
+
1088
+ if [ -n "$version" ]; then
1089
+ echo "$version"
1090
+ return 0
1091
+ fi
1092
+
1093
+ echo "not-installed"
1094
+ return 1
1095
+ }
1096
+
1097
+ # Validate project dependencies
1098
+ # Usage: validate_project_dependencies "package_yaml_path"
1099
+ # Returns: 0 if valid or user confirms, 1 if invalid and user cancels
1100
+ validate_project_dependencies() {
1101
+ local package_yaml="$1"
1102
+ local package_manager
1103
+ package_manager=$(detect_package_manager)
1104
+
1105
+ if [ "$package_manager" = "unknown" ]; then
1106
+ info "No package manager detected, skipping dependency check"
1107
+ return 0
1108
+ fi
1109
+
1110
+ echo ""
1111
+ echo "${BLUE}Checking project dependencies ($package_manager)...${NC}"
1112
+ echo ""
1113
+
1114
+ # Source YAML parser if not already loaded
1115
+ if ! command -v yaml_get >/dev/null 2>&1; then
1116
+ source_yaml_parser || return 1
1117
+ fi
1118
+
1119
+ # Check if requires section exists
1120
+ local has_requires
1121
+ has_requires=$(grep -c "^requires:" "$package_yaml" 2>/dev/null || echo "0")
1122
+
1123
+ if [ "$has_requires" -eq 0 ]; then
1124
+ success "No project dependencies required"
1125
+ return 0
1126
+ fi
1127
+
1128
+ # Check if package manager section exists
1129
+ local has_pm_section
1130
+ has_pm_section=$(grep -c "^ ${package_manager}:" "$package_yaml" 2>/dev/null || echo "0")
1131
+
1132
+ if [ "$has_pm_section" -eq 0 ]; then
1133
+ success "No ${package_manager} dependencies required"
1134
+ return 0
1135
+ fi
1136
+
1137
+ local has_incompatible=false
1138
+ local dep_count=0
1139
+
1140
+ # Parse dependencies using awk
1141
+ while IFS=: read -r dep_name required_version; do
1142
+ # Skip empty lines and section headers
1143
+ [ -z "$dep_name" ] && continue
1144
+ [[ "$dep_name" =~ ^[[:space:]]*$ ]] && continue
1145
+ [[ "$dep_name" =~ ^requires ]] && continue
1146
+ [[ "$dep_name" =~ ^[[:space:]]*${package_manager} ]] && continue
1147
+
1148
+ # Clean up whitespace
1149
+ dep_name=$(echo "$dep_name" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
1150
+ required_version=$(echo "$required_version" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | tr -d '"' | tr -d "'")
1151
+
1152
+ # Skip if not a dependency line
1153
+ [[ ! "$dep_name" =~ ^[a-zA-Z0-9@/_-]+$ ]] && continue
1154
+
1155
+ dep_count=$((dep_count + 1))
1156
+
1157
+ # Check if installed
1158
+ local installed_version=""
1159
+ case $package_manager in
1160
+ npm)
1161
+ installed_version=$(check_npm_dependency "$dep_name" "$required_version")
1162
+ ;;
1163
+ pip)
1164
+ installed_version=$(check_pip_dependency "$dep_name" "$required_version")
1165
+ ;;
1166
+ cargo)
1167
+ installed_version=$(check_cargo_dependency "$dep_name" "$required_version")
1168
+ ;;
1169
+ go)
1170
+ installed_version=$(check_go_dependency "$dep_name" "$required_version")
1171
+ ;;
1172
+ esac
1173
+
1174
+ if [ "$installed_version" = "not-installed" ]; then
1175
+ echo " ${RED}✗${NC} $dep_name: not installed (requires $required_version)"
1176
+ has_incompatible=true
1177
+ elif [ "$installed_version" = "unknown" ]; then
1178
+ echo " ${YELLOW}?${NC} $dep_name: unable to verify (requires $required_version)"
1179
+ else
1180
+ echo " ${GREEN}✓${NC} $dep_name: $installed_version (requires $required_version)"
1181
+ fi
1182
+ done < <(awk -v pm="$package_manager" '
1183
+ BEGIN { in_requires=0; in_pm=0 }
1184
+ /^requires:/ { in_requires=1; next }
1185
+ in_requires && /^[a-z]/ && !/^ / { in_requires=0 }
1186
+ in_requires && $0 ~ "^ " pm ":" { in_pm=1; next }
1187
+ in_pm && /^ [a-z]/ && !/^ / { in_pm=0 }
1188
+ in_pm && /^ [a-zA-Z0-9@/_-]+:/ {
1189
+ print $0
1190
+ }
1191
+ ' "$package_yaml")
1192
+
1193
+ echo ""
1194
+
1195
+ if [ "$dep_count" -eq 0 ]; then
1196
+ success "No ${package_manager} dependencies required"
1197
+ return 0
1198
+ fi
1199
+
1200
+ if [ "$has_incompatible" = true ]; then
1201
+ echo "${YELLOW}⚠️ Some dependencies are missing or incompatible${NC}"
1202
+ echo ""
1203
+ echo "Recommendation:"
1204
+ echo " Install missing dependencies before using this package"
1205
+ echo " The package patterns may not work correctly without them"
1206
+ echo ""
1207
+
1208
+ # Only prompt if not in auto-confirm mode
1209
+ if [ "${SKIP_CONFIRM:-false}" != "true" ]; then
1210
+ read -p "Continue installation anyway? (y/N) " -n 1 -r
1211
+ echo
1212
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
1213
+ return 1
1214
+ fi
1215
+ else
1216
+ warn "Auto-confirm enabled, continuing despite missing dependencies"
1217
+ fi
1218
+ else
1219
+ success "All dependencies satisfied"
1220
+ fi
1221
+
1222
+ return 0
1223
+ }
1224
+
1225
+ # ============================================================================
1226
+ # Namespace Utilities
1227
+ # ============================================================================
1228
+
1229
+ # Check if current directory is an ACP package
1230
+ # Usage: if is_acp_package; then ...
1231
+ # Returns: 0 if package.yaml exists, 1 otherwise
1232
+ is_acp_package() {
1233
+ [ -f "package.yaml" ]
1234
+ }
1235
+
1236
+ # Infer package namespace from multiple sources
1237
+ # Usage: namespace=$(infer_namespace)
1238
+ # Returns: namespace string or empty if can't infer
1239
+ # Priority: 1) package.yaml, 2) directory name, 3) git remote
1240
+ infer_namespace() {
1241
+ local namespace=""
1242
+
1243
+ # Priority 1: Read from package.yaml
1244
+ if [ -f "package.yaml" ]; then
1245
+ namespace=$(yaml_get "package.yaml" "name" 2>/dev/null)
1246
+ if [ -n "$namespace" ]; then
1247
+ echo "$namespace"
1248
+ return 0
1249
+ fi
1250
+ fi
1251
+
1252
+ # Priority 2: Parse from directory name (acp-{namespace})
1253
+ local dir_name=$(basename "$PWD")
1254
+ if [[ "$dir_name" =~ ^acp-(.+)$ ]]; then
1255
+ namespace="${BASH_REMATCH[1]}"
1256
+ echo "$namespace"
1257
+ return 0
1258
+ fi
1259
+
1260
+ # Priority 3: Parse from git remote URL
1261
+ if git remote get-url origin >/dev/null 2>&1; then
1262
+ local remote_url=$(git remote get-url origin)
1263
+ if [[ "$remote_url" =~ acp-([a-z0-9-]+)(\.git)?$ ]]; then
1264
+ namespace="${BASH_REMATCH[1]}"
1265
+ echo "$namespace"
1266
+ return 0
1267
+ fi
1268
+ fi
1269
+
1270
+ # Could not infer
1271
+ return 1
1272
+ }
1273
+
1274
+ # Validate namespace format and check reserved names
1275
+ # Usage: if validate_namespace "firebase"; then ...
1276
+ # Returns: 0 if valid, 1 if invalid
1277
+ validate_namespace() {
1278
+ local namespace="$1"
1279
+
1280
+ if [ -z "$namespace" ]; then
1281
+ echo "${RED}Error: Namespace cannot be empty${NC}" >&2
1282
+ return 1
1283
+ fi
1284
+
1285
+ # Check format (lowercase, alphanumeric, hyphens)
1286
+ if ! echo "$namespace" | grep -qE '^[a-z0-9-]+$'; then
1287
+ echo "${RED}Error: Namespace must be lowercase, alphanumeric, and hyphens only${NC}" >&2
1288
+ return 1
1289
+ fi
1290
+
1291
+ # Check reserved names
1292
+ case "$namespace" in
1293
+ acp|local|core|system|global)
1294
+ echo "${RED}Error: Namespace '$namespace' is reserved${NC}" >&2
1295
+ return 1
1296
+ ;;
1297
+ esac
1298
+
1299
+ return 0
1300
+ }
1301
+
1302
+ # Get namespace for file creation (context-aware)
1303
+ # Usage: namespace=$(get_namespace_for_file)
1304
+ # Returns: package namespace or "local" for non-packages
1305
+ get_namespace_for_file() {
1306
+ if is_acp_package; then
1307
+ local namespace=$(infer_namespace)
1308
+ if [ -n "$namespace" ]; then
1309
+ echo "$namespace"
1310
+ return 0
1311
+ else
1312
+ # In package but can't infer, ask user
1313
+ read -p "Package namespace: " namespace
1314
+ if validate_namespace "$namespace"; then
1315
+ echo "$namespace"
1316
+ return 0
1317
+ else
1318
+ return 1
1319
+ fi
1320
+ fi
1321
+ else
1322
+ # Not a package, use local namespace
1323
+ echo "local"
1324
+ return 0
1325
+ fi
1326
+ }
1327
+
1328
+ # Validate namespace consistency across sources
1329
+ # Usage: if validate_namespace_consistency; then ...
1330
+ # Returns: 0 if consistent, 1 if conflicts found
1331
+ validate_namespace_consistency() {
1332
+ if ! is_acp_package; then
1333
+ return 0 # Not a package, no consistency to check
1334
+ fi
1335
+
1336
+ local from_yaml=$(yaml_get "package.yaml" "name" 2>/dev/null)
1337
+ local from_dir=$(basename "$PWD" | sed 's/^acp-//')
1338
+ local from_remote=""
1339
+
1340
+ if git remote get-url origin >/dev/null 2>&1; then
1341
+ local remote_url=$(git remote get-url origin)
1342
+ if [[ "$remote_url" =~ acp-([a-z0-9-]+)(\.git)?$ ]]; then
1343
+ from_remote="${BASH_REMATCH[1]}"
1344
+ fi
1345
+ fi
1346
+
1347
+ # Check for conflicts
1348
+ local has_conflict=false
1349
+
1350
+ if [ -n "$from_yaml" ] && [ -n "$from_dir" ] && [ "$from_yaml" != "$from_dir" ]; then
1351
+ echo "${YELLOW}Warning: Namespace mismatch${NC}" >&2
1352
+ echo " package.yaml: $from_yaml" >&2
1353
+ echo " directory: $from_dir" >&2
1354
+ has_conflict=true
1355
+ fi
1356
+
1357
+ if [ -n "$from_yaml" ] && [ -n "$from_remote" ] && [ "$from_yaml" != "$from_remote" ]; then
1358
+ echo "${YELLOW}Warning: Namespace mismatch${NC}" >&2
1359
+ echo " package.yaml: $from_yaml" >&2
1360
+ echo " git remote: $from_remote" >&2
1361
+ has_conflict=true
1362
+ fi
1363
+
1364
+ if [ "$has_conflict" = true ]; then
1365
+ return 1
1366
+ fi
1367
+
1368
+ return 0
1369
+ }
1370
+
1371
+ # ============================================================================
1372
+ # README Update Utilities
1373
+ # ============================================================================
1374
+
1375
+ # Update README.md contents section from package.yaml
1376
+ # Usage: update_readme_contents
1377
+ # Returns: 0 if successful, 1 if error
1378
+ update_readme_contents() {
1379
+ local readme="README.md"
1380
+ local package_yaml="package.yaml"
1381
+
1382
+ if [ ! -f "$readme" ]; then
1383
+ echo "${YELLOW}Warning: README.md not found${NC}" >&2
1384
+ return 1
1385
+ fi
1386
+
1387
+ if [ ! -f "$package_yaml" ]; then
1388
+ echo "${YELLOW}Warning: package.yaml not found${NC}" >&2
1389
+ return 1
1390
+ fi
1391
+
1392
+ # Generate contents section
1393
+ local contents=$(generate_contents_section)
1394
+
1395
+ # Check if markers exist
1396
+ if ! grep -q "<!-- ACP_AUTO_UPDATE_START:CONTENTS -->" "$readme"; then
1397
+ echo "${YELLOW}Warning: README.md missing auto-update markers${NC}" >&2
1398
+ return 1
1399
+ fi
1400
+
1401
+ # Replace section between markers using awk
1402
+ awk -v contents="$contents" '
1403
+ /<!-- ACP_AUTO_UPDATE_START:CONTENTS -->/ {
1404
+ print
1405
+ print contents
1406
+ skip=1
1407
+ next
1408
+ }
1409
+ /<!-- ACP_AUTO_UPDATE_END:CONTENTS -->/ {
1410
+ skip=0
1411
+ }
1412
+ !skip
1413
+ ' "$readme" > "${readme}.tmp"
1414
+
1415
+ mv "${readme}.tmp" "$readme"
1416
+ echo "${GREEN}✓${NC} Updated README.md contents section"
1417
+ return 0
1418
+ }
1419
+
1420
+ # Generate contents section from package.yaml
1421
+ # Usage: contents=$(generate_contents_section)
1422
+ # Returns: Formatted markdown content list
1423
+ generate_contents_section() {
1424
+ local package_yaml="package.yaml"
1425
+
1426
+ # Parse and format contents using awk
1427
+ awk '
1428
+ BEGIN { section="" }
1429
+
1430
+ /^ commands:/ { section="commands"; print "### Commands"; next }
1431
+ /^ patterns:/ { section="patterns"; print ""; print "### Patterns"; next }
1432
+ /^ designs:/ { section="designs"; print ""; print "### Designs"; next }
1433
+
1434
+ section != "" && /^ - name:/ {
1435
+ gsub(/^ - name: /, "")
1436
+ name = $0
1437
+ getline
1438
+ if (/^ version:/) {
1439
+ getline
1440
+ if (/^ description:/) {
1441
+ gsub(/^ description: /, "")
1442
+ desc = $0
1443
+ print "- `" name "` - " desc
1444
+ } else {
1445
+ print "- `" name "`"
1446
+ }
1447
+ }
1448
+ }
1449
+
1450
+ /^[a-z]/ && !/^ / { section="" }
1451
+ ' "$package_yaml"
1452
+ }
1453
+
1454
+ # Add file to README contents (updates entire section)
1455
+ # Usage: add_file_to_readme "patterns" "firebase.my-pattern.md" "Description"
1456
+ add_file_to_readme() {
1457
+ local type="$1"
1458
+ local filename="$2"
1459
+ local description="$3"
1460
+
1461
+ # Simply update entire contents section
1462
+ update_readme_contents
1463
+ }
1464
+
1465
+ # ============================================================================
1466
+ # Display Functions
1467
+ # ============================================================================
1468
+
1469
+ # Display available ACP commands
1470
+ # Usage: display_available_commands
1471
+ display_available_commands() {
1472
+ echo "${BLUE}ACP Commands Available:${NC}"
1473
+ echo ""
1474
+ echo " ${GREEN}@acp.init${NC} - Initialize agent context (start here!)"
1475
+ echo " ${GREEN}@acp.proceed${NC} - Continue with next task"
1476
+ echo " ${GREEN}@acp.status${NC} - Display project status"
1477
+ echo " ${GREEN}@acp.update${NC} - Update progress tracking"
1478
+ echo " ${GREEN}@acp.sync${NC} - Sync documentation with code"
1479
+ echo " ${GREEN}@acp.validate${NC} - Validate ACP documents"
1480
+ echo " ${GREEN}@acp.report${NC} - Generate project report"
1481
+ echo " ${GREEN}@acp.version-check${NC} - Show current ACP version"
1482
+ echo " ${GREEN}@acp.version-check-for-updates${NC} - Check for ACP updates"
1483
+ echo " ${GREEN}@acp.version-update${NC} - Update ACP to latest version"
1484
+ echo ""
1485
+ echo "${BLUE}Package Management Commands:${NC}"
1486
+ echo ""
1487
+ echo " ${GREEN}@acp.package-install${NC} - Install ACP packages from GitHub"
1488
+ echo " ${GREEN}@acp.package-list${NC} - List installed packages"
1489
+ echo " ${GREEN}@acp.package-update${NC} - Update installed packages"
1490
+ echo " ${GREEN}@acp.package-remove${NC} - Remove installed packages"
1491
+ echo " ${GREEN}@acp.package-info${NC} - Show package details"
1492
+ echo " ${GREEN}@acp.package-search${NC} - Search for packages on GitHub"
1493
+ echo ""
1494
+ echo "${BLUE}Git Commands Available:${NC}"
1495
+ echo ""
1496
+ echo " ${GREEN}@git.init${NC} - Initialize git repository with smart .gitignore"
1497
+ echo " ${GREEN}@git.commit${NC} - Intelligent version-aware git commit"
1498
+ }
1499
+
1500
+ # ============================================================================
1501
+ # Pre-Commit Hook System
1502
+ # ============================================================================
1503
+
1504
+ # Install pre-commit hook for package validation
1505
+ # Usage: install_precommit_hook
1506
+ # Returns: 0 on success, 1 on failure
1507
+ install_precommit_hook() {
1508
+ local hook_file=".git/hooks/pre-commit"
1509
+
1510
+ # Check if .git directory exists
1511
+ if [ ! -d ".git" ]; then
1512
+ echo "${RED}Error: Not a git repository${NC}" >&2
1513
+ return 1
1514
+ fi
1515
+
1516
+ # Create hooks directory if it doesn't exist
1517
+ mkdir -p ".git/hooks"
1518
+
1519
+ # Check if hook already exists
1520
+ if [ -f "$hook_file" ]; then
1521
+ echo "${YELLOW}⚠ Pre-commit hook already exists${NC}"
1522
+ echo " Backing up to pre-commit.backup"
1523
+ cp "$hook_file" "${hook_file}.backup"
1524
+ fi
1525
+
1526
+ # Create hook from template
1527
+ cat > "$hook_file" << 'EOF'
1528
+ #!/bin/sh
1529
+ # ACP Package Pre-Commit Hook
1530
+ # Validates package.yaml before allowing commit
1531
+
1532
+ # Colors for output
1533
+ if command -v tput >/dev/null 2>&1 && [ -t 1 ]; then
1534
+ RED=$(tput setaf 1)
1535
+ GREEN=$(tput setaf 2)
1536
+ YELLOW=$(tput setaf 3)
1537
+ NC=$(tput sgr0)
1538
+ else
1539
+ RED=''
1540
+ GREEN=''
1541
+ YELLOW=''
1542
+ NC=''
1543
+ fi
1544
+
1545
+ # Check if package.yaml exists
1546
+ if [ ! -f "package.yaml" ]; then
1547
+ # Not a package directory, skip validation
1548
+ exit 0
1549
+ fi
1550
+
1551
+ # Check if validation script exists
1552
+ if [ ! -f "agent/scripts/acp.yaml-validate.sh" ]; then
1553
+ echo "${YELLOW}Warning: acp.yaml-validate.sh not found, skipping validation${NC}"
1554
+ exit 0
1555
+ fi
1556
+
1557
+ # Check if schema exists
1558
+ if [ ! -f "agent/schemas/package.schema.yaml" ]; then
1559
+ echo "${YELLOW}Warning: package.schema.yaml not found, skipping validation${NC}"
1560
+ exit 0
1561
+ fi
1562
+
1563
+ # Validate package.yaml by running the script directly (not sourcing)
1564
+ echo "Validating package.yaml..."
1565
+ if ! ./agent/scripts/acp.yaml-validate.sh "package.yaml" "agent/schemas/package.schema.yaml" 2>/dev/null; then
1566
+ echo ""
1567
+ echo "${RED}✗ Pre-commit validation failed${NC}"
1568
+ echo ""
1569
+ echo "package.yaml has validation errors."
1570
+ echo "Please fix the errors and try again."
1571
+ echo ""
1572
+ echo "To see detailed errors, run:"
1573
+ echo " ./agent/scripts/acp.yaml-validate.sh package.yaml agent/schemas/package.schema.yaml"
1574
+ echo ""
1575
+ exit 1
1576
+ fi
1577
+
1578
+ echo "${GREEN}✓${NC} package.yaml is valid"
1579
+
1580
+ # Future enhancements (documented for reference):
1581
+ # - Namespace consistency checking across all files
1582
+ # - CHANGELOG.md validation for version changes
1583
+ # - File existence verification (all files in package.yaml exist)
1584
+ # - README.md structure validation
1585
+ # - Prevent commits to non-release branches
1586
+
1587
+ exit 0
1588
+ EOF
1589
+
1590
+ # Make executable
1591
+ chmod +x "$hook_file"
1592
+
1593
+ echo "${GREEN}✓${NC} Installed pre-commit hook"
1594
+ }
1595
+
1596
+ # ============================================================================
1597
+ # Project Registry Functions
1598
+ # ============================================================================
1599
+
1600
+ # Get path to projects registry
1601
+ # Usage: registry_path=$(get_projects_registry_path)
1602
+ get_projects_registry_path() {
1603
+ echo "$HOME/.acp/projects.yaml"
1604
+ }
1605
+
1606
+ # Check if projects registry exists
1607
+ # Usage: if projects_registry_exists; then ...
1608
+ projects_registry_exists() {
1609
+ [ -f "$(get_projects_registry_path)" ]
1610
+ }
1611
+
1612
+ # Initialize projects registry
1613
+ # Usage: init_projects_registry
1614
+ init_projects_registry() {
1615
+ local registry_path
1616
+ registry_path=$(get_projects_registry_path)
1617
+
1618
+ if [ -f "$registry_path" ]; then
1619
+ return 0 # Already exists
1620
+ fi
1621
+
1622
+ # Ensure ~/.acp/ exists
1623
+ mkdir -p "$HOME/.acp"
1624
+
1625
+ # Get timestamp
1626
+ local timestamp
1627
+ timestamp=$(get_timestamp)
1628
+
1629
+ # Create registry with timestamp
1630
+ cat > "$registry_path" << EOF
1631
+ # ACP Project Registry
1632
+ current_project: null
1633
+ projects:
1634
+ registry_version: 1.0.0
1635
+ last_updated: ${timestamp}
1636
+ EOF
1637
+ }
1638
+
1639
+ # Get git remote origin URL for a directory
1640
+ # Usage: origin=$(get_git_origin "/path/to/repo")
1641
+ # Returns: Git remote origin URL, or empty string if not a git repo or no origin
1642
+ get_git_origin() {
1643
+ local dir="${1:-.}"
1644
+ if [ -d "$dir/.git" ] || git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
1645
+ git -C "$dir" remote get-url origin 2>/dev/null || echo ""
1646
+ else
1647
+ echo ""
1648
+ fi
1649
+ }
1650
+
1651
+ # Get current git branch for a directory
1652
+ # Usage: branch=$(get_git_branch "/path/to/repo")
1653
+ # Returns: Current branch name, or empty string if not a git repo
1654
+ get_git_branch() {
1655
+ local dir="${1:-.}"
1656
+ if [ -d "$dir/.git" ] || git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
1657
+ git -C "$dir" branch --show-current 2>/dev/null || echo ""
1658
+ else
1659
+ echo ""
1660
+ fi
1661
+ }
1662
+
1663
+ # Register project in registry
1664
+ # Usage: register_project "project-name" "/path/to/project" "project-type" "description" ["git_origin"] ["git_branch"]
1665
+ # NOTE: Caller must source acp.yaml-parser.sh before calling this function
1666
+ # git_origin and git_branch are optional; if omitted, auto-detected from project path
1667
+ register_project() {
1668
+ local project_name="$1"
1669
+ local project_path="$2"
1670
+ local project_type="$3"
1671
+ local project_description="$4"
1672
+ local git_origin="${5:-}"
1673
+ local git_branch="${6:-}"
1674
+ local registry_path
1675
+ registry_path=$(get_projects_registry_path)
1676
+
1677
+ # Initialize registry if needed
1678
+ if ! projects_registry_exists; then
1679
+ init_projects_registry
1680
+ fi
1681
+
1682
+ # Source YAML parser
1683
+ source_yaml_parser
1684
+
1685
+ # Get timestamp
1686
+ local timestamp
1687
+ timestamp=$(get_timestamp)
1688
+
1689
+ # Parse registry
1690
+ yaml_parse "$registry_path"
1691
+
1692
+ # Add project entry (yaml_set now creates missing nodes!)
1693
+ yaml_set "projects.${project_name}.path" "$project_path"
1694
+ yaml_set "projects.${project_name}.type" "$project_type"
1695
+ yaml_set "projects.${project_name}.description" "$project_description"
1696
+ yaml_set "projects.${project_name}.created" "$timestamp"
1697
+ yaml_set "projects.${project_name}.last_modified" "$timestamp"
1698
+ yaml_set "projects.${project_name}.last_accessed" "$timestamp"
1699
+ yaml_set "projects.${project_name}.status" "active"
1700
+
1701
+ # Auto-detect git origin/branch if not provided
1702
+ local expanded_path="${project_path/#\~/$HOME}"
1703
+ if [ -z "$git_origin" ] && [ -d "$expanded_path" ]; then
1704
+ git_origin=$(get_git_origin "$expanded_path")
1705
+ fi
1706
+ if [ -z "$git_branch" ] && [ -d "$expanded_path" ]; then
1707
+ git_branch=$(get_git_branch "$expanded_path")
1708
+ fi
1709
+
1710
+ # Set git fields if available
1711
+ if [ -n "$git_origin" ]; then
1712
+ yaml_set "projects.${project_name}.git_origin" "$git_origin"
1713
+ fi
1714
+ if [ -n "$git_branch" ]; then
1715
+ yaml_set "projects.${project_name}.git_branch" "$git_branch"
1716
+ fi
1717
+
1718
+ # Set as current project if first project
1719
+ local current
1720
+ current=$(yaml_get "$registry_path" "current_project" 2>/dev/null || echo "")
1721
+ current=$(echo "$current" | sed "s/^['\"]//; s/['\"]$//")
1722
+ if [ -z "$current" ] || [ "$current" = "null" ]; then
1723
+ yaml_set "current_project" "$project_name"
1724
+ fi
1725
+
1726
+ # Update registry timestamp
1727
+ yaml_set "last_updated" "$timestamp"
1728
+
1729
+ # Write changes
1730
+ yaml_write "$registry_path"
1731
+ }
1732
+
1733
+ # Check if project exists in registry
1734
+ # Usage: if project_exists "project-name"; then ...
1735
+ project_exists() {
1736
+ local project_name="$1"
1737
+ local registry_path
1738
+ registry_path=$(get_projects_registry_path)
1739
+
1740
+ if ! projects_registry_exists; then
1741
+ return 1
1742
+ fi
1743
+
1744
+ grep -q "^ ${project_name}:" "$registry_path"
1745
+ }
1746
+
1747
+ # Get current project name
1748
+ # Usage: current=$(get_current_project)
1749
+ get_current_project() {
1750
+ local registry_path
1751
+ registry_path=$(get_projects_registry_path)
1752
+
1753
+ if ! projects_registry_exists; then
1754
+ return 1
1755
+ fi
1756
+
1757
+ local current
1758
+ current=$(grep "^current_project:" "$registry_path" | awk '{print $2}')
1759
+ if [ -n "$current" ] && [ "$current" != "null" ]; then
1760
+ echo "$current"
1761
+ fi
1762
+ }
1763
+
1764
+ # Get current project path
1765
+ # Usage: path=$(get_current_project_path)
1766
+ get_current_project_path() {
1767
+ local current
1768
+ current=$(get_current_project)
1769
+
1770
+ if [ -z "$current" ]; then
1771
+ pwd # Fallback to current directory
1772
+ return 0
1773
+ fi
1774
+
1775
+ local registry_path
1776
+ registry_path=$(get_projects_registry_path)
1777
+ local path
1778
+ path=$(awk "/^ ${current}:/,/^ [a-z]/ {if (/^ path:/) print \$2}" "$registry_path")
1779
+ # Expand ~ to HOME
1780
+ echo "$path" | sed "s|^~|$HOME|"
1781
+ }