@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 +111 -14
- package/dist/prompts.js +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,12 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../shared/dist/index.js
|
|
13
|
-
|
|
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
|
-
/**
|
|
1561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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