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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.16.0] - 2026-04-03

### Added

- **Catalog temporal discovery** — New `sort_by`, `limit`, and `filter_epoch` parameters on `oddkit_catalog`. `sort_by: "date"` returns articles sorted newest-first with full frontmatter metadata. `filter_epoch` provides server-side deterministic filtering. Addresses the "what's new?" discoverability gap — no new tools added, extending catalog as the discovery tool.

- **Full frontmatter indexing** — `IndexEntry` now stores complete parsed frontmatter on every document (previously cherry-picked 6 fields). Generic YAML parser replaces field-specific regex extraction. Enables `date`, `epoch`, `audience`, `tier`, `stability`, and all custom fields in metadata responses.

- **Proactive tool descriptions (E0007)** — Every tool description now includes a proactive usage hint: orient ("call at every context shift"), search ("search before claiming"), challenge ("challenge before encoding"), gate ("gate at every implicit transition"), validate ("validate before claiming done"), preflight ("preflight before every execution task").

- **Encode persistence warning** — Encode responses now include `persist_required: true` and `next_action` instructing the caller to save the output. Addresses the silent data loss pattern where operators assumed encode persisted.

- **Orient OLDC+H instruction** — Orient responses now include a proactive posture instruction: "Track OLDC+H continuously throughout this session." Includes artifact provenance gate: capture what happened (journal), what changed (summary), and what version — at every milestone, before every review, and before finalizing.

- **Validate artifact provenance gate** — When completion claims mention finalizing work (commit, merge, publish, submit, deliver, etc.), validate checks for session capture (OLDC+H), change summary, and version tracking. Domain-agnostic — applies to code, writing, planning, or any domain.

### Fixed

- **Branch ref extraction from canon_url** — `getZipUrl` was discarding the branch name from `raw.githubusercontent.com` URLs, always fetching `main.zip`. Branch-specific articles never appeared in canon_url overrides. Now correctly extracts `parts[2]` as the branch ref.

### Changed

- **Index version bumped to 2.3** — Reflects full frontmatter indexing, branch ref fix, and cache invalidation.

## [0.15.1] - 2026-03-14

### Added
Expand Down
62 changes: 62 additions & 0 deletions odd/ledger/journal/2026-04-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## 2026-04-03 Session — E0007 Implementation

> Epistemic ledger entry for the session that implemented E0007 proactive posture changes in the oddkit Worker: catalog temporal discovery, full frontmatter indexing, proactive tool descriptions, encode persistence warning, and the branch ref extraction bug fix.

### Session Context

