@papi-ai/server 0.3.0-alpha.1 → 0.3.1-alpha
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 +1742 -122
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1122,6 +1122,17 @@ function serializeReview(review) {
|
|
|
1122
1122
|
);
|
|
1123
1123
|
if (review.handoffRevision !== void 0) lines.push(`- **Handoff Revision:** ${review.handoffRevision}`);
|
|
1124
1124
|
if (review.buildCommitSha) lines.push(`- **Build Commit SHA:** ${review.buildCommitSha}`);
|
|
1125
|
+
if (review.autoReview) {
|
|
1126
|
+
lines.push("", `#### Auto-Review (${review.autoReview.verdict})`);
|
|
1127
|
+
lines.push(`> ${review.autoReview.summary}`);
|
|
1128
|
+
if (review.autoReview.findings.length > 0) {
|
|
1129
|
+
lines.push("");
|
|
1130
|
+
for (const f of review.autoReview.findings) {
|
|
1131
|
+
const loc = f.file ? f.line ? `${f.file}:${f.line}` : f.file : "";
|
|
1132
|
+
lines.push(`- \`${f.severity}\`${loc ? ` ${loc}` : ""}: ${f.message}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1125
1136
|
return lines.join("\n");
|
|
1126
1137
|
}
|
|
1127
1138
|
function prependReview(review, content) {
|
|
@@ -4344,6 +4355,7 @@ __export(dist_exports, {
|
|
|
4344
4355
|
PgAdapter: () => PgAdapter,
|
|
4345
4356
|
PgPapiAdapter: () => PgPapiAdapter,
|
|
4346
4357
|
configFromEnv: () => configFromEnv,
|
|
4358
|
+
ensureSchema: () => ensureSchema,
|
|
4347
4359
|
normaliseComplexity: () => normaliseComplexity,
|
|
4348
4360
|
normaliseEffort: () => normaliseEffort
|
|
4349
4361
|
});
|
|
@@ -4374,6 +4386,29 @@ function definedEntries(obj) {
|
|
|
4374
4386
|
}
|
|
4375
4387
|
return result;
|
|
4376
4388
|
}
|
|
4389
|
+
async function ensureSchema(config2) {
|
|
4390
|
+
const sql = src_default(config2.connectionString, {
|
|
4391
|
+
max: 1,
|
|
4392
|
+
connect_timeout: 10
|
|
4393
|
+
});
|
|
4394
|
+
try {
|
|
4395
|
+
const [result] = await sql`
|
|
4396
|
+
SELECT EXISTS (
|
|
4397
|
+
SELECT 1 FROM information_schema.tables
|
|
4398
|
+
WHERE table_schema = 'public' AND table_name = 'projects'
|
|
4399
|
+
) as exists
|
|
4400
|
+
`;
|
|
4401
|
+
if (result.exists) {
|
|
4402
|
+
return false;
|
|
4403
|
+
}
|
|
4404
|
+
console.error("[papi] Fresh database detected \u2014 creating PAPI schema...");
|
|
4405
|
+
await sql.unsafe(BASE_SCHEMA_SQL);
|
|
4406
|
+
console.error("[papi] \u2713 Schema created (31 tables)");
|
|
4407
|
+
return true;
|
|
4408
|
+
} finally {
|
|
4409
|
+
await sql.end();
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4377
4412
|
function rowToTask(row) {
|
|
4378
4413
|
const task = {
|
|
4379
4414
|
uuid: row.id,
|
|
@@ -4449,6 +4484,7 @@ function rowToReview(row) {
|
|
|
4449
4484
|
};
|
|
4450
4485
|
if (row.handoff_revision != null) review.handoffRevision = row.handoff_revision;
|
|
4451
4486
|
if (row.build_commit_sha != null) review.buildCommitSha = row.build_commit_sha;
|
|
4487
|
+
if (row.auto_review != null) review.autoReview = row.auto_review;
|
|
4452
4488
|
return review;
|
|
4453
4489
|
}
|
|
4454
4490
|
function rowToActiveDecision(row) {
|
|
@@ -4644,12 +4680,13 @@ function normaliseComplexity(value) {
|
|
|
4644
4680
|
function normaliseEffort(value) {
|
|
4645
4681
|
return EFFORT_MAP[value] ?? value;
|
|
4646
4682
|
}
|
|
4647
|
-
var PgAdapter, rowToCycle, COMPLEXITY_MAP, EFFORT_MAP, PgPapiAdapter;
|
|
4683
|
+
var PgAdapter, BASE_SCHEMA_SQL, rowToCycle, COMPLEXITY_MAP, EFFORT_MAP, PgPapiAdapter;
|
|
4648
4684
|
var init_dist3 = __esm({
|
|
4649
4685
|
"../adapter-pg/dist/index.js"() {
|
|
4650
4686
|
"use strict";
|
|
4651
4687
|
init_src();
|
|
4652
4688
|
init_src();
|
|
4689
|
+
init_src();
|
|
4653
4690
|
init_dist2();
|
|
4654
4691
|
PgAdapter = class {
|
|
4655
4692
|
sql;
|
|
@@ -5169,6 +5206,736 @@ var init_dist3 = __esm({
|
|
|
5169
5206
|
await this.sql.end();
|
|
5170
5207
|
}
|
|
5171
5208
|
};
|
|
5209
|
+
BASE_SCHEMA_SQL = `
|
|
5210
|
+
-- =============================================================================
|
|
5211
|
+
-- Core tables (no foreign key dependencies)
|
|
5212
|
+
-- =============================================================================
|
|
5213
|
+
|
|
5214
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
5215
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5216
|
+
slug TEXT NOT NULL,
|
|
5217
|
+
name TEXT NOT NULL,
|
|
5218
|
+
repo_url TEXT,
|
|
5219
|
+
papi_dir TEXT,
|
|
5220
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5221
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5222
|
+
site_url TEXT,
|
|
5223
|
+
PRIMARY KEY (id),
|
|
5224
|
+
UNIQUE (slug)
|
|
5225
|
+
);
|
|
5226
|
+
|
|
5227
|
+
CREATE TABLE IF NOT EXISTS shared_milestones (
|
|
5228
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5229
|
+
title TEXT NOT NULL,
|
|
5230
|
+
description TEXT DEFAULT ''::text NOT NULL,
|
|
5231
|
+
status TEXT DEFAULT 'not_started'::text NOT NULL,
|
|
5232
|
+
target_date DATE,
|
|
5233
|
+
completed_at TIMESTAMPTZ,
|
|
5234
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5235
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5236
|
+
archived_at TIMESTAMPTZ,
|
|
5237
|
+
PRIMARY KEY (id)
|
|
5238
|
+
);
|
|
5239
|
+
|
|
5240
|
+
CREATE TABLE IF NOT EXISTS shared_decisions (
|
|
5241
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5242
|
+
display_id TEXT NOT NULL,
|
|
5243
|
+
title TEXT NOT NULL,
|
|
5244
|
+
decision TEXT NOT NULL,
|
|
5245
|
+
confidence TEXT NOT NULL,
|
|
5246
|
+
status TEXT DEFAULT 'proposed'::text NOT NULL,
|
|
5247
|
+
origin_project_id UUID NOT NULL REFERENCES projects(id),
|
|
5248
|
+
evidence TEXT DEFAULT ''::text NOT NULL,
|
|
5249
|
+
superseded_by_id UUID REFERENCES shared_decisions(id),
|
|
5250
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5251
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5252
|
+
archived_at TIMESTAMPTZ,
|
|
5253
|
+
PRIMARY KEY (id),
|
|
5254
|
+
UNIQUE (display_id)
|
|
5255
|
+
);
|
|
5256
|
+
|
|
5257
|
+
-- =============================================================================
|
|
5258
|
+
-- Project-scoped tables
|
|
5259
|
+
-- =============================================================================
|
|
5260
|
+
|
|
5261
|
+
CREATE TABLE IF NOT EXISTS product_briefs (
|
|
5262
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5263
|
+
content TEXT DEFAULT ''::text NOT NULL,
|
|
5264
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5265
|
+
landscape_references JSONB,
|
|
5266
|
+
user_journeys JSONB,
|
|
5267
|
+
mvp_boundary TEXT,
|
|
5268
|
+
assumptions_open_questions JSONB,
|
|
5269
|
+
success_signals JSONB,
|
|
5270
|
+
PRIMARY KEY (project_id)
|
|
5271
|
+
);
|
|
5272
|
+
|
|
5273
|
+
CREATE TABLE IF NOT EXISTS cycles (
|
|
5274
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5275
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5276
|
+
number INTEGER NOT NULL,
|
|
5277
|
+
status TEXT DEFAULT 'planning'::text NOT NULL,
|
|
5278
|
+
start_date TIMESTAMPTZ NOT NULL,
|
|
5279
|
+
end_date TIMESTAMPTZ,
|
|
5280
|
+
goals TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5281
|
+
board_health TEXT DEFAULT ''::text NOT NULL,
|
|
5282
|
+
task_ids TEXT[] DEFAULT '{}'::uuid[] NOT NULL,
|
|
5283
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5284
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5285
|
+
user_id UUID,
|
|
5286
|
+
context_hashes JSONB,
|
|
5287
|
+
PRIMARY KEY (id),
|
|
5288
|
+
UNIQUE (project_id, number)
|
|
5289
|
+
);
|
|
5290
|
+
|
|
5291
|
+
CREATE TABLE IF NOT EXISTS horizons (
|
|
5292
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5293
|
+
slug TEXT NOT NULL,
|
|
5294
|
+
label TEXT NOT NULL,
|
|
5295
|
+
description TEXT,
|
|
5296
|
+
status TEXT DEFAULT 'active'::text NOT NULL,
|
|
5297
|
+
sort_order INTEGER DEFAULT 0 NOT NULL,
|
|
5298
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5299
|
+
user_id UUID,
|
|
5300
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5301
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5302
|
+
PRIMARY KEY (id),
|
|
5303
|
+
UNIQUE (project_id, slug)
|
|
5304
|
+
);
|
|
5305
|
+
|
|
5306
|
+
CREATE TABLE IF NOT EXISTS stages (
|
|
5307
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5308
|
+
slug TEXT NOT NULL,
|
|
5309
|
+
label TEXT NOT NULL,
|
|
5310
|
+
description TEXT,
|
|
5311
|
+
status TEXT DEFAULT 'active'::text NOT NULL,
|
|
5312
|
+
sort_order INTEGER DEFAULT 0 NOT NULL,
|
|
5313
|
+
horizon_id UUID NOT NULL REFERENCES horizons(id),
|
|
5314
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5315
|
+
user_id UUID,
|
|
5316
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5317
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5318
|
+
PRIMARY KEY (id),
|
|
5319
|
+
UNIQUE (project_id, slug)
|
|
5320
|
+
);
|
|
5321
|
+
|
|
5322
|
+
CREATE TABLE IF NOT EXISTS phases (
|
|
5323
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5324
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5325
|
+
slug TEXT NOT NULL,
|
|
5326
|
+
label TEXT NOT NULL,
|
|
5327
|
+
description TEXT DEFAULT ''::text NOT NULL,
|
|
5328
|
+
status TEXT DEFAULT 'Not Started'::text NOT NULL,
|
|
5329
|
+
sort_order INTEGER DEFAULT 0 NOT NULL,
|
|
5330
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5331
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5332
|
+
user_id UUID,
|
|
5333
|
+
stage_id UUID REFERENCES stages(id),
|
|
5334
|
+
PRIMARY KEY (id),
|
|
5335
|
+
UNIQUE (project_id, slug)
|
|
5336
|
+
);
|
|
5337
|
+
|
|
5338
|
+
CREATE TABLE IF NOT EXISTS cycle_tasks (
|
|
5339
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5340
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5341
|
+
display_id TEXT NOT NULL,
|
|
5342
|
+
title TEXT NOT NULL,
|
|
5343
|
+
status TEXT DEFAULT 'Backlog'::text NOT NULL,
|
|
5344
|
+
priority TEXT DEFAULT 'P3 Low'::text NOT NULL,
|
|
5345
|
+
complexity TEXT DEFAULT 'Medium'::text NOT NULL,
|
|
5346
|
+
module TEXT DEFAULT ''::text NOT NULL,
|
|
5347
|
+
epic TEXT DEFAULT ''::text NOT NULL,
|
|
5348
|
+
phase TEXT DEFAULT ''::text NOT NULL,
|
|
5349
|
+
owner TEXT DEFAULT 'TBD'::text NOT NULL,
|
|
5350
|
+
reviewed BOOLEAN DEFAULT false NOT NULL,
|
|
5351
|
+
sprint INTEGER,
|
|
5352
|
+
created_sprint INTEGER,
|
|
5353
|
+
why TEXT,
|
|
5354
|
+
depends_on TEXT,
|
|
5355
|
+
notes TEXT,
|
|
5356
|
+
closure_reason TEXT,
|
|
5357
|
+
state_history JSONB DEFAULT '[]'::jsonb,
|
|
5358
|
+
build_handoff JSONB,
|
|
5359
|
+
build_report TEXT,
|
|
5360
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5361
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5362
|
+
user_id UUID,
|
|
5363
|
+
task_type TEXT,
|
|
5364
|
+
maturity TEXT,
|
|
5365
|
+
cycle_number INTEGER,
|
|
5366
|
+
created_cycle INTEGER,
|
|
5367
|
+
stage_id UUID REFERENCES stages(id),
|
|
5368
|
+
PRIMARY KEY (id),
|
|
5369
|
+
UNIQUE (project_id, display_id)
|
|
5370
|
+
);
|
|
5371
|
+
|
|
5372
|
+
CREATE TABLE IF NOT EXISTS active_decisions (
|
|
5373
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5374
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5375
|
+
display_id TEXT NOT NULL,
|
|
5376
|
+
title TEXT NOT NULL,
|
|
5377
|
+
confidence TEXT DEFAULT 'MEDIUM'::text NOT NULL,
|
|
5378
|
+
superseded BOOLEAN DEFAULT false NOT NULL,
|
|
5379
|
+
superseded_by TEXT,
|
|
5380
|
+
created_sprint INTEGER,
|
|
5381
|
+
modified_sprint INTEGER,
|
|
5382
|
+
body TEXT DEFAULT ''::text NOT NULL,
|
|
5383
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5384
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5385
|
+
outcome TEXT DEFAULT 'pending'::text NOT NULL,
|
|
5386
|
+
revision_count INTEGER DEFAULT 0 NOT NULL,
|
|
5387
|
+
last_referenced_sprint INTEGER,
|
|
5388
|
+
user_id UUID,
|
|
5389
|
+
created_cycle INTEGER,
|
|
5390
|
+
modified_cycle INTEGER,
|
|
5391
|
+
last_referenced_cycle INTEGER,
|
|
5392
|
+
PRIMARY KEY (id),
|
|
5393
|
+
UNIQUE (project_id, display_id)
|
|
5394
|
+
);
|
|
5395
|
+
|
|
5396
|
+
CREATE TABLE IF NOT EXISTS north_stars (
|
|
5397
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5398
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5399
|
+
statement TEXT NOT NULL,
|
|
5400
|
+
set_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5401
|
+
superseded_by_id UUID REFERENCES north_stars(id),
|
|
5402
|
+
superseded_at TIMESTAMPTZ,
|
|
5403
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5404
|
+
user_id UUID,
|
|
5405
|
+
PRIMARY KEY (id)
|
|
5406
|
+
);
|
|
5407
|
+
|
|
5408
|
+
CREATE TABLE IF NOT EXISTS planning_log_entries (
|
|
5409
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5410
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5411
|
+
sprint_number INTEGER NOT NULL,
|
|
5412
|
+
title TEXT NOT NULL,
|
|
5413
|
+
content TEXT DEFAULT ''::text NOT NULL,
|
|
5414
|
+
carry_forward TEXT,
|
|
5415
|
+
notes TEXT,
|
|
5416
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5417
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5418
|
+
user_id UUID,
|
|
5419
|
+
cycle_number INTEGER,
|
|
5420
|
+
PRIMARY KEY (id),
|
|
5421
|
+
UNIQUE (project_id, sprint_number)
|
|
5422
|
+
);
|
|
5423
|
+
|
|
5424
|
+
CREATE TABLE IF NOT EXISTS build_handoffs (
|
|
5425
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5426
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5427
|
+
display_id TEXT NOT NULL,
|
|
5428
|
+
task_id TEXT NOT NULL,
|
|
5429
|
+
task_title TEXT NOT NULL,
|
|
5430
|
+
sprint INTEGER NOT NULL,
|
|
5431
|
+
why_now TEXT DEFAULT ''::text NOT NULL,
|
|
5432
|
+
scope TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5433
|
+
scope_boundary TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5434
|
+
acceptance_criteria TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5435
|
+
security_considerations TEXT DEFAULT ''::text NOT NULL,
|
|
5436
|
+
files_likely_touched TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5437
|
+
effort TEXT DEFAULT 'M'::text NOT NULL,
|
|
5438
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5439
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5440
|
+
user_id UUID,
|
|
5441
|
+
PRIMARY KEY (id),
|
|
5442
|
+
UNIQUE (project_id, display_id)
|
|
5443
|
+
);
|
|
5444
|
+
|
|
5445
|
+
CREATE TABLE IF NOT EXISTS build_reports (
|
|
5446
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5447
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5448
|
+
display_id TEXT NOT NULL,
|
|
5449
|
+
task_id TEXT NOT NULL,
|
|
5450
|
+
task_name TEXT NOT NULL,
|
|
5451
|
+
date TEXT NOT NULL,
|
|
5452
|
+
sprint INTEGER NOT NULL,
|
|
5453
|
+
completed TEXT DEFAULT 'No'::text NOT NULL,
|
|
5454
|
+
actual_effort TEXT NOT NULL,
|
|
5455
|
+
estimated_effort TEXT NOT NULL,
|
|
5456
|
+
scope_accuracy TEXT DEFAULT 'accurate'::text NOT NULL,
|
|
5457
|
+
surprises TEXT DEFAULT ''::text NOT NULL,
|
|
5458
|
+
discovered_issues TEXT DEFAULT ''::text NOT NULL,
|
|
5459
|
+
architecture_notes TEXT DEFAULT ''::text NOT NULL,
|
|
5460
|
+
commit_sha TEXT,
|
|
5461
|
+
files_changed TEXT[] DEFAULT '{}'::text[],
|
|
5462
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5463
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5464
|
+
related_decisions TEXT[] DEFAULT '{}'::text[],
|
|
5465
|
+
user_id UUID,
|
|
5466
|
+
handoff_accuracy JSONB,
|
|
5467
|
+
corrections_count INTEGER DEFAULT 0 NOT NULL,
|
|
5468
|
+
brief_implications JSONB,
|
|
5469
|
+
cycle_number INTEGER,
|
|
5470
|
+
dead_ends TEXT,
|
|
5471
|
+
PRIMARY KEY (id),
|
|
5472
|
+
UNIQUE (project_id, display_id)
|
|
5473
|
+
);
|
|
5474
|
+
|
|
5475
|
+
CREATE TABLE IF NOT EXISTS reviews (
|
|
5476
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5477
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5478
|
+
display_id TEXT NOT NULL,
|
|
5479
|
+
task_id TEXT NOT NULL,
|
|
5480
|
+
stage TEXT NOT NULL,
|
|
5481
|
+
reviewer TEXT DEFAULT ''::text NOT NULL,
|
|
5482
|
+
verdict TEXT NOT NULL,
|
|
5483
|
+
sprint INTEGER NOT NULL,
|
|
5484
|
+
date TEXT NOT NULL,
|
|
5485
|
+
comments TEXT DEFAULT ''::text NOT NULL,
|
|
5486
|
+
handoff_revision INTEGER,
|
|
5487
|
+
build_commit_sha TEXT,
|
|
5488
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5489
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5490
|
+
user_id UUID,
|
|
5491
|
+
cycle_number INTEGER,
|
|
5492
|
+
auto_review JSONB,
|
|
5493
|
+
PRIMARY KEY (id),
|
|
5494
|
+
UNIQUE (project_id, display_id)
|
|
5495
|
+
);
|
|
5496
|
+
|
|
5497
|
+
CREATE TABLE IF NOT EXISTS registries (
|
|
5498
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5499
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5500
|
+
modules TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5501
|
+
epics TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5502
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5503
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5504
|
+
PRIMARY KEY (id),
|
|
5505
|
+
UNIQUE (project_id)
|
|
5506
|
+
);
|
|
5507
|
+
|
|
5508
|
+
CREATE TABLE IF NOT EXISTS tool_call_metrics (
|
|
5509
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5510
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5511
|
+
timestamp TEXT NOT NULL,
|
|
5512
|
+
tool TEXT NOT NULL,
|
|
5513
|
+
duration_ms INTEGER NOT NULL,
|
|
5514
|
+
input_tokens INTEGER,
|
|
5515
|
+
output_tokens INTEGER,
|
|
5516
|
+
estimated_cost_usd NUMERIC,
|
|
5517
|
+
model TEXT,
|
|
5518
|
+
sprint_number INTEGER,
|
|
5519
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5520
|
+
user_id UUID,
|
|
5521
|
+
context_bytes INTEGER,
|
|
5522
|
+
context_utilisation NUMERIC,
|
|
5523
|
+
cycle_number INTEGER,
|
|
5524
|
+
PRIMARY KEY (id)
|
|
5525
|
+
);
|
|
5526
|
+
|
|
5527
|
+
CREATE TABLE IF NOT EXISTS cost_snapshots (
|
|
5528
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5529
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5530
|
+
sprint INTEGER NOT NULL,
|
|
5531
|
+
date TEXT NOT NULL,
|
|
5532
|
+
total_cost_usd NUMERIC DEFAULT 0 NOT NULL,
|
|
5533
|
+
total_input_tokens BIGINT DEFAULT 0 NOT NULL,
|
|
5534
|
+
total_output_tokens BIGINT DEFAULT 0 NOT NULL,
|
|
5535
|
+
total_calls INTEGER DEFAULT 0 NOT NULL,
|
|
5536
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5537
|
+
user_id UUID,
|
|
5538
|
+
cycle_number INTEGER,
|
|
5539
|
+
PRIMARY KEY (id),
|
|
5540
|
+
UNIQUE (project_id, sprint)
|
|
5541
|
+
);
|
|
5542
|
+
|
|
5543
|
+
CREATE TABLE IF NOT EXISTS cycle_metrics_snapshots (
|
|
5544
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5545
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5546
|
+
sprint INTEGER NOT NULL,
|
|
5547
|
+
date TEXT NOT NULL,
|
|
5548
|
+
accuracy JSONB DEFAULT '[]'::jsonb NOT NULL,
|
|
5549
|
+
velocity JSONB DEFAULT '[]'::jsonb NOT NULL,
|
|
5550
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5551
|
+
user_id UUID,
|
|
5552
|
+
cycle_number INTEGER,
|
|
5553
|
+
PRIMARY KEY (id),
|
|
5554
|
+
UNIQUE (project_id, sprint)
|
|
5555
|
+
);
|
|
5556
|
+
|
|
5557
|
+
CREATE TABLE IF NOT EXISTS strategy_reviews (
|
|
5558
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5559
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5560
|
+
sprint_number INTEGER NOT NULL,
|
|
5561
|
+
title TEXT NOT NULL,
|
|
5562
|
+
content TEXT NOT NULL,
|
|
5563
|
+
notes TEXT,
|
|
5564
|
+
board_health TEXT,
|
|
5565
|
+
strategic_direction TEXT,
|
|
5566
|
+
recommendations JSONB,
|
|
5567
|
+
user_id UUID,
|
|
5568
|
+
created_at TIMESTAMPTZ DEFAULT now(),
|
|
5569
|
+
sprint_range TEXT,
|
|
5570
|
+
full_analysis TEXT,
|
|
5571
|
+
velocity_assessment TEXT,
|
|
5572
|
+
structured_data JSONB,
|
|
5573
|
+
cycle_number INTEGER,
|
|
5574
|
+
cycle_range TEXT,
|
|
5575
|
+
PRIMARY KEY (id)
|
|
5576
|
+
);
|
|
5577
|
+
|
|
5578
|
+
CREATE TABLE IF NOT EXISTS strategy_recommendations (
|
|
5579
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5580
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5581
|
+
type TEXT NOT NULL,
|
|
5582
|
+
status TEXT DEFAULT 'pending'::text NOT NULL,
|
|
5583
|
+
content TEXT DEFAULT ''::text NOT NULL,
|
|
5584
|
+
created_sprint INTEGER NOT NULL,
|
|
5585
|
+
actioned_sprint INTEGER,
|
|
5586
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5587
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5588
|
+
dismissal_reason TEXT,
|
|
5589
|
+
created_cycle INTEGER,
|
|
5590
|
+
actioned_cycle INTEGER,
|
|
5591
|
+
target TEXT,
|
|
5592
|
+
PRIMARY KEY (id)
|
|
5593
|
+
);
|
|
5594
|
+
|
|
5595
|
+
CREATE TABLE IF NOT EXISTS decision_events (
|
|
5596
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5597
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5598
|
+
decision_id TEXT NOT NULL,
|
|
5599
|
+
event_type TEXT NOT NULL,
|
|
5600
|
+
sprint INTEGER NOT NULL,
|
|
5601
|
+
source TEXT NOT NULL,
|
|
5602
|
+
source_ref TEXT,
|
|
5603
|
+
detail TEXT,
|
|
5604
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5605
|
+
cycle_number INTEGER,
|
|
5606
|
+
PRIMARY KEY (id)
|
|
5607
|
+
);
|
|
5608
|
+
|
|
5609
|
+
CREATE TABLE IF NOT EXISTS decision_scores (
|
|
5610
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5611
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5612
|
+
decision_id TEXT NOT NULL,
|
|
5613
|
+
sprint INTEGER NOT NULL,
|
|
5614
|
+
effort SMALLINT NOT NULL,
|
|
5615
|
+
risk SMALLINT NOT NULL,
|
|
5616
|
+
reversibility SMALLINT NOT NULL,
|
|
5617
|
+
scale_cost SMALLINT NOT NULL,
|
|
5618
|
+
lock_in SMALLINT NOT NULL,
|
|
5619
|
+
total_score SMALLINT,
|
|
5620
|
+
rationale TEXT,
|
|
5621
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5622
|
+
cycle_number INTEGER,
|
|
5623
|
+
PRIMARY KEY (id),
|
|
5624
|
+
UNIQUE (project_id, decision_id, sprint)
|
|
5625
|
+
);
|
|
5626
|
+
|
|
5627
|
+
CREATE TABLE IF NOT EXISTS entity_references (
|
|
5628
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5629
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5630
|
+
user_id UUID,
|
|
5631
|
+
entity_type TEXT DEFAULT 'active_decision'::text NOT NULL,
|
|
5632
|
+
entity_id TEXT NOT NULL,
|
|
5633
|
+
reference_context TEXT NOT NULL,
|
|
5634
|
+
tool_name TEXT NOT NULL,
|
|
5635
|
+
sprint_number INTEGER NOT NULL,
|
|
5636
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5637
|
+
cycle_number INTEGER,
|
|
5638
|
+
PRIMARY KEY (id)
|
|
5639
|
+
);
|
|
5640
|
+
|
|
5641
|
+
CREATE TABLE IF NOT EXISTS dogfood_log (
|
|
5642
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5643
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5644
|
+
cycle_number INTEGER NOT NULL,
|
|
5645
|
+
category TEXT NOT NULL,
|
|
5646
|
+
content TEXT NOT NULL,
|
|
5647
|
+
source_tool TEXT DEFAULT 'strategy_review'::text NOT NULL,
|
|
5648
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5649
|
+
status TEXT DEFAULT 'observed'::text NOT NULL,
|
|
5650
|
+
linked_task_id TEXT,
|
|
5651
|
+
PRIMARY KEY (id)
|
|
5652
|
+
);
|
|
5653
|
+
|
|
5654
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
5655
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5656
|
+
project_id UUID NOT NULL,
|
|
5657
|
+
task_id TEXT NOT NULL,
|
|
5658
|
+
author TEXT DEFAULT 'human'::text NOT NULL,
|
|
5659
|
+
content TEXT NOT NULL,
|
|
5660
|
+
user_id UUID,
|
|
5661
|
+
created_at TIMESTAMPTZ DEFAULT now(),
|
|
5662
|
+
PRIMARY KEY (id)
|
|
5663
|
+
);
|
|
5664
|
+
|
|
5665
|
+
CREATE TABLE IF NOT EXISTS task_transitions (
|
|
5666
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5667
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5668
|
+
task_id VARCHAR(50) NOT NULL,
|
|
5669
|
+
from_status VARCHAR(50) NOT NULL,
|
|
5670
|
+
to_status VARCHAR(50) NOT NULL,
|
|
5671
|
+
changed_by VARCHAR(100) DEFAULT 'system'::character varying NOT NULL,
|
|
5672
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5673
|
+
PRIMARY KEY (id)
|
|
5674
|
+
);
|
|
5675
|
+
|
|
5676
|
+
-- =============================================================================
|
|
5677
|
+
-- Cross-project tables
|
|
5678
|
+
-- =============================================================================
|
|
5679
|
+
|
|
5680
|
+
CREATE TABLE IF NOT EXISTS acknowledgements (
|
|
5681
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5682
|
+
decision_id UUID NOT NULL REFERENCES shared_decisions(id),
|
|
5683
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5684
|
+
status TEXT DEFAULT 'pending'::text NOT NULL,
|
|
5685
|
+
comments TEXT,
|
|
5686
|
+
responded_at TIMESTAMPTZ,
|
|
5687
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5688
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5689
|
+
PRIMARY KEY (id),
|
|
5690
|
+
UNIQUE (decision_id, project_id)
|
|
5691
|
+
);
|
|
5692
|
+
|
|
5693
|
+
CREATE TABLE IF NOT EXISTS milestone_dependencies (
|
|
5694
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5695
|
+
milestone_id UUID NOT NULL REFERENCES shared_milestones(id),
|
|
5696
|
+
depends_on_id UUID NOT NULL REFERENCES shared_milestones(id),
|
|
5697
|
+
PRIMARY KEY (id),
|
|
5698
|
+
UNIQUE (milestone_id, depends_on_id)
|
|
5699
|
+
);
|
|
5700
|
+
|
|
5701
|
+
CREATE TABLE IF NOT EXISTS project_contributions (
|
|
5702
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5703
|
+
milestone_id UUID NOT NULL REFERENCES shared_milestones(id),
|
|
5704
|
+
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5705
|
+
status TEXT DEFAULT 'blocked'::text NOT NULL,
|
|
5706
|
+
required_phases TEXT[] DEFAULT '{}'::text[] NOT NULL,
|
|
5707
|
+
notes TEXT,
|
|
5708
|
+
delivered_at TIMESTAMPTZ,
|
|
5709
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5710
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5711
|
+
PRIMARY KEY (id),
|
|
5712
|
+
UNIQUE (milestone_id, project_id)
|
|
5713
|
+
);
|
|
5714
|
+
|
|
5715
|
+
CREATE TABLE IF NOT EXISTS conflict_alerts (
|
|
5716
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5717
|
+
conflict_type TEXT NOT NULL,
|
|
5718
|
+
title TEXT NOT NULL,
|
|
5719
|
+
description TEXT NOT NULL,
|
|
5720
|
+
status TEXT DEFAULT 'open'::text NOT NULL,
|
|
5721
|
+
decision_a_id UUID REFERENCES shared_decisions(id),
|
|
5722
|
+
decision_b_id UUID REFERENCES shared_decisions(id),
|
|
5723
|
+
north_star_id UUID REFERENCES north_stars(id),
|
|
5724
|
+
resolution_decision_id UUID REFERENCES shared_decisions(id),
|
|
5725
|
+
raised_by TEXT NOT NULL,
|
|
5726
|
+
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5727
|
+
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5728
|
+
resolved_at TIMESTAMPTZ,
|
|
5729
|
+
PRIMARY KEY (id)
|
|
5730
|
+
);
|
|
5731
|
+
|
|
5732
|
+
-- =============================================================================
|
|
5733
|
+
-- Indexes
|
|
5734
|
+
-- =============================================================================
|
|
5735
|
+
|
|
5736
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_project ON cycles(project_id);
|
|
5737
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_status ON cycles(status);
|
|
5738
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_project ON cycle_tasks(project_id);
|
|
5739
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_status ON cycle_tasks(status);
|
|
5740
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_sprint ON cycle_tasks(sprint);
|
|
5741
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_priority ON cycle_tasks(priority);
|
|
5742
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_phase ON cycle_tasks(phase);
|
|
5743
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_tasks_user_id ON cycle_tasks(user_id);
|
|
5744
|
+
CREATE INDEX IF NOT EXISTS idx_phases_project ON phases(project_id);
|
|
5745
|
+
CREATE INDEX IF NOT EXISTS idx_phases_stage_id ON phases(stage_id);
|
|
5746
|
+
CREATE INDEX IF NOT EXISTS idx_horizons_project_id ON horizons(project_id);
|
|
5747
|
+
CREATE INDEX IF NOT EXISTS idx_stages_project_id ON stages(project_id);
|
|
5748
|
+
CREATE INDEX IF NOT EXISTS idx_stages_horizon_id ON stages(horizon_id);
|
|
5749
|
+
CREATE INDEX IF NOT EXISTS idx_active_decisions_project ON active_decisions(project_id);
|
|
5750
|
+
CREATE INDEX IF NOT EXISTS idx_active_decisions_confidence ON active_decisions(confidence);
|
|
5751
|
+
CREATE INDEX IF NOT EXISTS idx_active_decisions_user_id ON active_decisions(user_id);
|
|
5752
|
+
CREATE INDEX IF NOT EXISTS idx_north_stars_project ON north_stars(project_id);
|
|
5753
|
+
CREATE INDEX IF NOT EXISTS idx_north_stars_active ON north_stars(project_id) WHERE superseded_by_id IS NULL;
|
|
5754
|
+
CREATE INDEX IF NOT EXISTS idx_planning_log_entries_project ON planning_log_entries(project_id);
|
|
5755
|
+
CREATE INDEX IF NOT EXISTS idx_planning_log_entries_sprint ON planning_log_entries(sprint_number);
|
|
5756
|
+
CREATE INDEX IF NOT EXISTS idx_build_handoffs_project ON build_handoffs(project_id);
|
|
5757
|
+
CREATE INDEX IF NOT EXISTS idx_build_handoffs_sprint ON build_handoffs(sprint);
|
|
5758
|
+
CREATE INDEX IF NOT EXISTS idx_build_handoffs_task_id ON build_handoffs(task_id);
|
|
5759
|
+
CREATE INDEX IF NOT EXISTS idx_build_reports_project ON build_reports(project_id);
|
|
5760
|
+
CREATE INDEX IF NOT EXISTS idx_build_reports_sprint ON build_reports(sprint);
|
|
5761
|
+
CREATE INDEX IF NOT EXISTS idx_build_reports_task_id ON build_reports(task_id);
|
|
5762
|
+
CREATE INDEX IF NOT EXISTS idx_build_reports_user_id ON build_reports(user_id);
|
|
5763
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_project ON reviews(project_id);
|
|
5764
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_sprint ON reviews(sprint);
|
|
5765
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_task_id ON reviews(task_id);
|
|
5766
|
+
CREATE INDEX IF NOT EXISTS idx_registries_project ON registries(project_id);
|
|
5767
|
+
CREATE INDEX IF NOT EXISTS idx_tool_call_metrics_project ON tool_call_metrics(project_id);
|
|
5768
|
+
CREATE INDEX IF NOT EXISTS idx_tool_call_metrics_sprint ON tool_call_metrics(sprint_number);
|
|
5769
|
+
CREATE INDEX IF NOT EXISTS idx_tool_call_metrics_tool ON tool_call_metrics(tool);
|
|
5770
|
+
CREATE INDEX IF NOT EXISTS idx_tool_call_metrics_user_id ON tool_call_metrics(user_id);
|
|
5771
|
+
CREATE INDEX IF NOT EXISTS idx_cost_snapshots_project ON cost_snapshots(project_id);
|
|
5772
|
+
CREATE INDEX IF NOT EXISTS idx_cost_snapshots_user_id ON cost_snapshots(user_id);
|
|
5773
|
+
CREATE INDEX IF NOT EXISTS idx_cycle_metrics_snapshots_project ON cycle_metrics_snapshots(project_id);
|
|
5774
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_strategy_reviews_project_sprint ON strategy_reviews(project_id, sprint_number);
|
|
5775
|
+
CREATE INDEX IF NOT EXISTS idx_strategy_reviews_user_id ON strategy_reviews(user_id);
|
|
5776
|
+
CREATE INDEX IF NOT EXISTS idx_strategy_recommendations_sprint ON strategy_recommendations(project_id, created_sprint);
|
|
5777
|
+
CREATE INDEX IF NOT EXISTS idx_strategy_recommendations_project_status ON strategy_recommendations(project_id, status);
|
|
5778
|
+
CREATE INDEX IF NOT EXISTS idx_decision_events_project_decision ON decision_events(project_id, decision_id);
|
|
5779
|
+
CREATE INDEX IF NOT EXISTS idx_decision_events_project_sprint ON decision_events(project_id, sprint);
|
|
5780
|
+
CREATE INDEX IF NOT EXISTS idx_decision_scores_project_decision ON decision_scores(project_id, decision_id);
|
|
5781
|
+
CREATE INDEX IF NOT EXISTS idx_entity_references_project ON entity_references(project_id);
|
|
5782
|
+
CREATE INDEX IF NOT EXISTS idx_entity_references_entity ON entity_references(entity_id, entity_type);
|
|
5783
|
+
CREATE INDEX IF NOT EXISTS idx_entity_references_sprint ON entity_references(sprint_number);
|
|
5784
|
+
CREATE INDEX IF NOT EXISTS idx_dogfood_log_project ON dogfood_log(project_id);
|
|
5785
|
+
CREATE INDEX IF NOT EXISTS idx_dogfood_log_cycle ON dogfood_log(cycle_number);
|
|
5786
|
+
CREATE INDEX IF NOT EXISTS idx_dogfood_log_status ON dogfood_log(project_id, status) WHERE status = 'observed';
|
|
5787
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_project_task ON task_comments(project_id, task_id);
|
|
5788
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_user_id ON task_comments(user_id);
|
|
5789
|
+
CREATE INDEX IF NOT EXISTS idx_task_transitions_project_task ON task_transitions(project_id, task_id);
|
|
5790
|
+
CREATE INDEX IF NOT EXISTS idx_task_transitions_created ON task_transitions(project_id, created_at DESC);
|
|
5791
|
+
CREATE INDEX IF NOT EXISTS idx_acknowledgements_decision ON acknowledgements(decision_id);
|
|
5792
|
+
CREATE INDEX IF NOT EXISTS idx_acknowledgements_project ON acknowledgements(project_id);
|
|
5793
|
+
CREATE INDEX IF NOT EXISTS idx_shared_decisions_origin_project ON shared_decisions(origin_project_id);
|
|
5794
|
+
CREATE INDEX IF NOT EXISTS idx_shared_decisions_status ON shared_decisions(status);
|
|
5795
|
+
CREATE INDEX IF NOT EXISTS idx_shared_decisions_superseded_by ON shared_decisions(superseded_by_id);
|
|
5796
|
+
CREATE INDEX IF NOT EXISTS idx_shared_milestones_status ON shared_milestones(status);
|
|
5797
|
+
CREATE INDEX IF NOT EXISTS idx_milestone_deps_milestone ON milestone_dependencies(milestone_id);
|
|
5798
|
+
CREATE INDEX IF NOT EXISTS idx_milestone_deps_depends_on ON milestone_dependencies(depends_on_id);
|
|
5799
|
+
CREATE INDEX IF NOT EXISTS idx_project_contributions_project ON project_contributions(project_id);
|
|
5800
|
+
CREATE INDEX IF NOT EXISTS idx_project_contributions_milestone ON project_contributions(milestone_id);
|
|
5801
|
+
CREATE INDEX IF NOT EXISTS idx_project_contributions_status ON project_contributions(status);
|
|
5802
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_decision_a ON conflict_alerts(decision_a_id);
|
|
5803
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_decision_b ON conflict_alerts(decision_b_id);
|
|
5804
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_north_star ON conflict_alerts(north_star_id);
|
|
5805
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_resolution ON conflict_alerts(resolution_decision_id);
|
|
5806
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_status ON conflict_alerts(status);
|
|
5807
|
+
CREATE INDEX IF NOT EXISTS idx_conflict_alerts_type ON conflict_alerts(conflict_type);
|
|
5808
|
+
|
|
5809
|
+
-- =============================================================================
|
|
5810
|
+
-- Row Level Security \u2014 permissive for alpha (service role bypasses RLS)
|
|
5811
|
+
-- =============================================================================
|
|
5812
|
+
|
|
5813
|
+
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
|
|
5814
|
+
ALTER TABLE cycles ENABLE ROW LEVEL SECURITY;
|
|
5815
|
+
ALTER TABLE cycle_tasks ENABLE ROW LEVEL SECURITY;
|
|
5816
|
+
ALTER TABLE phases ENABLE ROW LEVEL SECURITY;
|
|
5817
|
+
ALTER TABLE horizons ENABLE ROW LEVEL SECURITY;
|
|
5818
|
+
ALTER TABLE stages ENABLE ROW LEVEL SECURITY;
|
|
5819
|
+
ALTER TABLE active_decisions ENABLE ROW LEVEL SECURITY;
|
|
5820
|
+
ALTER TABLE north_stars ENABLE ROW LEVEL SECURITY;
|
|
5821
|
+
ALTER TABLE planning_log_entries ENABLE ROW LEVEL SECURITY;
|
|
5822
|
+
ALTER TABLE build_handoffs ENABLE ROW LEVEL SECURITY;
|
|
5823
|
+
ALTER TABLE build_reports ENABLE ROW LEVEL SECURITY;
|
|
5824
|
+
ALTER TABLE reviews ENABLE ROW LEVEL SECURITY;
|
|
5825
|
+
ALTER TABLE registries ENABLE ROW LEVEL SECURITY;
|
|
5826
|
+
ALTER TABLE tool_call_metrics ENABLE ROW LEVEL SECURITY;
|
|
5827
|
+
ALTER TABLE cost_snapshots ENABLE ROW LEVEL SECURITY;
|
|
5828
|
+
ALTER TABLE cycle_metrics_snapshots ENABLE ROW LEVEL SECURITY;
|
|
5829
|
+
ALTER TABLE strategy_reviews ENABLE ROW LEVEL SECURITY;
|
|
5830
|
+
ALTER TABLE strategy_recommendations ENABLE ROW LEVEL SECURITY;
|
|
5831
|
+
ALTER TABLE decision_events ENABLE ROW LEVEL SECURITY;
|
|
5832
|
+
ALTER TABLE decision_scores ENABLE ROW LEVEL SECURITY;
|
|
5833
|
+
ALTER TABLE entity_references ENABLE ROW LEVEL SECURITY;
|
|
5834
|
+
ALTER TABLE dogfood_log ENABLE ROW LEVEL SECURITY;
|
|
5835
|
+
ALTER TABLE task_comments ENABLE ROW LEVEL SECURITY;
|
|
5836
|
+
ALTER TABLE task_transitions ENABLE ROW LEVEL SECURITY;
|
|
5837
|
+
ALTER TABLE product_briefs ENABLE ROW LEVEL SECURITY;
|
|
5838
|
+
ALTER TABLE acknowledgements ENABLE ROW LEVEL SECURITY;
|
|
5839
|
+
ALTER TABLE shared_decisions ENABLE ROW LEVEL SECURITY;
|
|
5840
|
+
ALTER TABLE shared_milestones ENABLE ROW LEVEL SECURITY;
|
|
5841
|
+
ALTER TABLE milestone_dependencies ENABLE ROW LEVEL SECURITY;
|
|
5842
|
+
ALTER TABLE project_contributions ENABLE ROW LEVEL SECURITY;
|
|
5843
|
+
ALTER TABLE conflict_alerts ENABLE ROW LEVEL SECURITY;
|
|
5844
|
+
|
|
5845
|
+
DO $$ BEGIN
|
|
5846
|
+
CREATE POLICY "allow_all_projects" ON projects FOR ALL USING (true) WITH CHECK (true);
|
|
5847
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5848
|
+
DO $$ BEGIN
|
|
5849
|
+
CREATE POLICY "allow_all_cycles" ON cycles FOR ALL USING (true) WITH CHECK (true);
|
|
5850
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5851
|
+
DO $$ BEGIN
|
|
5852
|
+
CREATE POLICY "allow_all_cycle_tasks" ON cycle_tasks FOR ALL USING (true) WITH CHECK (true);
|
|
5853
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5854
|
+
DO $$ BEGIN
|
|
5855
|
+
CREATE POLICY "allow_all_phases" ON phases FOR ALL USING (true) WITH CHECK (true);
|
|
5856
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5857
|
+
DO $$ BEGIN
|
|
5858
|
+
CREATE POLICY "allow_all_horizons" ON horizons FOR ALL USING (true) WITH CHECK (true);
|
|
5859
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5860
|
+
DO $$ BEGIN
|
|
5861
|
+
CREATE POLICY "allow_all_stages" ON stages FOR ALL USING (true) WITH CHECK (true);
|
|
5862
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5863
|
+
DO $$ BEGIN
|
|
5864
|
+
CREATE POLICY "allow_all_active_decisions" ON active_decisions FOR ALL USING (true) WITH CHECK (true);
|
|
5865
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5866
|
+
DO $$ BEGIN
|
|
5867
|
+
CREATE POLICY "allow_all_north_stars" ON north_stars FOR ALL USING (true) WITH CHECK (true);
|
|
5868
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5869
|
+
DO $$ BEGIN
|
|
5870
|
+
CREATE POLICY "allow_all_planning_log_entries" ON planning_log_entries FOR ALL USING (true) WITH CHECK (true);
|
|
5871
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5872
|
+
DO $$ BEGIN
|
|
5873
|
+
CREATE POLICY "allow_all_build_handoffs" ON build_handoffs FOR ALL USING (true) WITH CHECK (true);
|
|
5874
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5875
|
+
DO $$ BEGIN
|
|
5876
|
+
CREATE POLICY "allow_all_build_reports" ON build_reports FOR ALL USING (true) WITH CHECK (true);
|
|
5877
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5878
|
+
DO $$ BEGIN
|
|
5879
|
+
CREATE POLICY "allow_all_reviews" ON reviews FOR ALL USING (true) WITH CHECK (true);
|
|
5880
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5881
|
+
DO $$ BEGIN
|
|
5882
|
+
CREATE POLICY "allow_all_registries" ON registries FOR ALL USING (true) WITH CHECK (true);
|
|
5883
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5884
|
+
DO $$ BEGIN
|
|
5885
|
+
CREATE POLICY "allow_all_tool_call_metrics" ON tool_call_metrics FOR ALL USING (true) WITH CHECK (true);
|
|
5886
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5887
|
+
DO $$ BEGIN
|
|
5888
|
+
CREATE POLICY "allow_all_cost_snapshots" ON cost_snapshots FOR ALL USING (true) WITH CHECK (true);
|
|
5889
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5890
|
+
DO $$ BEGIN
|
|
5891
|
+
CREATE POLICY "allow_all_cycle_metrics_snapshots" ON cycle_metrics_snapshots FOR ALL USING (true) WITH CHECK (true);
|
|
5892
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5893
|
+
DO $$ BEGIN
|
|
5894
|
+
CREATE POLICY "allow_all_strategy_reviews" ON strategy_reviews FOR ALL USING (true) WITH CHECK (true);
|
|
5895
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5896
|
+
DO $$ BEGIN
|
|
5897
|
+
CREATE POLICY "allow_all_strategy_recommendations" ON strategy_recommendations FOR ALL USING (true) WITH CHECK (true);
|
|
5898
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5899
|
+
DO $$ BEGIN
|
|
5900
|
+
CREATE POLICY "allow_all_decision_events" ON decision_events FOR ALL USING (true) WITH CHECK (true);
|
|
5901
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5902
|
+
DO $$ BEGIN
|
|
5903
|
+
CREATE POLICY "allow_all_decision_scores" ON decision_scores FOR ALL USING (true) WITH CHECK (true);
|
|
5904
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5905
|
+
DO $$ BEGIN
|
|
5906
|
+
CREATE POLICY "allow_all_entity_references" ON entity_references FOR ALL USING (true) WITH CHECK (true);
|
|
5907
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5908
|
+
DO $$ BEGIN
|
|
5909
|
+
CREATE POLICY "allow_all_dogfood_log" ON dogfood_log FOR ALL USING (true) WITH CHECK (true);
|
|
5910
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5911
|
+
DO $$ BEGIN
|
|
5912
|
+
CREATE POLICY "allow_all_task_comments" ON task_comments FOR ALL USING (true) WITH CHECK (true);
|
|
5913
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5914
|
+
DO $$ BEGIN
|
|
5915
|
+
CREATE POLICY "allow_all_task_transitions" ON task_transitions FOR ALL USING (true) WITH CHECK (true);
|
|
5916
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5917
|
+
DO $$ BEGIN
|
|
5918
|
+
CREATE POLICY "allow_all_product_briefs" ON product_briefs FOR ALL USING (true) WITH CHECK (true);
|
|
5919
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5920
|
+
DO $$ BEGIN
|
|
5921
|
+
CREATE POLICY "allow_all_acknowledgements" ON acknowledgements FOR ALL USING (true) WITH CHECK (true);
|
|
5922
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5923
|
+
DO $$ BEGIN
|
|
5924
|
+
CREATE POLICY "allow_all_shared_decisions" ON shared_decisions FOR ALL USING (true) WITH CHECK (true);
|
|
5925
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5926
|
+
DO $$ BEGIN
|
|
5927
|
+
CREATE POLICY "allow_all_shared_milestones" ON shared_milestones FOR ALL USING (true) WITH CHECK (true);
|
|
5928
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5929
|
+
DO $$ BEGIN
|
|
5930
|
+
CREATE POLICY "allow_all_milestone_dependencies" ON milestone_dependencies FOR ALL USING (true) WITH CHECK (true);
|
|
5931
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5932
|
+
DO $$ BEGIN
|
|
5933
|
+
CREATE POLICY "allow_all_project_contributions" ON project_contributions FOR ALL USING (true) WITH CHECK (true);
|
|
5934
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5935
|
+
DO $$ BEGIN
|
|
5936
|
+
CREATE POLICY "allow_all_conflict_alerts" ON conflict_alerts FOR ALL USING (true) WITH CHECK (true);
|
|
5937
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
5938
|
+
`;
|
|
5172
5939
|
rowToCycle = rowToSprint;
|
|
5173
5940
|
COMPLEXITY_MAP = {
|
|
5174
5941
|
"XS": "Small",
|
|
@@ -5596,6 +6363,12 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
5596
6363
|
modified_sprint = EXCLUDED.modified_sprint,
|
|
5597
6364
|
revision_count = active_decisions.revision_count + 1,
|
|
5598
6365
|
outcome = CASE WHEN active_decisions.outcome = 'pending' THEN 'revised' ELSE active_decisions.outcome END
|
|
6366
|
+
`;
|
|
6367
|
+
}
|
|
6368
|
+
async deleteActiveDecision(id) {
|
|
6369
|
+
await this.sql`
|
|
6370
|
+
DELETE FROM active_decisions
|
|
6371
|
+
WHERE project_id = ${this.projectId} AND display_id = ${id}
|
|
5599
6372
|
`;
|
|
5600
6373
|
}
|
|
5601
6374
|
// -------------------------------------------------------------------------
|
|
@@ -5819,12 +6592,13 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
5819
6592
|
await this.sql`
|
|
5820
6593
|
INSERT INTO reviews (
|
|
5821
6594
|
project_id, display_id, task_id, stage, reviewer, verdict,
|
|
5822
|
-
sprint, date, comments, handoff_revision, build_commit_sha
|
|
6595
|
+
sprint, date, comments, handoff_revision, build_commit_sha, auto_review
|
|
5823
6596
|
) VALUES (
|
|
5824
6597
|
${this.projectId}, ${displayId}, ${review.taskId}, ${review.stage},
|
|
5825
6598
|
${review.reviewer}, ${review.verdict}, ${review.sprint},
|
|
5826
6599
|
${review.date}, ${review.comments},
|
|
5827
|
-
${review.handoffRevision ?? null}, ${review.buildCommitSha ?? null}
|
|
6600
|
+
${review.handoffRevision ?? null}, ${review.buildCommitSha ?? null},
|
|
6601
|
+
${review.autoReview ? JSON.stringify(review.autoReview) : null}
|
|
5828
6602
|
)
|
|
5829
6603
|
`;
|
|
5830
6604
|
}
|
|
@@ -6854,6 +7628,331 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6854
7628
|
}
|
|
6855
7629
|
});
|
|
6856
7630
|
|
|
7631
|
+
// src/proxy-adapter.ts
|
|
7632
|
+
var proxy_adapter_exports = {};
|
|
7633
|
+
__export(proxy_adapter_exports, {
|
|
7634
|
+
ProxyPapiAdapter: () => ProxyPapiAdapter
|
|
7635
|
+
});
|
|
7636
|
+
var ProxyPapiAdapter;
|
|
7637
|
+
var init_proxy_adapter = __esm({
|
|
7638
|
+
"src/proxy-adapter.ts"() {
|
|
7639
|
+
"use strict";
|
|
7640
|
+
ProxyPapiAdapter = class {
|
|
7641
|
+
endpoint;
|
|
7642
|
+
apiKey;
|
|
7643
|
+
projectId;
|
|
7644
|
+
constructor(config2) {
|
|
7645
|
+
this.endpoint = config2.endpoint.replace(/\/$/, "");
|
|
7646
|
+
this.apiKey = config2.apiKey;
|
|
7647
|
+
this.projectId = config2.projectId;
|
|
7648
|
+
}
|
|
7649
|
+
/**
|
|
7650
|
+
* Send an adapter method call to the proxy Edge Function.
|
|
7651
|
+
* Serializes { projectId, method, args } and deserializes the response.
|
|
7652
|
+
*/
|
|
7653
|
+
async invoke(method, args = []) {
|
|
7654
|
+
const url = `${this.endpoint}/invoke`;
|
|
7655
|
+
const response = await fetch(url, {
|
|
7656
|
+
method: "POST",
|
|
7657
|
+
headers: {
|
|
7658
|
+
"Content-Type": "application/json",
|
|
7659
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
7660
|
+
},
|
|
7661
|
+
body: JSON.stringify({
|
|
7662
|
+
projectId: this.projectId,
|
|
7663
|
+
method,
|
|
7664
|
+
args
|
|
7665
|
+
})
|
|
7666
|
+
});
|
|
7667
|
+
if (!response.ok) {
|
|
7668
|
+
const errorBody = await response.text();
|
|
7669
|
+
let message;
|
|
7670
|
+
try {
|
|
7671
|
+
const parsed = JSON.parse(errorBody);
|
|
7672
|
+
message = parsed.error ?? errorBody;
|
|
7673
|
+
} catch {
|
|
7674
|
+
message = errorBody;
|
|
7675
|
+
}
|
|
7676
|
+
throw new Error(`Proxy error (${response.status}) on ${method}: ${message}`);
|
|
7677
|
+
}
|
|
7678
|
+
const body = await response.json();
|
|
7679
|
+
if (!body.ok && body.error) {
|
|
7680
|
+
throw new Error(`Proxy error on ${method}: ${body.error}`);
|
|
7681
|
+
}
|
|
7682
|
+
return body.result;
|
|
7683
|
+
}
|
|
7684
|
+
/** Check if the proxy is reachable. */
|
|
7685
|
+
async probeConnection() {
|
|
7686
|
+
try {
|
|
7687
|
+
const response = await fetch(`${this.endpoint}/health`, {
|
|
7688
|
+
signal: AbortSignal.timeout(5e3)
|
|
7689
|
+
});
|
|
7690
|
+
return response.ok;
|
|
7691
|
+
} catch {
|
|
7692
|
+
return false;
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
// --- Planning & Health ---
|
|
7696
|
+
readPlanningLog() {
|
|
7697
|
+
return this.invoke("readPlanningLog");
|
|
7698
|
+
}
|
|
7699
|
+
getSprintHealth() {
|
|
7700
|
+
return this.invoke("getSprintHealth");
|
|
7701
|
+
}
|
|
7702
|
+
getActiveDecisions() {
|
|
7703
|
+
return this.invoke("getActiveDecisions");
|
|
7704
|
+
}
|
|
7705
|
+
getSprintLog(limit) {
|
|
7706
|
+
return this.invoke("getSprintLog", [limit]);
|
|
7707
|
+
}
|
|
7708
|
+
getSprintLogSince(sprintNumber) {
|
|
7709
|
+
return this.invoke("getSprintLogSince", [sprintNumber]);
|
|
7710
|
+
}
|
|
7711
|
+
setSprintHealth(updates) {
|
|
7712
|
+
return this.invoke("setSprintHealth", [updates]);
|
|
7713
|
+
}
|
|
7714
|
+
writeSprintLogEntry(entry) {
|
|
7715
|
+
return this.invoke("writeSprintLogEntry", [entry]);
|
|
7716
|
+
}
|
|
7717
|
+
updateActiveDecision(id, body, sprintNumber) {
|
|
7718
|
+
return this.invoke("updateActiveDecision", [id, body, sprintNumber]);
|
|
7719
|
+
}
|
|
7720
|
+
upsertActiveDecision(id, body, title, confidence, sprintNumber) {
|
|
7721
|
+
return this.invoke("upsertActiveDecision", [id, body, title, confidence, sprintNumber]);
|
|
7722
|
+
}
|
|
7723
|
+
deleteActiveDecision(id) {
|
|
7724
|
+
return this.invoke("deleteActiveDecision", [id]);
|
|
7725
|
+
}
|
|
7726
|
+
// --- Board / Tasks ---
|
|
7727
|
+
queryBoard(options) {
|
|
7728
|
+
return this.invoke("queryBoard", [options]);
|
|
7729
|
+
}
|
|
7730
|
+
getTask(id) {
|
|
7731
|
+
return this.invoke("getTask", [id]);
|
|
7732
|
+
}
|
|
7733
|
+
getTasks(ids) {
|
|
7734
|
+
return this.invoke("getTasks", [ids]);
|
|
7735
|
+
}
|
|
7736
|
+
createTask(task) {
|
|
7737
|
+
return this.invoke("createTask", [task]);
|
|
7738
|
+
}
|
|
7739
|
+
updateTask(id, updates, options) {
|
|
7740
|
+
return this.invoke("updateTask", [id, updates, options]);
|
|
7741
|
+
}
|
|
7742
|
+
updateTaskStatus(id, status) {
|
|
7743
|
+
return this.invoke("updateTaskStatus", [id, status]);
|
|
7744
|
+
}
|
|
7745
|
+
recordTransition(taskId, fromStatus, toStatus, changedBy) {
|
|
7746
|
+
return this.invoke("recordTransition", [taskId, fromStatus, toStatus, changedBy]);
|
|
7747
|
+
}
|
|
7748
|
+
// --- Build Reports ---
|
|
7749
|
+
appendBuildReport(report) {
|
|
7750
|
+
return this.invoke("appendBuildReport", [report]);
|
|
7751
|
+
}
|
|
7752
|
+
getRecentBuildReports(count) {
|
|
7753
|
+
return this.invoke("getRecentBuildReports", [count]);
|
|
7754
|
+
}
|
|
7755
|
+
getBuildReportsSince(sprintNumber) {
|
|
7756
|
+
return this.invoke("getBuildReportsSince", [sprintNumber]);
|
|
7757
|
+
}
|
|
7758
|
+
// --- Reviews ---
|
|
7759
|
+
getRecentReviews(count) {
|
|
7760
|
+
return this.invoke("getRecentReviews", [count]);
|
|
7761
|
+
}
|
|
7762
|
+
writeReview(review) {
|
|
7763
|
+
return this.invoke("writeReview", [review]);
|
|
7764
|
+
}
|
|
7765
|
+
// --- Compression / Archive ---
|
|
7766
|
+
compressSprintLog(threshold, summary) {
|
|
7767
|
+
return this.invoke("compressSprintLog", [threshold, summary]);
|
|
7768
|
+
}
|
|
7769
|
+
compressBuildReports(threshold, summary) {
|
|
7770
|
+
return this.invoke("compressBuildReports", [threshold, summary]);
|
|
7771
|
+
}
|
|
7772
|
+
archiveTasks(phases, statuses) {
|
|
7773
|
+
return this.invoke("archiveTasks", [phases, statuses]);
|
|
7774
|
+
}
|
|
7775
|
+
// --- Product Brief & Discovery ---
|
|
7776
|
+
readProductBrief() {
|
|
7777
|
+
return this.invoke("readProductBrief");
|
|
7778
|
+
}
|
|
7779
|
+
updateProductBrief(content) {
|
|
7780
|
+
return this.invoke("updateProductBrief", [content]);
|
|
7781
|
+
}
|
|
7782
|
+
readDiscoveryCanvas() {
|
|
7783
|
+
return this.invoke("readDiscoveryCanvas");
|
|
7784
|
+
}
|
|
7785
|
+
updateDiscoveryCanvas(canvas) {
|
|
7786
|
+
return this.invoke("updateDiscoveryCanvas", [canvas]);
|
|
7787
|
+
}
|
|
7788
|
+
// --- Phases & Hierarchy ---
|
|
7789
|
+
readPhases() {
|
|
7790
|
+
return this.invoke("readPhases");
|
|
7791
|
+
}
|
|
7792
|
+
writePhases(phases) {
|
|
7793
|
+
return this.invoke("writePhases", [phases]);
|
|
7794
|
+
}
|
|
7795
|
+
readHorizons() {
|
|
7796
|
+
return this.invoke("readHorizons");
|
|
7797
|
+
}
|
|
7798
|
+
readStages(horizonId) {
|
|
7799
|
+
return this.invoke("readStages", [horizonId]);
|
|
7800
|
+
}
|
|
7801
|
+
updateStageStatus(stageId, status) {
|
|
7802
|
+
return this.invoke("updateStageStatus", [stageId, status]);
|
|
7803
|
+
}
|
|
7804
|
+
updateHorizonStatus(horizonId, status) {
|
|
7805
|
+
return this.invoke("updateHorizonStatus", [horizonId, status]);
|
|
7806
|
+
}
|
|
7807
|
+
getActiveStage() {
|
|
7808
|
+
return this.invoke("getActiveStage");
|
|
7809
|
+
}
|
|
7810
|
+
createHorizon(horizon) {
|
|
7811
|
+
return this.invoke("createHorizon", [horizon]);
|
|
7812
|
+
}
|
|
7813
|
+
createStage(stage) {
|
|
7814
|
+
return this.invoke("createStage", [stage]);
|
|
7815
|
+
}
|
|
7816
|
+
linkPhasesToStage(stageId) {
|
|
7817
|
+
return this.invoke("linkPhasesToStage", [stageId]);
|
|
7818
|
+
}
|
|
7819
|
+
// --- Metrics ---
|
|
7820
|
+
appendToolMetric(metric) {
|
|
7821
|
+
return this.invoke("appendToolMetric", [metric]);
|
|
7822
|
+
}
|
|
7823
|
+
readToolMetrics() {
|
|
7824
|
+
return this.invoke("readToolMetrics");
|
|
7825
|
+
}
|
|
7826
|
+
getCostSummary(sprintNumber) {
|
|
7827
|
+
return this.invoke("getCostSummary", [sprintNumber]);
|
|
7828
|
+
}
|
|
7829
|
+
writeCostSnapshot(snapshot) {
|
|
7830
|
+
return this.invoke("writeCostSnapshot", [snapshot]);
|
|
7831
|
+
}
|
|
7832
|
+
getCostSnapshots() {
|
|
7833
|
+
return this.invoke("getCostSnapshots");
|
|
7834
|
+
}
|
|
7835
|
+
appendSprintMetrics(snapshot) {
|
|
7836
|
+
return this.invoke("appendSprintMetrics", [snapshot]);
|
|
7837
|
+
}
|
|
7838
|
+
readSprintMetrics() {
|
|
7839
|
+
return this.invoke("readSprintMetrics");
|
|
7840
|
+
}
|
|
7841
|
+
// --- Sprints / Cycles ---
|
|
7842
|
+
readSprints() {
|
|
7843
|
+
return this.invoke("readSprints");
|
|
7844
|
+
}
|
|
7845
|
+
createSprint(sprint) {
|
|
7846
|
+
return this.invoke("createSprint", [sprint]);
|
|
7847
|
+
}
|
|
7848
|
+
readCycles() {
|
|
7849
|
+
return this.invoke("readCycles");
|
|
7850
|
+
}
|
|
7851
|
+
createCycle(cycle) {
|
|
7852
|
+
return this.invoke("createCycle", [cycle]);
|
|
7853
|
+
}
|
|
7854
|
+
getContextHashes(sprintNumber) {
|
|
7855
|
+
return this.invoke("getContextHashes", [sprintNumber]);
|
|
7856
|
+
}
|
|
7857
|
+
// --- Registries ---
|
|
7858
|
+
readRegistries() {
|
|
7859
|
+
return this.invoke("readRegistries");
|
|
7860
|
+
}
|
|
7861
|
+
updateRegistries(registries) {
|
|
7862
|
+
return this.invoke("updateRegistries", [registries]);
|
|
7863
|
+
}
|
|
7864
|
+
// --- Recommendations ---
|
|
7865
|
+
writeRecommendation(rec) {
|
|
7866
|
+
return this.invoke("writeRecommendation", [rec]);
|
|
7867
|
+
}
|
|
7868
|
+
getPendingRecommendations() {
|
|
7869
|
+
return this.invoke("getPendingRecommendations");
|
|
7870
|
+
}
|
|
7871
|
+
actionRecommendation(id, sprintNumber) {
|
|
7872
|
+
return this.invoke("actionRecommendation", [id, sprintNumber]);
|
|
7873
|
+
}
|
|
7874
|
+
// --- Decision Events & Scores ---
|
|
7875
|
+
appendDecisionEvent(event) {
|
|
7876
|
+
return this.invoke("appendDecisionEvent", [event]);
|
|
7877
|
+
}
|
|
7878
|
+
getDecisionEvents(decisionId, limit) {
|
|
7879
|
+
return this.invoke("getDecisionEvents", [decisionId, limit]);
|
|
7880
|
+
}
|
|
7881
|
+
getDecisionEventsSince(sprint) {
|
|
7882
|
+
return this.invoke("getDecisionEventsSince", [sprint]);
|
|
7883
|
+
}
|
|
7884
|
+
writeDecisionScore(score) {
|
|
7885
|
+
return this.invoke("writeDecisionScore", [score]);
|
|
7886
|
+
}
|
|
7887
|
+
getDecisionScores(decisionId) {
|
|
7888
|
+
return this.invoke("getDecisionScores", [decisionId]);
|
|
7889
|
+
}
|
|
7890
|
+
getLatestDecisionScores() {
|
|
7891
|
+
return this.invoke("getLatestDecisionScores");
|
|
7892
|
+
}
|
|
7893
|
+
logEntityReferences(refs) {
|
|
7894
|
+
return this.invoke("logEntityReferences", [refs]);
|
|
7895
|
+
}
|
|
7896
|
+
getDecisionUsage(currentSprint) {
|
|
7897
|
+
return this.invoke("getDecisionUsage", [currentSprint]);
|
|
7898
|
+
}
|
|
7899
|
+
getContextUtilisation() {
|
|
7900
|
+
return this.invoke("getContextUtilisation");
|
|
7901
|
+
}
|
|
7902
|
+
// --- Strategy Reviews ---
|
|
7903
|
+
writeStrategyReview(review) {
|
|
7904
|
+
return this.invoke("writeStrategyReview", [review]);
|
|
7905
|
+
}
|
|
7906
|
+
getLastStrategyReviewSprint() {
|
|
7907
|
+
return this.invoke("getLastStrategyReviewSprint");
|
|
7908
|
+
}
|
|
7909
|
+
getStrategyReviews(limit, includeFullAnalysis) {
|
|
7910
|
+
return this.invoke("getStrategyReviews", [limit, includeFullAnalysis]);
|
|
7911
|
+
}
|
|
7912
|
+
// --- Dogfood ---
|
|
7913
|
+
writeDogfoodEntries(entries) {
|
|
7914
|
+
return this.invoke("writeDogfoodEntries", [entries]);
|
|
7915
|
+
}
|
|
7916
|
+
getDogfoodLog(limit) {
|
|
7917
|
+
return this.invoke("getDogfoodLog", [limit]);
|
|
7918
|
+
}
|
|
7919
|
+
getUnactionedDogfoodEntries(limit) {
|
|
7920
|
+
return this.invoke("getUnactionedDogfoodEntries", [limit]);
|
|
7921
|
+
}
|
|
7922
|
+
updateDogfoodEntryStatus(id, status, linkedTaskId) {
|
|
7923
|
+
return this.invoke("updateDogfoodEntryStatus", [id, status, linkedTaskId]);
|
|
7924
|
+
}
|
|
7925
|
+
// --- North Star ---
|
|
7926
|
+
getCurrentNorthStar() {
|
|
7927
|
+
return this.invoke("getCurrentNorthStar");
|
|
7928
|
+
}
|
|
7929
|
+
getNorthStarSetCycle() {
|
|
7930
|
+
return this.invoke("getNorthStarSetCycle");
|
|
7931
|
+
}
|
|
7932
|
+
// --- Optional pg-only methods ---
|
|
7933
|
+
getEstimationCalibration() {
|
|
7934
|
+
return this.invoke("getEstimationCalibration");
|
|
7935
|
+
}
|
|
7936
|
+
getRecentTaskComments(limit) {
|
|
7937
|
+
return this.invoke("getRecentTaskComments", [limit]);
|
|
7938
|
+
}
|
|
7939
|
+
getRecommendationEffectiveness() {
|
|
7940
|
+
return this.invoke("getRecommendationEffectiveness");
|
|
7941
|
+
}
|
|
7942
|
+
getPlanContextSummary() {
|
|
7943
|
+
return this.invoke("getPlanContextSummary");
|
|
7944
|
+
}
|
|
7945
|
+
projectExists() {
|
|
7946
|
+
return this.invoke("projectExists");
|
|
7947
|
+
}
|
|
7948
|
+
// --- Atomic plan write-back ---
|
|
7949
|
+
planWriteBack(payload) {
|
|
7950
|
+
return this.invoke("planWriteBack", [payload]);
|
|
7951
|
+
}
|
|
7952
|
+
};
|
|
7953
|
+
}
|
|
7954
|
+
});
|
|
7955
|
+
|
|
6857
7956
|
// src/index.ts
|
|
6858
7957
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6859
7958
|
|
|
@@ -6872,7 +7971,10 @@ function loadConfig() {
|
|
|
6872
7971
|
const baseBranch = process.env.PAPI_BASE_BRANCH ?? "main";
|
|
6873
7972
|
const autoPR = process.env.PAPI_AUTO_PR !== "false";
|
|
6874
7973
|
const papiEndpoint = process.env.PAPI_ENDPOINT;
|
|
6875
|
-
const
|
|
7974
|
+
const dataEndpoint = process.env.PAPI_DATA_ENDPOINT;
|
|
7975
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
7976
|
+
const explicitAdapter = process.env.PAPI_ADAPTER;
|
|
7977
|
+
const adapterType = papiEndpoint ? "pg" : databaseUrl && explicitAdapter === "pg" ? "pg" : dataEndpoint ? "proxy" : explicitAdapter ? explicitAdapter : databaseUrl ? "pg" : "proxy";
|
|
6876
7978
|
return {
|
|
6877
7979
|
projectRoot,
|
|
6878
7980
|
papiDir: path.join(projectRoot, ".papi"),
|
|
@@ -6888,6 +7990,8 @@ function loadConfig() {
|
|
|
6888
7990
|
// src/adapter-factory.ts
|
|
6889
7991
|
init_dist2();
|
|
6890
7992
|
import path2 from "path";
|
|
7993
|
+
var HOSTED_PROXY_ENDPOINT = "https://guewgygcpcmrcoppihzx.supabase.co/functions/v1/data-proxy";
|
|
7994
|
+
var HOSTED_PROXY_KEY = "e9891a0a2225ac376f88ebdad78b4814b52ce0a39a41c5ec";
|
|
6891
7995
|
var PLACEHOLDER_PATTERNS = [
|
|
6892
7996
|
"<YOUR_DATABASE_URL>",
|
|
6893
7997
|
"your-database-url",
|
|
@@ -6933,6 +8037,11 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
6933
8037
|
}
|
|
6934
8038
|
const config2 = papiEndpoint ? { connectionString: papiEndpoint } : configFromEnv2();
|
|
6935
8039
|
validateDatabaseUrl(config2.connectionString);
|
|
8040
|
+
try {
|
|
8041
|
+
const { ensureSchema: ensureSchema2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports));
|
|
8042
|
+
await ensureSchema2(config2);
|
|
8043
|
+
} catch {
|
|
8044
|
+
}
|
|
6936
8045
|
try {
|
|
6937
8046
|
const pgAdapter = new PgAdapter2(config2);
|
|
6938
8047
|
const existing = await pgAdapter.getProject(projectId);
|
|
@@ -6960,10 +8069,36 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
6960
8069
|
}
|
|
6961
8070
|
return adapter2;
|
|
6962
8071
|
}
|
|
8072
|
+
case "proxy": {
|
|
8073
|
+
const { ProxyPapiAdapter: ProxyPapiAdapter2 } = await Promise.resolve().then(() => (init_proxy_adapter(), proxy_adapter_exports));
|
|
8074
|
+
const projectId = process.env["PAPI_PROJECT_ID"];
|
|
8075
|
+
if (!projectId) {
|
|
8076
|
+
throw new Error(
|
|
8077
|
+
"PAPI_PROJECT_ID is required. Generate a UUID (run `uuidgen` in terminal) and set it in your .mcp.json env config."
|
|
8078
|
+
);
|
|
8079
|
+
}
|
|
8080
|
+
const dataEndpoint = process.env["PAPI_DATA_ENDPOINT"] || HOSTED_PROXY_ENDPOINT;
|
|
8081
|
+
const dataApiKey = process.env["PAPI_DATA_API_KEY"] || HOSTED_PROXY_KEY;
|
|
8082
|
+
const adapter2 = new ProxyPapiAdapter2({
|
|
8083
|
+
endpoint: dataEndpoint,
|
|
8084
|
+
apiKey: dataApiKey,
|
|
8085
|
+
projectId
|
|
8086
|
+
});
|
|
8087
|
+
const connected = await adapter2.probeConnection();
|
|
8088
|
+
if (connected) {
|
|
8089
|
+
_connectionStatus = "connected";
|
|
8090
|
+
console.error("[papi] \u2713 Data proxy connected");
|
|
8091
|
+
} else {
|
|
8092
|
+
_connectionStatus = "degraded";
|
|
8093
|
+
console.error("[papi] \u2717 Data proxy unreachable \u2014 running in degraded mode");
|
|
8094
|
+
console.error("[papi] Check your PAPI_DATA_ENDPOINT configuration.");
|
|
8095
|
+
}
|
|
8096
|
+
return adapter2;
|
|
8097
|
+
}
|
|
6963
8098
|
default: {
|
|
6964
8099
|
const _exhaustive = adapterType;
|
|
6965
8100
|
throw new Error(
|
|
6966
|
-
`Unknown PAPI_ADAPTER value: "${_exhaustive}". Valid options: "md", "pg".`
|
|
8101
|
+
`Unknown PAPI_ADAPTER value: "${_exhaustive}". Valid options: "md", "pg", "proxy".`
|
|
6967
8102
|
);
|
|
6968
8103
|
}
|
|
6969
8104
|
}
|
|
@@ -7981,6 +9116,7 @@ Standard planning sprint with full board review.
|
|
|
7981
9116
|
- **Surprises:** If a surprise reveals a gap (e.g. "schema assumed but not verified"), propose a task to close it.
|
|
7982
9117
|
- **Architecture Notes:** If a pattern was established that needs follow-up (e.g. "shared service layer created, MCP migration needed"), propose the follow-up.
|
|
7983
9118
|
- **Strategy gaps:** If an Active Decision has no board tasks supporting it, propose one.
|
|
9119
|
+
- **Dogfood observations:** If unactioned dogfood entries are listed in context (with IDs), check if any map to existing tasks. If not, propose a new task. Include \`dogfood:ID\` in the new task's notes so the pipeline can link them.
|
|
7984
9120
|
Create new tasks via the \`newTasks\` array in Part 2. Use \`new-N\` IDs in \`cycleHandoffs\` to reference them. **Limit: 3 new tasks per sprint** to prevent backlog bloat.
|
|
7985
9121
|
**\u26A0\uFE0F DUPLICATE CHECK:** Before adding a task to \`newTasks\`, scan the Sprint Board above for any existing task with the same or very similar title/scope. If a matching task already exists (even with slightly different wording), do NOT create a duplicate \u2014 reference the existing task ID instead. The board already contains all active tasks; re-creating them wastes IDs and bloats the board.
|
|
7986
9122
|
**\u26A0\uFE0F ALREADY-BUILT CHECK:** Before creating a task, check the recent build reports and sprint log for evidence that this capability was already shipped. If a recent build report shows this feature was completed (even under a different task name), do NOT create a new task for it. This is especially important for UI features, data models, and integrations that may already exist.
|
|
@@ -8280,8 +9416,8 @@ After your natural language output, include this EXACT format on its own line:
|
|
|
8280
9416
|
"activeDecisionUpdates": [
|
|
8281
9417
|
{
|
|
8282
9418
|
"id": "string \u2014 AD-N (existing) or new AD-N (for new decisions)",
|
|
8283
|
-
"action": "confidence_change | modify | resolve | supersede | new",
|
|
8284
|
-
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text"
|
|
9419
|
+
"action": "confidence_change | modify | resolve | supersede | new | delete",
|
|
9420
|
+
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text (empty string for delete)"
|
|
8285
9421
|
}
|
|
8286
9422
|
],
|
|
8287
9423
|
"decisionScores": [
|
|
@@ -8346,7 +9482,7 @@ Everything in Part 1 (natural language) is **display-only**. Part 2 (structured
|
|
|
8346
9482
|
|
|
8347
9483
|
**If you analysed it in Part 1, it MUST appear in Part 2 to persist. Empty arrays/null = nothing saved.**
|
|
8348
9484
|
|
|
8349
|
-
- Recommended AD changes in Part 1? \u2192 Put them in \`activeDecisionUpdates\` with full body including ### heading
|
|
9485
|
+
- Recommended AD changes in Part 1? \u2192 Put them in \`activeDecisionUpdates\` with full body including ### heading. Use \`delete\` action (with empty body) to permanently remove non-strategic ADs (implementation details, resolved decisions, library choices). Use \`supersede\` when a decision is replaced by a new one.
|
|
8350
9486
|
- Scored ADs in Part 1? \u2192 Put scores in \`decisionScores\` array with id, dimensions, and rationale
|
|
8351
9487
|
- Identified proven insights, direction changes, or deprecated approaches? \u2192 Put the full updated product brief in \`productBriefUpdates\`. This is how strategic learnings get locked into the project's institutional memory. Common triggers: a phase completing, a hypothesis being validated/invalidated, a new constraint emerging, or the North Star evolving.${compressionPersistence}
|
|
8352
9488
|
- Wrote a strategy review in Part 1? \u2192 \`sessionLogTitle\`, \`sessionLogContent\`, \`velocityAssessment\`, \`strategicRecommendations\` must all be populated
|
|
@@ -8480,8 +9616,8 @@ After your natural language output, include this EXACT format on its own line:
|
|
|
8480
9616
|
"activeDecisionUpdates": [
|
|
8481
9617
|
{
|
|
8482
9618
|
"id": "string \u2014 AD-N (existing) or new AD-N (for new decisions)",
|
|
8483
|
-
"action": "confidence_change | modify | resolve | supersede | new",
|
|
8484
|
-
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text"
|
|
9619
|
+
"action": "confidence_change | modify | resolve | supersede | new | delete",
|
|
9620
|
+
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text (empty string for delete)"
|
|
8485
9621
|
}
|
|
8486
9622
|
],
|
|
8487
9623
|
"phaseUpdates": [
|
|
@@ -8794,6 +9930,60 @@ ${inputs.codebaseContext}
|
|
|
8794
9930
|
Return a JSON array of 3-10 tasks based on gaps, improvements, and next steps visible from the codebase analysis above.`;
|
|
8795
9931
|
}
|
|
8796
9932
|
|
|
9933
|
+
// src/lib/prompt-registry.ts
|
|
9934
|
+
var LOCAL_PROMPTS = {
|
|
9935
|
+
"plan-system": PLAN_SYSTEM,
|
|
9936
|
+
"plan-bootstrap": PLAN_BOOTSTRAP_INSTRUCTIONS,
|
|
9937
|
+
"plan-full": PLAN_FULL_INSTRUCTIONS,
|
|
9938
|
+
"review-system": buildReviewSystemPrompt(),
|
|
9939
|
+
"strategy-change-system": STRATEGY_CHANGE_SYSTEM,
|
|
9940
|
+
"handoff-regen-system": HANDOFF_REGEN_SYSTEM
|
|
9941
|
+
};
|
|
9942
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
9943
|
+
var cache = /* @__PURE__ */ new Map();
|
|
9944
|
+
function getEndpoint() {
|
|
9945
|
+
return process.env.PAPI_PROMPT_ENDPOINT;
|
|
9946
|
+
}
|
|
9947
|
+
function getApiKey() {
|
|
9948
|
+
return process.env.PAPI_PROMPT_API_KEY;
|
|
9949
|
+
}
|
|
9950
|
+
async function getPrompt(name) {
|
|
9951
|
+
const endpoint = getEndpoint();
|
|
9952
|
+
if (!endpoint) {
|
|
9953
|
+
return LOCAL_PROMPTS[name];
|
|
9954
|
+
}
|
|
9955
|
+
const cached = cache.get(name);
|
|
9956
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
9957
|
+
return cached.content;
|
|
9958
|
+
}
|
|
9959
|
+
try {
|
|
9960
|
+
const url = endpoint.endsWith("/") ? `${endpoint}${name}` : `${endpoint}/${name}`;
|
|
9961
|
+
const apiKey = getApiKey();
|
|
9962
|
+
const headers = {
|
|
9963
|
+
"Accept": "application/json"
|
|
9964
|
+
};
|
|
9965
|
+
if (apiKey) {
|
|
9966
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
9967
|
+
}
|
|
9968
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(1e4) });
|
|
9969
|
+
if (!response.ok) {
|
|
9970
|
+
if (cached) return cached.content;
|
|
9971
|
+
return LOCAL_PROMPTS[name];
|
|
9972
|
+
}
|
|
9973
|
+
const data = await response.json();
|
|
9974
|
+
const content = data.content;
|
|
9975
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
9976
|
+
if (cached) return cached.content;
|
|
9977
|
+
return LOCAL_PROMPTS[name];
|
|
9978
|
+
}
|
|
9979
|
+
cache.set(name, { content, fetchedAt: Date.now() });
|
|
9980
|
+
return content;
|
|
9981
|
+
} catch {
|
|
9982
|
+
if (cached) return cached.content;
|
|
9983
|
+
return LOCAL_PROMPTS[name];
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
|
|
8797
9987
|
// src/services/plan.ts
|
|
8798
9988
|
function determineMode(totalSprints) {
|
|
8799
9989
|
if (totalSprints === 0) return "bootstrap";
|
|
@@ -9007,6 +10197,15 @@ function applyContextDiff(ctx, prevHashes) {
|
|
|
9007
10197
|
return { ctx, newHashes, savedBytes };
|
|
9008
10198
|
}
|
|
9009
10199
|
async function readDogfoodEntries(projectRoot, count = 5, adapter2) {
|
|
10200
|
+
if (adapter2?.getUnactionedDogfoodEntries) {
|
|
10201
|
+
try {
|
|
10202
|
+
const entries = await adapter2.getUnactionedDogfoodEntries(count);
|
|
10203
|
+
if (entries.length > 0) {
|
|
10204
|
+
return "**Unactioned observations (status: observed):**\n" + entries.map((e) => `- **[${e.category}]** (Cycle ${e.cycleNumber}, id: ${e.id}) ${e.content}`).join("\n");
|
|
10205
|
+
}
|
|
10206
|
+
} catch {
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
9010
10209
|
if (adapter2?.getDogfoodLog) {
|
|
9011
10210
|
try {
|
|
9012
10211
|
const entries = await adapter2.getDogfoodLog(count);
|
|
@@ -9462,6 +10661,15 @@ ${cleanContent}`;
|
|
|
9462
10661
|
notes: task.notes || ""
|
|
9463
10662
|
});
|
|
9464
10663
|
newTaskIdMap.set(`new-${i + 1}`, created.id);
|
|
10664
|
+
if (adapter2.updateDogfoodEntryStatus && task.notes) {
|
|
10665
|
+
const dogfoodRefs = task.notes.match(/dogfood:([a-f0-9-]+)/gi);
|
|
10666
|
+
if (dogfoodRefs) {
|
|
10667
|
+
for (const ref of dogfoodRefs) {
|
|
10668
|
+
const entryId = ref.split(":")[1];
|
|
10669
|
+
await adapter2.updateDogfoodEntryStatus(entryId, "backlog-created", created.id);
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
9465
10673
|
}
|
|
9466
10674
|
})();
|
|
9467
10675
|
const priorityLockNotes = [];
|
|
@@ -9769,10 +10977,11 @@ async function preparePlan(adapter2, config2, filters, focus, force) {
|
|
|
9769
10977
|
}
|
|
9770
10978
|
} catch {
|
|
9771
10979
|
}
|
|
10980
|
+
const planSystemPrompt = await getPrompt("plan-system");
|
|
9772
10981
|
return {
|
|
9773
10982
|
mode,
|
|
9774
10983
|
cycleNumber: sprintNumber,
|
|
9775
|
-
systemPrompt:
|
|
10984
|
+
systemPrompt: planSystemPrompt,
|
|
9776
10985
|
userMessage,
|
|
9777
10986
|
strategyReviewWarning,
|
|
9778
10987
|
contextBytes,
|
|
@@ -10408,14 +11617,14 @@ async function assembleContext2(adapter2, sprintNumber, sprintsSinceLastReview,
|
|
|
10408
11617
|
northStar: currentNorthStar ?? void 0,
|
|
10409
11618
|
recommendationEffectiveness: recEffectivenessText
|
|
10410
11619
|
};
|
|
10411
|
-
const
|
|
10412
|
-
const
|
|
11620
|
+
const BUDGET_SOFT2 = 8e4;
|
|
11621
|
+
const BUDGET_HARD2 = 1e5;
|
|
10413
11622
|
const compressionSteps = [];
|
|
10414
11623
|
function measureContext(ctx) {
|
|
10415
11624
|
return Object.values(ctx).filter((v) => typeof v === "string").reduce((sum, s) => sum + s.length, 0);
|
|
10416
11625
|
}
|
|
10417
11626
|
let contextSize = measureContext(context);
|
|
10418
|
-
if (contextSize >
|
|
11627
|
+
if (contextSize > BUDGET_SOFT2 && context.previousReviews) {
|
|
10419
11628
|
const reviews2 = previousStrategyReviews;
|
|
10420
11629
|
if (reviews2.length > 1) {
|
|
10421
11630
|
const firstFull = formatPreviousReviews([reviews2[0]]) ?? "";
|
|
@@ -10429,7 +11638,7 @@ async function assembleContext2(adapter2, sprintNumber, sprintsSinceLastReview,
|
|
|
10429
11638
|
compressionSteps.push(`Step 1: previous reviews capped (1 full + ${reviews2.length - 1} headers)`);
|
|
10430
11639
|
}
|
|
10431
11640
|
}
|
|
10432
|
-
if (contextSize >
|
|
11641
|
+
if (contextSize > BUDGET_SOFT2 && context.humanReviews) {
|
|
10433
11642
|
const trimmed = formatReviews(reviews.slice(0, 5));
|
|
10434
11643
|
if (trimmed.length < context.humanReviews.length) {
|
|
10435
11644
|
context.humanReviews = trimmed;
|
|
@@ -10437,7 +11646,7 @@ async function assembleContext2(adapter2, sprintNumber, sprintsSinceLastReview,
|
|
|
10437
11646
|
compressionSteps.push("Step 2: human reviews capped to 5");
|
|
10438
11647
|
}
|
|
10439
11648
|
}
|
|
10440
|
-
if (contextSize >
|
|
11649
|
+
if (contextSize > BUDGET_SOFT2 && context.allBuildReports) {
|
|
10441
11650
|
const summary = formatRecentReportsSummary(reports, 10);
|
|
10442
11651
|
if (summary.length < context.allBuildReports.length) {
|
|
10443
11652
|
context.allBuildReports = summary;
|
|
@@ -10445,11 +11654,11 @@ async function assembleContext2(adapter2, sprintNumber, sprintsSinceLastReview,
|
|
|
10445
11654
|
compressionSteps.push("Step 3: build reports summarized");
|
|
10446
11655
|
}
|
|
10447
11656
|
}
|
|
10448
|
-
if (contextSize >
|
|
11657
|
+
if (contextSize > BUDGET_HARD2) {
|
|
10449
11658
|
const entries = Object.entries(context).filter((e) => typeof e[1] === "string").sort((a, b2) => b2[1].length - a[1].length);
|
|
10450
11659
|
if (entries.length > 0) {
|
|
10451
11660
|
const [key, value] = entries[0];
|
|
10452
|
-
const excess = contextSize -
|
|
11661
|
+
const excess = contextSize - BUDGET_SOFT2;
|
|
10453
11662
|
const newLength = Math.max(value.length - excess, 1e3);
|
|
10454
11663
|
context[key] = value.slice(0, newLength) + "\n\n[truncated \u2014 context budget exceeded]";
|
|
10455
11664
|
contextSize = measureContext(context);
|
|
@@ -10506,8 +11715,12 @@ ${cleanContent}`;
|
|
|
10506
11715
|
}
|
|
10507
11716
|
if (data.activeDecisionUpdates && data.activeDecisionUpdates.length > 0) {
|
|
10508
11717
|
for (const ad of data.activeDecisionUpdates) {
|
|
10509
|
-
|
|
10510
|
-
|
|
11718
|
+
if (ad.action === "delete" && adapter2.deleteActiveDecision) {
|
|
11719
|
+
await adapter2.deleteActiveDecision(ad.id);
|
|
11720
|
+
} else {
|
|
11721
|
+
await adapter2.updateActiveDecision(ad.id, ad.body, sprintNumber);
|
|
11722
|
+
}
|
|
11723
|
+
const eventType = ad.action === "delete" ? "deleted" : ad.action === "confidence_change" ? "confidence_changed" : ad.action === "supersede" ? "superseded" : ad.action === "new" ? "created" : "modified";
|
|
10511
11724
|
try {
|
|
10512
11725
|
await adapter2.appendDecisionEvent({
|
|
10513
11726
|
decisionId: ad.id,
|
|
@@ -10939,8 +12152,12 @@ ${cleanContent}`;
|
|
|
10939
12152
|
});
|
|
10940
12153
|
if (data.activeDecisionUpdates && data.activeDecisionUpdates.length > 0) {
|
|
10941
12154
|
for (const ad of data.activeDecisionUpdates) {
|
|
10942
|
-
|
|
10943
|
-
|
|
12155
|
+
if (ad.action === "delete" && adapter2.deleteActiveDecision) {
|
|
12156
|
+
await adapter2.deleteActiveDecision(ad.id);
|
|
12157
|
+
} else {
|
|
12158
|
+
await adapter2.updateActiveDecision(ad.id, ad.body, sprintNumber);
|
|
12159
|
+
}
|
|
12160
|
+
const eventType = ad.action === "delete" ? "deleted" : ad.action === "confidence_change" ? "confidence_changed" : ad.action === "supersede" ? "superseded" : ad.action === "new" ? "created" : "modified";
|
|
10944
12161
|
try {
|
|
10945
12162
|
await adapter2.appendDecisionEvent({
|
|
10946
12163
|
decisionId: ad.id,
|
|
@@ -11005,9 +12222,10 @@ async function prepareStrategyChange(adapter2, text) {
|
|
|
11005
12222
|
const message = err instanceof Error ? err.message : String(err);
|
|
11006
12223
|
throw new Error(`reading project state: ${message}`);
|
|
11007
12224
|
}
|
|
12225
|
+
const strategyChangePrompt = await getPrompt("strategy-change-system");
|
|
11008
12226
|
return {
|
|
11009
12227
|
sprintNumber,
|
|
11010
|
-
systemPrompt:
|
|
12228
|
+
systemPrompt: strategyChangePrompt,
|
|
11011
12229
|
userMessage: buildStrategyChangeUserMessage(sprintNumber, text, productBrief, activeDecisions, phases, tasks, buildReports, previousReviewsText)
|
|
11012
12230
|
};
|
|
11013
12231
|
}
|
|
@@ -11426,25 +12644,34 @@ var boardViewTool = {
|
|
|
11426
12644
|
};
|
|
11427
12645
|
var boardDeprioritiseTool = {
|
|
11428
12646
|
name: "board_deprioritise",
|
|
11429
|
-
description:
|
|
12647
|
+
description: `Remove a task from the current cycle. Three actions: "backlog" (not now, maybe later \u2014 preserves handoff), "defer" (valid but premature \u2014 hidden from planner), "cancel" (don't want this \u2014 permanently closed with reason). When a user rejects a task, ALWAYS ask which action they want. Does not call the Anthropic API.`,
|
|
11430
12648
|
inputSchema: {
|
|
11431
12649
|
type: "object",
|
|
11432
12650
|
properties: {
|
|
11433
12651
|
task_id: {
|
|
11434
12652
|
type: "string",
|
|
11435
|
-
description: "The task ID to
|
|
12653
|
+
description: "The task ID to act on."
|
|
12654
|
+
},
|
|
12655
|
+
action: {
|
|
12656
|
+
type: "string",
|
|
12657
|
+
enum: ["backlog", "defer", "cancel"],
|
|
12658
|
+
description: `"backlog" = not now, maybe later (preserves handoff). "defer" = valid but premature (hidden from planner). "cancel" = don't want this at all (permanently closed). If omitted, defaults to "backlog" for backwards compatibility.`
|
|
12659
|
+
},
|
|
12660
|
+
reason: {
|
|
12661
|
+
type: "string",
|
|
12662
|
+
description: 'Why this task is being removed. Required for "cancel", recommended for "defer".'
|
|
11436
12663
|
},
|
|
11437
12664
|
defer: {
|
|
11438
12665
|
type: "boolean",
|
|
11439
|
-
description:
|
|
12666
|
+
description: 'DEPRECATED \u2014 use action instead. If true, equivalent to action="defer".'
|
|
11440
12667
|
},
|
|
11441
12668
|
priority: {
|
|
11442
12669
|
type: "string",
|
|
11443
|
-
description: 'Optional new priority
|
|
12670
|
+
description: 'Optional new priority (only applies to "backlog" action).'
|
|
11444
12671
|
},
|
|
11445
12672
|
phase: {
|
|
11446
12673
|
type: "string",
|
|
11447
|
-
description:
|
|
12674
|
+
description: 'Optional new phase (only applies to "backlog" and "defer" actions).'
|
|
11448
12675
|
}
|
|
11449
12676
|
},
|
|
11450
12677
|
required: ["task_id"]
|
|
@@ -11543,19 +12770,43 @@ async function handleBoardDeprioritise(adapter2, args) {
|
|
|
11543
12770
|
if (!taskId) {
|
|
11544
12771
|
return errorResponse("task_id is required for deprioritise.");
|
|
11545
12772
|
}
|
|
11546
|
-
const
|
|
12773
|
+
const explicitAction = args.action;
|
|
12774
|
+
const legacyDefer = args.defer;
|
|
12775
|
+
const action = explicitAction ?? (legacyDefer ? "defer" : "backlog");
|
|
12776
|
+
const reason = args.reason;
|
|
11547
12777
|
const newPriority = args.priority;
|
|
11548
12778
|
const newPhase = args.phase;
|
|
12779
|
+
if (action === "cancel") {
|
|
12780
|
+
if (!reason) {
|
|
12781
|
+
return errorResponse("reason is required when cancelling a task.");
|
|
12782
|
+
}
|
|
12783
|
+
try {
|
|
12784
|
+
const task = await adapter2.getTask(taskId);
|
|
12785
|
+
if (!task) return errorResponse(`Task ${taskId} not found.`);
|
|
12786
|
+
await adapter2.updateTask(taskId, {
|
|
12787
|
+
status: "Cancelled",
|
|
12788
|
+
buildHandoff: void 0,
|
|
12789
|
+
closureReason: reason
|
|
12790
|
+
});
|
|
12791
|
+
return textResponse(`Cancelled **${taskId}** (${task.title}).
|
|
12792
|
+
|
|
12793
|
+
Reason: ${reason}`);
|
|
12794
|
+
} catch (err) {
|
|
12795
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
12796
|
+
}
|
|
12797
|
+
}
|
|
11549
12798
|
try {
|
|
11550
12799
|
const { task, changes } = await deprioritiseTask(adapter2, taskId, {
|
|
11551
|
-
defer,
|
|
12800
|
+
defer: action === "defer",
|
|
11552
12801
|
priority: newPriority,
|
|
11553
12802
|
phase: newPhase
|
|
11554
12803
|
});
|
|
11555
|
-
const
|
|
11556
|
-
|
|
12804
|
+
const label = action === "defer" ? "Deferred" : "Moved to Backlog";
|
|
12805
|
+
const reasonNote = reason ? `
|
|
12806
|
+
Reason: ${reason}` : "";
|
|
12807
|
+
return textResponse(`${label} **${taskId}** (${task.title}).
|
|
11557
12808
|
|
|
11558
|
-
Changes: ${changes.join(", ")}
|
|
12809
|
+
Changes: ${changes.join(", ")}.${reasonNote}`);
|
|
11559
12810
|
} catch (err) {
|
|
11560
12811
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
11561
12812
|
}
|
|
@@ -11946,51 +13197,62 @@ function substitute(template, vars) {
|
|
|
11946
13197
|
return result;
|
|
11947
13198
|
}
|
|
11948
13199
|
async function scaffoldPapiDir(adapter2, config2, input) {
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
try {
|
|
11953
|
-
await access2(config2.papiDir);
|
|
11954
|
-
return false;
|
|
11955
|
-
} catch {
|
|
11956
|
-
await mkdir(config2.papiDir, { recursive: true });
|
|
11957
|
-
const vars = { project_name: input.projectName, description: input.description };
|
|
11958
|
-
for (const [filename, template] of Object.entries(FILE_TEMPLATES)) {
|
|
11959
|
-
const content = substitute(template, vars);
|
|
11960
|
-
await writeFile2(join2(config2.papiDir, filename), content, "utf-8");
|
|
11961
|
-
}
|
|
11962
|
-
const commandsDir = join2(config2.projectRoot, ".claude", "commands");
|
|
11963
|
-
const docsDir = join2(config2.projectRoot, "docs");
|
|
11964
|
-
await mkdir(commandsDir, { recursive: true });
|
|
11965
|
-
await mkdir(docsDir, { recursive: true });
|
|
11966
|
-
const claudeMdPath = join2(config2.projectRoot, "CLAUDE.md");
|
|
11967
|
-
let claudeMdExists = false;
|
|
13200
|
+
const vars = { project_name: input.projectName, description: input.description };
|
|
13201
|
+
const isPg = config2.adapterType === "pg";
|
|
13202
|
+
if (!isPg) {
|
|
11968
13203
|
try {
|
|
11969
|
-
await access2(
|
|
11970
|
-
|
|
13204
|
+
await access2(config2.papiDir);
|
|
13205
|
+
return false;
|
|
11971
13206
|
} catch {
|
|
13207
|
+
await mkdir(config2.papiDir, { recursive: true });
|
|
13208
|
+
for (const [filename, template] of Object.entries(FILE_TEMPLATES)) {
|
|
13209
|
+
const content = substitute(template, vars);
|
|
13210
|
+
await writeFile2(join2(config2.papiDir, filename), content, "utf-8");
|
|
13211
|
+
}
|
|
11972
13212
|
}
|
|
11973
|
-
|
|
11974
|
-
|
|
13213
|
+
}
|
|
13214
|
+
const commandsDir = join2(config2.projectRoot, ".claude", "commands");
|
|
13215
|
+
const docsDir = join2(config2.projectRoot, "docs");
|
|
13216
|
+
await mkdir(commandsDir, { recursive: true });
|
|
13217
|
+
await mkdir(docsDir, { recursive: true });
|
|
13218
|
+
const claudeMdPath = join2(config2.projectRoot, "CLAUDE.md");
|
|
13219
|
+
let claudeMdExists = false;
|
|
13220
|
+
try {
|
|
13221
|
+
await access2(claudeMdPath);
|
|
13222
|
+
claudeMdExists = true;
|
|
13223
|
+
} catch {
|
|
13224
|
+
}
|
|
13225
|
+
const docsIndexPath = join2(docsDir, "INDEX.md");
|
|
13226
|
+
let docsIndexExists = false;
|
|
13227
|
+
try {
|
|
13228
|
+
await access2(docsIndexPath);
|
|
13229
|
+
docsIndexExists = true;
|
|
13230
|
+
} catch {
|
|
13231
|
+
}
|
|
13232
|
+
const scaffoldFiles = {
|
|
13233
|
+
[join2(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
|
|
13234
|
+
[join2(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
|
|
13235
|
+
[join2(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
|
|
13236
|
+
};
|
|
13237
|
+
if (!docsIndexExists) {
|
|
13238
|
+
scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
|
|
13239
|
+
}
|
|
13240
|
+
if (!claudeMdExists) {
|
|
13241
|
+
scaffoldFiles[claudeMdPath] = substitute(CLAUDE_MD_TEMPLATE, vars);
|
|
13242
|
+
} else {
|
|
11975
13243
|
try {
|
|
11976
|
-
await
|
|
11977
|
-
|
|
13244
|
+
const existing = await readFile3(claudeMdPath, "utf-8");
|
|
13245
|
+
if (!existing.includes("## Workflow Sequences") && !existing.includes("### The Cycle (main flow)")) {
|
|
13246
|
+
const papiSection = "\n\n" + substitute(CLAUDE_MD_TEMPLATE, vars).split("\n").slice(1).join("\n");
|
|
13247
|
+
scaffoldFiles[claudeMdPath] = existing + papiSection;
|
|
13248
|
+
}
|
|
11978
13249
|
} catch {
|
|
11979
13250
|
}
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
if (!docsIndexExists) {
|
|
11986
|
-
scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
|
|
11987
|
-
}
|
|
11988
|
-
if (!claudeMdExists) {
|
|
11989
|
-
scaffoldFiles[claudeMdPath] = substitute(CLAUDE_MD_TEMPLATE, vars);
|
|
11990
|
-
}
|
|
11991
|
-
for (const [filepath, content] of Object.entries(scaffoldFiles)) {
|
|
11992
|
-
await writeFile2(filepath, content, "utf-8");
|
|
11993
|
-
}
|
|
13251
|
+
}
|
|
13252
|
+
for (const [filepath, content] of Object.entries(scaffoldFiles)) {
|
|
13253
|
+
await writeFile2(filepath, content, "utf-8");
|
|
13254
|
+
}
|
|
13255
|
+
if (!isPg) {
|
|
11994
13256
|
await adapter2.writePhases([{
|
|
11995
13257
|
id: "phase-0",
|
|
11996
13258
|
slug: "setup",
|
|
@@ -11999,9 +13261,9 @@ async function scaffoldPapiDir(adapter2, config2, input) {
|
|
|
11999
13261
|
status: "In Progress",
|
|
12000
13262
|
order: 0
|
|
12001
13263
|
}]);
|
|
12002
|
-
await ensurePapiPermission(config2.projectRoot);
|
|
12003
|
-
return true;
|
|
12004
13264
|
}
|
|
13265
|
+
await ensurePapiPermission(config2.projectRoot);
|
|
13266
|
+
return true;
|
|
12005
13267
|
}
|
|
12006
13268
|
var PAPI_PERMISSION = "mcp__papi__*";
|
|
12007
13269
|
async function ensurePapiPermission(projectRoot) {
|
|
@@ -12745,7 +14007,7 @@ async function describeTask(adapter2, taskId) {
|
|
|
12745
14007
|
}
|
|
12746
14008
|
return { task };
|
|
12747
14009
|
}
|
|
12748
|
-
async function startBuild(adapter2, config2, taskId) {
|
|
14010
|
+
async function startBuild(adapter2, config2, taskId, options = {}) {
|
|
12749
14011
|
const task = await adapter2.getTask(taskId);
|
|
12750
14012
|
if (!task) {
|
|
12751
14013
|
throw new Error(`Task "${taskId}" not found on the Sprint Board.`);
|
|
@@ -12772,7 +14034,9 @@ async function startBuild(adapter2, config2, taskId) {
|
|
|
12772
14034
|
throw err;
|
|
12773
14035
|
}
|
|
12774
14036
|
const branchLines = [];
|
|
12775
|
-
if (
|
|
14037
|
+
if (options.light) {
|
|
14038
|
+
branchLines.push("Light mode: skipping branch creation \u2014 working on current branch.");
|
|
14039
|
+
} else if (config2.autoCommit && isGitAvailable() && isGitRepo(config2.projectRoot)) {
|
|
12776
14040
|
const featureBranch = taskBranchName(taskId);
|
|
12777
14041
|
const currentBranch = getCurrentBranch(config2.projectRoot);
|
|
12778
14042
|
if (currentBranch === featureBranch) {
|
|
@@ -12806,11 +14070,21 @@ async function startBuild(adapter2, config2, taskId) {
|
|
|
12806
14070
|
branchLines.push(
|
|
12807
14071
|
checkout.success ? `Checked out existing branch '${featureBranch}'.` : `Warning: ${checkout.message}`
|
|
12808
14072
|
);
|
|
14073
|
+
if (checkout.success) {
|
|
14074
|
+
branchLines.push(
|
|
14075
|
+
'Note: IDE may report files as "modified by linter" after branch switch \u2014 this is normal git checkout behaviour, not reverted changes. Ignore these notifications.'
|
|
14076
|
+
);
|
|
14077
|
+
}
|
|
12809
14078
|
} else {
|
|
12810
14079
|
const create = createAndCheckoutBranch(config2.projectRoot, featureBranch);
|
|
12811
14080
|
branchLines.push(
|
|
12812
14081
|
create.success ? `Created branch '${featureBranch}'.` : `Warning: ${create.message}`
|
|
12813
14082
|
);
|
|
14083
|
+
if (create.success) {
|
|
14084
|
+
branchLines.push(
|
|
14085
|
+
'Note: IDE may report files as "modified by linter" after branch switch \u2014 this is normal git checkout behaviour, not reverted changes. Ignore these notifications.'
|
|
14086
|
+
);
|
|
14087
|
+
}
|
|
12814
14088
|
}
|
|
12815
14089
|
}
|
|
12816
14090
|
}
|
|
@@ -12824,7 +14098,7 @@ async function startBuild(adapter2, config2, taskId) {
|
|
|
12824
14098
|
}
|
|
12825
14099
|
return { task, branchLines, phaseChanges };
|
|
12826
14100
|
}
|
|
12827
|
-
async function completeBuild(adapter2, config2, taskId, input) {
|
|
14101
|
+
async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
12828
14102
|
const task = await adapter2.getTask(taskId);
|
|
12829
14103
|
if (!task) {
|
|
12830
14104
|
throw new Error(`Task "${taskId}" not found on the Sprint Board.`);
|
|
@@ -12884,9 +14158,13 @@ async function completeBuild(adapter2, config2, taskId, input) {
|
|
|
12884
14158
|
const buildReportSummary = `${capitalizeCompleted(input.completed)}. Effort ${input.effort} vs estimated ${input.estimatedEffort}.${surpriseNote}${issueNote}`;
|
|
12885
14159
|
await adapter2.updateTask(taskId, { buildReport: buildReportSummary });
|
|
12886
14160
|
if (input.completed === "yes") {
|
|
12887
|
-
|
|
14161
|
+
if (options.light) {
|
|
14162
|
+
await adapter2.updateTaskStatus(taskId, "Done");
|
|
14163
|
+
} else {
|
|
14164
|
+
await adapter2.updateTaskStatus(taskId, "In Review");
|
|
14165
|
+
}
|
|
12888
14166
|
}
|
|
12889
|
-
const statusNote = input.completed === "yes" ? `Task "${task.title}" (${taskId}) marked In Review \u2014 ready for your sign-off via \`review_submit\`.` : `Task "${task.title}" (${taskId}) status unchanged (completed: ${input.completed}).`;
|
|
14167
|
+
const statusNote = input.completed === "yes" ? options.light ? `Task "${task.title}" (${taskId}) marked Done (light mode \u2014 no review needed).` : `Task "${task.title}" (${taskId}) marked In Review \u2014 ready for your sign-off via \`review_submit\`.` : `Task "${task.title}" (${taskId}) status unchanged (completed: ${input.completed}).`;
|
|
12890
14168
|
let commitLine;
|
|
12891
14169
|
if (config2.autoCommit) {
|
|
12892
14170
|
commitLine = autoCommit(config2, taskId, task.title);
|
|
@@ -12901,7 +14179,9 @@ async function completeBuild(adapter2, config2, taskId, input) {
|
|
|
12901
14179
|
if (changed.length > 0) report.filesChanged = changed;
|
|
12902
14180
|
}
|
|
12903
14181
|
let prLines = [];
|
|
12904
|
-
if (
|
|
14182
|
+
if (options.light) {
|
|
14183
|
+
prLines.push("Light mode: skipping push and PR creation.");
|
|
14184
|
+
} else if (config2.autoCommit && input.completed === "yes") {
|
|
12905
14185
|
prLines = pushAndCreatePR(config2, taskId, task.title);
|
|
12906
14186
|
}
|
|
12907
14187
|
const allTasks = await adapter2.queryBoard();
|
|
@@ -12976,6 +14256,10 @@ var buildExecuteTool = {
|
|
|
12976
14256
|
type: "string",
|
|
12977
14257
|
description: "The task ID to execute."
|
|
12978
14258
|
},
|
|
14259
|
+
light: {
|
|
14260
|
+
type: "boolean",
|
|
14261
|
+
description: "Light-ceremony mode for XS/S tasks. Skips feature branch creation and PR. Work stays on current branch. Build report is still captured. Default false."
|
|
14262
|
+
},
|
|
12979
14263
|
completed: {
|
|
12980
14264
|
type: "string",
|
|
12981
14265
|
enum: ["yes", "no", "partial"],
|
|
@@ -13130,11 +14414,12 @@ async function handleBuildExecute(adapter2, config2, args) {
|
|
|
13130
14414
|
if (!taskId) {
|
|
13131
14415
|
return errorResponse("task_id is required.");
|
|
13132
14416
|
}
|
|
14417
|
+
const light = args.light === true;
|
|
13133
14418
|
if (hasReportFields(args)) {
|
|
13134
|
-
return handleExecuteComplete(adapter2, config2, taskId, args);
|
|
14419
|
+
return handleExecuteComplete(adapter2, config2, taskId, args, light);
|
|
13135
14420
|
}
|
|
13136
14421
|
try {
|
|
13137
|
-
const result = await startBuild(adapter2, config2, taskId);
|
|
14422
|
+
const result = await startBuild(adapter2, config2, taskId, { light });
|
|
13138
14423
|
const branchInfo = result.branchLines.length > 0 ? result.branchLines.map((l) => `> ${l}`).join("\n") + "\n\n" : "";
|
|
13139
14424
|
const phaseNote = result.phaseChanges.length > 0 ? "\n\n" + result.phaseChanges.map((c) => `Phase auto-updated: ${c.phaseId} ${c.oldStatus} \u2192 ${c.newStatus}`).join("\n") : "";
|
|
13140
14425
|
const header = `${branchInfo}**Executing ${result.task.id}: ${result.task.title}** \u2014 marked In Progress.
|
|
@@ -13167,7 +14452,7 @@ async function handleBuildExecute(adapter2, config2, args) {
|
|
|
13167
14452
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
13168
14453
|
}
|
|
13169
14454
|
}
|
|
13170
|
-
async function handleExecuteComplete(adapter2, config2, taskId, args) {
|
|
14455
|
+
async function handleExecuteComplete(adapter2, config2, taskId, args, light = false) {
|
|
13171
14456
|
const completed = args.completed;
|
|
13172
14457
|
const effort = args.effort;
|
|
13173
14458
|
const estimatedEffort = args.estimated_effort;
|
|
@@ -13214,7 +14499,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args) {
|
|
|
13214
14499
|
type: bi.type ?? "new",
|
|
13215
14500
|
detail: bi.detail ?? ""
|
|
13216
14501
|
}))
|
|
13217
|
-
});
|
|
14502
|
+
}, { light });
|
|
13218
14503
|
return textResponse(formatCompleteResult(result));
|
|
13219
14504
|
} catch (err) {
|
|
13220
14505
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
@@ -13366,6 +14651,131 @@ function resolveCurrentPhase(phases) {
|
|
|
13366
14651
|
const sorted = [...phases].sort((a, b2) => a.order - b2.order);
|
|
13367
14652
|
return sorted[0].label;
|
|
13368
14653
|
}
|
|
14654
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
14655
|
+
"a",
|
|
14656
|
+
"an",
|
|
14657
|
+
"the",
|
|
14658
|
+
"and",
|
|
14659
|
+
"or",
|
|
14660
|
+
"but",
|
|
14661
|
+
"in",
|
|
14662
|
+
"on",
|
|
14663
|
+
"at",
|
|
14664
|
+
"to",
|
|
14665
|
+
"for",
|
|
14666
|
+
"of",
|
|
14667
|
+
"with",
|
|
14668
|
+
"by",
|
|
14669
|
+
"from",
|
|
14670
|
+
"is",
|
|
14671
|
+
"are",
|
|
14672
|
+
"was",
|
|
14673
|
+
"were",
|
|
14674
|
+
"be",
|
|
14675
|
+
"been",
|
|
14676
|
+
"has",
|
|
14677
|
+
"have",
|
|
14678
|
+
"had",
|
|
14679
|
+
"do",
|
|
14680
|
+
"does",
|
|
14681
|
+
"did",
|
|
14682
|
+
"will",
|
|
14683
|
+
"would",
|
|
14684
|
+
"could",
|
|
14685
|
+
"should",
|
|
14686
|
+
"may",
|
|
14687
|
+
"might",
|
|
14688
|
+
"can",
|
|
14689
|
+
"not",
|
|
14690
|
+
"no",
|
|
14691
|
+
"if",
|
|
14692
|
+
"then",
|
|
14693
|
+
"than",
|
|
14694
|
+
"that",
|
|
14695
|
+
"this",
|
|
14696
|
+
"it",
|
|
14697
|
+
"its",
|
|
14698
|
+
"all",
|
|
14699
|
+
"each",
|
|
14700
|
+
"every",
|
|
14701
|
+
"both",
|
|
14702
|
+
"as",
|
|
14703
|
+
"so",
|
|
14704
|
+
"up",
|
|
14705
|
+
"out",
|
|
14706
|
+
"about",
|
|
14707
|
+
"into",
|
|
14708
|
+
"over",
|
|
14709
|
+
"after",
|
|
14710
|
+
"before",
|
|
14711
|
+
"between",
|
|
14712
|
+
"under",
|
|
14713
|
+
"above",
|
|
14714
|
+
"such",
|
|
14715
|
+
"only",
|
|
14716
|
+
"also",
|
|
14717
|
+
"just",
|
|
14718
|
+
"more",
|
|
14719
|
+
"most",
|
|
14720
|
+
"other",
|
|
14721
|
+
"some",
|
|
14722
|
+
"any",
|
|
14723
|
+
"new",
|
|
14724
|
+
"when",
|
|
14725
|
+
"how",
|
|
14726
|
+
"what",
|
|
14727
|
+
"which",
|
|
14728
|
+
"who",
|
|
14729
|
+
"add",
|
|
14730
|
+
"create",
|
|
14731
|
+
"build",
|
|
14732
|
+
"implement",
|
|
14733
|
+
"make",
|
|
14734
|
+
"update",
|
|
14735
|
+
"fix",
|
|
14736
|
+
"use",
|
|
14737
|
+
"via",
|
|
14738
|
+
"show",
|
|
14739
|
+
"display",
|
|
14740
|
+
"view",
|
|
14741
|
+
"page",
|
|
14742
|
+
"data",
|
|
14743
|
+
"based",
|
|
14744
|
+
"using"
|
|
14745
|
+
]);
|
|
14746
|
+
function extractKeywords(text) {
|
|
14747
|
+
return new Set(
|
|
14748
|
+
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w))
|
|
14749
|
+
);
|
|
14750
|
+
}
|
|
14751
|
+
async function findSimilarTasks(adapter2, ideaTitle) {
|
|
14752
|
+
const ideaKeywords = extractKeywords(ideaTitle);
|
|
14753
|
+
if (ideaKeywords.size < 2) return [];
|
|
14754
|
+
const [backlog, done] = await Promise.all([
|
|
14755
|
+
adapter2.queryBoard({ status: ["Backlog", "In Cycle", "In Progress", "In Review"] }),
|
|
14756
|
+
adapter2.queryBoard({ status: ["Done"] })
|
|
14757
|
+
]);
|
|
14758
|
+
const allTasks = [...backlog, ...done];
|
|
14759
|
+
const matches = [];
|
|
14760
|
+
for (const task of allTasks) {
|
|
14761
|
+
const taskKeywords = extractKeywords(task.title);
|
|
14762
|
+
if (taskKeywords.size < 2) continue;
|
|
14763
|
+
let covered = 0;
|
|
14764
|
+
for (const word of ideaKeywords) {
|
|
14765
|
+
if (taskKeywords.has(word)) covered++;
|
|
14766
|
+
}
|
|
14767
|
+
const coverage = covered / ideaKeywords.size;
|
|
14768
|
+
if (coverage >= 0.4) {
|
|
14769
|
+
matches.push({
|
|
14770
|
+
id: task.id,
|
|
14771
|
+
title: task.title,
|
|
14772
|
+
status: task.status,
|
|
14773
|
+
coverage
|
|
14774
|
+
});
|
|
14775
|
+
}
|
|
14776
|
+
}
|
|
14777
|
+
return matches.sort((a, b2) => b2.coverage - a.coverage).slice(0, 3);
|
|
14778
|
+
}
|
|
13369
14779
|
async function captureIdea(adapter2, input) {
|
|
13370
14780
|
if (input.discovery) {
|
|
13371
14781
|
const routing = classifyIdea(input.text, input.notes);
|
|
@@ -13373,6 +14783,37 @@ async function captureIdea(adapter2, input) {
|
|
|
13373
14783
|
return routeToDiscovery(adapter2, routing, input);
|
|
13374
14784
|
}
|
|
13375
14785
|
}
|
|
14786
|
+
let similarWarning = "";
|
|
14787
|
+
try {
|
|
14788
|
+
const similar = await findSimilarTasks(adapter2, input.text);
|
|
14789
|
+
if (similar.length > 0) {
|
|
14790
|
+
const highOverlap = similar.filter((s) => s.coverage >= 0.7);
|
|
14791
|
+
if (highOverlap.length > 0 && !input.force) {
|
|
14792
|
+
const lines = highOverlap.map(
|
|
14793
|
+
(s) => ` - **${s.id}** [${s.status}]: "${s.title}" (${Math.round(s.coverage * 100)}% keyword overlap)`
|
|
14794
|
+
);
|
|
14795
|
+
const doneMatch = highOverlap.find((s) => s.status === "Done");
|
|
14796
|
+
const reason = doneMatch ? `This looks like it was **already done** as ${doneMatch.id}.` : `This looks like a **duplicate** of ${highOverlap[0].id}.`;
|
|
14797
|
+
return {
|
|
14798
|
+
routing: "task",
|
|
14799
|
+
message: `\u26D4 **Blocked \u2014 ${reason}**
|
|
14800
|
+
|
|
14801
|
+
High-overlap tasks:
|
|
14802
|
+
${lines.join("\n")}
|
|
14803
|
+
|
|
14804
|
+
If this is genuinely different, re-run with \`force: true\`.`
|
|
14805
|
+
};
|
|
14806
|
+
}
|
|
14807
|
+
const warnLines = similar.map(
|
|
14808
|
+
(s) => ` - **${s.id}** [${s.status}]: "${s.title}" (${Math.round(s.coverage * 100)}% keyword overlap)`
|
|
14809
|
+
);
|
|
14810
|
+
similarWarning = `
|
|
14811
|
+
|
|
14812
|
+
\u26A0\uFE0F **Similar tasks found** \u2014 check before scheduling:
|
|
14813
|
+
${warnLines.join("\n")}`;
|
|
14814
|
+
}
|
|
14815
|
+
} catch {
|
|
14816
|
+
}
|
|
13376
14817
|
const [health, phases] = await Promise.all([
|
|
13377
14818
|
adapter2.getSprintHealth(),
|
|
13378
14819
|
adapter2.readPhases()
|
|
@@ -13396,7 +14837,7 @@ async function captureIdea(adapter2, input) {
|
|
|
13396
14837
|
taskType: "idea",
|
|
13397
14838
|
maturity: "raw"
|
|
13398
14839
|
});
|
|
13399
|
-
return { routing: "task", task, message: `${task.id}: "${task.title}" \u2014 added to backlog` };
|
|
14840
|
+
return { routing: "task", task, message: `${task.id}: "${task.title}" \u2014 added to backlog${similarWarning}` };
|
|
13400
14841
|
}
|
|
13401
14842
|
var CANVAS_SECTION_LABELS = {
|
|
13402
14843
|
landscape: "Landscape References",
|
|
@@ -13463,6 +14904,10 @@ var ideaTool = {
|
|
|
13463
14904
|
discovery: {
|
|
13464
14905
|
type: "boolean",
|
|
13465
14906
|
description: "When true, classify the idea and route to Discovery Canvas instead of backlog. Default: false (always creates a backlog task)."
|
|
14907
|
+
},
|
|
14908
|
+
force: {
|
|
14909
|
+
type: "boolean",
|
|
14910
|
+
description: "Force creation even if a high-overlap duplicate or already-done task is detected. Default: false."
|
|
13466
14911
|
}
|
|
13467
14912
|
},
|
|
13468
14913
|
required: ["text"]
|
|
@@ -13486,7 +14931,8 @@ async function handleIdea(adapter2, config2, args) {
|
|
|
13486
14931
|
epic: args.epic,
|
|
13487
14932
|
phase: args.phase,
|
|
13488
14933
|
notes: rawNotes,
|
|
13489
|
-
discovery: args.discovery === true
|
|
14934
|
+
discovery: args.discovery === true,
|
|
14935
|
+
force: args.force === true
|
|
13490
14936
|
};
|
|
13491
14937
|
const useGit = isGitAvailable() && isGitRepo(config2.projectRoot);
|
|
13492
14938
|
const currentBranch = useGit ? getCurrentBranch(config2.projectRoot) : null;
|
|
@@ -13860,6 +15306,40 @@ async function prepareReconcile(adapter2) {
|
|
|
13860
15306
|
}
|
|
13861
15307
|
lines.push("");
|
|
13862
15308
|
}
|
|
15309
|
+
const doneTasks = await adapter2.queryBoard({ status: ["Done"] });
|
|
15310
|
+
const doneOverlaps = [];
|
|
15311
|
+
for (const bt of allTasks) {
|
|
15312
|
+
const bWords = titleKeywords(bt.title);
|
|
15313
|
+
if (bWords.size < 3) continue;
|
|
15314
|
+
for (const dt of doneTasks) {
|
|
15315
|
+
const dWords = titleKeywords(dt.title);
|
|
15316
|
+
if (dWords.size < 3) continue;
|
|
15317
|
+
let covered = 0;
|
|
15318
|
+
for (const w of bWords) {
|
|
15319
|
+
if (dWords.has(w)) covered++;
|
|
15320
|
+
}
|
|
15321
|
+
const coverage = covered / Math.min(bWords.size, dWords.size);
|
|
15322
|
+
if (coverage >= 0.6) {
|
|
15323
|
+
doneOverlaps.push({ backlog: bt, done: dt, coverage });
|
|
15324
|
+
}
|
|
15325
|
+
}
|
|
15326
|
+
}
|
|
15327
|
+
if (doneOverlaps.length > 0) {
|
|
15328
|
+
const bestMatches = /* @__PURE__ */ new Map();
|
|
15329
|
+
for (const { backlog, done, coverage } of doneOverlaps) {
|
|
15330
|
+
const existing = bestMatches.get(backlog.id);
|
|
15331
|
+
if (!existing || coverage > existing.coverage) {
|
|
15332
|
+
bestMatches.set(backlog.id, { done, coverage });
|
|
15333
|
+
}
|
|
15334
|
+
}
|
|
15335
|
+
lines.push("### Already Done? (backlog tasks overlapping with completed work)");
|
|
15336
|
+
lines.push("These backlog tasks may duplicate work already shipped. **Cancel unless the scope is genuinely different.**");
|
|
15337
|
+
for (const [backlogId, { done, coverage }] of bestMatches) {
|
|
15338
|
+
const bt = allTasks.find((t) => t.id === backlogId);
|
|
15339
|
+
lines.push(`- **${bt.id}** "${bt.title.slice(0, 60)}" \u2190 already done as **${done.id}** "${done.title.slice(0, 60)}" (${Math.round(coverage * 100)}% overlap)`);
|
|
15340
|
+
}
|
|
15341
|
+
lines.push("");
|
|
15342
|
+
}
|
|
13863
15343
|
lines.push("### All Backlog Tasks by Module");
|
|
13864
15344
|
lines.push("");
|
|
13865
15345
|
for (const [mod, tasks] of byModule) {
|
|
@@ -13873,11 +15353,16 @@ async function prepareReconcile(adapter2) {
|
|
|
13873
15353
|
}
|
|
13874
15354
|
return lines.join("\n");
|
|
13875
15355
|
}
|
|
15356
|
+
var STOP_WORDS2 = /* @__PURE__ */ new Set(["the", "a", "an", "and", "or", "for", "in", "on", "to", "of", "is", "with", "from", "by", "vs", "not", "no", "do"]);
|
|
15357
|
+
function tokenize(s) {
|
|
15358
|
+
return s.toLowerCase().replace(/[^a-z0-9\s-]/g, "").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS2.has(w));
|
|
15359
|
+
}
|
|
15360
|
+
function titleKeywords(title) {
|
|
15361
|
+
return new Set(tokenize(title));
|
|
15362
|
+
}
|
|
13876
15363
|
function titleKeywordOverlap(a, b2) {
|
|
13877
|
-
const
|
|
13878
|
-
const
|
|
13879
|
-
const wordsA = new Set(tokenize(a));
|
|
13880
|
-
const wordsB = new Set(tokenize(b2));
|
|
15364
|
+
const wordsA = titleKeywords(a);
|
|
15365
|
+
const wordsB = titleKeywords(b2);
|
|
13881
15366
|
return [...wordsA].filter((w) => wordsB.has(w)).length;
|
|
13882
15367
|
}
|
|
13883
15368
|
async function applyReconcile(adapter2, corrections) {
|
|
@@ -13938,7 +15423,17 @@ async function applyReconcile(adapter2, corrections) {
|
|
|
13938
15423
|
skipped++;
|
|
13939
15424
|
}
|
|
13940
15425
|
}
|
|
13941
|
-
|
|
15426
|
+
let phaseChanges = [];
|
|
15427
|
+
if (applied > 0) {
|
|
15428
|
+
try {
|
|
15429
|
+
phaseChanges = await propagatePhaseStatus(adapter2);
|
|
15430
|
+
for (const c of phaseChanges) {
|
|
15431
|
+
details.push(`Phase "${c.phaseId}": ${c.oldStatus} \u2192 ${c.newStatus}`);
|
|
15432
|
+
}
|
|
15433
|
+
} catch {
|
|
15434
|
+
}
|
|
15435
|
+
}
|
|
15436
|
+
return { applied, skipped, details, phaseChanges };
|
|
13942
15437
|
}
|
|
13943
15438
|
|
|
13944
15439
|
// src/tools/board-reconcile.ts
|
|
@@ -14555,9 +16050,10 @@ async function listPendingReviews(adapter2) {
|
|
|
14555
16050
|
const pendingBuilds = tasks.filter((t) => t.status === "In Review");
|
|
14556
16051
|
return { pendingBuilds };
|
|
14557
16052
|
}
|
|
14558
|
-
function prepareHandoffRegen(task, reviewerComments) {
|
|
16053
|
+
async function prepareHandoffRegen(task, reviewerComments) {
|
|
16054
|
+
const systemPrompt = await getPrompt("handoff-regen-system");
|
|
14559
16055
|
return {
|
|
14560
|
-
systemPrompt
|
|
16056
|
+
systemPrompt,
|
|
14561
16057
|
userMessage: buildHandoffRegenMessage({
|
|
14562
16058
|
existingHandoff: task.buildHandoff ? serializeBuildHandoff(task.buildHandoff) : "",
|
|
14563
16059
|
reviewerComments,
|
|
@@ -14610,6 +16106,9 @@ async function submitReview(adapter2, input) {
|
|
|
14610
16106
|
review.buildCommitSha = taskReport.commitSha;
|
|
14611
16107
|
}
|
|
14612
16108
|
}
|
|
16109
|
+
if (input.autoReview) {
|
|
16110
|
+
review.autoReview = input.autoReview;
|
|
16111
|
+
}
|
|
14613
16112
|
await adapter2.writeReview(review);
|
|
14614
16113
|
const newStatus = resolveStatus(input.stage, input.verdict);
|
|
14615
16114
|
if (newStatus) {
|
|
@@ -14635,7 +16134,7 @@ async function submitReview(adapter2, input) {
|
|
|
14635
16134
|
const handoffRegenerated = false;
|
|
14636
16135
|
let handoffRegenPrompt;
|
|
14637
16136
|
if (input.stage === "handoff-review" && input.verdict === "request-changes" && task.buildHandoff) {
|
|
14638
|
-
handoffRegenPrompt = prepareHandoffRegen(task, input.comments);
|
|
16137
|
+
handoffRegenPrompt = await prepareHandoffRegen(task, input.comments);
|
|
14639
16138
|
}
|
|
14640
16139
|
const stageLabel = input.stage === "handoff-review" ? "Handoff Review" : "Build Acceptance";
|
|
14641
16140
|
const slackWarning = void 0;
|
|
@@ -14706,6 +16205,29 @@ var reviewSubmitTool = {
|
|
|
14706
16205
|
notify: {
|
|
14707
16206
|
type: "boolean",
|
|
14708
16207
|
description: "Send Slack notification. Default true. Set false for batch middle reviews to avoid spam."
|
|
16208
|
+
},
|
|
16209
|
+
auto_review: {
|
|
16210
|
+
type: "object",
|
|
16211
|
+
description: "Optional automated code review results to attach to this review. Run PR analysis first, then pass findings here.",
|
|
16212
|
+
properties: {
|
|
16213
|
+
verdict: { type: "string", enum: ["pass", "warn", "fail"], description: "Auto-review verdict." },
|
|
16214
|
+
summary: { type: "string", description: "One-line summary of auto-review findings." },
|
|
16215
|
+
findings: {
|
|
16216
|
+
type: "array",
|
|
16217
|
+
description: "Structured findings from automated review.",
|
|
16218
|
+
items: {
|
|
16219
|
+
type: "object",
|
|
16220
|
+
properties: {
|
|
16221
|
+
severity: { type: "string", enum: ["error", "warning", "info"] },
|
|
16222
|
+
file: { type: "string", description: "File path (optional)." },
|
|
16223
|
+
line: { type: "integer", description: "Line number (optional)." },
|
|
16224
|
+
message: { type: "string", description: "Finding description." }
|
|
16225
|
+
},
|
|
16226
|
+
required: ["severity", "message"]
|
|
16227
|
+
}
|
|
16228
|
+
}
|
|
16229
|
+
},
|
|
16230
|
+
required: ["verdict", "summary", "findings"]
|
|
14709
16231
|
}
|
|
14710
16232
|
},
|
|
14711
16233
|
required: ["task_id", "stage", "verdict", "comments"]
|
|
@@ -14784,6 +16306,20 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
14784
16306
|
const comments = args.comments;
|
|
14785
16307
|
const reviewer = args.reviewer ?? "human";
|
|
14786
16308
|
const notify = args.notify !== false;
|
|
16309
|
+
const rawAutoReview = args.auto_review;
|
|
16310
|
+
let autoReview;
|
|
16311
|
+
if (rawAutoReview?.verdict && rawAutoReview?.summary && Array.isArray(rawAutoReview?.findings)) {
|
|
16312
|
+
autoReview = {
|
|
16313
|
+
verdict: rawAutoReview.verdict,
|
|
16314
|
+
summary: rawAutoReview.summary,
|
|
16315
|
+
findings: rawAutoReview.findings.map((f) => ({
|
|
16316
|
+
severity: f.severity ?? "info",
|
|
16317
|
+
...f.file ? { file: f.file } : {},
|
|
16318
|
+
...f.line ? { line: f.line } : {},
|
|
16319
|
+
message: f.message ?? ""
|
|
16320
|
+
}))
|
|
16321
|
+
};
|
|
16322
|
+
}
|
|
14787
16323
|
if (!taskId) {
|
|
14788
16324
|
return errorResponse("task_id is required.");
|
|
14789
16325
|
}
|
|
@@ -14812,7 +16348,7 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
14812
16348
|
try {
|
|
14813
16349
|
const result = await submitReview(
|
|
14814
16350
|
adapter2,
|
|
14815
|
-
{ taskId, stage, verdict, comments, reviewer, notify }
|
|
16351
|
+
{ taskId, stage, verdict, comments, reviewer, notify, autoReview }
|
|
14816
16352
|
);
|
|
14817
16353
|
const statusNote = result.newStatus ? ` Task status updated to **${result.newStatus}**.` : " Task status unchanged.";
|
|
14818
16354
|
const unblockNote = result.unblockedTasks.length > 0 ? `
|
|
@@ -14879,13 +16415,16 @@ Run \`plan\` to create Cycle ${result.currentSprint + 1}.`;
|
|
|
14879
16415
|
}
|
|
14880
16416
|
}
|
|
14881
16417
|
const phaseNote = result.phaseChanges.length > 0 ? "\n\n" + result.phaseChanges.map((c) => `Phase auto-updated: ${c.phaseId} ${c.oldStatus} \u2192 ${c.newStatus}`).join("\n") : "";
|
|
16418
|
+
const autoReviewNote = autoReview ? `
|
|
16419
|
+
|
|
16420
|
+
**Auto-Review:** ${autoReview.verdict} \u2014 ${autoReview.summary}` + (autoReview.findings.length > 0 ? ` (${autoReview.findings.length} finding${autoReview.findings.length === 1 ? "" : "s"})` : "") : "";
|
|
14882
16421
|
return textResponse(
|
|
14883
16422
|
`**${result.stageLabel}** recorded for ${result.taskId}.
|
|
14884
16423
|
|
|
14885
16424
|
- **Verdict:** ${result.verdict}
|
|
14886
16425
|
- **Comments:** ${result.comments}
|
|
14887
16426
|
|
|
14888
|
-
${statusNote}${unblockNote}${regenNote}${mergeNote}${slackNote}${autoReleaseNote}${phaseNote}`
|
|
16427
|
+
${statusNote}${autoReviewNote}${unblockNote}${regenNote}${mergeNote}${slackNote}${autoReleaseNote}${phaseNote}`
|
|
14889
16428
|
);
|
|
14890
16429
|
} catch (err) {
|
|
14891
16430
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
@@ -15275,6 +16814,20 @@ async function handleHierarchyUpdate(adapter2, args) {
|
|
|
15275
16814
|
}
|
|
15276
16815
|
|
|
15277
16816
|
// src/services/zoom-out.ts
|
|
16817
|
+
var BUDGET_SOFT = 12e4;
|
|
16818
|
+
var BUDGET_HARD = 15e4;
|
|
16819
|
+
function measureSections(secs) {
|
|
16820
|
+
return secs.reduce((sum, s) => sum + s.length, 0);
|
|
16821
|
+
}
|
|
16822
|
+
function summariseBuildReports(reports, count) {
|
|
16823
|
+
const recent = reports.slice(0, count);
|
|
16824
|
+
if (recent.length === 0) return "No recent build reports.";
|
|
16825
|
+
return recent.map((r) => {
|
|
16826
|
+
const effort = `${r.actualEffort} vs ${r.estimatedEffort}`;
|
|
16827
|
+
const surprises = r.surprises && r.surprises !== "None" ? ` \u2014 ${r.surprises.slice(0, 80)}${r.surprises.length > 80 ? "..." : ""}` : "";
|
|
16828
|
+
return `- S${r.sprint} ${r.taskName}: ${effort}${surprises}`;
|
|
16829
|
+
}).join("\n");
|
|
16830
|
+
}
|
|
15278
16831
|
async function assembleZoomOutContext(adapter2, sprintNumber, projectRoot) {
|
|
15279
16832
|
const [
|
|
15280
16833
|
productBrief,
|
|
@@ -15288,12 +16841,15 @@ async function assembleZoomOutContext(adapter2, sprintNumber, projectRoot) {
|
|
|
15288
16841
|
] = await Promise.all([
|
|
15289
16842
|
adapter2.readProductBrief(),
|
|
15290
16843
|
adapter2.getActiveDecisions(),
|
|
15291
|
-
adapter2.getRecentBuildReports(
|
|
15292
|
-
|
|
16844
|
+
adapter2.getRecentBuildReports(50),
|
|
16845
|
+
// Cap: 50 (was 100)
|
|
16846
|
+
adapter2.getSprintLog(30),
|
|
16847
|
+
// Cap: 30 (was 50)
|
|
15293
16848
|
adapter2.queryBoard({
|
|
15294
16849
|
status: ["Backlog", "In Cycle", "In Progress", "In Review", "Blocked", "Deferred"]
|
|
15295
16850
|
}),
|
|
15296
|
-
adapter2.getStrategyReviews(
|
|
16851
|
+
adapter2.getStrategyReviews(5, true),
|
|
16852
|
+
// Cap: 5 (was 10)
|
|
15297
16853
|
readDogfoodEntries(projectRoot, 20, adapter2),
|
|
15298
16854
|
adapter2.getCurrentNorthStar?.() ?? Promise.resolve(null)
|
|
15299
16855
|
]);
|
|
@@ -15333,7 +16889,8 @@ async function assembleZoomOutContext(adapter2, sprintNumber, projectRoot) {
|
|
|
15333
16889
|
}
|
|
15334
16890
|
let adLifecycleText = "";
|
|
15335
16891
|
try {
|
|
15336
|
-
const
|
|
16892
|
+
const sinceSprintNumber = Math.max(1, sprintNumber - 20);
|
|
16893
|
+
const events = await adapter2.getDecisionEventsSince(sinceSprintNumber);
|
|
15337
16894
|
if (events.length > 0) {
|
|
15338
16895
|
const byDecision = /* @__PURE__ */ new Map();
|
|
15339
16896
|
for (const e of events) {
|
|
@@ -15351,14 +16908,30 @@ async function assembleZoomOutContext(adapter2, sprintNumber, projectRoot) {
|
|
|
15351
16908
|
}
|
|
15352
16909
|
let reviewsText = "";
|
|
15353
16910
|
if (strategyReviews.length > 0) {
|
|
15354
|
-
const
|
|
16911
|
+
const fullReviews = strategyReviews.slice(0, 3).map((r) => {
|
|
15355
16912
|
const summary = r.fullAnalysis ? r.fullAnalysis.slice(0, 500) + (r.fullAnalysis.length > 500 ? "..." : "") : r.content;
|
|
15356
16913
|
return `### Cycle ${r.sprintNumber}${r.sprintRange ? ` (${r.sprintRange})` : ""}
|
|
15357
16914
|
${summary}`;
|
|
15358
16915
|
});
|
|
15359
|
-
|
|
16916
|
+
const headerReviews = strategyReviews.slice(3).map((r) => {
|
|
16917
|
+
const range = r.sprintRange ? `Cycles ${r.sprintRange}` : `Cycle ${r.sprintNumber}`;
|
|
16918
|
+
return `#### ${range} \u2014 ${r.title}
|
|
16919
|
+
**Direction:** ${r.strategicDirection ?? "N/A"}`;
|
|
16920
|
+
});
|
|
16921
|
+
reviewsText = [...fullReviews, ...headerReviews].join("\n\n");
|
|
16922
|
+
}
|
|
16923
|
+
let doneTasksSummary = "";
|
|
16924
|
+
try {
|
|
16925
|
+
const doneTasks = await adapter2.queryBoard({ status: ["Done", "Cancelled"] });
|
|
16926
|
+
if (doneTasks.length > 0) {
|
|
16927
|
+
const doneCount = doneTasks.filter((t) => t.status === "Done").length;
|
|
16928
|
+
const cancelledCount = doneTasks.filter((t) => t.status === "Cancelled").length;
|
|
16929
|
+
doneTasksSummary = `**Completed:** ${doneCount} Done, ${cancelledCount} Cancelled`;
|
|
16930
|
+
}
|
|
16931
|
+
} catch {
|
|
15360
16932
|
}
|
|
15361
16933
|
const sections = [];
|
|
16934
|
+
const compressionSteps = [];
|
|
15362
16935
|
sections.push(`## Project State \u2014 Cycle ${sprintNumber}`);
|
|
15363
16936
|
if (northStar) sections.push(`**North Star:** ${northStar}`);
|
|
15364
16937
|
if (productBrief) {
|
|
@@ -15371,54 +16944,101 @@ ${summary}`;
|
|
|
15371
16944
|
sections.push("## Active Decisions\n" + formatActiveDecisionsForReview(decisions));
|
|
15372
16945
|
}
|
|
15373
16946
|
if (adLifecycleText) {
|
|
15374
|
-
sections.push(
|
|
16947
|
+
sections.push(`## AD Lifecycle Events (last 20 cycles)
|
|
16948
|
+
${adLifecycleText}`);
|
|
15375
16949
|
}
|
|
15376
16950
|
if (reviewsText) {
|
|
15377
16951
|
sections.push("## Strategy Review History\n" + reviewsText);
|
|
15378
16952
|
}
|
|
15379
16953
|
if (log.length > 0) {
|
|
15380
|
-
sections.push("## Sprint Log (recent
|
|
16954
|
+
sections.push("## Sprint Log (recent 30)\n" + formatSprintLog(log));
|
|
15381
16955
|
}
|
|
15382
|
-
|
|
15383
|
-
|
|
16956
|
+
let buildReportsText = reports.length > 0 ? formatBuildReports(reports) : "";
|
|
16957
|
+
if (buildReportsText) {
|
|
16958
|
+
sections.push("## Build Reports (recent 50)\n" + buildReportsText);
|
|
15384
16959
|
}
|
|
15385
16960
|
if (activeTasks.length > 0) {
|
|
15386
16961
|
sections.push("## Current Board\n" + formatBoardForReview(activeTasks));
|
|
15387
16962
|
}
|
|
16963
|
+
if (doneTasksSummary) {
|
|
16964
|
+
sections.push("## Historical Task Counts\n" + doneTasksSummary);
|
|
16965
|
+
}
|
|
15388
16966
|
if (metricsText) {
|
|
15389
16967
|
sections.push("## Velocity & Estimation Trends\n" + metricsText);
|
|
15390
16968
|
}
|
|
15391
16969
|
if (dogfoodLog) {
|
|
15392
16970
|
sections.push("## Dogfood Log (recent 20)\n" + dogfoodLog);
|
|
15393
16971
|
}
|
|
16972
|
+
let contextSize = measureSections(sections);
|
|
16973
|
+
if (contextSize > BUDGET_SOFT && buildReportsText) {
|
|
16974
|
+
const summaryText = summariseBuildReports(reports, 30);
|
|
16975
|
+
const idx = sections.findIndex((s) => s.startsWith("## Build Reports"));
|
|
16976
|
+
if (idx >= 0) {
|
|
16977
|
+
sections[idx] = "## Build Reports (summarised, recent 30)\n" + summaryText;
|
|
16978
|
+
contextSize = measureSections(sections);
|
|
16979
|
+
compressionSteps.push("Step 1: build reports summarised");
|
|
16980
|
+
}
|
|
16981
|
+
}
|
|
16982
|
+
if (contextSize > BUDGET_SOFT) {
|
|
16983
|
+
const logIdx = sections.findIndex((s) => s.startsWith("## Sprint Log"));
|
|
16984
|
+
if (logIdx >= 0 && log.length > 15) {
|
|
16985
|
+
sections[logIdx] = "## Sprint Log (recent 15)\n" + formatSprintLog(log.slice(0, 15));
|
|
16986
|
+
contextSize = measureSections(sections);
|
|
16987
|
+
compressionSteps.push("Step 2: sprint log trimmed to 15");
|
|
16988
|
+
}
|
|
16989
|
+
}
|
|
16990
|
+
if (contextSize > BUDGET_HARD) {
|
|
16991
|
+
const longest = sections.map((s, i) => ({ i, len: s.length })).sort((a, b2) => b2.len - a.len)[0];
|
|
16992
|
+
if (longest) {
|
|
16993
|
+
const excess = contextSize - BUDGET_SOFT;
|
|
16994
|
+
const newLen = Math.max(longest.len - excess, 1e3);
|
|
16995
|
+
sections[longest.i] = sections[longest.i].slice(0, newLen) + "\n\n[truncated \u2014 context budget exceeded]";
|
|
16996
|
+
contextSize = measureSections(sections);
|
|
16997
|
+
compressionSteps.push(`Step 3: truncated section ${longest.i} (was ${longest.len} chars)`);
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
const estimatedTokens = Math.ceil(contextSize / 4);
|
|
17001
|
+
if (compressionSteps.length > 0) {
|
|
17002
|
+
console.error(`[zoom_out] Context compressed: ${compressionSteps.join(" \u2192 ")}`);
|
|
17003
|
+
}
|
|
17004
|
+
console.error(`[zoom_out] Context size: ${contextSize.toLocaleString()} chars (~${estimatedTokens.toLocaleString()} tokens)`);
|
|
15394
17005
|
return sections.join("\n\n");
|
|
15395
17006
|
}
|
|
15396
|
-
var ZOOM_OUT_SYSTEM_PROMPT = `You are the PAPI Zoom-Out Analyst \u2014 a meta-retrospective engine that operates above individual strategy reviews.
|
|
17007
|
+
var ZOOM_OUT_SYSTEM_PROMPT = `You are the PAPI Zoom-Out Analyst \u2014 a strategic meta-retrospective engine that operates above individual strategy reviews.
|
|
15397
17008
|
|
|
15398
|
-
Strategy reviews look at 5-cycle windows. You look at the
|
|
17009
|
+
Strategy reviews look at 5-cycle windows. You look at the full project arc \u2014 horizons, stages, decisions, pivots \u2014 and surface strategic patterns invisible at the sprint level. Focus on WHERE the project is going, not WHAT it recently did.
|
|
15399
17010
|
|
|
15400
17011
|
IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions or wait for confirmation. Be decisive and specific.
|
|
15401
17012
|
|
|
15402
17013
|
## OUTPUT FORMAT
|
|
15403
17014
|
|
|
15404
|
-
Produce a
|
|
17015
|
+
Produce a strategic retrospective with these sections:
|
|
17016
|
+
|
|
17017
|
+
### 1. Strategic Position
|
|
17018
|
+
Where is the project on its horizon/stage map? What stage gates have been passed and which are approaching? What has changed about the product's strategic position since the last zoom-out? How does the current trajectory compare to the North Star? 2-3 paragraphs, not a project history.
|
|
17019
|
+
|
|
17020
|
+
### 2. Hard Questions
|
|
17021
|
+
5-7 questions the project must answer to advance to the next stage. These should be questions that strategy reviews are too narrow to surface \u2014 they require the full arc. For each question: state it, explain why it matters now, and what the cost of deferring it is.
|
|
15405
17022
|
|
|
15406
|
-
###
|
|
15407
|
-
|
|
17023
|
+
### 3. Horizon Assessment
|
|
17024
|
+
For each active horizon and stage in the hierarchy: what's the honest completion status? Are stages progressing or stalled? Are phases within stages actually delivering toward the stage goal, or is work scattered? Identify the 1-2 stages that are most at risk.
|
|
15408
17025
|
|
|
15409
|
-
###
|
|
15410
|
-
What has the project consistently NOT
|
|
17026
|
+
### 4. Blind Spots & Deferred Patterns
|
|
17027
|
+
What has the project consistently NOT addressed? Patterns of avoidance, questions raised but never answered, tasks deferred 3+ times. These compound invisibly. Focus on strategic blind spots (market, users, competition) not operational ones (code quality, test coverage).
|
|
15411
17028
|
|
|
15412
|
-
###
|
|
15413
|
-
|
|
17029
|
+
### 5. Opportunities
|
|
17030
|
+
Capabilities built but underutilised. Success patterns that could be amplified. Strategic positions that could be strengthened. What would a board advisor say the project should double down on?
|
|
15414
17031
|
|
|
15415
|
-
###
|
|
15416
|
-
|
|
17032
|
+
### 6. Active Decision Health
|
|
17033
|
+
Audit every AD provided in context. For each, assess:
|
|
17034
|
+
- **Still relevant?** Does the decision actively guide current work, or is it stale/resolved?
|
|
17035
|
+
- **Correct confidence?** Does the evidence support the stated confidence level?
|
|
17036
|
+
- **Missing ADs?** Are there strategic conclusions from recent cycles that should be captured as new ADs but aren't?
|
|
17037
|
+
- **Wrong scope?** Are any ADs actually implementation details, config preferences, or process choices rather than product/architecture decisions?
|
|
15417
17038
|
|
|
15418
|
-
|
|
15419
|
-
Where is the methodology itself failing? Are strategy reviews catching real issues? Are build reports accurate? Is the planning cycle producing the right tasks? Are decisions being made at the right time? Meta-level critique of how PAPI is being used on this project.
|
|
17039
|
+
Output a table: AD ID | Title | Verdict (Keep/Update/Supersede) | Reason. Then list any recommended new ADs with a one-line rationale.
|
|
15420
17040
|
|
|
15421
|
-
###
|
|
17041
|
+
### 7. Recommendations
|
|
15422
17042
|
3-5 specific, actionable recommendations. Each must include:
|
|
15423
17043
|
- **What:** The specific action
|
|
15424
17044
|
- **Why:** What evidence from the full history supports this
|
|
@@ -15428,9 +17048,9 @@ Where is the methodology itself failing? Are strategy reviews catching real issu
|
|
|
15428
17048
|
## PRINCIPLES
|
|
15429
17049
|
- Back every claim with specific cycle numbers, task IDs, or AD references.
|
|
15430
17050
|
- Compare early project decisions with current reality \u2014 did assumptions hold?
|
|
15431
|
-
-
|
|
17051
|
+
- Focus on stages and horizons, not individual tasks or sprints.
|
|
15432
17052
|
- Identify the 2-3 decisions that had the most outsized impact (positive or negative).
|
|
15433
|
-
- Be brutally honest. This is a
|
|
17053
|
+
- Be brutally honest. This is a strategic assessment, not a progress report.`;
|
|
15434
17054
|
async function prepareZoomOut(adapter2, projectRoot) {
|
|
15435
17055
|
const health = await adapter2.getSprintHealth();
|
|
15436
17056
|
const sprintNumber = health.totalSprints;
|