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