@neikyun/ciel 6.10.0 → 6.11.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.
|
@@ -18,6 +18,7 @@ See docs/adrs/0001-cued-recall-memory.md for design rationale.
|
|
|
18
18
|
import sys
|
|
19
19
|
import os
|
|
20
20
|
import json
|
|
21
|
+
import math
|
|
21
22
|
import re
|
|
22
23
|
import fnmatch
|
|
23
24
|
import argparse
|
|
@@ -233,8 +234,16 @@ def match_path_pattern(pattern: str, paths) -> bool:
|
|
|
233
234
|
return False
|
|
234
235
|
|
|
235
236
|
|
|
236
|
-
def score_memory(mem, paths, symbols, intents, langs) -> int:
|
|
237
|
-
"""Score a memory's relevance. 0 = exclude. Positive = include, higher first.
|
|
237
|
+
def score_memory(mem, paths, symbols, intents, langs, prompt_lower="") -> int:
|
|
238
|
+
"""Score a memory's relevance. 0 = exclude. Positive = include, higher first.
|
|
239
|
+
|
|
240
|
+
Symbol and intent matching are case-insensitive and fall back to a
|
|
241
|
+
word-boundary search against the raw prompt. This lets a memory tagged
|
|
242
|
+
`symbols: [OkHttp]` fire on a prompt that mentions "okhttp" in prose,
|
|
243
|
+
and lets free-form intent tags (e.g. `intents: [okhttp, diagnostics]`)
|
|
244
|
+
match without being members of the fixed INTENT_KEYWORDS vocabulary.
|
|
245
|
+
Word boundaries prevent "test" intent from firing on "contest".
|
|
246
|
+
"""
|
|
238
247
|
# Hard language gate: if memory is language-specific AND prompt has language
|
|
239
248
|
# cues AND no overlap → exclude. Avoids Kotlin memories firing on TS edits.
|
|
240
249
|
mem_langs = mem.get('languages') or []
|
|
@@ -245,11 +254,23 @@ def score_memory(mem, paths, symbols, intents, langs) -> int:
|
|
|
245
254
|
for pattern in mem.get('path_patterns') or []:
|
|
246
255
|
if match_path_pattern(pattern, paths):
|
|
247
256
|
score += 10
|
|
257
|
+
|
|
258
|
+
# Case-insensitive comparison sets — built once per memory call.
|
|
259
|
+
symbols_lower = {s.lower() for s in symbols}
|
|
260
|
+
intents_lower = {i.lower() for i in intents}
|
|
261
|
+
|
|
248
262
|
for sym in mem.get('symbols') or []:
|
|
249
|
-
|
|
263
|
+
sym_lower = sym.lower()
|
|
264
|
+
if sym_lower in symbols_lower or (
|
|
265
|
+
prompt_lower and re.search(r'\b' + re.escape(sym_lower) + r'\b', prompt_lower)
|
|
266
|
+
):
|
|
250
267
|
score += 8
|
|
268
|
+
|
|
251
269
|
for intent in mem.get('intents') or []:
|
|
252
|
-
|
|
270
|
+
intent_lower = intent.lower()
|
|
271
|
+
if intent_lower in intents_lower or (
|
|
272
|
+
prompt_lower and re.search(r'\b' + re.escape(intent_lower) + r'\b', prompt_lower)
|
|
273
|
+
):
|
|
253
274
|
score += 5
|
|
254
275
|
|
|
255
276
|
# No cue match at all → don't include (cued recall, not free recall)
|
|
@@ -399,6 +420,7 @@ def cmd_query(args):
|
|
|
399
420
|
symbols = extract_symbol_cues(prompt)
|
|
400
421
|
intents = extract_intent_cues(prompt)
|
|
401
422
|
langs = extract_language_cues(prompt)
|
|
423
|
+
prompt_lower = prompt.lower()
|
|
402
424
|
now = datetime.now(timezone.utc)
|
|
403
425
|
iso_now = now.isoformat().replace('+00:00', 'Z')
|
|
404
426
|
|
|
@@ -418,7 +440,7 @@ def cmd_query(args):
|
|
|
418
440
|
for mid, m in mems.items():
|
|
419
441
|
if m.get('stale'):
|
|
420
442
|
continue
|
|
421
|
-
s = score_memory(m, paths, symbols, intents, langs)
|
|
443
|
+
s = score_memory(m, paths, symbols, intents, langs, prompt_lower=prompt_lower)
|
|
422
444
|
if s > 0:
|
|
423
445
|
scored.append((s, mid, m))
|
|
424
446
|
|
|
@@ -674,6 +696,232 @@ def cmd_capture(args):
|
|
|
674
696
|
print(f"Index rebuilt with memory: {mid}")
|
|
675
697
|
|
|
676
698
|
|
|
699
|
+
def cmd_analyze(args):
|
|
700
|
+
"""Mine recurring patterns across the corpus and emit insights.
|
|
701
|
+
|
|
702
|
+
Read-only on memories. Computes promotion candidates, dead anchors,
|
|
703
|
+
intent/path clusters and 7 health metrics, then writes:
|
|
704
|
+
- .ciel/memory/insights.json (machine; consumed by ciel-audit Dim 10)
|
|
705
|
+
- .ciel/memory/INSIGHTS.md (human digest)
|
|
706
|
+
|
|
707
|
+
Min-support floor (3 memories) enforced by the engine — refuses to
|
|
708
|
+
surface a "cluster" the model could narrate from sparse evidence.
|
|
709
|
+
Generation cap is computed from optional `derived_from` chains so a
|
|
710
|
+
future synthesizer cannot recurse on its own outputs without it
|
|
711
|
+
showing up as a metric.
|
|
712
|
+
"""
|
|
713
|
+
cwd = resolve_cwd(args.cwd)
|
|
714
|
+
base = cwd / '.ciel' / 'memory'
|
|
715
|
+
if not base.exists():
|
|
716
|
+
print(f"No memory directory at {base}", file=sys.stderr)
|
|
717
|
+
sys.exit(1)
|
|
718
|
+
|
|
719
|
+
index_file = base / 'index.json'
|
|
720
|
+
if index_file.exists():
|
|
721
|
+
with index_file.open('r', encoding='utf-8') as f:
|
|
722
|
+
index = json.load(f)
|
|
723
|
+
else:
|
|
724
|
+
index = {"version": 2, "memories": {}, "by_path": {}, "by_symbol": {},
|
|
725
|
+
"by_intent": {}, "by_language": {}}
|
|
726
|
+
|
|
727
|
+
memories = index.get('memories', {}) or {}
|
|
728
|
+
by_intent = index.get('by_intent', {}) or {}
|
|
729
|
+
by_path = index.get('by_path', {}) or {}
|
|
730
|
+
|
|
731
|
+
MIN_PROMOTION = 5
|
|
732
|
+
MIN_SUPPORT = 3
|
|
733
|
+
|
|
734
|
+
episodes = {mid: m for mid, m in memories.items()
|
|
735
|
+
if str(m.get('file', '')).startswith('episodes/')}
|
|
736
|
+
concepts = {mid: m for mid, m in memories.items()
|
|
737
|
+
if str(m.get('file', '')).startswith('concepts/')}
|
|
738
|
+
guards = {mid: m for mid, m in memories.items()
|
|
739
|
+
if str(m.get('file', '')).startswith('guards/')}
|
|
740
|
+
|
|
741
|
+
promotion_candidates = [mid for mid, m in episodes.items()
|
|
742
|
+
if (m.get('trigger_count') or 0) >= MIN_PROMOTION]
|
|
743
|
+
promotion_candidates.sort(key=lambda mid: -(episodes[mid].get('trigger_count') or 0))
|
|
744
|
+
|
|
745
|
+
dead_anchors = []
|
|
746
|
+
for mid, m in memories.items():
|
|
747
|
+
patterns = m.get('path_patterns') or []
|
|
748
|
+
if not patterns:
|
|
749
|
+
continue
|
|
750
|
+
alive = False
|
|
751
|
+
for pat in patterns:
|
|
752
|
+
try:
|
|
753
|
+
# Path.glob raises NotImplementedError on absolute patterns
|
|
754
|
+
# in Python 3.13+, so route absolute paths through Path.exists
|
|
755
|
+
# directly. Relative patterns keep the glob (** support).
|
|
756
|
+
if pat.startswith('/'):
|
|
757
|
+
if Path(pat).exists():
|
|
758
|
+
alive = True
|
|
759
|
+
break
|
|
760
|
+
elif any(True for _ in cwd.glob(pat)):
|
|
761
|
+
alive = True
|
|
762
|
+
break
|
|
763
|
+
except (ValueError, OSError, NotImplementedError):
|
|
764
|
+
continue
|
|
765
|
+
if not alive:
|
|
766
|
+
dead_anchors.append(mid)
|
|
767
|
+
dead_anchors.sort()
|
|
768
|
+
|
|
769
|
+
intent_clusters = {k: sorted(v) for k, v in by_intent.items() if len(v) >= MIN_SUPPORT}
|
|
770
|
+
path_clusters = {k: sorted(v) for k, v in by_path.items() if len(v) >= MIN_SUPPORT}
|
|
771
|
+
|
|
772
|
+
now = datetime.now(timezone.utc)
|
|
773
|
+
|
|
774
|
+
def days_ago(iso_str):
|
|
775
|
+
if not iso_str:
|
|
776
|
+
return None
|
|
777
|
+
try:
|
|
778
|
+
dt = datetime.fromisoformat(str(iso_str).replace('Z', '+00:00'))
|
|
779
|
+
return (now - dt).days
|
|
780
|
+
except (ValueError, TypeError):
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
total = len(memories)
|
|
784
|
+
|
|
785
|
+
recent = sum(1 for m in memories.values()
|
|
786
|
+
if (days_ago(m.get('captured_at')) or 10**6) <= 30)
|
|
787
|
+
recency_30d_ratio = round(recent / total, 3) if total else 0.0
|
|
788
|
+
|
|
789
|
+
intent_counts = [len(ids) for ids in by_intent.values() if ids]
|
|
790
|
+
intent_diversity_entropy = 0.0
|
|
791
|
+
if intent_counts:
|
|
792
|
+
total_tags = sum(intent_counts)
|
|
793
|
+
for c in intent_counts:
|
|
794
|
+
p = c / total_tags
|
|
795
|
+
intent_diversity_entropy -= p * math.log2(p)
|
|
796
|
+
intent_diversity_entropy = round(intent_diversity_entropy, 3)
|
|
797
|
+
|
|
798
|
+
dead_anchor_ratio = round(len(dead_anchors) / total, 3) if total else 0.0
|
|
799
|
+
|
|
800
|
+
# Generation depth from optional `derived_from` chains. Cycle-guarded.
|
|
801
|
+
def gen_depth(mid, seen):
|
|
802
|
+
if mid in seen:
|
|
803
|
+
return 0
|
|
804
|
+
m = memories.get(mid, {})
|
|
805
|
+
parents = m.get('derived_from') or []
|
|
806
|
+
if not parents:
|
|
807
|
+
return 1
|
|
808
|
+
return 1 + max(gen_depth(p, seen | {mid}) for p in parents)
|
|
809
|
+
|
|
810
|
+
max_generation_depth = max((gen_depth(mid, set()) for mid in memories), default=0)
|
|
811
|
+
|
|
812
|
+
flat_intents = [i for m in memories.values() for i in (m.get('intents') or [])]
|
|
813
|
+
tag_specificity = round(len(set(flat_intents)) / len(flat_intents), 3) if flat_intents else 0.0
|
|
814
|
+
|
|
815
|
+
promotion_ratio = round(len(concepts) / len(episodes), 3) if episodes else 0.0
|
|
816
|
+
|
|
817
|
+
corrections = sum(1 for m in episodes.values()
|
|
818
|
+
if m.get('captured_from') in ('user-intervention', 'intervention'))
|
|
819
|
+
capture_correction_ratio = round(corrections / len(episodes), 3) if episodes else 0.0
|
|
820
|
+
|
|
821
|
+
health = {
|
|
822
|
+
"recency_30d_ratio": recency_30d_ratio,
|
|
823
|
+
"intent_diversity_entropy": intent_diversity_entropy,
|
|
824
|
+
"dead_anchor_ratio": dead_anchor_ratio,
|
|
825
|
+
"max_generation_depth": max_generation_depth,
|
|
826
|
+
"tag_specificity": tag_specificity,
|
|
827
|
+
"promotion_ratio": promotion_ratio,
|
|
828
|
+
"capture_correction_ratio": capture_correction_ratio,
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
insights = {
|
|
832
|
+
"version": 1,
|
|
833
|
+
"generated_at": now.isoformat().replace('+00:00', 'Z'),
|
|
834
|
+
"corpus_size": {
|
|
835
|
+
"episodes": len(episodes),
|
|
836
|
+
"concepts": len(concepts),
|
|
837
|
+
"guards": len(guards),
|
|
838
|
+
"total": total,
|
|
839
|
+
},
|
|
840
|
+
"promotion_candidates": promotion_candidates,
|
|
841
|
+
"dead_anchors": dead_anchors,
|
|
842
|
+
"intent_clusters": intent_clusters,
|
|
843
|
+
"path_clusters": path_clusters,
|
|
844
|
+
"health": health,
|
|
845
|
+
"thresholds": {
|
|
846
|
+
"min_promotion_trigger_count": MIN_PROMOTION,
|
|
847
|
+
"min_support_episodes": MIN_SUPPORT,
|
|
848
|
+
},
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
insights_json = base / 'insights.json'
|
|
852
|
+
atomic_write_json(insights_json, insights)
|
|
853
|
+
|
|
854
|
+
lines = [
|
|
855
|
+
"# Memory insights",
|
|
856
|
+
"",
|
|
857
|
+
f"_Generated {insights['generated_at']} by `memory-engine.py analyze`. Read by `ciel-audit` Dim 10._",
|
|
858
|
+
"",
|
|
859
|
+
f"**Corpus**: {len(episodes)} episodes, {len(concepts)} concepts, {len(guards)} guards (total {total}).",
|
|
860
|
+
"",
|
|
861
|
+
"## Health metrics",
|
|
862
|
+
"",
|
|
863
|
+
]
|
|
864
|
+
for key, val in health.items():
|
|
865
|
+
lines.append(f"- `{key}`: **{val}**")
|
|
866
|
+
lines.append("")
|
|
867
|
+
|
|
868
|
+
if promotion_candidates:
|
|
869
|
+
lines += [
|
|
870
|
+
"## Promotion candidates",
|
|
871
|
+
"",
|
|
872
|
+
f"Episodes triggered >= {MIN_PROMOTION} times. Promote via skill `memoire-consolidator`.",
|
|
873
|
+
"",
|
|
874
|
+
]
|
|
875
|
+
for mid in promotion_candidates:
|
|
876
|
+
m = episodes[mid]
|
|
877
|
+
lines.append(f"- `{mid}` (trigger_count={m.get('trigger_count', 0)}) - {m.get('title', '?')}")
|
|
878
|
+
lines.append("")
|
|
879
|
+
|
|
880
|
+
if dead_anchors:
|
|
881
|
+
lines += [
|
|
882
|
+
"## Dead anchors",
|
|
883
|
+
"",
|
|
884
|
+
"Memories whose every `path_patterns` entry resolves to no file. Triage in `.ciel/memory/review-queue.md`.",
|
|
885
|
+
"",
|
|
886
|
+
]
|
|
887
|
+
for mid in dead_anchors:
|
|
888
|
+
m = memories[mid]
|
|
889
|
+
patterns = ", ".join(m.get('path_patterns') or [])
|
|
890
|
+
lines.append(f"- `{mid}` - {m.get('title', '?')} (patterns: {patterns})")
|
|
891
|
+
lines.append("")
|
|
892
|
+
|
|
893
|
+
if intent_clusters:
|
|
894
|
+
lines += [
|
|
895
|
+
"## Intent clusters",
|
|
896
|
+
"",
|
|
897
|
+
f"Intents shared by >= {MIN_SUPPORT} memories - recurring topics.",
|
|
898
|
+
"",
|
|
899
|
+
]
|
|
900
|
+
for intent, ids in sorted(intent_clusters.items(), key=lambda x: -len(x[1])):
|
|
901
|
+
lines.append(f"- `{intent}` ({len(ids)}): {', '.join(ids)}")
|
|
902
|
+
lines.append("")
|
|
903
|
+
|
|
904
|
+
if path_clusters:
|
|
905
|
+
lines += [
|
|
906
|
+
"## Path clusters",
|
|
907
|
+
"",
|
|
908
|
+
f"Paths referenced by >= {MIN_SUPPORT} memories - high-traffic surface.",
|
|
909
|
+
"",
|
|
910
|
+
]
|
|
911
|
+
for path, ids in sorted(path_clusters.items(), key=lambda x: -len(x[1])):
|
|
912
|
+
lines.append(f"- `{path}` ({len(ids)}): {', '.join(ids)}")
|
|
913
|
+
lines.append("")
|
|
914
|
+
|
|
915
|
+
insights_md = base / 'INSIGHTS.md'
|
|
916
|
+
insights_md.write_text('\n'.join(lines), encoding='utf-8')
|
|
917
|
+
|
|
918
|
+
print(f"Insights written: {insights_json.relative_to(cwd)}, {insights_md.relative_to(cwd)}")
|
|
919
|
+
print(f" promotion_candidates: {len(promotion_candidates)}")
|
|
920
|
+
print(f" dead_anchors: {len(dead_anchors)}")
|
|
921
|
+
print(f" intent_clusters: {len(intent_clusters)}")
|
|
922
|
+
print(f" path_clusters: {len(path_clusters)}")
|
|
923
|
+
|
|
924
|
+
|
|
677
925
|
# ─── CLI ────────────────────────────────────────────────────────────────────
|
|
678
926
|
|
|
679
927
|
|
|
@@ -710,6 +958,10 @@ def main():
|
|
|
710
958
|
cp.add_argument('--cwd', default=None)
|
|
711
959
|
cp.set_defaults(func=cmd_capture)
|
|
712
960
|
|
|
961
|
+
ap = sub.add_parser('analyze', help='Mine patterns + emit insights.json + INSIGHTS.md (read by ciel-audit Dim 10)')
|
|
962
|
+
ap.add_argument('--cwd', default=None)
|
|
963
|
+
ap.set_defaults(func=cmd_analyze)
|
|
964
|
+
|
|
713
965
|
args = p.parse_args()
|
|
714
966
|
args.func(args)
|
|
715
967
|
|
|
@@ -182,6 +182,47 @@ else
|
|
|
182
182
|
fi
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
+
#### Dimension 10: Memory insight quality — penalty up to -10
|
|
186
|
+
|
|
187
|
+
Auto-runs the memory pattern analyzer (`python3 .claude/hooks/memory-engine.py analyze`) before scoring. The analyzer is read-only on the corpus — it scans `index.json`, computes pattern clusters and 7 health metrics, and writes `.ciel/memory/insights.json` + `.ciel/memory/INSIGHTS.md`. This dimension scores the *output* of that analysis: untreated promotion candidates, dead anchors, and structural drift in the memory corpus.
|
|
188
|
+
|
|
189
|
+
**Anti-double-counting with Dim 9.** Memories already counted as `stale` in Dim 9 must be excluded from the Dim 10 dead-anchor penalty: compute `dead_anchors_new = insights.dead_anchors - dim9_stale_ids` before scoring. A single rotted memory that is both stale (Dim 9) and a dead anchor (Dim 10) is one defect, not two — never charge -4 for what costs the user one consolidator pass.
|
|
190
|
+
|
|
191
|
+
- **Engine failed to produce insights**: `python3 .claude/hooks/memory-engine.py analyze` exited non-zero, OR `.ciel/memory/insights.json` was not written. The pattern surface is invisible — Dim 10 cannot grade. **-2**
|
|
192
|
+
- **Promotion candidates ignored**: `insights.json.promotion_candidates.length >= 3` AND `insights.json.health.promotion_ratio == 0` — the analyzer flagged hot episodes (>= 5 triggers) but the consolidator skill has never crystallized any of them into concepts. The corpus accumulates without distillation. **-3**
|
|
193
|
+
- **Dead anchors not triaged**: `insights.json.dead_anchors.length > 0` AND `.ciel/memory/review-queue.md` is missing or empty. Memories point to files that no longer exist; cued recall keeps firing on broken anchors until the user reviews. **-2**
|
|
194
|
+
- **Recursion drift starting**: `insights.json.health.max_generation_depth >= 3`. Synthesizer outputs are being re-derived from prior synthesizer outputs beyond depth 2, violating ADR-0001's "no self-feeding loops" principle. **-2**
|
|
195
|
+
- **Tag explosion**: `insights.json.health.tag_specificity > 0.9` AND total memories >= 10. Almost every tag is bespoke — clustering is impossible, recall degrades to per-memory matching. **-1**
|
|
196
|
+
|
|
197
|
+
Run these checks:
|
|
198
|
+
```bash
|
|
199
|
+
# Auto-run analyzer (read-only on memories; writes only insights artifacts)
|
|
200
|
+
python3 .claude/hooks/memory-engine.py analyze 2>&1 || echo "analyze: FAILED"
|
|
201
|
+
|
|
202
|
+
# Read insights.json and emit per-check diagnostics
|
|
203
|
+
python3 -c "
|
|
204
|
+
import json, os, sys
|
|
205
|
+
try:
|
|
206
|
+
with open('.ciel/memory/insights.json') as f:
|
|
207
|
+
ins = json.load(f)
|
|
208
|
+
except FileNotFoundError:
|
|
209
|
+
print('insights: MISSING (engine failed?)')
|
|
210
|
+
sys.exit(0)
|
|
211
|
+
pc = ins.get('promotion_candidates', [])
|
|
212
|
+
da = ins.get('dead_anchors', [])
|
|
213
|
+
h = ins.get('health', {})
|
|
214
|
+
print(f'promotion_candidates: {len(pc)} (promotion_ratio={h.get(\"promotion_ratio\", 0)})')
|
|
215
|
+
print(f'dead_anchors: {len(da)}')
|
|
216
|
+
print(f'max_generation_depth: {h.get(\"max_generation_depth\", 0)}')
|
|
217
|
+
print(f'tag_specificity: {h.get(\"tag_specificity\", 0)}')
|
|
218
|
+
print(f'corpus: {ins.get(\"corpus_size\", {})}')
|
|
219
|
+
review = '.ciel/memory/review-queue.md'
|
|
220
|
+
print(f'review-queue: {\"present\" if os.path.exists(review) else \"absent\"}')
|
|
221
|
+
" 2>/dev/null || echo "memory insights check failed (no python3?)"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The analyzer is **idempotent**: every audit run regenerates `insights.json` from the live corpus, so this dimension cannot be gamed by stale artifacts. Patches the audit recommends in this dimension should target either (a) running the consolidator skill to drain the promotion queue, or (b) populating `.ciel/memory/review-queue.md` to clear dead anchors.
|
|
225
|
+
|
|
185
226
|
---
|
|
186
227
|
|
|
187
228
|
### Scoring
|
|
@@ -257,6 +298,7 @@ Begin the output with the literal line `# Ciel Session Audit Report`. End with t
|
|
|
257
298
|
| D7 — npm version | -<N> |
|
|
258
299
|
| D8 — Platform health | -<N> |
|
|
259
300
|
| D9 — Memory health | -<N> |
|
|
301
|
+
| D10 — Memory insight quality | -<N> |
|
|
260
302
|
| **Total** | **-<N>** |
|
|
261
303
|
| **Health Score** | **<N>/100** |
|
|
262
304
|
|
|
@@ -44,7 +44,7 @@ Principe : **"Understand before generating. Verify before claiming done."**
|
|
|
44
44
|
| 12 | ADR | Documenter decisions architecturales |
|
|
45
45
|
| 13 | RELIRE | @ciel-critic MODE=RELIRE |
|
|
46
46
|
| 14 | PROUVER | Preuve AVANT/APRES |
|
|
47
|
-
| 15 | MEMOIRE |
|
|
47
|
+
| 15 | MEMOIRE | Cued-recall: hooks capture interventions to .ciel/memory/episodes/ + index.json (see skill `memoire`, ADR-0001). |
|
|
48
48
|
| 16 | META | 30s reflexion |
|
|
49
49
|
|
|
50
50
|
## Depth et dispatch
|
|
@@ -98,5 +98,6 @@ Pour les prompts non-anglais (francais, etc.), appliquer le matching semantique
|
|
|
98
98
|
## References
|
|
99
99
|
|
|
100
100
|
- **Depth signals** → load `depth-classifier`
|
|
101
|
+
- **Memory (etape 15 MEMOIRE)** → load `memoire` (capture/recall) + `memoire-consolidator` (maintenance). Hooks `user-prompt-submit.sh` (capture trigger) + `session-start.sh` (recall injection). Engine `hooks/memory-engine.py`. Token budget by depth: Trivial 1K / Standard 3K / Critical 5K injected memory tokens. Design: `docs/adrs/0001-cued-recall-memory.md`.
|
|
101
102
|
- **Utility skills** → load pr-opener, commit-writer, branch-setup, issue-creator, issue-closer
|
|
102
103
|
- For full philosophy and guards, see `reference.md`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neikyun/ciel",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.0",
|
|
4
4
|
"description": "Ciel — Deep-reasoning pipeline for LLM-assisted development. OpenCode plugin + multi-platform CLI (OpenCode, Claude Code, more).",
|
|
5
5
|
"main": "./dist/plugin/index.js",
|
|
6
6
|
"types": "./dist/plugin/index.d.ts",
|