agent-knowledge-cli 0.1.2__py3-none-any.whl

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 (88) hide show
  1. agent_knowledge/__init__.py +3 -0
  2. agent_knowledge/__main__.py +3 -0
  3. agent_knowledge/assets/__init__.py +0 -0
  4. agent_knowledge/assets/claude/global.md +44 -0
  5. agent_knowledge/assets/claude/project-template.md +46 -0
  6. agent_knowledge/assets/claude/scripts/install.sh +85 -0
  7. agent_knowledge/assets/commands/doctor.md +21 -0
  8. agent_knowledge/assets/commands/global-knowledge-sync.md +27 -0
  9. agent_knowledge/assets/commands/graphify-sync.md +26 -0
  10. agent_knowledge/assets/commands/knowledge-sync.md +26 -0
  11. agent_knowledge/assets/commands/ship.md +29 -0
  12. agent_knowledge/assets/rules/generate-architecture-doc.mdc +87 -0
  13. agent_knowledge/assets/rules/history-backfill.mdc +67 -0
  14. agent_knowledge/assets/rules/memory-bootstrap.mdc +53 -0
  15. agent_knowledge/assets/rules/memory-writeback.mdc +90 -0
  16. agent_knowledge/assets/rules/shared-memory.mdc +102 -0
  17. agent_knowledge/assets/rules/workflow-orchestration.mdc +93 -0
  18. agent_knowledge/assets/rules-global/action-first.mdc +26 -0
  19. agent_knowledge/assets/rules-global/no-icons-emojis.mdc +16 -0
  20. agent_knowledge/assets/rules-global/no-unsolicited-docs.mdc +20 -0
  21. agent_knowledge/assets/scripts/bootstrap-memory-tree.sh +389 -0
  22. agent_knowledge/assets/scripts/compact-memory.sh +191 -0
  23. agent_knowledge/assets/scripts/doctor.sh +137 -0
  24. agent_knowledge/assets/scripts/global-knowledge-sync.sh +372 -0
  25. agent_knowledge/assets/scripts/graphify-sync.sh +397 -0
  26. agent_knowledge/assets/scripts/import-agent-history.sh +706 -0
  27. agent_knowledge/assets/scripts/install-project-links.sh +258 -0
  28. agent_knowledge/assets/scripts/lib/knowledge-common.sh +875 -0
  29. agent_knowledge/assets/scripts/measure-token-savings.py +540 -0
  30. agent_knowledge/assets/scripts/ship.sh +256 -0
  31. agent_knowledge/assets/scripts/update-knowledge.sh +341 -0
  32. agent_knowledge/assets/scripts/validate-knowledge.sh +265 -0
  33. agent_knowledge/assets/skills/decision-recording/SKILL.md +124 -0
  34. agent_knowledge/assets/skills/history-backfill/SKILL.md +115 -0
  35. agent_knowledge/assets/skills/memory-compaction/SKILL.md +115 -0
  36. agent_knowledge/assets/skills/memory-management/SKILL.md +134 -0
  37. agent_knowledge/assets/skills/project-ontology-bootstrap/SKILL.md +173 -0
  38. agent_knowledge/assets/skills/session-management/SKILL.md +116 -0
  39. agent_knowledge/assets/skills-cursor/create-rule/SKILL.md +164 -0
  40. agent_knowledge/assets/skills-cursor/create-skill/SKILL.md +498 -0
  41. agent_knowledge/assets/skills-cursor/create-subagent/SKILL.md +225 -0
  42. agent_knowledge/assets/skills-cursor/migrate-to-skills/SKILL.md +134 -0
  43. agent_knowledge/assets/skills-cursor/shell/SKILL.md +24 -0
  44. agent_knowledge/assets/skills-cursor/update-cursor-settings/SKILL.md +122 -0
  45. agent_knowledge/assets/templates/dashboards/project-overview.template.md +24 -0
  46. agent_knowledge/assets/templates/dashboards/session-rollup.template.md +23 -0
  47. agent_knowledge/assets/templates/hooks/hooks.json.template +11 -0
  48. agent_knowledge/assets/templates/integrations/claude/CLAUDE.md +7 -0
  49. agent_knowledge/assets/templates/integrations/codex/AGENTS.md +7 -0
  50. agent_knowledge/assets/templates/integrations/cursor/agent-knowledge.mdc +11 -0
  51. agent_knowledge/assets/templates/integrations/cursor/hooks.json +11 -0
  52. agent_knowledge/assets/templates/memory/MEMORY.root.template.md +36 -0
  53. agent_knowledge/assets/templates/memory/branch.template.md +33 -0
  54. agent_knowledge/assets/templates/memory/decision.template.md +33 -0
  55. agent_knowledge/assets/templates/memory/profile.hybrid.yaml +16 -0
  56. agent_knowledge/assets/templates/memory/profile.ml-platform.yaml +18 -0
  57. agent_knowledge/assets/templates/memory/profile.robotics.yaml +19 -0
  58. agent_knowledge/assets/templates/memory/profile.web-app.yaml +16 -0
  59. agent_knowledge/assets/templates/portfolio/.obsidian/README.md +21 -0
  60. agent_knowledge/assets/templates/portfolio/.obsidian/app.json +5 -0
  61. agent_knowledge/assets/templates/portfolio/.obsidian/core-plugins.json +7 -0
  62. agent_knowledge/assets/templates/project/.agent-project.yaml +36 -0
  63. agent_knowledge/assets/templates/project/.agentknowledgeignore +10 -0
  64. agent_knowledge/assets/templates/project/AGENTS.md +87 -0
  65. agent_knowledge/assets/templates/project/agent-knowledge/.obsidian/README.md +23 -0
  66. agent_knowledge/assets/templates/project/agent-knowledge/.obsidian/app.json +5 -0
  67. agent_knowledge/assets/templates/project/agent-knowledge/.obsidian/core-plugins.json +7 -0
  68. agent_knowledge/assets/templates/project/agent-knowledge/Evidence/README.md +34 -0
  69. agent_knowledge/assets/templates/project/agent-knowledge/Evidence/imports/README.md +29 -0
  70. agent_knowledge/assets/templates/project/agent-knowledge/Evidence/raw/README.md +25 -0
  71. agent_knowledge/assets/templates/project/agent-knowledge/Memory/MEMORY.md +37 -0
  72. agent_knowledge/assets/templates/project/agent-knowledge/Memory/decisions/decisions.md +31 -0
  73. agent_knowledge/assets/templates/project/agent-knowledge/Outputs/README.md +24 -0
  74. agent_knowledge/assets/templates/project/agent-knowledge/STATUS.md +43 -0
  75. agent_knowledge/assets/templates/project/agent-knowledge/Sessions/README.md +21 -0
  76. agent_knowledge/assets/templates/project/agent-knowledge/Templates/README.md +19 -0
  77. agent_knowledge/assets/templates/project/gitignore.agent-knowledge +13 -0
  78. agent_knowledge/cli.py +457 -0
  79. agent_knowledge/runtime/__init__.py +0 -0
  80. agent_knowledge/runtime/integrations.py +154 -0
  81. agent_knowledge/runtime/paths.py +46 -0
  82. agent_knowledge/runtime/shell.py +22 -0
  83. agent_knowledge/runtime/sync.py +255 -0
  84. agent_knowledge_cli-0.1.2.dist-info/METADATA +155 -0
  85. agent_knowledge_cli-0.1.2.dist-info/RECORD +88 -0
  86. agent_knowledge_cli-0.1.2.dist-info/WHEEL +4 -0
  87. agent_knowledge_cli-0.1.2.dist-info/entry_points.txt +2 -0
  88. agent_knowledge_cli-0.1.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,875 @@
