@neikyun/ciel 6.10.1 → 6.11.1
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.
- package/assets/.claude/hooks/memory-engine.py +256 -0
- package/assets/commands/ciel-audit.md +42 -0
- package/assets/commands/ciel-create-skill.md +2 -2
- package/assets/commands/ciel-status.md +1 -1
- package/assets/platforms/opencode/.opencode/agents/ciel-improver.md +2 -2
- package/assets/platforms/opencode/.opencode/commands/ciel-create-skill.md +2 -2
- package/assets/platforms/opencode/.opencode/commands/ciel-memory-bootstrap.md +195 -0
- package/assets/skills/ciel/SKILL.md +2 -1
- package/assets/skills/workflow/adr-auto/SKILL.md +88 -0
- package/assets/skills/workflow/ai-failure-modes-detector/SKILL.md +180 -0
- package/assets/skills/workflow/ask-window/SKILL.md +119 -0
- package/assets/skills/workflow/avec-quoi-versioner/SKILL.md +111 -0
- package/assets/skills/workflow/ci-watcher/SKILL.md +194 -0
- package/assets/skills/workflow/critiquer-auditor/SKILL.md +135 -0
- package/assets/skills/workflow/critiquer-auditor/reference.md +134 -0
- package/assets/skills/workflow/debug-reasoning-rca/SKILL.md +174 -0
- package/assets/skills/workflow/depth-classifier/SKILL.md +118 -0
- package/assets/skills/workflow/diverge/SKILL.md +91 -0
- package/assets/skills/workflow/doc-validator-official/SKILL.md +196 -0
- package/assets/skills/workflow/evaluer-sizer/SKILL.md +112 -0
- package/assets/skills/workflow/faire-gatekeeper/SKILL.md +99 -0
- package/assets/skills/workflow/flux-narrator/SKILL.md +93 -0
- package/assets/skills/workflow/memoire/SKILL.md +198 -0
- package/assets/skills/workflow/memoire-consolidator/SKILL.md +91 -0
- package/assets/skills/workflow/meta-critiquer/SKILL.md +112 -0
- package/assets/skills/workflow/modern-patterns-checker/SKILL.md +166 -0
- package/assets/skills/workflow/pattern-fitness-check/SKILL.md +108 -0
- package/assets/skills/workflow/playwright-visual-critic/SKILL.md +98 -0
- package/assets/skills/workflow/pr-review-responder/SKILL.md +214 -0
- package/assets/skills/workflow/prouver-verifier/SKILL.md +184 -0
- package/assets/skills/workflow/prouver-verifier/reference.md +152 -0
- package/assets/skills/workflow/quoi-framer/SKILL.md +91 -0
- package/assets/skills/workflow/relire-critic/SKILL.md +99 -0
- package/assets/skills/workflow/security-regression-check/SKILL.md +86 -0
- package/assets/skills/workflow/self-consistency-verifier/SKILL.md +85 -0
- package/assets/skills/workflow/spike-mode/SKILL.md +101 -0
- package/assets/skills/workflow/stride-analyzer/SKILL.md +96 -0
- package/assets/skills/workflow/stride-analyzer/reference.md +144 -0
- package/assets/skills/workflow/test-strategy-vitest-playwright/SKILL.md +119 -0
- package/package.json +1 -1
|
@@ -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
|
|
@@ -695,6 +696,257 @@ def cmd_capture(args):
|
|
|
695
696
|
print(f"Index rebuilt with memory: {mid}")
|
|
696
697
|
|
|
697
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
|
+
# Cap human-readable INSIGHTS.md sections when the corpus is large.
|
|
855
|
+
# insights.json (machine consumer) keeps everything; INSIGHTS.md is read
|
|
856
|
+
# by humans + by ciel-audit narration so token cost matters at scale.
|
|
857
|
+
LARGE_CORPUS_THRESHOLD = 150
|
|
858
|
+
TOP_N = 10
|
|
859
|
+
|
|
860
|
+
def maybe_cap(items):
|
|
861
|
+
if total > LARGE_CORPUS_THRESHOLD and len(items) > TOP_N:
|
|
862
|
+
return items[:TOP_N], len(items) - TOP_N
|
|
863
|
+
return items, 0
|
|
864
|
+
|
|
865
|
+
lines = [
|
|
866
|
+
"# Memory insights",
|
|
867
|
+
"",
|
|
868
|
+
f"_Generated {insights['generated_at']} by `memory-engine.py analyze`. Read by `ciel-audit` Dim 10._",
|
|
869
|
+
"",
|
|
870
|
+
f"**Corpus**: {len(episodes)} episodes, {len(concepts)} concepts, {len(guards)} guards (total {total}).",
|
|
871
|
+
"",
|
|
872
|
+
"## Health metrics",
|
|
873
|
+
"",
|
|
874
|
+
]
|
|
875
|
+
for key, val in health.items():
|
|
876
|
+
lines.append(f"- `{key}`: **{val}**")
|
|
877
|
+
lines.append("")
|
|
878
|
+
|
|
879
|
+
if promotion_candidates:
|
|
880
|
+
shown, omitted = maybe_cap(promotion_candidates)
|
|
881
|
+
lines += [
|
|
882
|
+
"## Promotion candidates",
|
|
883
|
+
"",
|
|
884
|
+
f"Episodes triggered >= {MIN_PROMOTION} times. Promote via skill `memoire-consolidator`.",
|
|
885
|
+
"",
|
|
886
|
+
]
|
|
887
|
+
for mid in shown:
|
|
888
|
+
m = episodes[mid]
|
|
889
|
+
lines.append(f"- `{mid}` (trigger_count={m.get('trigger_count', 0)}) - {m.get('title', '?')}")
|
|
890
|
+
if omitted:
|
|
891
|
+
lines.append(f"- _+{omitted} more, see insights.json_")
|
|
892
|
+
lines.append("")
|
|
893
|
+
|
|
894
|
+
if dead_anchors:
|
|
895
|
+
shown, omitted = maybe_cap(dead_anchors)
|
|
896
|
+
lines += [
|
|
897
|
+
"## Dead anchors",
|
|
898
|
+
"",
|
|
899
|
+
"Memories whose every `path_patterns` entry resolves to no file. Triage in `.ciel/memory/review-queue.md`.",
|
|
900
|
+
"",
|
|
901
|
+
]
|
|
902
|
+
for mid in shown:
|
|
903
|
+
m = memories[mid]
|
|
904
|
+
patterns = ", ".join(m.get('path_patterns') or [])
|
|
905
|
+
lines.append(f"- `{mid}` - {m.get('title', '?')} (patterns: {patterns})")
|
|
906
|
+
if omitted:
|
|
907
|
+
lines.append(f"- _+{omitted} more, see insights.json_")
|
|
908
|
+
lines.append("")
|
|
909
|
+
|
|
910
|
+
if intent_clusters:
|
|
911
|
+
ranked = sorted(intent_clusters.items(), key=lambda x: -len(x[1]))
|
|
912
|
+
shown, omitted = maybe_cap(ranked)
|
|
913
|
+
lines += [
|
|
914
|
+
"## Intent clusters",
|
|
915
|
+
"",
|
|
916
|
+
f"Intents shared by >= {MIN_SUPPORT} memories - recurring topics.",
|
|
917
|
+
"",
|
|
918
|
+
]
|
|
919
|
+
for intent, ids in shown:
|
|
920
|
+
lines.append(f"- `{intent}` ({len(ids)}): {', '.join(ids)}")
|
|
921
|
+
if omitted:
|
|
922
|
+
lines.append(f"- _+{omitted} more, see insights.json_")
|
|
923
|
+
lines.append("")
|
|
924
|
+
|
|
925
|
+
if path_clusters:
|
|
926
|
+
ranked = sorted(path_clusters.items(), key=lambda x: -len(x[1]))
|
|
927
|
+
shown, omitted = maybe_cap(ranked)
|
|
928
|
+
lines += [
|
|
929
|
+
"## Path clusters",
|
|
930
|
+
"",
|
|
931
|
+
f"Paths referenced by >= {MIN_SUPPORT} memories - high-traffic surface.",
|
|
932
|
+
"",
|
|
933
|
+
]
|
|
934
|
+
for path, ids in shown:
|
|
935
|
+
lines.append(f"- `{path}` ({len(ids)}): {', '.join(ids)}")
|
|
936
|
+
if omitted:
|
|
937
|
+
lines.append(f"- _+{omitted} more, see insights.json_")
|
|
938
|
+
lines.append("")
|
|
939
|
+
|
|
940
|
+
insights_md = base / 'INSIGHTS.md'
|
|
941
|
+
insights_md.write_text('\n'.join(lines), encoding='utf-8')
|
|
942
|
+
|
|
943
|
+
print(f"Insights written: {insights_json.relative_to(cwd)}, {insights_md.relative_to(cwd)}")
|
|
944
|
+
print(f" promotion_candidates: {len(promotion_candidates)}")
|
|
945
|
+
print(f" dead_anchors: {len(dead_anchors)}")
|
|
946
|
+
print(f" intent_clusters: {len(intent_clusters)}")
|
|
947
|
+
print(f" path_clusters: {len(path_clusters)}")
|
|
948
|
+
|
|
949
|
+
|
|
698
950
|
# ─── CLI ────────────────────────────────────────────────────────────────────
|
|
699
951
|
|
|
700
952
|
|
|
@@ -731,6 +983,10 @@ def main():
|
|
|
731
983
|
cp.add_argument('--cwd', default=None)
|
|
732
984
|
cp.set_defaults(func=cmd_capture)
|
|
733
985
|
|
|
986
|
+
ap = sub.add_parser('analyze', help='Mine patterns + emit insights.json + INSIGHTS.md (read by ciel-audit Dim 10)')
|
|
987
|
+
ap.add_argument('--cwd', default=None)
|
|
988
|
+
ap.set_defaults(func=cmd_analyze)
|
|
989
|
+
|
|
734
990
|
args = p.parse_args()
|
|
735
991
|
args.func(args)
|
|
736
992
|
|
|
@@ -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
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Generates a valid Ciel SKILL.md scaffold following Anthropic Skills-first rules (kebab-case ≤64, YAML description ≤
|
|
2
|
+
description: Generates a valid Ciel SKILL.md scaffold following Anthropic Skills-first rules (kebab-case ≤64, YAML description ≤1536, body ≤500 lines).
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# /ciel-create-skill — Create a new Ciel skill
|
|
6
6
|
|
|
7
|
-
*Generates a valid SKILL.md scaffold following Anthropic Skills-first rules (kebab-case name ≤64 chars, YAML frontmatter ≤
|
|
7
|
+
*Generates a valid SKILL.md scaffold following Anthropic Skills-first rules (kebab-case name ≤64 chars, YAML frontmatter ≤1536-char description, ≤500-line body, progressive disclosure to one reference.md).*
|
|
8
8
|
|
|
9
9
|
Usage: `/ciel-create-skill <name> <purpose>`
|
|
10
10
|
|
|
@@ -265,7 +265,7 @@ Generates a valid SKILL.md scaffold following Ciel's conventions. Returns a diff
|
|
|
265
265
|
- Third person: "Analyzes X" ✓ / "I analyze X" ✗
|
|
266
266
|
- Front-load use case + trigger keywords
|
|
267
267
|
- Include "Use when..." clause
|
|
268
|
-
- ≤
|
|
268
|
+
- ≤ 1536 chars, recommended 200-500
|
|
269
269
|
|
|
270
270
|
### 3. Scaffold SKILL.md
|
|
271
271
|
|
|
@@ -333,7 +333,7 @@ Problems: no trigger, no output, no specificity.
|
|
|
333
333
|
|
|
334
334
|
- [ ] Name valid kebab-case, ≤ 64 chars, unique?
|
|
335
335
|
- [ ] Category is one of the 5 valid categories?
|
|
336
|
-
- [ ] Description: third person, ≤
|
|
336
|
+
- [ ] Description: third person, ≤ 1536 chars, includes trigger?
|
|
337
337
|
- [ ] SKILL.md ≤ 300 lines?
|
|
338
338
|
- [ ] No overlap with existing skills (grep checked)?
|
|
339
339
|
- [ ] YAML frontmatter valid?
|
|
@@ -7,12 +7,12 @@ subtask: true
|
|
|
7
7
|
> **OpenCode note**: This command requires `claude --print` headless mode for full functionality (binary evals, skill scaffold generation). On OpenCode it runs in degraded mode — the improver agent returns proposals only. For the full harness, use Claude Code.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
|
-
description: Generates a valid Ciel SKILL.md scaffold following Anthropic Skills-first rules (kebab-case ≤64, YAML description ≤
|
|
10
|
+
description: Generates a valid Ciel SKILL.md scaffold following Anthropic Skills-first rules (kebab-case ≤64, YAML description ≤1536, body ≤500 lines).
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# /ciel-create-skill — Create a new Ciel skill
|
|
14
14
|
|
|
15
|
-
*Generates a valid SKILL.md scaffold following Anthropic Skills-first rules (kebab-case name ≤64 chars, YAML frontmatter ≤
|
|
15
|
+
*Generates a valid SKILL.md scaffold following Anthropic Skills-first rules (kebab-case name ≤64 chars, YAML frontmatter ≤1536-char description, ≤500-line body, progressive disclosure to one reference.md).*
|
|
16
16
|
|
|
17
17
|
Usage: `/ciel-create-skill <name> <purpose>`
|
|
18
18
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scan project for ingestable tribal docs (lessons.md, ciel-overlay.md, .claude/rules/, Claude Code auto-memory at ~/.claude/projects/<slug>/memory/, etc.) and propose ingestion into the cued-recall memory under .ciel/memory/. Reports findings if no sources found. Always confirms each candidate with the user before writing.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /ciel-memory-bootstrap — Initialize Cued-Recall Memory
|
|
6
|
+
|
|
7
|
+
**Purpose:** First-run scan of an existing project to convert tribal knowledge already documented in `lessons.md`, `ciel-overlay.md`, `.claude/rules/`, Claude Code's per-project auto-memory (`~/.claude/projects/<slug>/memory/`), and similar files into the structured cued-recall memory at `.ciel/memory/`.
|
|
8
|
+
|
|
9
|
+
**Usage:** `/ciel-memory-bootstrap` (no args)
|
|
10
|
+
|
|
11
|
+
This is **deterministic**: no agent dispatch, no pipeline, no DIVERGE/EVALUER. Just scan, propose, write on user confirmation.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Instructions
|
|
16
|
+
|
|
17
|
+
You are bootstrapping the cued-recall memory for this project. Follow these steps in order.
|
|
18
|
+
|
|
19
|
+
### Step 1 — Scan
|
|
20
|
+
|
|
21
|
+
Run the bootstrap script in `scan` mode:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Try installed location first, fallback to dev location
|
|
25
|
+
script="$CLAUDE_PROJECT_DIR/.claude/hooks/memory-bootstrap.sh"
|
|
26
|
+
[ -f "$script" ] || script="$CLAUDE_PROJECT_DIR/hooks/memory-bootstrap.sh"
|
|
27
|
+
bash "$script" scan
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or, if running on an installed Ciel: `bash "$HOME/.ciel/hooks/memory-bootstrap.sh" scan`.
|
|
31
|
+
|
|
32
|
+
Report the output verbatim to the user.
|
|
33
|
+
|
|
34
|
+
### Step 2 — Decide path
|
|
35
|
+
|
|
36
|
+
Based on the scan output:
|
|
37
|
+
|
|
38
|
+
- **If 0 sources found** → tell the user clearly: "No tribal docs to bootstrap from. The cued-recall memory will populate organically as you intervene with me. Nothing more to do." End here.
|
|
39
|
+
- **If sources found** → proceed to Step 3.
|
|
40
|
+
|
|
41
|
+
### Step 3 — Initialize structure
|
|
42
|
+
|
|
43
|
+
Run:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
script="$CLAUDE_PROJECT_DIR/.claude/hooks/memory-bootstrap.sh"
|
|
47
|
+
[ -f "$script" ] || script="$CLAUDE_PROJECT_DIR/hooks/memory-bootstrap.sh"
|
|
48
|
+
bash "$script" ingest
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This creates `.ciel/memory/{episodes,concepts,guards}/` and an empty `index.json`. It does NOT auto-write memories — auto-ingestion would create cargo-cult entries from possibly-stale docs (see ADR-0001).
|
|
52
|
+
|
|
53
|
+
### Step 4 — Read each source
|
|
54
|
+
|
|
55
|
+
For each source found in Step 1, `Read` the file fully. Identify candidate memories:
|
|
56
|
+
|
|
57
|
+
| Source format | What becomes a memory |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `[YYYY-MM-DD] MISTAKE: X → RULE: Y` lines (lessons.md style) | One memory per line. Title = the rule. |
|
|
60
|
+
| `## Heading\n\n- rule\n- rule` (rules.md style) | One memory per rule. |
|
|
61
|
+
| Numbered lessons in `ciel-overlay.md` "Key Lessons" | One memory per lesson. |
|
|
62
|
+
| `## section` in CLAUDE.md/AGENTS.md describing a non-obvious convention | One memory per section. |
|
|
63
|
+
| **Claude Code auto-memory** entries (`~/.claude/projects/<slug>/memory/*.md`, excluding `MEMORY.md`) | One memory per file. Title = frontmatter `description`. Cues derived per "Auto-memory mapping" below. |
|
|
64
|
+
|
|
65
|
+
#### Auto-memory mapping (special parser)
|
|
66
|
+
|
|
67
|
+
Claude Code auto-memory uses a different frontmatter than Ciel's cued-recall. Each source file looks like:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
---
|
|
71
|
+
name: feedback-okhttp-cookiejar-override
|
|
72
|
+
description: Neiyomi shared PersistentCookieJar overrides manual Cookie headers via OkHttp BridgeInterceptor
|
|
73
|
+
metadata:
|
|
74
|
+
type: feedback
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
(body markdown — Context / Why / How to apply sections)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
When you encounter a file under `$AUTO_MEMORY_DIR`, map it to a Ciel episode as follows:
|
|
81
|
+
|
|
82
|
+
| Auto-memory field | Ciel frontmatter field | Notes |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `description:` | `title:` | one-line summary |
|
|
85
|
+
| `name:` | base of slug for filename | already kebab-case |
|
|
86
|
+
| `metadata.type:` (`user`/`feedback`/`project`/`reference`) | `intents:` `[<type>]` plus topic-specific intents inferred from body | e.g. `feedback` + `okhttp` + `cookie` |
|
|
87
|
+
| body markdown | Ciel episode body, verbatim | preserve Context/Why/How to apply structure |
|
|
88
|
+
| paths cited in body (e.g. `src/`, `*.kt`, `Caddyfile`) | `path_patterns:` | infer from grep — narrow patterns preferred |
|
|
89
|
+
| symbols cited in body (class/function/table names) | `symbols:` | infer from grep |
|
|
90
|
+
| language hint (file extensions in body) | `languages:` | `kotlin`/`typescript`/`python`/`sql`/etc. |
|
|
91
|
+
| `captured_from:` (NEW) | `auto-memory-migration` | distinguishes from user-intervention captures |
|
|
92
|
+
|
|
93
|
+
**Skip `MEMORY.md`** — it's a table-of-contents index, not memory content. The scan already excludes it.
|
|
94
|
+
|
|
95
|
+
**Backup before delete.** After successfully writing an episode file for an auto-memory entry, MOVE (not delete) the source to `$AUTO_MEMORY_DIR/.migrated-to-ciel/<filename>` so the user can audit migration. The MEMORY.md index file itself stays in place — Claude Code may regenerate it on next session.
|
|
96
|
+
|
|
97
|
+
Skip:
|
|
98
|
+
- The pipeline / workflow descriptions (those belong in CLAUDE.md, not memory)
|
|
99
|
+
- General principles already in CLAUDE.md
|
|
100
|
+
- Anything that's just project description (READMEish)
|
|
101
|
+
- Code examples (those go in skills/, not memory)
|
|
102
|
+
|
|
103
|
+
### Step 5 — Propose batch capture
|
|
104
|
+
|
|
105
|
+
Once you have N candidate memories from the sources, present them to the user **as a batch**, not one by one (avoid 50 confirmation prompts). Use a single `AskUserQuestion` with the structure:
|
|
106
|
+
|
|
107
|
+
> "Found N candidates from your tribal docs. I'll list them; you tell me which to capture, which to skip, or 'all'."
|
|
108
|
+
|
|
109
|
+
For each candidate, show:
|
|
110
|
+
- **Title** (one line)
|
|
111
|
+
- **Source** (file:line)
|
|
112
|
+
- **Suggested tags** (paths, symbols, intents, language inferred from the lesson content)
|
|
113
|
+
|
|
114
|
+
The user replies with: "all", "1,3,5,8" (specific indices), or "skip".
|
|
115
|
+
|
|
116
|
+
### Step 6 — Write captured memories
|
|
117
|
+
|
|
118
|
+
For each captured candidate, create `.ciel/memory/episodes/<YYYY-MM-DD>-<slug>.md` with frontmatter:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
---
|
|
122
|
+
id: mem_<NNN>
|
|
123
|
+
title: <title>
|
|
124
|
+
languages: [<inferred>]
|
|
125
|
+
path_patterns:
|
|
126
|
+
- <pattern>
|
|
127
|
+
symbols: [<inferred>]
|
|
128
|
+
intents: [<inferred>]
|
|
129
|
+
captured_at: <ISO8601 now>
|
|
130
|
+
captured_from: bootstrap
|
|
131
|
+
source: <original-file:line>
|
|
132
|
+
trigger_count: 0
|
|
133
|
+
last_triggered: null
|
|
134
|
+
stale_after_days: 90
|
|
135
|
+
stale: false
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# <title>
|
|
139
|
+
|
|
140
|
+
<content from source, lightly cleaned>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
ID strategy: read existing `index.json` for max id, increment. Slug = first 5 words of title, kebab-cased.
|
|
144
|
+
|
|
145
|
+
### Step 7 — Rebuild index
|
|
146
|
+
|
|
147
|
+
After all writes, regenerate `.ciel/memory/index.json` by parsing every frontmatter under `.ciel/memory/{episodes,concepts,guards}/`:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# pseudo — use python3 -c '...' inline
|
|
151
|
+
for each *.md file:
|
|
152
|
+
parse frontmatter
|
|
153
|
+
add to memories dict by id
|
|
154
|
+
for each path_pattern, symbol, intent, language:
|
|
155
|
+
append id to corresponding by_* index
|
|
156
|
+
write back to index.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Step 8 — Confirm
|
|
160
|
+
|
|
161
|
+
Report:
|
|
162
|
+
|
|
163
|
+
- N memories captured
|
|
164
|
+
- Sources processed
|
|
165
|
+
- Index rebuilt with M total entries
|
|
166
|
+
- Suggest: "Cued-recall memory now active. Memories will auto-inject when their cues match in future tasks. Run `/ciel-memory-bootstrap` again anytime to re-scan for new tribal docs."
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Constraints
|
|
171
|
+
|
|
172
|
+
- **Never write a memory without user confirmation.** Even on bulk confirmation ("all"), display the list first.
|
|
173
|
+
- **Do not delete the source files.** Bootstrap converts; the user keeps the originals as long as they want.
|
|
174
|
+
- **Tag conservatively.** A memory tagged with `**/*` will fire on every task and pollute. If unsure, narrow the path pattern.
|
|
175
|
+
- **No agent dispatch.** This command is deterministic and runs inline.
|
|
176
|
+
- **Idempotent.** Re-running on an already-bootstrapped project should detect existing memories (by source field) and offer to skip duplicates.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Failure modes
|
|
181
|
+
|
|
182
|
+
| Symptom | Cause | Fix |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| Script not found | `$CLAUDE_PROJECT_DIR` not set | Try `$HOME/.ciel/hooks/memory-bootstrap.sh` instead |
|
|
185
|
+
| Nothing scanned | No tribal docs in this project | Working as intended; report and end |
|
|
186
|
+
| Memories all tagged with broad paths | Source content didn't include path hints | Ask user to refine tags after listing |
|
|
187
|
+
| index.json malformed after rebuild | python3 parse error | Recreate empty index, re-run rebuild step |
|
|
188
|
+
| Auto-memory not detected | Slug derivation mismatch (cwd has unexpected characters) | Override via `CIEL_AUTO_MEMORY_DIR=<absolute-path> bash hooks/memory-bootstrap.sh scan` |
|
|
189
|
+
| Auto-memory file has `name:` but no `description:` | Older auto-memory format | Use first heading or filename as title; ask user to confirm before writing |
|
|
190
|
+
|
|
191
|
+
## See also
|
|
192
|
+
|
|
193
|
+
- `docs/adrs/0001-cued-recall-memory.md` — full design rationale
|
|
194
|
+
- `skills/workflow/memoire/SKILL.md` — capture/recall flow
|
|
195
|
+
- `skills/workflow/memoire-consolidator/SKILL.md` — periodic maintenance
|
|
@@ -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`
|