@papi-ai/server 0.7.5 → 0.7.6

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.
package/dist/index.js CHANGED
@@ -4747,18 +4747,20 @@ var init_dist3 = __esm({
4747
4747
  }
4748
4748
  async getProject(id) {
4749
4749
  const [row] = await this.sql`
4750
- SELECT * FROM projects WHERE id = ${id}
4750
+ SELECT id, slug, name, repo_url, papi_dir, user_id, created_at, updated_at FROM projects WHERE id = ${id}
4751
4751
  `;
4752
4752
  return row ?? null;
4753
4753
  }
4754
4754
  async listProjects(filter) {
4755
4755
  if (filter?.slug) {
4756
4756
  return this.sql`
4757
- SELECT * FROM projects WHERE slug = ${filter.slug} ORDER BY created_at
4757
+ SELECT id, slug, name, repo_url, papi_dir, user_id, created_at, updated_at FROM projects WHERE slug = ${filter.slug} ORDER BY created_at
4758
+ LIMIT 200 -- bounded: project list per user
4758
4759
  `;
4759
4760
  }
4760
4761
  return this.sql`
4761
- SELECT * FROM projects ORDER BY created_at
4762
+ SELECT id, slug, name, repo_url, papi_dir, user_id, created_at, updated_at FROM projects ORDER BY created_at
4763
+ LIMIT 200 -- bounded: project list per user
4762
4764
  `;
4763
4765
  }
4764
4766
  async updateProject(id, updates) {
@@ -4818,7 +4820,7 @@ var init_dist3 = __esm({
4818
4820
  }
4819
4821
  async getSharedDecision(id) {
4820
4822
  const [row] = await this.sql`
4821
- SELECT * FROM shared_decisions WHERE id = ${id}
4823
+ SELECT id, display_id, title, decision, confidence, status, origin_project_id, evidence, superseded_by_id, created_at, updated_at, archived_at FROM shared_decisions WHERE id = ${id}
4822
4824
  `;
4823
4825
  return row ?? null;
4824
4826
  }
@@ -4838,7 +4840,8 @@ var init_dist3 = __esm({
4838
4840
  }
4839
4841
  if (conditions.length === 0) {
4840
4842
  return this.sql`
4841
- SELECT * FROM shared_decisions WHERE archived_at IS NULL ORDER BY created_at
4843
+ SELECT id, display_id, title, decision, confidence, status, origin_project_id, evidence, superseded_by_id, created_at, updated_at, archived_at FROM shared_decisions WHERE archived_at IS NULL ORDER BY created_at
4844
+ LIMIT 200 -- cross-project shared decisions; 200 is ample
4842
4845
  `;
4843
4846
  }
4844
4847
  let where = conditions[0];
@@ -4846,7 +4849,8 @@ var init_dist3 = __esm({
4846
4849
  where = this.sql`${where} AND ${conditions[i]}`;
4847
4850
  }
4848
4851
  return this.sql`
4849
- SELECT * FROM shared_decisions WHERE ${where} ORDER BY created_at
4852
+ SELECT id, display_id, title, decision, confidence, status, origin_project_id, evidence, superseded_by_id, created_at, updated_at, archived_at FROM shared_decisions WHERE ${where} ORDER BY created_at
4853
+ LIMIT 200
4850
4854
  `;
4851
4855
  }
4852
4856
  async updateSharedDecision(id, updates) {
@@ -4915,7 +4919,7 @@ var init_dist3 = __esm({
4915
4919
  }
4916
4920
  async getAcknowledgement(id) {
4917
4921
  const [row] = await this.sql`
4918
- SELECT * FROM acknowledgements WHERE id = ${id}
4922
+ SELECT id, decision_id, project_id, status, comments, responded_at, created_at, updated_at FROM acknowledgements WHERE id = ${id}
4919
4923
  `;
4920
4924
  return row ?? null;
4921
4925
  }
@@ -4932,7 +4936,8 @@ var init_dist3 = __esm({
4932
4936
  }
4933
4937
  if (conditions.length === 0) {
4934
4938
  return this.sql`
4935
- SELECT * FROM acknowledgements ORDER BY created_at
4939
+ SELECT id, decision_id, project_id, status, comments, responded_at, created_at, updated_at FROM acknowledgements ORDER BY created_at
4940
+ LIMIT 500 -- acknowledgements grow with decisions × projects
4936
4941
  `;
4937
4942
  }
4938
4943
  let where = conditions[0];
@@ -4940,7 +4945,8 @@ var init_dist3 = __esm({
4940
4945
  where = this.sql`${where} AND ${conditions[i]}`;
4941
4946
  }
4942
4947
  return this.sql`
4943
- SELECT * FROM acknowledgements WHERE ${where} ORDER BY created_at
4948
+ SELECT id, decision_id, project_id, status, comments, responded_at, created_at, updated_at FROM acknowledgements WHERE ${where} ORDER BY created_at
4949
+ LIMIT 500
4944
4950
  `;
4945
4951
  }
4946
4952
  // -------------------------------------------------------------------------
@@ -4956,7 +4962,7 @@ var init_dist3 = __esm({
4956
4962
  }
4957
4963
  async getSharedMilestone(id) {
4958
4964
  const [row] = await this.sql`
4959
- SELECT * FROM shared_milestones WHERE id = ${id}
4965
+ SELECT id, title, description, status, target_date, completed_at, created_at, updated_at, archived_at FROM shared_milestones WHERE id = ${id}
4960
4966
  `;
4961
4967
  return row ?? null;
4962
4968
  }
@@ -4970,7 +4976,8 @@ var init_dist3 = __esm({
4970
4976
  }
4971
4977
  if (conditions.length === 0) {
4972
4978
  return this.sql`
4973
- SELECT * FROM shared_milestones WHERE archived_at IS NULL ORDER BY created_at
4979
+ SELECT id, title, description, status, target_date, completed_at, created_at, updated_at, archived_at FROM shared_milestones WHERE archived_at IS NULL ORDER BY created_at
4980
+ LIMIT 200
4974
4981
  `;
4975
4982
  }
4976
4983
  let where = conditions[0];
@@ -4978,7 +4985,8 @@ var init_dist3 = __esm({
4978
4985
  where = this.sql`${where} AND ${conditions[i]}`;
4979
4986
  }
4980
4987
  return this.sql`
4981
- SELECT * FROM shared_milestones WHERE ${where} ORDER BY created_at
4988
+ SELECT id, title, description, status, target_date, completed_at, created_at, updated_at, archived_at FROM shared_milestones WHERE ${where} ORDER BY created_at
4989
+ LIMIT 200
4982
4990
  `;
4983
4991
  }
4984
4992
  async updateSharedMilestone(id, updates) {
@@ -5016,7 +5024,7 @@ var init_dist3 = __esm({
5016
5024
  }
5017
5025
  async listMilestoneDependencies(milestoneId) {
5018
5026
  return this.sql`
5019
- SELECT * FROM milestone_dependencies WHERE milestone_id = ${milestoneId}
5027
+ SELECT id, milestone_id, depends_on_id FROM milestone_dependencies WHERE milestone_id = ${milestoneId}
5020
5028
  `;
5021
5029
  }
5022
5030
  async deleteMilestoneDependency(id) {
@@ -5045,7 +5053,7 @@ var init_dist3 = __esm({
5045
5053
  }
5046
5054
  async getProjectContribution(id) {
5047
5055
  const [row] = await this.sql`
5048
- SELECT * FROM project_contributions WHERE id = ${id}
5056
+ SELECT id, milestone_id, project_id, status, required_phases, notes, delivered_at, created_at, updated_at FROM project_contributions WHERE id = ${id}
5049
5057
  `;
5050
5058
  return row ?? null;
5051
5059
  }
@@ -5062,7 +5070,8 @@ var init_dist3 = __esm({
5062
5070
  }
5063
5071
  if (conditions.length === 0) {
5064
5072
  return this.sql`
5065
- SELECT * FROM project_contributions ORDER BY created_at
5073
+ SELECT id, milestone_id, project_id, status, required_phases, notes, delivered_at, created_at, updated_at FROM project_contributions ORDER BY created_at
5074
+ LIMIT 500
5066
5075
  `;
5067
5076
  }
5068
5077
  let where = conditions[0];
@@ -5070,7 +5079,8 @@ var init_dist3 = __esm({
5070
5079
  where = this.sql`${where} AND ${conditions[i]}`;
5071
5080
  }
5072
5081
  return this.sql`
5073
- SELECT * FROM project_contributions WHERE ${where} ORDER BY created_at
5082
+ SELECT id, milestone_id, project_id, status, required_phases, notes, delivered_at, created_at, updated_at FROM project_contributions WHERE ${where} ORDER BY created_at
5083
+ LIMIT 500
5074
5084
  `;
5075
5085
  }
5076
5086
  async updateProjectContribution(id, updates) {
@@ -5108,7 +5118,7 @@ var init_dist3 = __esm({
5108
5118
  }
5109
5119
  async getActiveNorthStar(projectId) {
5110
5120
  const [row] = await this.sql`
5111
- SELECT * FROM north_stars
5121
+ SELECT id, project_id, statement, set_at, superseded_by_id, superseded_at, created_at FROM north_stars
5112
5122
  WHERE project_id = ${projectId}
5113
5123
  AND superseded_by_id IS NULL
5114
5124
  ORDER BY created_at DESC
@@ -5126,7 +5136,8 @@ var init_dist3 = __esm({
5126
5136
  }
5127
5137
  if (conditions.length === 0) {
5128
5138
  return this.sql`
5129
- SELECT * FROM north_stars ORDER BY created_at
5139
+ SELECT id, project_id, statement, set_at, superseded_by_id, superseded_at, created_at FROM north_stars ORDER BY created_at
5140
+ LIMIT 200
5130
5141
  `;
5131
5142
  }
5132
5143
  let where = conditions[0];
@@ -5134,7 +5145,8 @@ var init_dist3 = __esm({
5134
5145
  where = this.sql`${where} AND ${conditions[i]}`;
5135
5146
  }
5136
5147
  return this.sql`
5137
- SELECT * FROM north_stars WHERE ${where} ORDER BY created_at
5148
+ SELECT id, project_id, statement, set_at, superseded_by_id, superseded_at, created_at FROM north_stars WHERE ${where} ORDER BY created_at
5149
+ LIMIT 200
5138
5150
  `;
5139
5151
  }
5140
5152
  /**
@@ -5148,7 +5160,7 @@ var init_dist3 = __esm({
5148
5160
  return this.sql.begin(async (_tx) => {
5149
5161
  const tx = _tx;
5150
5162
  const [old] = await tx`
5151
- SELECT * FROM north_stars WHERE id = ${id}
5163
+ SELECT id, project_id, statement, set_at, superseded_by_id, superseded_at, created_at FROM north_stars WHERE id = ${id}
5152
5164
  `;
5153
5165
  if (!old) {
5154
5166
  throw new Error(`NorthStar not found: ${id}`);
@@ -5191,7 +5203,7 @@ var init_dist3 = __esm({
5191
5203
  }
5192
5204
  async getConflictAlert(id) {
5193
5205
  const [row] = await this.sql`
5194
- SELECT * FROM conflict_alerts WHERE id = ${id}
5206
+ SELECT id, conflict_type, title, description, status, decision_a_id, decision_b_id, north_star_id, resolution_decision_id, raised_by, created_at, updated_at, resolved_at FROM conflict_alerts WHERE id = ${id}
5195
5207
  `;
5196
5208
  return row ?? null;
5197
5209
  }
@@ -5205,7 +5217,8 @@ var init_dist3 = __esm({
5205
5217
  }
5206
5218
  if (conditions.length === 0) {
5207
5219
  return this.sql`
5208
- SELECT * FROM conflict_alerts ORDER BY created_at
5220
+ SELECT id, conflict_type, title, description, status, decision_a_id, decision_b_id, north_star_id, resolution_decision_id, raised_by, created_at, updated_at, resolved_at FROM conflict_alerts ORDER BY created_at
5221
+ LIMIT 200
5209
5222
  `;
5210
5223
  }
5211
5224
  let where = conditions[0];
@@ -5213,7 +5226,8 @@ var init_dist3 = __esm({
5213
5226
  where = this.sql`${where} AND ${conditions[i]}`;
5214
5227
  }
5215
5228
  return this.sql`
5216
- SELECT * FROM conflict_alerts WHERE ${where} ORDER BY created_at
5229
+ SELECT id, conflict_type, title, description, status, decision_a_id, decision_b_id, north_star_id, resolution_decision_id, raised_by, created_at, updated_at, resolved_at FROM conflict_alerts WHERE ${where} ORDER BY created_at
5230
+ LIMIT 200
5217
5231
  `;
5218
5232
  }
5219
5233
  async updateConflictAlert(id, updates) {
@@ -6176,19 +6190,23 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
6176
6190
  }
6177
6191
  async getActiveDecisions() {
6178
6192
  const rows = await this.sql`
6179
- SELECT * FROM active_decisions
6193
+ SELECT id, display_id, title, confidence, superseded, superseded_by, created_cycle, modified_cycle, body, outcome, revision_count
6194
+ FROM active_decisions
6180
6195
  WHERE project_id = ${this.projectId}
6181
6196
  ORDER BY display_id
6197
+ LIMIT 200 -- bounded: ADs are bounded by project lifecycle, 200 is a safe ceiling
6182
6198
  `;
6183
6199
  return rows.map(rowToActiveDecision);
6184
6200
  }
6185
6201
  async getSiblingAds(projectIds) {
6186
6202
  if (projectIds.length === 0) return [];
6187
6203
  const rows = await this.sql`
6188
- SELECT *, project_id FROM active_decisions
6204
+ SELECT id, display_id, title, confidence, superseded, superseded_by, created_cycle, modified_cycle, body, outcome, revision_count, project_id
6205
+ FROM active_decisions
6189
6206
  WHERE project_id = ANY(${projectIds}::uuid[])
6190
6207
  AND superseded = false
6191
6208
  ORDER BY project_id, display_id
6209
+ LIMIT 500 -- cross-project query: 500 covers up to 10 projects × 50 ADs each
6192
6210
  `;
6193
6211
  return rows.map((row) => ({
6194
6212
  ...rowToActiveDecision(row),
@@ -6198,7 +6216,8 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
6198
6216
  async getCycleLog(limit) {
6199
6217
  if (limit != null) {
6200
6218
  const rows2 = await this.sql`
6201
- SELECT * FROM planning_log_entries
6219
+ SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
6220
+ FROM planning_log_entries
6202
6221
  WHERE project_id = ${this.projectId}
6203
6222
  ORDER BY cycle_number DESC
6204
6223
  LIMIT ${limit}
@@ -6206,18 +6225,22 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
6206
6225
  return rows2.map(rowToCycleLogEntry);
6207
6226
  }
6208
6227
  const rows = await this.sql`
6209
- SELECT * FROM planning_log_entries
6228
+ SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
6229
+ FROM planning_log_entries
6210
6230
  WHERE project_id = ${this.projectId}
6211
6231
  ORDER BY cycle_number DESC
6232
+ LIMIT 500 -- 500 cycles is ~years of history, sufficient ceiling
6212
6233
  `;
6213
6234
  return rows.map(rowToCycleLogEntry);
6214
6235
  }
6215
6236
  async getCycleLogSince(cycleNumber) {
6216
6237
  const rows = await this.sql`
6217
- SELECT * FROM planning_log_entries
6238
+ SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
6239
+ FROM planning_log_entries
6218
6240
  WHERE project_id = ${this.projectId}
6219
6241
  AND cycle_number >= ${cycleNumber}
6220
6242
  ORDER BY cycle_number DESC
6243
+ LIMIT 500 -- bounded by cycle range, 500 is a safe ceiling
6221
6244
  `;
6222
6245
  return rows.map(rowToCycleLogEntry);
6223
6246
  }
@@ -6449,7 +6472,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
6449
6472
  const sinceCycle = input.sinceCycle ?? 0;
6450
6473
  const hasPending = input.hasPendingActions ?? false;
6451
6474
  const rows = await this.sql`
6452
- SELECT * FROM doc_registry
6475
+ SELECT id, title, type, path, status, summary, tags, cycle_created, cycle_updated, superseded_by, actions, created_at, updated_at
6476
+ FROM doc_registry
6453
6477
  WHERE project_id = ${this.projectId}
6454
6478
  AND (${matchAllStatuses} OR status = ${status})
6455
6479
  AND (${input.type ?? null}::text IS NULL OR type = ${input.type ?? null})
@@ -6479,9 +6503,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
6479
6503
  async getDoc(idOrPath) {
6480
6504
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(idOrPath);
6481
6505
  const rows = isUuid ? await this.sql`
6482
- SELECT * FROM doc_registry WHERE id = ${idOrPath} AND project_id = ${this.projectId}
6506
+ SELECT id, title, type, path, status, summary, tags, cycle_created, cycle_updated, superseded_by, actions, created_at, updated_at
6507
+ FROM doc_registry WHERE id = ${idOrPath} AND project_id = ${this.projectId}
6483
6508
  ` : await this.sql`
6484
- SELECT * FROM doc_registry WHERE path = ${idOrPath} AND project_id = ${this.projectId}
6509
+ SELECT id, title, type, path, status, summary, tags, cycle_created, cycle_updated, superseded_by, actions, created_at, updated_at
6510
+ FROM doc_registry WHERE path = ${idOrPath} AND project_id = ${this.projectId}
6485
6511
  `;
6486
6512
  if (rows.length === 0) return null;
6487
6513
  const r = rows[0];
@@ -6640,9 +6666,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
6640
6666
  async queryBoard(options) {
6641
6667
  if (!options) {
6642
6668
  const rows2 = await this.sql`
6643
- SELECT * FROM cycle_tasks
6669
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, updated_at
6670
+ FROM cycle_tasks
6644
6671
  WHERE project_id = ${this.projectId}
6645
6672
  ORDER BY display_id
6673
+ LIMIT 2000 -- hard ceiling; single project task count won't approach this
6646
6674
  `;
6647
6675
  return rows2.map(rowToTask);
6648
6676
  }
@@ -6679,22 +6707,28 @@ ${newParts.join("\n")}` : newParts.join("\n");
6679
6707
  where = this.sql`${where} AND ${conditions[i]}`;
6680
6708
  }
6681
6709
  const rows = await this.sql`
6682
- SELECT * FROM cycle_tasks WHERE ${where} ORDER BY display_id
6710
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, updated_at
6711
+ FROM cycle_tasks WHERE ${where} ORDER BY display_id
6712
+ LIMIT 2000 -- matches no-options path ceiling
6683
6713
  `;
6684
6714
  return rows.map(rowToTask);
6685
6715
  }
6686
6716
  async getTask(id) {
6687
6717
  const [row] = await this.sql`
6688
- SELECT * FROM cycle_tasks
6718
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, updated_at
6719
+ FROM cycle_tasks
6689
6720
  WHERE project_id = ${this.projectId} AND display_id = ${id}
6721
+ LIMIT 1
6690
6722
  `;
6691
6723
  return row ? rowToTask(row) : null;
6692
6724
  }
6693
6725
  async getTasks(ids) {
6694
6726
  if (ids.length === 0) return [];
6695
6727
  const rows = await this.sql`
6696
- SELECT * FROM cycle_tasks
6728
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, updated_at
6729
+ FROM cycle_tasks
6697
6730
  WHERE project_id = ${this.projectId} AND display_id = ANY(${ids})
6731
+ LIMIT 2000 -- matches board ceiling; ids[] won't exceed this in practice
6698
6732
  `;
6699
6733
  return rows.map(rowToTask);
6700
6734
  }
@@ -6804,7 +6838,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
6804
6838
  completed, actual_effort, estimated_effort, scope_accuracy,
6805
6839
  surprises, discovered_issues, architecture_notes,
6806
6840
  commit_sha, files_changed, related_decisions, handoff_accuracy,
6807
- corrections_count, brief_implications, dead_ends
6841
+ corrections_count, brief_implications, dead_ends,
6842
+ started_at, completed_at, tool_call_count
6808
6843
  ) VALUES (
6809
6844
  ${this.projectId}, ${displayId}, ${report.taskId}, ${report.taskName},
6810
6845
  ${report.date}, ${report.cycle}, ${report.completed},
@@ -6814,13 +6849,15 @@ ${newParts.join("\n")}` : newParts.join("\n");
6814
6849
  ${report.handoffAccuracy ? this.sql.json(report.handoffAccuracy) : null},
6815
6850
  ${report.correctionsCount ?? 0},
6816
6851
  ${report.briefImplications ? this.sql.json(report.briefImplications) : null},
6817
- ${report.deadEnds ?? null}
6852
+ ${report.deadEnds ?? null},
6853
+ ${report.startedAt ?? null}, ${report.completedAt ?? null}, ${report.toolCallCount ?? 0}
6818
6854
  )
6819
6855
  `;
6820
6856
  }
6821
6857
  async getRecentBuildReports(count) {
6822
6858
  const rows = await this.sql`
6823
- SELECT * FROM build_reports
6859
+ SELECT id, display_id, task_id, task_name, date, cycle, completed, actual_effort, estimated_effort, scope_accuracy, surprises, discovered_issues, architecture_notes, commit_sha, files_changed, related_decisions, corrections_count, handoff_accuracy, brief_implications, dead_ends, created_at
6860
+ FROM build_reports
6824
6861
  WHERE project_id = ${this.projectId}
6825
6862
  ORDER BY created_at DESC
6826
6863
  LIMIT ${count}
@@ -6836,9 +6873,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
6836
6873
  }
6837
6874
  async getBuildReportsSince(cycleNumber) {
6838
6875
  const rows = await this.sql`
6839
- SELECT * FROM build_reports
6876
+ SELECT id, display_id, task_id, task_name, date, cycle, completed, actual_effort, estimated_effort, scope_accuracy, surprises, discovered_issues, architecture_notes, commit_sha, files_changed, related_decisions, corrections_count, handoff_accuracy, brief_implications, dead_ends, created_at
6877
+ FROM build_reports
6840
6878
  WHERE project_id = ${this.projectId} AND cycle >= ${cycleNumber}
6841
6879
  ORDER BY created_at
6880
+ LIMIT 1000 -- bounded by cycle range; 1000 covers ~200 cycles × 5 tasks
6842
6881
  `;
6843
6882
  return rows.map(rowToBuildReport);
6844
6883
  }
@@ -6865,25 +6904,29 @@ ${newParts.join("\n")}` : newParts.join("\n");
6865
6904
  let rows;
6866
6905
  if (opts?.cycleNumber && opts?.category) {
6867
6906
  rows = await this.sql`
6868
- SELECT * FROM cycle_learnings
6907
+ SELECT id, project_id, task_id, cycle_number, category, severity, summary, detail, tags, related_decision, action_taken, action_ref, created_at
6908
+ FROM cycle_learnings
6869
6909
  WHERE project_id = ${this.projectId} AND cycle_number = ${opts.cycleNumber} AND category = ${opts.category}
6870
6910
  ORDER BY created_at DESC LIMIT ${limit}
6871
6911
  `;
6872
6912
  } else if (opts?.cycleNumber) {
6873
6913
  rows = await this.sql`
6874
- SELECT * FROM cycle_learnings
6914
+ SELECT id, project_id, task_id, cycle_number, category, severity, summary, detail, tags, related_decision, action_taken, action_ref, created_at
6915
+ FROM cycle_learnings
6875
6916
  WHERE project_id = ${this.projectId} AND cycle_number = ${opts.cycleNumber}
6876
6917
  ORDER BY created_at DESC LIMIT ${limit}
6877
6918
  `;
6878
6919
  } else if (opts?.category) {
6879
6920
  rows = await this.sql`
6880
- SELECT * FROM cycle_learnings
6921
+ SELECT id, project_id, task_id, cycle_number, category, severity, summary, detail, tags, related_decision, action_taken, action_ref, created_at
6922
+ FROM cycle_learnings
6881
6923
  WHERE project_id = ${this.projectId} AND category = ${opts.category}
6882
6924
  ORDER BY created_at DESC LIMIT ${limit}
6883
6925
  `;
6884
6926
  } else {
6885
6927
  rows = await this.sql`
6886
- SELECT * FROM cycle_learnings
6928
+ SELECT id, project_id, task_id, cycle_number, category, severity, summary, detail, tags, related_decision, action_taken, action_ref, created_at
6929
+ FROM cycle_learnings
6887
6930
  WHERE project_id = ${this.projectId}
6888
6931
  ORDER BY created_at DESC LIMIT ${limit}
6889
6932
  `;
@@ -6937,7 +6980,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
6937
6980
  async getRecentReviews(count) {
6938
6981
  const limit = count ?? 20;
6939
6982
  const rows = await this.sql`
6940
- SELECT * FROM reviews
6983
+ SELECT id, display_id, task_id, stage, reviewer, verdict, cycle, date, comments, handoff_revision, build_commit_sha, auto_review
6984
+ FROM reviews
6941
6985
  WHERE project_id = ${this.projectId}
6942
6986
  ORDER BY created_at DESC
6943
6987
  LIMIT ${limit}
@@ -7071,9 +7115,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
7071
7115
  // -------------------------------------------------------------------------
7072
7116
  async readPhases() {
7073
7117
  const rows = await this.sql`
7074
- SELECT * FROM phases
7118
+ SELECT id, slug, label, description, status, sort_order, stage_id
7119
+ FROM phases
7075
7120
  WHERE project_id = ${this.projectId}
7076
7121
  ORDER BY sort_order
7122
+ LIMIT 200 -- phases are bounded by project structure
7077
7123
  `;
7078
7124
  return rows.map(rowToPhase);
7079
7125
  }
@@ -7123,21 +7169,24 @@ ${newParts.join("\n")}` : newParts.join("\n");
7123
7169
  INSERT INTO tool_call_metrics (
7124
7170
  project_id, timestamp, tool, duration_ms,
7125
7171
  input_tokens, output_tokens, estimated_cost_usd, model, cycle_number,
7126
- context_bytes, context_utilisation
7172
+ context_bytes, context_utilisation, success
7127
7173
  ) VALUES (
7128
7174
  ${this.projectId}, ${metric.timestamp}, ${metric.tool}, ${metric.durationMs},
7129
7175
  ${metric.inputTokens ?? null}, ${metric.outputTokens ?? null},
7130
7176
  ${metric.estimatedCostUsd ?? null}, ${metric.model ?? null},
7131
7177
  ${metric.cycleNumber ?? null},
7132
- ${metric.contextBytes ?? null}, ${metric.contextUtilisation ?? null}
7178
+ ${metric.contextBytes ?? null}, ${metric.contextUtilisation ?? null},
7179
+ ${metric.success ?? true}
7133
7180
  )
7134
7181
  `;
7135
7182
  }
7136
7183
  async readToolMetrics() {
7137
7184
  const rows = await this.sql`
7138
- SELECT * FROM tool_call_metrics
7185
+ SELECT timestamp, tool, duration_ms, input_tokens, output_tokens, estimated_cost_usd, model, cycle_number, context_bytes, context_utilisation
7186
+ FROM tool_call_metrics
7139
7187
  WHERE project_id = ${this.projectId}
7140
7188
  ORDER BY timestamp
7189
+ LIMIT 5000 -- metrics are high-volume; task-1189 adds pagination for dashboard
7141
7190
  `;
7142
7191
  return rows.map(rowToToolCallMetric);
7143
7192
  }
@@ -7146,10 +7195,12 @@ ${newParts.join("\n")}` : newParts.join("\n");
7146
7195
  // -------------------------------------------------------------------------
7147
7196
  async getCostSummary(cycleNumber) {
7148
7197
  const metrics = cycleNumber != null ? await this.sql`
7149
- SELECT * FROM tool_call_metrics
7198
+ SELECT timestamp, tool, duration_ms, input_tokens, output_tokens, estimated_cost_usd, model, cycle_number, context_bytes, context_utilisation
7199
+ FROM tool_call_metrics
7150
7200
  WHERE project_id = ${this.projectId} AND cycle_number = ${cycleNumber}
7151
7201
  ` : await this.sql`
7152
- SELECT * FROM tool_call_metrics
7202
+ SELECT timestamp, tool, duration_ms, input_tokens, output_tokens, estimated_cost_usd, model, cycle_number, context_bytes, context_utilisation
7203
+ FROM tool_call_metrics
7153
7204
  WHERE project_id = ${this.projectId}
7154
7205
  `;
7155
7206
  let totalCostUsd = 0;
@@ -7186,7 +7237,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
7186
7237
  }
7187
7238
  async getCostSnapshots() {
7188
7239
  const rows = await this.sql`
7189
- SELECT * FROM cost_snapshots
7240
+ SELECT cycle, date, total_cost_usd, total_input_tokens, total_output_tokens, total_calls
7241
+ FROM cost_snapshots
7190
7242
  WHERE project_id = ${this.projectId}
7191
7243
  ORDER BY cycle
7192
7244
  `;
@@ -7213,9 +7265,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
7213
7265
  }
7214
7266
  async readCycleMetrics() {
7215
7267
  const rows = await this.sql`
7216
- SELECT * FROM cycle_metrics_snapshots
7268
+ SELECT cycle, date, accuracy, velocity
7269
+ FROM cycle_metrics_snapshots
7217
7270
  WHERE project_id = ${this.projectId}
7218
7271
  ORDER BY cycle
7272
+ LIMIT 500 -- one row per cycle; 500 is ~years of history
7219
7273
  `;
7220
7274
  return rows.map(rowToCycleMetrics);
7221
7275
  }
@@ -7224,9 +7278,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
7224
7278
  // -------------------------------------------------------------------------
7225
7279
  async readCycles() {
7226
7280
  const rows = await this.sql`
7227
- SELECT * FROM cycles
7281
+ SELECT id, number, status, start_date, end_date, goals, board_health, task_ids
7282
+ FROM cycles
7228
7283
  WHERE project_id = ${this.projectId}
7229
7284
  ORDER BY number
7285
+ LIMIT 500 -- one row per cycle; 500 is years of history
7230
7286
  `;
7231
7287
  return rows.map(rowToCycle);
7232
7288
  }
@@ -7273,25 +7329,31 @@ ${newParts.join("\n")}` : newParts.join("\n");
7273
7329
  // -------------------------------------------------------------------------
7274
7330
  async readHorizons() {
7275
7331
  const rows = await this.sql`
7276
- SELECT * FROM horizons
7332
+ SELECT id, slug, label, description, status, sort_order, project_id, created_at, updated_at
7333
+ FROM horizons
7277
7334
  WHERE project_id = ${this.projectId}
7278
7335
  ORDER BY sort_order, created_at
7336
+ LIMIT 50 -- horizons are high-level; 50 is an ample ceiling
7279
7337
  `;
7280
7338
  return rows.map(rowToHorizon);
7281
7339
  }
7282
7340
  async readStages(horizonId) {
7283
7341
  if (horizonId) {
7284
7342
  const rows2 = await this.sql`
7285
- SELECT * FROM stages
7343
+ SELECT id, slug, label, description, status, sort_order, horizon_id, project_id, exit_criteria, created_at, updated_at
7344
+ FROM stages
7286
7345
  WHERE project_id = ${this.projectId} AND horizon_id = ${horizonId}
7287
7346
  ORDER BY sort_order, created_at
7347
+ LIMIT 100 -- stages per horizon are bounded; 100 is ample
7288
7348
  `;
7289
7349
  return rows2.map(rowToStage);
7290
7350
  }
7291
7351
  const rows = await this.sql`
7292
- SELECT * FROM stages
7352
+ SELECT id, slug, label, description, status, sort_order, horizon_id, project_id, exit_criteria, created_at, updated_at
7353
+ FROM stages
7293
7354
  WHERE project_id = ${this.projectId}
7294
7355
  ORDER BY sort_order, created_at
7356
+ LIMIT 200 -- all stages across horizons; 200 is a safe ceiling
7295
7357
  `;
7296
7358
  return rows.map(rowToStage);
7297
7359
  }
@@ -7321,7 +7383,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
7321
7383
  }
7322
7384
  async getActiveStage() {
7323
7385
  const rows = await this.sql`
7324
- SELECT * FROM stages
7386
+ SELECT id, slug, label, description, status, sort_order, horizon_id, project_id, exit_criteria, created_at, updated_at
7387
+ FROM stages
7325
7388
  WHERE project_id = ${this.projectId} AND status = 'Active'
7326
7389
  ORDER BY sort_order
7327
7390
  LIMIT 1
@@ -7433,7 +7496,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
7433
7496
  }
7434
7497
  async getDecisionEvents(decisionId, limit) {
7435
7498
  const rows = await this.sql`
7436
- SELECT * FROM decision_events
7499
+ SELECT id, decision_id, event_type, cycle, source, source_ref, detail, created_at
7500
+ FROM decision_events
7437
7501
  WHERE project_id = ${this.projectId} AND decision_id = ${decisionId}
7438
7502
  ORDER BY created_at DESC
7439
7503
  LIMIT ${limit ?? 50}
@@ -7442,9 +7506,11 @@ ${newParts.join("\n")}` : newParts.join("\n");
7442
7506
  }
7443
7507
  async getDecisionEventsSince(cycle) {
7444
7508
  const rows = await this.sql`
7445
- SELECT * FROM decision_events
7509
+ SELECT id, decision_id, event_type, cycle, source, source_ref, detail, created_at
7510
+ FROM decision_events
7446
7511
  WHERE project_id = ${this.projectId} AND cycle >= ${cycle}
7447
7512
  ORDER BY created_at
7513
+ LIMIT 1000 -- bounded by cycle range; 1000 is ample
7448
7514
  `;
7449
7515
  return rows.map(rowToDecisionEvent);
7450
7516
  }
@@ -7471,7 +7537,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
7471
7537
  }
7472
7538
  async getDecisionScores(decisionId) {
7473
7539
  const rows = await this.sql`
7474
- SELECT * FROM decision_scores
7540
+ SELECT id, decision_id, cycle, effort, risk, reversibility, scale_cost, lock_in, total_score, rationale, created_at
7541
+ FROM decision_scores
7475
7542
  WHERE project_id = ${this.projectId} AND decision_id = ${decisionId}
7476
7543
  ORDER BY cycle
7477
7544
  `;
@@ -7479,7 +7546,7 @@ ${newParts.join("\n")}` : newParts.join("\n");
7479
7546
  }
7480
7547
  async getLatestDecisionScores() {
7481
7548
  const rows = await this.sql`
7482
- SELECT DISTINCT ON (decision_id) *
7549
+ SELECT DISTINCT ON (decision_id) id, decision_id, cycle, effort, risk, reversibility, scale_cost, lock_in, total_score, rationale, created_at
7483
7550
  FROM decision_scores
7484
7551
  WHERE project_id = ${this.projectId}
7485
7552
  ORDER BY decision_id, cycle DESC
@@ -8394,6 +8461,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8394
8461
  readToolMetrics() {
8395
8462
  return this.invoke("readToolMetrics");
8396
8463
  }
8464
+ insertPlanRun(entry) {
8465
+ return this.invoke("insertPlanRun", [entry]);
8466
+ }
8397
8467
  getCostSummary(cycleNumber) {
8398
8468
  return this.invoke("getCostSummary", [cycleNumber]);
8399
8469
  }
@@ -8520,6 +8590,46 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8520
8590
  submitBugReport(report) {
8521
8591
  return this.invoke("submitBugReport", [report]);
8522
8592
  }
8593
+ // --- Doc Registry ---
8594
+ registerDoc(entry) {
8595
+ return this.invoke("registerDoc", [entry]);
8596
+ }
8597
+ searchDocs(input) {
8598
+ return this.invoke("searchDocs", [input]);
8599
+ }
8600
+ getDoc(idOrPath) {
8601
+ return this.invoke("getDoc", [idOrPath]);
8602
+ }
8603
+ updateDocStatus(id, status, supersededBy) {
8604
+ return this.invoke("updateDocStatus", [id, status, supersededBy]);
8605
+ }
8606
+ // --- Cycle Learnings ---
8607
+ appendCycleLearnings(learnings) {
8608
+ return this.invoke("appendCycleLearnings", [learnings]);
8609
+ }
8610
+ getCycleLearnings(opts) {
8611
+ return this.invoke("getCycleLearnings", [opts]);
8612
+ }
8613
+ getCycleLearningPatterns() {
8614
+ return this.invoke("getCycleLearningPatterns", []);
8615
+ }
8616
+ updateCycleLearningActionRef(learningId, taskDisplayId) {
8617
+ return this.invoke("updateCycleLearningActionRef", [learningId, taskDisplayId]);
8618
+ }
8619
+ // --- Strategy Review Drafts ---
8620
+ savePendingReviewResponse(cycleNumber, rawResponse) {
8621
+ return this.invoke("savePendingReviewResponse", [cycleNumber, rawResponse]);
8622
+ }
8623
+ getPendingReviewResponse() {
8624
+ return this.invoke("getPendingReviewResponse", []);
8625
+ }
8626
+ clearPendingReviewResponse() {
8627
+ return this.invoke("clearPendingReviewResponse", []);
8628
+ }
8629
+ // --- Active Decisions ---
8630
+ confirmPendingActiveDecisions(cycleNumber) {
8631
+ return this.invoke("confirmPendingActiveDecisions", [cycleNumber]);
8632
+ }
8523
8633
  // --- Atomic plan write-back ---
8524
8634
  async planWriteBack(payload) {
8525
8635
  const raw = await this.invoke("planWriteBack", [payload]);
@@ -8975,8 +9085,10 @@ function detectUnrecordedCommits(cwd, baseBranch) {
8975
9085
  // release commits
8976
9086
  /^[a-f0-9]+ Merge /,
8977
9087
  // merge commits from PRs
8978
- /chore\(task-/
9088
+ /chore\(task-/,
8979
9089
  // task-related housekeeping
9090
+ /chore: dogfood log/
9091
+ // automated dogfood log entries post-release
8980
9092
  ];
8981
9093
  return output.split("\n").filter((line) => line.trim() && !CYCLE_PATTERNS.some((p) => p.test(line))).map((line) => {
8982
9094
  const spaceIdx = line.indexOf(" ");
@@ -10624,6 +10736,8 @@ You MUST cover these 5 sections. Each is mandatory.
10624
10736
  - Note any hierarchy/phase issues worth correcting (1-2 bullets max)
10625
10737
  - Delete ADs that are legacy, process-level, or redundant without discussion
10626
10738
 
10739
+ **Registered Documents:** If a "### Registered Documents" section is present in context, scan it for: (a) research findings that contradict current ADs or strategy, (b) unactioned research that should influence the next plan. Reference relevant docs by title in your review. If unregistered docs are listed, flag 1-2 that look strategically relevant and suggest registering them.
10740
+
10627
10741
  ## CONDITIONAL SECTIONS (include only when genuinely useful \u2014 most reviews should have 0-2 of these)
10628
10742
 
10629
10743
  6. **Security Posture Review** \u2014 Only if \`[SECURITY]\` tags exist in recent cycle logs.
@@ -13610,21 +13724,6 @@ ${cleanContent}`;
13610
13724
  });
13611
13725
  } catch {
13612
13726
  }
13613
- try {
13614
- const cycleLog = await adapter2.getCycleLogSince(cycleNumber);
13615
- const currentEntry = cycleLog.find((e) => e.cycleNumber === cycleNumber);
13616
- if (currentEntry) {
13617
- const reviewCompleteText = `Strategy review completed Cycle ${cycleNumber}. Next due ~Cycle ${cycleNumber + 5}.`;
13618
- const existingCf = currentEntry.carryForward ?? "";
13619
- const updatedCf = existingCf ? existingCf.replace(/strategy review (?:due|available|overdue)[^.]*\.[^.]*/gi, reviewCompleteText).trim() : reviewCompleteText;
13620
- const finalCf = updatedCf === existingCf ? `${existingCf} ${reviewCompleteText}`.trim() : updatedCf;
13621
- await adapter2.writeCycleLogEntry({
13622
- ...currentEntry,
13623
- carryForward: finalCf
13624
- });
13625
- }
13626
- } catch {
13627
- }
13628
13727
  if (data.activeDecisionUpdates && data.activeDecisionUpdates.length > 0) {
13629
13728
  await Promise.all(data.activeDecisionUpdates.map(async (ad) => {
13630
13729
  if (ad.action === "delete" && adapter2.deleteActiveDecision) {
@@ -16454,6 +16553,7 @@ init_git();
16454
16553
  import { randomUUID as randomUUID9 } from "crypto";
16455
16554
  import { readdirSync as readdirSync3, existsSync as existsSync3, readFileSync } from "fs";
16456
16555
  import { join as join5 } from "path";
16556
+ var buildStartTimes = /* @__PURE__ */ new Map();
16457
16557
  function capitalizeCompleted(value) {
16458
16558
  const map = {
16459
16559
  yes: "Yes",
@@ -16659,6 +16759,7 @@ async function startBuild(adapter2, config2, taskId, options = {}) {
16659
16759
  if (task.status !== "In Progress") {
16660
16760
  await adapter2.updateTaskStatus(taskId, "In Progress");
16661
16761
  }
16762
+ buildStartTimes.set(taskId, (/* @__PURE__ */ new Date()).toISOString());
16662
16763
  let phaseChanges = [];
16663
16764
  try {
16664
16765
  phaseChanges = await propagatePhaseStatus(adapter2);
@@ -16724,8 +16825,11 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
16724
16825
  correctionsCount: input.correctionsCount,
16725
16826
  briefImplications: input.briefImplications,
16726
16827
  deadEnds: input.deadEnds,
16727
- iterationCount
16828
+ iterationCount,
16829
+ startedAt: buildStartTimes.get(taskId) ?? void 0,
16830
+ completedAt: now.toISOString()
16728
16831
  };
16832
+ buildStartTimes.delete(taskId);
16729
16833
  if (input.relatedDecisions) {
16730
16834
  const adIds = input.relatedDecisions.split(",").map((s) => s.trim()).filter(Boolean);
16731
16835
  if (adIds.length > 0) report.relatedDecisions = adIds;
@@ -20362,7 +20466,7 @@ var hierarchyUpdateTool = {
20362
20466
  },
20363
20467
  status: {
20364
20468
  type: "string",
20365
- enum: ["active", "completed", "deferred"],
20469
+ enum: ["Not Started", "In Progress", "Done", "Deferred"],
20366
20470
  description: "The new status to set."
20367
20471
  },
20368
20472
  exit_criteria: {
@@ -20374,7 +20478,7 @@ var hierarchyUpdateTool = {
20374
20478
  required: ["level", "name"]
20375
20479
  }
20376
20480
  };
20377
- var VALID_STATUSES3 = /* @__PURE__ */ new Set(["active", "completed", "deferred"]);
20481
+ var VALID_STATUSES3 = /* @__PURE__ */ new Set(["Not Started", "In Progress", "Done", "Deferred"]);
20378
20482
  async function handleHierarchyUpdate(adapter2, args) {
20379
20483
  const level = args.level;
20380
20484
  const name = args.name;
@@ -20390,7 +20494,7 @@ async function handleHierarchyUpdate(adapter2, args) {
20390
20494
  return errorResponse(`Invalid level "${level}". Must be "phase", "stage", or "horizon".`);
20391
20495
  }
20392
20496
  if (status && !VALID_STATUSES3.has(status)) {
20393
- return errorResponse(`Invalid status "${status}". Must be one of: active, completed, deferred.`);
20497
+ return errorResponse(`Invalid status "${status}". Must be one of: Not Started, In Progress, Done, Deferred.`);
20394
20498
  }
20395
20499
  if (exitCriteria !== void 0 && level !== "stage") {
20396
20500
  return errorResponse("exit_criteria can only be set on stages.");
@@ -20989,7 +21093,7 @@ function extractTitle(filePath) {
20989
21093
  }
20990
21094
  async function handleDocScan(adapter2, config2, args) {
20991
21095
  if (!adapter2.searchDocs) {
20992
- return errorResponse("Doc registry not available \u2014 requires pg adapter.");
21096
+ return errorResponse("Doc registry not available on this adapter.");
20993
21097
  }
20994
21098
  const includePlans = args.include_plans ?? false;
20995
21099
  const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
@@ -21599,12 +21703,13 @@ function createServer(adapter2, config2) {
21599
21703
  delete result._usage;
21600
21704
  delete result._contextBytes;
21601
21705
  delete result._contextUtilisation;
21602
- if (contextBytes !== void 0 || contextUtilisation !== void 0) {
21603
- try {
21604
- const metric = buildMetric(name, elapsed, usage, void 0, contextBytes, contextUtilisation);
21605
- await adapter2.appendToolMetric(metric);
21606
- } catch {
21607
- }
21706
+ const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
21707
+ try {
21708
+ const metric = buildMetric(name, elapsed, usage, void 0, contextBytes, contextUtilisation);
21709
+ metric.success = !isError;
21710
+ adapter2.appendToolMetric(metric).catch(() => {
21711
+ });
21712
+ } catch {
21608
21713
  }
21609
21714
  const telemetryProjectId = process.env["PAPI_PROJECT_ID"];
21610
21715
  if (telemetryProjectId) {
@@ -21612,7 +21717,6 @@ function createServer(adapter2, config2) {
21612
21717
  adapter_type: config2.adapterType
21613
21718
  });
21614
21719
  const isApplyMode = safeArgs.mode === "apply";
21615
- const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
21616
21720
  if (!isError) {
21617
21721
  if (name === "setup" && isApplyMode) {
21618
21722
  emitMilestone(telemetryProjectId, "setup_completed");
package/dist/prompts.js CHANGED
@@ -754,6 +754,8 @@ You MUST cover these 5 sections. Each is mandatory.
754
754
  - Note any hierarchy/phase issues worth correcting (1-2 bullets max)
755
755
  - Delete ADs that are legacy, process-level, or redundant without discussion
756
756
 
757
+ **Registered Documents:** If a "### Registered Documents" section is present in context, scan it for: (a) research findings that contradict current ADs or strategy, (b) unactioned research that should influence the next plan. Reference relevant docs by title in your review. If unregistered docs are listed, flag 1-2 that look strategically relevant and suggest registering them.
758
+
757
759
  ## CONDITIONAL SECTIONS (include only when genuinely useful \u2014 most reviews should have 0-2 of these)
758
760
 
759
761
  6. **Security Posture Review** \u2014 Only if \`[SECURITY]\` tags exist in recent cycle logs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",