@papi-ai/server 0.7.84 → 0.7.98
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/backfill-cycle-metrics.js +184 -49
- package/dist/index.js +3331 -1025
- package/dist/prompts.js +77 -17
- package/dist/statusline.js +168 -0
- package/package.json +7 -8
package/dist/prompts.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/lib/carry-forward-shape.ts
|
|
2
|
+
import { splitCarryForward, renderCarryForward } from "@papi-ai/shared";
|
|
3
|
+
|
|
1
4
|
// src/prompts.ts
|
|
2
5
|
var AD_REJECTION_RULES = `**AD Minting Guard \u2014 REJECT observations dressed as decisions.**
|
|
3
6
|
|
|
@@ -30,7 +33,11 @@ The guard above says what to reject. This says what to propose. Rejecting is not
|
|
|
30
33
|
|
|
31
34
|
**Copy variants are NEVER decisions.** A tagline, a headline, a value proposition, a piece of marketing or product wording: swapping one for another constrains no future work, so it fails test (b) outright. Wording changes as often as the market teaches you something, and routing every edit through decision ceremony is what buries the handful of decisions that genuinely constrain the project. Edit the wording where the wording lives. If two variants are being compared against a measurement, that is an experiment, not a decision.
|
|
32
35
|
|
|
33
|
-
**A decision decaying into a convention is the healthy path, not a failure.** When a live AD stops being arguable \u2014 the alternatives are no longer on the table and nobody would re-litigate it \u2014
|
|
36
|
+
**A decision decaying into a convention is the healthy path, not a failure.** When a live AD stops being arguable \u2014 the alternatives are no longer on the table and nobody would re-litigate it \u2014 propose demoting it during housekeeping with \`action: "demote"\`. An AD registry where most entries are settled is one nobody reads.
|
|
37
|
+
|
|
38
|
+
DEMOTION IS THE THIRD TRANSITION, and it is not either of the other two. \`supersede\` replaces a stance with a NEW one; \`delete\` removes something that was never a decision. \`demote\` is for a decision that was RIGHT and still holds \u2014 it keeps riding every build handoff as a convention and simply stops occupying decision surface. Apply test (c) to each live AD: could a competent person argue the other side TODAY, on current evidence? If no, propose \`demote\` and give the one-line reason it is no longer contested.
|
|
39
|
+
|
|
40
|
+
PROPOSE, NEVER ASSUME. A demotion is a judgement about whether something is still arguable, and the owner is the one who knows. Surface candidates with reasoning and let the human confirm on apply, exactly as board corrections and cancellations are gated. Do not demote in bulk to tidy the registry.`;
|
|
34
41
|
var AD_CONFLICT_SURFACING_RULES = `**A contradiction is NOT a veto \u2014 surface it, never silently shelve it.**
|
|
35
42
|
|
|
36
43
|
Active Decisions are *active*: they can be superseded, modified, or abandoned. You do NOT have authority to kill a piece of work simply because it cuts against one. That is the user's call, and they can only make it if you show it to them.
|
|
@@ -92,7 +99,7 @@ SECURITY CONSIDERATIONS
|
|
|
92
99
|
[data exposure, secrets handling, auth/access control, dependency risks \u2014 or "None \u2014 no security-relevant changes"]
|
|
93
100
|
|
|
94
101
|
DEPLOY VERIFICATION
|
|
95
|
-
[Include this section ONLY when FILES LIKELY TOUCHED contains a TRIGGER-SURFACE path. Trigger surfaces (exact set): lib/install-snippets.ts, packages/server/src/transport-http.ts, packages/server/src/index.ts, app/well-known/**, app/api/auth/oauth/**, app/proxy.ts, app/middleware.ts, lib/auth*, next.config.ts,
|
|
102
|
+
[Include this section ONLY when FILES LIKELY TOUCHED contains a TRIGGER-SURFACE path. Trigger surfaces (exact set): lib/install-snippets.ts, packages/server/src/transport-http.ts, packages/server/src/index.ts, app/well-known/**, app/api/auth/oauth/**, app/proxy.ts, app/middleware.ts, lib/auth*, next.config.ts, **/Dockerfile, **/Procfile, supabase/functions/**, lib/env*.ts. When required, instruct the builder: "Production verification REQUIRED. After deploy, run a curl against the live URL(s) you changed and pass production_verification on build_execute complete with { urls, curl_command, http_status, response_excerpt, verified_at }. http_status MUST be 2xx; build_execute and review_submit reject otherwise. Server inspects the diff against origin/main to enforce this." Omit this section entirely when no trigger surface is touched.]
|
|
96
103
|
|
|
97
104
|
REFERENCE DOCS
|
|
98
105
|
[Optional \u2014 paths to docs/ files that provide background context for this task. Include only when the task originated from research or scoping work and the doc contains context the builder will need beyond what is in this handoff. Omit this section entirely for tasks that don't need supplementary context.]
|
|
@@ -300,7 +307,7 @@ var PLAN_FRAGMENT_MARKETING_BRIEF = `
|
|
|
300
307
|
Add to ACCEPTANCE CRITERIA: "[ ] Message Frame confirmed with Owner before drafting" and "[ ] Final content reviewed by Owner before publishing."`;
|
|
301
308
|
var PLAN_FRAGMENT_OPS_BRIEF = `
|
|
302
309
|
**Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
303
|
-
- SYSTEM: Which system or service this ops task touches \u2014 Vercel,
|
|
310
|
+
- SYSTEM: Which system or service this ops task touches \u2014 Vercel, Supabase, GitHub Actions, DNS, etc.
|
|
304
311
|
- RISK: What could go wrong \u2014 data loss, downtime, broken deployments. Include estimated blast radius (e.g. "affects all authenticated users").
|
|
305
312
|
- ROLLBACK PLAN: Exact steps to undo the change if something breaks. Must be specific enough to execute under pressure.
|
|
306
313
|
- DONE CONDITION: The specific observable state that confirms the task is complete \u2014 a health check URL, a metric, a log line, a manual verification step.
|
|
@@ -328,7 +335,19 @@ var PLAN_FRAGMENT_FORWARD_HORIZON = `
|
|
|
328
335
|
- **Actionable** \u2014 frame as a decision to make, not a vague warning (e.g. "Decide whether to use WebSockets or SSE for real-time updates before starting Phase 4: Real-Time Features")
|
|
329
336
|
- **Tied to trajectory** \u2014 based on current board state, ADs, and velocity, not generic advice
|
|
330
337
|
If the Forward Horizon context is absent or there are no meaningful decisions to surface, omit this section entirely. Do NOT generate generic advice like "plan ahead" or "consider testing".`;
|
|
331
|
-
function
|
|
338
|
+
function omitSection(source, startMarker, endMarker) {
|
|
339
|
+
const start = source.indexOf(startMarker);
|
|
340
|
+
if (start === -1) return source;
|
|
341
|
+
const end = source.indexOf(endMarker, start + startMarker.length);
|
|
342
|
+
if (end === -1) return source;
|
|
343
|
+
return source.slice(0, start) + source.slice(end);
|
|
344
|
+
}
|
|
345
|
+
var ALL_PLAN_GATES_ON = {
|
|
346
|
+
planHealthCheck: true,
|
|
347
|
+
backlogTriage: true,
|
|
348
|
+
buildOrder: true
|
|
349
|
+
};
|
|
350
|
+
function composeFullModeInstructions(flags, ctx, gates = ALL_PLAN_GATES_ON) {
|
|
332
351
|
const parts = [
|
|
333
352
|
`## FULL MODE
|
|
334
353
|
|
|
@@ -461,7 +480,21 @@ ${AD_ADMISSION_RULES}
|
|
|
461
480
|
}
|
|
462
481
|
parts.push(`
|
|
463
482
|
**CRITICAL: Review your Part 2 JSON before finishing. Every action from Part 1 must have a corresponding entry in Part 2. If Part 1 mentions corrections, new tasks, AD changes, or handoffs but Part 2 has empty arrays \u2014 you have a persistence bug.**`);
|
|
464
|
-
|
|
483
|
+
let composed = parts.join("\n");
|
|
484
|
+
if (!gates.planHealthCheck) {
|
|
485
|
+
composed = omitSection(composed, "1. **Cycle Health Check**", "\n2. **Inbox Triage**");
|
|
486
|
+
}
|
|
487
|
+
if (!gates.backlogTriage) {
|
|
488
|
+
composed = omitSection(composed, "2. **Inbox Triage**", "\n3. **Board Integrity**");
|
|
489
|
+
}
|
|
490
|
+
if (!gates.buildOrder) {
|
|
491
|
+
composed = omitSection(
|
|
492
|
+
composed,
|
|
493
|
+
" **Intra-cycle dependency detection:**",
|
|
494
|
+
" **Branch-coherence check (task-1908):**"
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return composed;
|
|
465
498
|
}
|
|
466
499
|
var PLAN_FULL_BASELINE_FLAGS = {
|
|
467
500
|
hasBugTasks: true,
|
|
@@ -479,9 +512,11 @@ var PLAN_FULL_INSTRUCTIONS = composeFullModeInstructions(PLAN_FULL_BASELINE_FLAG
|
|
|
479
512
|
hasDiscoveryCanvas: true,
|
|
480
513
|
hasHorizonContext: true
|
|
481
514
|
});
|
|
482
|
-
function buildPlanFullInstructionsConditional(flags, ctx) {
|
|
483
|
-
if (!flags || !ctx)
|
|
484
|
-
|
|
515
|
+
function buildPlanFullInstructionsConditional(flags, ctx, gates) {
|
|
516
|
+
if (!flags || !ctx) {
|
|
517
|
+
return gates ? composeFullModeInstructions(PLAN_FULL_BASELINE_FLAGS, { hasDiscoveryCanvas: true, hasHorizonContext: true }, gates) : PLAN_FULL_INSTRUCTIONS;
|
|
518
|
+
}
|
|
519
|
+
return composeFullModeInstructions(flags, ctx, gates);
|
|
485
520
|
}
|
|
486
521
|
var CYCLE_DENSITY_TARGETS = {
|
|
487
522
|
light: { label: "Light", range: "2-3" },
|
|
@@ -522,10 +557,14 @@ function buildPlanUserMessage(ctx) {
|
|
|
522
557
|
if (ctx.mode === "bootstrap") {
|
|
523
558
|
parts.push(PLAN_BOOTSTRAP_INSTRUCTIONS);
|
|
524
559
|
} else {
|
|
525
|
-
const instructions = ctx.boardFlags ? buildPlanFullInstructionsConditional(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
560
|
+
const instructions = ctx.boardFlags ? buildPlanFullInstructionsConditional(
|
|
561
|
+
ctx.boardFlags,
|
|
562
|
+
{
|
|
563
|
+
hasDiscoveryCanvas: !!ctx.discoveryCanvas,
|
|
564
|
+
hasHorizonContext: !!ctx.horizonContext
|
|
565
|
+
},
|
|
566
|
+
ctx.planGates
|
|
567
|
+
) : buildPlanFullInstructionsConditional(void 0, void 0, ctx.planGates);
|
|
529
568
|
parts.push(instructions);
|
|
530
569
|
}
|
|
531
570
|
if (ctx.foundationalTasksGuidance) {
|
|
@@ -798,11 +837,31 @@ function coerceToString(value) {
|
|
|
798
837
|
if (value === null || value === void 0) return "";
|
|
799
838
|
return JSON.stringify(value, null, 2);
|
|
800
839
|
}
|
|
840
|
+
function normalizePlannerPriority(value) {
|
|
841
|
+
const raw = coerceToString(value).trim();
|
|
842
|
+
const aliases = {
|
|
843
|
+
p0: "P0 Critical",
|
|
844
|
+
"p0 critical": "P0 Critical",
|
|
845
|
+
p1: "P1 High",
|
|
846
|
+
"p1 high": "P1 High",
|
|
847
|
+
p2: "P2 Medium",
|
|
848
|
+
"p2 medium": "P2 Medium",
|
|
849
|
+
p3: "P3 Low",
|
|
850
|
+
"p3 low": "P3 Low"
|
|
851
|
+
};
|
|
852
|
+
return aliases[raw.toLowerCase()] ?? raw;
|
|
853
|
+
}
|
|
801
854
|
function coerceCarryForward(value) {
|
|
802
855
|
if (value === null || value === void 0) return { value: null };
|
|
803
856
|
if (typeof value === "string") {
|
|
804
857
|
const trimmed = value.trim();
|
|
805
|
-
|
|
858
|
+
if (trimmed.length === 0) return { value: null };
|
|
859
|
+
if (!splitCarryForward(trimmed).split) {
|
|
860
|
+
const warning2 = 'cycleLogCarryForward was persisted, but it has neither a "WHAT SHIPS FOR USERS:" nor a "RELEASE MECHANICS:" label \u2014 orient will render it verbatim instead of leading with product signal. Re-run plan apply with both labels in cycleLogCarryForward for this cycle if that split matters here.';
|
|
861
|
+
console.error(`[plan] ${warning2}`);
|
|
862
|
+
return { value: trimmed, warning: warning2 };
|
|
863
|
+
}
|
|
864
|
+
return { value: trimmed };
|
|
806
865
|
}
|
|
807
866
|
const shape = Array.isArray(value) ? "array" : typeof value;
|
|
808
867
|
const warning = `cycleLogCarryForward was ${shape}, not a string \u2014 DROPPED rather than persisted. Carry-forward is prose that orient parses for the WHAT SHIPS FOR USERS / RELEASE MECHANICS labels; a non-string value cannot carry them. Re-run plan apply with cycleLogCarryForward as a single string (or null) to record one for this cycle.`;
|
|
@@ -826,7 +885,7 @@ function coerceStructuredOutput(parsed) {
|
|
|
826
885
|
tempId: t.tempId !== void 0 && t.tempId !== null ? coerceToString(t.tempId) : void 0,
|
|
827
886
|
title: coerceToString(t.title),
|
|
828
887
|
status: coerceToString(t.status),
|
|
829
|
-
priority:
|
|
888
|
+
priority: normalizePlannerPriority(t.priority),
|
|
830
889
|
complexity: coerceToString(t.complexity),
|
|
831
890
|
module: coerceToString(t.module),
|
|
832
891
|
epic: coerceToString(t.epic),
|
|
@@ -839,7 +898,7 @@ function coerceStructuredOutput(parsed) {
|
|
|
839
898
|
const updates = typeof c.updates === "object" && c.updates !== null ? c.updates : {};
|
|
840
899
|
const coercedUpdates = {};
|
|
841
900
|
for (const [key, val] of Object.entries(updates)) {
|
|
842
|
-
coercedUpdates[key] = typeof val === "object" && val !== null ? JSON.stringify(val) : val;
|
|
901
|
+
coercedUpdates[key] = key === "priority" ? normalizePlannerPriority(val) : typeof val === "object" && val !== null ? JSON.stringify(val) : val;
|
|
843
902
|
}
|
|
844
903
|
return { taskId: coerceToString(c.taskId), updates: coercedUpdates };
|
|
845
904
|
}) : [];
|
|
@@ -1017,7 +1076,7 @@ After your natural language output, include this EXACT format on its own line:
|
|
|
1017
1076
|
"activeDecisionUpdates": [
|
|
1018
1077
|
{
|
|
1019
1078
|
"id": "string \u2014 AD-N (existing) or new AD-N (for new decisions)",
|
|
1020
|
-
"action": "confidence_change | modify | resolve | supersede | new | delete",
|
|
1079
|
+
"action": "confidence_change | modify | resolve | supersede | new | delete | demote",
|
|
1021
1080
|
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text (empty string for delete)",
|
|
1022
1081
|
"evidenceRef": "string (optional) \u2014 for DELIBERATE decisions (modify / resolve / delete = validate/modify/invalidate), a pointer to the evidence that justified the change: a doc path (docs/research/foo.md), a build-report id, or a metric name. Builds the decision->outcome ledger. Omit if no concrete evidence.",
|
|
1023
1082
|
"metricDelta": "object (optional) \u2014 { "metric": string, "before": number, "after": number, "delta": number } \u2014 which metric moved and by how much. Use a REAL metric name from cycle_metrics_snapshots where possible (e.g. scope_accuracy, velocity, est_actual_drift). Omit if no metric moved."
|
|
@@ -1269,7 +1328,7 @@ After your natural language output, include this EXACT format on its own line:
|
|
|
1269
1328
|
"activeDecisionUpdates": [
|
|
1270
1329
|
{
|
|
1271
1330
|
"id": "string \u2014 AD-N (existing) or new AD-N (for new decisions)",
|
|
1272
|
-
"action": "confidence_change | modify | resolve | supersede | new | delete",
|
|
1331
|
+
"action": "confidence_change | modify | resolve | supersede | new | delete | demote",
|
|
1273
1332
|
"body": "string \u2014 full AD block including ### heading, confidence tag, and body text (empty string for delete)",
|
|
1274
1333
|
"evidenceRef": "string (optional) \u2014 for DELIBERATE changes (modify / resolve / delete), a pointer to the justifying evidence: a doc path, a build-report id, or a metric name. Builds the decision->outcome ledger. Omit if none.",
|
|
1275
1334
|
"metricDelta": "object (optional) \u2014 { "metric": string, "before": number, "after": number, "delta": number } \u2014 which metric moved. Prefer a real metric name from cycle_metrics_snapshots. Omit if none."
|
|
@@ -1761,6 +1820,7 @@ export {
|
|
|
1761
1820
|
buildVisionTasksPrompt,
|
|
1762
1821
|
coerceCarryForward,
|
|
1763
1822
|
extractDependencyChain,
|
|
1823
|
+
normalizePlannerPriority,
|
|
1764
1824
|
parseReviewStructuredOutput,
|
|
1765
1825
|
parseStrategyChangeOutput,
|
|
1766
1826
|
parseStructuredOutput
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/cli/statusline.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { homedir, tmpdir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { pathToFileURL } from "url";
|
|
7
|
+
var CACHE_TTL_MS = 3e4;
|
|
8
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
9
|
+
function renderPeekLine(c) {
|
|
10
|
+
const parts = [`PAPI c${c.cycle ?? "?"}`];
|
|
11
|
+
parts.push(`build ${c.inFlight}`);
|
|
12
|
+
parts.push(`review ${c.inReview}`);
|
|
13
|
+
if (c.ownerActions !== null) parts.push(`you ${c.ownerActions}`);
|
|
14
|
+
return parts.join(" | ");
|
|
15
|
+
}
|
|
16
|
+
function cacheFilePath(endpoint, projectId) {
|
|
17
|
+
const hash = createHash("sha256").update(`${endpoint}|${projectId ?? ""}`).digest("hex").slice(0, 16);
|
|
18
|
+
return join(tmpdir(), `papi-statusline-${hash}.json`);
|
|
19
|
+
}
|
|
20
|
+
function readClientCache(path, now = Date.now()) {
|
|
21
|
+
try {
|
|
22
|
+
const hit = JSON.parse(readFileSync(path, "utf-8"));
|
|
23
|
+
if (!hit || typeof hit.expires !== "number" || hit.expires <= now) return void 0;
|
|
24
|
+
return hit.payload;
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeClientCache(path, payload, now = Date.now()) {
|
|
30
|
+
try {
|
|
31
|
+
writeFileSync(path, JSON.stringify({ expires: now + CACHE_TTL_MS, payload }));
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function configFromMcpJson(body) {
|
|
36
|
+
if (!body || typeof body !== "object") return void 0;
|
|
37
|
+
const servers = body.mcpServers;
|
|
38
|
+
if (!servers || typeof servers !== "object") return void 0;
|
|
39
|
+
for (const entry of Object.values(servers)) {
|
|
40
|
+
if (!entry || typeof entry !== "object") continue;
|
|
41
|
+
const s = entry;
|
|
42
|
+
if (typeof s.url !== "string" || !/papi/i.test(s.url)) continue;
|
|
43
|
+
const auth = s.headers?.["Authorization"];
|
|
44
|
+
const authString = typeof auth === "string" ? auth : "";
|
|
45
|
+
const bearer = authString.startsWith("Bearer ") ? authString.slice(7).trim() : "";
|
|
46
|
+
const projectHeader = s.headers?.["x-papi-project-id"];
|
|
47
|
+
if (!bearer) continue;
|
|
48
|
+
return {
|
|
49
|
+
endpoint: new URL(s.url).origin,
|
|
50
|
+
bearer,
|
|
51
|
+
projectId: typeof projectHeader === "string" && projectHeader.length > 0 ? projectHeader : void 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
function mergeStatusLine(body, command) {
|
|
57
|
+
const obj = body && typeof body === "object" ? body : {};
|
|
58
|
+
if (obj["statusLine"] !== void 0) return null;
|
|
59
|
+
return { ...obj, statusLine: { type: "command", command } };
|
|
60
|
+
}
|
|
61
|
+
function resolveConfig(argv) {
|
|
62
|
+
const flag = (name) => {
|
|
63
|
+
const i = argv.indexOf(name);
|
|
64
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
|
|
65
|
+
};
|
|
66
|
+
const endpoint = flag("--endpoint") ?? process.env["PAPI_ENDPOINT"];
|
|
67
|
+
const bearer = flag("--bearer") ?? process.env["PAPI_BEARER"];
|
|
68
|
+
const projectId = flag("--project") ?? process.env["PAPI_PROJECT_ID"];
|
|
69
|
+
if (endpoint && bearer) return { endpoint: new URL(endpoint).origin, bearer, projectId };
|
|
70
|
+
const mcpJsonPath = join(process.cwd(), ".mcp.json");
|
|
71
|
+
if (existsSync(mcpJsonPath)) {
|
|
72
|
+
try {
|
|
73
|
+
return configFromMcpJson(JSON.parse(readFileSync(mcpJsonPath, "utf-8")));
|
|
74
|
+
} catch {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
async function fetchPeek(config) {
|
|
81
|
+
const response = await fetch(`${config.endpoint}/peek`, {
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: `Bearer ${config.bearer}`,
|
|
84
|
+
...config.projectId ? { "x-papi-project-id": config.projectId } : {}
|
|
85
|
+
},
|
|
86
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) throw new Error(`peek returned ${response.status}`);
|
|
89
|
+
const body = await response.json();
|
|
90
|
+
return {
|
|
91
|
+
cycle: typeof body.cycle === "number" ? body.cycle : null,
|
|
92
|
+
inFlight: typeof body.inFlight === "number" ? body.inFlight : 0,
|
|
93
|
+
inReview: typeof body.inReview === "number" ? body.inReview : 0,
|
|
94
|
+
ownerActions: typeof body.ownerActions === "number" ? body.ownerActions : null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var RENDER_COMMAND = "npx -y @papi-ai/cli papi-statusline";
|
|
98
|
+
async function install() {
|
|
99
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
100
|
+
let body = {};
|
|
101
|
+
if (existsSync(settingsPath)) {
|
|
102
|
+
try {
|
|
103
|
+
body = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error(`papi-statusline: could not parse ${settingsPath}: ${err.message}`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const merged = mergeStatusLine(body, RENDER_COMMAND);
|
|
110
|
+
if (merged === null) {
|
|
111
|
+
console.log(
|
|
112
|
+
`papi-statusline: ${settingsPath} already has a statusLine entry \u2014 left untouched.
|
|
113
|
+
To switch it to PAPI manually, set:
|
|
114
|
+
"statusLine": { "type": "command", "command": "${RENDER_COMMAND}" }
|
|
115
|
+
|
|
116
|
+
Any other MCP client with a custom status-bar command can run the same command.`
|
|
117
|
+
);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
writeFileSync(settingsPath, `${JSON.stringify(merged, null, 2)}
|
|
121
|
+
`);
|
|
122
|
+
console.log(`papi-statusline: installed into ${settingsPath}. Restart your AI client to see it.`);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
async function runStatusline(argv, stdin) {
|
|
126
|
+
if (argv.includes("--install")) return install();
|
|
127
|
+
void stdin;
|
|
128
|
+
const config = resolveConfig(argv);
|
|
129
|
+
if (!config) return 0;
|
|
130
|
+
const cachePath = cacheFilePath(config.endpoint, config.projectId);
|
|
131
|
+
const cached = readClientCache(cachePath);
|
|
132
|
+
if (cached) {
|
|
133
|
+
process.stdout.write(`${renderPeekLine(cached)}
|
|
134
|
+
`);
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const payload = await fetchPeek(config);
|
|
139
|
+
writeClientCache(cachePath, payload);
|
|
140
|
+
process.stdout.write(`${renderPeekLine(payload)}
|
|
141
|
+
`);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
147
|
+
const chunks = [];
|
|
148
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
149
|
+
process.stdin.on("end", () => {
|
|
150
|
+
void runStatusline(process.argv.slice(2), Buffer.concat(chunks).toString("utf-8")).then((code) => {
|
|
151
|
+
process.exitCode = code;
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
void runStatusline(process.argv.slice(2), Buffer.concat(chunks).toString("utf-8")).then((code) => {
|
|
156
|
+
process.exitCode = code;
|
|
157
|
+
});
|
|
158
|
+
}, 250);
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
cacheFilePath,
|
|
162
|
+
configFromMcpJson,
|
|
163
|
+
mergeStatusLine,
|
|
164
|
+
readClientCache,
|
|
165
|
+
renderPeekLine,
|
|
166
|
+
runStatusline,
|
|
167
|
+
writeClientCache
|
|
168
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "PAPI MCP server
|
|
3
|
+
"version": "0.7.98",
|
|
4
|
+
"description": "PAPI MCP server \u2014 AI-powered sprint planning, build execution, and strategy review for software projects",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"mcpName": "io.github.getpapi/papi",
|
|
7
7
|
"type": "module",
|
|
@@ -49,17 +49,16 @@
|
|
|
49
49
|
"node": ">=18.0.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@anthropic-ai/sdk": "^0.
|
|
52
|
+
"@anthropic-ai/sdk": "^0.91.1",
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
54
|
-
"@papi-ai/adapter-pg": "^0.2.
|
|
55
|
-
"@papi-ai/shared": "^0.2.
|
|
56
|
-
"@papi-ai/skills": "^0.1.
|
|
57
|
-
"js-yaml": "^4.1.0"
|
|
54
|
+
"@papi-ai/adapter-pg": "^0.2.16",
|
|
55
|
+
"@papi-ai/shared": "^0.2.5",
|
|
56
|
+
"@papi-ai/skills": "^0.1.4"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
59
|
"@papi-ai/adapter-md": "^0.2.0",
|
|
61
|
-
"@types/js-yaml": "^4.0.9",
|
|
62
60
|
"@types/node": "^22.0.0",
|
|
61
|
+
"ajv": "^8.20.0",
|
|
63
62
|
"tsup": "^8.0.0",
|
|
64
63
|
"typescript": "^5.5.0",
|
|
65
64
|
"vitest": "^4.0.18"
|