# slice

`ai.stravica.ops.slice`; operator profile slice generator.

Produces a compact "boot slice" of the operator's profile -- a short markdown
document that loads into the PA's context at every session start, in place of
the full ~18 KB `operator/profile/profile.md`. The full profile remains the
source of truth; the slice is a fresh snapshot, regenerated periodically (a
daily cron is the intended cadence; wired separately by the scheduler).

Architecturally the slice replaces a chronic over-load on boot: the canonical
profile, accumulated rules, and per-domain context can amount to 80--120 KB
of context every session start, before the operator's first message. The
slice trades freshness (stale by up to 24h is fine) for cheapness (~1--2 KB
on boot).

## Status

Implemented: `generate`, `generate --dry-run`, `--help`.

Bundled templates:

- `dave-boot`; the original use case. Six-section operator-boot card aimed
  at single-operator PA instances like Dave. Sections: Operator basics,
  Mission framing, Active relationships, Operational constraints, Tools at
  your disposal, Live state. Post-generate lint enforces the 2026-09-03
  contract (w-2026-07-15-012 / w-2026-09-04-dave-003).

Custom templates are loaded with `--template <path>`.

## Install / invoke

Called via the bin shim:

```
slice generate dave-boot \
  --profile /path/to/operator/profile/profile.md \
  --output  /path/to/operator/profile/slices/dave-boot.md
```

Optional inputs:

- `--learned-rules <path>`; `learned-rules.md` (~50 KB), drives the
  "load-bearing rules" section.
- `--observations <dir>`; a directory of recent observation `.md` files;
  the generator filters to mtime within the last 21 days (mtimes older than
  that are dropped). Files prefixed `_` (e.g. `_archive.md`) are always
  skipped.

## Commands

### `slice generate <name>`

1. Load inputs (`profile.md` is required; `learned-rules.md` and
   observations dir are optional).
2. Load the bundled prompt template at
   `src/slice/prompts/<name>.md` (or `--template <path>` if supplied) and
   substitute the assembled inputs into the `{{INPUTS}}` placeholder.
3. Invoke `claude --print --permission-mode bypassPermissions
   --no-session-persistence --name slice-<ts>` with the assembled prompt as
   the positional argument. The prompt is handed via `child_process.spawn`
   with `shell: false`; no shell interpolation, the inputs are safe even if
   they contain shell metacharacters.
4. Validate the response against the required-headers contract (six
   sections: Operator basics, Mission framing, Active relationships,
   Operational constraints, Tools at your disposal, Live state). A response
   missing any required header is treated as a generator failure (exit 2)
   and no file is written. The 2026-09-03 rewrite (w-2026-07-15-012)
   retired the Active projects and Load-bearing rules sections: live boot
   state is injected by the `boot-context` hook, and operational rules live
   in `operator/knowledge/operator-interface/operational-constraints.md`.
5. Validate the size against the 2 KB soft budget. Over-budget responses
   produce a stderr warning; the slice is still written when the byte
   count sits inside the lint hard ceiling (`SIZE_LINT_HARD_BYTES`, 3000
   B). Above that ceiling the post-generate lint refuses the write.
6. Run the template-specific post-generate lint (dave-boot: names the
   platform package `@stravica-ai/digital-operator-pa`, points at the
   constraints doc, carries no `## Active projects` or `## In-flight work`
   block, size within the hard ceiling). A lint failure refuses the write
   with exit 2 so a defective regeneration cannot clobber the good slice
   in place. Pass `--no-lint` to bypass. Custom templates loaded with
   `--template <path>` have no template-specific checks and always
   proceed to write. w-2026-09-04-dave-003.
7. Write the response to the configured `--output` path. Parent directories
   are created if missing.

#### Flags

- `--profile <path>`         Path to `profile.md` (required).
- `--learned-rules <path>`   Optional.
- `--observations <dir>`     Optional.
- `--output <path>`          Where to write the slice (required).
- `--template <path>`        Override the bundled template path.
- `--runs-log <path>`        Append one JSON audit line per invocation to
                             this file. Default-off. See **Audit log** below.
- `--dry-run`                Build the prompt and report; skip the claude
                             call and the file write.
- `--no-lint`                Bypass the post-generate template-specific
                             lint. See step 6 above.
- `-h`, `--help`             Show usage.

### Exit codes

- `0`  ok; slice generated, validated, written.
- `1`  usage error (missing/extra flags, missing slice name or template).
- `2`  operational failure (missing input file, missing template,
        validation failure on the generator's response).
- `3`  claude transport failure (claude binary exited non-zero).

### Audit log (`--runs-log`)

When `--runs-log <path>` is passed, each invocation appends one JSON line to
the file in the canonical shape consumed by the `tasks` health monitor:

```json
{"ts":"2026-05-21T03:00:01Z","status":"ok","exitCode":0,"durationSec":42,"dryRun":false,"outputBytes":1843}
```

Status vocabulary (closed):

- `ok`; slice generated, validated, written.
- `noop-dry-run`; `--dry-run` path; no claude call, no file write.
- `fail-claude`; claude exited non-zero (transport failure, exit 3).
- `fail-validation`; claude returned content missing required headers (exit 2).
- `fail-lint`: post-generate template lint refused the write (exit 2). The
  regeneration was blocked from clobbering the good slice in place.
  w-2026-09-04-dave-003.
- `fail-prereq`; input loading failed (missing profile / observations / template, exit 2).
- `fail-write`; writing the slice output file failed (permissions, ENOSPC, etc., exit 2).
- `fail-usage`; early arg-validation failed (missing required option, exit 1).
- `fail-unexpected`; any other thrown exception (defensive catch-all so
  cron failures never disappear silently).

`reason` is omitted on `ok` / `noop-dry-run`. `outputBytes` is `null` on
paths that didn't produce a file. Every non-help invocation appends
exactly one audit line; including the throw paths above; so the
freshness monitor can distinguish "tool exploded" from "tool was never
invoked".

When `slice` runs on a schedule (e.g. `30 3 * * *` for nightly slice
regeneration), the `--runs-log` path becomes the freshness signal for the
task; point it at a per-task `runs.jsonl` (one task per file; the
monitor uses `tail -n 1`). The default is **off** so human hand-invocations
don't pollute the freshness signal of the scheduled run.

Audit writes are best-effort: a failed audit append (missing dir, ENOSPC,
etc.) is logged to stderr but does NOT change the process exit code. A
lost audit line is preferable to misclassifying a successful slice
regeneration as a transport failure.

## Output contract

A single markdown file at the `--output` path. Plain markdown, no
front-matter, no JSON envelope. The PA's boot rule reads it the same way it
reads the full profile.

Soft target: under 2 KB / ~30 lines. Hard requirements: the six section
headers must be present, and (for the bundled `dave-boot` template) the
post-generate lint contract in step 6.

## Design notes

- The Claude invocation is funnelled through the same chokepoint runner
  used by `distill` (`packages/core/src/distill/runner.mjs`). One place
  in core/src spawns external processes.
- The prompt template is an `.md` file with a `{{INPUTS}}` placeholder, not
  a hardcoded string, so an operator can drop in a custom slice template
  (e.g. `--template ./my-slice.md`) without forking the package.
- No git, no commit, no push; `slice` is a pure file writer. The per-host
  `sync` tool picks up any changes the slice produced on its next tick.
- Tests use a fake runner; no real claude calls in CI.
