@papi-ai/adapter-md 0.1.1-alpha → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +768 -16
  2. package/dist/index.js +305 -46
  3. package/package.json +8 -2
package/dist/index.d.ts CHANGED
@@ -13,8 +13,12 @@ interface Phase {
13
13
  /** The stage this phase belongs to (AD-14 hierarchy). */
14
14
  stageId?: string;
15
15
  }
16
- /** Horizon status in the project hierarchy (AD-14). */
17
- type HorizonStatus = 'active' | 'completed' | 'deferred';
16
+ /**
17
+ * Horizon status in the project hierarchy (AD-14). Shares the PhaseStatus
18
+ * vocabulary — the DB constraint + all live rows use 'Not Started'/'In Progress'/
19
+ * 'Done'/'Deferred', NOT the legacy 'active'/'completed'/'deferred' (task-2136).
20
+ */
21
+ type HorizonStatus = PhaseStatus;
18
22
  /** A horizon — the highest level of the project hierarchy (AD-14: Horizon → Stage → Phase → Task). */
19
23
  interface Horizon {
20
24
  id: string;
@@ -27,8 +31,8 @@ interface Horizon {
27
31
  createdAt: string;
28
32
  updatedAt: string;
29
33
  }
30
- /** Stage status in the project hierarchy (AD-14). */
31
- type StageStatus = 'active' | 'completed' | 'deferred';
34
+ /** Stage status in the project hierarchy (AD-14). Shares the PhaseStatus vocabulary (task-2136 — see HorizonStatus). */
35
+ type StageStatus = PhaseStatus;
32
36
  /** A stage — sits between Horizon and Phase in the hierarchy (AD-14: Horizon → Stage → Phase → Task). */
33
37
  interface Stage {
34
38
  id: string;
@@ -39,6 +43,7 @@ interface Stage {
39
43
  sortOrder: number;
40
44
  horizonId: string;
41
45
  projectId: string;
46
+ exitCriteria?: string[];
42
47
  createdAt: string;
43
48
  updatedAt: string;
44
49
  }
@@ -62,6 +67,14 @@ interface Cycle {
62
67
  boardHealth: string;
63
68
  taskIds: string[];
64
69
  contextHashes?: ContextHashes;
70
+ /**
71
+ * task-2071 (C293, MU-3): the member who owns this cycle. Each member runs
72
+ * their own cycle into the shared repo; the DB enforces one active cycle per
73
+ * (project_id, user_id). createCycle stamps this from the calling identity;
74
+ * the C293 migration backfilled existing active cycles to the project owner so
75
+ * solo/owner-operated projects are unchanged. NULL = legacy/unowned.
76
+ */
77
+ userId?: string;
65
78
  }
66
79
 
67
80
  type TaskStatus = TaskStatus$1;
@@ -92,6 +105,17 @@ type Confidence = 'HIGH' | 'MEDIUM' | 'LOW';
92
105
  type DecisionEventType = 'created' | 'confidence_changed' | 'modified' | 'superseded' | 'validated' | 'invalidated' | 'scored';
93
106
  /** Source of a decision event. */
94
107
  type DecisionEventSource = 'strategy_review' | 'build_complete' | 'strategy_change' | 'manual';
108
+ /**
109
+ * Structured before->after movement of a single metric, captured on a
110
+ * deliberate decision event (task-2168). Consumes cycle_metrics_snapshots.
111
+ */
112
+ interface MetricDelta {
113
+ /** Metric name — ideally a real metric from cycle_metrics_snapshots. */
114
+ metric: string;
115
+ before?: number;
116
+ after?: number;
117
+ delta?: number;
118
+ }
95
119
  /** An append-only event in a decision's lifecycle. */
96
120
  interface DecisionEvent {
97
121
  id: string;
@@ -101,6 +125,14 @@ interface DecisionEvent {
101
125
  source: DecisionEventSource;
102
126
  sourceRef?: string;
103
127
  detail?: string;
128
+ /**
129
+ * Pointer to supporting evidence (doc path / build-report id / metric name).
130
+ * Captured on deliberate events (validated / invalidated / modified) at
131
+ * strategy_review / strategy_change. Optional — writer fails open (task-2168).
132
+ */
133
+ evidenceRef?: string;
134
+ /** Which metric moved + before->after for this decision (task-2168). */
135
+ metricDelta?: MetricDelta | null;
104
136
  createdAt: string;
105
137
  }
106
138
  /** Dimensional scores for an Active Decision at a point in time. */
@@ -126,6 +158,8 @@ interface CycleHealth {
126
158
  boardHealth: string;
127
159
  strategicDirection: string;
128
160
  lastFullMode: number;
161
+ /** Number of auth.users rows with no matching user_profiles row. Non-zero = trigger gap. pg adapter only. */
162
+ orphanAuthUsers?: number;
129
163
  }
130
164
  /** An Active Decision (AD) tracked in the planning log. */
131
165
  interface ActiveDecision {
@@ -139,7 +173,7 @@ interface ActiveDecision {
139
173
  createdCycle?: number;
140
174
  modifiedCycle?: number;
141
175
  body: string;
142
- outcome?: 'pending' | 'validated' | 'revised' | 'abandoned' | 'superseded';
176
+ outcome?: 'pending' | 'confirmed' | 'validated' | 'revised' | 'resolved' | 'abandoned' | 'superseded';
143
177
  revisionCount?: number;
144
178
  }
145
179
  /** A single cycle entry in the planning log. */
@@ -150,6 +184,16 @@ interface CycleLogEntry {
150
184
  content: string;
151
185
  carryForward?: string;
152
186
  notes?: string;
187
+ /** Number of tasks in this cycle (structured — no prose parsing needed). */
188
+ taskCount?: number;
189
+ /** Total effort points for this cycle (XS=1, S=2, M=3, L=5, XL=8). */
190
+ effortPoints?: number;
191
+ /**
192
+ * ISO timestamp for when this cycle log entry was last written (plan or
193
+ * release). Optional — historical entries may not have it. Surfaced from
194
+ * planning_log_entries.updated_at in the pg adapter.
195
+ */
196
+ date?: string;
153
197
  }
154
198
  /** A strategy review entry stored in its own table (not planning_log_entries). */
155
199
  interface StrategyReviewEntry {
@@ -166,6 +210,49 @@ interface StrategyReviewEntry {
166
210
  /** Full ReviewStructuredOutput from the strategy review LLM, stored as JSONB for dashboard querying */
167
211
  structuredData?: Record<string, unknown>;
168
212
  createdAt?: string;
213
+ /** Sequential review number per project (1, 2, 3...) */
214
+ reviewNumber?: number;
215
+ /** Review type: 'scheduled' (every 5 cycles), 'ad-hoc' (user-triggered), 'triggered' (by event) */
216
+ reviewType?: string;
217
+ }
218
+ /**
219
+ * A single entry in a project's harness inventory (task-1896).
220
+ * Captures what skills / sub-agents / hooks / MCP tools a project is running,
221
+ * scanned from the local filesystem by the MCP server and persisted so the
222
+ * dashboard (which has no filesystem access) can surface it per project.
223
+ */
224
+ interface HarnessInventoryEntry {
225
+ id?: string;
226
+ projectId?: string;
227
+ /** What kind of harness artifact this row describes. */
228
+ kind: 'skill' | 'agent' | 'hook' | 'mcp_tool';
229
+ /** Human-readable name (skill/agent/hook filename stem, or MCP tool name). */
230
+ name: string;
231
+ /** One-line description where available (frontmatter / tool description). */
232
+ description?: string;
233
+ /** Version of the artifact, where known (skills, from the @papi-ai/skills manifest). */
234
+ version?: string;
235
+ /** Content checksum, where known (skills — used to detect stale forks). */
236
+ checksum?: string;
237
+ /**
238
+ * Status signal surfaced in the dashboard:
239
+ * - 'ok': present and current
240
+ * - 'stale_fork': a local skill that has drifted from the pinned registry version
241
+ * - 'missing': a recommended hook that is not installed in the project
242
+ */
243
+ status: 'ok' | 'stale_fork' | 'missing';
244
+ /** Project-relative path, where applicable (never absolute, never file contents). */
245
+ path?: string;
246
+ syncedAt?: string;
247
+ }
248
+ /**
249
+ * Change-detection marker for the harness inventory (task-1896).
250
+ * A cheap fingerprint of the project's harness layout; the sync writer only
251
+ * does the full scan + DB write when this fingerprint changes.
252
+ */
253
+ interface HarnessState {
254
+ fingerprint: string;
255
+ syncedAt?: string;
169
256
  }
170
257
  /** A dogfood observation captured during strategy reviews or releases. */
171
258
  interface DogfoodEntry {
@@ -175,7 +262,8 @@ interface DogfoodEntry {
175
262
  category: 'friction' | 'methodology' | 'signal' | 'commercial';
176
263
  content: string;
177
264
  sourceTool?: string;
178
- status?: 'observed' | 'backlog-created' | 'resolved';
265
+ sourceRef?: string;
266
+ status?: 'observed' | 'backlog-created' | 'resolved' | 'actioned' | 'dismissed';
179
267
  linkedTaskId?: string;
180
268
  createdAt?: string;
181
269
  }
@@ -192,7 +280,17 @@ interface StateTransition {
192
280
  status: TaskStatus;
193
281
  timestamp: string;
194
282
  }
283
+ /** AD-29: scope_class distinguishes directly-buildable tasks from brief-class items needing decomposition. */
284
+ type ScopeClass = 'task' | 'brief';
195
285
  /** A task on the board (parsed from CYCLE_BOARD.md YAML). */
286
+ /** A sibling project's in-flight task, surfaced for the plan duplicate-risk check (task-2139). */
287
+ interface SiblingRepoTask {
288
+ displayId: string;
289
+ title: string;
290
+ epic?: string;
291
+ status: string;
292
+ sourceProjectName: string;
293
+ }
196
294
  interface CycleTask {
197
295
  uuid: string;
198
296
  id: string;
@@ -221,6 +319,50 @@ interface CycleTask {
221
319
  buildReport?: string;
222
320
  taskType?: TaskType;
223
321
  maturity?: TaskMaturity;
322
+ /** Path to a reference document (e.g. docs/research/foo.md). */
323
+ docRef?: string;
324
+ /** Provenance: where this task originated (owner, llm, strategy_review, etc.). */
325
+ source?: string;
326
+ /** What user problem does this solve? Used by planner for opportunity-based clustering. */
327
+ opportunity?: string;
328
+ /** AD-29: 'task' = directly buildable; 'brief' = needs scope_brief decomposition first. Default 'task'. */
329
+ scopeClass?: ScopeClass;
330
+ /** task-1699 (C255): audit who initiated a cancellation. Set when status transitions to 'Cancelled'.
331
+ * 'planner' = planner proposed + user confirmed; 'user' = direct user action; 'system' = automated. */
332
+ cancelledBy?: 'planner' | 'user' | 'system';
333
+ /**
334
+ * Visibility tier (task-2013). Inherited from the source doc when a task is born
335
+ * from `idea`/`plan` with a doc_ref; defaults to 'public'. Read-only after creation —
336
+ * build_execute/review_submit must NOT mutate it.
337
+ */
338
+ visibility?: DocVisibility;
339
+ /** Owner lock (task-2013) — set on private/contributors rows so a project_id reassignment cannot expose them. */
340
+ ownerUserId?: string;
341
+ /**
342
+ * task-1763 (C293): claim assignee — the user who has claimed this task from the
343
+ * shared pool. NULL/undefined = unclaimed. Set only via the atomic claimTask CAS
344
+ * (bearer-derived identity, never client input). Consumed by MU-3 (task-2071).
345
+ */
346
+ assigneeId?: string;
347
+ /**
348
+ * task-2071 (C293, MU-3): how this task entered a personal backlog. 'pool' =
349
+ * claimed from the shared org pool via task_claim; 'self_generated' = created
350
+ * already attributed to a user. NULL/undefined = legacy/unattributed. The
351
+ * pooled-task invariant is `assigneeId == null && cycle == null`.
352
+ */
353
+ claimSource?: 'pool' | 'self_generated';
354
+ /**
355
+ * task-2071 (C293): reviewer slot for MU-4/5 (cross-user review, task-2072).
356
+ * Schema-only this task — no code reads it yet.
357
+ */
358
+ reviewerId?: string;
359
+ /**
360
+ * task-2180 (C295): ISO timestamp the task's branch was successfully merged to the
361
+ * base branch (review_submit accept or release). NULL/undefined = not yet merged.
362
+ * Write-only audit tie between Done status and git reality — set on a successful
363
+ * merge so a Done task is provably backed by a real merge; no reader yet.
364
+ */
365
+ mergedAt?: string;
224
366
  }
225
367
  /** Structured handoff accuracy — replaces coarse scope_accuracy text. */
226
368
  interface HandoffAccuracy {
@@ -253,11 +395,113 @@ interface BuildReport {
253
395
  scopeAccuracy: 'accurate' | 'over-scoped' | 'under-scoped' | 'missed-context';
254
396
  commitSha?: string;
255
397
  filesChanged?: string[];
398
+ /**
399
+ * task-1523 (C242): scope drift signal.
400
+ * Populated when filesChanged diverges materially from the handoff's filesLikelyTouched
401
+ * (e.g. >50% of changed files unpredicted, or >5 unexpected files). Format is a single
402
+ * human-readable line surfaced under "Scope drift signal" in the build report. Does NOT block.
403
+ */
404
+ scopeDriftSignal?: string;
256
405
  relatedDecisions?: string[];
257
406
  handoffAccuracy?: HandoffAccuracy;
258
407
  correctionsCount?: number;
259
408
  briefImplications?: BriefImplication[];
260
409
  deadEnds?: string;
410
+ /** Number of build iterations for this task (1 = first attempt, 2+ = rework after pushback). */
411
+ iterationCount?: number;
412
+ /** ISO 8601 timestamp when build_execute (start) was called. */
413
+ startedAt?: string;
414
+ /** ISO 8601 timestamp when build_execute (complete) was called. */
415
+ completedAt?: string;
416
+ /** Number of tool calls recorded during this build session. */
417
+ toolCallCount?: number;
418
+ /**
419
+ * task-1853 deploy-verification record. Populated when the task's diff hit a
420
+ * trigger surface (TRIGGER_SURFACE_GLOBS) and the builder verified the live deploy.
421
+ * Required by `completeBuild` for trigger-surface tasks; gates `review_submit` accept.
422
+ */
423
+ productionVerification?: {
424
+ urls: string[];
425
+ curl_command: string;
426
+ http_status: number;
427
+ response_excerpt: string;
428
+ verified_at: string;
429
+ };
430
+ }
431
+ /** A registered document in the doc registry. */
432
+ /**
433
+ * Doc visibility tier (task-1977, C283).
434
+ * - public: shipped with PAPI, readable cross-project by any user.
435
+ * - contributors: "team member" tier — readable by the project's contributor cohort.
436
+ * - private: owner-only (default — safety bias).
437
+ * User-facing label for `contributors` is "team member".
438
+ */
439
+ type DocVisibility = 'public' | 'contributors' | 'private';
440
+ interface DocRegistryEntry {
441
+ id: string;
442
+ title: string;
443
+ type: string;
444
+ path: string;
445
+ status: string;
446
+ summary: string;
447
+ tags: string[];
448
+ cycleCreated: number;
449
+ cycleUpdated?: number;
450
+ supersededBy?: string;
451
+ actions?: Array<{
452
+ description: string;
453
+ status: string;
454
+ linkedTaskId?: string;
455
+ }>;
456
+ /** Visibility tier (task-1977). Defaults to 'private' at write time when omitted. */
457
+ visibility?: DocVisibility;
458
+ /** Owner lock — the user_id that owns this doc; set on register so a project_id reassignment cannot expose private rows. */
459
+ ownerUserId?: string;
460
+ createdAt?: string;
461
+ updatedAt?: string;
462
+ }
463
+ /** A structured learning entry from a build report. */
464
+ interface CycleLearning {
465
+ id?: string;
466
+ projectId?: string;
467
+ taskId: string;
468
+ cycleNumber: number;
469
+ category: 'surprise' | 'issue' | 'dead_end' | 'architecture' | 'estimation_miss' | 'doc_closure' | 'observability';
470
+ severity?: 'P0' | 'P1' | 'P2' | 'P3';
471
+ summary: string;
472
+ detail?: string;
473
+ tags: string[];
474
+ relatedDecision?: string;
475
+ actionTaken?: 'idea_submitted' | 'task_created' | 'ad_updated' | 'none';
476
+ actionRef?: string;
477
+ createdAt?: string;
478
+ /** When the underlying issue was marked resolved. null/undefined = still open. (task-1541) */
479
+ resolvedAt?: string;
480
+ /** Identifier of the agent/user that resolved the issue (task-1541). */
481
+ resolvedBy?: string;
482
+ }
483
+ /** A cross-cycle pattern detected from learning tags. */
484
+ interface CycleLearningPattern {
485
+ tag: string;
486
+ cycles: number[];
487
+ frequency: number;
488
+ }
489
+ /** Input for doc_search queries. */
490
+ interface DocSearchInput {
491
+ type?: string;
492
+ status?: string;
493
+ tags?: string[];
494
+ keyword?: string;
495
+ hasPendingActions?: boolean;
496
+ sinceCycle?: number;
497
+ limit?: number;
498
+ /**
499
+ * Visibility scoping (task-1977). The user_id of the requester. When set and the
500
+ * requester is NOT the project owner, results are filtered to public docs plus
501
+ * contributors-tier docs the requester is a cohort member of (private excluded —
502
+ * fail-closed). When omitted (local owner/CLI context), all rows are returned.
503
+ */
504
+ requesterUserId?: string;
261
505
  }
262
506
  /** Aggregated recommendation effectiveness row from v_recommendation_effectiveness. */
263
507
  interface RecommendationEffectivenessRow {
@@ -276,6 +520,15 @@ interface EstimationCalibrationRow {
276
520
  accuracyLabel: string;
277
521
  count: number;
278
522
  }
523
+ /** Per-module estimation stats — builds joined with task module via cycle_tasks. */
524
+ interface ModuleEstimationRow {
525
+ module: string;
526
+ total: number;
527
+ /** % of tasks where estimated effort matched actual effort. */
528
+ accuracyPct: number;
529
+ /** % of tasks where scope_accuracy was 'accurate'. */
530
+ scopeAccuratePct: number;
531
+ }
279
532
  /** A single tool call metric entry from METRICS.md. */
280
533
  interface ToolCallMetric {
281
534
  timestamp: string;
@@ -291,6 +544,15 @@ interface ToolCallMetric {
291
544
  contextBytes?: number;
292
545
  /** Context utilisation ratio (0.0–1.0). Fraction of input entities referenced in output. */
293
546
  contextUtilisation?: number;
547
+ /** Whether the tool call succeeded (no error prefix in response). */
548
+ success?: boolean;
549
+ /**
550
+ * MCP client `Implementation.name` advertised at initialize time.
551
+ * Captured verbatim (Claude Code = "claude-code"; Codex advertises a
552
+ * different string). Used to split activation/feature funnels by CLI.
553
+ * Absent on legacy rows + non-MCP code paths. (task-1680)
554
+ */
555
+ clientName?: string;
294
556
  }
295
557
  /** Per-command cost aggregation. */
296
558
  interface CommandCost {
@@ -452,12 +714,27 @@ interface BuildHandoff {
452
714
  securityConsiderations: string;
453
715
  filesLikelyTouched: string[];
454
716
  effort: EffortSize;
717
+ /** Files the builder should check before implementing to verify the feature doesn't already exist. */
718
+ verificationFiles?: string[];
455
719
  }
456
720
  /** Module and epic registries loaded from REGISTRIES.md. */
457
721
  interface Registries {
458
722
  modules: string[];
459
723
  epics: string[];
460
724
  }
725
+ /** Lifecycle status for a queued strategy review agenda topic. */
726
+ type AgendaTopicStatus = 'pending' | 'addressed' | 'dismissed';
727
+ /** A topic queued for the next strategy review. */
728
+ interface AgendaTopic {
729
+ id: string;
730
+ topic: string;
731
+ source: string;
732
+ sourceCycle?: number;
733
+ status: AgendaTopicStatus;
734
+ createdAt: string;
735
+ addressedAt?: string;
736
+ addressedInReview?: number;
737
+ }
461
738
  /** Recommendation types written by strategy_review, consumed by plan. */
462
739
  type RecommendationType = 'priority_change' | 'new_task' | 'ad_update' | 'phase_transition' | 'process_improvement' | 'infrastructure' | 'velocity_observation';
463
740
  /** Recommendation lifecycle status. */
@@ -472,6 +749,22 @@ interface StrategyRecommendation {
472
749
  actionedCycle?: number;
473
750
  target?: string;
474
751
  }
752
+ /** Upstream submission (bug or idea) sent to PAPI for cross-project visibility. */
753
+ interface BugReport {
754
+ id: string;
755
+ projectId: string;
756
+ userId?: string;
757
+ /** Kind of submission. Defaults to 'bug' for back-compat with the legacy `bug report=true` flow (task-2174, C296). */
758
+ type?: 'bug' | 'idea';
759
+ description: string;
760
+ diagnostics: Record<string, unknown>;
761
+ status: 'open' | 'resolved' | 'dismissed';
762
+ /** Submitter opted in to be notified when this is resolved (friction-moment opt-in). */
763
+ notifyRequested?: boolean;
764
+ /** Submitter consented to the PAPI team reaching out about this submission. */
765
+ contactOk?: boolean;
766
+ createdAt: string;
767
+ }
475
768
  /** Entity reference — tracks which decisions/entities are used in tool calls. */
476
769
  interface EntityReference {
477
770
  id: string;
@@ -514,31 +807,175 @@ interface BoardQueryOptions {
514
807
  contextTier?: ContextTier;
515
808
  /** Filter to tasks assigned to this cycle or later (cycle >= N). */
516
809
  cycleSince?: number;
810
+ /** Exclude heavy JSONB columns (build_handoff, build_report, state_history) to reduce DB transfer. */
811
+ compact?: boolean;
812
+ /**
813
+ * Visibility scoping (task-2013). The user_id of the requester. When set and the
814
+ * requester is NOT the project owner, results are filtered to public tasks plus
815
+ * contributors-tier tasks the requester is a cohort member of (private excluded —
816
+ * fail-closed). When omitted (local owner/CLI context), all rows are returned.
817
+ * Mirrors DocSearchInput.requesterUserId exactly.
818
+ */
819
+ requesterUserId?: string;
820
+ /**
821
+ * task-2071 (C293, MU-3): filter to tasks claimed by this user (assignee_id =
822
+ * value). Used by plan's per-user "draw from my personal backlog" path. Does
823
+ * NOT widen visibility — it narrows an already-authorised result set.
824
+ */
825
+ assigneeId?: string;
826
+ }
827
+ interface OwnerActionInput {
828
+ name: string;
829
+ project_ids?: string[];
830
+ category?: string | null;
831
+ status?: 'todo' | 'waiting' | 'done';
832
+ dependency?: string | null;
833
+ docs_link?: string | null;
834
+ unlocks_task_id?: string | null;
835
+ }
836
+ interface OwnerActionRow extends OwnerActionInput {
837
+ id: string;
838
+ user_id: string;
839
+ project_ids: string[];
840
+ status: 'todo' | 'waiting' | 'done';
841
+ completed_at: string | null;
842
+ created_at: string;
843
+ updated_at: string;
517
844
  }
518
845
  /** Core adapter interface for reading/writing .papi/ markdown files. */
519
846
  interface PapiAdapter {
520
847
  readPlanningLog(): Promise<PlanningLog>;
521
848
  getCycleHealth(): Promise<CycleHealth>;
522
- getActiveDecisions(): Promise<ActiveDecision[]>;
849
+ /**
850
+ * Read Active Decisions for this project.
851
+ *
852
+ * Default behaviour (no options or `{ includeRetired: false }`) excludes ADs that
853
+ * are no longer steering the project: outcome ∈ ('abandoned', 'superseded', 'resolved')
854
+ * OR superseded = true. This is the correct signal for context-injection surfaces
855
+ * (orient, build_execute handoffs, plan context, strategy housekeeping displays).
856
+ *
857
+ * Pass `{ includeRetired: true }` for management/triage surfaces that need to see
858
+ * retired ADs explicitly — e.g. strategy_review (so retired ADs can be re-validated
859
+ * or restored), and any code that needs the full ID space (next-AD-number computation,
860
+ * dedupe against existing IDs, lifecycle counts).
861
+ *
862
+ * task-1546 (C242 hot-fix): closes the bug where retired ADs (e.g. AD-26, abandoned
863
+ * in C241) continued to appear in build/plan/orient context as "active" because
864
+ * consumers only filtered the `superseded` boolean and ignored the `outcome` lifecycle.
865
+ */
866
+ getActiveDecisions(options?: {
867
+ includeRetired?: boolean;
868
+ }): Promise<ActiveDecision[]>;
869
+ /** Query ADs from sibling project IDs in the same Supabase instance. pg adapter only. */
870
+ getSiblingAds?(projectIds: string[]): Promise<Array<ActiveDecision & {
871
+ sourceProjectId: string;
872
+ }>>;
873
+ /**
874
+ * task-2139: in-flight tasks from OTHER projects that share THIS project's
875
+ * repo_url. Surfaced in plan prepare context so the planner does not
876
+ * re-schedule work already underway on the shared repo (the C292 duplicate
877
+ * incident). Scoped to repo_url siblings (never the whole DB); returns only
878
+ * the fields the dup-check needs. pg adapter only — md/proxy omit it and the
879
+ * caller feature-checks.
880
+ */
881
+ getSiblingRepoTasks?(): Promise<SiblingRepoTask[]>;
523
882
  getCycleLog(limit?: number): Promise<CycleLogEntry[]>;
524
883
  getCycleLogSince(cycleNumber: number): Promise<CycleLogEntry[]>;
525
884
  setCycleHealth(updates: Partial<CycleHealth>): Promise<void>;
526
885
  writeCycleLogEntry(entry: CycleLogEntry): Promise<void>;
527
- updateActiveDecision(id: string, body: string, cycleNumber?: number): Promise<void>;
886
+ updateActiveDecision(id: string, body: string, cycleNumber?: number, action?: 'confidence_change' | 'modify' | 'resolve' | 'supersede' | 'new' | 'delete'): Promise<void>;
528
887
  /** Upsert an AD — creates if display_id doesn't exist, updates if it does. */
529
888
  upsertActiveDecision?(id: string, body: string, title: string, confidence: string, cycleNumber: number): Promise<void>;
530
889
  /** Delete an AD permanently by display_id. */
531
890
  deleteActiveDecision?(id: string): Promise<void>;
891
+ /** After a strategy review, mark all still-pending ADs as confirmed (reviewed, no changes). */
892
+ confirmPendingActiveDecisions?(cycleNumber: number): Promise<void>;
532
893
  queryBoard(options?: BoardQueryOptions): Promise<CycleTask[]>;
533
894
  getTask(id: string): Promise<CycleTask | null>;
534
895
  getTasks(ids: string[]): Promise<CycleTask[]>;
535
896
  createTask(task: Omit<CycleTask, 'id'>): Promise<CycleTask>;
897
+ /**
898
+ * task-1917 (C280): Owner Action Queue support.
899
+ * Optional — only adapters backed by a database that has the owner_actions
900
+ * table need to implement these. Callers must feature-check before use:
901
+ * if (adapter.createOwnerAction) { ... }
902
+ */
903
+ createOwnerAction?(userId: string, input: OwnerActionInput): Promise<OwnerActionRow>;
904
+ /**
905
+ * Count rows in owner_actions for the given user that are not yet
906
+ * completed. Used by orient to surface "N actions waiting on you".
907
+ * task-2041 (C284): optional projectId scopes to one project (untagged actions
908
+ * count as global). Omit it for the cross-project /hub aggregate.
909
+ */
910
+ countOpenOwnerActions?(userId: string, projectId?: string): Promise<number>;
911
+ /**
912
+ * Count open owner_actions a delegate has nudged the owner about (task-2018).
913
+ * Surfaced by orient separately from the open-count. Optional — md adapter and
914
+ * pre-v2 deployments simply skip the line. task-2041: optional projectId scope.
915
+ */
916
+ countNudgedOwnerActions?(userId: string, projectId?: string): Promise<number>;
917
+ /** task-2041 (C284): the project this adapter is scoped to (pg adapter only). */
918
+ getProjectId?(): string;
919
+ /**
920
+ * task-2051 (C284): count the user's OPEN owner actions whose unlocks_task_id
921
+ * points at a build task currently 'Blocked'. Surfaced by orient as a
922
+ * "complete these to unblock build work" nudge. Optional — md adapter and
923
+ * pre-status deployments skip the line.
924
+ */
925
+ countOwnerActionsBlockingTasks?(userId: string): Promise<number>;
926
+ /**
927
+ * Team Ops v4: count the user's OPEN owner actions by due date — dueToday
928
+ * (due_date = today) and overdue (due_date < today). Folded into orient's
929
+ * "N actions waiting on you" line. Optional — md adapter and pre-v4
930
+ * deployments skip the suffix. Optional projectId scope as countOpenOwnerActions.
931
+ */
932
+ countDueOwnerActions?(userId: string, projectId?: string): Promise<{
933
+ dueToday: number;
934
+ overdue: number;
935
+ }>;
536
936
  updateTask(id: string, updates: Partial<Omit<CycleTask, 'id'>>, options?: UpdateTaskOptions): Promise<void>;
537
937
  updateTaskStatus(id: string, status: TaskStatus): Promise<void>;
938
+ /**
939
+ * task-2182: correct a mis-recorded estimate/actual on the LATEST build report
940
+ * for a task. pg-only (project_id-scoped UPDATE on the MAX(created_at) row); md
941
+ * is legacy and omits it. Carried by board_edit's estimated_effort/actual_effort.
942
+ */
943
+ correctLatestBuildReportEffort?(taskId: string, fields: {
944
+ estimatedEffort?: string;
945
+ actualEffort?: string;
946
+ }): Promise<void>;
947
+ /**
948
+ * task-1763 (C293): atomic compare-and-swap task claim. Sets assignee_id ONLY if
949
+ * the task is currently unclaimed (assignee_id IS NULL) — first-claim-wins, no
950
+ * queueing. Returns the claimed task on success, or null if it was already claimed
951
+ * (or does not exist). assigneeId must be the bearer-derived identity, never client
952
+ * input; the implementation scopes by project_id so a claim can never cross tenant.
953
+ * Optional: the primitive layer for MU-3 (task-2071); proxy/edge wiring lands with
954
+ * the task_claim tool in task-2071.
955
+ */
956
+ claimTask?(taskId: string, assigneeId: string): Promise<CycleTask | null>;
957
+ /**
958
+ * task-2071 (C293, MU-3): atomic claimer-only release. Clears assignee_id (and
959
+ * claim_source) ONLY if the task is currently assigned to `assigneeId` AND has
960
+ * not yet entered review (status NOT IN 'In Review','Done'). Returns the
961
+ * unclaimed task on success, or null if the caller is not the claimer, the task
962
+ * has progressed past claim, or it does not exist. Scoped by project_id.
963
+ */
964
+ unclaimTask?(taskId: string, assigneeId: string): Promise<CycleTask | null>;
965
+ /**
966
+ * task-2072 (C293, MU-4): atomic compare-and-swap REVIEW claim — the cross-user
967
+ * review queue primitive. Sets reviewer_id ONLY if the task is currently In
968
+ * Review and unclaimed-for-review (reviewer_id IS NULL). Returns the claimed
969
+ * task on success, or null if another reviewer already claimed it, it is not In
970
+ * Review, or it does not exist. reviewerId is bearer-derived, never client
971
+ * input; scoped by project_id. First-claim-wins (no double-review race).
972
+ */
973
+ claimReview?(taskId: string, reviewerId: string): Promise<CycleTask | null>;
538
974
  /** Record a task status transition for audit trail. */
539
975
  recordTransition(taskId: string, fromStatus: string, toStatus: string, changedBy?: string): Promise<void>;
540
976
  appendBuildReport(report: BuildReport): Promise<void>;
541
977
  getRecentBuildReports(count: number): Promise<BuildReport[]>;
978
+ getBuildReportCountForTask(taskId: string): Promise<number>;
542
979
  getBuildReportsSince(cycleNumber: number): Promise<BuildReport[]>;
543
980
  getRecentReviews(count?: number): Promise<HumanReview[]>;
544
981
  writeReview(review: HumanReview): Promise<void>;
@@ -562,8 +999,11 @@ interface PapiAdapter {
562
999
  readStages?(horizonId?: string): Promise<Stage[]>;
563
1000
  /** Update a stage's status (e.g. 'Active' → 'Complete'). */
564
1001
  updateStageStatus?(stageId: string, status: string): Promise<void>;
1002
+ updateStageExitCriteria?(stageId: string, exitCriteria: string[]): Promise<void>;
565
1003
  /** Update a horizon's status (e.g. 'Active' → 'Complete'). */
566
1004
  updateHorizonStatus?(horizonId: string, status: string): Promise<void>;
1005
+ /** Update a phase's status (e.g. 'Active' → 'Complete'). */
1006
+ updatePhaseStatus?(phaseId: string, status: string): Promise<void>;
567
1007
  /** Get the currently active stage for the project. */
568
1008
  getActiveStage?(): Promise<Stage | undefined>;
569
1009
  /** Create a new horizon (AD-14 hierarchy). Returns the created horizon's ID. */
@@ -587,8 +1027,16 @@ interface PapiAdapter {
587
1027
  linkPhasesToStage?(stageId: string): Promise<void>;
588
1028
  appendToolMetric(metric: ToolCallMetric): Promise<void>;
589
1029
  readToolMetrics(): Promise<ToolCallMetric[]>;
1030
+ /** Count tool calls recorded between two ISO timestamps. Optional — pg only. */
1031
+ getToolCallCount?(startedAt: string, completedAt: string): Promise<number>;
1032
+ /**
1033
+ * Cheap existence check for a single milestone tool name. Returns true when
1034
+ * any tool_call_metrics row matches `tool = name`. Replaces full-table reads
1035
+ * for one-shot orient checks (skill scan, TTFV) which previously pulled up
1036
+ * to 5000 rows just to test for one milestone.
1037
+ */
1038
+ hasToolMilestone?(name: string): Promise<boolean>;
590
1039
  getCostSummary(cycleNumber?: number): Promise<CostSummary>;
591
- writeCostSnapshot(snapshot: CostSnapshot): Promise<void>;
592
1040
  getCostSnapshots(): Promise<CostSnapshot[]>;
593
1041
  appendCycleMetrics(snapshot: CycleMetricsSnapshot): Promise<void>;
594
1042
  readCycleMetrics(): Promise<CycleMetricsSnapshot[]>;
@@ -600,6 +1048,20 @@ interface PapiAdapter {
600
1048
  writeRecommendation(rec: Omit<StrategyRecommendation, 'id'>): Promise<StrategyRecommendation>;
601
1049
  getPendingRecommendations(): Promise<StrategyRecommendation[]>;
602
1050
  actionRecommendation(id: string, cycleNumber: number): Promise<void>;
1051
+ dismissRecommendation?(id: string, reason: string): Promise<void>;
1052
+ /**
1053
+ * Append a topic to the strategy review agenda queue.
1054
+ * Topics surface at the next strategy_review prepare phase and are marked addressed after apply.
1055
+ */
1056
+ addAgendaTopic?(topic: {
1057
+ topic: string;
1058
+ source: string;
1059
+ sourceCycle?: number;
1060
+ }): Promise<AgendaTopic>;
1061
+ /** List pending (unaddressed) agenda topics for the current project. */
1062
+ getPendingAgendaTopics?(): Promise<AgendaTopic[]>;
1063
+ /** Mark a set of agenda topics as addressed in the given review cycle. */
1064
+ markAgendaTopicsAddressed?(ids: string[], cycleNumber: number): Promise<void>;
603
1065
  appendDecisionEvent(event: Omit<DecisionEvent, 'id' | 'createdAt'>): Promise<DecisionEvent>;
604
1066
  getDecisionEvents(decisionId: string, limit?: number): Promise<DecisionEvent[]>;
605
1067
  getDecisionEventsSince(cycle: number): Promise<DecisionEvent[]>;
@@ -613,10 +1075,88 @@ interface PapiAdapter {
613
1075
  writeStrategyReview(review: StrategyReviewEntry): Promise<void>;
614
1076
  getLastStrategyReviewCycle(): Promise<number>;
615
1077
  getStrategyReviews(limit?: number, includeFullAnalysis?: boolean): Promise<StrategyReviewEntry[]>;
1078
+ /** Save a strategy review LLM response that failed write-back, for retry next session. */
1079
+ savePendingReviewResponse?(cycleNumber: number, rawResponse: string): Promise<void>;
1080
+ /** Get the pending review response, if any. Returns null if none. */
1081
+ getPendingReviewResponse?(): Promise<{
1082
+ cycleNumber: number;
1083
+ rawResponse: string;
1084
+ } | null>;
1085
+ /** Clear the pending review response after successful retry. */
1086
+ clearPendingReviewResponse?(): Promise<void>;
1087
+ /** Register a document in the doc registry. */
1088
+ registerDoc?(entry: Omit<DocRegistryEntry, 'id' | 'createdAt' | 'updatedAt'>): Promise<DocRegistryEntry>;
1089
+ /** Search documents by filters. */
1090
+ searchDocs?(input: DocSearchInput): Promise<DocRegistryEntry[]>;
1091
+ /** Get a single doc by ID or path. */
1092
+ getDoc?(idOrPath: string): Promise<DocRegistryEntry | null>;
1093
+ /** Update a doc's status (e.g. supersede, archive). */
1094
+ updateDocStatus?(id: string, status: string, supersededBy?: string): Promise<void>;
1095
+ /**
1096
+ * Update a single action on a doc — used by `doc_action_promote` to mark an action
1097
+ * as resolved and link it to the Backlog task that now owns the work.
1098
+ * `actionIndex` is the 0-based index in the doc's `actions` array.
1099
+ */
1100
+ updateDocAction?(docId: string, actionIndex: number, update: {
1101
+ status?: 'pending' | 'resolved';
1102
+ linkedTaskId?: string;
1103
+ }): Promise<void>;
1104
+ /**
1105
+ * Find every (doc, action) pair whose action references the given task and is still pending.
1106
+ * Used by `submitReview` accept-path to auto-resolve doc actions when their owning task ships
1107
+ * (task-1719). Bounded to 50 results per task — anything beyond is bug-shaped.
1108
+ */
1109
+ findPendingDocActionsForTask?(taskDisplayId: string): Promise<Array<{
1110
+ docId: string;
1111
+ actionIndex: number;
1112
+ description: string;
1113
+ }>>;
616
1114
  writeDogfoodEntries?(entries: DogfoodEntry[]): Promise<void>;
617
1115
  getDogfoodLog?(limit?: number): Promise<DogfoodEntry[]>;
618
1116
  getUnactionedDogfoodEntries?(limit?: number): Promise<DogfoodEntry[]>;
619
1117
  updateDogfoodEntryStatus?(id: string, status: DogfoodEntry['status'], linkedTaskId?: string): Promise<void>;
1118
+ /**
1119
+ * Harness inventory (task-1896). Persists per-project skills/agents/hooks/MCP
1120
+ * tools so the dashboard can surface them. All scoped to the adapter's project.
1121
+ * Optional — the md adapter does not implement these; the sync writer is a
1122
+ * no-op when they are absent.
1123
+ */
1124
+ getHarnessInventory?(): Promise<HarnessInventoryEntry[]>;
1125
+ /** Replace the entire inventory for this project in one atomic operation. */
1126
+ replaceHarnessInventory?(entries: HarnessInventoryEntry[]): Promise<void>;
1127
+ /** Read the stored change-detection fingerprint, or null if never synced. */
1128
+ getHarnessState?(): Promise<HarnessState | null>;
1129
+ /** Persist the change-detection fingerprint after a successful sync. */
1130
+ setHarnessState?(fingerprint: string): Promise<void>;
1131
+ /** Append structured cycle learning entries. */
1132
+ appendCycleLearnings?(learnings: CycleLearning[]): Promise<void>;
1133
+ /**
1134
+ * Get cycle learnings, optionally filtered by cycle and/or category.
1135
+ * `includeResolved` defaults to false — resolved discovered-issues (resolved_at IS NOT NULL)
1136
+ * are excluded by default so planner / orient reads stay focused on still-open work (task-1541).
1137
+ */
1138
+ getCycleLearnings?(opts?: {
1139
+ cycleNumber?: number;
1140
+ category?: string;
1141
+ limit?: number;
1142
+ includeResolved?: boolean;
1143
+ }): Promise<CycleLearning[]>;
1144
+ /** Get cross-cycle patterns from learning tags (tags appearing in 2+ cycles). */
1145
+ getCycleLearningPatterns?(): Promise<CycleLearningPattern[]>;
1146
+ /** Set action_ref on a cycle learning to link it to the task that acted on it. */
1147
+ updateCycleLearningActionRef?(learningId: string, taskDisplayId: string): Promise<void>;
1148
+ /**
1149
+ * Mark a cycle learning resolved. Sets resolved_at = NOW() and (optionally) resolved_by.
1150
+ * Pg-only — md / proxy adapters can leave this unimplemented. (task-1541)
1151
+ */
1152
+ markCycleLearningResolved?(learningId: string, resolvedBy?: string): Promise<void>;
1153
+ /**
1154
+ * Auto-resolve discovered-issues (category='issue') whose linked task is Done/Cancelled,
1155
+ * draining the stale-issue clog so orient/planner reads stay clean (task-2079, C291).
1156
+ * Pass a task display_id to resolve only that task's issues (task→Done transition path);
1157
+ * omit for a full sweep. Restricted to category='issue'. Pg-only; returns rows resolved.
1158
+ */
1159
+ resolveLearningsForDoneTasks?(taskDisplayId?: string): Promise<number>;
620
1160
  /**
621
1161
  * Optional: return the current (unsuperseded) North Star statement.
622
1162
  * When implemented, plan and strategy_review use this instead of relying
@@ -636,11 +1176,21 @@ interface PapiAdapter {
636
1176
  setCycle: number;
637
1177
  setAt: string;
638
1178
  } | null>;
1179
+ /**
1180
+ * Optional: persist a new North Star statement, superseding the previous active one.
1181
+ * Called by strategy_review apply and strategy_change apply when the LLM outputs a northStar field.
1182
+ */
1183
+ upsertNorthStar?(statement: string, cycleNumber: number): Promise<void>;
639
1184
  /**
640
1185
  * Optional: return aggregated estimation calibration data from v_estimation_accuracy.
641
1186
  * Used by the planner to adjust effort estimates based on historical patterns.
642
1187
  */
643
1188
  getEstimationCalibration?(): Promise<EstimationCalibrationRow[]>;
1189
+ /**
1190
+ * Optional: return per-module estimation accuracy stats from the last 20 cycles.
1191
+ * Joins build_reports with cycle_tasks on task_id. pg adapter only.
1192
+ */
1193
+ getModuleEstimationStats?(): Promise<ModuleEstimationRow[]>;
644
1194
  /**
645
1195
  * Optional: return recent task comments for plan context.
646
1196
  * Returns up to `limit` comments across all active tasks, newest first.
@@ -669,6 +1219,157 @@ interface PapiAdapter {
669
1219
  * When not implemented, defaults to true (assumes project exists).
670
1220
  */
671
1221
  projectExists?(): Promise<boolean>;
1222
+ /**
1223
+ * Optional: fetch the project name, slug, papi_dir, and repo_url for connection + path-identity verification.
1224
+ * `papi_dir` is the absolute path of the project's `.papi/` directory as stored in the database.
1225
+ * Used by the path-identity guardrail to detect cwd↔stored-path mismatches.
1226
+ * `repo_url` (task-1979, C279) is the GitHub URL the project is associated with, used by the
1227
+ * release tool's remote-project guard. Optional in the result so adapters that don't expose it
1228
+ * can return null without breaking legacy callers.
1229
+ */
1230
+ getProjectInfo?(): Promise<{
1231
+ name: string;
1232
+ slug: string;
1233
+ papi_dir?: string | null;
1234
+ repo_url?: string | null;
1235
+ } | null>;
1236
+ /**
1237
+ * Optional: persist a new value for the project's papi_dir column. Used by the path-identity
1238
+ * guardrail when backfilling legacy null values or applying PAPI_ALLOW_PATH_MIGRATE updates.
1239
+ */
1240
+ setProjectPapiDir?(papiDir: string): Promise<void>;
1241
+ /**
1242
+ * Optional: resolve the user_id that owns the currently-bound project.
1243
+ * Returns null for legacy pre-multi-user projects whose row was created before the
1244
+ * user_id column existed. Used by verifyProject's detect-only legacy-project probe
1245
+ * (task-1621). pg-only — md/proxy adapters can leave it unimplemented.
1246
+ */
1247
+ getProjectOwnerUserId?(): Promise<string | null>;
1248
+ /**
1249
+ * Optional (task-2052, C288): resolve BOTH sides of the owner-gate identity in
1250
+ * one call — who is calling, and who owns the project. On the proxy adapter the
1251
+ * edge function derives callerUserId from the bearer token server-side, so a
1252
+ * hosted/proxy owner is recognised without needing PAPI_USER_ID configured
1253
+ * locally (and a non-owner cannot claim ownership by setting it). Adapters that
1254
+ * cannot resolve the caller (pg/md, where identity comes from local config)
1255
+ * leave this unimplemented and the gate falls back to
1256
+ * getProjectOwnerUserId + config.userId. Consumed by resolveOwnerGate in
1257
+ * packages/server/src/lib/owner-identity.ts.
1258
+ */
1259
+ getOwnerIdentity?(): Promise<OwnerIdentity>;
1260
+ /**
1261
+ * task-2029 (C288): contributor cohort management on the project_contributors
1262
+ * table (MVP slice — current schema, no roles/invites; that's MU-2/task-2070).
1263
+ * Email resolution happens adapter-side (pg: user_profiles join; proxy: edge
1264
+ * function) so the tool layer never touches identity tables directly. The
1265
+ * OWNER-ONLY rule is enforced in the tool layer via resolveOwnerGate AND
1266
+ * (proxy) server-side in the edge function — defence in depth, because the
1267
+ * npm server runs on the user's machine. pg/proxy only; md leaves these
1268
+ * unimplemented (no cohort concept in local single-user mode).
1269
+ */
1270
+ listContributors?(): Promise<ContributorEntry[]>;
1271
+ addContributorByEmail?(email: string): Promise<ContributorEntry>;
1272
+ removeContributorByEmail?(email: string): Promise<boolean>;
1273
+ /**
1274
+ * Optional (task-1888 / 1885-C): list all projects the authenticated user owns.
1275
+ * Scopes to the auth user (proxy: bearer; pg: the bound project's user_id). Used by the
1276
+ * project_list MCP tool so a user on one API key across many folders can see and select
1277
+ * the right project from the LLM.
1278
+ */
1279
+ listUserProjects?(): Promise<ProjectSummary[]>;
1280
+ /**
1281
+ * Optional (task-1888 / 1885-C): create a project for the authenticated user, idempotent on
1282
+ * papi_dir then name — re-running in the same workspace (or with the same name) returns the
1283
+ * EXISTING project instead of creating a duplicate (fixes the SUP-2026-013 duplicate-project
1284
+ * bug). NO auto-plan / NO seeded backlog — an empty project only. `papiDir` is omitted on the
1285
+ * stateless remote transport (no real workspace there).
1286
+ */
1287
+ createUserProject?(input: {
1288
+ name: string;
1289
+ papiDir?: string;
1290
+ repoUrl?: string;
1291
+ }): Promise<ProjectLifecycleResult>;
1292
+ /**
1293
+ * Optional (task-1888 / 1885-C): resolve a project the user owns by id or slug and (when a
1294
+ * workspace signal is available) stamp it as the papi_dir for the current folder. Callable from
1295
+ * the LLM ("switch to golf"). Fails closed if the identifier does not resolve to a project the
1296
+ * auth user owns.
1297
+ */
1298
+ switchUserProject?(identifier: string, papiDir?: string): Promise<ProjectLifecycleResult>;
1299
+ /**
1300
+ * Optional (task-2119): return a view of this adapter scoped to a DIFFERENT
1301
+ * project for a single call, without rebinding the session adapter (unlike
1302
+ * switchUserProject) and without persisting anything. Callers MUST validate
1303
+ * the target project belongs to the auth user first (listUserProjects) —
1304
+ * this method does not gate ownership itself.
1305
+ */
1306
+ withProject?(projectId: string): PapiAdapter;
1307
+ /** Optional: insert a plan run record for telemetry. Non-blocking — callers should catch errors. */
1308
+ insertPlanRun?(entry: PlanRunEntry): Promise<void>;
1309
+ /** Submit a bug report with diagnostics for cross-project visibility. */
1310
+ submitBugReport?(report: Omit<BugReport, 'id' | 'createdAt'>): Promise<BugReport>;
1311
+ /**
1312
+ * Optional (task-2061): the authenticated user's tier + metered tool-call
1313
+ * count for the current month. Only the proxy adapter implements this —
1314
+ * its identity is server-derived from the bearer. Local adapters (pg/md)
1315
+ * deliberately omit it, so local sessions are never metered or blocked.
1316
+ */
1317
+ getMeteredUsage?(): Promise<{
1318
+ tier: string;
1319
+ monthlyToolCalls: number;
1320
+ }>;
1321
+ }
1322
+ /** A project the auth user owns — returned by listUserProjects (task-1888 / 1885-C). */
1323
+ /**
1324
+ * task-2052 (C288): both sides of the owner-gate identity. callerUserId is the
1325
+ * authenticated caller (proxy: bearer-derived server-side; null when the
1326
+ * transport cannot resolve a caller); ownerUserId is the project's owner row.
1327
+ */
1328
+ interface OwnerIdentity {
1329
+ callerUserId: string | null;
1330
+ ownerUserId: string | null;
1331
+ }
1332
+ /**
1333
+ * task-2029 (C288): one row of a project's contributor cohort
1334
+ * (project_contributors joined to user_profiles for the human-readable bits).
1335
+ */
1336
+ interface ContributorEntry {
1337
+ userId: string;
1338
+ email: string | null;
1339
+ displayName: string | null;
1340
+ role: string;
1341
+ createdAt: string;
1342
+ }
1343
+ interface ProjectSummary {
1344
+ id: string;
1345
+ name: string;
1346
+ slug: string;
1347
+ papi_dir?: string | null;
1348
+ }
1349
+ /** Result of project_create / project_switch (task-1888 / 1885-C). */
1350
+ interface ProjectLifecycleResult {
1351
+ projectId: string;
1352
+ name: string;
1353
+ slug: string;
1354
+ papiDir?: string | null;
1355
+ /** True when a new project row was created; false when an existing one was returned (idempotent). */
1356
+ created: boolean;
1357
+ }
1358
+ /** Plan quality telemetry — one row per plan apply. */
1359
+ interface PlanRunEntry {
1360
+ cycleNumber: number;
1361
+ contextBytes?: number;
1362
+ durationMs?: number;
1363
+ taskCountIn?: number;
1364
+ taskCountOut?: number;
1365
+ backlogDepth?: number;
1366
+ notes?: string;
1367
+ /** Context utilisation ratio (0-1) and other token metrics captured at apply time. */
1368
+ tokenUsage?: {
1369
+ utilisation?: number;
1370
+ };
1371
+ /** Origin of this plan run: "mcp-server" (default) or "dashboard". */
1372
+ source?: string;
672
1373
  }
673
1374
  /** Lean plan context returned by adapters that support SQL aggregation. */
674
1375
  interface PlanContextSummary {
@@ -739,8 +1440,15 @@ declare class MdFileAdapter implements PapiAdapter {
739
1440
  readPlanningLog(): Promise<PlanningLog>;
740
1441
  /** Read the Cycle Health table from PLANNING_LOG.md. */
741
1442
  getCycleHealth(): Promise<CycleHealth>;
742
- /** Read all Active Decisions from ACTIVE_DECISIONS.md. */
743
- getActiveDecisions(): Promise<ActiveDecision[]>;
1443
+ /**
1444
+ * Read Active Decisions from ACTIVE_DECISIONS.md.
1445
+ *
1446
+ * Default filters out retired ADs (outcome ∈ abandoned/superseded/resolved or superseded=true).
1447
+ * Pass { includeRetired: true } for management/triage surfaces. See PapiAdapter docstring.
1448
+ */
1449
+ getActiveDecisions(options?: {
1450
+ includeRetired?: boolean;
1451
+ }): Promise<ActiveDecision[]>;
744
1452
  /** Read cycle log entries (newest first), optionally limited to {@link limit} entries. */
745
1453
  getCycleLog(limit?: number): Promise<CycleLogEntry[]>;
746
1454
  getCycleLogSince(cycleNumber: number): Promise<CycleLogEntry[]>;
@@ -755,7 +1463,7 @@ declare class MdFileAdapter implements PapiAdapter {
755
1463
  /** Get strategy reviews — md adapter returns empty (reviews live in cycle log). */
756
1464
  getStrategyReviews(_limit?: number, _includeFullAnalysis?: boolean): Promise<StrategyReviewEntry[]>;
757
1465
  /** Update or insert an Active Decision block by ID. */
758
- updateActiveDecision(id: string, body: string, cycleNumber?: number): Promise<void>;
1466
+ updateActiveDecision(id: string, body: string, cycleNumber?: number, _action?: string): Promise<void>;
759
1467
  /** Query the cycle board, optionally filtering by status/priority/phase/etc. */
760
1468
  queryBoard(options?: BoardQueryOptions): Promise<CycleTask[]>;
761
1469
  /** Look up a single task by ID, returning null if not found. */
@@ -774,11 +1482,39 @@ declare class MdFileAdapter implements PapiAdapter {
774
1482
  updateTask(id: string, updates: Partial<Omit<CycleTask, 'id'>>, options?: UpdateTaskOptions): Promise<void>;
775
1483
  /** Shorthand to update only the status field of a task. */
776
1484
  updateTaskStatus(id: string, status: TaskStatus): Promise<void>;
1485
+ /**
1486
+ * task-1763 (C293): atomic compare-and-swap task claim. First-claim-wins —
1487
+ * sets assigneeId only if the task is currently unclaimed. The markdown adapter
1488
+ * is single-process, so the read-check-write is trivially atomic here; the real
1489
+ * concurrency guarantee lives in the pg adapter's RETURNING CAS. Returns the
1490
+ * claimed task, or null if it was already claimed or does not exist.
1491
+ *
1492
+ * task-2071 (MU-3): the pooled-task invariant is `assigneeId == null && cycle
1493
+ * == null` — a task already pulled into someone's cycle is NOT in the pool and
1494
+ * cannot be claimed. Sets claimSource='pool' on success.
1495
+ */
1496
+ claimTask(taskId: string, assigneeId: string): Promise<CycleTask | null>;
1497
+ /**
1498
+ * task-2071 (C293, MU-3): claimer-only release. Clears assigneeId + claimSource
1499
+ * only if the task is currently assigned to `assigneeId` and has not entered
1500
+ * review. Returns the unclaimed task, or null if the caller is not the claimer,
1501
+ * the task has progressed, or it does not exist.
1502
+ */
1503
+ unclaimTask(taskId: string, assigneeId: string): Promise<CycleTask | null>;
1504
+ /**
1505
+ * task-2072 (C293, MU-4): atomic review claim. Sets reviewerId only if the task
1506
+ * is In Review and not yet claimed for review (reviewerId == null). First-claim-
1507
+ * wins. Returns the claimed task, or null if already review-claimed / not In
1508
+ * Review / missing.
1509
+ */
1510
+ claimReview(taskId: string, reviewerId: string): Promise<CycleTask | null>;
777
1511
  recordTransition(_taskId: string, _fromStatus: string, _toStatus: string, _changedBy?: string): Promise<void>;
778
1512
  /** Insert a new build report at the top of BUILD_REPORTS.md. */
779
1513
  appendBuildReport(report: BuildReport): Promise<void>;
780
1514
  /** Return the most recent {@link count} build reports. */
781
1515
  getRecentBuildReports(count: number): Promise<BuildReport[]>;
1516
+ /** Return the number of build reports for a specific task. */
1517
+ getBuildReportCountForTask(taskId: string): Promise<number>;
782
1518
  /** Return all build reports from cycles >= {@link cycleNumber}. */
783
1519
  getBuildReportsSince(cycleNumber: number): Promise<BuildReport[]>;
784
1520
  /** Return recent human reviews from REVIEWS.md (newest first), optionally limited to {@link count}. */
@@ -814,10 +1550,9 @@ declare class MdFileAdapter implements PapiAdapter {
814
1550
  appendToolMetric(metric: ToolCallMetric): Promise<void>;
815
1551
  /** Read all tool call metrics from METRICS.md. */
816
1552
  readToolMetrics(): Promise<ToolCallMetric[]>;
1553
+ hasToolMilestone(name: string): Promise<boolean>;
817
1554
  /** Aggregate tool call metrics into a cost summary, optionally filtered by cycle. */
818
1555
  getCostSummary(cycleNumber?: number): Promise<CostSummary>;
819
- /** Write a cost snapshot to the Cost Summary section of METRICS.md. */
820
- writeCostSnapshot(snapshot: CostSnapshot): Promise<void>;
821
1556
  /** Read all cost snapshots from the Cost Summary section of METRICS.md. */
822
1557
  getCostSnapshots(): Promise<CostSnapshot[]>;
823
1558
  /** Append a cycle metrics snapshot to CYCLE_METRICS.md. */
@@ -841,6 +1576,13 @@ declare class MdFileAdapter implements PapiAdapter {
841
1576
  getPendingRecommendations(): Promise<StrategyRecommendation[]>;
842
1577
  /** Mark a recommendation as actioned. */
843
1578
  actionRecommendation(id: string, cycleNumber: number): Promise<void>;
1579
+ addAgendaTopic(input: {
1580
+ topic: string;
1581
+ source: string;
1582
+ sourceCycle?: number;
1583
+ }): Promise<AgendaTopic>;
1584
+ getPendingAgendaTopics(): Promise<AgendaTopic[]>;
1585
+ markAgendaTopicsAddressed(ids: string[], cycleNumber: number): Promise<void>;
844
1586
  appendDecisionEvent(event: Omit<DecisionEvent, 'id' | 'createdAt'>): Promise<DecisionEvent>;
845
1587
  getDecisionEvents(decisionId: string, limit?: number): Promise<DecisionEvent[]>;
846
1588
  getDecisionEventsSince(cycle: number): Promise<DecisionEvent[]>;
@@ -851,6 +1593,13 @@ declare class MdFileAdapter implements PapiAdapter {
851
1593
  private parseDecisionScores;
852
1594
  logEntityReferences(_refs: Omit<EntityReference, 'id' | 'createdAt'>[]): Promise<void>;
853
1595
  getDecisionUsage(_currentCycle: number): Promise<DecisionUsageSummary[]>;
1596
+ getCurrentNorthStar(): Promise<string | null>;
1597
+ getNorthStarSetCycle(): Promise<number | null>;
1598
+ getNorthStarStaleness(): Promise<{
1599
+ setCycle: number;
1600
+ setAt: string;
1601
+ } | null>;
1602
+ upsertNorthStar(statement: string, _cycleNumber: number): Promise<void>;
854
1603
  }
855
1604
 
856
1605
  /** Validate and normalise an effort string to EffortSize. Returns the value or undefined if invalid. */
@@ -862,7 +1611,7 @@ declare function parseEffortSize(value: string): EffortSize | undefined;
862
1611
  */
863
1612
  declare function parseBuildHandoff(markdown: string): BuildHandoff | null;
864
1613
  /** Serialize a BuildHandoff object back into the standard markdown format. */
865
- declare function serializeBuildHandoff(handoff: BuildHandoff): string;
1614
+ declare function serializeBuildHandoff(raw: BuildHandoff | string): string;
866
1615
 
867
1616
  /** Calculate Tier 1 cycle metrics from build reports. */
868
1617
  declare function calculateCycleMetrics(reports: BuildReport[], currentCycle: number, window?: number): {
@@ -963,6 +1712,9 @@ interface Project {
963
1712
  name: string;
964
1713
  repo_url?: string;
965
1714
  papi_dir?: string;
1715
+ user_id?: string;
1716
+ root_commit_hash?: string;
1717
+ resolution_method?: string;
966
1718
  created_at: string;
967
1719
  updated_at: string;
968
1720
  }
@@ -1082,4 +1834,4 @@ interface ConflictAlert {
1082
1834
  resolved_at?: string;
1083
1835
  }
1084
1836
 
1085
- export { type Acknowledgement, type AcknowledgementStatus, type ActiveDecision, type AutoReview, type AutoReviewFinding, type AutoReviewVerdict, type BoardQueryOptions, type BriefImplication, type BuildHandoff, type BuildPatterns, type BuildReport, type ClusterEntry, type ClusterResult, type CommandCost, type Confidence, type ConflictAlert, type ConflictAlertStatus, type ConflictRaisedBy, type ConflictType, type ContextHashes, type ContextTier, type ContextUtilisationSummary, type CostSnapshot, type CostSummary, type Cycle, type CycleEstimationAccuracy, type CycleHealth, type CycleLogEntry, type CycleMetricsSnapshot, type CycleStatus, type CycleTask, type CycleVelocity, type DecisionEvent, type DecisionEventSource, type DecisionEventType, type DecisionScore, type DecisionUsageSummary, type DiscoveryCanvas, type DogfoodEntry, type EffortSize, type EntityReference, type EstimationCalibrationRow, type Horizon, type HorizonStatus, type HumanReview, MdFileAdapter, type MilestoneDependency, type NorthStar, type PapiAdapter, type Phase, type PhaseStatus, type PlanContextSummary, type PlanWriteBackPayload, type PlanWriteBackResult, type PlanningLog, type Project, type ProjectContribution, type ProjectContributionStatus, type RecommendationEffectivenessRow, type RecommendationStatus, type RecommendationType, type RecurringFeedback, type RecurringSurprise, type Registries, type ReviewPatterns, type ReviewStage, type ReviewVerdict, type SharedDecision, type SharedDecisionConfidence, type SharedDecisionStatus, type SharedMilestone, type SharedMilestoneStatus, type Stage, type StageStatus, type StateTransition, type StrategyRecommendation, type StrategyReviewEntry, TASK_TYPE_TIERS, type TaskComplexity, type TaskMaturity, type TaskPriority, type TaskStatus, type TaskType, type TextClusterer, type ToolCallMetric, type UpdateTaskOptions, VALID_TRANSITIONS, aggregateCostSummary, appendToolMetricToContent, calculateCycleMetrics, detectBuildPatterns, detectReviewPatterns, hasBuildPatterns, hasReviewPatterns, isValidStatus, isValidTransition, parseBuildHandoff, parseCycles, parseEffortSize, parsePhases, parseRegistries, parseReviews, parseToolMetrics, prependCycle, prependReview, serializeBuildHandoff, serializeCycle, serializeCycles, serializePhases, serializeRegistries, serializeReview, serializeToolMetric, validateTransition, writePhasesToContent };
1837
+ export { type Acknowledgement, type AcknowledgementStatus, type ActiveDecision, type AgendaTopic, type AgendaTopicStatus, type AutoReview, type AutoReviewFinding, type AutoReviewVerdict, type BoardQueryOptions, type BriefImplication, type BugReport, type BuildHandoff, type BuildPatterns, type BuildReport, type ClusterEntry, type ClusterResult, type CommandCost, type Confidence, type ConflictAlert, type ConflictAlertStatus, type ConflictRaisedBy, type ConflictType, type ContextHashes, type ContextTier, type ContextUtilisationSummary, type ContributorEntry, type CostSnapshot, type CostSummary, type Cycle, type CycleEstimationAccuracy, type CycleHealth, type CycleLearning, type CycleLearningPattern, type CycleLogEntry, type CycleMetricsSnapshot, type CycleStatus, type CycleTask, type CycleVelocity, type DecisionEvent, type DecisionEventSource, type DecisionEventType, type DecisionScore, type DecisionUsageSummary, type DiscoveryCanvas, type DocRegistryEntry, type DocSearchInput, type DocVisibility, type DogfoodEntry, type EffortSize, type EntityReference, type EstimationCalibrationRow, type HarnessInventoryEntry, type HarnessState, type Horizon, type HorizonStatus, type HumanReview, MdFileAdapter, type MetricDelta, type MilestoneDependency, type ModuleEstimationRow, type NorthStar, type OwnerActionInput, type OwnerActionRow, type OwnerIdentity, type PapiAdapter, type Phase, type PhaseStatus, type PlanContextSummary, type PlanRunEntry, type PlanWriteBackPayload, type PlanWriteBackResult, type PlanningLog, type Project, type ProjectContribution, type ProjectContributionStatus, type ProjectLifecycleResult, type ProjectSummary, type RecommendationEffectivenessRow, type RecommendationStatus, type RecommendationType, type RecurringFeedback, type RecurringSurprise, type Registries, type ReviewPatterns, type ReviewStage, type ReviewVerdict, type SharedDecision, type SharedDecisionConfidence, type SharedDecisionStatus, type SharedMilestone, type SharedMilestoneStatus, type SiblingRepoTask, type Stage, type StageStatus, type StateTransition, type StrategyRecommendation, type StrategyReviewEntry, TASK_TYPE_TIERS, type TaskComplexity, type TaskMaturity, type TaskPriority, type TaskStatus, type TaskType, type TextClusterer, type ToolCallMetric, type UpdateTaskOptions, VALID_TRANSITIONS, aggregateCostSummary, appendToolMetricToContent, calculateCycleMetrics, detectBuildPatterns, detectReviewPatterns, hasBuildPatterns, hasReviewPatterns, isValidStatus, isValidTransition, parseBuildHandoff, parseCycles, parseEffortSize, parsePhases, parseRegistries, parseReviews, parseToolMetrics, prependCycle, prependReview, serializeBuildHandoff, serializeCycle, serializeCycles, serializePhases, serializeRegistries, serializeReview, serializeToolMetric, validateTransition, writePhasesToContent };