rollout-cli 0.5.1__tar.gz → 0.7.0__tar.gz

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 (133) hide show
  1. rollout_cli-0.7.0/.claude/skills/recall/SKILL.md +185 -0
  2. rollout_cli-0.7.0/.claude/skills/recall/scripts/recall.sh +164 -0
  3. rollout_cli-0.7.0/.claude/skills/remember/SKILL.md +121 -0
  4. rollout_cli-0.7.0/.claude/skills/remember/scripts/remember.sh +166 -0
  5. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/CHANGELOG.md +23 -0
  6. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/CLAUDE.md +28 -0
  7. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/PKG-INFO +4 -3
  8. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/README.md +3 -2
  9. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/docs/skill-sources.md +10 -0
  10. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/pyproject.toml +1 -1
  11. rollout_cli-0.7.0/recipes/eidetic-memory/README.md +161 -0
  12. rollout_cli-0.7.0/recipes/eidetic-memory/apply.sh +118 -0
  13. rollout_cli-0.7.0/recipes/eidetic-memory/check.sh +46 -0
  14. rollout_cli-0.7.0/recipes/eidetic-memory/files/CLAUDE.snippet.tmpl +25 -0
  15. rollout_cli-0.7.0/recipes/eidetic-memory/files/SKILL.md.recall.tmpl +185 -0
  16. rollout_cli-0.7.0/recipes/eidetic-memory/files/SKILL.md.remember.tmpl +121 -0
  17. rollout_cli-0.7.0/recipes/eidetic-memory/files/scripts/recall.sh +164 -0
  18. rollout_cli-0.7.0/recipes/eidetic-memory/files/scripts/remember.sh +166 -0
  19. rollout_cli-0.7.0/recipes/eidetic-memory/manifest.toml +51 -0
  20. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/devex_pr.py +18 -8
  21. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_devex_pr.py +23 -5
  22. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/uv.lock +1 -1
  23. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/agent-config/SKILL.md +0 -0
  24. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  25. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  26. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
  27. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  28. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  29. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  30. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  31. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  32. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  33. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/SKILL.md +0 -0
  34. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  35. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  36. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  37. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  38. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  39. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/SKILL.md +0 -0
  40. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  41. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  42. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  43. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  44. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  45. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  46. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  47. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  48. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/mass-update/SKILL.md +0 -0
  49. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/mass-update/scripts/mass-update.sh +0 -0
  50. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  51. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  52. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/run-tests/SKILL.md +0 -0
  53. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  54. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  55. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  56. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  57. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  58. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/think/SKILL.md +0 -0
  59. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/think/scripts/think.sh +0 -0
  60. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/version-bump/SKILL.md +0 -0
  61. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  62. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.claude/skills.local.yaml.example +0 -0
  63. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.devague/frames/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
  64. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
  65. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.devague/plans/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
  66. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
  67. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.flake8 +0 -0
  68. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.github/workflows/publish.yml +0 -0
  69. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.github/workflows/tests.yml +0 -0
  70. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.gitignore +0 -0
  71. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/.markdownlint-cli2.yaml +0 -0
  72. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/AGENTS.colleague.md +0 -0
  73. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/LICENSE +0 -0
  74. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/culture.yaml +0 -0
  75. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
  76. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/docs/plans/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
  77. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
  78. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/docs/specs/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
  79. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/apache-relicense/apply.sh +0 -0
  80. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/apache-relicense/check.sh +0 -0
  81. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/apache-relicense/files/LICENSE +0 -0
  82. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/apache-relicense/manifest.toml +0 -0
  83. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/README.md +0 -0
  84. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/apply.sh +0 -0
  85. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/check.sh +0 -0
  86. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +0 -0
  87. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/files/prompts/explore.md +0 -0
  88. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/files/prompts/review.md +0 -0
  89. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/files/prompts/write.md +0 -0
  90. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +0 -0
  91. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/recipes/ask-colleague-revendor/manifest.toml +0 -0
  92. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/__init__.py +0 -0
  93. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/__main__.py +0 -0
  94. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/__init__.py +0 -0
  95. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/__init__.py +0 -0
  96. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/apply.py +0 -0
  97. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/cli.py +0 -0
  98. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/doctor.py +0 -0
  99. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/explain.py +0 -0
  100. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/learn.py +0 -0
  101. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/overview.py +0 -0
  102. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/plan.py +0 -0
  103. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_commands/whoami.py +0 -0
  104. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_errors.py +0 -0
  105. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/cli/_output.py +0 -0
  106. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/__init__.py +0 -0
  107. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/bump.py +0 -0
  108. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/gitops.py +0 -0
  109. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/orchestrate.py +0 -0
  110. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/preflight.py +0 -0
  111. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/recipe.py +0 -0
  112. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/report.py +0 -0
  113. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/core/targets.py +0 -0
  114. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/explain/__init__.py +0 -0
  115. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/rollout/explain/catalog.py +0 -0
  116. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/sonar-project.properties +0 -0
  117. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/__init__.py +0 -0
  118. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_apache_relicense.py +0 -0
  119. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_apply_cmd.py +0 -0
  120. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_bump.py +0 -0
  121. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_cli.py +0 -0
  122. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_cli_introspection.py +0 -0
  123. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_cli_wiring.py +0 -0
  124. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_e2e_apache_relicense.py +0 -0
  125. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_explain_catalog.py +0 -0
  126. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_gitops.py +0 -0
  127. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_mass_update.py +0 -0
  128. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_orchestrate.py +0 -0
  129. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_plan_cmd.py +0 -0
  130. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_preflight.py +0 -0
  131. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_recipe.py +0 -0
  132. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_report.py +0 -0
  133. {rollout_cli-0.5.1 → rollout_cli-0.7.0}/tests/test_targets.py +0 -0
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: recall
3
+ type: command
4
+ description: >
5
+ Search the shared eidetic memory store and get back ranked, provenanced
6
+ records. Drives `eidetic recall` with four search modes — exact (verbatim
7
+ substring), approximate (vector/semantic), keyword (BM25 lexical), and hybrid
8
+ (a weighted blend of vector+keyword, the default) — each hit carrying its
9
+ text, full metadata, a relevance `score`, and a freshness `signal`. Recall
10
+ passively reinforces matched records (bumps last_recall + recall_count).
11
+ Shadowed and archived records are excluded by default; use
12
+ --include-shadowed / --include-archived to retrieve them. The store uses
13
+ visibility-aware routing: PUBLIC records inside a git repo go to
14
+ <repo-root>/.eidetic/memory (committed, team-shared); PRIVATE records, or any
15
+ record outside a git repo, go to $HOME/.eidetic/memory (never committed). An
16
+ explicit EIDETIC_DATA_DIR wins and short-circuits to that single dir. Recall
17
+ reads both stores and merges. The wrapper defaults queries to this agent's
18
+ PERSONAL, PRIVATE scope (`--scope rollout-cli --visibility private`, suffix
19
+ read from culture.yaml) — matching where /remember writes — so a no-flag
20
+ recall returns this agent's own private records plus the shared public pool,
21
+ and Claude and the colleague backend recall each other's memories because both
22
+ resolve the same suffix via this skill. Use
23
+ when the user says "recall", "what do we know about X", "search memory",
24
+ "have we seen X before", "look it up in memory", "eidetic recall", or before
25
+ answering from scratch when prior context may already be stored. Pairs with
26
+ the sibling /remember skill.
27
+ ---
28
+
29
+ # recall — search the shared eidetic memory
30
+
31
+ `recall` drives **`eidetic recall`**: given a query, it returns the top-k stored
32
+ records ranked by relevance, each with its `text`, full `metadata` (provenance),
33
+ a numeric `score`, and a freshness `signal`. It is the read half of the memory
34
+ surface; the write half is the sibling **/remember** skill.
35
+
36
+ The point of a *shared* store is that memory is a **team faculty**, not a
37
+ per-agent silo: a record Claude wrote is recallable by the colleague backend
38
+ (and vice versa), because both resolve the same `$HOME/.eidetic/memory` path.
39
+
40
+ ## How to run
41
+
42
+ ```bash
43
+ bash .claude/skills/recall/scripts/recall.sh "<query>" [flags...]
44
+ ```
45
+
46
+ The wrapper resolves the CLI portably (installed `eidetic` on `PATH`, else
47
+ `uv run eidetic` from the checkout) and forwards every flag verbatim, so it is
48
+ exactly `eidetic recall …`. Run it from anywhere; the store is the same.
49
+
50
+ ## Search modes (`--mode`, default `hybrid`)
51
+
52
+ | Mode | What it matches | Needs embed server? |
53
+ |------|-----------------|---------------------|
54
+ | `exact` | case-insensitive verbatim substring (`--case-sensitive` to tighten) | no — offline-safe |
55
+ | `approximate` | vector cosine / semantic similarity | yes (falls back offline) |
56
+ | `keyword` | BM25 lexical; only records sharing a query term | no — offline-safe |
57
+ | `hybrid` | `alpha*approximate + (1-alpha)*keyword` (`--alpha`, default 0.5) | uses it when up |
58
+
59
+ `hybrid` is the default because the two signals cover each other's blind spots:
60
+ vector catches paraphrases, keyword catches exact ids/quotes. When the embed
61
+ server is unreachable, `hybrid` collapses to keyword-only (it never fuses
62
+ meaningless offline-fallback cosine).
63
+
64
+ ## Output fields
65
+
66
+ Each hit in `--json` output includes:
67
+
68
+ | Field | Notes |
69
+ |-------|-------|
70
+ | `id` | stable record identity |
71
+ | `text` | the stored chunk |
72
+ | `type` | record type |
73
+ | `metadata` | full provenance, round-tripped verbatim from ingest |
74
+ | `score` | relevance score from the chosen search mode (freshness-blended) |
75
+ | `signal` | freshness strength in [0, 1]; computed at recall time from age, recall frequency, and staleness |
76
+ | `created` | ISO-8601 ingest date (may be DATE_UNKNOWN for legacy records) |
77
+ | `last_recall` | ISO-8601 timestamp of the most recent recall hit (null if never recalled) |
78
+ | `recall_count` | number of times this record has been recalled (passive reinforcement counter) |
79
+ | `lifecycle` | `active`, `shadowed`, or `archived` |
80
+ | `links` | list of related-memory ids |
81
+
82
+ ## Freshness signal
83
+
84
+ Every `recall` hit carries a `signal` field (float in `[0, 1]`). The signal
85
+ blends **multiplicatively** into the lexical/vector score so recently-created
86
+ and frequently-recalled records surface ahead of stale ones. The formula:
87
+
88
+ ```
89
+ access_bonus = min(0.5, recall_count * 0.05)
90
+ age_factor = 1 / (1 + days_since_creation * 0.01)
91
+ staleness = days_since_last_recall * 0.01
92
+ signal = clamp((0.5 - staleness + access_bonus) * age_factor, 0, 1)
93
+ blended_score = score * (1 + 0.25 * (signal - 0.5))
94
+ ```
95
+
96
+ Records with no temporal data (legacy, undated) are an exact no-op — the blend
97
+ is skipped for them so pre-existing fixture scores are unchanged.
98
+
99
+ Each `recall` call is also **passive reinforcement**: it bumps `last_recall` and
100
+ `recall_count` on every matched record, so frequently-recalled memories organically
101
+ gain signal strength over time.
102
+
103
+ ## Lifecycle flags
104
+
105
+ By default, `recall` returns only `active` records. Use these flags to retrieve
106
+ non-active records:
107
+
108
+ - `--include-shadowed` — include records whose `lifecycle == "shadowed"` (records
109
+ superseded within their scope by a newer record). Shadowed records are preserved
110
+ and still searchable; they are just hidden from the default result set.
111
+ - `--include-archived` — include records whose `lifecycle == "archived"` (records
112
+ older than ~1 year or below the signal threshold). Archived records are fully
113
+ preserved; the flag makes them retrievable again.
114
+
115
+ Both flags can be combined. Neither affects ranking — shadowed/archived records
116
+ compete on score/signal just like active ones when included.
117
+
118
+ ## Common flags (forwarded to `eidetic recall`)
119
+
120
+ - `--mode exact|approximate|keyword|hybrid` — default `hybrid`.
121
+ - `--top-k N` — max results (default 5).
122
+ - `--alpha F` — hybrid blend weight in `[0,1]` (default 0.5).
123
+ - `--case-sensitive` — for `--mode exact`.
124
+ - `--filter KEY=VALUE` — metadata facet filter (repeatable): e.g. `--filter source=docs`.
125
+ - `--scope NAME` / `--visibility public|private` — scope isolation (no private
126
+ leak). **The wrapper defaults this to the agent's PERSONAL, PRIVATE scope**
127
+ (`--scope rollout-cli --visibility private`, suffix read from `culture.yaml`),
128
+ matching where `/remember` writes — so a no-flag recall returns this agent's
129
+ own private records **plus** the shared public pool, while those private records
130
+ stay invisible to a `default`/other-scope recall. Pass `--scope`/`--visibility`
131
+ to query elsewhere; a wheel install with no `culture.yaml` falls back to the
132
+ CLI default `default`/`public`.
133
+ - `--backend files|mongo|neo4j` — default `files` (the shared home-dir store).
134
+ - `--include-shadowed` — include shadowed records in results (excluded by default).
135
+ - `--include-archived` — include archived records in results (excluded by default).
136
+ - `--json` — structured list to stdout (use this when an agent parses the result).
137
+
138
+ ## Examples
139
+
140
+ ```bash
141
+ # Default hybrid recall, JSON for an agent to parse:
142
+ bash .claude/skills/recall/scripts/recall.sh "jetson nano power draw" --json
143
+
144
+ # Find the exact message that mentions a phrase:
145
+ bash .claude/skills/recall/scripts/recall.sh "Orin Nano" --mode exact
146
+
147
+ # Keyword search, offline-safe, narrowed to a source:
148
+ bash .claude/skills/recall/scripts/recall.sh "thermal throttle" --mode keyword \
149
+ --filter source=discord --top-k 10
150
+
151
+ # Retrieve a record that was recently shadowed (its superseding record is now active):
152
+ bash .claude/skills/recall/scripts/recall.sh "old topic" --include-shadowed --json
153
+
154
+ # Retrieve all records including archived (to audit stale memories):
155
+ bash .claude/skills/recall/scripts/recall.sh "power" --include-archived --include-shadowed --json
156
+ ```
157
+
158
+ ## Notes
159
+
160
+ - **Provenance is mandatory** on every hit — recall is for *cited* answers.
161
+ - The embed endpoint defaults to the local model-gear embed gear
162
+ (`http://localhost:8002/v1`, model `Qwen/Qwen3-Embedding-0.6B`); override with
163
+ `EIDETIC_EMBED_URL` / `EIDETIC_EMBED_MODEL`. `exact`/`keyword` ignore it.
164
+ - **Use the wrapper, not a bare `eidetic`.** The console script may not be on
165
+ `PATH` (in a dev checkout it isn't) — the wrapper resolves it for you (`PATH`
166
+ first, else `uv run eidetic`). For the docs, run `eidetic explain recall` if
167
+ installed, otherwise `uv run --project <eidetic-cli checkout> eidetic explain
168
+ recall`. (`explain` is an **`eidetic`** verb — a sibling tool like `devex`
169
+ won't know it.)
170
+ - **Reading scores:** `exact`, `keyword`, and `hybrid` drop non-matching records
171
+ (hybrid drops any record with a `0.0` blended score), so their hits are real
172
+ matches. `approximate` keeps every candidate ranked by raw cosine, so it can
173
+ return low/near-zero scores when the store is small — lower `--top-k` to trim.
174
+ A `--min-score` threshold is a tracked follow-up.
175
+ - **Sharing scope = one OS user.** The default store is `$HOME/.eidetic/memory`, so
176
+ every agent/process running as the *same* OS user shares it (that is the point —
177
+ Claude + colleague). It is not isolated between OS users by anything but file
178
+ permissions; keep genuinely private data in a `--visibility private` scope and
179
+ treat the host as the trust boundary.
180
+
181
+ ## Provenance
182
+
183
+ First-party to **eidetic-cli** — eidetic owns its memory surface. Cite, don't
184
+ import: downstream repos copy this skill, they don't symlink it. See
185
+ [`docs/skill-sources.md`](../../../docs/skill-sources.md).
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ # recall.sh — search the shared eidetic memory store (the /recall skill).
3
+ #
4
+ # Thin, portable wrapper around `eidetic recall`. It resolves the CLI, points
5
+ # the embedding modes at the local model-gear embed gear (overridable), and
6
+ # forwards every flag verbatim — so `recall.sh "<query>" --mode hybrid --json`
7
+ # is exactly `eidetic recall "<query>" --mode hybrid --json`.
8
+ #
9
+ # The store is the files backend. Default location resolves per-operation:
10
+ # PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
11
+ # team-shared); PRIVATE records, or any record outside a git repo →
12
+ # $HOME/.eidetic/memory (never committed). Recall reads both stores and merges.
13
+ # An explicit EIDETIC_DATA_DIR wins and short-circuits to that single dir.
14
+
15
+ set -euo pipefail
16
+
17
+ # ── resolve the eidetic CLI (installed tool first, then dev checkout) ────────
18
+ EIDETIC=()
19
+ resolve_eidetic() {
20
+ if command -v eidetic >/dev/null 2>&1; then
21
+ EIDETIC=(eidetic) # installed console script — the normal case
22
+ return 0
23
+ fi
24
+ # Dev fallback: inside the eidetic-cli checkout, run via uv.
25
+ local dir
26
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
27
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
28
+ if [ -f "$dir/pyproject.toml" ] \
29
+ && grep -q '^name = "eidetic-cli"' "$dir/pyproject.toml" 2>/dev/null; then
30
+ if command -v uv >/dev/null 2>&1; then
31
+ EIDETIC=(uv run --project "$dir" eidetic)
32
+ return 0
33
+ fi
34
+ break
35
+ fi
36
+ dir=$(dirname "$dir")
37
+ done
38
+ # In a vendored copy there is no eidetic-cli checkout to fall back to, so the
39
+ # only honest remedy is to install the CLI. One `error:` + one `hint:` line.
40
+ printf 'error: eidetic CLI not found.\n' >&2
41
+ printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
42
+ return 1
43
+ }
44
+
45
+ usage() {
46
+ cat <<'EOF'
47
+ recall.sh — search the shared eidetic memory store (the /recall skill).
48
+
49
+ Usage:
50
+ recall.sh "<query>" [--mode exact|approximate|keyword|hybrid] [--top-k N] \
51
+ [--alpha F] [--case-sensitive] [--filter KEY=VALUE]... \
52
+ [--backend files|mongo|neo4j] [--scope NAME] [--visibility public|private] \
53
+ [--json]
54
+
55
+ Modes (default: hybrid):
56
+ exact case-insensitive verbatim substring (--case-sensitive to tighten); offline-safe
57
+ approximate vector cosine / semantic similarity (uses the embed server)
58
+ keyword BM25 lexical; only records sharing a query term; offline-safe
59
+ hybrid alpha*approximate + (1-alpha)*keyword (--alpha, default 0.5);
60
+ degrades to keyword-only when the embed server is offline
61
+
62
+ Every flag is forwarded verbatim to `eidetic recall`. See `eidetic explain recall`.
63
+ EOF
64
+ }
65
+
66
+ case "${1:-}" in
67
+ -h | --help)
68
+ usage
69
+ exit 0
70
+ ;;
71
+ "")
72
+ # A missing query is a usage error, not success. The bareword `help` is
73
+ # a legitimate search term, so it is intentionally NOT a usage alias.
74
+ printf 'error: no query given.\n' >&2
75
+ printf 'hint: recall.sh "<query>" [--mode ...] [--json]; run recall.sh --help for usage.\n' >&2
76
+ exit 1
77
+ ;;
78
+ esac
79
+
80
+ resolve_eidetic || exit 2
81
+
82
+ # ── default to this agent's PERSONAL scope, PUBLIC visibility (culture.yaml `suffix`) ──
83
+ # Query this agent's OWN personal scope by default, matching where /remember
84
+ # writes, instead of the global `default` scope shared by every project on this
85
+ # host. We read the `suffix` from the nearest culture.yaml (walking up from this
86
+ # script), so the scope follows the repo identity rather than being hard-coded —
87
+ # a downstream cite-don't-import copy adapts to its own suffix, and the colleague
88
+ # backend (running in a worktree of this same repo) resolves the same suffix,
89
+ # keeping the Claude↔colleague shared-memory story intact.
90
+ #
91
+ # Visibility defaults to PUBLIC (a recipe-level policy override of eidetic's
92
+ # upstream private default — see the POLICY OVERRIDE comment below) to match
93
+ # /remember, so a plain recall queries the in-repo public pool at
94
+ # <repo>/.eidetic/memory that /remember writes to. Scope and visibility are
95
+ # paired — the public default applies only when we inject the resolved scope,
96
+ # and only if the caller didn't pass --visibility (so an explicit
97
+ # `--visibility private` still wins, also surfacing this agent's private $HOME
98
+ # notes). An explicit --scope on the command line takes over steering entirely; a
99
+ # wheel install with no culture.yaml falls back to the plain CLI default
100
+ # (`default`/`public`).
101
+ resolve_scope() {
102
+ local dir suffix=""
103
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
104
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
105
+ if [ -f "$dir/culture.yaml" ]; then
106
+ # Capture only the first non-space token after `suffix:` (so an
107
+ # inline `# comment` or trailing space can't bleed into the scope),
108
+ # then strip surrounding quotes only — matching the canonical parser
109
+ # in .claude/skills/cicd/scripts/_resolve-nick.sh.
110
+ # `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
111
+ # early can SIGPIPE `sed`, making the substitution non-zero and
112
+ # aborting the script. An empty parse must yield "" here, not exit.
113
+ suffix=$(sed -n \
114
+ 's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
115
+ "$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
116
+ break
117
+ fi
118
+ dir=$(dirname "$dir")
119
+ done
120
+ printf '%s' "$suffix"
121
+ }
122
+
123
+ has_flag() {
124
+ local needle=$1
125
+ shift
126
+ local a
127
+ for a in "$@"; do
128
+ case "$a" in
129
+ "$needle" | "$needle"=*) return 0 ;;
130
+ esac
131
+ done
132
+ return 1
133
+ }
134
+
135
+ SCOPE_ARGS=()
136
+ if ! has_flag --scope "$@"; then
137
+ EIDETIC_SCOPE=$(resolve_scope)
138
+ if [ -n "$EIDETIC_SCOPE" ]; then
139
+ SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
140
+ # rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
141
+ # upstream private default): default to PUBLIC, so a plain recall queries
142
+ # the in-repo public pool (<repo>/.eidetic/memory) this repo writes to.
143
+ # Pass --visibility private to also surface this agent's private ($HOME)
144
+ # notes. The two-store read model reads both dirs regardless.
145
+ has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
146
+ elif ! has_flag --visibility "$@"; then
147
+ # No suffix AND no explicit --visibility: the query runs against
148
+ # eidetic's own default (scope=default, visibility=public), not this
149
+ # agent's private personal scope — so an empty result isn't silently
150
+ # misread. Warn on stderr (stdout stays clean for --json). Warn ONLY
151
+ # here: an explicit --scope (outer guard) or --visibility (this guard) is
152
+ # a deliberate choice, honored verbatim, so either flag silences this.
153
+ printf 'warning: no culture.yaml suffix resolved; querying the public default scope rather than a private personal scope. Pass --scope or --visibility to target deliberately.\n' >&2
154
+ fi
155
+ fi
156
+
157
+ # Default the embedding endpoint to the local model-gear embed gear. eidetic
158
+ # falls back to a deterministic offline embedding if it's unreachable, so this
159
+ # is safe even when the gear is down. Override by exporting these yourself.
160
+ : "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
161
+ : "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
162
+ export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
163
+
164
+ exec "${EIDETIC[@]}" recall "${SCOPE_ARGS[@]}" "$@"
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: remember
3
+ type: command
4
+ description: >
5
+ Ingest records into the shared eidetic memory store so they can be recalled
6
+ later. Drives `eidetic remember`: accepts one record as a JSON object, or a
7
+ batch as NDJSON on stdin for bulk ingest. Upsert is idempotent by id (and
8
+ dedups by content hash) — re-remembering updates in place, never duplicates.
9
+ Stamps a `created` date on every record at ingest time. Accepts `supersedes`
10
+ (id of the record this one replaces, for within-scope shadowing via `sweep`)
11
+ and `links` (list of related-memory ids). The store uses visibility-aware
12
+ routing: PUBLIC records inside a git repo go to <repo-root>/.eidetic/memory
13
+ (committed, team-shared); PRIVATE records, or any record outside a git repo,
14
+ go to $HOME/.eidetic/memory (never committed). An explicit EIDETIC_DATA_DIR
15
+ wins and short-circuits to that single dir. The wrapper defaults records to
16
+ this agent's PERSONAL, PRIVATE scope (`--scope rollout-cli --visibility
17
+ private`, suffix read from culture.yaml) so they don't leak to a
18
+ default/other-scope recall — Claude and the colleague backend still share them
19
+ because both resolve the same suffix via this skill. Pass `--visibility public`
20
+ to contribute to the shared public pool instead. Use when the user says
21
+ "remember this", "store this", "save to memory", "index these", "eidetic
22
+ remember", or when something learned this session should outlive it. Pairs with
23
+ the sibling /recall skill.
24
+ ---
25
+
26
+ # remember — write to the shared eidetic memory
27
+
28
+ `remember` drives **`eidetic remember`**, the write half of the memory surface
29
+ (the read half is the sibling **/recall** skill). Records you store here are
30
+ recallable later by *any* agent on this machine — Claude or the colleague
31
+ backend — because the default store is one shared `$HOME/.eidetic/memory` path.
32
+
33
+ ## How to run
34
+
35
+ ```bash
36
+ # One record (JSON object as the argument):
37
+ bash .claude/skills/remember/scripts/remember.sh \
38
+ '{"id":"d1","text":"Orin Nano draws 7-15W","type":"docs","metadata":{"source":"docs","permalink":"https://..."}}' --json
39
+
40
+ # Batch (NDJSON on stdin, one record per line) — for bulk re-index:
41
+ cat records.ndjson | bash .claude/skills/remember/scripts/remember.sh --json
42
+
43
+ # Record that supersedes an older one (same scope required for sweep to shadow):
44
+ bash .claude/skills/remember/scripts/remember.sh \
45
+ '{"id":"r2","text":"Updated Orin Nano draw: 10-20W","type":"note","supersedes":"r1","links":["r3"]}' --json
46
+ ```
47
+
48
+ The wrapper resolves the CLI portably (installed `eidetic` on `PATH`, else
49
+ `uv run eidetic` from the checkout) and forwards every flag verbatim.
50
+
51
+ ## Record shape
52
+
53
+ | Field | Required? | Notes |
54
+ |-------|-----------|-------|
55
+ | `id` | yes | stable identity; the upsert key |
56
+ | `text` | yes | the chunk being remembered |
57
+ | `type` | yes | e.g. `note`, `docs`, `discord`, a research object type |
58
+ | `hash` | optional | content hash for dedup; derived from `text` when omitted |
59
+ | `metadata` | recommended | provenance + facets; **round-trips verbatim** on recall |
60
+ | `created` | auto-stamped | ISO-8601 UTC date; stamped at ingest if absent; drives freshness signal age-decay |
61
+ | `supersedes` | optional | id of an earlier same-scope record this one replaces; `sweep` auto-shadows the target |
62
+ | `links` | optional | list of related-memory ids; persisted for future corroboration scoring |
63
+
64
+ `score` and `signal` are recall-only and are ignored on ingest. **Mind the
65
+ scope:** the default personal scope is **private** (`--scope rollout-cli
66
+ --visibility private`), so personal/role-gated notes stay isolated to this
67
+ agent's recall and are safe to store. Only when you deliberately write to a
68
+ **public** scope (`--visibility public`) does the record enter the shared pool
69
+ visible to every scope — keep public-scope records to public data only.
70
+
71
+ ## Idempotency
72
+
73
+ Re-submitting a record with the same `id` overwrites the previous value; a record
74
+ with a matching content `hash` is de-duplicated. So re-running an ingest (e.g. a
75
+ periodic re-scan) is safe and will not create duplicates.
76
+
77
+ ## Lifecycle — supersedes and sweep
78
+
79
+ Setting `supersedes` on a record declares that this record replaces an earlier one
80
+ **within the same scope**. The actual lifecycle transition (marking the older record
81
+ as `shadowed`) is applied by `eidetic sweep`, not by `remember` itself. Cross-scope
82
+ `supersedes` links are recorded but never auto-shadow (preserving the
83
+ public/private no-leak invariant).
84
+
85
+ To apply pending transitions after ingesting superseding records:
86
+
87
+ ```bash
88
+ eidetic sweep --dry-run # preview what would change
89
+ eidetic sweep # apply transitions
90
+ ```
91
+
92
+ ## Flags (forwarded to `eidetic remember`)
93
+
94
+ - `--json` — structured result (`{"upserted": N, "ids": [...]}`) to stdout.
95
+ - `--scope NAME` / `--visibility public|private` — record scope. **The wrapper
96
+ defaults this to the agent's PERSONAL, PRIVATE scope** — `--scope <suffix>
97
+ --visibility private`, where `<suffix>` is read from the nearest `culture.yaml`
98
+ (here, `rollout-cli`). Private records are served only to a recall in the same
99
+ scope, so they don't leak to a `default`/other-scope query. Pass `--scope` to
100
+ steer to a different scope (which then uses the plain CLI default visibility),
101
+ or `--visibility public` to keep the personal scope but make it shared. A wheel
102
+ install with no `culture.yaml` falls back to the CLI default `default`/`public`.
103
+ - `--backend files|mongo|neo4j` — default `files` (the shared home-dir store);
104
+ use `mongo`/`neo4j` (with `EIDETIC_MONGO_URI` / `NEO4J_URI`) for a server store.
105
+
106
+ ## Notes
107
+
108
+ - The embed endpoint defaults to the local model-gear embed gear
109
+ (`http://localhost:8002/v1`); override with `EIDETIC_EMBED_URL` /
110
+ `EIDETIC_EMBED_MODEL`. Ingest still works offline (embeddings are recomputed at
111
+ recall time).
112
+ - **Use the wrapper, not a bare `eidetic`.** The console script may not be on
113
+ `PATH` (in a dev checkout it isn't); the wrapper resolves it (`PATH` first, else
114
+ `uv run eidetic`). For the docs, run `eidetic explain remember` if installed,
115
+ otherwise `uv run --project <eidetic-cli checkout> eidetic explain remember`.
116
+
117
+ ## Provenance
118
+
119
+ First-party to **eidetic-cli** — eidetic owns its memory surface. Cite, don't
120
+ import: downstream repos copy this skill, they don't symlink it. See
121
+ [`docs/skill-sources.md`](../../../docs/skill-sources.md).
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # remember.sh — ingest records into the shared eidetic memory store (the /remember skill).
3
+ #
4
+ # Thin, portable wrapper around `eidetic remember`. It resolves the CLI, points
5
+ # the embedding endpoint at the local model-gear embed gear (overridable), and
6
+ # forwards every argument verbatim. Accepts ONE record as a JSON object argument,
7
+ # or a BATCH as NDJSON on stdin (one JSON object per line) for bulk ingest.
8
+ #
9
+ # remember.sh '{"id":"d1","text":"...","type":"docs","metadata":{...}}' --json
10
+ # cat records.ndjson | remember.sh --json
11
+ #
12
+ # Upsert is idempotent by id (and dedups by content hash): re-remembering the
13
+ # same record updates it in place, never duplicates.
14
+ #
15
+ # The store is the files backend. Default location resolves per-operation:
16
+ # PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
17
+ # team-shared); PRIVATE records, or any record outside a git repo →
18
+ # $HOME/.eidetic/memory (never committed). An explicit EIDETIC_DATA_DIR still
19
+ # wins and short-circuits to that single dir. Use --backend mongo|neo4j (with
20
+ # EIDETIC_MONGO_URI / NEO4J_URI) for a server-backed shared store.
21
+
22
+ set -euo pipefail
23
+
24
+ # ── resolve the eidetic CLI (installed tool first, then dev checkout) ────────
25
+ EIDETIC=()
26
+ resolve_eidetic() {
27
+ if command -v eidetic >/dev/null 2>&1; then
28
+ EIDETIC=(eidetic)
29
+ return 0
30
+ fi
31
+ local dir
32
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
33
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
34
+ if [ -f "$dir/pyproject.toml" ] \
35
+ && grep -q '^name = "eidetic-cli"' "$dir/pyproject.toml" 2>/dev/null; then
36
+ if command -v uv >/dev/null 2>&1; then
37
+ EIDETIC=(uv run --project "$dir" eidetic)
38
+ return 0
39
+ fi
40
+ break
41
+ fi
42
+ dir=$(dirname "$dir")
43
+ done
44
+ # In a vendored copy there is no eidetic-cli checkout to fall back to, so the
45
+ # only honest remedy is to install the CLI. One `error:` + one `hint:` line.
46
+ printf 'error: eidetic CLI not found.\n' >&2
47
+ printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
48
+ return 1
49
+ }
50
+
51
+ usage() {
52
+ cat <<'EOF'
53
+ remember.sh — ingest records into the shared eidetic memory store (the /remember skill).
54
+
55
+ Usage:
56
+ remember.sh '<json-object>' [--json] [--backend files|mongo|neo4j] \
57
+ [--scope NAME] [--visibility public|private]
58
+ cat records.ndjson | remember.sh [--json] ...
59
+
60
+ A record needs `id`, `text`, and `type`; `hash` and `metadata` are recommended
61
+ (hash is derived from text when omitted). Upsert is idempotent by id.
62
+ Records default to this agent's PUBLIC personal scope (--scope from the
63
+ culture.yaml suffix), committed in-repo at <repo>/.eidetic/memory; pass
64
+ --visibility private to keep a record in $HOME (uncommitted). Every flag is
65
+ forwarded verbatim to `eidetic remember`.
66
+ See `eidetic explain remember`.
67
+ EOF
68
+ }
69
+
70
+ case "${1:-}" in
71
+ -h | --help | help)
72
+ usage
73
+ exit 0
74
+ ;;
75
+ esac
76
+
77
+ # No record argument AND stdin is an interactive terminal → `eidetic remember`
78
+ # would block forever waiting for NDJSON. Show usage instead of hanging. A piped
79
+ # or redirected stdin (`cat records.ndjson | remember.sh`) is not a TTY and
80
+ # proceeds to the batch path normally.
81
+ if [ "$#" -eq 0 ] && [ -t 0 ]; then
82
+ usage >&2
83
+ printf 'hint: pass a JSON record as an argument, or pipe NDJSON on stdin.\n' >&2
84
+ exit 1
85
+ fi
86
+
87
+ resolve_eidetic || exit 2
88
+
89
+ # ── default to this agent's PERSONAL scope, PUBLIC visibility (culture.yaml `suffix`) ──
90
+ # A record this agent remembers should land in its OWN personal scope, not the
91
+ # global `default` scope shared by every project on this host. We read the
92
+ # `suffix` from the nearest culture.yaml (walking up from this script), so the
93
+ # scope follows the repo identity rather than being hard-coded — a downstream
94
+ # cite-don't-import copy adapts to its own suffix, and the colleague backend
95
+ # (running in a worktree of this same repo) resolves the same suffix, keeping
96
+ # the Claude↔colleague shared-memory story intact.
97
+ #
98
+ # Visibility defaults to PUBLIC (a recipe-level policy override of eidetic's
99
+ # upstream private default — see the POLICY OVERRIDE comment below), so a plain
100
+ # remember lands in the in-repo, committed, team- and mesh-shared pool at
101
+ # <repo>/.eidetic/memory. Scope and visibility are paired — the public default
102
+ # applies only when we inject the resolved scope, and only if the caller didn't
103
+ # pass --visibility (so an explicit `--visibility private` still wins, keeping
104
+ # the record in $HOME / uncommitted). An explicit --scope on the command line
105
+ # takes over steering entirely; a wheel install with no culture.yaml falls back
106
+ # to the plain CLI default (`default`/`public`).
107
+ resolve_scope() {
108
+ local dir suffix=""
109
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
110
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
111
+ if [ -f "$dir/culture.yaml" ]; then
112
+ # Capture only the first non-space token after `suffix:` (so an
113
+ # inline `# comment` or trailing space can't bleed into the scope),
114
+ # then strip surrounding quotes only — matching the canonical parser
115
+ # in .claude/skills/cicd/scripts/_resolve-nick.sh.
116
+ # `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
117
+ # early can SIGPIPE `sed`, making the substitution non-zero and
118
+ # aborting the script. An empty parse must yield "" here, not exit.
119
+ suffix=$(sed -n \
120
+ 's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
121
+ "$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
122
+ break
123
+ fi
124
+ dir=$(dirname "$dir")
125
+ done
126
+ printf '%s' "$suffix"
127
+ }
128
+
129
+ has_flag() {
130
+ local needle=$1
131
+ shift
132
+ local a
133
+ for a in "$@"; do
134
+ case "$a" in
135
+ "$needle" | "$needle"=*) return 0 ;;
136
+ esac
137
+ done
138
+ return 1
139
+ }
140
+
141
+ SCOPE_ARGS=()
142
+ if ! has_flag --scope "$@"; then
143
+ EIDETIC_SCOPE=$(resolve_scope)
144
+ if [ -n "$EIDETIC_SCOPE" ]; then
145
+ SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
146
+ # rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
147
+ # upstream private default): default to PUBLIC so a plain remember lands
148
+ # in <repo>/.eidetic/memory — committed, team- and mesh-shared. Pass
149
+ # --visibility private to keep a record in $HOME (uncommitted).
150
+ has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
151
+ elif ! has_flag --visibility "$@"; then
152
+ # No suffix AND no explicit --visibility: the record falls back to
153
+ # eidetic's own default (scope=default, visibility=public). Don't let an
154
+ # expected-private record go public silently — warn on stderr (stdout
155
+ # stays clean for --json). Warn ONLY here: an explicit --scope (outer
156
+ # guard) or --visibility (this guard) means the caller chose deliberately
157
+ # and is honored verbatim, so either flag silences this.
158
+ printf 'warning: no culture.yaml suffix resolved; this record falls back to the public default scope. Pass --scope or --visibility to place it deliberately.\n' >&2
159
+ fi
160
+ fi
161
+
162
+ : "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
163
+ : "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
164
+ export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
165
+
166
+ exec "${EIDETIC[@]}" remember "${SCOPE_ARGS[@]}" "$@"