Hybrid: the classic UserPromptSubmit hook runs everywhere; the module loads only where function hooks are on (Claude Code >= 2.1.287, or…

Current product version: v0.0.1 (locked until the complete production-capable product gate)
Implementation state: narrow independently earned backend/API slices after the 2026-09-17 purge. Phase 0A remains qualified; the former 0B Parties implementation is retired history.
The repository codename is not the public product identity. Every deployed presentation obtains its name, legal identity, theme and links from validated branding configuration; the checked-in runtime has no codename branding fallback. Earlier code and retired phase records remain in Git history as evidence and context, not current implementation authority.
README.IMPLEMENTATION.md — current implementation truth.docs/product/PRODUCT_IDENTITY_AND_VERSIONING.md — codename, public identity and locked-version contract.docs/decisions/CURRENT_IMPLEMENTATION_PURGE_2026-09-17.md — purge decision and removed inventory.docs/implementation/PHASE_GATE_PRODUCTION_HONESTY.md — scope and quality contract.docs/implementation/PHASE_GATE_EVIDENCE_REGRESSION_AND_TRANSITIONS.md — evidence permanence and requalification contract.docs/implementation/phases/phase-0/0A_BASELINE_STATUS.md — qualified reset baseline.docs/review/APPLICATION_BASELINE_IMPLEMENTATION_SOURCE_REVIEW.md — proven-source routing for future implementation.docs/domain/BUSINESS_TERMS.md, docs/decisions/CURRENT_DECISIONS.md, and OPEN_DECISIONS.md — accepted and unresolved product meaning.focused canonical owner
↓
accepted/current decision record
↓
current implementation/gate record
↓
historical review/source-study/branch/MR material
Repository state proves what is implemented. Architecture documents can define future ownership and accepted direction without implying that a project or runtime exists.
production projects: 12
test projects: 12
executable hosts: 2
solution/build/test contract: present
active runtime responsibilities: public application bootstrap; classified OpenAPI v1 description; JWT access-token validation; authenticated account and tenant membership resolution; Finbuckle route-candidate resolution; pinned-model OpenFGA workspace/customer/order authorization; account/tenancy/customer/order-draft PostgreSQL persistence with tenant RLS; liveness and bounded dependency readiness; Autofac root composition and dormant tenant-keyed runtime mechanics; ordered one-shot DB migration
active host-neutral responsibilities: bounded feature graph validation and effective-selection compilation; immutable customer organization/program context; optionally attributed priced order-draft creation, read, browse and abandonment with caller-scoped semantic idempotency
BLOCKED: none
The deleted PartyKind enum and Parties implementation are not current implementation. The current solution/build/test files belong to independently earned ApplicationProfiles, Branding, IdentityAccess, Tenancy, Customers, Orders, CoreApi and DbMigrator slices and do not revive the retired 0B shape. Customer organization/program association on a draft is attribution, not account billing or settlement authority. The tenant-keyed Autofac registry is internal mechanics; no production request acquires it, and durable Tenant Application Profile authority remains absent.
global.json).Scope is a choice; honesty is not.
real useful responsibility
↓
inspect applicable proven implementations
↓
declare production intent and exact scope
↓
NOT_INTRODUCED / PRODUCTION_HONEST / BLOCKED
↓
implement the smallest useful production-honest slice
↓
prove claims with falsifiable evidence
↓
retain recurring regression guards
↓
qualify only with BLOCKED = none
Production-honest does not mean writing every mechanism from scratch. Before custom infrastructure, inspect the source review and record whether a focused dependency, bounded source adaptation, reusable tests/algorithms, or reference-only decision fits the active responsibility. The repository keeps ownership of its business meaning, authority, security, compatibility, and evidence.
Phase 0A is the current qualified baseline. The former 0B qualification proved properties of code that has since been deliberately purged, so those claims and CI results are historical and no longer describe the repository.
The repository-owned verification entry point is ./eng/verify.sh. It restores, verifies formatting, builds in Release configuration and runs the complete test suite, including real PostgreSQL Testcontainers integration tests. .github/workflows/verify.yml is the thin GitHub Actions wrapper around the same command. A workflow definition is not evidence that a remote run passed; report remote CI evidence only after inspecting that run.
Phase 0 and the already-concrete Phase 1 trust boundary retain architecture direction. Phase 2–10 remain direction-only NOT_INTRODUCED planning until real work earns detail.
The product version is exactly v0.0.1 and remains locked there until the complete product is explicitly qualified as capable of its accepted real-production responsibilities. Git revisions identify builds and changes without creating alternate product versions. /api/v1 and similar identifiers version a contract family; they are not the product version. Historical documents may retain the version that identified their original decision or qualification event.
hooks/register.ts 62 lines1import type { Hook, Register } from 'claude-code'
2
3import { show, statusLine, type Host } from './host'
4import { newCounters, STATUS_PATH, statusText, type Counters } from './status'
5
6type Dollar = Parameters<Hook<'session.start'>>[0]
7
8/** What one session keeps: the counters for the status file, the project root and the start time. */
9type Session = { readonly counters: Counters; root?: string; startedMs: number }
10
11/** A top-level function declaration, so `import type { Hook, Register } from 'claude-code'
12
13import { show, statusLine, type Host } from './host'
14 may be passed to it; a refused write is ignored (the status file is a courtesy). */
15async function flush($: Dollar, s: Session): Promise<void> {
16 if (s.root === undefined) return
17 try {
18 await $.fs.write(`${s.root}/${STATUS_PATH}`, statusText(s.counters, await $.clock.now(), s.startedMs))
19 } catch {
20 // never fail a hook over the status file
21 }
22}
23
24/**
25 * A mod scaffolded by ruflo-plugin-creator (ADR-404 patterns):
26 * - observe: `const r = await next(e); …; return r` on tool.call;
27 * - every `$` call literal, wrapped in a host adapter, refusal-tolerant;
28 * - namespaced command (`my-mod-status`), never a built-in's name;
29 * - the classic fallback handed over through MY_MOD_ACTIVE;
30 * - a status file (status.ts) the console reads: keep `calls` a number.
31 * Hot reload re-runs register and session.start: keep anything that must
32 * survive a reload in `$.store` or `$.state`, not in these variables.
33 */
34export const register: Register = (on, options) => {
35 const showLine = options.statusLine !== false
36 const s: Session = { counters: newCounters(), startedMs: 0 }
37
38 on('session.start', async ($, e, next) => {
39 await $.env.set('MY_MOD_ACTIVE', '1').catch(() => undefined)
40 await $.command.register({ name: 'my-mod-status', description: 'my-mod: tool calls this session' }).catch(() => undefined)
41 try {
42 s.root = (await $.session.root()) as string | undefined
43 s.startedMs = await $.clock.now()
44 } catch {
45 // no project root: the mod still runs, it just writes no status file
46 }
47 await flush($, s)
48 return next(e)
49 })
50
51 on('tool.call', async ($, e, next) => {
52 const result = await next(e)
53 s.counters.calls++
54 const host: Host = { status: text => $.ui.status(text) }
55 if (showLine) show(host, s.counters.calls)
56 await flush($, s)
57 return result
58 })
59
60 on('command.run', { command: 'my-mod-status' }, () => ({ text: statusLine(s.counters.calls) }))
61}
62hooks/host.ts 23 lines1/**
2 * What the pure logic needs from the engine, as plain functions. The engine
3 * reads what a module calls off its source, so `$` never leaves a hook: the
4 * hook builds this from literal `$.noun.method(...)` calls (register.ts).
5 */
6export type Host = {
7 readonly status: (text: string | undefined) => void
8}
9
10/** The tool-call tally and the line it shows; pure, testable without the engine. */
11export function statusLine(calls: number): string {
12 return `my-mod · ${calls} tool call${calls === 1 ? '' : 's'}`
13}
14
15/** Draws through the host; a refused `ui.status` (an admin may withhold it) is ignored. */
16export function show(host: Host, calls: number): void {
17 try {
18 host.status(statusLine(calls))
19 } catch {
20 // never fail a hook over the status line
21 }
22}
23hooks/status.ts 41 lines1/**
2 * The status contract (ADR-446): the file the console's Mods section reads. It scans `.claude-flow` for `<name>-mod` folders (the folder name
3 * must match /^[a-z0-9][a-z0-9-]{0,40}-mod$/) and keeps a file only when `version` is 1. Anything else you write is ignored by the console.
4 */
5export const STATUS_PATH = '.claude-flow/my-mod/status.json'
6
7/** What the console reads; `calls` is a number (not a per-tool object), `lastDenied` is a short reason, left out until the mod refuses something. */
8export type ModStatus = {
9 version: 1
10 updatedMs: number
11 startedMs: number
12 modVersion: string
13 summary: string
14 guard: boolean
15 calls: number
16 blocked: number
17 lastDenied?: string
18}
19
20/** The counters the mod keeps for one session. This template guards nothing, so `blocked` stays 0 and `guard` is false. */
21export type Counters = { calls: number; blocked: number; lastDenied?: string }
22
23export const newCounters = (): Counters => ({ calls: 0, blocked: 0 })
24
25/** Builds the document; pure, so a test can assert the shape without the engine. */
26export function modStatus(c: Counters, nowMs: number, startedMs: number): ModStatus {
27 return {
28 version: 1,
29 updatedMs: nowMs,
30 startedMs,
31 modVersion: '0.1.0',
32 summary: 'counts tool calls; guards nothing',
33 guard: false,
34 calls: c.calls,
35 blocked: c.blocked,
36 ...(c.lastDenied !== undefined && { lastDenied: c.lastDenied }),
37 }
38}
39
40export const statusText = (c: Counters, nowMs: number, startedMs: number): string => `${JSON.stringify(modStatus(c, nowMs, startedMs), null, 2)}\n`
41