Skip to content
Merged
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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ node mcp-server.mjs --personal ~/kb-personal --shared ~/kb-shared
node mcp-server.mjs --manifest layers.json --capture --telemetry --harness claude-code

# Promote a live capture through the review queue (request, then approve)
node promote.mjs --from-live ~/kb-live --capture captures/investigation/<id> --target ~/kb-team
node promote.mjs --from-live ~/kb-live --target ~/kb-team --approve ~/kb-team/_review/promotions/<slug>.md
node promote.mjs --legacy-paths --from-live ~/kb-live --capture captures/investigation/<id> --target ~/kb-team
node promote.mjs --legacy-paths --from-live ~/kb-live --target ~/kb-team --approve ~/kb-team/_review/promotions/<slug>.md

# Team activity dashboard data (feed + cross-brain-hit metrics)
node team-activity.mjs --live-root ~/kb-live --out apps/control-surface/team-activity.json
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src/cli/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ const COMMANDS = {
resolve: { entry: 'resolver.mjs', manifest: true, blurb: 'resolve a concept across layers' },
ingest: { entry: 'ingest.mjs', manifest: false, blurb: 'classify repo events into signals' },
write: { entry: 'write.mjs', manifest: true, blurb: 'write captured signals into a layer' },
promote: { entry: 'promote.mjs', manifest: false, blurb: 'promote a concept up one layer' },
promote: { entry: 'promote.mjs', manifest: true, blurb: 'promote a live capture inside one profile' },
pack: { entry: 'pack-cli.mjs', manifest: false, blurb: 'inspect, install, update, and roll back local Packs' },
profile: { entry: 'profile-cli.mjs', manifest: true, blurb: 'inspect and manage project profiles' },
}

