@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
198 lines (197 loc) • 9.32 kB
TypeScript
import { type PhrenResult } from "../shared.js";
import { type ProjectConfigOverrides } from "../project-config.js";
/** @internal Exported for tests. */
export declare const MAX_QUEUE_ENTRY_LENGTH = 500;
export interface RetentionPolicy {
schemaVersion?: number;
ttlDays: number;
retentionDays: number;
autoAcceptThreshold: number;
minInjectConfidence: number;
decay: {
d30: number;
d60: number;
d90: number;
d120: number;
};
}
export interface WorkflowPolicy {
schemaVersion?: number;
lowConfidenceThreshold: number;
riskySections: Array<"Review" | "Stale" | "Conflicts">;
taskMode: "off" | "manual" | "suggest" | "auto";
findingSensitivity: "minimal" | "conservative" | "balanced" | "aggressive";
}
export interface IndexPolicy {
schemaVersion?: number;
includeGlobs: string[];
excludeGlobs: string[];
includeHidden: boolean;
}
/**
* Outcome of the push leg of a sync.
*
* `saved-local` means "committed, nothing to push or no remote" — a success.
* It used to double as the report for a *failed* push, which is how a store
* could go two months without syncing while every Stop hook looked healthy.
* Failures now get their own values:
*
* - `pull-failed` - the fetch/merge leg failed, so push never ran
* - `push-failed` — push was attempted and rejected
* - `unrelated-histories` — local and remote share no merge base; no amount of
* retrying will fix it, so it is called out by name
*/
export type PushStatus = "saved-local" | "saved-pushed" | "no-upstream" | "pull-failed" | "push-failed" | "unrelated-histories" | "error";
export declare const PUSH_STATUSES: readonly PushStatus[];
/** Push statuses that mean the store is NOT in sync with its remote. */
export declare const FAILED_PUSH_STATUSES: ReadonlySet<PushStatus>;
export type AutoSaveStatus = "clean" | "saved-local" | "saved-pushed" | "no-upstream" | "sync-failed" | "error";
export declare const AUTO_SAVE_STATUSES: readonly AutoSaveStatus[];
export interface RuntimeHealth {
schemaVersion?: number;
lastSessionStartAt?: string;
lastPromptAt?: string;
lastStopAt?: string;
lastAutoSave?: {
at: string;
status: AutoSaveStatus;
detail?: string;
};
lastGovernance?: {
at: string;
status: "ok" | "error";
detail: string;
};
lastSync?: {
lastPullAt?: string;
lastPullStatus?: "ok" | "error";
lastPullDetail?: string;
lastSuccessfulPullAt?: string;
lastPushAt?: string;
lastPushStatus?: PushStatus;
lastPushDetail?: string;
unsyncedCommits?: number;
/**
* Consecutive sync attempts that failed to reach the remote. Reset to 0 on
* any successful push. This is what turns "one flaky push" into "this store
* has been broken for weeks" without needing to keep a history.
*/
consecutiveFailures?: number;
/** Last time a push actually reached the remote. */
lastSuccessfulPushAt?: string;
/** Commits ahead of and behind the upstream tracking ref at the last sync outcome. */
ahead?: number;
behind?: number;
/** The last failure of a store sync in plain words, cleared by the next full success. */
lastError?: string;
lastErrorAt?: string;
};
}
export type SyncStatus = NonNullable<RuntimeHealth["lastSync"]>;
export interface BuildSyncStatusOpts {
now: string;
pushStatus: SyncStatus["lastPushStatus"];
pushDetail?: string;
pullAt?: string;
pullStatus?: SyncStatus["lastPullStatus"];
pullDetail?: string;
successfulPullAt?: string;
unsyncedCommits?: number;
consecutiveFailures?: number;
successfulPushAt?: string;
}
export declare function buildSyncStatus(opts: BuildSyncStatusOpts): SyncStatus;
/** Warn after this many consecutive failed syncs. */
export declare const SYNC_FAILURE_WARN_RUNS = 3;
/** ...or after this many days without a successful push, whichever comes first. */
export declare const SYNC_FAILURE_WARN_DAYS = 3;
export interface SyncOutageAssessment {
/** True when the store has been failing long enough to tell the user. */
degraded: boolean;
consecutiveFailures: number;
daysSinceSuccess: number | null;
/** One-line, user-facing explanation. Empty when not degraded. */
summary: string;
}
/**
* Decide whether a store's sync failures have gone on long enough to warrant a
* visible warning. Pure so both the Stop hook and `phren status` can ask the
* same question and get the same answer.
*/
export declare function assessSyncOutage(sync: SyncStatus | undefined, nowMs?: number): SyncOutageAssessment;
export type RetentionPolicyPatch = Partial<Omit<RetentionPolicy, "decay">> & {
decay?: Partial<RetentionPolicy["decay"]>;
};
export declare const GOVERNANCE_SCHEMA_VERSION = 1;
/** Default retention policy. Exported so {@link config/schema} can render one source of truth. */
export declare const DEFAULT_POLICY: RetentionPolicy;
/** Default workflow policy. Exported so {@link config/schema} can render one source of truth. */
export declare const DEFAULT_WORKFLOW_POLICY: WorkflowPolicy;
/** Default index policy. Exported so {@link config/schema} can render one source of truth. */
export declare const DEFAULT_INDEX_POLICY: IndexPolicy;
type GovernanceSchema = "retention-policy" | "workflow-policy" | "index-policy";
export declare function validateGovernanceJson(filePath: string, schema: GovernanceSchema): boolean;
export interface ResolvedConfig {
findingSensitivity: WorkflowPolicy["findingSensitivity"];
proactivity: {
base?: "high" | "medium" | "low";
findings?: "high" | "medium" | "low";
tasks?: "high" | "medium" | "low";
};
taskMode: WorkflowPolicy["taskMode"];
retentionPolicy: RetentionPolicy;
workflowPolicy: WorkflowPolicy;
}
export { VALID_PROACTIVITY_LEVELS, VALID_TASK_MODES, type TaskMode, VALID_FINDING_SENSITIVITY, type FindingSensitivityLevel, VALID_RISKY_SECTIONS, } from "./policy-constants.js";
export declare function getProjectConfigOverrides(phrenPath: string, projectName: string): ProjectConfigOverrides | null;
export declare function mergeConfig(phrenPath: string, projectName?: string, profile?: string): ResolvedConfig;
export declare function getRetentionPolicy(phrenPath: string, projectName?: string): RetentionPolicy;
export declare function updateRetentionPolicy(phrenPath: string, patch: RetentionPolicyPatch): PhrenResult<RetentionPolicy>;
export declare function getWorkflowPolicy(phrenPath: string, projectName?: string): WorkflowPolicy;
export declare function updateWorkflowPolicy(phrenPath: string, patch: Partial<WorkflowPolicy>): PhrenResult<WorkflowPolicy>;
export declare function getIndexPolicy(phrenPath: string): IndexPolicy;
export declare function updateIndexPolicy(phrenPath: string, patch: Partial<IndexPolicy>): PhrenResult<IndexPolicy>;
export declare function getRuntimeHealth(phrenPath: string): RuntimeHealth;
export declare function updateRuntimeHealth(phrenPath: string, patch: Partial<RuntimeHealth>): RuntimeHealth;
export declare function normalizeQueueEntryText(raw: string, opts?: {
truncate?: boolean;
}): PhrenResult<{
text: string;
truncated: boolean;
}>;
/**
* A queue entry that carries structured provenance alongside its text.
*
* `meta` is a pre-rendered run of HTML comments (e.g. `<!-- source:extract -->
* <!-- phren:cite {...} -->`) appended verbatim to the queue line. It exists so a
* queued candidate stays *promotable*: `approveQueueItem` reads it back and hands
* the same repo/commit/source provenance to the normal add-finding path, which
* makes a promoted finding indistinguishable from one added directly.
*
* Producers build the comments themselves (this module deliberately does not import
* the citation helpers, to keep governance free of a content-layer import cycle).
*/
export interface ReviewQueueEntry {
text: string;
meta?: string;
}
export type ReviewQueueEntryInput = string | ReviewQueueEntry;
export declare function appendReviewQueue(phrenPath: string, project: string, section: "Review" | "Stale" | "Conflicts", entries: ReviewQueueEntryInput[]): PhrenResult<number>;
export interface PruneMemoriesResult {
/** Human-readable summary (plus dry-run detail lines), ready to print. */
message: string;
/** Entries deleted for exceeding the retention window. */
pruned: number;
/** TTL-expired entries promoted into review.md's `## Stale` section. */
ttlExpired: number;
}
/**
* Delete entries past the retention window and promote TTL-expired entries into review.md.
*
* TTL promotion lives here rather than in the CLI handler so that nightly maintenance
* (`handleBackgroundMaintenance`, which calls this directly) gets it too — previously it
* only ran on a manual `phren maintain prune`, leaving `## Stale` empty while `## Review`
* filled up.
*/
export declare function pruneDeadMemories(phrenPath: string, project?: string, dryRun?: boolean): PhrenResult<PruneMemoriesResult>;
export declare function consolidateProjectFindings(phrenPath: string, project: string, dryRun?: boolean): PhrenResult<string>;