# `@aethrekh/pisces-core`

Domain-agnostic learning engine for [Pi Coding Agent](https://github.com/earendil-works/pi). Provides rubric-driven evaluation, integrity enforcement, attempt history, and a teaching persona that adapts to any subject — not just CS.

This package is the foundation. Domain packages (`pisces-cs`, `pisces-sysdesign`, etc.) sit on top of it, contributing subject-specific rubrics without modifying the engine itself.

---

## What it does

| Capability | How |
|---|---|
| Teaching persona | `SYSTEM.md` injected as the Pisces AI identity on every session |
| Workspace gating | Skills and themes only activate inside a `.pisces`-marked directory |
| Attempt evaluation | `/attempt` command feeds submitted work + rubric + gap history to the model |
| Integrity enforcement | `on_input` extension blocks code-generation requests during graded evaluations |
| Gap memory | Attempt history persists across sessions; recurring weaknesses surface automatically |
| Burnout nudges | Session length tracking with configurable break reminders |
| Custom input bar | Replaces Pi's default prompt with a framed bar showing workspace state and session cost |

---

## Installation

```bash
pi install npm:@aethrekh/pisces-core
```

Alternatively, for local development:

```bash
pi install /path/to/packages/core
```

The `postinstall` script copies `SYSTEM.md` to `~/.pi/agent/APPEND_SYSTEM.md` (persona injection) and installs the `pisces-editorial-noir` theme to `~/.pi/agent/themes/`.

---

## Quick start

```bash
# 1. Install (or add to local Pi settings)
pi install npm:@aethrekh/pisces-core
pi install npm:@aethrekh/pisces-cli   # workspace commands (/pisces)

# 2. Navigate to your workspace and activate
cd ~/my-project
pi
/pisces --activate

# 3. Submit work for evaluation
/attempt function twoSum(nums, target) { ... }
```

---

## Workspace activation

Skills and the `/attempt` command are **off by default**. They activate only when Pi is started from a directory that has a `.pisces` marker file at or above it.

```
~/my-project/           ← .pisces lives here
├── module-1/
│   └── lab1/           ← start Pi here; workspace-gate walks up and activates
└── module-2/
```

Workspace state is detected once at `resources_discover` (before session start) and cached for the session. Pi's working directory is fixed at startup — a mid-session `cd` does not re-trigger detection.

Use [`@aethrekh/pisces-cli`](../cli) for the `/pisces` command that creates and removes `.pisces` markers.

---

## `/attempt` command

The primary user-facing command. Submits work to the model for rubric-driven evaluation.

```
/attempt <your code or text>
/attempt --type essay <your text>
/attempt --goal "implement binary search" <your code>
```

**Flags:**

| Flag | Description |
|---|---|
| `--type code\|essay\|generic` | Override the inferred attempt type (inferred from content by default) |
| `--goal "description"` | Describe what you were trying to achieve (stored in attempt history) |

**What happens:**

1. Workspace state is checked — redirects to `/pisces --activate` if no `.pisces` found.
2. The active rubric is loaded from `.pisces/rubric.json` (domain rubric) or the built-in `generic-attempt` rubric.
3. Recent gap history is fetched from `~/.pisces/attempt-history.json`.
4. Rubric + gaps + submitted content are injected into the conversation.
5. The model evaluates and scores against the rubric criteria.
6. The attempt record (without the score — scoring is model-side) is written to history.

---

## Rubric resolution

The calibration engine searches for a rubric in this order:

| Priority | Path | Source |
|---|---|---|
| 1 | `.pisces/rubric.json` (workspace-relative) | Dropped by a domain package on activation |
| 2 | Built-in `generic-attempt/rubric.json` | Bundled with `pisces-core` |

When no domain rubric is present, the built-in five-criterion rubric is used:

| Criterion | Weight |
|---|---|
| Functional Correctness & Robustness | 30% |
| Conceptual Understanding | 25% |
| Code Quality & Readability | 20% |
| Design, Modularity & Algorithms | 15% |
| Documentation & Reflection | 10% |

---

## Extensions

Five extensions run automatically. All are gated behind workspace activation except where noted.

### `workspace-gate` — `resources_discover`

Controls which skill and theme paths Pi discovers at session start. Returns `skillPaths` pointing at the bundled skills directory only when a `.pisces` marker is found. This is the sole gating mechanism — no `.pisces`, no skills.

### `attempt-capture` — `session_start`

Registers the `/attempt` Pi command. See [the command section](#attempt-command) above.

### `integrity-guard` — `on_input`

Monitors every message for academic integrity risk. Two guard layers:

1. **General patterns** — phrases like "write the complete solution for my assignment" or "do my homework" trigger a redirect to guided-learning mode.
2. **Graded-skill routing** — within a `gradedSkills` session (e.g. immediately after `/attempt`), code-generation requests like "fix this" or "rewrite this" are blocked.

Risk levels: `none` → `low` → `medium` → `high`. Only `high` produces a hard redirect; lower levels inject a warning comment into the context.

The active `safeSkills` and `gradedSkills` lists come from the loaded `RubricSpec.integrityProfile`. Domain packages can override these via `setIntegrityProfile()`.

### `progress-tracker` — `on_session_end`

Tracks session duration and skill usage. At session end:
- If `productivity.burnout_nudges` is enabled and the session exceeded `session_warning_minutes`, surfaces a break reminder.
- If `productivity.weekly_summary` is enabled, shows a compact weekly activity table.

### `input-revamp` — `session_start`

Replaces Pi's default input bar with a framed prompt bar:

```
╭─ 🐠 Pisces · active ────────────────────────── 5.2% · $0.015 · 8.3K out ─╮
│ › your message here                                                         │
╰──────────────────────────────────────────────── T3 · $0.008 · OUT 4.1K ───╯
```

Features: typing-speed whitening on the border, animated thinking VU-meter, submit flash, real-time session cost and token count.

---

## Configuration

Config is loaded via a priority chain — first match wins:

1. `.pisces.json` in the current working directory
2. `~/.pi/pisces.json`
3. `~/.config/pisces/config.json`
4. Built-in defaults

```json
{
  "student": {
    "name": "Alex",
    "year_of_study": 2,
    "timezone": "America/New_York"
  },
  "explanations": {
    "default_depth": "intermediate",
    "prefer_visuals": true,
    "use_analogies": true
  },
  "integrity": {
    "enabled": true,
    "strictness": "balanced"
  },
  "productivity": {
    "burnout_nudges": true,
    "session_warning_minutes": 180,
    "weekly_summary": true
  }
}
```

**Full schema:** [`config/schema.json`](config/schema.json)

**Defaults:** [`config/defaults.json`](config/defaults.json)

### Config fields

#### `student`
| Field | Type | Default | Description |
|---|---|---|---|
| `name` | string | — | First name for personalised greetings |
| `year_of_study` | integer (1–8) | `1` | Year of study for depth calibration |
| `timezone` | string | — | Timezone string, e.g. `America/New_York` |

#### `explanations`
| Field | Type | Default | Description |
|---|---|---|---|
| `default_depth` | `beginner\|intermediate\|advanced` | `intermediate` | Explanation depth when not specified |
| `prefer_visuals` | boolean | `true` | Prefer diagrams for structural concepts |
| `use_analogies` | boolean | `true` | Include real-world analogies |

#### `integrity`
| Field | Type | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Enable the integrity guard |
| `strictness` | `strict\|balanced\|relaxed` | `balanced` | Aggressiveness of integrity pattern matching |

#### `productivity`
| Field | Type | Default | Description |
|---|---|---|---|
| `burnout_nudges` | boolean | `true` | Show break reminders after long sessions |
| `session_warning_minutes` | integer | `180` | Minutes before a break reminder appears |
| `weekly_summary` | boolean | `true` | Show a weekly activity table at session end |

---

## Attempt history

Attempt records are stored in `~/.pisces/attempt-history.json`. The file is capped at 200 records (oldest pruned first). Each record:

```json
{
  "id": "m5ix2kfz9",
  "timestamp": "2026-07-03T10:22:00.000Z",
  "skillName": "attempt",
  "attemptType": "code",
  "goal": "implement binary search tree",
  "gaps": [],
  "strengths": [],
  "score": null
}
```

The `gaps` and `strengths` arrays are populated after the model evaluates the attempt. On subsequent `/attempt` invocations, a compact deduplicated summary of unresolved gaps is injected into the conversation so the model can check for recurring weaknesses.

---

## Public API (for domain packages)

`@aethrekh/pisces-core` exports the following for use by domain packages and `pisces-cli`:

```ts
import {
  // Workspace detection
  findWorkspace,       // (cwd: string) => WorkspaceResult
  getWorkspaceState,   // () => WorkspaceResult  (cached singleton)
  syncWorkspaceState,  // (cwd: string) => WorkspaceResult  (update cache)

  // Config
  loadConfig,          // () => PiscesConfig
  deepMerge,           // (base, override) => merged config
  getConfigSearchPaths, // () => string[]

  // Lifecycle hooks (for custom integrations)
  onLoad,
  onStartup,
  onDirectoryChange,
  onSkillCall,
  onMidSession,
  onSessionEnd,
  createSessionState,

  // Metadata
  PACKAGE_VERSION,
  SKILLS,
  isValidSkill,
} from "@aethrekh/pisces-core";
```

---

## For domain package authors

A domain package extends Pisces Core by:

1. **Providing a rubric** — ship a `rubric.json` validated against `@aethrekh/rubric-schema`. On workspace activation, write it to `.pisces/rubric.json` in the learner's workspace root.
2. **Registering its skills** — declare additional skills in `pi-package.yaml`. Skills are gated by `workspace-gate.ts` automatically via `resources_discover`.
3. **Optionally setting the integrity profile** — call `setIntegrityProfile({ safeSkills, gradedSkills })` from `pisces-core/integrity-guard` to override the default routing rules.

Pisces Core resolves the domain rubric at `/attempt` time with no further configuration needed.

---

## Package scripts

```bash
pnpm build            # Compile src/ → dist/ and scripts/ → dist/scripts/
pnpm test             # Run Jest test suite
pnpm test:coverage    # With coverage (70% branch / 80% line thresholds)
pnpm typecheck        # Type-check without emit
pnpm lint             # ESLint over src/ and scripts/
pnpm check            # typecheck + lint + test:coverage (CI gate)
pnpm validate         # Validate package.json, pi-package.yaml, and skill frontmatter
pnpm pack             # Bundle into dist/pack/ and dist/*.tar.gz
pnpm release          # Cut a release: bump version, update CHANGELOG, tag, pack
```

---

## Architecture

```
packages/core/
├── SYSTEM.md                        # Pisces teaching persona (Pi system prompt)
├── pi-package.yaml                  # Pi runtime manifest
├── config/
│   ├── schema.json                  # Config schema
│   └── defaults.json                # Default values
└── src/
    ├── index.ts                     # Public API + lifecycle hooks
    ├── pi-adapter.ts                # Isolation layer for all Pi SDK calls
    ├── workspace-detector.ts        # .pisces discovery + singleton cache
    ├── attempt-capture.ts           # /attempt Pi command
    ├── calibration-engine.ts        # Rubric loading + feedback context builder
    ├── correction-memory.ts         # Attempt history read/write
    ├── memory-policy.ts             # Write/inject validation (anti-softening)
    ├── postinstall.ts               # Copies SYSTEM.md + theme on install
    ├── extensions/
    │   ├── workspace-gate.ts        # resources_discover gating
    │   ├── integrity-guard.ts       # on_input pattern matching
    │   ├── progress-tracker.ts      # on_session_end burnout nudges
    │   ├── input-revamp.ts          # session_start custom input bar
    │   └── lib/
    │       ├── config.ts            # Config loader (deepMerge, search paths)
    │       └── ui.ts                # Shared UI primitives (row, sep, skillsList)
    ├── skills/
    │   └── generic-attempt/
    │       ├── SKILL.md             # Evaluation mode prompt
    │       └── rubric.json          # Built-in 5-criterion rubric
    └── themes/
        └── pisces-editorial-noir.json
```

---

## License

MIT