function usage() {
Expand Down Expand Up @@ -72,7 +73,8 @@ if (!command) {
}

const args = [...rest]
if (command.manifest && !args.includes('--manifest') && !args.includes('--personal')) {
const isHelp = args.some((arg) => ['help', '--help', '-h'].includes(arg))
if (command.manifest && !isHelp && !args.includes('--manifest') && !args.includes('--personal') && !args.includes('--legacy-paths')) {
if (!fs.existsSync(DEFAULT_MANIFEST)) {
console.error(`contextcake: no manifest at ${DEFAULT_MANIFEST}`)
console.error('Open the ContextCake app to run first-time setup, or pass --manifest.')
Expand Down
7 changes: 3 additions & 4 deletions apps/desktop/src/main/settings-sync.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import fs from 'node:fs'
import path from 'node:path'
import { isDeepStrictEqual } from 'node:util'

const SYNC_FIELDS = ['theme', 'updateCheck', 'activeProfile', 'profiles', 'sources']
// activeProfile is deliberately local-only: remote activity must never select
// a profile that can open machine-local paths or trusted executable sources.
const SYNC_FIELDS = ['theme', 'updateCheck', 'profiles', 'sources']
const SCRUBBED = new Set(['execution', 'path', 'secret'])
const SECRET_KEY = /(?:^|_)(?:password|passwd|secret|token|api_?key|authorization|cookie|credential)(?:$|_)/i
const CONTEXT_KEY = /^(?:content|contents|body|document|documents|knowledge|markdown|payload|raw|resolved|sections|text)$/i
Expand Down Expand Up @@ -128,9 +130,6 @@ function assertSettingsShape(settings) {
if (settings.updateCheck !== undefined && typeof settings.updateCheck !== 'boolean') {
throw new Error('Settings sync rejected an invalid update preference.')
}
if (settings.activeProfile !== undefined && typeof settings.activeProfile !== 'string') {
throw new Error('Settings sync rejected an invalid active profile.')
}
const assertSources = (sources) => {
if (!Array.isArray(sources)) throw new Error('Settings sync rejected invalid source definitions.')
for (const source of sources) {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/test/settings-sync.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('scrubSettings recursively removes absolute paths and indirect secrets', ()
})

test('prepareSyncPayload allowlists metadata and rejects credentials or context', () => {
assert.deepEqual(prepareSyncPayload({ theme: 'light', updateCheck: true, privateNotes: 'never upload' }), {
assert.deepEqual(prepareSyncPayload({ theme: 'light', updateCheck: true, activeProfile: 'work', privateNotes: 'never upload' }), {
theme: 'light',
updateCheck: true,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Point it at the bundled demo:
node mcp-server.mjs --manifest apps/playground/manifest.json
```

With a Project Profiles manifest, register this command once. The server selects
`--profile` when explicitly supplied; otherwise it uses the deepest project
mapping that contains the MCP process's startup working directory, then falls
back to `default`.

Or use the legacy two-layer form, no manifest file required:

```bash
Expand Down Expand Up @@ -44,6 +49,27 @@ The server identifies itself over the MCP `initialize` handshake as
`contextcake`. Any MCP-compatible client — not just Claude Code — can spawn it
the same way: `command` is `node`, `args` are the script path and flags.

## Verify automatic selection

From a mapped project folder, first confirm the CLI selection:

```bash
cd /ABS/PATH/TO/MAPPED/PROJECT
contextcake profile current
```

Start a new agent session from that folder. Its MCP initialization instructions
should name the same stable profile id and `reason: project`; the display label
is available in structured initialization metadata. Then call `list_concepts`
and confirm only that profile's concepts appear. The project path itself is
never included in MCP instructions.

Some clients do not start a global stdio server with the project as its working
directory. For those clients, automatic folder mapping is unavailable. Use the
default profile, or create a separately named opt-in registration whose command
is `contextcake mcp --profile <id>`. Do not replace the one global automatic
registration unless that client requires it.

## Tools

| Tool | What it returns |
Expand Down
6 changes: 6 additions & 0 deletions apps/site/src/content/docs/docs/guides/capture-write-path.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ Flags: `--signals <file>` (required), `--manifest <file>` (required),
`--target-layer <name>` (defaults to the highest layer level below 3 — i.e.
not personal — or level 3 if that's the only layer available),
`--dry-run` (prints what would be written without touching disk), `--help`.
The target must be a non-live local OKF layer; plain Markdown-folder sources
and live capture repositories are not batch-write targets.

Per signal:

Expand All @@ -111,6 +113,10 @@ Per signal:
— pending a human decision. A signal that already resolves to an existing
file is skipped rather than overwritten.

Destinations are validated as concept ids. Traversal and symlinked parents or
files are rejected before a write, so classifier output cannot escape the
selected profile's target root.

Every written or staged concept carries `draft: true`, `source: <repo>`, and
an `updated` date in its frontmatter, plus a `## Context {#context}` section
with the recommended action and a `## Signals {#signals}` section listing the
Expand Down
75 changes: 53 additions & 22 deletions apps/site/src/content/docs/docs/guides/promoting-concepts.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
---
title: Promoting concepts
description: Move a concept up the stack — personal to shared.
description: Promote a live capture into a curated layer without crossing profile boundaries.
---

A concept that started as a personal note often deserves to become team
knowledge. `promote.mjs` copies a concept from a personal OKF bundle into a
shared bundle, rewrites its links, and rebuilds the shared index — it doesn't
touch the resolver or the manifest at all; it operates directly on two
bundle directories.
A useful live capture often deserves to become curated team knowledge.
`promote.mjs` uses the same Project Profile selection as MCP, resolves both
the live source and curated target from that one profile, and stages a review
before it removes anything from the live repository.

## Usage

Request a promotion:

```bash
node promote.mjs --personal <dir> --shared <dir> --file <concept-or-path> [--dry-run] [--print-git]
contextcake promote --capture captures/investigation/<id> --target-layer team --dest systems/<id>
```

`--personal <dir>` and `--shared <dir>` are the two bundle roots (required).
`--file <concept-or-path>` is the concept to promote — a bare id like
`decisions/primary-db` or a path, with or without `.md` (required).
The command selects an explicit `--profile <id>`, the deepest project mapping
for the current directory, or `default`, in that order. The selected profile
must contain exactly one `live: true` local OKF layer and the named curated
`--target-layer`.

After reviewing the generated file under the curated layer's
`_review/promotions/` directory, approve it:

```bash
contextcake promote --approve /ABS/PATH/team/_review/promotions/<slug>.md --target-layer team
```

The review carries the profile id, manifest revision, live-layer and
target-layer fingerprints, capture id, destination, and capture-content hash
from the request. Approval reselects the same profile and fails if configuration
or capture content changed. ContextCake holds the manifest boundary across that
check and the local mutation. Only after the curated write verifies and the
live deletion commits does it remove the review entry; a failed commit restores
the capture and leaves the review retryable.

Live captures and review entries must be regular files inside their selected
layer roots. Symlinked files and any parent path that escapes its selected root
are rejected rather than followed.
The editable review carries an opaque binding id; its authoritative tuple is
kept in ContextCake's machine-local state, not in the team repository. This
lets people edit the proposed prose without letting the review redefine its
source capture, destination, or trust boundary.

## Legacy raw-directory mode

The original personal-to-shared copy and raw live-root flow remain available
for advanced compatibility. They require `--legacy-paths` so a command that
bypasses Project Profile isolation is explicit:

```bash
node promote.mjs --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db
node promote.mjs --legacy-paths --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db
node promote.mjs --legacy-paths --from-live ~/kb-live --capture captures/investigation/<id> --target ~/kb-team
```

This copies `~/kb-personal/decisions/primary-db.md` to
`~/kb-team/decisions/primary-db.md`, preserving its relative path, and
rewrites the shared bundle's `index.md` to include it.
The remaining options on this page describe that legacy personal-to-shared
copy mode.

## Link rewriting

Expand Down Expand Up @@ -54,7 +85,7 @@ the shared index authoritative without hand-maintaining it.
touching disk:

```bash
node promote.mjs --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db --dry-run
node promote.mjs --legacy-paths --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db --dry-run
```

```json
Expand All @@ -74,7 +105,7 @@ node promote.mjs --personal ~/kb-personal --shared ~/kb-team --file decisions/pr
— it doesn't run them for you:

```bash
node promote.mjs --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db --print-git
node promote.mjs --legacy-paths --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db --print-git
```

```
Expand All @@ -95,13 +126,13 @@ override it. Since the shared bundle is its own git repo, promotion becomes a
normal reviewable PR against team or company knowledge — nothing is written
straight to a shared main branch unreviewed.

## Why directories, not a manifest
## Why the legacy mode still exists

`promote.mjs` takes `--personal`/`--shared` directories rather than
`--manifest`/`--target-layer` like `write.mjs`. Promotion is a bundle-to-bundle
file operation between two known OKF roots you already have local paths to
— it doesn't need the full source-adapter machinery that lets a manifest
layer be `mcp` or a remote clone.
Older scripts may already know two explicit bundle roots and may not use a
ContextCake manifest. `--legacy-paths` preserves that workflow, but it cannot
claim cross-profile isolation or bind an approval to a manifest revision. New
team-sync workflows should use `--manifest`/`--target-layer` (or the packaged
CLI defaults) instead.

## Next

Expand Down
65 changes: 46 additions & 19 deletions apps/site/src/content/docs/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ merge, level precedence, provenance, and per-section conflicts — and prints it
JSON.

```
node resolver.mjs --manifest <file> --concept <id>
node resolver.mjs --manifest <file> --concept <id> [--profile <id>]
```

| Flag | Required | Meaning |
|------|----------|---------|
| `--manifest <file>` | yes | Path to the [layers manifest](/docs/reference/manifest). |
| `--concept <id>` | yes | Concept ID to resolve, e.g. `decisions/primary-db`. |
| `--profile <id>` | no | Select this profile before inspecting the working directory. |
| `--help`, `-h` | no | Print usage and exit. |

```bash
Expand All @@ -41,13 +42,14 @@ cascade mode (a manifest) or the legacy two-layer mode (explicit personal + shar
directories).

```
node mcp-server.mjs --manifest <file>
node mcp-server.mjs --manifest <file> [--profile <id>]
node mcp-server.mjs --personal <dir> --shared <dir>
```

| Flag | Required | Meaning |
|------|----------|---------|
| `--manifest <file>` | one mode | Cascade mode: resolve the full stack in the manifest. |
| `--profile <id>` | no | Explicit profile override. Otherwise the server uses its startup working directory, then `default`. |
| `--personal <dir>` | one mode | Legacy mode: personal bundle directory (level 3). Use with `--shared`. |
| `--shared <dir>` | one mode | Legacy mode: shared bundle directory (level 0). Use with `--personal`. |
| `--help`, `-h` | no | Print usage and exit. |
Expand All @@ -62,6 +64,32 @@ The server speaks MCP over stdin/stdout. See [MCP tools](/docs/reference/mcp-too
for the tools it exposes and [Connect an agent](/docs/getting-started/connect-an-agent)
for wiring it into a client.

The MCP initialization instructions identify the stable profile id and reason
(`explicit`, `project`, `default`, or `legacy-default`) without including a
local project path. The human-readable label is returned as structured
initialization metadata rather than instruction prose, so synced display text
cannot become an agent directive. Selection is fixed for that process lifetime.

## profile

Inspects and manages local Project Profiles without opening source adapters:

```bash
contextcake profile current [--profile <id>] [--json]
contextcake profile list [--json]
contextcake profile create <label> [--project <path>]
contextcake profile map <id> <path>
contextcake profile unmap <path>
contextcake profile delete <id> [--confirm]
```

`current` reports the selected id, label, reason, and matched root when one
applies. `create` is the deliberate migration point for a flat manifest and
returns the verified backup path. Project folders are canonicalized locally and
never synced. `delete` refuses `default`, previews affected mappings and Pack
assignments, and requires `--confirm`; it removes references but never source,
Pack, overlay, cache, or live-repository files.

## ingest.mjs

Classifies a list of normalized repo signal events through the context policy and
Expand Down Expand Up @@ -127,14 +155,15 @@ bundle. `team_candidate` signals are written directly; `review_required` signals
staged under `_review/` for human approval; `ignore` and `local` are skipped.

```
node write.mjs --signals <file> --manifest <file> --target-layer <name>
node write.mjs --signals <file> --manifest <file> [--profile <id>] --target-layer <name>
```

| Flag | Required | Meaning |
|------|----------|---------|
| `--signals <file>` | yes | The `signals.json` produced by `ingest.mjs`. |
| `--manifest <file>` | yes | Manifest whose layers name the write targets. |
| `--target-layer <name>` | no | Layer to write into. Defaults to the highest level below 3 (team, company, etc.). |
| `--profile <id>` | no | Explicit profile override; otherwise use cwd mapping then `default`. |
| `--target-layer <name>` | no | Curated local OKF layer to write into. Plain Markdown folders and live layers are rejected. Defaults to the highest eligible level below 3. |
| `--dry-run` | no | Report what would be written without touching disk. |
| `--help`, `-h` | no | Print usage and exit. |

Expand All @@ -146,26 +175,24 @@ See [The capture write path](/docs/guides/capture-write-path).

## promote.mjs

Copies one markdown concept from a personal OKF bundle into a shared bundle,
rewriting personal links and rebuilding the shared `index.md`. It works over two
**directories**, not a manifest.
Profile-aware live promotion resolves both the live source and curated target
from one selected profile:

```
node promote.mjs --personal <dir> --shared <dir> --file <concept-or-path>
```bash
node promote.mjs --manifest <file> [--profile <id>] --capture <id> --target-layer <name> [--dest <id>]
node promote.mjs --manifest <file> [--profile <id>] --approve <review-file> --target-layer <name> [--telemetry]
```

| Flag | Required | Meaning |
|------|----------|---------|
| `--personal <dir>` | yes | Source personal bundle directory. |
| `--shared <dir>` | yes | Destination shared bundle directory. |
| `--file <concept-or-path>` | yes | Concept ID or path to promote (`.md` optional). |
| `--branch <name>` | no | Branch name suggested by `--print-git`. Defaults to `promote/<concept-slug>` — `.md` dropped and every character outside `[a-zA-Z0-9._-]` (including `/`) collapsed to a hyphen, e.g. `decisions/primary-db` → `promote/decisions-primary-db`. |
| `--dry-run` | no | Print the planned operations and promoted content as JSON; write nothing. |
| `--print-git` | no | After writing, print suggested `git` commands to open a PR. |
| `--help`, `-h` | no | Print usage and exit. |
The review file records the profile id, manifest revision, live-layer and
target-layer fingerprints, capture id, destination, and capture-content hash.
Approval fails closed if configuration or capture content changed after staging.

The old directory-to-directory commands remain available only as an explicit
advanced compatibility mode and carry no Project Profile isolation guarantee:

```bash
node promote.mjs --personal ~/kb-personal --shared ~/kb-team --file decisions/primary-db --print-git
node promote.mjs --legacy-paths --from-live <root> --capture <id> --target <root>
node promote.mjs --legacy-paths --personal <dir> --shared <dir> --file <concept-or-path> [--dry-run] [--print-git]
```

See [Promoting concepts](/docs/guides/promoting-concepts).
Expand Down
Loading