1
+ #!/bin/bash
2
+
3
+ # Shared helpers for the operational knowledge scripts.
4
+ # Bash 3 compatible.
5
+
6
+ DRY_RUN="${DRY_RUN:-0}"
7
+ JSON_MODE="${JSON_MODE:-0}"
8
+ FORCE="${FORCE:-0}"
9
+ SUMMARY_FILE="${SUMMARY_FILE:-}"
10
+ SHOW_HELP="${SHOW_HELP:-0}"
11
+
12
+ KC_LAST_ACTION=""
13
+ KC_CHILD_SUMMARY=""
14
+
15
+ kc_now_utc() {
16
+ date -u +"%Y-%m-%dT%H:%M:%SZ"
17
+ }
18
+
19
+ kc_today() {
20
+ date +"%Y-%m-%d"
21
+ }
22
+
23
+ kc_log() {
24
+ if [ "${JSON_MODE:-0}" -ne 1 ]; then
25
+ printf '%s\n' "$*"
26
+ fi
27
+ }
28
+
29
+ kc_err() {
30
+ printf '%s\n' "$*" >&2
31
+ }
32
+
33
+ kc_fail() {
34
+ kc_err "$*"
35
+ exit 1
36
+ }
37
+
38
+ kc_parse_common_flag() {
39
+ case "${1:-}" in
40
+ --dry-run)
41
+ DRY_RUN=1
42
+ return 0
43
+ ;;
44
+ --json)
45
+ JSON_MODE=1
46
+ return 0
47
+ ;;
48
+ --summary-file)
49
+ SUMMARY_FILE="${2:-}"
50
+ return 2
51
+ ;;
52
+ --force)
53
+ FORCE=1
54
+ return 0
55
+ ;;
56
+ --help|-h)
57
+ SHOW_HELP=1
58
+ return 0
59
+ ;;
60
+ esac
61
+ return 1
62
+ }
63
+
64
+ kc_json_escape() {
65
+ local value="${1:-}"
66
+ value=${value//\\/\\\\}
67
+ value=${value//\"/\\\"}
68
+ value=${value//$'\n'/\\n}
69
+ value=${value//$'\r'/\\r}
70
+ value=${value//$'\t'/\\t}
71
+ printf '%s' "$value"
72
+ }
73
+
74
+ kc_json_bool() {
75
+ if [ "${1:-0}" -eq 1 ] 2>/dev/null; then
76
+ printf 'true'
77
+ else
78
+ printf 'false'
79
+ fi
80
+ }
81
+
82
+ kc_json_array() {
83
+ local first=1
84
+ local item=""
85
+ printf '['
86
+ for item in "$@"; do
87
+ if [ $first -eq 0 ]; then
88
+ printf ','
89
+ fi
90
+ first=0
91
+ printf '"%s"' "$(kc_json_escape "$item")"
92
+ done
93
+ printf ']'
94
+ }
95
+
96
+ kc_json_array_from_lines() {
97
+ local text="${1:-}"
98
+ local items=()
99
+ local line=""
100
+
101
+ while IFS= read -r line; do
102
+ [ -n "$line" ] || continue
103
+ items+=("$line")
104
+ done <<EOF
105
+ $text
106
+ EOF
107
+
108
+ kc_json_array "${items[@]+"${items[@]}"}"
109
+ }
110
+
111
+ kc_write_json_output() {
112
+ local json="$1"
113
+
114
+ if [ -n "${SUMMARY_FILE:-}" ]; then
115
+ printf '%s\n' "$json" > "$SUMMARY_FILE"
116
+ fi
117
+
118
+ if [ "${JSON_MODE:-0}" -eq 1 ]; then
119
+ printf '%s\n' "$json"
120
+ fi
121
+ }
122
+
123
+ kc_run_child_script() {
124
+ local script="$1"
125
+ shift
126
+ local tmp_file=""
127
+ local exit_code=0
128
+
129
+ KC_CHILD_SUMMARY=""
130
+
131
+ if [ "${JSON_MODE:-0}" -eq 1 ]; then
132
+ tmp_file="$(mktemp)"
133
+ if ! "$script" "$@" --json --summary-file "$tmp_file" >/dev/null; then
134
+ exit_code=$?
135
+ fi
136
+ if [ -f "$tmp_file" ]; then
137
+ KC_CHILD_SUMMARY="$(cat "$tmp_file" 2>/dev/null || true)"
138
+ rm -f "$tmp_file"
139
+ fi
140
+ return "$exit_code"
141
+ fi
142
+
143
+ "$script" "$@"
144
+ }
145
+
146
+ kc_resolve_relative() {
147
+ local base="$1"
148
+ local path_value="$2"
149
+
150
+ case "$path_value" in
151
+ "")
152
+ return 1
153
+ ;;
154
+ /*)
155
+ printf '%s\n' "$path_value"
156
+ ;;
157
+ ~/*)
158
+ printf '%s\n' "$HOME/${path_value#~/}"
159
+ ;;
160
+ *)
161
+ (
162
+ cd "$base" 2>/dev/null && printf '%s/%s\n' "$(pwd)" "$path_value"
163
+ )
164
+ ;;
165
+ esac
166
+ }
167
+
168
+ kc_yaml_leaf_value() {
169
+ local file="$1"
170
+ local key="$2"
171
+
172
+ awk -v key="$key" '
173
+ $0 ~ "^[[:space:]]*" key ":[[:space:]]*" {
174
+ value = $0
175
+ sub("^[[:space:]]*" key ":[[:space:]]*", "", value)
176
+ gsub(/^["'"'"']|["'"'"']$/, "", value)
177
+ print value
178
+ exit
179
+ }
180
+ ' "$file" 2>/dev/null
181
+ }
182
+
183
+ kc_slugify() {
184
+ printf '%s' "$1" \
185
+ | tr '[:upper:]' '[:lower:]' \
186
+ | sed 's/[^a-z0-9]/-/g' \
187
+ | sed 's/-\{2,\}/-/g' \
188
+ | sed 's/^-//' \
189
+ | sed 's/-$//'
190
+ }
191
+
192
+ kc_ensure_dir() {
193
+ local dir="$1"
194
+ local label="${2:-$1}"
195
+
196
+ if [ -d "$dir" ]; then
197
+ KC_LAST_ACTION="unchanged"
198
+ return 0
199
+ fi
200
+
201
+ if [ "${DRY_RUN:-0}" -eq 1 ]; then
202
+ kc_log " would create dir: $label"
203
+ KC_LAST_ACTION="would-create"
204
+ return 0
205
+ fi
206
+
207
+ mkdir -p "$dir"
208
+ kc_log " created dir: $label"
209
+ KC_LAST_ACTION="created"
210
+ }
211
+
212
+ kc_apply_temp_file() {
213
+ local tmp_file="$1"
214
+ local dst="$2"
215
+ local label="${3:-$2}"
216
+ local existed=0
217
+
218
+ if [ -e "$dst" ]; then
219
+ existed=1
220
+ fi
221
+
222
+ if [ "$existed" -eq 1 ] && cmp -s "$tmp_file" "$dst"; then
223
+ rm -f "$tmp_file"
224
+ KC_LAST_ACTION="unchanged"
225
+ return 0
226
+ fi
227
+
228
+ if [ "${DRY_RUN:-0}" -eq 1 ]; then
229
+ if [ "$existed" -eq 1 ]; then
230
+ kc_log " would update: $label"
231
+ KC_LAST_ACTION="would-update"
232
+ else
233
+ kc_log " would create: $label"
234
+ KC_LAST_ACTION="would-create"
235
+ fi
236
+ rm -f "$tmp_file"
237
+ return 0
238
+ fi
239
+
240
+ mkdir -p "$(dirname "$dst")"
241
+ cp "$tmp_file" "$dst"
242
+ rm -f "$tmp_file"
243
+
244
+ if [ "$existed" -eq 1 ]; then
245
+ kc_log " updated: $label"
246
+ KC_LAST_ACTION="updated"
247
+ else
248
+ kc_log " created: $label"
249
+ KC_LAST_ACTION="created"
250
+ fi
251
+ }
252
+
253
+ kc_write_text_file() {
254
+ local dst="$1"
255
+ local label="${2:-$1}"
256
+ local tmp_file
257
+
258
+ tmp_file="$(mktemp)"
259
+ cat > "$tmp_file"
260
+ kc_apply_temp_file "$tmp_file" "$dst" "$label"
261
+ }
262
+
263
+ kc_copy_file() {
264
+ local src="$1"
265
+ local dst="$2"
266
+ local label="${3:-$2}"
267
+ local tmp_file
268
+
269
+ tmp_file="$(mktemp)"
270
+ cat "$src" > "$tmp_file"
271
+ kc_apply_temp_file "$tmp_file" "$dst" "$label"
272
+ }
273
+
274
+ kc_replace_in_template() {
275
+ local src="$1"
276
+ local dst="$2"
277
+ local label="$3"
278
+ shift 3
279
+
280
+ local tmp_file
281
+ local expr=()
282
+ local pair=""
283
+ local key=""
284
+ local value=""
285
+
286
+ tmp_file="$(mktemp)"
287
+ while [ "$#" -gt 1 ]; do
288
+ key="$1"
289
+ value="$2"
290
+ expr+=("-e" "s|$key|$value|g")
291
+ shift 2
292
+ done
293
+
294
+ sed "${expr[@]}" "$src" > "$tmp_file"
295
+ kc_apply_temp_file "$tmp_file" "$dst" "$label"
296
+ }
297
+
298
+ kc_ensure_symlink() {
299
+ local target="$1"
300
+ local link_path="$2"
301
+ local label="${3:-$2}"
302
+ local current_target=""
303
+
304
+ if [ -L "$link_path" ]; then
305
+ current_target="$(readlink "$link_path" 2>/dev/null || true)"
306
+ if [ "$current_target" = "$target" ]; then
307
+ KC_LAST_ACTION="unchanged"
308
+ return 0
309
+ fi
310
+ elif [ -e "$link_path" ]; then
311
+ kc_fail "Refusing to replace non-symlink path: $link_path"
312
+ fi
313
+
314
+ if [ "${DRY_RUN:-0}" -eq 1 ]; then
315
+ kc_log " would link: $label -> $target"
316
+ if [ -L "$link_path" ]; then
317
+ KC_LAST_ACTION="would-update"
318
+ else
319
+ KC_LAST_ACTION="would-create"
320
+ fi
321
+ return 0
322
+ fi
323
+
324
+ mkdir -p "$(dirname "$link_path")"
325
+ ln -sfn "$target" "$link_path"
326
+ kc_log " linked: $label -> $target"
327
+ if [ -L "$link_path" ]; then
328
+ KC_LAST_ACTION="updated"
329
+ else
330
+ KC_LAST_ACTION="created"
331
+ fi
332
+ }
333
+
334
+ kc_detect_symlink_caveat() {
335
+ case "$(uname -s 2>/dev/null || echo unknown)" in
336
+ MINGW*|MSYS*|CYGWIN*)
337
+ printf '%s\n' "Windows environments may require Developer Mode or administrator rights for symlinks. If link creation fails, use a junction or run the shell with elevated permissions."
338
+ ;;
339
+ *)
340
+ printf '%s\n' ""
341
+ ;;
342
+ esac
343
+ }
344
+
345
+ kc_load_project_context() {
346
+ local project_dir="${1:-.}"
347
+ local pointer_value=""
348
+ local real_value=""
349
+
350
+ TARGET_PROJECT="$(cd "$project_dir" 2>/dev/null && pwd)"
351
+ [ -n "$TARGET_PROJECT" ] || kc_fail "Unable to resolve project dir: $project_dir"
352
+
353
+ PROJECT_NAME="$(basename "$TARGET_PROJECT")"
354
+ PROJECT_SLUG="$(kc_slugify "$PROJECT_NAME")"
355
+ PROJECT_PROFILE="unknown"
356
+ AGENT_PROJECT_FILE="$TARGET_PROJECT/.agent-project.yaml"
357
+ POINTER_PATH="$TARGET_PROJECT/agent-knowledge"
358
+ POINTER_DISPLAY="./agent-knowledge"
359
+ FRAMEWORK_REPO=""
360
+
361
+ if [ -f "$AGENT_PROJECT_FILE" ]; then
362
+ PROJECT_NAME="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "name" || printf '%s' "$PROJECT_NAME")"
363
+ PROJECT_SLUG="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "slug" || printf '%s' "$PROJECT_SLUG")"
364
+ PROJECT_PROFILE="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "profile_hint" || kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "profile" || printf 'unknown')"
365
+ FRAMEWORK_REPO="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "repo" || true)"
366
+ pointer_value="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "pointer_path" || true)"
367
+ real_value="$(kc_yaml_leaf_value "$AGENT_PROJECT_FILE" "real_path" || true)"
368
+ if [ -n "$pointer_value" ]; then
369
+ POINTER_DISPLAY="$pointer_value"
370
+ POINTER_PATH="$(kc_resolve_relative "$TARGET_PROJECT" "$pointer_value")"
371
+ fi
372
+ fi
373
+
374
+ if [ -n "$real_value" ]; then
375
+ KNOWLEDGE_REAL_DIR="$(kc_resolve_relative "$TARGET_PROJECT" "$real_value")"
376
+ if [ -d "$KNOWLEDGE_REAL_DIR" ]; then
377
+ KNOWLEDGE_REAL_DIR="$(cd "$KNOWLEDGE_REAL_DIR" 2>/dev/null && pwd -P)"
378
+ fi
379
+ elif [ -d "$POINTER_PATH" ]; then
380
+ KNOWLEDGE_REAL_DIR="$(cd "$POINTER_PATH" 2>/dev/null && pwd -P)"
381
+ else
382
+ KNOWLEDGE_REAL_DIR="$POINTER_PATH"
383
+ fi
384
+
385
+ KNOWLEDGE_POINTER_PATH="$POINTER_PATH"
386
+ KNOWLEDGE_DIR="$POINTER_PATH"
387
+ MEMORY_DIR="$KNOWLEDGE_DIR/Memory"
388
+ MEMORY_ROOT="$MEMORY_DIR/MEMORY.md"
389
+ DECISIONS_DIR="$MEMORY_DIR/decisions"
390
+ EVIDENCE_DIR="$KNOWLEDGE_DIR/Evidence"
391
+ EVIDENCE_RAW_DIR="$EVIDENCE_DIR/raw"
392
+ EVIDENCE_IMPORTS_DIR="$EVIDENCE_DIR/imports"
393
+ EVIDENCE_TOOLING_DIR="$EVIDENCE_DIR/tooling"
394
+ SESSIONS_DIR="$KNOWLEDGE_DIR/Sessions"
395
+ OUTPUTS_DIR="$KNOWLEDGE_DIR/Outputs"
396
+ DASHBOARDS_DIR="$KNOWLEDGE_DIR/Dashboards"
397
+ LOCAL_TEMPLATES_DIR="$KNOWLEDGE_DIR/Templates"
398
+ OBSIDIAN_DIR="$KNOWLEDGE_DIR/.obsidian"
399
+ STATUS_FILE="$KNOWLEDGE_DIR/STATUS.md"
400
+ KNOWLEDGE_CACHE_DIR="$KNOWLEDGE_DIR/.cache"
401
+ EVIDENCE_CACHE_DIR="$EVIDENCE_DIR/.cache"
402
+ IGNORE_FILE="$TARGET_PROJECT/.agentknowledgeignore"
403
+ KC_IGNORE_PATTERNS=()
404
+ if [ -f "$IGNORE_FILE" ]; then
405
+ while IFS= read -r pattern; do
406
+ pattern="${pattern%$'\r'}"
407
+ case "$pattern" in
408
+ ""|\#*)
409
+ continue
410
+ ;;
411
+ esac
412
+ KC_IGNORE_PATTERNS+=("$pattern")
413
+ done < "$IGNORE_FILE"
414
+ fi
415
+ }
416
+
417
+ kc_is_windows_like() {
418
+ case "$(uname -s 2>/dev/null || echo unknown)" in
419
+ MINGW*|MSYS*|CYGWIN*)
420
+ return 0
421
+ ;;
422
+ *)
423
+ return 1
424
+ ;;
425
+ esac
426
+ }
427
+
428
+ kc_pointer_resolved_path() {
429
+ [ -d "$KNOWLEDGE_POINTER_PATH" ] || return 1
430
+ (
431
+ cd "$KNOWLEDGE_POINTER_PATH" 2>/dev/null && pwd -P
432
+ )
433
+ }
434
+
435
+ kc_rel_knowledge_path() {
436
+ local path="$1"
437
+
438
+ case "$path" in
439
+ "$KNOWLEDGE_DIR"/*)
440
+ printf '%s\n' "${path#$KNOWLEDGE_DIR/}"
441
+ ;;
442
+ "$KNOWLEDGE_REAL_DIR"/*)
443
+ printf '%s\n' "${path#$KNOWLEDGE_REAL_DIR/}"
444
+ ;;
445
+ *)
446
+ printf '%s\n' "$path"
447
+ ;;
448
+ esac
449
+ }
450
+
451
+ kc_normalize_relative_path() {
452
+ local path="${1:-}"
453
+ path="${path#./}"
454
+ path="${path#/}"
455
+ printf '%s\n' "$path"
456
+ }
457
+
458
+ kc_path_is_ignored() {
459
+ local path=""
460
+ local pattern=""
461
+ local normalized_pattern=""
462
+ local base=""
463
+
464
+ path="$(kc_normalize_relative_path "${1:-}")"
465
+ [ -n "$path" ] || return 1
466
+
467
+ for pattern in "${KC_IGNORE_PATTERNS[@]+"${KC_IGNORE_PATTERNS[@]}"}"; do
468
+ normalized_pattern="$(kc_normalize_relative_path "$pattern")"
469
+ [ -n "$normalized_pattern" ] || continue
470
+ case "$normalized_pattern" in
471
+ */)
472
+ base="${normalized_pattern%/}"
473
+ case "$path" in
474
+ $base|$base/*)
475
+ return 0
476
+ ;;
477
+ esac
478
+ ;;
479
+ *)
480
+ case "$path" in
481
+ $normalized_pattern|$normalized_pattern/*)
482
+ return 0
483
+ ;;
484
+ esac
485
+ ;;
486
+ esac
487
+ done
488
+ return 1
489
+ }
490
+
491
+ kc_filter_relative_lines() {
492
+ local line=""
493
+ local normalized=""
494
+
495
+ while IFS= read -r line; do
496
+ [ -n "$line" ] || continue
497
+ normalized="$(kc_normalize_relative_path "$line")"
498
+ if kc_path_is_ignored "$normalized"; then
499
+ continue
500
+ fi
501
+ printf '%s\n' "$normalized"
502
+ done
503
+ }
504
+
505
+ kc_hash_text() {
506
+ if command -v shasum >/dev/null 2>&1; then
507
+ shasum -a 256 | awk '{print $1}'
508
+ else
509
+ openssl dgst -sha256 | awk '{print $NF}'
510
+ fi
511
+ }
512
+
513
+ kc_stat_signature_line() {
514
+ local path="$1"
515
+ local label="${2:-$1}"
516
+ local meta=""
517
+
518
+ if stat -f '%m|%z' "$path" >/dev/null 2>&1; then
519
+ meta="$(stat -f '%m|%z' "$path" 2>/dev/null)"
520
+ else
521
+ meta="$(stat -c '%Y|%s' "$path" 2>/dev/null)"
522
+ fi
523
+ printf '%s|%s\n' "$label" "$meta"
524
+ }
525
+
526
+ kc_signature_from_lines() {
527
+ local text="${1:-}"
528
+ printf '%s' "$text" | kc_hash_text
529
+ }
530
+
531
+ kc_signature_from_paths() {
532
+ local line=""
533
+ local text=""
534
+
535
+ for line in "$@"; do
536
+ [ -n "$line" ] || continue
537
+ if [ -e "$line" ]; then
538
+ text="${text}$(kc_stat_signature_line "$line")"$'\n'
539
+ else
540
+ text="${text}missing|$line"$'\n'
541
+ fi
542
+ done
543
+
544
+ kc_signature_from_lines "$text"
545
+ }
546
+
547
+ kc_signature_from_project_relative_lines() {
548
+ local relpaths_text="${1:-}"
549
+ local line=""
550
+ local abs=""
551
+ local text=""
552
+
553
+ while IFS= read -r line; do
554
+ [ -n "$line" ] || continue
555
+ abs="$TARGET_PROJECT/$(kc_normalize_relative_path "$line")"
556
+ if [ -e "$abs" ]; then
557
+ text="${text}$(kc_stat_signature_line "$abs" "$line")"$'\n'
558
+ else
559
+ text="${text}missing|$line"$'\n'
560
+ fi
561
+ done <<EOF
562
+ $relpaths_text
563
+ EOF
564
+
565
+ if [ -f "$IGNORE_FILE" ]; then
566
+ text="${text}$(kc_stat_signature_line "$IGNORE_FILE" ".agentknowledgeignore")"$'\n'
567
+ fi
568
+
569
+ kc_signature_from_lines "$text"
570
+ }
571
+
572
+ kc_cache_key_name() {
573
+ printf '%s' "$1" | sed 's/[^A-Za-z0-9._-]/_/g'
574
+ }
575
+
576
+ kc_cache_signature_path() {
577
+ local namespace="$1"
578
+ local key="$2"
579
+ printf '%s/%s/%s.sig\n' "$KNOWLEDGE_CACHE_DIR" "$(kc_cache_key_name "$namespace")" "$(kc_cache_key_name "$key")"
580
+ }
581
+
582
+ kc_cache_is_current() {
583
+ local namespace="$1"
584
+ local key="$2"
585
+ local signature="$3"
586
+ local cache_path=""
587
+ local current=""
588
+ shift 3
589
+
590
+ cache_path="$(kc_cache_signature_path "$namespace" "$key")"
591
+ [ -f "$cache_path" ] || return 1
592
+ current="$(cat "$cache_path" 2>/dev/null || true)"
593
+ [ "$current" = "$signature" ] || return 1
594
+
595
+ for current in "$@"; do
596
+ [ -e "$current" ] || return 1
597
+ done
598
+ return 0
599
+ }
600
+
601
+ kc_cache_store() {
602
+ local namespace="$1"
603
+ local key="$2"
604
+ local signature="$3"
605
+ local dst
606
+
607
+ dst="$(kc_cache_signature_path "$namespace" "$key")"
608
+ kc_write_text_file "$dst" ".cache/$(kc_cache_key_name "$namespace")/$(kc_cache_key_name "$key").sig" <<EOF
609
+ $signature
610
+ EOF
611
+ }
612
+
613
+ kc_write_metadata_json() {
614
+ local dst="$1"
615
+ local label="$2"
616
+ local source="$3"
617
+ local kind="$4"
618
+ local confidence="$5"
619
+ local generated_at="$6"
620
+ local related_paths_text="${7:-}"
621
+ local notes_text="${8:-}"
622
+ local tmp_file
623
+
624
+ tmp_file="$(mktemp)"
625
+ {
626
+ printf '{'
627
+ printf '"source":"%s",' "$(kc_json_escape "$source")"
628
+ printf '"kind":"%s",' "$(kc_json_escape "$kind")"
629
+ printf '"confidence":"%s",' "$(kc_json_escape "$confidence")"
630
+ printf '"generated_at":"%s",' "$(kc_json_escape "$generated_at")"
631
+ printf '"related_paths":%s,' "$(kc_json_array_from_lines "$related_paths_text")"
632
+ printf '"notes":%s' "$(kc_json_array_from_lines "$notes_text")"
633
+ printf '}\n'
634
+ } > "$tmp_file"
635
+
636
+ kc_apply_temp_file "$tmp_file" "$dst" "$label"
637
+ }
638
+
639
+ kc_yaml_list() {
640
+ local text="${1:-}"
641
+ local indent="${2:-2}"
642
+ local line=""
643
+ local prefix=""
644
+ local i=0
645
+
646
+ while [ "$i" -lt "$indent" ]; do
647
+ prefix="${prefix} "
648
+ i=$((i + 1))
649
+ done
650
+
651
+ while IFS= read -r line; do
652
+ [ -n "$line" ] || continue
653
+ printf '%s- %s\n' "$prefix" "$line"
654
+ done <<EOF
655
+ $text
656
+ EOF
657
+ }
658
+
659
+ kc_require_project_metadata() {
660
+ [ -f "$AGENT_PROJECT_FILE" ] || kc_fail "Missing $AGENT_PROJECT_FILE"
661
+ }
662
+
663
+ kc_require_knowledge_pointer() {
664
+ [ -e "$KNOWLEDGE_POINTER_PATH" ] || kc_fail "Missing local knowledge pointer: $KNOWLEDGE_POINTER_PATH"
665
+ [ -d "$KNOWLEDGE_POINTER_PATH" ] || kc_fail "Local knowledge pointer is not a directory: $KNOWLEDGE_POINTER_PATH"
666
+ pointer_resolved="$(kc_pointer_resolved_path || true)"
667
+ [ -n "$pointer_resolved" ] || kc_fail "Unable to resolve local knowledge pointer: $KNOWLEDGE_POINTER_PATH"
668
+ [ "$pointer_resolved" = "$KNOWLEDGE_REAL_DIR" ] || kc_fail "Local knowledge pointer must resolve to the external knowledge folder: $KNOWLEDGE_REAL_DIR"
669
+ if [ ! -L "$KNOWLEDGE_POINTER_PATH" ] && ! kc_is_windows_like; then
670
+ kc_fail "Local knowledge handle must be a symlink to the external knowledge folder, not a repo-local directory: $KNOWLEDGE_POINTER_PATH"
671
+ fi
672
+ }
673
+
674
+ kc_git_available() {
675
+ git -C "$TARGET_PROJECT" rev-parse --git-dir >/dev/null 2>&1
676
+ }
677
+
678
+ kc_git_has_commits() {
679
+ git -C "$TARGET_PROJECT" rev-parse --verify HEAD >/dev/null 2>&1
680
+ }
681
+
682
+ kc_git_changed_files() {
683
+ if ! kc_git_available; then
684
+ return 0
685
+ fi
686
+
687
+ git -C "$TARGET_PROJECT" status --porcelain 2>/dev/null \
688
+ | sed 's/^...//' \
689
+ | sed 's/ -> /\n/' \
690
+ | awk 'NF { print $0 }' \
691
+ | sort -u
692
+ }
693
+
694
+ kc_append_unique_bullet() {
695
+ local file="$1"
696
+ local section="$2"
697
+ local bullet="$3"
698
+ local label="${4:-$file}"
699
+ local tmp_file
700
+
701
+ [ -f "$file" ] || return 1
702
+
703
+ tmp_file="$(mktemp)"
704
+ awk -v heading="## $section" -v bullet="$bullet" '
705
+ BEGIN {
706
+ in_section = 0
707
+ seen = 0
708
+ inserted = 0
709
+ }
710
+ {
711
+ if (in_section && /^## / && $0 != heading) {
712
+ if (!seen && !inserted) {
713
+ print bullet
714
+ inserted = 1
715
+ }
716
+ in_section = 0
717
+ }
718
+
719
+ if ($0 == heading) {
720
+ in_section = 1
721
+ }
722
+
723
+ if (in_section && $0 == bullet) {
724
+ seen = 1
725
+ }
726
+
727
+ print
728
+ }
729
+ END {
730
+ if (in_section && !seen && !inserted) {
731
+ print bullet
732
+ }
733
+ }
734
+ ' "$file" > "$tmp_file"
735
+
736
+ kc_apply_temp_file "$tmp_file" "$file" "$label"
737
+ }
738
+
739
+ kc_has_frontmatter() {
740
+ local file="$1"
741
+ head -1 "$file" 2>/dev/null | grep -q '^---$'
742
+ }
743
+
744
+ kc_status_load() {
745
+ STATUS_PROJECT="$PROJECT_NAME"
746
+ STATUS_PROFILE="$PROJECT_PROFILE"
747
+ STATUS_ONTOLOGY_MODEL="2"
748
+ STATUS_REAL_PATH="$KNOWLEDGE_REAL_DIR"
749
+ STATUS_POINTER_PATH="$POINTER_DISPLAY"
750
+ STATUS_LAST_BOOTSTRAP=""
751
+ STATUS_LAST_IMPORT=""
752
+ STATUS_LAST_PROJECT_SYNC=""
753
+ STATUS_LAST_GLOBAL_SYNC=""
754
+ STATUS_LAST_GRAPH_SYNC=""
755
+ STATUS_LAST_COMPACTION=""
756
+ STATUS_LAST_VALIDATION=""
757
+ STATUS_LAST_VALIDATION_RESULT="unknown"
758
+ STATUS_LAST_DOCTOR=""
759
+ STATUS_LAST_DOCTOR_RESULT="unknown"
760
+ STATUS_ONBOARDING="pending"
761
+ STATUS_WARNING_LINES=""
762
+
763
+ if [ ! -f "$STATUS_FILE" ]; then
764
+ return 0
765
+ fi
766
+
767
+ STATUS_PROJECT="$(kc_yaml_leaf_value "$STATUS_FILE" "project" || printf '%s' "$STATUS_PROJECT")"
768
+ # Read profile_hint first, fall back to legacy profile key
769
+ STATUS_PROFILE="$(kc_yaml_leaf_value "$STATUS_FILE" "profile_hint" || kc_yaml_leaf_value "$STATUS_FILE" "profile" || printf '%s' "$STATUS_PROFILE")"
770
+ STATUS_ONTOLOGY_MODEL="$(kc_yaml_leaf_value "$STATUS_FILE" "ontology_model" || printf '2')"
771
+ STATUS_REAL_PATH="$(kc_yaml_leaf_value "$STATUS_FILE" "real_knowledge_path" || printf '%s' "$STATUS_REAL_PATH")"
772
+ STATUS_POINTER_PATH="$(kc_yaml_leaf_value "$STATUS_FILE" "local_pointer_path" || printf '%s' "$STATUS_POINTER_PATH")"
773
+ STATUS_ONBOARDING="$(kc_yaml_leaf_value "$STATUS_FILE" "onboarding" || printf 'pending')"
774
+ STATUS_LAST_BOOTSTRAP="$(kc_yaml_leaf_value "$STATUS_FILE" "last_bootstrap" || true)"
775
+ STATUS_LAST_IMPORT="$(kc_yaml_leaf_value "$STATUS_FILE" "last_backfill_import" || true)"
776
+ STATUS_LAST_PROJECT_SYNC="$(kc_yaml_leaf_value "$STATUS_FILE" "last_project_sync" || true)"
777
+ STATUS_LAST_GLOBAL_SYNC="$(kc_yaml_leaf_value "$STATUS_FILE" "last_global_sync" || true)"
778
+ STATUS_LAST_GRAPH_SYNC="$(kc_yaml_leaf_value "$STATUS_FILE" "last_graph_sync" || true)"
779
+ STATUS_LAST_COMPACTION="$(kc_yaml_leaf_value "$STATUS_FILE" "last_compaction" || true)"
780
+ STATUS_LAST_VALIDATION="$(kc_yaml_leaf_value "$STATUS_FILE" "last_validation" || true)"
781
+ STATUS_LAST_VALIDATION_RESULT="$(kc_yaml_leaf_value "$STATUS_FILE" "last_validation_result" || printf 'unknown')"
782
+ STATUS_LAST_DOCTOR="$(kc_yaml_leaf_value "$STATUS_FILE" "last_doctor" || true)"
783
+ STATUS_LAST_DOCTOR_RESULT="$(kc_yaml_leaf_value "$STATUS_FILE" "last_doctor_result" || printf 'unknown')"
784
+ STATUS_WARNING_LINES="$(
785
+ awk '
786
+ $0 == "## Health Warnings" { in_section = 1; next }
787
+ in_section && /^## / { exit }
788
+ in_section && /^- / { sub(/^- /, "", $0); print }
789
+ ' "$STATUS_FILE" 2>/dev/null
790
+ )"
791
+ }
792
+
793
+ kc_status_write() {
794
+ local tmp_file
795
+ local warnings_text="${1:-$STATUS_WARNING_LINES}"
796
+ local warning=""
797
+
798
+ tmp_file="$(mktemp)"
799
+ {
800
+ printf '%s\n' '---'
801
+ printf 'note_type: knowledge-status\n'
802
+ printf 'project: %s\n' "$STATUS_PROJECT"
803
+ printf 'profile_hint: %s\n' "$STATUS_PROFILE"
804
+ printf 'ontology_model: %s\n' "${STATUS_ONTOLOGY_MODEL:-2}"
805
+ printf 'real_knowledge_path: %s\n' "$STATUS_REAL_PATH"
806
+ printf 'local_pointer_path: %s\n' "$STATUS_POINTER_PATH"
807
+ printf 'onboarding: %s\n' "$STATUS_ONBOARDING"
808
+ printf 'last_bootstrap: %s\n' "$STATUS_LAST_BOOTSTRAP"
809
+ printf 'last_backfill_import: %s\n' "$STATUS_LAST_IMPORT"
810
+ printf 'last_project_sync: %s\n' "$STATUS_LAST_PROJECT_SYNC"
811
+ printf 'last_global_sync: %s\n' "$STATUS_LAST_GLOBAL_SYNC"
812
+ printf 'last_graph_sync: %s\n' "$STATUS_LAST_GRAPH_SYNC"
813
+ printf 'last_compaction: %s\n' "$STATUS_LAST_COMPACTION"
814
+ printf 'last_validation: %s\n' "$STATUS_LAST_VALIDATION"
815
+ printf 'last_validation_result: %s\n' "$STATUS_LAST_VALIDATION_RESULT"
816
+ printf 'last_doctor: %s\n' "$STATUS_LAST_DOCTOR"
817
+ printf 'last_doctor_result: %s\n' "$STATUS_LAST_DOCTOR_RESULT"
818
+ printf '%s\n\n' '---'
819
+ printf '# Knowledge Status: %s\n\n' "$STATUS_PROJECT"
820
+ printf '## Current State\n\n'
821
+ printf -- '- Profile hint: `%s`\n' "$STATUS_PROFILE"
822
+ printf -- '- Ontology model: `%s`\n' "${STATUS_ONTOLOGY_MODEL:-2}"
823
+ printf -- '- Real knowledge path: `%s`\n' "$STATUS_REAL_PATH"
824
+ printf -- '- Local pointer path: `%s`\n' "$STATUS_POINTER_PATH"
825
+ printf -- '- Onboarding: `%s`\n\n' "$STATUS_ONBOARDING"
826
+ printf '## Activity\n\n'
827
+ printf -- '- Last bootstrap: `%s`\n' "${STATUS_LAST_BOOTSTRAP:-not-yet}"
828
+ printf -- '- Last backfill/import: `%s`\n' "${STATUS_LAST_IMPORT:-not-yet}"
829
+ printf -- '- Last project sync: `%s`\n' "${STATUS_LAST_PROJECT_SYNC:-not-yet}"
830
+ printf -- '- Last global sync: `%s`\n' "${STATUS_LAST_GLOBAL_SYNC:-not-yet}"
831
+ printf -- '- Last graph sync: `%s`\n' "${STATUS_LAST_GRAPH_SYNC:-not-yet}"
832
+ printf -- '- Last compaction: `%s`\n' "${STATUS_LAST_COMPACTION:-not-yet}"
833
+ printf -- '- Last validation: `%s` (`%s`)\n' "${STATUS_LAST_VALIDATION:-not-yet}" "${STATUS_LAST_VALIDATION_RESULT:-unknown}"
834
+ printf -- '- Last doctor: `%s` (`%s`)\n\n' "${STATUS_LAST_DOCTOR:-not-yet}" "${STATUS_LAST_DOCTOR_RESULT:-unknown}"
835
+ printf '## Health Warnings\n\n'
836
+ if [ -n "$warnings_text" ]; then
837
+ printf '%s\n' "$warnings_text" | while read -r warning; do
838
+ [ -n "$warning" ] || continue
839
+ printf -- '- %s\n' "$warning"
840
+ done
841
+ else
842
+ printf -- '- None.\n'
843
+ fi
844
+ } > "$tmp_file"
845
+
846
+ kc_apply_temp_file "$tmp_file" "$STATUS_FILE" "agent-knowledge/STATUS.md"
847
+ }
848
+
849
+ kc_run_shell_command() {
850
+ local label="$1"
851
+ local command="$2"
852
+ local workdir="${3:-$PWD}"
853
+ local tmp_file
854
+
855
+ tmp_file="$(mktemp)"
856
+ KC_COMMAND_OUTPUT=""
857
+ KC_COMMAND_STATUS="skipped"
858
+
859
+ if [ "${DRY_RUN:-0}" -eq 1 ]; then
860
+ kc_log " would run [$label]: $command"
861
+ KC_COMMAND_STATUS="dry-run"
862
+ rm -f "$tmp_file"
863
+ return 0
864
+ fi
865
+
866
+ if (cd "$workdir" && bash -lc "$command") > "$tmp_file" 2>&1; then
867
+ KC_COMMAND_STATUS="passed"
868
+ else
869
+ KC_COMMAND_STATUS="failed"
870
+ fi
871
+ KC_COMMAND_OUTPUT="$(cat "$tmp_file")"
872
+ rm -f "$tmp_file"
873
+
874
+ return 0
875
+ }