Continuation of E0007 epoch work. Governance articles were written first on the klappy.dev repo (PR #72), then implementation moved to the oddkit Worker codebase. The session produced catalog metadata exposure, proactive tool description rewrites, encode response changes, and a critical bug fix in branch ref handling.

### Observations

**O1: The frontmatter was already parsed — it was just discarded.**
`parseFrontmatter` in the Worker cherry-picked 6 specific fields via regex and threw away everything else. The `date`, `epoch`, `audience`, `tier`, and `stability` fields were parsed but never stored on `IndexEntry`. The data was there; the code didn't keep it.

**O2: `getZipUrl` was silently fetching main for every branch override.**
The function received `raw.githubusercontent.com/owner/repo/branch` URLs, extracted `parts[0]` (owner) and `parts[1]` (repo), and discarded `parts[2]` (branch). Every `canon_url` branch override was downloading `main.zip`. Branch-specific articles never appeared. The bug was silent — the index looked plausible because baseline articles filled the gap.

**O3: INDEX_VERSION is the cache invalidation mechanism — must bump on schema changes.**
The Worker caches indexes keyed to `INDEX_VERSION + commit SHA`. Changing what fields are stored on `IndexEntry` without bumping INDEX_VERSION means stale cached indexes (without the new fields) are served until the commit SHA changes. Bumped 2.1 → 2.2 → 2.3 across the session.

### Learnings

**L1: Deterministic work belongs server-side, not in the LLM.**
Sort and filter are deterministic operations — cheap and correct on the server, slow and error-prone in the LLM. Metadata exposure enables LLM judgment (synthesis, recommendation). Server-side sort/filter prevents the LLM from burning tokens on arithmetic. Both are required.

**L2: Adding tools dilutes them all.**
New features should be params on existing tools, not new tools. Every MCP tool competes for attention in tool selection. Catalog was the natural home for temporal discovery because it's already the discovery tool.

**L3: "Cache issue" is a hypothesis, not a diagnosis.**
When articles didn't appear after the first deploy, the assumption was cache timing. The operator correctly pushed back: "It could be a code issue." It was. The `getZipUrl` bug was the root cause — not cache propagation delay.

### Decisions

**D1: Catalog gets sort_by, limit, filter_epoch — no new tools.**
Temporal discovery is params on the existing catalog tool. Rationale: adding tools dilutes the set.

**D2: Full frontmatter passthrough on IndexEntry.**
No cherry-picking. The `frontmatter` field stores the complete parsed YAML. Consumers decide what to use.

**D3: Generic YAML parser replaces field-specific regex.**
`parseFrontmatter` rewritten to capture all top-level YAML fields automatically. No more adding a regex each time a new field is needed.

**D4: Proactive session close should be a governance article.**
The operator's frustration at remembering to request journal/changelog/version bump is the E0007 signal. Written as `docs/oddkit/proactive/proactive-session-close.md`.

### Constraints

**C1: INDEX_VERSION must be bumped whenever IndexEntry schema changes.**
Stale cached indexes will serve old field shapes until the version key changes.

**C2: Governance articles before code changes — always.**
This session followed the pattern: IMPL-catalog-recent.md was written and committed before any oddkit code was modified.

### Handoffs

**H1: oddkit PR #67 ready for review and merge.**
Branch: `e0007-proactive-posture`. All changes typecheck clean. Preview verified at `e0007-proactive-posture-oddkit.klappy.workers.dev`.

**H2: klappy.dev PR #72 needs session close artifacts.**
Journal entry, version bump, proactive-session-close.md governance article.

**H3: Phase 4 (A/B testing) is next after merge.**
Both PRs need merge before testing proactive behavior in fresh sessions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oddkit",
"version": "0.15.1",
"version": "0.16.0",
"description": "Agent-first CLI for ODD-governed repos. Epistemic terrain rendering with portable baseline.",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion tests/cloudflare-production.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fi
# Test 4c: GET /mcp with SSE Accept header returns stream
echo ""
echo "Test 4c: GET /mcp with SSE Accept returns text/event-stream"
CONTENT_TYPE=$(curl -sf --max-time 30 "$WORKER_URL/mcp" -X GET \
CONTENT_TYPE=$(curl -sf --max-time 5 "$WORKER_URL/mcp" -X GET \
-H "Accept: text/event-stream" \
-D - -o /dev/null 2>&1 | grep -i "content-type" | head -1 || true)
if echo "$CONTENT_TYPE" | grep -qi "text/event-stream"; then
Expand Down
4 changes: 2 additions & 2 deletions workers/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion workers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oddkit-mcp-worker",
"version": "0.15.1",
"version": "0.16.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
28 changes: 20 additions & 8 deletions workers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ Use when:
canon_url: z.string().optional().describe("Optional GitHub repo URL for canon override."),
include_metadata: z.boolean().optional().describe("When true, search/get responses include a metadata object with full parsed frontmatter. Default: false."),
section: z.string().optional().describe("For action='get': extract only the named ## section from the document. Returns section content or available sections if not found."),
sort_by: z.enum(["date"]).optional().describe("For action='catalog': sort articles by frontmatter field. 'date' returns newest first with full metadata."),
limit: z.number().min(1).max(100).optional().describe("For action='catalog': max articles to return when sort_by is provided. Default: 10."),
filter_epoch: z.string().optional().describe("For action='catalog': filter to articles with this epoch value in frontmatter (e.g. 'E0007')."),
state: z.record(z.string(), z.unknown()).optional().describe("Optional client-side conversation state, passed back and forth."),
},
{
Expand All @@ -162,6 +165,9 @@ Use when:
canon_url: args.canon_url,
include_metadata: args.include_metadata,
section: args.section,
sort_by: args.sort_by,
limit: args.limit,
filter_epoch: args.filter_epoch,
state: args.state as any,
env,
});
Expand All @@ -180,7 +186,7 @@ Use when:
}> = [
{
name: "oddkit_orient",
description: "Assess a goal, idea, or situation against epistemic modes (exploration/planning/execution). Surfaces unresolved items, assumptions, and questions.",
description: "Assess a goal, idea, or situation against epistemic modes (exploration/planning/execution). Surfaces unresolved items, assumptions, and questions. Call proactively whenever context shifts, not just at session start.",
action: "orient",
schema: {
input: z.string().describe("A goal, idea, or situation description to orient against."),
Expand All @@ -190,7 +196,7 @@ Use when:
},
{
name: "oddkit_challenge",
description: "Pressure-test a claim, assumption, or proposal against canon constraints. Surfaces tensions, missing evidence, and contradictions.",
description: "Pressure-test a claim, assumption, or proposal against canon constraints. Surfaces tensions, missing evidence, and contradictions. Challenge proactively before encoding consequential decisions.",
action: "challenge",
schema: {
input: z.string().describe("A claim, assumption, or proposal to challenge."),
Expand All @@ -201,7 +207,7 @@ Use when:
},
{
name: "oddkit_gate",
description: "Check transition prerequisites before changing epistemic modes. Validates readiness and blocks premature convergence.",
description: "Check transition prerequisites before changing epistemic modes. Validates readiness and blocks premature convergence. Gate at every implicit mode transition, not just formal ones.",
action: "gate",
schema: {
input: z.string().describe("The proposed transition (e.g., 'ready to build', 'moving to planning')."),
Expand All @@ -212,7 +218,7 @@ Use when:
},
{
name: "oddkit_encode",
description: "Structure a decision, insight, or boundary as a durable record. Assesses quality and suggests improvements.",
description: "Structure a decision, insight, or boundary as a durable record. IMPORTANT: This tool returns the structured artifact in the response — it does NOT persist or save it. The caller must save the output to storage. Standard artifact types: Observations (O), Learnings (L), Decisions (D), Constraints (C), Handoffs (H) — OLDC+H. Track OLDC+H continuously — encode what the user shared, encode what you did. Persist at natural breakpoints.",
action: "encode",
schema: {
input: z.string().describe("A decision, insight, or boundary to capture."),
Expand All @@ -223,7 +229,7 @@ Use when:
},
{
name: "oddkit_search",
description: "Search canon and baseline docs by natural language query or tags. Returns ranked results with citations and excerpts.",
description: "Search canon and baseline docs by natural language query or tags. Returns ranked results with citations and excerpts. Search before claiming — not just when asked.",
action: "search",
schema: {
input: z.string().describe("Natural language query or tags to search for."),
Expand All @@ -246,16 +252,19 @@ Use when:
},
{
name: "oddkit_catalog",
description: "Lists available documentation with categories, counts, and start-here suggestions.",
description: "Lists available documentation with categories, counts, and start-here suggestions. Supports temporal discovery: use sort_by='date' to get recent articles with full frontmatter metadata.",
action: "catalog",
schema: {
canon_url: z.string().optional().describe("Optional: GitHub repo URL for canon override."),
sort_by: z.enum(["date"]).optional().describe("Sort articles by frontmatter field. 'date' returns newest first with full metadata."),
limit: z.number().min(1).max(100).optional().describe("Max articles to return when sort_by is provided. Default: 10."),
filter_epoch: z.string().optional().describe("Filter to articles with this epoch value in frontmatter (e.g. 'E0007')."),
},
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
{
name: "oddkit_validate",
description: "Validates completion claims against required artifacts. Returns VERIFIED or NEEDS_ARTIFACTS.",
description: "Validates completion claims against required artifacts. Returns VERIFIED or NEEDS_ARTIFACTS. Validate proactively before claiming any task complete.",
action: "validate",
schema: {
input: z.string().describe("The completion claim with artifact references."),
Expand All @@ -264,7 +273,7 @@ Use when:
},
{
name: "oddkit_preflight",
description: "Pre-implementation check. Returns relevant docs, constraints, definition of done, and pitfalls.",
description: "Pre-implementation check. Returns relevant docs, constraints, definition of done, and pitfalls. Preflight before any execution that produces an artifact.",
action: "preflight",
schema: {
input: z.string().describe("Description of what you're about to implement."),
Expand Down Expand Up @@ -307,6 +316,9 @@ Use when:
canon_url: args.canon_url as string | undefined,
include_metadata: args.include_metadata as boolean | undefined,
section: args.section as string | undefined,
sort_by: args.sort_by as string | undefined,
limit: args.limit as number | undefined,
filter_epoch: args.filter_epoch as string | undefined,
env,
});
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
Expand Down
Loading
Loading