@papi-ai/server 0.7.4-alpha.4 → 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 +590 -138
- package/dist/prompts.js +29 -4
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(" ");
|
|
@@ -10071,7 +10183,7 @@ Standard planning cycle with full board review.
|
|
|
10071
10183
|
- **What to do with premature tasks:** Leave them in Backlog. Do NOT generate BUILD HANDOFFs for them. If a high-priority task fails the maturity gate due to phase prerequisites or dependencies, note it in the cycle log: "task-XXX deferred \u2014 Phase N prerequisites not met". Raw tasks are NOT premature \u2014 they just need scoping (see Task maturity above).
|
|
10072
10184
|
|
|
10073
10185
|
7. **Recommendation** \u2014 Select tasks for this cycle:
|
|
10074
|
-
**Pre-assigned tasks:** If a "Pre-Assigned Tasks" section is provided in the context below, those tasks are ALREADY committed to this cycle by the user. Include them automatically \u2014 do NOT re-evaluate whether they belong. Generate BUILD HANDOFFs for each. Count their effort toward the cycle budget. Then fill remaining slots
|
|
10186
|
+
**Pre-assigned tasks:** If a "Pre-Assigned Tasks" section is provided in the context below, those tasks are ALREADY committed to this cycle by the user. Include them automatically \u2014 do NOT re-evaluate whether they belong. Generate BUILD HANDOFFs for each. Count their effort toward the cycle budget. Then fill remaining slots from the backlog using the priority rules and cycle sizing rules below.
|
|
10075
10187
|
**If USER DIRECTION is provided above:** Follow the user's stated focus. Pick the highest-impact task that aligns with their direction. The user knows what they need. Only deviate if a genuine P0 Critical fix exists (broken builds, data loss).
|
|
10076
10188
|
**Otherwise, select by priority level then impact:**
|
|
10077
10189
|
- **P0 Critical** \u2014 Broken, blocking, or data-loss risk. Always first.
|
|
@@ -10095,7 +10207,7 @@ Standard planning cycle with full board review.
|
|
|
10095
10207
|
- **Backlog as steering wheel:** Task priority and notes in the backlog are the user's primary control mechanism over what gets planned. Respect the priority rankings and read task notes carefully \u2014 they contain user intent that shapes scope and scheduling.
|
|
10096
10208
|
- **Planning quality is the bar:** Strategy review depth and plan quality set the standard for the product. Do not cut corners on analysis depth, triage thoroughness, or handoff specificity \u2014 these are what users experience as PAPI's value.
|
|
10097
10209
|
|
|
10098
|
-
10. **BUILD HANDOFFs** \u2014 Generate a full BUILD HANDOFF block for
|
|
10210
|
+
10. **BUILD HANDOFFs** \u2014 Generate a full BUILD HANDOFF block for every task selected for this cycle. Include each handoff in the \`cycleHandoffs\` array in the structured output. The handoffs are written to each task on the board for durability.
|
|
10099
10211
|
**SKIP existing handoffs:** Tasks marked with "Has BUILD HANDOFF: yes" or "\u2713 handoff" on the board already have a valid handoff from a previous plan. Do NOT regenerate handoffs for these tasks \u2014 omit them from the \`cycleHandoffs\` array entirely. Only generate handoffs for tasks that do NOT have one yet. Exception: if a task's dependencies have been completed since its handoff was written, or a relevant Active Decision has changed, you MAY regenerate its handoff \u2014 but note this explicitly in the cycle log.
|
|
10100
10212
|
**Scope pre-check:** Before writing the SCOPE section of each handoff, cross-reference the task against the "Recently Shipped Capabilities" section in the context below (if present). For each candidate task: (1) check if the task's title or scope overlaps with any recently shipped task, (2) check if the FILES LIKELY TOUCHED overlap with files already modified in recent builds, (3) check the architecture notes from recent builds for patterns that already cover this task's scope. If >80% of a task's scope appears in recently shipped capabilities, recommend cancellation via \`boardCorrections\` or reduce the handoff scope to only the missing pieces \u2014 explicitly note what already exists. C126 task-728 was over-scoped because the planner assumed Blocked status needed creating from scratch \u2014 it already existed in types, DB, orient, and build_list. Over-scoped handoffs waste builder time on verification and cause estimation mismatches.
|
|
10101
10213
|
**Simplest Viable Path rule:** Before writing each BUILD HANDOFF, identify the simplest approach that satisfies the task's goal \u2014 the minimum change, fewest new abstractions, and smallest blast radius. Write the SCOPE (DO THIS) section for that simplest path FIRST. If you believe a more complex approach is warranted (new abstractions, multi-file refactors, framework changes), you MUST include a "WHY NOT SIMPLER" line in the handoff explaining why the simple path is insufficient. If you cannot articulate a concrete reason, use the simpler path. Pay special attention to tasks involving auth, data access, multi-user features, and infrastructure \u2014 these are the most common over-engineering targets.
|
|
@@ -10258,7 +10370,7 @@ Standard planning cycle with full board review.
|
|
|
10258
10370
|
- **What to do with premature tasks:** Leave them in Backlog. Do NOT generate BUILD HANDOFFs for them. If a high-priority task fails the maturity gate due to phase prerequisites or dependencies, note it in the cycle log: "task-XXX deferred \u2014 Phase N prerequisites not met". Raw tasks are NOT premature \u2014 they just need scoping (see Task maturity above).
|
|
10259
10371
|
|
|
10260
10372
|
7. **Recommendation** \u2014 Select tasks for this cycle:
|
|
10261
|
-
**Pre-assigned tasks:** If a "Pre-Assigned Tasks" section is provided in the context below, those tasks are ALREADY committed to this cycle by the user. Include them automatically \u2014 do NOT re-evaluate whether they belong. Generate BUILD HANDOFFs for each. Count their effort toward the cycle budget. Then fill remaining slots
|
|
10373
|
+
**Pre-assigned tasks:** If a "Pre-Assigned Tasks" section is provided in the context below, those tasks are ALREADY committed to this cycle by the user. Include them automatically \u2014 do NOT re-evaluate whether they belong. Generate BUILD HANDOFFs for each. Count their effort toward the cycle budget. Then fill remaining slots from the backlog using the priority rules and cycle sizing rules below.
|
|
10262
10374
|
**If USER DIRECTION is provided above:** Follow the user's stated focus. Pick the highest-impact task that aligns with their direction. The user knows what they need. Only deviate if a genuine P0 Critical fix exists (broken builds, data loss).
|
|
10263
10375
|
**Otherwise, select by priority level then impact:**
|
|
10264
10376
|
- **P0 Critical** \u2014 Broken, blocking, or data-loss risk. Always first.
|
|
@@ -10282,7 +10394,7 @@ Standard planning cycle with full board review.
|
|
|
10282
10394
|
- **Backlog as steering wheel:** Task priority and notes in the backlog are the user's primary control mechanism over what gets planned. Respect the priority rankings and read task notes carefully \u2014 they contain user intent that shapes scope and scheduling.
|
|
10283
10395
|
- **Planning quality is the bar:** Strategy review depth and plan quality set the standard for the product. Do not cut corners on analysis depth, triage thoroughness, or handoff specificity \u2014 these are what users experience as PAPI's value.
|
|
10284
10396
|
|
|
10285
|
-
10. **BUILD HANDOFFs** \u2014 Generate a full BUILD HANDOFF block for
|
|
10397
|
+
10. **BUILD HANDOFFs** \u2014 Generate a full BUILD HANDOFF block for every task selected for this cycle. Include each handoff in the \`cycleHandoffs\` array in the structured output. The handoffs are written to each task on the board for durability.
|
|
10286
10398
|
**SKIP existing handoffs:** Tasks marked with "Has BUILD HANDOFF: yes" or "\u2713 handoff" on the board already have a valid handoff from a previous plan. Do NOT regenerate handoffs for these tasks \u2014 omit them from the \`cycleHandoffs\` array entirely. Only generate handoffs for tasks that do NOT have one yet. Exception: if a task's dependencies have been completed since its handoff was written, or a relevant Active Decision has changed, you MAY regenerate its handoff \u2014 but note this explicitly in the cycle log.
|
|
10287
10399
|
**Scope pre-check:** Before writing the SCOPE section of each handoff, cross-reference the task against the "Recently Shipped Capabilities" section in the context below (if present). For each candidate task: (1) check if the task's title or scope overlaps with any recently shipped task, (2) check if the FILES LIKELY TOUCHED overlap with files already modified in recent builds, (3) check the architecture notes from recent builds for patterns that already cover this task's scope. If >80% of a task's scope appears in recently shipped capabilities, recommend cancellation via \`boardCorrections\` or reduce the handoff scope to only the missing pieces \u2014 explicitly note what already exists. C126 task-728 was over-scoped because the planner assumed Blocked status needed creating from scratch \u2014 it already existed in types, DB, orient, and build_list. Over-scoped handoffs waste builder time on verification and cause estimation mismatches.
|
|
10288
10400
|
**Simplest Viable Path rule:** Before writing each BUILD HANDOFF, identify the simplest approach that satisfies the task's goal \u2014 the minimum change, fewest new abstractions, and smallest blast radius. Write the SCOPE (DO THIS) section for that simplest path FIRST. If you believe a more complex approach is warranted (new abstractions, multi-file refactors, framework changes), you MUST include a "WHY NOT SIMPLER" line in the handoff explaining why the simple path is insufficient. If you cannot articulate a concrete reason, use the simpler path. Pay special attention to tasks involving auth, data access, multi-user features, and infrastructure \u2014 these are the most common over-engineering targets.
|
|
@@ -10341,6 +10453,24 @@ function buildPlanUserMessage(ctx) {
|
|
|
10341
10453
|
}) : PLAN_FULL_INSTRUCTIONS;
|
|
10342
10454
|
parts.push(instructions);
|
|
10343
10455
|
}
|
|
10456
|
+
if (ctx.skipHandoffs) {
|
|
10457
|
+
parts.push(
|
|
10458
|
+
"",
|
|
10459
|
+
"## SKIP HANDOFFS MODE",
|
|
10460
|
+
"",
|
|
10461
|
+
"**IMPORTANT OVERRIDE:** Do NOT generate BUILD HANDOFF blocks in this plan run.",
|
|
10462
|
+
"Select tasks for the cycle using all the normal criteria (Steps 1-7), but SKIP Step 10 (BUILD HANDOFFs) entirely.",
|
|
10463
|
+
"",
|
|
10464
|
+
"In your Part 2 structured output:",
|
|
10465
|
+
"- Set `cycleHandoffs` to an EMPTY array `[]`",
|
|
10466
|
+
'- Add a `cycleTaskIds` array with the task IDs you selected for the cycle: `["task-123", "task-456", ...]`',
|
|
10467
|
+
"- All other fields (cycleLogTitle, cycleLogContent, newTasks, boardCorrections, activeDecisions, etc.) work as normal.",
|
|
10468
|
+
"",
|
|
10469
|
+
"BUILD HANDOFFs will be generated separately via `handoff_generate` after this plan completes.",
|
|
10470
|
+
"This reduces your cognitive load \u2014 focus on triage, selection, and board management only.",
|
|
10471
|
+
""
|
|
10472
|
+
);
|
|
10473
|
+
}
|
|
10344
10474
|
parts.push("", "---", "", "## PROJECT CONTEXT", "");
|
|
10345
10475
|
parts.push("### Product Brief", "", ctx.productBrief, "");
|
|
10346
10476
|
if (ctx.northStar) {
|
|
@@ -10517,6 +10647,7 @@ function coerceStructuredOutput(parsed) {
|
|
|
10517
10647
|
id: coerceToString(ad.id),
|
|
10518
10648
|
body: coerceToString(ad.body)
|
|
10519
10649
|
})) : [];
|
|
10650
|
+
const cycleTaskIds = Array.isArray(parsed.cycleTaskIds) ? parsed.cycleTaskIds.map((id) => coerceToString(id)) : void 0;
|
|
10520
10651
|
return {
|
|
10521
10652
|
cycleLogTitle: coerceToString(parsed.cycleLogTitle),
|
|
10522
10653
|
cycleLogContent: coerceToString(parsed.cycleLogContent),
|
|
@@ -10527,6 +10658,7 @@ function coerceStructuredOutput(parsed) {
|
|
|
10527
10658
|
strategicDirection: coerceToString(parsed.strategicDirection),
|
|
10528
10659
|
recommendedTaskId: parsed.recommendedTaskId === null ? null : coerceToString(parsed.recommendedTaskId),
|
|
10529
10660
|
cycleHandoffs,
|
|
10661
|
+
cycleTaskIds,
|
|
10530
10662
|
newTasks,
|
|
10531
10663
|
boardCorrections,
|
|
10532
10664
|
productBrief: parsed.productBrief === null ? null : coerceToString(parsed.productBrief),
|
|
@@ -10604,6 +10736,8 @@ You MUST cover these 5 sections. Each is mandatory.
|
|
|
10604
10736
|
- Note any hierarchy/phase issues worth correcting (1-2 bullets max)
|
|
10605
10737
|
- Delete ADs that are legacy, process-level, or redundant without discussion
|
|
10606
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
|
+
|
|
10607
10741
|
## CONDITIONAL SECTIONS (include only when genuinely useful \u2014 most reviews should have 0-2 of these)
|
|
10608
10742
|
|
|
10609
10743
|
6. **Security Posture Review** \u2014 Only if \`[SECURITY]\` tags exist in recent cycle logs.
|
|
@@ -10824,6 +10958,9 @@ function buildReviewUserMessage(ctx) {
|
|
|
10824
10958
|
if (ctx.taskComments) {
|
|
10825
10959
|
parts.push("### Task Discussion (Recent Comments)", "", ctx.taskComments, "");
|
|
10826
10960
|
}
|
|
10961
|
+
if (ctx.docActionStaleness) {
|
|
10962
|
+
parts.push("### Doc Action Staleness", "", ctx.docActionStaleness, "");
|
|
10963
|
+
}
|
|
10827
10964
|
return parts.join("\n");
|
|
10828
10965
|
}
|
|
10829
10966
|
function parseReviewStructuredOutput(raw) {
|
|
@@ -11268,6 +11405,8 @@ function buildPlanSlackSummary(cycleNumber, mode, data) {
|
|
|
11268
11405
|
return `${h.taskId}: ${title}`;
|
|
11269
11406
|
}).join(", ");
|
|
11270
11407
|
parts.push(`*Recommended:* ${tasks}`);
|
|
11408
|
+
} else if (data.cycleTaskIds && data.cycleTaskIds.length > 0) {
|
|
11409
|
+
parts.push(`*Recommended:* ${data.cycleTaskIds.join(", ")} (handoffs pending)`);
|
|
11271
11410
|
}
|
|
11272
11411
|
if (data.strategicDirection) {
|
|
11273
11412
|
parts.push(`*Direction:* ${data.strategicDirection}`);
|
|
@@ -11316,7 +11455,7 @@ function formatPreAssignedTasks(tasks, targetCycle) {
|
|
|
11316
11455
|
"",
|
|
11317
11456
|
...lines,
|
|
11318
11457
|
"",
|
|
11319
|
-
"These tasks MUST be included in the cycle. Generate BUILD HANDOFFs for each. Fill remaining slots
|
|
11458
|
+
"These tasks MUST be included in the cycle. Generate BUILD HANDOFFs for each. Fill remaining slots from the backlog based on cycle sizing rules."
|
|
11320
11459
|
].join("\n");
|
|
11321
11460
|
}
|
|
11322
11461
|
function pushAfterCommit(config2) {
|
|
@@ -11614,9 +11753,10 @@ async function assembleContext(adapter2, mode, _config, filters, focus) {
|
|
|
11614
11753
|
adapter2.readCycleMetrics(),
|
|
11615
11754
|
adapter2.getRecentReviews(5)
|
|
11616
11755
|
]);
|
|
11756
|
+
let leanBuildReports = [];
|
|
11617
11757
|
try {
|
|
11618
|
-
|
|
11619
|
-
metricsSnapshots2 = computeSnapshotsFromBuildReports(
|
|
11758
|
+
leanBuildReports = await adapter2.getRecentBuildReports(50);
|
|
11759
|
+
metricsSnapshots2 = computeSnapshotsFromBuildReports(leanBuildReports);
|
|
11620
11760
|
} catch {
|
|
11621
11761
|
}
|
|
11622
11762
|
timings["metricsAndReviews"] = t();
|
|
@@ -11645,7 +11785,7 @@ async function assembleContext(adapter2, mode, _config, filters, focus) {
|
|
|
11645
11785
|
adapter2.searchDocs?.({ status: "active", limit: 5 }),
|
|
11646
11786
|
adapter2.getCycleLog(5),
|
|
11647
11787
|
adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready"] }),
|
|
11648
|
-
|
|
11788
|
+
Promise.resolve(leanBuildReports.slice(0, 10)),
|
|
11649
11789
|
adapter2.getContextHashes?.(health.totalCycles) ?? Promise.resolve(null)
|
|
11650
11790
|
]);
|
|
11651
11791
|
timings["parallelReads"] = t();
|
|
@@ -11743,7 +11883,7 @@ ${lines.join("\n")}`;
|
|
|
11743
11883
|
return { context: ctx2, contextHashes: newHashes2 };
|
|
11744
11884
|
}
|
|
11745
11885
|
t = startTimer();
|
|
11746
|
-
const [decisions, reportsSinceCycle, log, tasks, rawMetricsSnapshots, reviews, phases, dogfoodEntries] = await Promise.all([
|
|
11886
|
+
const [decisions, reportsSinceCycle, log, tasks, rawMetricsSnapshots, reviews, phases, dogfoodEntries, allBuildReports] = await Promise.all([
|
|
11747
11887
|
adapter2.getActiveDecisions(),
|
|
11748
11888
|
adapter2.getBuildReportsSince(health.totalCycles ?? 0),
|
|
11749
11889
|
adapter2.getCycleLog(3),
|
|
@@ -11751,10 +11891,11 @@ ${lines.join("\n")}`;
|
|
|
11751
11891
|
adapter2.readCycleMetrics(),
|
|
11752
11892
|
adapter2.getRecentReviews(5),
|
|
11753
11893
|
adapter2.readPhases(),
|
|
11754
|
-
readDogfoodEntries(_config.projectRoot, 5, adapter2)
|
|
11894
|
+
readDogfoodEntries(_config.projectRoot, 5, adapter2),
|
|
11895
|
+
adapter2.getRecentBuildReports(50)
|
|
11755
11896
|
]);
|
|
11756
11897
|
timings["fullQueries"] = t();
|
|
11757
|
-
const reports = reportsSinceCycle.length > 0 ? reportsSinceCycle :
|
|
11898
|
+
const reports = reportsSinceCycle.length > 0 ? reportsSinceCycle : allBuildReports.slice(0, 5);
|
|
11758
11899
|
t = startTimer();
|
|
11759
11900
|
const [
|
|
11760
11901
|
allReportsResult,
|
|
@@ -11765,7 +11906,7 @@ ${lines.join("\n")}`;
|
|
|
11765
11906
|
docsResultFull,
|
|
11766
11907
|
contextHashesResultFull
|
|
11767
11908
|
] = await Promise.allSettled([
|
|
11768
|
-
|
|
11909
|
+
Promise.resolve(allBuildReports),
|
|
11769
11910
|
detectReviewPatterns(reviews, health.totalCycles, 5),
|
|
11770
11911
|
adapter2.getPendingRecommendations(),
|
|
11771
11912
|
assembleDiscoveryCanvasText(adapter2),
|
|
@@ -11883,7 +12024,7 @@ ${cleanContent}`;
|
|
|
11883
12024
|
}
|
|
11884
12025
|
return { taskId: h.taskId, handoff: parsed };
|
|
11885
12026
|
}).filter((h) => h.handoff != null);
|
|
11886
|
-
const cycleTaskIds = (data.cycleHandoffs ?? []).map((h) => h.taskId);
|
|
12027
|
+
const cycleTaskIds = data.cycleTaskIds?.length ? data.cycleTaskIds : (data.cycleHandoffs ?? []).map((h) => h.taskId);
|
|
11887
12028
|
const cycle = {
|
|
11888
12029
|
id: `cycle-${newCycleNumber}`,
|
|
11889
12030
|
number: newCycleNumber,
|
|
@@ -11984,12 +12125,12 @@ ${cleanContent}`;
|
|
|
11984
12125
|
if (!newCycle) {
|
|
11985
12126
|
verifyWarnings.push(`Post-write verification FAILED: cycle ${newCycleNumber} entity not found after commit \u2014 data may not have persisted`);
|
|
11986
12127
|
} else {
|
|
11987
|
-
const
|
|
12128
|
+
const expectedTaskCount = data.cycleTaskIds?.length ?? data.cycleHandoffs?.length ?? 0;
|
|
11988
12129
|
const actualCycleTasks = boardTasks.filter((t) => t.cycle === newCycleNumber).length;
|
|
11989
|
-
if (
|
|
11990
|
-
verifyWarnings.push(`Post-write verification FAILED: cycle ${newCycleNumber} exists but has 0 tasks assigned (expected ${
|
|
11991
|
-
} else if (
|
|
11992
|
-
verifyWarnings.push(`Post-write verification WARNING: cycle ${newCycleNumber} has ${actualCycleTasks} tasks but expected ${
|
|
12130
|
+
if (expectedTaskCount > 0 && actualCycleTasks === 0) {
|
|
12131
|
+
verifyWarnings.push(`Post-write verification FAILED: cycle ${newCycleNumber} exists but has 0 tasks assigned (expected ${expectedTaskCount}) \u2014 task cycle assignment may have failed`);
|
|
12132
|
+
} else if (expectedTaskCount > 0 && actualCycleTasks < expectedTaskCount) {
|
|
12133
|
+
verifyWarnings.push(`Post-write verification WARNING: cycle ${newCycleNumber} has ${actualCycleTasks} tasks but expected ${expectedTaskCount} \u2014 some task assignments may have failed`);
|
|
11993
12134
|
}
|
|
11994
12135
|
}
|
|
11995
12136
|
} catch {
|
|
@@ -12000,7 +12141,7 @@ ${cleanContent}`;
|
|
|
12000
12141
|
const correctionCount = data.boardCorrections?.length ?? 0;
|
|
12001
12142
|
const newTaskCount = result.newTaskIdMap.size;
|
|
12002
12143
|
const adCount = data.activeDecisions?.length ?? 0;
|
|
12003
|
-
const taskIds = (data.cycleHandoffs ?? []).map((h) => result.newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12144
|
+
const taskIds = data.cycleTaskIds?.length ? data.cycleTaskIds.map((id) => result.newTaskIdMap.get(id) ?? id) : (data.cycleHandoffs ?? []).map((h) => result.newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12004
12145
|
return {
|
|
12005
12146
|
priorityLockNotes: result.priorityLockNotes,
|
|
12006
12147
|
newTaskIdMap: result.newTaskIdMap,
|
|
@@ -12042,15 +12183,7 @@ ${cleanContent}`;
|
|
|
12042
12183
|
taskCount: cycleTaskCount > 0 ? cycleTaskCount : void 0,
|
|
12043
12184
|
effortPoints: cycleEffortPoints > 0 ? cycleEffortPoints : void 0
|
|
12044
12185
|
});
|
|
12045
|
-
const healthPromise =
|
|
12046
|
-
(health) => adapter2.setCycleHealth({
|
|
12047
|
-
totalCycles: newCycleNumber,
|
|
12048
|
-
cyclesSinceLastStrategyReview: health.cyclesSinceLastStrategyReview + 1,
|
|
12049
|
-
lastFullMode: newCycleNumber,
|
|
12050
|
-
boardHealth: data.boardHealth,
|
|
12051
|
-
strategicDirection: data.strategicDirection
|
|
12052
|
-
})
|
|
12053
|
-
);
|
|
12186
|
+
const healthPromise = Promise.resolve();
|
|
12054
12187
|
const newTaskIdMap = /* @__PURE__ */ new Map();
|
|
12055
12188
|
const createTasksPromise = (async () => {
|
|
12056
12189
|
if (!data.newTasks || data.newTasks.length === 0) return;
|
|
@@ -12241,7 +12374,7 @@ ${cleanContent}`;
|
|
|
12241
12374
|
})();
|
|
12242
12375
|
const cycleEntityPromise = (async () => {
|
|
12243
12376
|
try {
|
|
12244
|
-
const cycleTaskIds = (data.cycleHandoffs ?? []).map((h) => newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12377
|
+
const cycleTaskIds = data.cycleTaskIds?.length ? data.cycleTaskIds.map((id) => newTaskIdMap.get(id) ?? id) : (data.cycleHandoffs ?? []).map((h) => newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12245
12378
|
const cycle = {
|
|
12246
12379
|
id: `cycle-${newCycleNumber}`,
|
|
12247
12380
|
number: newCycleNumber,
|
|
@@ -12271,7 +12404,7 @@ ${cleanContent}`;
|
|
|
12271
12404
|
const correctionCount = data.boardCorrections?.length ?? 0;
|
|
12272
12405
|
const newTaskCount = newTaskIdMap.size;
|
|
12273
12406
|
const adCount = data.activeDecisions?.length ?? 0;
|
|
12274
|
-
const taskIds = (data.cycleHandoffs ?? []).map((h) => newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12407
|
+
const taskIds = data.cycleTaskIds?.length ? data.cycleTaskIds.map((id) => newTaskIdMap.get(id) ?? id) : (data.cycleHandoffs ?? []).map((h) => newTaskIdMap.get(h.taskId) ?? h.taskId);
|
|
12275
12408
|
return {
|
|
12276
12409
|
priorityLockNotes,
|
|
12277
12410
|
newTaskIdMap,
|
|
@@ -12418,7 +12551,7 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
|
|
|
12418
12551
|
writeSummary
|
|
12419
12552
|
};
|
|
12420
12553
|
}
|
|
12421
|
-
async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnly) {
|
|
12554
|
+
async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs) {
|
|
12422
12555
|
const prepareTimer = startTimer();
|
|
12423
12556
|
let t = startTimer();
|
|
12424
12557
|
const { mode, cycleNumber, strategyReviewWarning } = await validateAndPrepare(adapter2, force);
|
|
@@ -12468,6 +12601,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
12468
12601
|
if (mode !== "bootstrap" && context.productBrief.includes(TEMPLATE_MARKER)) {
|
|
12469
12602
|
throw new Error("TEMPLATE_BRIEF");
|
|
12470
12603
|
}
|
|
12604
|
+
if (skipHandoffs) context.skipHandoffs = true;
|
|
12471
12605
|
t = startTimer();
|
|
12472
12606
|
const userMessage = buildPlanUserMessage(context);
|
|
12473
12607
|
const buildMessageMs = t();
|
|
@@ -12640,9 +12774,11 @@ async function propagatePhaseStatus(adapter2) {
|
|
|
12640
12774
|
var lastPrepareContextHashes;
|
|
12641
12775
|
var lastPrepareUserMessage;
|
|
12642
12776
|
var lastPrepareContextBytes;
|
|
12777
|
+
var lastPrepareCycleNumber;
|
|
12778
|
+
var lastPrepareSkipHandoffs;
|
|
12643
12779
|
var planTool = {
|
|
12644
12780
|
name: "plan",
|
|
12645
|
-
description: 'Run once per cycle to generate BUILD HANDOFFs
|
|
12781
|
+
description: 'Run once per cycle to select tasks and generate BUILD HANDOFFs. Call after setup (first time) or after completing all builds AND running release for the previous cycle. Returns prioritised task recommendations with detailed implementation specs. NEVER call when unbuilt cycle tasks exist \u2014 build and release first. First call returns a planning prompt for you to execute (prepare phase). Then call again with mode "apply" and your output to write results. Use skip_handoffs=true for large backlogs \u2014 handoffs are then generated separately via `handoff_generate`.',
|
|
12646
12782
|
inputSchema: {
|
|
12647
12783
|
type: "object",
|
|
12648
12784
|
properties: {
|
|
@@ -12695,6 +12831,10 @@ var planTool = {
|
|
|
12695
12831
|
handoffs_only: {
|
|
12696
12832
|
type: "boolean",
|
|
12697
12833
|
description: "Skip backlog analysis and task selection. Only generate BUILD HANDOFFs for tasks already assigned to the target cycle. Requires pre-assigned tasks (set cycle number on tasks first). ~30% of normal plan cost."
|
|
12834
|
+
},
|
|
12835
|
+
skip_handoffs: {
|
|
12836
|
+
type: "boolean",
|
|
12837
|
+
description: "Run full planning (triage, task selection, board management) but skip BUILD HANDOFF generation. Selected tasks are assigned to the cycle without handoffs. Run `handoff_generate` after to create handoffs separately. Reduces planner cognitive load for large backlogs."
|
|
12698
12838
|
}
|
|
12699
12839
|
},
|
|
12700
12840
|
required: []
|
|
@@ -12733,7 +12873,12 @@ function formatPlanResult(result) {
|
|
|
12733
12873
|
if (result.priorityLockNote) lines.push(result.priorityLockNote.trim());
|
|
12734
12874
|
if (result.slackWarning) lines.push(result.slackWarning);
|
|
12735
12875
|
if (result.autoCommitNote) lines.push(result.autoCommitNote.trim());
|
|
12736
|
-
|
|
12876
|
+
if (result.skipHandoffs) {
|
|
12877
|
+
const taskCount = result.writeSummary?.taskIds.length ?? 0;
|
|
12878
|
+
lines.push("", `Next: run \`handoff_generate\` to create BUILD HANDOFFs for your ${taskCount} cycle task(s), then \`build_list\` to start building.`);
|
|
12879
|
+
} else {
|
|
12880
|
+
lines.push("", `Next: run \`build_list\` to see your cycle tasks, then \`build_execute <task_id>\` to start building.`);
|
|
12881
|
+
}
|
|
12737
12882
|
if (result.contextBytes !== void 0) {
|
|
12738
12883
|
const kb = (result.contextBytes / 1024).toFixed(1);
|
|
12739
12884
|
lines.push(`---`, `Context: ${kb}KB`);
|
|
@@ -12766,10 +12911,20 @@ async function handlePlan(adapter2, config2, args) {
|
|
|
12766
12911
|
const contextHashes = lastPrepareContextHashes;
|
|
12767
12912
|
const inputContext = lastPrepareUserMessage;
|
|
12768
12913
|
const contextBytes = lastPrepareContextBytes;
|
|
12914
|
+
const expectedCycleNumber = lastPrepareCycleNumber;
|
|
12915
|
+
const skipHandoffsCached = lastPrepareSkipHandoffs;
|
|
12769
12916
|
lastPrepareContextHashes = void 0;
|
|
12770
12917
|
lastPrepareUserMessage = void 0;
|
|
12771
12918
|
lastPrepareContextBytes = void 0;
|
|
12772
|
-
|
|
12919
|
+
lastPrepareCycleNumber = void 0;
|
|
12920
|
+
lastPrepareSkipHandoffs = void 0;
|
|
12921
|
+
const skipHandoffs = args.skip_handoffs === true || skipHandoffsCached === true;
|
|
12922
|
+
if (expectedCycleNumber !== void 0 && cycleNumber !== expectedCycleNumber) {
|
|
12923
|
+
return errorResponse(
|
|
12924
|
+
`cycle_number mismatch: prepare phase returned cycle ${expectedCycleNumber} but apply received ${cycleNumber}. Pass cycle_number: ${expectedCycleNumber} to match the prepare output.`
|
|
12925
|
+
);
|
|
12926
|
+
}
|
|
12927
|
+
const result = await applyPlan(adapter2, config2, llmResponse, planMode, cycleNumber, strategyReviewWarning, contextHashes, { contextBytes: contextBytes ?? void 0, skipHandoffs: skipHandoffs || void 0 });
|
|
12773
12928
|
let utilisation;
|
|
12774
12929
|
if (inputContext) {
|
|
12775
12930
|
try {
|
|
@@ -12777,7 +12932,7 @@ async function handlePlan(adapter2, config2, args) {
|
|
|
12777
12932
|
} catch {
|
|
12778
12933
|
}
|
|
12779
12934
|
}
|
|
12780
|
-
const response = formatPlanResult({ ...result, contextUtilisation: utilisation, contextBytes });
|
|
12935
|
+
const response = formatPlanResult({ ...result, contextUtilisation: utilisation, contextBytes, skipHandoffs });
|
|
12781
12936
|
return {
|
|
12782
12937
|
...response,
|
|
12783
12938
|
...contextBytes !== void 0 ? { _contextBytes: contextBytes } : {},
|
|
@@ -12789,10 +12944,13 @@ async function handlePlan(adapter2, config2, args) {
|
|
|
12789
12944
|
await propagatePhaseStatus(adapter2);
|
|
12790
12945
|
} catch {
|
|
12791
12946
|
}
|
|
12792
|
-
const
|
|
12947
|
+
const skipHandoffs = args.skip_handoffs === true;
|
|
12948
|
+
const result = await preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs);
|
|
12793
12949
|
lastPrepareContextHashes = result.contextHashes;
|
|
12794
12950
|
lastPrepareUserMessage = result.userMessage;
|
|
12795
12951
|
lastPrepareContextBytes = result.contextBytes;
|
|
12952
|
+
lastPrepareCycleNumber = result.cycleNumber;
|
|
12953
|
+
lastPrepareSkipHandoffs = skipHandoffs || void 0;
|
|
12796
12954
|
const modeLabel = result.mode === "bootstrap" ? "Bootstrap" : "Full";
|
|
12797
12955
|
const header = result.strategyReviewWarning ? `${result.strategyReviewWarning}
|
|
12798
12956
|
` : "";
|
|
@@ -13145,7 +13303,8 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
13145
13303
|
decisionUsage,
|
|
13146
13304
|
recData,
|
|
13147
13305
|
pendingRecs,
|
|
13148
|
-
registeredDocs
|
|
13306
|
+
registeredDocs,
|
|
13307
|
+
docsWithPendingActions
|
|
13149
13308
|
] = await Promise.all([
|
|
13150
13309
|
adapter2.readProductBrief(),
|
|
13151
13310
|
adapter2.getActiveDecisions(),
|
|
@@ -13169,7 +13328,9 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
13169
13328
|
adapter2.getRecommendationEffectiveness?.()?.catch(() => []) ?? Promise.resolve([]),
|
|
13170
13329
|
adapter2.getPendingRecommendations().catch(() => []),
|
|
13171
13330
|
// Doc registry — summaries for strategy review context
|
|
13172
|
-
adapter2.searchDocs?.({ status: "active", limit: 10 })?.catch(() => []) ?? Promise.resolve([])
|
|
13331
|
+
adapter2.searchDocs?.({ status: "active", limit: 10 })?.catch(() => []) ?? Promise.resolve([]),
|
|
13332
|
+
// Doc registry — docs with pending actions for staleness audit
|
|
13333
|
+
adapter2.searchDocs?.({ hasPendingActions: true, limit: 20 })?.catch(() => []) ?? Promise.resolve([])
|
|
13173
13334
|
]);
|
|
13174
13335
|
const tasks = [...activeTasks, ...recentDoneTasks];
|
|
13175
13336
|
const existingAdIds = new Set(decisions.map((d) => d.id));
|
|
@@ -13371,6 +13532,44 @@ ${unregistered.slice(0, 10).map((f) => `- ${f}`).join("\n")}`;
|
|
|
13371
13532
|
}
|
|
13372
13533
|
} catch {
|
|
13373
13534
|
}
|
|
13535
|
+
let docActionStalenessText;
|
|
13536
|
+
try {
|
|
13537
|
+
if (docsWithPendingActions && docsWithPendingActions.length > 0) {
|
|
13538
|
+
const STALE_THRESHOLD = 20;
|
|
13539
|
+
const doneTaskIds = new Set(recentDoneTasks.map((t) => t.displayId ?? t.id));
|
|
13540
|
+
const completed = [];
|
|
13541
|
+
const deferred = [];
|
|
13542
|
+
const stale = [];
|
|
13543
|
+
for (const doc of docsWithPendingActions) {
|
|
13544
|
+
const pendingActions = (doc.actions ?? []).filter((a) => a.status === "pending");
|
|
13545
|
+
if (pendingActions.length === 0) continue;
|
|
13546
|
+
const ageInCycles = cycleNumber - (doc.cycleCreated ?? cycleNumber);
|
|
13547
|
+
for (const action of pendingActions) {
|
|
13548
|
+
const line = ` - **${doc.title}** (C${doc.cycleCreated ?? "?"}): ${action.description}${action.linkedTaskId ? ` [\u2192${action.linkedTaskId}]` : ""}`;
|
|
13549
|
+
if (action.linkedTaskId && doneTaskIds.has(action.linkedTaskId)) {
|
|
13550
|
+
completed.push(line);
|
|
13551
|
+
} else if (ageInCycles > STALE_THRESHOLD) {
|
|
13552
|
+
stale.push(line);
|
|
13553
|
+
} else {
|
|
13554
|
+
deferred.push(line);
|
|
13555
|
+
}
|
|
13556
|
+
}
|
|
13557
|
+
}
|
|
13558
|
+
if (completed.length === 0 && deferred.length === 0 && stale.length === 0) {
|
|
13559
|
+
docActionStalenessText = "Doc Actions: all clear \u2014 no pending actions.";
|
|
13560
|
+
} else {
|
|
13561
|
+
const sections = [];
|
|
13562
|
+
if (stale.length > 0) sections.push(`**Stale (>${STALE_THRESHOLD} cycles, no matching Done task):**
|
|
13563
|
+
${stale.join("\n")}`);
|
|
13564
|
+
if (completed.length > 0) sections.push(`**Completed but not closed (linked task is Done):**
|
|
13565
|
+
${completed.join("\n")}`);
|
|
13566
|
+
if (deferred.length > 0) sections.push(`**Deferred (<${STALE_THRESHOLD} cycles, no matching Done task):**
|
|
13567
|
+
${deferred.join("\n")}`);
|
|
13568
|
+
docActionStalenessText = sections.join("\n\n");
|
|
13569
|
+
}
|
|
13570
|
+
}
|
|
13571
|
+
} catch {
|
|
13572
|
+
}
|
|
13374
13573
|
logDataSourceSummary("strategy_review_audit", [
|
|
13375
13574
|
{ label: "discoveryCanvas", hasData: discoveryCanvasText !== void 0 },
|
|
13376
13575
|
{ label: "briefImplications", hasData: briefImplicationsText !== void 0 },
|
|
@@ -13382,7 +13581,8 @@ ${unregistered.slice(0, 10).map((f) => `- ${f}`).join("\n")}`;
|
|
|
13382
13581
|
{ label: "registeredDocs", hasData: registeredDocsText !== void 0 },
|
|
13383
13582
|
{ label: "recentPlans", hasData: recentPlansText !== void 0 },
|
|
13384
13583
|
{ label: "unregisteredDocs", hasData: unregisteredDocsText !== void 0 },
|
|
13385
|
-
{ label: "taskComments", hasData: taskCommentsText !== void 0 }
|
|
13584
|
+
{ label: "taskComments", hasData: taskCommentsText !== void 0 },
|
|
13585
|
+
{ label: "docActionStaleness", hasData: docActionStalenessText !== void 0 }
|
|
13386
13586
|
]);
|
|
13387
13587
|
const context = {
|
|
13388
13588
|
sessionNumber: cycleNumber,
|
|
@@ -13408,7 +13608,8 @@ ${unregistered.slice(0, 10).map((f) => `- ${f}`).join("\n")}`;
|
|
|
13408
13608
|
registeredDocs: registeredDocsText,
|
|
13409
13609
|
recentPlans: recentPlansText,
|
|
13410
13610
|
unregisteredDocs: unregisteredDocsText,
|
|
13411
|
-
taskComments: taskCommentsText
|
|
13611
|
+
taskComments: taskCommentsText,
|
|
13612
|
+
docActionStaleness: docActionStalenessText
|
|
13412
13613
|
};
|
|
13413
13614
|
const BUDGET_SOFT2 = 5e4;
|
|
13414
13615
|
const BUDGET_HARD2 = 6e4;
|
|
@@ -13523,21 +13724,6 @@ ${cleanContent}`;
|
|
|
13523
13724
|
});
|
|
13524
13725
|
} catch {
|
|
13525
13726
|
}
|
|
13526
|
-
try {
|
|
13527
|
-
const cycleLog = await adapter2.getCycleLogSince(cycleNumber);
|
|
13528
|
-
const currentEntry = cycleLog.find((e) => e.cycleNumber === cycleNumber);
|
|
13529
|
-
if (currentEntry) {
|
|
13530
|
-
const reviewCompleteText = `Strategy review completed Cycle ${cycleNumber}. Next due ~Cycle ${cycleNumber + 5}.`;
|
|
13531
|
-
const existingCf = currentEntry.carryForward ?? "";
|
|
13532
|
-
const updatedCf = existingCf ? existingCf.replace(/strategy review (?:due|available|overdue)[^.]*\.[^.]*/gi, reviewCompleteText).trim() : reviewCompleteText;
|
|
13533
|
-
const finalCf = updatedCf === existingCf ? `${existingCf} ${reviewCompleteText}`.trim() : updatedCf;
|
|
13534
|
-
await adapter2.writeCycleLogEntry({
|
|
13535
|
-
...currentEntry,
|
|
13536
|
-
carryForward: finalCf
|
|
13537
|
-
});
|
|
13538
|
-
}
|
|
13539
|
-
} catch {
|
|
13540
|
-
}
|
|
13541
13727
|
if (data.activeDecisionUpdates && data.activeDecisionUpdates.length > 0) {
|
|
13542
13728
|
await Promise.all(data.activeDecisionUpdates.map(async (ad) => {
|
|
13543
13729
|
if (ad.action === "delete" && adapter2.deleteActiveDecision) {
|
|
@@ -14514,8 +14700,8 @@ async function viewBoard(adapter2, phaseFilter, options) {
|
|
|
14514
14700
|
const bi = PRIORITY_ORDER.indexOf(b2.priority);
|
|
14515
14701
|
const priorityDiff = (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
14516
14702
|
if (priorityDiff !== 0) return priorityDiff;
|
|
14517
|
-
const aDate = a.createdAt
|
|
14518
|
-
const bDate = b2.createdAt
|
|
14703
|
+
const aDate = a.createdAt ? String(a.createdAt) : "";
|
|
14704
|
+
const bDate = b2.createdAt ? String(b2.createdAt) : "";
|
|
14519
14705
|
return bDate.localeCompare(aDate);
|
|
14520
14706
|
});
|
|
14521
14707
|
const total = allTasks.length;
|
|
@@ -15694,6 +15880,7 @@ async function ensurePapiPermission(projectRoot) {
|
|
|
15694
15880
|
}
|
|
15695
15881
|
}
|
|
15696
15882
|
async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText, conventionsText) {
|
|
15883
|
+
const warnings = [];
|
|
15697
15884
|
if (config2.adapterType !== "pg") {
|
|
15698
15885
|
await writeFile2(join4(config2.papiDir, "PRODUCT_BRIEF.md"), briefText, "utf-8");
|
|
15699
15886
|
}
|
|
@@ -15701,6 +15888,10 @@ async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText
|
|
|
15701
15888
|
const briefPhases = parsePhases(briefText);
|
|
15702
15889
|
if (briefPhases.length > 0) {
|
|
15703
15890
|
await adapter2.writePhases(briefPhases);
|
|
15891
|
+
} else {
|
|
15892
|
+
warnings.push(
|
|
15893
|
+
"Phase parsing produced 0 phases \u2014 the brief may be missing a valid <!-- PHASES:START --> ... <!-- PHASES:END --> block. Run `plan` and the planner will infer phases from your description. To fix: re-run `setup` with a brief that includes a PHASES YAML block."
|
|
15894
|
+
);
|
|
15704
15895
|
}
|
|
15705
15896
|
try {
|
|
15706
15897
|
if (adapter2.createHorizon && adapter2.createStage && adapter2.linkPhasesToStage) {
|
|
@@ -15739,7 +15930,19 @@ async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText
|
|
|
15739
15930
|
}
|
|
15740
15931
|
}
|
|
15741
15932
|
}
|
|
15742
|
-
} catch {
|
|
15933
|
+
} catch (err) {
|
|
15934
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
15935
|
+
warnings.push(
|
|
15936
|
+
`AD seeding failed \u2014 active decisions were not created. Check that your ad_seed_response is valid JSON. Error: ${msg}`
|
|
15937
|
+
);
|
|
15938
|
+
seededAds = 0;
|
|
15939
|
+
}
|
|
15940
|
+
if (seededAds === 0 && adSeedText) {
|
|
15941
|
+
if (!warnings.some((w) => w.startsWith("AD seeding failed"))) {
|
|
15942
|
+
warnings.push(
|
|
15943
|
+
"AD seeding produced 0 active decisions \u2014 the JSON may be valid but empty or missing required `id` and `body` fields."
|
|
15944
|
+
);
|
|
15945
|
+
}
|
|
15743
15946
|
}
|
|
15744
15947
|
}
|
|
15745
15948
|
if (conventionsText?.trim()) {
|
|
@@ -15750,7 +15953,7 @@ async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText
|
|
|
15750
15953
|
} catch {
|
|
15751
15954
|
}
|
|
15752
15955
|
}
|
|
15753
|
-
return { seededAds };
|
|
15956
|
+
return { seededAds, warnings };
|
|
15754
15957
|
}
|
|
15755
15958
|
var SKIP_PATTERNS = /* @__PURE__ */ new Set([
|
|
15756
15959
|
"node_modules",
|
|
@@ -15988,7 +16191,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
15988
16191
|
if (!briefText.trim()) {
|
|
15989
16192
|
throw new Error("brief_response is required and cannot be empty.");
|
|
15990
16193
|
}
|
|
15991
|
-
const { seededAds } = await applySetupOutputs(adapter2, config2, input, briefText, adSeedText, conventionsText);
|
|
16194
|
+
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, briefText, adSeedText, conventionsText);
|
|
15992
16195
|
let createdTasks = 0;
|
|
15993
16196
|
if (initialTasksText?.trim()) {
|
|
15994
16197
|
try {
|
|
@@ -16073,7 +16276,8 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
16073
16276
|
projectName: input.projectName,
|
|
16074
16277
|
seededAds,
|
|
16075
16278
|
createdTasks,
|
|
16076
|
-
cursorScaffolded
|
|
16279
|
+
cursorScaffolded,
|
|
16280
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
16077
16281
|
};
|
|
16078
16282
|
}
|
|
16079
16283
|
|
|
@@ -16182,8 +16386,12 @@ ${result.seededAds} Active Decision${result.seededAds > 1 ? "s" : ""} seeded bas
|
|
|
16182
16386
|
${result.createdTasks} initial backlog task${result.createdTasks > 1 ? "s" : ""} created from codebase analysis.` : "";
|
|
16183
16387
|
const constraintsHint = !constraints ? '\n\nTip: consider adding `constraints` (e.g. "must use PostgreSQL", "HIPAA compliant", "offline-first") to improve Active Decision seeding.' : "";
|
|
16184
16388
|
const editorNote = result.cursorScaffolded ? "\n\nCursor detected \u2014 `.cursor/rules/papi.mdc` scaffolded alongside CLAUDE.md." : "";
|
|
16389
|
+
const warningsNote = result.warnings && result.warnings.length > 0 ? `
|
|
16390
|
+
|
|
16391
|
+
\u26A0\uFE0F **Setup warnings (non-blocking):**
|
|
16392
|
+
${result.warnings.map((w) => `- ${w}`).join("\n")}` : "";
|
|
16185
16393
|
return textResponse(
|
|
16186
|
-
`${prefix}Product Brief generated and saved.${adNote}${taskNote}${constraintsHint}${editorNote}
|
|
16394
|
+
`${prefix}Product Brief generated and saved.${adNote}${taskNote}${constraintsHint}${editorNote}${warningsNote}
|
|
16187
16395
|
|
|
16188
16396
|
**Important:** Setup created/modified files (CLAUDE.md, .claude/settings.json, docs/). Commit these changes before running \`build_execute\` \u2014 it requires a clean working directory.
|
|
16189
16397
|
|
|
@@ -16345,6 +16553,7 @@ init_git();
|
|
|
16345
16553
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
16346
16554
|
import { readdirSync as readdirSync3, existsSync as existsSync3, readFileSync } from "fs";
|
|
16347
16555
|
import { join as join5 } from "path";
|
|
16556
|
+
var buildStartTimes = /* @__PURE__ */ new Map();
|
|
16348
16557
|
function capitalizeCompleted(value) {
|
|
16349
16558
|
const map = {
|
|
16350
16559
|
yes: "Yes",
|
|
@@ -16550,6 +16759,7 @@ async function startBuild(adapter2, config2, taskId, options = {}) {
|
|
|
16550
16759
|
if (task.status !== "In Progress") {
|
|
16551
16760
|
await adapter2.updateTaskStatus(taskId, "In Progress");
|
|
16552
16761
|
}
|
|
16762
|
+
buildStartTimes.set(taskId, (/* @__PURE__ */ new Date()).toISOString());
|
|
16553
16763
|
let phaseChanges = [];
|
|
16554
16764
|
try {
|
|
16555
16765
|
phaseChanges = await propagatePhaseStatus(adapter2);
|
|
@@ -16615,8 +16825,11 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
16615
16825
|
correctionsCount: input.correctionsCount,
|
|
16616
16826
|
briefImplications: input.briefImplications,
|
|
16617
16827
|
deadEnds: input.deadEnds,
|
|
16618
|
-
iterationCount
|
|
16828
|
+
iterationCount,
|
|
16829
|
+
startedAt: buildStartTimes.get(taskId) ?? void 0,
|
|
16830
|
+
completedAt: now.toISOString()
|
|
16619
16831
|
};
|
|
16832
|
+
buildStartTimes.delete(taskId);
|
|
16620
16833
|
if (input.relatedDecisions) {
|
|
16621
16834
|
const adIds = input.relatedDecisions.split(",").map((s) => s.trim()).filter(Boolean);
|
|
16622
16835
|
if (adIds.length > 0) report.relatedDecisions = adIds;
|
|
@@ -20253,7 +20466,7 @@ var hierarchyUpdateTool = {
|
|
|
20253
20466
|
},
|
|
20254
20467
|
status: {
|
|
20255
20468
|
type: "string",
|
|
20256
|
-
enum: ["
|
|
20469
|
+
enum: ["Not Started", "In Progress", "Done", "Deferred"],
|
|
20257
20470
|
description: "The new status to set."
|
|
20258
20471
|
},
|
|
20259
20472
|
exit_criteria: {
|
|
@@ -20265,7 +20478,7 @@ var hierarchyUpdateTool = {
|
|
|
20265
20478
|
required: ["level", "name"]
|
|
20266
20479
|
}
|
|
20267
20480
|
};
|
|
20268
|
-
var VALID_STATUSES3 = /* @__PURE__ */ new Set(["
|
|
20481
|
+
var VALID_STATUSES3 = /* @__PURE__ */ new Set(["Not Started", "In Progress", "Done", "Deferred"]);
|
|
20269
20482
|
async function handleHierarchyUpdate(adapter2, args) {
|
|
20270
20483
|
const level = args.level;
|
|
20271
20484
|
const name = args.name;
|
|
@@ -20281,7 +20494,7 @@ async function handleHierarchyUpdate(adapter2, args) {
|
|
|
20281
20494
|
return errorResponse(`Invalid level "${level}". Must be "phase", "stage", or "horizon".`);
|
|
20282
20495
|
}
|
|
20283
20496
|
if (status && !VALID_STATUSES3.has(status)) {
|
|
20284
|
-
return errorResponse(`Invalid status "${status}". Must be one of:
|
|
20497
|
+
return errorResponse(`Invalid status "${status}". Must be one of: Not Started, In Progress, Done, Deferred.`);
|
|
20285
20498
|
}
|
|
20286
20499
|
if (exitCriteria !== void 0 && level !== "stage") {
|
|
20287
20500
|
return errorResponse("exit_criteria can only be set on stages.");
|
|
@@ -20880,7 +21093,7 @@ function extractTitle(filePath) {
|
|
|
20880
21093
|
}
|
|
20881
21094
|
async function handleDocScan(adapter2, config2, args) {
|
|
20882
21095
|
if (!adapter2.searchDocs) {
|
|
20883
|
-
return errorResponse("Doc registry not available
|
|
21096
|
+
return errorResponse("Doc registry not available on this adapter.");
|
|
20884
21097
|
}
|
|
20885
21098
|
const includePlans = args.include_plans ?? false;
|
|
20886
21099
|
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
@@ -20995,6 +21208,217 @@ Check that the project IDs are correct and exist in the same Supabase instance.`
|
|
|
20995
21208
|
return textResponse(lines.join("\n"));
|
|
20996
21209
|
}
|
|
20997
21210
|
|
|
21211
|
+
// src/services/handoff.ts
|
|
21212
|
+
init_dist2();
|
|
21213
|
+
async function prepareHandoffs(adapter2, _config, taskIds) {
|
|
21214
|
+
const timer2 = startTimer();
|
|
21215
|
+
const cycles = await adapter2.readCycles();
|
|
21216
|
+
const activeCycle = cycles.find((c) => c.status === "active");
|
|
21217
|
+
if (!activeCycle) {
|
|
21218
|
+
throw new Error("No active cycle found. Run `plan` first to create a cycle.");
|
|
21219
|
+
}
|
|
21220
|
+
const cycleNumber = activeCycle.number;
|
|
21221
|
+
const allTasks = await adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready", "In Progress"] });
|
|
21222
|
+
let cycleTasks = allTasks.filter((t) => t.cycle === cycleNumber);
|
|
21223
|
+
if (taskIds?.length) {
|
|
21224
|
+
const idSet = new Set(taskIds);
|
|
21225
|
+
cycleTasks = cycleTasks.filter((t) => idSet.has(t.id));
|
|
21226
|
+
}
|
|
21227
|
+
const tasksNeedingHandoffs = cycleTasks.filter((t) => !t.buildHandoff);
|
|
21228
|
+
if (tasksNeedingHandoffs.length === 0) {
|
|
21229
|
+
throw new Error(
|
|
21230
|
+
taskIds?.length ? `All specified tasks already have BUILD HANDOFFs. Nothing to generate.` : `All ${cycleTasks.length} cycle task(s) already have BUILD HANDOFFs. Nothing to generate.`
|
|
21231
|
+
);
|
|
21232
|
+
}
|
|
21233
|
+
const [decisions, reports, brief] = await Promise.all([
|
|
21234
|
+
adapter2.getActiveDecisions(),
|
|
21235
|
+
adapter2.getRecentBuildReports(10),
|
|
21236
|
+
adapter2.readProductBrief()
|
|
21237
|
+
]);
|
|
21238
|
+
const northStar = await adapter2.getCurrentNorthStar?.() ?? "";
|
|
21239
|
+
const userMessage = buildHandoffsOnlyUserMessage({
|
|
21240
|
+
cycleNumber: cycleNumber - 1,
|
|
21241
|
+
// buildHandoffsOnlyUserMessage adds +1 internally
|
|
21242
|
+
preAssignedTasks: tasksNeedingHandoffs,
|
|
21243
|
+
activeDecisions: formatActiveDecisionsForPlan(decisions),
|
|
21244
|
+
recentBuildReports: formatBuildReports(reports),
|
|
21245
|
+
productBrief: brief,
|
|
21246
|
+
northStar
|
|
21247
|
+
});
|
|
21248
|
+
const contextBytes = Buffer.byteLength(userMessage, "utf-8");
|
|
21249
|
+
const elapsed = timer2();
|
|
21250
|
+
console.error(`[handoff-perf] prepareHandoffs: ${elapsed}ms, ${contextBytes} bytes, ${tasksNeedingHandoffs.length} task(s)`);
|
|
21251
|
+
const systemPrompt = await getPrompt("plan-system");
|
|
21252
|
+
return {
|
|
21253
|
+
cycleNumber,
|
|
21254
|
+
systemPrompt,
|
|
21255
|
+
userMessage,
|
|
21256
|
+
contextBytes,
|
|
21257
|
+
taskCount: tasksNeedingHandoffs.length
|
|
21258
|
+
};
|
|
21259
|
+
}
|
|
21260
|
+
async function applyHandoffs(adapter2, rawLlmOutput, cycleNumber) {
|
|
21261
|
+
const timer2 = startTimer();
|
|
21262
|
+
const { data } = parseStructuredOutput(rawLlmOutput);
|
|
21263
|
+
if (!data) {
|
|
21264
|
+
throw new Error("Could not parse structured output. Ensure your output includes <!-- PAPI_STRUCTURED_OUTPUT --> with valid JSON.");
|
|
21265
|
+
}
|
|
21266
|
+
const handoffs = data.cycleHandoffs ?? [];
|
|
21267
|
+
if (handoffs.length === 0) {
|
|
21268
|
+
throw new Error("No cycleHandoffs found in structured output. Ensure your output includes handoffs in the cycleHandoffs array.");
|
|
21269
|
+
}
|
|
21270
|
+
const taskIdsToWrite = handoffs.map((h) => h.taskId);
|
|
21271
|
+
const existingHandoffSet = /* @__PURE__ */ new Set();
|
|
21272
|
+
try {
|
|
21273
|
+
const tasks = await adapter2.getTasks(taskIdsToWrite);
|
|
21274
|
+
for (const t of tasks) {
|
|
21275
|
+
if (t.buildHandoff) existingHandoffSet.add(t.id);
|
|
21276
|
+
}
|
|
21277
|
+
} catch {
|
|
21278
|
+
}
|
|
21279
|
+
const written = [];
|
|
21280
|
+
let skipped = 0;
|
|
21281
|
+
const warnings = [];
|
|
21282
|
+
for (const handoff of handoffs) {
|
|
21283
|
+
try {
|
|
21284
|
+
if (existingHandoffSet.has(handoff.taskId)) {
|
|
21285
|
+
console.error(`[handoff] skipping ${handoff.taskId} \u2014 already has handoff`);
|
|
21286
|
+
skipped++;
|
|
21287
|
+
continue;
|
|
21288
|
+
}
|
|
21289
|
+
const parsed = parseBuildHandoff(handoff.buildHandoff);
|
|
21290
|
+
if (!parsed) {
|
|
21291
|
+
warnings.push(`Failed to parse handoff for ${handoff.taskId}`);
|
|
21292
|
+
continue;
|
|
21293
|
+
}
|
|
21294
|
+
if (!parsed.createdAt) {
|
|
21295
|
+
parsed.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21296
|
+
}
|
|
21297
|
+
await adapter2.updateTask(handoff.taskId, { buildHandoff: parsed });
|
|
21298
|
+
written.push(handoff.taskId);
|
|
21299
|
+
} catch (err) {
|
|
21300
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21301
|
+
warnings.push(`Failed to write handoff for ${handoff.taskId}: ${msg}`);
|
|
21302
|
+
}
|
|
21303
|
+
}
|
|
21304
|
+
const elapsed = timer2();
|
|
21305
|
+
console.error(`[handoff-perf] applyHandoffs: ${elapsed}ms, ${written.length} written, ${skipped} skipped`);
|
|
21306
|
+
return {
|
|
21307
|
+
cycleNumber,
|
|
21308
|
+
handoffsWritten: written.length,
|
|
21309
|
+
skipped,
|
|
21310
|
+
taskIds: written,
|
|
21311
|
+
warnings
|
|
21312
|
+
};
|
|
21313
|
+
}
|
|
21314
|
+
|
|
21315
|
+
// src/tools/handoff.ts
|
|
21316
|
+
var lastPrepareCycleNumber2;
|
|
21317
|
+
var lastPrepareContextBytes2;
|
|
21318
|
+
var handoffGenerateTool = {
|
|
21319
|
+
name: "handoff_generate",
|
|
21320
|
+
description: "Generate BUILD HANDOFFs for cycle tasks that don't have one yet. Run after `plan` (with skip_handoffs=true) or to regenerate stale handoffs. Uses the prepare/apply pattern \u2014 first call returns a prompt, second call persists results.",
|
|
21321
|
+
inputSchema: {
|
|
21322
|
+
type: "object",
|
|
21323
|
+
properties: {
|
|
21324
|
+
mode: {
|
|
21325
|
+
type: "string",
|
|
21326
|
+
enum: ["prepare", "apply"],
|
|
21327
|
+
description: '"prepare" returns the handoff prompt for you to execute. "apply" accepts your generated output and persists handoffs. Defaults to "prepare" when omitted.'
|
|
21328
|
+
},
|
|
21329
|
+
task_ids: {
|
|
21330
|
+
type: "array",
|
|
21331
|
+
items: { type: "string" },
|
|
21332
|
+
description: "Specific task IDs to generate handoffs for. If omitted, generates for all cycle tasks missing handoffs."
|
|
21333
|
+
},
|
|
21334
|
+
llm_response: {
|
|
21335
|
+
type: "string",
|
|
21336
|
+
description: 'Your raw output from executing the handoff prompt (mode "apply" only). Must include both Part 1 (markdown) and Part 2 (structured JSON after <!-- PAPI_STRUCTURED_OUTPUT -->).'
|
|
21337
|
+
},
|
|
21338
|
+
cycle_number: {
|
|
21339
|
+
type: "number",
|
|
21340
|
+
description: 'The cycle number returned from prepare phase (mode "apply" only).'
|
|
21341
|
+
}
|
|
21342
|
+
},
|
|
21343
|
+
required: []
|
|
21344
|
+
}
|
|
21345
|
+
};
|
|
21346
|
+
async function handleHandoffGenerate(adapter2, config2, args) {
|
|
21347
|
+
const toolMode = args.mode;
|
|
21348
|
+
try {
|
|
21349
|
+
if (toolMode === "apply") {
|
|
21350
|
+
const llmResponse = args.llm_response;
|
|
21351
|
+
if (!llmResponse || !llmResponse.trim()) {
|
|
21352
|
+
return errorResponse('llm_response is required for mode "apply". Pass your complete handoff output including the <!-- PAPI_STRUCTURED_OUTPUT --> block.');
|
|
21353
|
+
}
|
|
21354
|
+
const cycleNumber = typeof args.cycle_number === "number" ? args.cycle_number : lastPrepareCycleNumber2;
|
|
21355
|
+
if (cycleNumber === void 0) {
|
|
21356
|
+
return errorResponse('cycle_number is required for mode "apply". Pass the cycle number from the prepare phase.');
|
|
21357
|
+
}
|
|
21358
|
+
const expectedCycleNumber = lastPrepareCycleNumber2;
|
|
21359
|
+
const contextBytes = lastPrepareContextBytes2;
|
|
21360
|
+
lastPrepareCycleNumber2 = void 0;
|
|
21361
|
+
lastPrepareContextBytes2 = void 0;
|
|
21362
|
+
if (expectedCycleNumber !== void 0 && cycleNumber !== expectedCycleNumber) {
|
|
21363
|
+
return errorResponse(
|
|
21364
|
+
`cycle_number mismatch: prepare phase returned cycle ${expectedCycleNumber} but apply received ${cycleNumber}.`
|
|
21365
|
+
);
|
|
21366
|
+
}
|
|
21367
|
+
const result = await applyHandoffs(adapter2, llmResponse, cycleNumber);
|
|
21368
|
+
const lines = [];
|
|
21369
|
+
lines.push(`**Handoff Generation \u2014 Cycle ${result.cycleNumber}**`);
|
|
21370
|
+
lines.push(`${result.handoffsWritten} handoff(s) written: ${result.taskIds.join(", ")}`);
|
|
21371
|
+
if (result.skipped > 0) lines.push(`${result.skipped} task(s) skipped (already had handoffs)`);
|
|
21372
|
+
if (result.warnings.length > 0) lines.push("\u26A0\uFE0F Warnings: " + result.warnings.join("; "));
|
|
21373
|
+
lines.push("", "Next: run `build_list` to see your cycle tasks, then `build_execute <task_id>` to start building.");
|
|
21374
|
+
if (contextBytes !== void 0) {
|
|
21375
|
+
const kb = (contextBytes / 1024).toFixed(1);
|
|
21376
|
+
lines.push("---", `Context: ${kb}KB`);
|
|
21377
|
+
}
|
|
21378
|
+
return textResponse(lines.join("\n"));
|
|
21379
|
+
}
|
|
21380
|
+
{
|
|
21381
|
+
const taskIds = Array.isArray(args.task_ids) ? args.task_ids.filter((id) => typeof id === "string") : void 0;
|
|
21382
|
+
const result = await prepareHandoffs(adapter2, config2, taskIds);
|
|
21383
|
+
lastPrepareCycleNumber2 = result.cycleNumber;
|
|
21384
|
+
lastPrepareContextBytes2 = result.contextBytes;
|
|
21385
|
+
return textResponse(
|
|
21386
|
+
`## PAPI Handoff Generation \u2014 Prepare Phase (Cycle ${result.cycleNumber})
|
|
21387
|
+
|
|
21388
|
+
Generate BUILD HANDOFFs for ${result.taskCount} task(s) that need them.
|
|
21389
|
+
|
|
21390
|
+
**IMPORTANT:** Your output must have TWO parts:
|
|
21391
|
+
1. Natural language markdown with BUILD HANDOFF blocks
|
|
21392
|
+
2. After \`<!-- PAPI_STRUCTURED_OUTPUT -->\`, a JSON block with structured data
|
|
21393
|
+
|
|
21394
|
+
When done, call \`handoff_generate\` again with:
|
|
21395
|
+
- \`mode\`: "apply"
|
|
21396
|
+
- \`llm_response\`: your complete output (both parts)
|
|
21397
|
+
- \`cycle_number\`: ${result.cycleNumber}
|
|
21398
|
+
|
|
21399
|
+
---
|
|
21400
|
+
|
|
21401
|
+
### System Prompt
|
|
21402
|
+
|
|
21403
|
+
<system_prompt>
|
|
21404
|
+
${result.systemPrompt}
|
|
21405
|
+
</system_prompt>
|
|
21406
|
+
|
|
21407
|
+
---
|
|
21408
|
+
|
|
21409
|
+
### Context
|
|
21410
|
+
|
|
21411
|
+
<context>
|
|
21412
|
+
${result.userMessage}
|
|
21413
|
+
</context>`
|
|
21414
|
+
);
|
|
21415
|
+
}
|
|
21416
|
+
} catch (err) {
|
|
21417
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21418
|
+
return errorResponse(message);
|
|
21419
|
+
}
|
|
21420
|
+
}
|
|
21421
|
+
|
|
20998
21422
|
// src/lib/telemetry.ts
|
|
20999
21423
|
var TELEMETRY_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
21000
21424
|
var TELEMETRY_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd1ZXdneWdjcGNtcmNvcHBpaHp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI2Njk2NTMsImV4cCI6MjA4ODI0NTY1M30.V5Jw7wJgiMpSQPa2mt0ftjyye5ynG1qLlam00yPVNJY";
|
|
@@ -21062,7 +21486,8 @@ var TOOLS_REQUIRING_PAPI = /* @__PURE__ */ new Set([
|
|
|
21062
21486
|
"review_submit",
|
|
21063
21487
|
"orient",
|
|
21064
21488
|
"hierarchy_update",
|
|
21065
|
-
"zoom_out"
|
|
21489
|
+
"zoom_out",
|
|
21490
|
+
"handoff_generate"
|
|
21066
21491
|
]);
|
|
21067
21492
|
function createServer(adapter2, config2) {
|
|
21068
21493
|
const server2 = new Server(
|
|
@@ -21148,7 +21573,8 @@ function createServer(adapter2, config2) {
|
|
|
21148
21573
|
docRegisterTool,
|
|
21149
21574
|
docSearchTool,
|
|
21150
21575
|
docScanTool,
|
|
21151
|
-
getSiblingAdsTool
|
|
21576
|
+
getSiblingAdsTool,
|
|
21577
|
+
handoffGenerateTool
|
|
21152
21578
|
]
|
|
21153
21579
|
}));
|
|
21154
21580
|
server2.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -21264,6 +21690,9 @@ function createServer(adapter2, config2) {
|
|
|
21264
21690
|
case "get_sibling_ads":
|
|
21265
21691
|
result = await handleGetSiblingAds(adapter2, safeArgs);
|
|
21266
21692
|
break;
|
|
21693
|
+
case "handoff_generate":
|
|
21694
|
+
result = await handleHandoffGenerate(adapter2, config2, safeArgs);
|
|
21695
|
+
break;
|
|
21267
21696
|
default:
|
|
21268
21697
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
21269
21698
|
}
|
|
@@ -21274,12 +21703,13 @@ function createServer(adapter2, config2) {
|
|
|
21274
21703
|
delete result._usage;
|
|
21275
21704
|
delete result._contextBytes;
|
|
21276
21705
|
delete result._contextUtilisation;
|
|
21277
|
-
|
|
21278
|
-
|
|
21279
|
-
|
|
21280
|
-
|
|
21281
|
-
|
|
21282
|
-
}
|
|
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 {
|
|
21283
21713
|
}
|
|
21284
21714
|
const telemetryProjectId = process.env["PAPI_PROJECT_ID"];
|
|
21285
21715
|
if (telemetryProjectId) {
|
|
@@ -21287,7 +21717,6 @@ function createServer(adapter2, config2) {
|
|
|
21287
21717
|
adapter_type: config2.adapterType
|
|
21288
21718
|
});
|
|
21289
21719
|
const isApplyMode = safeArgs.mode === "apply";
|
|
21290
|
-
const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
|
|
21291
21720
|
if (!isError) {
|
|
21292
21721
|
if (name === "setup" && isApplyMode) {
|
|
21293
21722
|
emitMilestone(telemetryProjectId, "setup_completed");
|
|
@@ -21389,5 +21818,28 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
|
|
|
21389
21818
|
}]
|
|
21390
21819
|
}));
|
|
21391
21820
|
}
|
|
21821
|
+
if (pkgVersion !== "unknown") {
|
|
21822
|
+
(async () => {
|
|
21823
|
+
try {
|
|
21824
|
+
const controller = new AbortController();
|
|
21825
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
21826
|
+
const res = await fetch("https://registry.npmjs.org/@papi-ai/server/latest", {
|
|
21827
|
+
signal: controller.signal
|
|
21828
|
+
});
|
|
21829
|
+
clearTimeout(timeout);
|
|
21830
|
+
if (res.ok) {
|
|
21831
|
+
const data = await res.json();
|
|
21832
|
+
const latest = data.version;
|
|
21833
|
+
if (latest && latest !== pkgVersion) {
|
|
21834
|
+
process.stderr.write(
|
|
21835
|
+
`\u26A0 Update available: ${pkgVersion} \u2192 ${latest}. Run: npx @papi-ai/server@latest
|
|
21836
|
+
`
|
|
21837
|
+
);
|
|
21838
|
+
}
|
|
21839
|
+
}
|
|
21840
|
+
} catch {
|
|
21841
|
+
}
|
|
21842
|
+
})();
|
|
21843
|
+
}
|
|
21392
21844
|
var transport = new StdioServerTransport();
|
|
21393
21845
|
await server.connect(transport);
|