Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions plugins/cc10x/scripts/cc10x_context_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,29 @@ def _current_version_number() -> int:
return 0


def _migrate_flat_to_project(target_root: Path) -> int:
"""One-time: copy root-flat memory files into project/ if not yet done.

This is an intra-v10 migration for repos that existed before the
project/ namespace was introduced. It copies (not moves) so that the
root-flat fallback remains intact for backward compatibility.
"""
marker = target_root / ".project-namespace-migrated"
if marker.exists():
return 0
project_path = target_root / "project"
project_path.mkdir(exist_ok=True)
count = 0
for name in ("activeContext.md", "patterns.md", "progress.md"):
src = target_root / name
dst = project_path / name
if src.exists() and not dst.exists():
shutil.copy2(src, dst)
count += 1
marker.write_text(now_iso(), encoding="utf-8")
return count


def _discover_sources(cc10x_base: Path) -> List[Dict[str, Any]]:
"""Find all migration sources ordered oldest-first."""
sources: List[Dict[str, Any]] = []
Expand All @@ -148,12 +171,15 @@ def _discover_sources(cc10x_base: Path) -> List[Dict[str, Any]]:
if (cc10x_base / "activeContext.md").exists():
sources.append({"label": "legacy", "path": cc10x_base, "sort_key": -1})

# Versioned directories
# Versioned directories (skip namespace subdirs introduced in v10.2)
_NAMESPACE_DIRS = {"project", "workflows"}
if cc10x_base.is_dir():
for child in sorted(cc10x_base.iterdir()):
if not child.is_dir():
continue
name = child.name
if name in _NAMESPACE_DIRS:
continue # skip project/ and workflows/ — not migration sources
if not (name.startswith("v") and name[1:].isdigit()):
continue
ver_num = int(name[1:])
Expand Down Expand Up @@ -273,10 +299,26 @@ def main() -> int:
_ = data # consumed for hook contract compliance

target_root = state_root()

# Intra-v10 migration: promote root-flat files into project/ namespace
promoted = _migrate_flat_to_project(target_root)
if promoted > 0:
log_event(
"context_migration",
{
"source": "flat-root",
"target": f"{STATE_VERSION}/project",
"files_merged": ["activeContext.md", "patterns.md", "progress.md"],
"bullets_added": 0,
"decision": "copy",
"reason": "project_namespace_init",
},
)

cc10x_base = project_dir() / ".claude" / "cc10x"

if not cc10x_base.exists():
return 0 # fresh install, nothing to migrate
return 0 # fresh install, nothing to migrate from legacy path

sources = _discover_sources(cc10x_base)
if not sources:
Expand Down
14 changes: 14 additions & 0 deletions plugins/cc10x/scripts/cc10x_hooklib.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ def workflows_dir() -> Path:
return path


def project_state_dir() -> Path:
"""Long-lived cross-workflow state: .cc10x/v10/project/"""
path = state_root() / "project"
path.mkdir(parents=True, exist_ok=True)
return path


def workflow_state_dir(workflow_id: str) -> Path:
"""Per-workflow isolated state: .cc10x/v10/workflows/<wf-id>/"""
path = workflows_dir() / workflow_id
path.mkdir(parents=True, exist_ok=True)
return path


def logs_dir() -> Path:
path = state_root()
path.mkdir(parents=True, exist_ok=True)
Expand Down
21 changes: 20 additions & 1 deletion plugins/cc10x/scripts/cc10x_pretooluse_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,32 @@
load_mode,
log_event,
pretool_deny,
project_state_dir,
state_root,
workflows_dir,
)


PROTECTED_MEMORY_FILES = ("activeContext.md", "patterns.md", "progress.md")


def _protected_memory_paths() -> set:
"""Return all active memory locations that should be write-guarded."""
paths = {state_root() / name for name in PROTECTED_MEMORY_FILES}
try:
paths |= {project_state_dir() / name for name in PROTECTED_MEMORY_FILES}
except Exception:
pass
try:
wf_dir = workflows_dir()
for name in PROTECTED_MEMORY_FILES:
for candidate in wf_dir.glob(f"*/{name}"):
paths.add(candidate.resolve())
except Exception:
pass
return paths


def main() -> int:
data = load_input()
mode = load_mode()
Expand All @@ -25,7 +44,7 @@ def main() -> int:
path = Path(file_path).resolve()
violations = []

protected_memory = {state_root() / name for name in PROTECTED_MEMORY_FILES}
protected_memory = _protected_memory_paths()

if path in protected_memory:
violations.append("memory-write")
Expand Down
68 changes: 51 additions & 17 deletions plugins/cc10x/skills/cc10x-router/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,30 @@ Rules:

## 2. Memory Load And Template Validation

