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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ Assets/*-master*
Assets/*-before-*
Assets/*-preview*
Assets/*.iconset/

# Local tool state
.wrangler/
.impeccable/
.xcodebuildmcp/
15 changes: 9 additions & 6 deletions CONTEXTDADDY_DISTRIBUTION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ContextDaddy 0.2.0
==================
ContextDaddy
============

Apple silicon Mac · macOS 14 Sonoma or later

Expand All @@ -14,9 +14,9 @@ Start with Usage for local history and provider allowance. Skills brings definit
agent access, and supports local create/import, editing, folder updates, link
sharing, and recoverable archival. Plugin-managed skills remain with their owner. Projects shows
discovered context files; it does not claim those files were loaded into a
live prompt. OpenTelemetry shows only verified signals from a compatible
local collector. Files & diagnostics holds raw inventory and configuration
findings. Missing sources are shown as unavailable, not as zero activity.
live prompt. Memory shows discovered instruction and memory files. Run telemetry
shows recorded runs and verified signals from a compatible local collector.
Missing sources are shown as unavailable, not as zero activity.

Privacy and limitations
-----------------------
Expand All @@ -27,7 +27,10 @@ bundled ccusage helper reads local history offline. Provider allowance checks
use installed provider CLIs only when requested or after you opt in to an
automatic check; local OpenTelemetry uses a loopback collector if available.
ContextDaddy does not measure internet bandwidth, and estimated token or
project values are labelled as such. It never edits agent configuration.
project values are labelled as such. Skill and invocation-policy changes use an
explicit preview, apply and recovery path. Configuration-health discovery is
read-only. Live agent status, attention, conversations and replies belong in
Agent Inbox; measured Mac and workload diagnosis belongs in PerformanceDaddy.

ContextDaddy includes ccusage under the MIT license; its acknowledgement and
license text are in CONTEXTDADDY_NOTICES.md inside the app bundle.
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ContextDaddy should feel unmistakably related to StorageDaddy while solving a di
- Pure black canvas with thin mint-outlined surfaces and minimal elevation, matching the StorageDaddy shell.
- Mint means measured/healthy; blue means derived; amber means partial/estimated; coral means failure or disabled.
- Rounded display typography, compact monospaced paths, and the same button geometry and secondary-text tint as StorageDaddy.
- A two-column native shell with four task destinations: Usage, Skills, Projects, OpenTelemetry. A secondary Files & diagnostics control opens raw Inventory and Diagnostics without making them peer destinations. Page headings carry the fuller explanation.
- A two-column native shell with five task destinations: Usage, Skills, Memory, Projects, Run telemetry. A secondary Files & diagnostics control opens raw Inventory and Diagnostics without making them peer destinations. Page headings carry the fuller explanation. Historical activity/telemetry naming below records earlier decisions.
- Evidence badges accompany values rather than relying on color alone.
- The inherited Daddy doodle sheet is functional art: the context-cart hero explains the product, section scenes reinforce location, and the agent scene becomes the in-app family mark.

Expand Down
20 changes: 19 additions & 1 deletion PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,28 @@ ContextDaddy helps developers who use multiple coding agents understand and gove

The primary user is a developer running Codex, Claude, Cursor, Devin, or Grok on a Mac. They need to diagnose a live agent's consumption and audit skill invocation policy without manually tracing symlinks, frontmatter, runtime-specific config, and telemetry dashboards.

## Ownership across the three apps

ContextDaddy owns skills, plugins, MCP structural health, instructions, memory,
context scope, persistent skill invocation/access policy, token history, provider
allowance and provenance-aware run telemetry. The telemetry page is named
**Run telemetry** to distinguish consumption evidence from live inbox status.
The historical “Agent activity” sections below record earlier naming decisions.

Agent Inbox owns live threads, working/waiting/unknown states, the segmented
agent-status battery, attention notifications, exact conversation linking,
acknowledged replies and individual permission decisions. Inbox execution modes
govern its own explicitly started runs; they do not edit global skill/MCP policy.
ContextDaddy does not resume conversations or answer permission prompts.

PerformanceDaddy owns measured Mac/workload diagnosis, process attribution,
device battery/power, captures and comparable performance follow-ups. Consumption
telemetry does not prove a runtime bottleneck, successful task or need for a reply.

## Product promises

- One physical skill is one ledger record, even when it has many logical exposures.
- The primary navigation follows user decisions: Usage, Skills, and OpenTelemetry. Project files and instructions remain a secondary inspector; working-folder selection lives directly in Skills. Raw source files and configuration diagnostics remain available as secondary Files & diagnostics, not competing representations of skills.
- The primary navigation follows user decisions: Usage, Skills, Memory, Projects and Run telemetry. Working-folder selection lives directly in Skills. Raw source files and configuration diagnostics remain available as secondary Files & diagnostics, not competing representations of skills.
- Policy is runtime-specific and explains whether it is explicit or derived.
- Definitions that cannot be read within the bounded metadata limit retain their discovered routes but have unavailable invocation controls. Claude definitions that disable both user and model invocation are disabled, rather than manual-only.
- Precedence applies only to qualified discovered routes: ordinary Claude personal skills override ordinary project definitions; Codex shared-root duplicates coexist. Custom routes, case-only collisions and unsupported vendor winner rules remain unverified, with no winner inferred from enumeration order. These states do not prove installed runtime activation.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ ContextDaddy is a local-first macOS control plane for coding-agent context. It a

The app combines a focused usage desk with a unified skill library and agent-policy inspector. Every value is labelled as measured, derived, estimated, partial, or unavailable. Missing instrumentation stays missing; ContextDaddy does not manufacture precision.

The primary navigation is **Usage**, **Skills**, **Projects**, and **OpenTelemetry**. **Files & diagnostics** is a secondary, always-visible route to raw source inventory and configuration checks. Skills explains how each agent can use a skill; Projects shows discovered files rather than claiming they entered a live prompt. OpenTelemetry is directly reachable and uses a separate fixed 24-hour window.
The primary navigation is **Usage**, **Skills**, **Memory**, **Projects**, and **Run telemetry**. **Files & diagnostics** is a secondary route to raw source inventory and configuration checks. Skills explains how each agent can use a skill; Projects shows discovered files rather than claiming they entered a live prompt. Run telemetry combines recorded runs with verified local signals and a separate fixed 24-hour telemetry window.

Agent Inbox owns live thread status, the agent-status battery, attention, conversations and replies. PerformanceDaddy owns measured Mac and workload diagnosis. ContextDaddy owns context, invocation policy and consumption evidence; discovery and telemetry do not prove live prompt use or task success.

## Current milestone

Expand Down
2 changes: 1 addition & 1 deletion Sources/ContextDaddy/ContextDaddyModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum AppSection: String, CaseIterable, Identifiable {
var label: String {
switch self {
case .overview: "Usage"
case .telemetry: "Agent activity"
case .telemetry: "Run telemetry"
case .skills, .memory, .projects: rawValue
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContextDaddy/FocusDeskView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct FocusDeskView: View {
ScreenHeader(
eyebrow: "Account limits and local history",
title: "Usage",
subtitle: "Codex and Claude allowances, plus unified local history across agents. Live activity is in Agent activity.",
subtitle: "Codex and Claude allowances, plus unified local history across agents. Recorded runs and live signals are in Run telemetry.",
art: .telemetry,
hero: !compact,
compact: compact
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContextDaddy/TelemetryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct TelemetryView: View {
ScrollView {
VStack(alignment: .leading, spacing: compact ? 13 : 19) {
VStack(alignment: .leading, spacing: 6) {
Text("Agent activity").font(.title.bold())
Text("Run telemetry").font(.title.bold())
Text("See what ran, where tokens went, and what to review next.")
.foregroundStyle(DaddyTheme.muted)
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/ContextDaddyTests/ContextDaddyModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct ContextDaddyModelTests {
#expect(model.visibleSkills.isEmpty)
#expect(AppSection.allCases == [.overview, .skills, .memory, .projects, .telemetry])
#expect(AppSection.overview.label == "Usage")
#expect(AppSection.telemetry.label == "Agent activity")
#expect(AppSection.telemetry.label == "Run telemetry")
#expect(SkillsMode.ledger.rawValue == "Agent policies")
#expect(!model.evidenceOpen)
#expect(model.skillsMode == .library)
Expand Down
20 changes: 17 additions & 3 deletions scripts/release-contextdaddy.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ def sha256(path):
return digest.hexdigest()


def validate_release_source(root, expected_sha, binary):
actual = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root, text=True).strip()
if actual != expected_sha:
raise ValueError("--source-sha does not match the checked-out source")
status = subprocess.check_output(
["git", "status", "--porcelain", "--untracked-files=all"], cwd=root, text=True)
if status.strip():
raise ValueError("Release source must be clean; retain local changes and qualify a committed checkout")
sources = list((root / "Sources").rglob("*")) + [root / "Package.swift", root / "Package.resolved"]
if any(path.is_file() and path.stat().st_mtime_ns > binary.stat().st_mtime_ns for path in sources):
raise ValueError("Source changed after the binary; rebuild before packaging")


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--identity", required=True, help="Installed Developer ID Application identity")
Expand All @@ -42,16 +55,17 @@ def main():
args = parser.parse_args()
if not re.fullmatch(r"[0-9a-f]{40}", args.source_sha):
parser.error("--source-sha must be a full commit hash")
source_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
if source_sha != args.source_sha:
parser.error("--source-sha does not match the checked-out source")
api_auth = [args.notary_api_key, args.notary_key_id, args.notary_issuer_id]
if (args.notary_profile and any(api_auth)) or (any(api_auth) and not all(api_auth)) or not (args.notary_profile or all(api_auth)):
parser.error("Pass a Keychain profile or the complete API key, key ID, and issuer ID")

binary = ROOT / ".build/release/ContextDaddy"
if not binary.is_file():
parser.error("Build the release executable first with swift build -c release")
try:
validate_release_source(ROOT, args.source_sha, binary)
except ValueError as error:
parser.error(str(error))
if args.output.exists():
parser.error(f"Output already exists: {args.output}")
if not args.ccusage.is_file():
Expand Down
51 changes: 51 additions & 0 deletions scripts/test_release_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""A matching HEAD alone must not qualify dirty or stale release inputs."""
import importlib.util
from pathlib import Path
import os
import tempfile
import unittest
from unittest.mock import patch

spec = importlib.util.spec_from_file_location("release", Path(__file__).with_name("release-contextdaddy.py"))
release = importlib.util.module_from_spec(spec)
spec.loader.exec_module(release)
SHA = "a" * 40


class ReleaseSourceTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
(self.root / "Sources").mkdir()
self.source = self.root / "Sources/App.swift"
self.source.write_text("source")
self.binary = self.root / "ContextDaddy"
self.binary.write_bytes(b"built")
os.utime(self.source, (100, 100))
os.utime(self.binary, (200, 200))

def validate(self, actual=SHA, status=""):
with patch.object(release.subprocess, "check_output", side_effect=[actual + "\n", status]):
release.validate_release_source(self.root, SHA, self.binary)

def test_clean_matching_fresh_source_is_eligible(self):
self.validate()

def test_another_commit_is_rejected(self):
with self.assertRaisesRegex(ValueError, "checked-out source"):
self.validate(actual="b" * 40)

def test_modified_and_untracked_source_are_rejected(self):
for status in [" M Sources/App.swift", "?? Sources/New.swift"]:
with self.subTest(status=status), self.assertRaisesRegex(ValueError, "clean"):
self.validate(status=status)

def test_stale_binary_is_rejected(self):
os.utime(self.source, (300, 300))
with self.assertRaisesRegex(ValueError, "rebuild"):
self.validate()


if __name__ == "__main__":
unittest.main()
Loading