@papi-ai/server 0.7.13 → 0.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,7 +10,12 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // ../shared/dist/index.js
13
- var VALID_TRANSITIONS;
13
+ function isLiveDecision(d) {
14
+ if (d.superseded === true) return false;
15
+ if (d.outcome != null && RETIRED_DECISION_OUTCOMES.includes(d.outcome)) return false;
16
+ return true;
17
+ }
18
+ var VALID_TRANSITIONS, RETIRED_DECISION_OUTCOMES;
14
19
  var init_dist = __esm({
15
20
  "../shared/dist/index.js"() {
16
21
  "use strict";
@@ -25,6 +30,7 @@ var init_dist = __esm({
25
30
  "Cancelled": [],
26
31
  "Deferred": ["Backlog", "Cancelled"]
27
32
  };
33
+ RETIRED_DECISION_OUTCOMES = ["resolved", "abandoned", "superseded"];
28
34
  }
29
35
  });
30
36
 
@@ -1441,12 +1447,13 @@ async function detectReviewPatterns(reviews, currentCycle, window = 5, clusterer
1441
1447
  function hasReviewPatterns(patterns) {
1442
1448
  return patterns.recurringFeedback.length > 0 || patterns.requestChangesRate >= 50;
1443
1449
  }
1444
- var VALID_TRANSITIONS2, TASK_TYPE_TIERS, VALID_EFFORT_SIZES, SECTION_HEADERS, YAML_MARKER, YAML_START, YAML_END, VALID_EFFORT_SIZES2, HEADER_SENTINEL, TABLE_HEADER, TABLE_SEPARATOR, PREV_TABLE_HEADER, LEGACY_TABLE_HEADER, SECTION_HEADING, FILE_TEMPLATE, COST_SECTION_HEADING, COST_TABLE_SEPARATOR, FILE_HEADING, ACCURACY_HEADER, ACCURACY_SEPARATOR, VELOCITY_HEADER, VELOCITY_SEPARATOR, EFFORT_SCALE, NONE_PATTERN, HEADER_SENTINEL2, VALID_STAGES, VALID_VERDICTS, STAGE_DISPLAY, VALID_STATUSES, PHASES_START, PHASES_END, YAML_MARKER2, YAML_START2, YAML_END2, VALID_STATUSES2, YAML_MARKER3, YAML_START3, YAML_END3, MdFileAdapter, NONE_PATTERN2;
1450
+ var VALID_TRANSITIONS2, isLiveDecision2, TASK_TYPE_TIERS, VALID_EFFORT_SIZES, SECTION_HEADERS, YAML_MARKER, YAML_START, YAML_END, VALID_EFFORT_SIZES2, HEADER_SENTINEL, TABLE_HEADER, TABLE_SEPARATOR, PREV_TABLE_HEADER, LEGACY_TABLE_HEADER, SECTION_HEADING, FILE_TEMPLATE, COST_SECTION_HEADING, COST_TABLE_SEPARATOR, FILE_HEADING, ACCURACY_HEADER, ACCURACY_SEPARATOR, VELOCITY_HEADER, VELOCITY_SEPARATOR, EFFORT_SCALE, NONE_PATTERN, HEADER_SENTINEL2, VALID_STAGES, VALID_VERDICTS, STAGE_DISPLAY, VALID_STATUSES, PHASES_START, PHASES_END, YAML_MARKER2, YAML_START2, YAML_END2, VALID_STATUSES2, YAML_MARKER3, YAML_START3, YAML_END3, MdFileAdapter, NONE_PATTERN2;
1445
1451
  var init_dist2 = __esm({
1446
1452
  "../adapter-md/dist/index.js"() {
1447
1453
  "use strict";
1448
1454
  init_dist();
1449
1455
  VALID_TRANSITIONS2 = VALID_TRANSITIONS;
1456
+ isLiveDecision2 = isLiveDecision;
1450
1457
  TASK_TYPE_TIERS = {
1451
1458
  bug: 1,
1452
1459
  task: 1,
@@ -1557,11 +1564,18 @@ ${TABLE_SEPARATOR}
1557
1564
  async getCycleHealth() {
1558
1565
  return parseCycleHealth(await this.read("PLANNING_LOG.md"));
1559
1566
  }
1560
- /** Read all Active Decisions from ACTIVE_DECISIONS.md. */
1561
- async getActiveDecisions() {
1567
+ /**
1568
+ * Read Active Decisions from ACTIVE_DECISIONS.md.
1569
+ *
1570
+ * Default filters out retired ADs (outcome ∈ abandoned/superseded/resolved or superseded=true).
1571
+ * Pass { includeRetired: true } for management/triage surfaces. See PapiAdapter docstring.
1572
+ */
1573
+ async getActiveDecisions(options) {
1562
1574
  const content = await this.readOptional("ACTIVE_DECISIONS.md");
1563
1575
  if (!content) return [];
1564
- return parseActiveDecisions(content);
1576
+ const all = parseActiveDecisions(content);
1577
+ if (options?.includeRetired) return all;
1578
+ return all.filter(isLiveDecision2);
1565
1579
  }
1566
1580
  /** Read cycle log entries (newest first), optionally limited to {@link limit} entries. */
1567
1581
  async getCycleLog(limit) {
@@ -6352,11 +6366,31 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
6352
6366
  lastFullMode: 0
6353
6367
  };
6354
6368
  }
6355
- async getActiveDecisions() {
6369
+ /**
6370
+ * Read Active Decisions for this project.
6371
+ *
6372
+ * Default filters out retired ADs (outcome ∈ abandoned/superseded/resolved or superseded=true).
6373
+ * Pass { includeRetired: true } for management/triage surfaces. See PapiAdapter docstring.
6374
+ *
6375
+ * task-1546 (C242 hot-fix): closes the bug where retired ADs leaked into context surfaces.
6376
+ */
6377
+ async getActiveDecisions(options) {
6378
+ if (options?.includeRetired) {
6379
+ const rows2 = await this.sql`
6380
+ SELECT id, display_id, title, confidence, superseded, superseded_by, created_cycle, modified_cycle, body, outcome, revision_count
6381
+ FROM active_decisions
6382
+ WHERE project_id = ${this.projectId}
6383
+ ORDER BY display_id
6384
+ LIMIT 200
6385
+ `;
6386
+ return rows2.map(rowToActiveDecision);
6387
+ }
6356
6388
  const rows = await this.sql`
6357
6389
  SELECT id, display_id, title, confidence, superseded, superseded_by, created_cycle, modified_cycle, body, outcome, revision_count
6358
6390
  FROM active_decisions
6359
6391
  WHERE project_id = ${this.projectId}
6392
+ AND superseded = false
6393
+ AND (outcome IS NULL OR outcome NOT IN ('abandoned', 'superseded', 'resolved'))
6360
6394
  ORDER BY display_id
6361
6395
  LIMIT 200 -- bounded: ADs are bounded by project lifecycle, 200 is a safe ceiling
6362
6396
  `;
@@ -8575,8 +8609,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8575
8609
  getCycleHealth() {
8576
8610
  return this.invoke("getCycleHealth");
8577
8611
  }
8578
- getActiveDecisions() {
8579
- return this.invoke("getActiveDecisions");
8612
+ getActiveDecisions(options) {
8613
+ return this.invoke("getActiveDecisions", [options ?? {}]);
8580
8614
  }
8581
8615
  getCycleLog(limit) {
8582
8616
  return this.invoke("getCycleLog", [limit]);
@@ -10535,6 +10569,20 @@ async function sendSlackWebhook(webhookUrl, summary, header = "PAPI Strategy Rev
10535
10569
  }
10536
10570
 
10537
10571
  // src/prompts.ts
10572
+ var AD_REJECTION_RULES = `**AD Minting Guard \u2014 REJECT observations dressed as decisions.**
10573
+
10574
+ An Active Decision expresses a *stance* the project is taking \u2014 a choice between alternatives that constrains future work. Reject any candidate AD whose body asserts:
10575
+ (a) the existence, identity, or status of a person, user, or external entity (e.g. "User X is building Y", "Customer Z is active");
10576
+ (b) a metric or measurement (e.g. "Signups grew 3x last cycle", "Latency dropped to 200ms");
10577
+ (c) a fact about the current state of the world that could be confirmed or denied by a query rather than challenged by argument (e.g. "External user feedback is now flowing", "The /admin route exists").
10578
+
10579
+ If a candidate AD body could be invalidated by running a SQL query, refreshing a dashboard, or checking a log \u2014 it is an observation, not a decision. Capture it as a build report finding, a dogfood observation, a cycle log note, or a registered doc instead. Do NOT mint it as an AD.
10580
+
10581
+ **Positive example (valid AD):** "Pricing tier strategy: free engine + paid intelligence. Decision: keep cycles free, charge for strategy reviews and analytics. Why: telemetry shows engagement clusters around intelligence surfaces, not engine surfaces." \u2014 this is a stance with alternatives.
10582
+
10583
+ **Negative example (reject):** "External user feedback is now flowing. Stonebridge Systems is actively building." \u2014 this is a fact about the current state of the world. Capture as dogfood/signal observation; do not mint.
10584
+
10585
+ This rule applies to: new ADs proposed during planning (Step 9), strategy review AD updates (section 5), and strategy_change AD updates. If you find an existing AD that violates this rule during housekeeping, propose deleting it (action: "delete") with a one-line rationale.`;
10538
10586
  var PLAN_SYSTEM = `You are the PAPI Cycle Planner \u2014 an autonomous planning engine for software projects.
10539
10587
  You receive project context and produce a planning cycle output with a BUILD HANDOFF.
10540
10588
 
@@ -10760,6 +10808,9 @@ Standard planning cycle with full board review.
10760
10808
 
10761
10809
  9. **Active Decisions** \u2014 If any AD needs updating: Type A (confidence change), Type B (modification), or Type C (reversal/supersede).
10762
10810
  **AD Quality Bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for: process preferences (commit style, PR size), configuration choices (linter rules, tab width), or temporary workarounds. If a decision doesn't affect what gets built or how it's architected, it's not an AD. Apply this bar when proposing new ADs and when triaging existing ones.
10811
+
10812
+ ${AD_REJECTION_RULES}
10813
+
10763
10814
  **\u2192 PERSIST:** EVERY AD you created, updated, or confirmed with changes MUST appear in \`activeDecisions\` array in Part 2. Include the full replacement body with ### heading.
10764
10815
 
10765
10816
  ### Operational Quality Rules
@@ -10997,6 +11048,9 @@ Standard planning cycle with full board review.
10997
11048
 
10998
11049
  9. **Active Decisions** \u2014 If any AD needs updating: Type A (confidence change), Type B (modification), or Type C (reversal/supersede).
10999
11050
  **AD Quality Bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for: process preferences (commit style, PR size), configuration choices (linter rules, tab width), or temporary workarounds. If a decision doesn't affect what gets built or how it's architected, it's not an AD. Apply this bar when proposing new ADs and when triaging existing ones.
11051
+
11052
+ ${AD_REJECTION_RULES}
11053
+
11000
11054
  **\u2192 PERSIST:** EVERY AD you created, updated, or confirmed with changes MUST appear in \`activeDecisions\` array in Part 2. Include the full replacement body with ### heading.
11001
11055
 
11002
11056
  ### Operational Quality Rules
@@ -11369,6 +11423,8 @@ You MUST cover these 5 sections. Each is mandatory.
11369
11423
  - Note any hierarchy/phase issues worth correcting (1-2 bullets max)
11370
11424
  - Delete ADs that are legacy, process-level, or redundant without discussion
11371
11425
 
11426
+ ${AD_REJECTION_RULES}
11427
+
11372
11428
  **Registered Documents:** If a "### Registered Documents" section is present in context, scan it for: (a) research findings that contradict current ADs or strategy, (b) unactioned research that should influence the next plan. Reference relevant docs by title in your review. If unregistered docs are listed, flag 1-2 that look strategically relevant and suggest registering them.
11373
11429
 
11374
11430
  ## CONDITIONAL SECTIONS (include only when genuinely useful \u2014 most reviews should have 0-2 of these)
@@ -11678,6 +11734,8 @@ The JSON must be valid. Only include ADs that need changes \u2014 omit unchanged
11678
11734
  For new ADs, use the next available AD number.
11679
11735
  The body field must be the COMPLETE replacement text for the AD block (including the ### heading line).
11680
11736
 
11737
+ ${AD_REJECTION_RULES}
11738
+
11681
11739
  ## PHASE UPDATES
11682
11740
 
11683
11741
  If the strategic change affects the project's phase structure, include a phaseUpdates array.
@@ -14140,7 +14198,8 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
14140
14198
  docsWithPendingActions
14141
14199
  ] = await Promise.all([
14142
14200
  adapter2.readProductBrief(),
14143
- adapter2.getActiveDecisions(),
14201
+ // Strategy review needs to see retired ADs to triage/restore them as needed.
14202
+ adapter2.getActiveDecisions({ includeRetired: true }),
14144
14203
  adapter2.getBuildReportsSince(lastReviewCycleNum),
14145
14204
  adapter2.getCycleLogSince(lastReviewCycleNum),
14146
14205
  adapter2.queryBoard({
@@ -14642,7 +14701,7 @@ ${cleanContent}`;
14642
14701
  try {
14643
14702
  const recs = extractRecommendations(data, cycleNumber);
14644
14703
  if (recs.length > 0) {
14645
- const existingAds = await adapter2.getActiveDecisions().catch(() => []);
14704
+ const existingAds = await adapter2.getActiveDecisions({ includeRetired: true }).catch(() => []);
14646
14705
  const existingAdIds = new Set(existingAds.map((ad) => ad.id));
14647
14706
  const filteredRecs = recs.filter((rec) => {
14648
14707
  if (rec.target && /^AD-\d+$/.test(rec.target)) {
@@ -15176,7 +15235,8 @@ async function prepareStrategyChange(adapter2, text) {
15176
15235
  try {
15177
15236
  const [brief, decisions, readPhases, boardTasks, reports, previousReviews] = await Promise.all([
15178
15237
  adapter2.readProductBrief(),
15179
- adapter2.getActiveDecisions(),
15238
+ // Strategy review needs ALL ADs (live + retired) for housekeeping.
15239
+ adapter2.getActiveDecisions({ includeRetired: true }),
15180
15240
  adapter2.readPhases(),
15181
15241
  adapter2.queryBoard().catch(() => []),
15182
15242
  adapter2.getRecentBuildReports(15).catch(() => []),
@@ -15221,7 +15281,7 @@ async function captureDecision(adapter2, input) {
15221
15281
  adId = input.adId;
15222
15282
  adAction = "updated";
15223
15283
  } else {
15224
- const existingAds = await adapter2.getActiveDecisions();
15284
+ const existingAds = await adapter2.getActiveDecisions({ includeRetired: true });
15225
15285
  const maxNum = existingAds.reduce((max, ad) => {
15226
15286
  const match = ad.id.match(/^AD-(\d+)$/);
15227
15287
  return match ? Math.max(max, parseInt(match[1], 10)) : max;
@@ -17638,6 +17698,23 @@ function isNoHandoffError(err) {
17638
17698
  function isBlockedError(err) {
17639
17699
  return err instanceof Error && err.code === "BLOCKED";
17640
17700
  }
17701
+ function computeScopeDriftSignal(predicted, changed) {
17702
+ if (!predicted || predicted.length === 0) return null;
17703
+ if (changed.length === 0) return null;
17704
+ const basename2 = (p) => {
17705
+ const parts = p.split(/[\\/]/);
17706
+ return parts[parts.length - 1] ?? p;
17707
+ };
17708
+ const predictedNames = new Set(predicted.map(basename2).filter(Boolean));
17709
+ const unexpected = changed.filter((c) => !predictedNames.has(basename2(c)));
17710
+ const fraction = unexpected.length / changed.length;
17711
+ const triggered = fraction > 0.5 || unexpected.length > 5;
17712
+ if (!triggered) return null;
17713
+ const sample = unexpected.slice(0, 5).join(", ");
17714
+ const more = unexpected.length > 5 ? ` (+${unexpected.length - 5} more)` : "";
17715
+ const pct = Math.round(fraction * 100);
17716
+ return `${unexpected.length}/${changed.length} changed files (${pct}%) outside FILES LIKELY TOUCHED: ${sample}${more}`;
17717
+ }
17641
17718
  function getUnresolvedDeps(task, allTasks) {
17642
17719
  if (!task.dependsOn) return [];
17643
17720
  const deps = task.dependsOn.split(",").map((d) => d.trim()).filter(Boolean);
@@ -17699,6 +17776,11 @@ async function startBuild(adapter2, config2, taskId, options = {}) {
17699
17776
  if (task.status === "Done" || task.status === "Archived") {
17700
17777
  throw new Error(`Task "${taskId}" (${task.title}) is already ${task.status}. Cannot execute a completed task.`);
17701
17778
  }
17779
+ if (task.status === "In Review") {
17780
+ throw new Error(
17781
+ `Task "${taskId}" (${task.title}) is already In Review \u2014 it has been built and is awaiting sign-off. Run \`review_submit\` instead of re-building. If the build genuinely needs rework, use \`review_submit\` with verdict \`request-changes\` first.`
17782
+ );
17783
+ }
17702
17784
  if (!task.buildHandoff) {
17703
17785
  const err = new Error(`Task "${taskId}" (${task.title}) has no BUILD HANDOFF.`);
17704
17786
  err.code = "NO_HANDOFF";
@@ -18099,6 +18181,8 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
18099
18181
  const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
18100
18182
  const changed = getFilesChangedFromBase(config2.projectRoot, baseBranch);
18101
18183
  if (changed.length > 0) report.filesChanged = changed;
18184
+ const drift = computeScopeDriftSignal(task.buildHandoff?.filesLikelyTouched, changed);
18185
+ if (drift) report.scopeDriftSignal = drift;
18102
18186
  }
18103
18187
  let prLines = [];
18104
18188
  if (options.light) {
@@ -18669,6 +18753,7 @@ function formatCompleteResult(result) {
18669
18753
  `**Discovered Issues:** ${result.report.discoveredIssues}`,
18670
18754
  `**Architecture Notes:** ${result.report.architectureNotes}`,
18671
18755
  ...result.report.deadEnds ? [`**Dead Ends:** ${result.report.deadEnds}`] : [],
18756
+ ...result.report.scopeDriftSignal ? [`**Scope drift signal:** ${result.report.scopeDriftSignal}`] : [],
18672
18757
  `**Scope Accuracy:** ${result.scopeAccuracy}`,
18673
18758
  "",
18674
18759
  "---",
@@ -21365,7 +21450,7 @@ ${lines.join("\n")}`;
21365
21450
  }
21366
21451
  let decisionLifecycleSection = "";
21367
21452
  try {
21368
- const decisions = await adapter2.getActiveDecisions();
21453
+ const decisions = await adapter2.getActiveDecisions({ includeRetired: true });
21369
21454
  const lifecycleSummary = formatDecisionLifecycleSummary(decisions);
21370
21455
  if (lifecycleSummary) {
21371
21456
  decisionLifecycleSection = `**Lifecycle:** ${lifecycleSummary}`;
@@ -22131,7 +22216,19 @@ ${versionDrift}` : "";
22131
22216
  const learnings = await adapter2.getCycleLearnings?.({ category: "issue", limit: 30 });
22132
22217
  if (learnings) {
22133
22218
  const byRecency = (a, b2) => (b2.createdAt ?? "").localeCompare(a.createdAt ?? "");
22134
- const unactionedAll = learnings.filter((l) => !l.actionTaken).map((l) => ({ ...l, severity: l.severity ?? "P3" }));
22219
+ const candidateLearnings = learnings.filter((l) => !l.actionTaken);
22220
+ const referencedTaskIds = Array.from(new Set(candidateLearnings.map((l) => l.taskId).filter(Boolean)));
22221
+ let closedTaskIds = /* @__PURE__ */ new Set();
22222
+ if (referencedTaskIds.length > 0) {
22223
+ try {
22224
+ const tasks = await adapter2.getTasks(referencedTaskIds);
22225
+ closedTaskIds = new Set(
22226
+ tasks.filter((t) => t.status === "Done" || t.status === "Cancelled").map((t) => t.id)
22227
+ );
22228
+ } catch {
22229
+ }
22230
+ }
22231
+ const unactionedAll = candidateLearnings.filter((l) => !closedTaskIds.has(l.taskId)).map((l) => ({ ...l, severity: l.severity ?? "P3" }));
22135
22232
  const allAlerts = unactionedAll.filter((l) => l.severity === "P0" || l.severity === "P1").sort(byRecency);
22136
22233
  const allLowSev = unactionedAll.filter((l) => l.severity === "P2" || l.severity === "P3").sort(byRecency);
22137
22234
  const totalP2 = allLowSev.filter((l) => l.severity === "P2").length;
package/dist/prompts.js CHANGED
@@ -1,4 +1,18 @@
1
1
  // src/prompts.ts
2
+ var AD_REJECTION_RULES = `**AD Minting Guard \u2014 REJECT observations dressed as decisions.**
3
+
4
+ An Active Decision expresses a *stance* the project is taking \u2014 a choice between alternatives that constrains future work. Reject any candidate AD whose body asserts:
5
+ (a) the existence, identity, or status of a person, user, or external entity (e.g. "User X is building Y", "Customer Z is active");
6
+ (b) a metric or measurement (e.g. "Signups grew 3x last cycle", "Latency dropped to 200ms");
7
+ (c) a fact about the current state of the world that could be confirmed or denied by a query rather than challenged by argument (e.g. "External user feedback is now flowing", "The /admin route exists").
8
+
9
+ If a candidate AD body could be invalidated by running a SQL query, refreshing a dashboard, or checking a log \u2014 it is an observation, not a decision. Capture it as a build report finding, a dogfood observation, a cycle log note, or a registered doc instead. Do NOT mint it as an AD.
10
+
11
+ **Positive example (valid AD):** "Pricing tier strategy: free engine + paid intelligence. Decision: keep cycles free, charge for strategy reviews and analytics. Why: telemetry shows engagement clusters around intelligence surfaces, not engine surfaces." \u2014 this is a stance with alternatives.
12
+
13
+ **Negative example (reject):** "External user feedback is now flowing. Stonebridge Systems is actively building." \u2014 this is a fact about the current state of the world. Capture as dogfood/signal observation; do not mint.
14
+
15
+ This rule applies to: new ADs proposed during planning (Step 9), strategy review AD updates (section 5), and strategy_change AD updates. If you find an existing AD that violates this rule during housekeeping, propose deleting it (action: "delete") with a one-line rationale.`;
2
16
  var PLAN_SYSTEM = `You are the PAPI Cycle Planner \u2014 an autonomous planning engine for software projects.
3
17
  You receive project context and produce a planning cycle output with a BUILD HANDOFF.
4
18
 
@@ -224,6 +238,9 @@ Standard planning cycle with full board review.
224
238
 
225
239
  9. **Active Decisions** \u2014 If any AD needs updating: Type A (confidence change), Type B (modification), or Type C (reversal/supersede).
226
240
  **AD Quality Bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for: process preferences (commit style, PR size), configuration choices (linter rules, tab width), or temporary workarounds. If a decision doesn't affect what gets built or how it's architected, it's not an AD. Apply this bar when proposing new ADs and when triaging existing ones.
241
+
242
+ ${AD_REJECTION_RULES}
243
+
227
244
  **\u2192 PERSIST:** EVERY AD you created, updated, or confirmed with changes MUST appear in \`activeDecisions\` array in Part 2. Include the full replacement body with ### heading.
228
245
 
229
246
  ### Operational Quality Rules
@@ -461,6 +478,9 @@ Standard planning cycle with full board review.
461
478
 
462
479
  9. **Active Decisions** \u2014 If any AD needs updating: Type A (confidence change), Type B (modification), or Type C (reversal/supersede).
463
480
  **AD Quality Bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for: process preferences (commit style, PR size), configuration choices (linter rules, tab width), or temporary workarounds. If a decision doesn't affect what gets built or how it's architected, it's not an AD. Apply this bar when proposing new ADs and when triaging existing ones.
481
+
482
+ ${AD_REJECTION_RULES}
483
+
464
484
  **\u2192 PERSIST:** EVERY AD you created, updated, or confirmed with changes MUST appear in \`activeDecisions\` array in Part 2. Include the full replacement body with ### heading.
465
485
 
466
486
  ### Operational Quality Rules
@@ -833,6 +853,8 @@ You MUST cover these 5 sections. Each is mandatory.
833
853
  - Note any hierarchy/phase issues worth correcting (1-2 bullets max)
834
854
  - Delete ADs that are legacy, process-level, or redundant without discussion
835
855
 
856
+ ${AD_REJECTION_RULES}
857
+
836
858
  **Registered Documents:** If a "### Registered Documents" section is present in context, scan it for: (a) research findings that contradict current ADs or strategy, (b) unactioned research that should influence the next plan. Reference relevant docs by title in your review. If unregistered docs are listed, flag 1-2 that look strategically relevant and suggest registering them.
837
859
 
838
860
  ## CONDITIONAL SECTIONS (include only when genuinely useful \u2014 most reviews should have 0-2 of these)
@@ -1142,6 +1164,8 @@ The JSON must be valid. Only include ADs that need changes \u2014 omit unchanged
1142
1164
  For new ADs, use the next available AD number.
1143
1165
  The body field must be the COMPLETE replacement text for the AD block (including the ### heading line).
1144
1166
 
1167
+ ${AD_REJECTION_RULES}
1168
+
1145
1169
  ## PHASE UPDATES
1146
1170
 
1147
1171
  If the strategic change affects the project's phase structure, include a phaseUpdates array.
@@ -1438,6 +1462,7 @@ ${inputs.codebaseContext}
1438
1462
  Return a JSON array of 3-10 tasks based on gaps, improvements, and next steps visible from the codebase analysis above.`;
1439
1463
  }
1440
1464
  export {
1465
+ AD_REJECTION_RULES,
1441
1466
  AD_SEED_SYSTEM,
1442
1467
  CONVENTIONS_SYSTEM,
1443
1468
  HANDOFF_REGEN_SYSTEM,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.13",
3
+ "version": "0.7.14",
4
4
  "description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",