Always run this before routing or resuming:
Always run this before routing or resuming. Memory is organized in two tiers:
- **project/** — long-lived cross-workflow state (architecture decisions, durable patterns, ongoing blockers). Always load first.
- **workflows/{wf-id}/** — per-workflow isolated state (current focus, active phase, in-flight tasks). Load only when a `workflow_uuid` is already known (resume path).

```text
1. Bash("mkdir -p .cc10x/v10")
2. Read(".cc10x/v10/activeContext.md")
3. Read(".cc10x/v10/patterns.md")
4. Read(".cc10x/v10/progress.md")
1. Bash("mkdir -p .cc10x/v10/project")
2. Read(".cc10x/v10/project/activeContext.md")
3. Read(".cc10x/v10/project/patterns.md")
4. Read(".cc10x/v10/project/progress.md")
5. If workflow_uuid is known (resume path):
a. Bash("mkdir -p .cc10x/v10/workflows/{workflow_uuid}")
b. Read(".cc10x/v10/workflows/{workflow_uuid}/activeContext.md")
c. Read(".cc10x/v10/workflows/{workflow_uuid}/patterns.md")
d. Read(".cc10x/v10/workflows/{workflow_uuid}/progress.md")
Merge: workflow-scoped values override project-scoped for current-focus
fields (## Current Focus, ## Next Steps, ## Tasks) only.
6. Fallback: If project/ files are missing or empty, also read the root-flat
files (.cc10x/v10/activeContext.md etc.) and merge content into project/
before proceeding. Root-flat files are the backward-compat layer.
```

Do not parallelize step 1 with reads.

If a memory file is missing:
If a project/ memory file is missing:
- Create it using the `cc10x:session-memory` template.
- Read it before continuing.

Expand Down Expand Up @@ -225,7 +237,16 @@ Write(
)
```

Only create child tasks after the v10 artifact exists.
4. Immediately after artifact creation, initialize the per-workflow state directory:

```text
Bash("mkdir -p .cc10x/v10/workflows/{workflow_uuid}")
```

This directory is where the memory-finalize task will write workflow-scoped
memory (activeContext.md, patterns.md, progress.md for this workflow only).

Only create child tasks after the v10 artifact and state directory exist.

### BUILD task graph

Expand Down Expand Up @@ -568,18 +589,31 @@ Before invoking `integration-verifier` in BUILD:

The memory task executes inline only. Never spawn it as a sub-agent.

The memory task:
- Reads the workflow artifact plus its own description payload, not conversation history.
- Persists learnings to:
- `activeContext.md ## Learnings`
- `patterns.md ## Common Gotchas`
- `progress.md ## Verification`
- Writes deferred items as `[Deferred]: ...` under `patterns.md ## Common Gotchas`.
- Replaces `progress.md ## Tasks` with the active workflow snapshot.
- Keeps only the most recent 10 items in `progress.md ## Completed`.
- Removes the matching `[cc10x-internal] memory_task_id` line from `activeContext.md ## References`.
Memory is written to two tiers. Route each `MEMORY_NOTES` field as follows:

| MEMORY_NOTES field | Write destination | Rationale |
|--------------------|-------------------|-----------|
| `learnings` | `workflows/{workflow_uuid}/activeContext.md ## Learnings` | Workflow-specific causal insights |
| `patterns` | `project/patterns.md ## Common Gotchas` | Durable conventions that apply to all future workflows |
| `verification` | `workflows/{workflow_uuid}/progress.md ## Verification` | Proof evidence scoped to this build/debug/review run |
| `deferred` | `workflows/{workflow_uuid}/activeContext.md` as `[Deferred]: ...` | Non-blocking follow-ups scoped to this workflow |

Cross-workflow promotion rule: If a `learnings` item is a project-wide constraint
(not specific to the current task), also copy it to `project/activeContext.md ## Learnings`.
Use judgment: workflow-local observations stay in `workflows/{wf}/`; durable project
truths belong in `project/`.

The memory task also:
- Replaces `workflows/{workflow_uuid}/progress.md ## Tasks` with the active workflow snapshot.
- Keeps only the most recent 10 items in `workflows/{workflow_uuid}/progress.md ## Completed`.
- Updates `project/progress.md ## Completed` with a one-line summary of the finished workflow.
- Removes the matching `[cc10x-internal] memory_task_id` line from `project/activeContext.md ## References`.
- If any artifact or memory write fails, stop immediately. Never advance the workflow after a failed persistence write.

Fallback: If `workflow_uuid` is unavailable, write to root-flat files
(`.cc10x/v10/activeContext.md`, `.cc10x/v10/patterns.md`, `.cc10x/v10/progress.md`)
as in prior versions.

For PLAN:
- Ensure `- Plan: {plan_file}` remains correct in `activeContext.md ## References`.
- Ensure `- Design: {design_file}` remains correct in `activeContext.md ## References` when a design exists.
Expand Down
54 changes: 42 additions & 12 deletions plugins/cc10x/skills/session-memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ If memory is narrated instead of distilled, it bloats context and loses signal.

## Load-Bearing Boundaries

- Memory lives under `.cc10x/v10/`.
- Memory lives under `.cc10x/v10/` with two active namespaces:
- `project/` — long-lived cross-workflow state; always loaded at workflow start.
- `workflows/{wf-id}/` — per-workflow isolated state; loaded on resume when `workflow_uuid` is known.
- Root-flat `.cc10x/v10/*.md` — backward-compatibility fallback; used when no `workflow_uuid` is available.
- The router loads and auto-heals memory files before routing or resume.
- WRITE agents read memory, but do **not** edit `.cc10x/v10/*.md` directly.
- WRITE agents read memory, but do **not** edit memory markdown files directly.
- WRITE agents emit structured `MEMORY_NOTES` in their Router Contract.
- READ-ONLY agents emit `### Memory Notes (For Workflow-Final Persistence)`.
- The router-owned memory-finalize task is the only final writer of memory markdown files.
Expand All @@ -60,15 +63,27 @@ If memory is narrated instead of distilled, it bloats context and loses signal.

## Memory Surfaces

Use the right layer for the right kind of information:
Memory is organized in three tiers. Use the right tier for the right kind of information:

- `activeContext.md`: current focus, recent changes, decisions, learnings, references,
blockers
- `patterns.md`: reusable project standards, gotchas, conventions, and skill hints
- `progress.md`: current workflow, tasks snapshot, completed items, verification evidence
- `docs/plans/*` and `docs/research/*`: the detailed artifacts; memory points to them
- `.cc10x/v10/workflows/{wf}.json` and `.events.jsonl`: the durable orchestration
truth
**project/** (`project/activeContext.md`, `project/patterns.md`, `project/progress.md`):
- Architecture decisions, domain patterns, ongoing blockers that span all workflows.
- Durable reusable conventions and skill hints.
- Cross-workflow completion history.
- Write here when context should survive to the next Claude Code session indefinitely.

**workflows/{wf-id}/** (`workflows/{wf}/activeContext.md`, etc.):
- Current focus, recent changes, decisions, and learnings for THIS workflow only.
- Task snapshot and verification evidence for this build/debug/review/plan run.
- Non-blocking follow-ups that are relevant only to this workflow.
- Isolated per-session; does not pollute parallel workflows on the same repo.

**Root-flat fallback** (`.cc10x/v10/activeContext.md`, etc.):
- Used when `workflow_uuid` is not yet available (first router turn before UUID generation).
- Backward-compatible layer; populated automatically on first SessionStart.

**External artifacts:**
- `docs/plans/*` and `docs/research/*`: detailed artifacts; memory points to them.
- `.cc10x/v10/workflows/{wf}.json` and `.events.jsonl`: the durable orchestration truth.

Read `references/memory-model-and-ownership.md` if you need the full ownership model or the
promotion ladder.
Expand Down Expand Up @@ -102,14 +117,29 @@ without re-reading the whole conversation, the memory is under-distilled.

### Always Load

At workflow start, continuation, or resume, read all three memory files:
At workflow start, continuation, or resume, load in this order:

```text
# Tier 1 — always (long-lived project state)
.cc10x/v10/project/activeContext.md
.cc10x/v10/project/patterns.md
.cc10x/v10/project/progress.md

# Tier 2 — when workflow_uuid is known (per-workflow isolated state)
.cc10x/v10/workflows/{workflow_uuid}/activeContext.md
.cc10x/v10/workflows/{workflow_uuid}/patterns.md
.cc10x/v10/workflows/{workflow_uuid}/progress.md

# Fallback — when project/ is empty or workflow_uuid is not yet available
.cc10x/v10/activeContext.md
.cc10x/v10/patterns.md
.cc10x/v10/progress.md
```

Merge rule: workflow-scoped values override project-scoped for current-focus fields
(`## Current Focus`, `## Next Steps`, `## Tasks`) only. Durable fields
(`## Decisions`, `## User Standards`, `## Architecture Patterns`) always come from `project/`.

### Re-Read Before These Actions

| Action | Re-read | Why |
Expand Down Expand Up @@ -237,7 +267,7 @@ Stop and correct course if you catch yourself:
- making decisions without checking `## Decisions` or project patterns
- writing long diary-style memory notes
- claiming you will "remember later"
- editing `.cc10x/v10/*.md` directly from a write agent
- editing `.cc10x/v10/*.md`, `project/*.md`, or `workflows/{wf}/*.md` directly from a write agent
- inventing new headings, markers, or memory file shapes
- treating stale conversation context as better than durable memory

Expand Down