@papi-ai/server 0.7.78 → 0.7.79
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 +1 -1
- package/dist/index.js +506 -301
- package/dist/prompts.js +32 -3
- package/package.json +1 -1
- package/skills/papi-cycle/papi-strategy/SKILL.md +1 -0
package/dist/index.js
CHANGED
|
@@ -2248,7 +2248,7 @@ var init_reap_orphans = __esm({
|
|
|
2248
2248
|
}
|
|
2249
2249
|
});
|
|
2250
2250
|
|
|
2251
|
-
//
|
|
2251
|
+
// ../../node_modules/postgres/src/query.js
|
|
2252
2252
|
function cachedError(xs) {
|
|
2253
2253
|
if (originCache.has(xs))
|
|
2254
2254
|
return originCache.get(xs);
|
|
@@ -2260,7 +2260,7 @@ function cachedError(xs) {
|
|
|
2260
2260
|
}
|
|
2261
2261
|
var originCache, originStackCache, originError, CLOSE, Query;
|
|
2262
2262
|
var init_query = __esm({
|
|
2263
|
-
"
|
|
2263
|
+
"../../node_modules/postgres/src/query.js"() {
|
|
2264
2264
|
"use strict";
|
|
2265
2265
|
originCache = /* @__PURE__ */ new Map();
|
|
2266
2266
|
originStackCache = /* @__PURE__ */ new Map();
|
|
@@ -2391,7 +2391,7 @@ var init_query = __esm({
|
|
|
2391
2391
|
}
|
|
2392
2392
|
});
|
|
2393
2393
|
|
|
2394
|
-
//
|
|
2394
|
+
// ../../node_modules/postgres/src/errors.js
|
|
2395
2395
|
function connection(x, options, socket) {
|
|
2396
2396
|
const { host, port } = socket || options;
|
|
2397
2397
|
const error = Object.assign(
|
|
@@ -2429,7 +2429,7 @@ function notSupported(x) {
|
|
|
2429
2429
|
}
|
|
2430
2430
|
var PostgresError, Errors;
|
|
2431
2431
|
var init_errors = __esm({
|
|
2432
|
-
"
|
|
2432
|
+
"../../node_modules/postgres/src/errors.js"() {
|
|
2433
2433
|
"use strict";
|
|
2434
2434
|
PostgresError = class extends Error {
|
|
2435
2435
|
constructor(x) {
|
|
@@ -2447,7 +2447,7 @@ var init_errors = __esm({
|
|
|
2447
2447
|
}
|
|
2448
2448
|
});
|
|
2449
2449
|
|
|
2450
|
-
//
|
|
2450
|
+
// ../../node_modules/postgres/src/types.js
|
|
2451
2451
|
function handleValue(x, parameters, types2, options) {
|
|
2452
2452
|
let value = x instanceof Parameter ? x.value : x;
|
|
2453
2453
|
if (value === void 0) {
|
|
@@ -2562,7 +2562,7 @@ function createJsonTransform(fn) {
|
|
|
2562
2562
|
}
|
|
2563
2563
|
var types, NotTagged, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes, escapeIdentifier, inferType, escapeBackslash, escapeQuote, arraySerializer, arrayParserState, arrayParser, toCamel, toPascal, toKebab, fromCamel, fromPascal, fromKebab, camel, pascal, kebab;
|
|
2564
2564
|
var init_types = __esm({
|
|
2565
|
-
"
|
|
2565
|
+
"../../node_modules/postgres/src/types.js"() {
|
|
2566
2566
|
"use strict";
|
|
2567
2567
|
init_query();
|
|
2568
2568
|
init_errors();
|
|
@@ -2741,10 +2741,10 @@ var init_types = __esm({
|
|
|
2741
2741
|
}
|
|
2742
2742
|
});
|
|
2743
2743
|
|
|
2744
|
-
//
|
|
2744
|
+
// ../../node_modules/postgres/src/result.js
|
|
2745
2745
|
var Result;
|
|
2746
2746
|
var init_result = __esm({
|
|
2747
|
-
"
|
|
2747
|
+
"../../node_modules/postgres/src/result.js"() {
|
|
2748
2748
|
"use strict";
|
|
2749
2749
|
Result = class extends Array {
|
|
2750
2750
|
constructor() {
|
|
@@ -2764,7 +2764,7 @@ var init_result = __esm({
|
|
|
2764
2764
|
}
|
|
2765
2765
|
});
|
|
2766
2766
|
|
|
2767
|
-
//
|
|
2767
|
+
// ../../node_modules/postgres/src/queue.js
|
|
2768
2768
|
function Queue(initial = []) {
|
|
2769
2769
|
let xs = initial.slice();
|
|
2770
2770
|
let index = 0;
|
|
@@ -2791,13 +2791,13 @@ function Queue(initial = []) {
|
|
|
2791
2791
|
}
|
|
2792
2792
|
var queue_default;
|
|
2793
2793
|
var init_queue = __esm({
|
|
2794
|
-
"
|
|
2794
|
+
"../../node_modules/postgres/src/queue.js"() {
|
|
2795
2795
|
"use strict";
|
|
2796
2796
|
queue_default = Queue;
|
|
2797
2797
|
}
|
|
2798
2798
|
});
|
|
2799
2799
|
|
|
2800
|
-
//
|
|
2800
|
+
// ../../node_modules/postgres/src/bytes.js
|
|
2801
2801
|
function fit(x) {
|
|
2802
2802
|
if (buffer.length - b.i < x) {
|
|
2803
2803
|
const prev = buffer, length = prev.length;
|
|
@@ -2811,7 +2811,7 @@ function reset() {
|
|
|
2811
2811
|
}
|
|
2812
2812
|
var size, buffer, messages, b, bytes_default;
|
|
2813
2813
|
var init_bytes = __esm({
|
|
2814
|
-
"
|
|
2814
|
+
"../../node_modules/postgres/src/bytes.js"() {
|
|
2815
2815
|
"use strict";
|
|
2816
2816
|
size = 256;
|
|
2817
2817
|
buffer = Buffer.allocUnsafe(size);
|
|
@@ -2876,7 +2876,7 @@ var init_bytes = __esm({
|
|
|
2876
2876
|
}
|
|
2877
2877
|
});
|
|
2878
2878
|
|
|
2879
|
-
//
|
|
2879
|
+
// ../../node_modules/postgres/src/connection.js
|
|
2880
2880
|
import net from "net";
|
|
2881
2881
|
import tls from "tls";
|
|
2882
2882
|
import crypto2 from "crypto";
|
|
@@ -3670,7 +3670,7 @@ function timer(fn, seconds) {
|
|
|
3670
3670
|
}
|
|
3671
3671
|
var connection_default, uid, Sync, Flush, SSLRequest, ExecuteUnnamed, DescribeUnnamed, noop, retryRoutines, errorFields;
|
|
3672
3672
|
var init_connection = __esm({
|
|
3673
|
-
"
|
|
3673
|
+
"../../node_modules/postgres/src/connection.js"() {
|
|
3674
3674
|
"use strict";
|
|
3675
3675
|
init_types();
|
|
3676
3676
|
init_errors();
|
|
@@ -3733,7 +3733,7 @@ var init_connection = __esm({
|
|
|
3733
3733
|
}
|
|
3734
3734
|
});
|
|
3735
3735
|
|
|
3736
|
-
//
|
|
3736
|
+
// ../../node_modules/postgres/src/subscribe.js
|
|
3737
3737
|
function Subscribe(postgres2, options) {
|
|
3738
3738
|
const subscribers = /* @__PURE__ */ new Map(), slot = "postgresjs_" + Math.random().toString(36).slice(2), state = {};
|
|
3739
3739
|
let connection2, stream, ended = false;
|
|
@@ -3944,14 +3944,14 @@ function parseEvent(x) {
|
|
|
3944
3944
|
}
|
|
3945
3945
|
var noop2;
|
|
3946
3946
|
var init_subscribe = __esm({
|
|
3947
|
-
"
|
|
3947
|
+
"../../node_modules/postgres/src/subscribe.js"() {
|
|
3948
3948
|
"use strict";
|
|
3949
3949
|
noop2 = () => {
|
|
3950
3950
|
};
|
|
3951
3951
|
}
|
|
3952
3952
|
});
|
|
3953
3953
|
|
|
3954
|
-
//
|
|
3954
|
+
// ../../node_modules/postgres/src/large.js
|
|
3955
3955
|
import Stream2 from "stream";
|
|
3956
3956
|
function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
3957
3957
|
return new Promise(async (resolve4, reject) => {
|
|
@@ -4017,12 +4017,12 @@ function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
|
4017
4017
|
});
|
|
4018
4018
|
}
|
|
4019
4019
|
var init_large = __esm({
|
|
4020
|
-
"
|
|
4020
|
+
"../../node_modules/postgres/src/large.js"() {
|
|
4021
4021
|
"use strict";
|
|
4022
4022
|
}
|
|
4023
4023
|
});
|
|
4024
4024
|
|
|
4025
|
-
//
|
|
4025
|
+
// ../../node_modules/postgres/src/index.js
|
|
4026
4026
|
var src_exports = {};
|
|
4027
4027
|
__export(src_exports, {
|
|
4028
4028
|
default: () => src_default
|
|
@@ -4412,7 +4412,7 @@ function osUsername() {
|
|
|
4412
4412
|
}
|
|
4413
4413
|
var src_default;
|
|
4414
4414
|
var init_src = __esm({
|
|
4415
|
-
"
|
|
4415
|
+
"../../node_modules/postgres/src/index.js"() {
|
|
4416
4416
|
"use strict";
|
|
4417
4417
|
init_types();
|
|
4418
4418
|
init_connection();
|
|
@@ -4452,7 +4452,7 @@ __export(doctor_exports, {
|
|
|
4452
4452
|
});
|
|
4453
4453
|
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
4454
4454
|
import { homedir as homedir4 } from "os";
|
|
4455
|
-
import { join as
|
|
4455
|
+
import { join as join22 } from "path";
|
|
4456
4456
|
function redact(name, value) {
|
|
4457
4457
|
if (!value) return "(empty)";
|
|
4458
4458
|
if (SECRET_VARS.has(name)) {
|
|
@@ -4463,9 +4463,9 @@ function redact(name, value) {
|
|
|
4463
4463
|
}
|
|
4464
4464
|
function findMcpJson() {
|
|
4465
4465
|
const candidates = [
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4466
|
+
join22(process.cwd(), ".mcp.json"),
|
|
4467
|
+
join22(homedir4(), ".claude", ".mcp.json"),
|
|
4468
|
+
join22(homedir4(), ".mcp.json")
|
|
4469
4469
|
];
|
|
4470
4470
|
for (const path7 of candidates) {
|
|
4471
4471
|
if (!existsSync12(path7)) continue;
|
|
@@ -4748,9 +4748,9 @@ __export(reset_exports, {
|
|
|
4748
4748
|
removePapiEntry: () => removePapiEntry,
|
|
4749
4749
|
runReset: () => runReset
|
|
4750
4750
|
});
|
|
4751
|
-
import { existsSync as existsSync13, readFileSync as readFileSync16, writeFileSync as
|
|
4751
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
4752
4752
|
import { homedir as homedir5 } from "os";
|
|
4753
|
-
import { join as
|
|
4753
|
+
import { join as join23 } from "path";
|
|
4754
4754
|
import { createInterface } from "readline/promises";
|
|
4755
4755
|
function findResetTarget() {
|
|
4756
4756
|
for (const path7 of CANDIDATE_PATHS()) {
|
|
@@ -4829,7 +4829,7 @@ async function runReset(args = []) {
|
|
|
4829
4829
|
}
|
|
4830
4830
|
}
|
|
4831
4831
|
try {
|
|
4832
|
-
|
|
4832
|
+
writeFileSync9(target.path, removePapiEntry(target), "utf-8");
|
|
4833
4833
|
process.stdout.write(`
|
|
4834
4834
|
\u2713 Removed papi entry from ${target.path}
|
|
4835
4835
|
`);
|
|
@@ -4846,9 +4846,9 @@ var init_reset = __esm({
|
|
|
4846
4846
|
"src/cli/reset.ts"() {
|
|
4847
4847
|
"use strict";
|
|
4848
4848
|
CANDIDATE_PATHS = () => [
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4849
|
+
join23(process.cwd(), ".mcp.json"),
|
|
4850
|
+
join23(homedir5(), ".claude", ".mcp.json"),
|
|
4851
|
+
join23(homedir5(), ".mcp.json")
|
|
4852
4852
|
];
|
|
4853
4853
|
}
|
|
4854
4854
|
});
|
|
@@ -4861,7 +4861,7 @@ __export(audit_exports, {
|
|
|
4861
4861
|
});
|
|
4862
4862
|
import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync7 } from "fs";
|
|
4863
4863
|
import { homedir as homedir6 } from "os";
|
|
4864
|
-
import { join as
|
|
4864
|
+
import { join as join24 } from "path";
|
|
4865
4865
|
function safeListDirs(dir) {
|
|
4866
4866
|
try {
|
|
4867
4867
|
return readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b2) => a.localeCompare(b2));
|
|
@@ -4877,7 +4877,7 @@ function safeListFiles(dir, ext) {
|
|
|
4877
4877
|
}
|
|
4878
4878
|
}
|
|
4879
4879
|
function readMcp(projectPath) {
|
|
4880
|
-
const path7 =
|
|
4880
|
+
const path7 = join24(projectPath, ".mcp.json");
|
|
4881
4881
|
if (!existsSync14(path7)) return { servers: [] };
|
|
4882
4882
|
try {
|
|
4883
4883
|
const parsed = JSON.parse(readFileSync17(path7, "utf-8"));
|
|
@@ -4912,18 +4912,18 @@ function auditProjectSync(projectPath, name) {
|
|
|
4912
4912
|
path: projectPath,
|
|
4913
4913
|
papiProjectId,
|
|
4914
4914
|
mcpServers: servers,
|
|
4915
|
-
skills: safeListDirs(
|
|
4916
|
-
agentSkills: safeListDirs(
|
|
4917
|
-
agents: safeListFiles(
|
|
4918
|
-
hooks: safeListFiles(
|
|
4915
|
+
skills: safeListDirs(join24(projectPath, ".claude", "skills")),
|
|
4916
|
+
agentSkills: safeListDirs(join24(projectPath, ".agents", "skills")),
|
|
4917
|
+
agents: safeListFiles(join24(projectPath, ".claude", "agents"), ".md"),
|
|
4918
|
+
hooks: safeListFiles(join24(projectPath, ".claude", "hooks"), ".sh")
|
|
4919
4919
|
};
|
|
4920
4920
|
}
|
|
4921
4921
|
function discoverProjects() {
|
|
4922
4922
|
const out = [];
|
|
4923
4923
|
for (const root of PROJECT_ROOTS) {
|
|
4924
4924
|
for (const name of safeListDirs(root)) {
|
|
4925
|
-
const path7 =
|
|
4926
|
-
if (existsSync14(
|
|
4925
|
+
const path7 = join24(root, name);
|
|
4926
|
+
if (existsSync14(join24(path7, ".mcp.json")) || existsSync14(join24(path7, ".claude"))) {
|
|
4927
4927
|
out.push({ name, path: path7 });
|
|
4928
4928
|
}
|
|
4929
4929
|
}
|
|
@@ -5088,9 +5088,9 @@ var PROJECT_ROOTS, GLOBAL_SKILLS_DIR, GLOBAL_CLAUDE_JSON, IDLE_WINDOW_DAYS, GLOB
|
|
|
5088
5088
|
var init_audit = __esm({
|
|
5089
5089
|
"src/cli/audit.ts"() {
|
|
5090
5090
|
"use strict";
|
|
5091
|
-
PROJECT_ROOTS = [
|
|
5092
|
-
GLOBAL_SKILLS_DIR =
|
|
5093
|
-
GLOBAL_CLAUDE_JSON =
|
|
5091
|
+
PROJECT_ROOTS = [join24(homedir6(), "Ai-App-Projects"), join24(homedir6(), "android-projects")];
|
|
5092
|
+
GLOBAL_SKILLS_DIR = join24(homedir6(), ".claude", "skills");
|
|
5093
|
+
GLOBAL_CLAUDE_JSON = join24(homedir6(), ".claude.json");
|
|
5094
5094
|
IDLE_WINDOW_DAYS = 30;
|
|
5095
5095
|
GLOBALIZE_THRESHOLD = 3;
|
|
5096
5096
|
__testing2 = { readMcp, computeFlags, formatReport: formatReport2, discoverProjects, auditProjectSync };
|
|
@@ -5102,8 +5102,8 @@ var setup_exports = {};
|
|
|
5102
5102
|
__export(setup_exports, {
|
|
5103
5103
|
runSetup: () => runSetup
|
|
5104
5104
|
});
|
|
5105
|
-
import { existsSync as existsSync15, readFileSync as readFileSync18, writeFileSync as
|
|
5106
|
-
import { join as
|
|
5105
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync10, chmodSync as chmodSync2, statSync as statSync9 } from "fs";
|
|
5106
|
+
import { join as join25 } from "path";
|
|
5107
5107
|
function baseUrl() {
|
|
5108
5108
|
const fromEnv = process.env["PAPI_HOST"] ?? process.env["PAPI_BASE_URL"];
|
|
5109
5109
|
if (fromEnv) return fromEnv.replace(/\/$/, "");
|
|
@@ -5135,7 +5135,7 @@ function sleep(ms) {
|
|
|
5135
5135
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5136
5136
|
}
|
|
5137
5137
|
function writeMcpJson(opts) {
|
|
5138
|
-
const path7 =
|
|
5138
|
+
const path7 = join25(process.cwd(), ".mcp.json");
|
|
5139
5139
|
let parsed = {};
|
|
5140
5140
|
if (existsSync15(path7)) {
|
|
5141
5141
|
try {
|
|
@@ -5160,7 +5160,7 @@ function writeMcpJson(opts) {
|
|
|
5160
5160
|
}
|
|
5161
5161
|
mcpServers.papi = papiEntry;
|
|
5162
5162
|
parsed.mcpServers = mcpServers;
|
|
5163
|
-
|
|
5163
|
+
writeFileSync10(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
5164
5164
|
try {
|
|
5165
5165
|
const mode = statSync9(path7).mode & 511;
|
|
5166
5166
|
if (mode !== 384) chmodSync2(path7, 384);
|
|
@@ -5271,7 +5271,7 @@ var init_setup = __esm({
|
|
|
5271
5271
|
|
|
5272
5272
|
// src/index.ts
|
|
5273
5273
|
import { readFileSync as readFileSync19 } from "fs";
|
|
5274
|
-
import { dirname as dirname7, join as
|
|
5274
|
+
import { dirname as dirname7, join as join26, basename as basename2 } from "path";
|
|
5275
5275
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5276
5276
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5277
5277
|
import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -5425,7 +5425,7 @@ Already have an account? Make sure PAPI_USER_ID is set in your .mcp.json env con
|
|
|
5425
5425
|
import path3 from "path";
|
|
5426
5426
|
import { execSync } from "child_process";
|
|
5427
5427
|
|
|
5428
|
-
//
|
|
5428
|
+
// ../adapter-md/dist/index.js
|
|
5429
5429
|
import { readFile, writeFile, access } from "fs/promises";
|
|
5430
5430
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
5431
5431
|
import { join } from "path";
|
|
@@ -8400,7 +8400,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
8400
8400
|
// src/server.ts
|
|
8401
8401
|
import { readFileSync as readFileSync14 } from "fs";
|
|
8402
8402
|
import { access as access4, readdir as readdir4, readFile as readFile9 } from "fs/promises";
|
|
8403
|
-
import { join as
|
|
8403
|
+
import { join as join21, dirname as dirname6 } from "path";
|
|
8404
8404
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8405
8405
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8406
8406
|
import {
|
|
@@ -9193,6 +9193,19 @@ If a candidate AD body could be invalidated by running a SQL query, refreshing a
|
|
|
9193
9193
|
**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.
|
|
9194
9194
|
|
|
9195
9195
|
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.`;
|
|
9196
|
+
var AD_CONFLICT_SURFACING_RULES = `**A contradiction is NOT a veto \u2014 surface it, never silently shelve it.**
|
|
9197
|
+
|
|
9198
|
+
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.
|
|
9199
|
+
|
|
9200
|
+
**Never do any of these to a task solely because it contradicts, competes with, or duplicates the intent of an existing AD:** cancel it; drop it to P3; defer it silently; omit it from the cycle without comment; or reduce its scope down to the part that fits the AD.
|
|
9201
|
+
|
|
9202
|
+
**Instead, emit a \`decisionConflicts\` entry** for it (see the structured-output schema) with: the task, the AD it collides with, one line on what the AD says versus what the task implies, 2-3 concrete options, and your recommendation. Also render a \`## Decisions Needed\` section in Part 1 listing the same, immediately before the BUILD HANDOFFs.
|
|
9203
|
+
|
|
9204
|
+
Apply will park each conflicted task as **Blocked** with a \`decision-gate\` blocker pointing at that AD. It clears automatically the moment the AD is resolved, confirmed, validated, or superseded \u2014 so the work returns to the board on its own once the user has decided. The task is preserved, not killed.
|
|
9205
|
+
|
|
9206
|
+
**Emit a conflict ONLY when the AD is the reason the work would not otherwise proceed.** If the task is fine to schedule, schedule it. If it is genuinely obsolete, duplicated, or already shipped, cancel it on THOSE grounds and say so \u2014 that is not an AD conflict. Do not manufacture conflicts to look thorough; a plan with zero real conflicts should emit an empty array.
|
|
9207
|
+
|
|
9208
|
+
This applies equally to *competing* work: two tasks proposing rival approaches to the same problem is a decision for the user, not a coin-flip for you.`;
|
|
9196
9209
|
var OUTPUT_QUALITY_RUBRIC = `Quality bar \u2014 before emitting, self-score the artifact 1-10 on five dimensions: (1) **Clarity** \u2014 could a third LLM act on it with no extra context? (2) **Scope tightness** \u2014 one focused unit of work, not three bundled together. (3) **Specificity** \u2014 it names concrete files/paths/tasks/ADs, not vague references like "the auth module". (4) **Dependency surfacing** \u2014 prerequisite or upstream items are called out explicitly. (5) **Success-criteria concreteness** \u2014 "done" is testable and observable, not "looks good". Sum to /50. If any single dimension scores \u22644, or the total is <35, revise the artifact and re-score before emitting \u2014 do not ship a below-threshold artifact. This is a self-check gate, not an output field: do NOT add the scores to the artifact or the structured JSON.`;
|
|
9197
9210
|
var PLAN_SYSTEM = `You are the PAPI Cycle Planner \u2014 an autonomous planning engine for software projects.
|
|
9198
9211
|
You receive project context and produce a planning cycle output with a BUILD HANDOFF.
|
|
@@ -9273,7 +9286,8 @@ After your natural language output, include this EXACT format on its own line:
|
|
|
9273
9286
|
"newTasks": [],
|
|
9274
9287
|
"boardCorrections": [],
|
|
9275
9288
|
"productBrief": null,
|
|
9276
|
-
"activeDecisions": []
|
|
9289
|
+
"activeDecisions": [],
|
|
9290
|
+
"decisionConflicts": [{"taskId": "string \u2014 an existing task ID or a newTasks tempId", "adId": "string \u2014 AD-N this collides with", "conflict": "string \u2014 one line: what the AD says vs what this task implies", "options": ["string \u2014 concrete option, e.g. 'Supersede AD-12 and schedule the task'"], "recommendation": "string \u2014 which option you would take and why, in one sentence"}]
|
|
9277
9291
|
}
|
|
9278
9292
|
\`\`\`
|
|
9279
9293
|
|
|
@@ -9310,6 +9324,7 @@ Everything in Part 1 (natural language) is **display-only**. Part 2 (structured
|
|
|
9310
9324
|
- Updated or created Active Decisions in Part 1? \u2192 Put them in \`activeDecisions\` array (with id and full body including ### heading)
|
|
9311
9325
|
- Found board corrections (wrong priority, missing fields, stale status) in Part 1? \u2192 Put them in \`boardCorrections\` array
|
|
9312
9326
|
- Generated BUILD HANDOFFs in Part 1? \u2192 Put them in \`cycleHandoffs\` array
|
|
9327
|
+
- Wrote a \`## Decisions Needed\` section in Part 1? \u2192 Put every entry in \`decisionConflicts\`. Nothing in that array means the user is never asked, and the work is lost. Omit the array (or leave it empty) when there are genuinely no AD conflicts.
|
|
9313
9328
|
- **\`complexity\` uses the LONG forms only** \u2014 "XS", "Small", "Medium", "Large", "XL". Do NOT reuse the handoff EFFORT short-forms (S/M/L) for task complexity.
|
|
9314
9329
|
|
|
9315
9330
|
**Example with populated fields (DO NOT copy literally \u2014 adapt to your actual analysis):**
|
|
@@ -9497,7 +9512,7 @@ Standard planning cycle with full board review.
|
|
|
9497
9512
|
**\u26A0\uFE0F PRIORITY RECALIBRATION \u2014 do NOT rubber-stamp the submitted priority.** The priority set at idea submission reflects the submitter's view at that time, which may be outdated by the time the planner runs. For EVERY unreviewed task, evaluate its priority FROM SCRATCH against: (a) current horizon/stage/phase goals, (b) recent Active Decision changes, (c) recently shipped functionality that makes this task more or less urgent. If your assessed priority differs from the submitted one, set the new priority in \`boardCorrections\` and include the change in a **Priority Recalibration** paragraph in your cycle log (Step 8): list each changed task by ID, old priority \u2192 new priority, and a 1-sentence rationale. This paragraph is how the user sees what the planner recalibrated and why. If no priorities changed during triage, omit the paragraph.
|
|
9498
9513
|
Also set complexity using the full range \u2014 **XS, Small, Medium, Large, XL** \u2014 based on actual scope, not conservatively. XS = single-line or config change. Small = one file, < 50 lines. Medium = 2-5 files. Large = cross-module, multiple components. XL = architectural, multi-day.
|
|
9499
9514
|
**Module classification for cross-cutting tasks:** When a task title contains "audit"/"unfiltered"/"scoping"/"leak" plus a database-entity name (e.g. "audit ... cycle_learnings reads", "unfiltered cycle_tasks queries"), classify the module by the actual code surface that reads/writes the entity \u2014 not by the tool names mentioned in the title. The reasoning surface (e.g. "health" or "strategy_review") is often unrelated to the data-access surface. Resolve this by treating the entity name as the routing signal: tasks touching dashboard read/write paths belong to the Dashboard module even if the title mentions an MCP tool. Misclassification routes the task to the wrong shared cycle branch and surfaces the wrong MODULE INSTRUCTIONS to the builder.
|
|
9500
|
-
If a task is clearly obsolete, duplicated, or rejected, set its status to "Cancelled" with a \`closureReason\` explaining why.
|
|
9515
|
+
If a task is clearly obsolete, duplicated, or rejected, set its status to "Cancelled" with a \`closureReason\` explaining why. **"It contradicts an Active Decision" is NOT one of those reasons** \u2014 route it to \`decisionConflicts\` instead (see the AD Conflict Surfacing rule below step 7).
|
|
9501
9516
|
**\u2192 PERSIST:** For each task you set reviewed: true, corrected fields on, or marked "Cancelled", include it in \`boardCorrections\` in Part 2.
|
|
9502
9517
|
|
|
9503
9518
|
3. **Board Integrity** \u2014 All tasks have complete fields? Priority still accurate? Duplicates? Stale In Progress tasks?
|
|
@@ -9540,6 +9555,10 @@ Standard planning cycle with full board review.
|
|
|
9540
9555
|
**Epic-aware batching:** Epic is the primary grouping signal for theme coherence. When multiple candidate tasks share the same epic (e.g. "Onboarding Redesign", "Dashboard Polish"), prefer co-scheduling them \u2014 they solve connected problems and benefit from shared context during the build. Steps: (1) After filtering by priority, group eligible tasks by epic. (2) If an epic has 3+ eligible tasks, prefer scheduling 2-4 of them together over cherry-picking across epics. (3) Report the epic distribution in the cycle log (e.g. "4 tasks from Onboarding epic, 1 from Platform"). Priority still overrides: a P0 fix from a different epic always takes precedence.
|
|
9541
9556
|
**Opportunity clustering:** If backlog tasks have an \`opportunity\` field populated, group them by opportunity before selecting. Tasks sharing the same opportunity solve the same user problem \u2014 co-scheduling them produces more coherent cycles. Report opportunity clusters in the cycle log when present (e.g. "3 tasks clustered under 'planner accuracy' opportunity").
|
|
9542
9557
|
|
|
9558
|
+
${AD_CONFLICT_SURFACING_RULES}
|
|
9559
|
+
|
|
9560
|
+
**Where this fires:** during Inbox Triage (step 2), the Priority Drift Check (step 3), and selection (step 7). A task whose notes already carry an \`AD-CONFLICT:\` line, or an "AD-N alignment/conflict candidate" note from the idea tool, is a mandatory candidate \u2014 read it, and either schedule it, cancel it on non-AD grounds, or emit the conflict. Do not leave it sitting silently in Backlog for a third cycle.
|
|
9561
|
+
|
|
9543
9562
|
8. **Cycle Log** \u2014 Write 5-10 line entry: what was triaged, what was recommended and why, observations, AD updates. Include a **Priority Recalibration** paragraph if any unreviewed task priorities were changed during triage (Step 2) \u2014 list each by ID with old \u2192 new priority and rationale. Include a **Priority Drift Suggestions** paragraph if reviewed task drift was detected (Step 3).
|
|
9544
9563
|
**Cycle Notes** \u2014 Optionally include 1-3 lines of cycle-level observations in \`cycleLogNotes\`: estimation accuracy patterns, recurring blockers, velocity trends, or dependency signals. These notes persist across cycles so future planning runs can learn from them. Use null if there are no noteworthy observations this cycle.
|
|
9545
9564
|
|
|
@@ -9989,6 +10008,13 @@ function coerceStructuredOutput(parsed) {
|
|
|
9989
10008
|
})) : [];
|
|
9990
10009
|
const cycleTaskIds = Array.isArray(parsed.cycleTaskIds) ? parsed.cycleTaskIds.map((id) => coerceToString(id)) : void 0;
|
|
9991
10010
|
const carryForward = coerceCarryForward(parsed.cycleLogCarryForward);
|
|
10011
|
+
const decisionConflicts = Array.isArray(parsed.decisionConflicts) ? parsed.decisionConflicts.map((c) => ({
|
|
10012
|
+
taskId: coerceToString(c.taskId).trim(),
|
|
10013
|
+
adId: coerceToString(c.adId).trim(),
|
|
10014
|
+
conflict: coerceToString(c.conflict).trim(),
|
|
10015
|
+
options: Array.isArray(c.options) ? c.options.map((o) => coerceToString(o)) : [],
|
|
10016
|
+
recommendation: coerceToString(c.recommendation).trim()
|
|
10017
|
+
})).filter((c) => c.taskId.length > 0 && c.adId.length > 0) : void 0;
|
|
9992
10018
|
return {
|
|
9993
10019
|
cycleLogTitle: coerceToString(parsed.cycleLogTitle),
|
|
9994
10020
|
cycleLogContent: coerceToString(parsed.cycleLogContent),
|
|
@@ -10004,7 +10030,8 @@ function coerceStructuredOutput(parsed) {
|
|
|
10004
10030
|
newTasks,
|
|
10005
10031
|
boardCorrections,
|
|
10006
10032
|
productBrief: parsed.productBrief === null ? null : coerceToString(parsed.productBrief),
|
|
10007
|
-
activeDecisions
|
|
10033
|
+
activeDecisions,
|
|
10034
|
+
decisionConflicts
|
|
10008
10035
|
};
|
|
10009
10036
|
}
|
|
10010
10037
|
var REVIEW_SYSTEM_COMPRESSION_SECTION = `
|
|
@@ -10079,6 +10106,7 @@ You MUST cover these 5 sections. Each is mandatory.
|
|
|
10079
10106
|
- Only flag ADs that represent a genuine strategic question requiring owner input
|
|
10080
10107
|
- Note any hierarchy/phase issues worth correcting (1-2 bullets max)
|
|
10081
10108
|
- Delete ADs that are legacy, process-level, or redundant without discussion
|
|
10109
|
+
- **Blocked-on-decision tasks are evidence (task-3219).** If the board carries tasks Blocked behind a \`decision-gate\` on an AD, that AD is actively costing the project work. Resolve it here \u2014 supersede, modify, or explicitly reaffirm it with a one-line reason. Reaffirming is a legitimate answer; leaving it unaddressed is not, because the blocked work stays parked until someone rules.
|
|
10082
10110
|
|
|
10083
10111
|
${AD_REJECTION_RULES}
|
|
10084
10112
|
|
|
@@ -11692,6 +11720,52 @@ function applyContextTier(ctx, cycleCount) {
|
|
|
11692
11720
|
}
|
|
11693
11721
|
return { tier, label };
|
|
11694
11722
|
}
|
|
11723
|
+
var PARKABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
11724
|
+
"Backlog",
|
|
11725
|
+
"In Cycle",
|
|
11726
|
+
"Ready",
|
|
11727
|
+
"Deferred"
|
|
11728
|
+
]);
|
|
11729
|
+
async function parkDecisionConflicts(adapter2, conflicts, newTaskIdMap, blockedCycle, scheduledTaskIds = []) {
|
|
11730
|
+
if (conflicts.length === 0) return [];
|
|
11731
|
+
const scheduled = new Set(scheduledTaskIds.map((id) => id.toLowerCase()));
|
|
11732
|
+
const results = [];
|
|
11733
|
+
for (const conflict of conflicts) {
|
|
11734
|
+
const resolvedTaskId = newTaskIdMap.get(conflict.taskId) ?? conflict.taskId;
|
|
11735
|
+
const base = { ...conflict, resolvedTaskId, parked: false };
|
|
11736
|
+
if (scheduled.has(resolvedTaskId.toLowerCase())) {
|
|
11737
|
+
results.push({ ...base, skipReason: "scheduled in this cycle \u2014 left in the cycle, conflict still surfaced" });
|
|
11738
|
+
continue;
|
|
11739
|
+
}
|
|
11740
|
+
try {
|
|
11741
|
+
const [task] = await adapter2.getTasks([resolvedTaskId]);
|
|
11742
|
+
if (!task) {
|
|
11743
|
+
results.push({ ...base, skipReason: "task not found" });
|
|
11744
|
+
continue;
|
|
11745
|
+
}
|
|
11746
|
+
if (!PARKABLE_STATUSES.has(task.status)) {
|
|
11747
|
+
results.push({ ...base, skipReason: `status is ${task.status} \u2014 left as-is` });
|
|
11748
|
+
continue;
|
|
11749
|
+
}
|
|
11750
|
+
if (task.status === "Deferred") {
|
|
11751
|
+
await adapter2.updateTask(resolvedTaskId, { status: "Backlog" });
|
|
11752
|
+
}
|
|
11753
|
+
await adapter2.updateTask(resolvedTaskId, {
|
|
11754
|
+
status: "Blocked",
|
|
11755
|
+
blocker: {
|
|
11756
|
+
type: "decision-gate",
|
|
11757
|
+
ref: conflict.adId,
|
|
11758
|
+
reason: conflict.conflict || `Contradicts ${conflict.adId} \u2014 awaiting the owner's decision.`,
|
|
11759
|
+
blockedCycle
|
|
11760
|
+
}
|
|
11761
|
+
});
|
|
11762
|
+
results.push({ ...base, parked: true });
|
|
11763
|
+
} catch (err) {
|
|
11764
|
+
results.push({ ...base, skipReason: err instanceof Error ? err.message : String(err) });
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
return results;
|
|
11768
|
+
}
|
|
11695
11769
|
function determineMode(totalCycles) {
|
|
11696
11770
|
if (totalCycles === 0) return "bootstrap";
|
|
11697
11771
|
return "full";
|
|
@@ -13203,6 +13277,7 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
|
|
|
13203
13277
|
let writeBackWarnings;
|
|
13204
13278
|
let writeSummary;
|
|
13205
13279
|
let skippedCancellations;
|
|
13280
|
+
let decisionConflicts;
|
|
13206
13281
|
const persistenceFailed = !data;
|
|
13207
13282
|
if (data) {
|
|
13208
13283
|
try {
|
|
@@ -13229,6 +13304,19 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
|
|
|
13229
13304
|
for (const [placeholder, realId] of newTaskIdMap) {
|
|
13230
13305
|
resolvedDisplayText = resolvedDisplayText.replaceAll(placeholder, realId);
|
|
13231
13306
|
}
|
|
13307
|
+
if (data.decisionConflicts && data.decisionConflicts.length > 0) {
|
|
13308
|
+
try {
|
|
13309
|
+
const parked = await parkDecisionConflicts(
|
|
13310
|
+
adapter2,
|
|
13311
|
+
data.decisionConflicts,
|
|
13312
|
+
newTaskIdMap,
|
|
13313
|
+
cycleNumber + 1,
|
|
13314
|
+
ws.taskIds
|
|
13315
|
+
);
|
|
13316
|
+
if (parked.length > 0) decisionConflicts = parked;
|
|
13317
|
+
} catch {
|
|
13318
|
+
}
|
|
13319
|
+
}
|
|
13232
13320
|
if (adapter2.insertPlanRun) {
|
|
13233
13321
|
const durationMs = Date.now() - applyStartMs + (planRunMeta?.prepareStartMs !== void 0 ? Date.now() - planRunMeta.prepareStartMs : 0);
|
|
13234
13322
|
adapter2.insertPlanRun({
|
|
@@ -13281,7 +13369,8 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
|
|
|
13281
13369
|
writeBackFailed,
|
|
13282
13370
|
writeBackWarnings,
|
|
13283
13371
|
writeSummary,
|
|
13284
|
-
skippedCancellations
|
|
13372
|
+
skippedCancellations,
|
|
13373
|
+
decisionConflicts
|
|
13285
13374
|
};
|
|
13286
13375
|
}
|
|
13287
13376
|
async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker, density) {
|
|
@@ -13900,6 +13989,47 @@ async function resolveLlmResponse(inlineResponse, filePath) {
|
|
|
13900
13989
|
return { ok: true, llmResponse: contents };
|
|
13901
13990
|
}
|
|
13902
13991
|
|
|
13992
|
+
// src/lib/overflow.ts
|
|
13993
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
13994
|
+
import { tmpdir } from "os";
|
|
13995
|
+
import { join as join3 } from "path";
|
|
13996
|
+
import { createHash as createHash2 } from "crypto";
|
|
13997
|
+
var DISPATCH_THRESHOLD_BYTES = 50 * 1024;
|
|
13998
|
+
var ELIDE_THRESHOLD_BYTES = 90 * 1024;
|
|
13999
|
+
var INLINE_CEILING_BYTES = 40 * 1024;
|
|
14000
|
+
function byteLength(s) {
|
|
14001
|
+
return Buffer.byteLength(s, "utf-8");
|
|
14002
|
+
}
|
|
14003
|
+
function shouldDispatch(contextBytes, explicit) {
|
|
14004
|
+
if (explicit === true) return true;
|
|
14005
|
+
if (explicit === false) return false;
|
|
14006
|
+
if (process.env.PAPI_AUTO_DISPATCH === "false") return false;
|
|
14007
|
+
return contextBytes > DISPATCH_THRESHOLD_BYTES;
|
|
14008
|
+
}
|
|
14009
|
+
function canSpill(adapterType) {
|
|
14010
|
+
return adapterType !== "proxy";
|
|
14011
|
+
}
|
|
14012
|
+
function spillPath(kind, projectId, callerKey) {
|
|
14013
|
+
const id = createHash2("sha256").update(`${projectId ?? "no-project"}|${callerKey ?? "default"}`).digest("hex").slice(0, 16);
|
|
14014
|
+
return join3(tmpdir(), `papi-${kind}-${id}.md`);
|
|
14015
|
+
}
|
|
14016
|
+
function spill(kind, content, projectId, callerKey) {
|
|
14017
|
+
const path7 = spillPath(kind, projectId, callerKey);
|
|
14018
|
+
writeFileSync2(path7, content, { mode: 384 });
|
|
14019
|
+
return path7;
|
|
14020
|
+
}
|
|
14021
|
+
function planDelivery(input) {
|
|
14022
|
+
const bytes = byteLength(input.payload);
|
|
14023
|
+
if (bytes <= INLINE_CEILING_BYTES) return { mode: "inline", payload: input.payload };
|
|
14024
|
+
if (!canSpill(input.adapterType)) return { mode: "undeliverable", bytes };
|
|
14025
|
+
try {
|
|
14026
|
+
const path7 = spill(input.kind, input.payload, input.projectId, input.callerKey);
|
|
14027
|
+
return { mode: "spilled", path: path7, bytes };
|
|
14028
|
+
} catch {
|
|
14029
|
+
return { mode: "undeliverable", bytes };
|
|
14030
|
+
}
|
|
14031
|
+
}
|
|
14032
|
+
|
|
13903
14033
|
// src/services/session-guidance.ts
|
|
13904
14034
|
var DEFAULT_CALLER_KEY = "__default__";
|
|
13905
14035
|
var sessionStates = /* @__PURE__ */ new Map();
|
|
@@ -14100,20 +14230,20 @@ var PerCallerCache = class {
|
|
|
14100
14230
|
};
|
|
14101
14231
|
|
|
14102
14232
|
// src/lib/plan-prepare-store.ts
|
|
14103
|
-
import { createHash as
|
|
14104
|
-
import { readFileSync as readFileSync2, writeFileSync as
|
|
14105
|
-
import { tmpdir } from "os";
|
|
14106
|
-
import { join as
|
|
14233
|
+
import { createHash as createHash3 } from "crypto";
|
|
14234
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, unlinkSync, existsSync, statSync } from "fs";
|
|
14235
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
14236
|
+
import { join as join4 } from "path";
|
|
14107
14237
|
var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
14108
14238
|
var DEFAULT_CALLER_KEY3 = "__default__";
|
|
14109
|
-
function
|
|
14110
|
-
const id =
|
|
14111
|
-
return
|
|
14239
|
+
function spillPath2(projectId, callerKey) {
|
|
14240
|
+
const id = createHash3("sha256").update(`${projectId ?? "no-project"}|${callerKey ?? DEFAULT_CALLER_KEY3}`).digest("hex").slice(0, 16);
|
|
14241
|
+
return join4(tmpdir2(), `papi-plan-prepare-${id}.json`);
|
|
14112
14242
|
}
|
|
14113
14243
|
function savePrepareSpill(projectId, callerKey, state) {
|
|
14114
14244
|
try {
|
|
14115
|
-
|
|
14116
|
-
|
|
14245
|
+
writeFileSync3(
|
|
14246
|
+
spillPath2(projectId, callerKey),
|
|
14117
14247
|
JSON.stringify({ savedAt: Date.now(), state }),
|
|
14118
14248
|
{ mode: 384 }
|
|
14119
14249
|
);
|
|
@@ -14121,7 +14251,7 @@ function savePrepareSpill(projectId, callerKey, state) {
|
|
|
14121
14251
|
}
|
|
14122
14252
|
}
|
|
14123
14253
|
function loadPrepareSpill(projectId, callerKey) {
|
|
14124
|
-
const path7 =
|
|
14254
|
+
const path7 = spillPath2(projectId, callerKey);
|
|
14125
14255
|
try {
|
|
14126
14256
|
if (!existsSync(path7)) return void 0;
|
|
14127
14257
|
if (Date.now() - statSync(path7).mtimeMs > SPILL_TTL_MS) {
|
|
@@ -14136,18 +14266,18 @@ function loadPrepareSpill(projectId, callerKey) {
|
|
|
14136
14266
|
}
|
|
14137
14267
|
function clearPrepareSpill(projectId, callerKey) {
|
|
14138
14268
|
try {
|
|
14139
|
-
const path7 =
|
|
14269
|
+
const path7 = spillPath2(projectId, callerKey);
|
|
14140
14270
|
if (existsSync(path7)) unlinkSync(path7);
|
|
14141
14271
|
} catch {
|
|
14142
14272
|
}
|
|
14143
14273
|
}
|
|
14144
14274
|
function contextPath(projectId, callerKey) {
|
|
14145
|
-
const id =
|
|
14146
|
-
return
|
|
14275
|
+
const id = createHash3("sha256").update(`${projectId ?? "no-project"}|${callerKey ?? DEFAULT_CALLER_KEY3}`).digest("hex").slice(0, 16);
|
|
14276
|
+
return join4(tmpdir2(), `papi-plan-context-${id}.md`);
|
|
14147
14277
|
}
|
|
14148
14278
|
function savePrepareContextFile(projectId, callerKey, content) {
|
|
14149
14279
|
const path7 = contextPath(projectId, callerKey);
|
|
14150
|
-
|
|
14280
|
+
writeFileSync3(path7, content, { mode: 384 });
|
|
14151
14281
|
return path7;
|
|
14152
14282
|
}
|
|
14153
14283
|
|
|
@@ -14287,6 +14417,20 @@ function formatPlanResult(result) {
|
|
|
14287
14417
|
lines.push('To apply these cancellations, re-run `plan` with `mode: "apply"` and `confirm_cancellations: true`.');
|
|
14288
14418
|
lines.push("To keep these tasks, do nothing \u2014 they remain on the board.");
|
|
14289
14419
|
}
|
|
14420
|
+
const conflicts = result.decisionConflicts ?? [];
|
|
14421
|
+
if (conflicts.length > 0) {
|
|
14422
|
+
lines.push("");
|
|
14423
|
+
lines.push(`\u{1F500} **${conflicts.length} decision(s) need your call \u2014 work was NOT dropped.**`);
|
|
14424
|
+
lines.push("These tasks cut against a live Active Decision. An AD is *active* \u2014 it can be superseded, so the planner parked the work instead of shelving it:");
|
|
14425
|
+
for (const c of conflicts) {
|
|
14426
|
+
const state = c.parked ? `Blocked behind ${c.adId}` : `still ${c.skipReason ?? "unparked"}`;
|
|
14427
|
+
lines.push(`- **${c.resolvedTaskId} vs ${c.adId}** \u2014 ${c.conflict} _(${state})_`);
|
|
14428
|
+
if (c.options.length > 0) lines.push(` Options: ${c.options.join(" | ")}`);
|
|
14429
|
+
if (c.recommendation) lines.push(` Planner recommends: ${c.recommendation}`);
|
|
14430
|
+
}
|
|
14431
|
+
lines.push("");
|
|
14432
|
+
lines.push('Decide: `strategy_change` with `mode: "capture"` to supersede or modify the AD (parked tasks auto-unblock once it moves), or `board_edit` to cancel the task if the AD stands.');
|
|
14433
|
+
}
|
|
14290
14434
|
if (result.skipHandoffs) {
|
|
14291
14435
|
const taskCount = result.writeSummary?.taskIds.length ?? 0;
|
|
14292
14436
|
lines.push("", `Next: run \`handoff_generate\` to create BUILD HANDOFFs for your ${taskCount} cycle task(s), then \`build_list\` to start building.`);
|
|
@@ -14409,16 +14553,8 @@ async function handlePlan(adapter2, config2, args) {
|
|
|
14409
14553
|
};
|
|
14410
14554
|
planPrepareCache.set(callerKey, prepareState);
|
|
14411
14555
|
savePrepareSpill(adapter2.getProjectId?.(), callerKey, prepareState);
|
|
14412
|
-
const
|
|
14413
|
-
const
|
|
14414
|
-
let dispatch;
|
|
14415
|
-
if (args.dispatch === "inline" || args.dispatch === "subagent") {
|
|
14416
|
-
dispatch = args.dispatch;
|
|
14417
|
-
} else if (autoDispatchEnabled && result.contextBytes !== void 0 && result.contextBytes > autoDispatchThreshold) {
|
|
14418
|
-
dispatch = "subagent";
|
|
14419
|
-
} else {
|
|
14420
|
-
dispatch = "inline";
|
|
14421
|
-
}
|
|
14556
|
+
const explicit = args.dispatch === "inline" ? false : args.dispatch === "subagent" ? true : void 0;
|
|
14557
|
+
const dispatch = shouldDispatch(result.contextBytes ?? 0, explicit) ? "subagent" : "inline";
|
|
14422
14558
|
const modeLabel = result.mode === "bootstrap" ? "Bootstrap" : "Full";
|
|
14423
14559
|
const header = result.strategyReviewWarning ? `${result.strategyReviewWarning}
|
|
14424
14560
|
` : "";
|
|
@@ -14532,10 +14668,10 @@ ${result.userMessage}
|
|
|
14532
14668
|
}
|
|
14533
14669
|
|
|
14534
14670
|
// src/services/strategy.ts
|
|
14535
|
-
import { randomUUID as randomUUID10, createHash as
|
|
14671
|
+
import { randomUUID as randomUUID10, createHash as createHash4 } from "crypto";
|
|
14536
14672
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
14537
14673
|
import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
14538
|
-
import { join as
|
|
14674
|
+
import { join as join5 } from "path";
|
|
14539
14675
|
import { homedir as homedir2 } from "os";
|
|
14540
14676
|
|
|
14541
14677
|
// src/services/idea.ts
|
|
@@ -14885,14 +15021,22 @@ ${lines.join("\n")}
|
|
|
14885
15021
|
}
|
|
14886
15022
|
}
|
|
14887
15023
|
let notesWithAlignment = input.notes || "";
|
|
15024
|
+
let detectedAdIds = [];
|
|
14888
15025
|
try {
|
|
14889
15026
|
const ads = await adapter2.getActiveDecisions();
|
|
14890
15027
|
const ideaCombined = `${input.text} ${input.notes ?? ""}`;
|
|
14891
15028
|
const alignmentMatches = findAdAlignmentMatches(ideaCombined, ads);
|
|
14892
15029
|
if (alignmentMatches.length > 0) {
|
|
14893
15030
|
notesWithAlignment = `${notesWithAlignment}${formatAdAlignmentNote(alignmentMatches)}`.trim();
|
|
15031
|
+
detectedAdIds = alignmentMatches.map((m) => m.displayId);
|
|
14894
15032
|
}
|
|
14895
15033
|
} catch {
|
|
15034
|
+
}
|
|
15035
|
+
const conflictsWithAd = input.conflictsWithAd?.trim();
|
|
15036
|
+
if (conflictsWithAd) {
|
|
15037
|
+
notesWithAlignment = `${notesWithAlignment}
|
|
15038
|
+
|
|
15039
|
+
AD-CONFLICT: ${conflictsWithAd} \u2014 submitter flagged this as cutting against that decision. Owner decides: supersede the AD or drop the task.`.trim();
|
|
14896
15040
|
}
|
|
14897
15041
|
const inherited = await resolveVisibilityFromDocs(adapter2, [input.docRef]);
|
|
14898
15042
|
const task = await adapter2.createTask({
|
|
@@ -14961,17 +15105,52 @@ ${lines.join("\n")}
|
|
|
14961
15105
|
} catch {
|
|
14962
15106
|
}
|
|
14963
15107
|
}
|
|
15108
|
+
let gated = false;
|
|
15109
|
+
if (conflictsWithAd) {
|
|
15110
|
+
try {
|
|
15111
|
+
await adapter2.updateTask(task.id, {
|
|
15112
|
+
status: "Blocked",
|
|
15113
|
+
blocker: {
|
|
15114
|
+
type: "decision-gate",
|
|
15115
|
+
ref: conflictsWithAd,
|
|
15116
|
+
reason: `Submitted as cutting against ${conflictsWithAd}. Awaiting the owner's decision.`,
|
|
15117
|
+
blockedCycle: health.totalCycles
|
|
15118
|
+
}
|
|
15119
|
+
});
|
|
15120
|
+
gated = true;
|
|
15121
|
+
} catch {
|
|
15122
|
+
}
|
|
15123
|
+
}
|
|
14964
15124
|
const typeNote = typeInferred ? ` [type: ${taskType} \u2014 inferred from text]` : "";
|
|
14965
15125
|
const visibilityNote = inherited.visibility !== "public" ? ` [visibility: ${inherited.visibility} \u2014 inherited from source doc]` : "";
|
|
14966
15126
|
const mismatchNote = inherited.mismatchWarning ? `
|
|
14967
15127
|
|
|
14968
15128
|
\u26A0\uFE0F ${inherited.mismatchWarning}` : "";
|
|
15129
|
+
const adIds = conflictsWithAd ? [conflictsWithAd] : detectedAdIds;
|
|
15130
|
+
const landing = gated ? "created and gated on the decision" : "added to backlog";
|
|
14969
15131
|
return {
|
|
14970
15132
|
routing: "task",
|
|
14971
15133
|
task,
|
|
14972
|
-
message: `${task.id}: "${task.title}" \u2014
|
|
15134
|
+
message: `${task.id}: "${task.title}" \u2014 ${landing}${typeNote}${visibilityNote}${mismatchNote}`,
|
|
15135
|
+
...adIds.length > 0 ? { adConflicts: { adIds, explicit: Boolean(conflictsWithAd), gated } } : {}
|
|
14973
15136
|
};
|
|
14974
15137
|
}
|
|
15138
|
+
function buildAdConflictNote(conflictsWithAd, detectedAdIds, gated) {
|
|
15139
|
+
if (conflictsWithAd) {
|
|
15140
|
+
const state = gated ? `Parked as **Blocked** behind a decision gate on ${conflictsWithAd} \u2014 it auto-unblocks once that AD is superseded, modified, or reaffirmed.` : `Recorded against ${conflictsWithAd} (gate could not be applied \u2014 the AD-CONFLICT note is on the task).`;
|
|
15141
|
+
return `
|
|
15142
|
+
|
|
15143
|
+
\u{1F500} **Conflicts with ${conflictsWithAd}.** ${state}
|
|
15144
|
+
**Tell the user now** \u2014 an AD is *active*, so this is their call to make, not yours. Offer both paths: supersede the AD via \`strategy_change\`, or leave the AD standing and drop the task.`;
|
|
15145
|
+
}
|
|
15146
|
+
if (detectedAdIds.length > 0) {
|
|
15147
|
+
return `
|
|
15148
|
+
|
|
15149
|
+
\u{1F500} **Possible conflict with ${detectedAdIds.join(", ")}** (keyword match \u2014 verify it is real).
|
|
15150
|
+
If it IS a real contradiction, **do not shelve the idea on those grounds**: surface it to the user and re-submit with \`conflicts_with_ad\` so the task is gated on the decision instead of quietly competing in the backlog.`;
|
|
15151
|
+
}
|
|
15152
|
+
return "";
|
|
15153
|
+
}
|
|
14975
15154
|
var CANVAS_SECTION_LABELS = {
|
|
14976
15155
|
landscape: "Landscape References",
|
|
14977
15156
|
journeys: "User Journeys",
|
|
@@ -15542,7 +15721,7 @@ ${surpriseDigestText}` : buildReportsText;
|
|
|
15542
15721
|
try {
|
|
15543
15722
|
const fullCanvasText = formatDiscoveryCanvas(canvas);
|
|
15544
15723
|
if (fullCanvasText) {
|
|
15545
|
-
const canvasHash =
|
|
15724
|
+
const canvasHash = createHash4("md5").update(fullCanvasText).digest("hex");
|
|
15546
15725
|
const lastReview = previousStrategyReviews?.[0];
|
|
15547
15726
|
const prevHash = lastReview?.structuredData?.canvasHash;
|
|
15548
15727
|
if (prevHash && prevHash === canvasHash) {
|
|
@@ -15612,11 +15791,11 @@ ${lines.join("\n")}`;
|
|
|
15612
15791
|
}
|
|
15613
15792
|
let recentPlansText;
|
|
15614
15793
|
try {
|
|
15615
|
-
const plansDir =
|
|
15794
|
+
const plansDir = join5(homedir2(), ".claude", "plans");
|
|
15616
15795
|
if (existsSync2(plansDir)) {
|
|
15617
15796
|
const lastReviewDate = previousStrategyReviews?.[0]?.createdAt ? new Date(previousStrategyReviews[0].createdAt) : /* @__PURE__ */ new Date(0);
|
|
15618
15797
|
const planFiles = readdirSync(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
15619
|
-
const fullPath =
|
|
15798
|
+
const fullPath = join5(plansDir, f);
|
|
15620
15799
|
const stat4 = statSync2(fullPath);
|
|
15621
15800
|
return { name: f, modified: stat4.mtime, size: stat4.size };
|
|
15622
15801
|
}).filter((f) => f.modified > lastReviewDate).sort((a, b2) => b2.modified.getTime() - a.modified.getTime()).slice(0, 15);
|
|
@@ -15633,7 +15812,7 @@ ${lines.join("\n")}`;
|
|
|
15633
15812
|
}
|
|
15634
15813
|
let unregisteredDocsText;
|
|
15635
15814
|
try {
|
|
15636
|
-
const docsDir =
|
|
15815
|
+
const docsDir = join5(projectRoot, "docs");
|
|
15637
15816
|
if (hasLocalWorkspace() && existsSync2(docsDir)) {
|
|
15638
15817
|
const registeredPaths = new Set(
|
|
15639
15818
|
(registeredDocs ?? []).map((d) => d.path).filter(Boolean)
|
|
@@ -15641,7 +15820,7 @@ ${lines.join("\n")}`;
|
|
|
15641
15820
|
const allDocFiles = [];
|
|
15642
15821
|
const scanDir = (dir, prefix) => {
|
|
15643
15822
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
15644
|
-
if (entry.isDirectory()) scanDir(
|
|
15823
|
+
if (entry.isDirectory()) scanDir(join5(dir, entry.name), `${prefix}${entry.name}/`);
|
|
15645
15824
|
else if (entry.name.endsWith(".md")) allDocFiles.push(`${prefix}${entry.name}`);
|
|
15646
15825
|
}
|
|
15647
15826
|
};
|
|
@@ -15981,7 +16160,7 @@ ${cleanContent}`;
|
|
|
15981
16160
|
const currentCanvas = await adapter2.readDiscoveryCanvas();
|
|
15982
16161
|
const canvasText = formatDiscoveryCanvas(currentCanvas);
|
|
15983
16162
|
if (canvasText) {
|
|
15984
|
-
return { ...sd, canvasHash:
|
|
16163
|
+
return { ...sd, canvasHash: createHash4("md5").update(canvasText).digest("hex") };
|
|
15985
16164
|
}
|
|
15986
16165
|
} catch {
|
|
15987
16166
|
}
|
|
@@ -17105,16 +17284,8 @@ ${recLines.join("\n")}
|
|
|
17105
17284
|
userMessage: result.userMessage,
|
|
17106
17285
|
contextBytes: reviewContextBytes
|
|
17107
17286
|
});
|
|
17108
|
-
const
|
|
17109
|
-
const
|
|
17110
|
-
let dispatch;
|
|
17111
|
-
if (args.dispatch === "inline" || args.dispatch === "subagent") {
|
|
17112
|
-
dispatch = args.dispatch;
|
|
17113
|
-
} else if (autoDispatchEnabled && reviewContextBytes > autoDispatchThreshold) {
|
|
17114
|
-
dispatch = "subagent";
|
|
17115
|
-
} else {
|
|
17116
|
-
dispatch = "inline";
|
|
17117
|
-
}
|
|
17287
|
+
const explicit = args.dispatch === "inline" ? false : args.dispatch === "subagent" ? true : void 0;
|
|
17288
|
+
const dispatch = shouldDispatch(reviewContextBytes, explicit) ? "subagent" : "inline";
|
|
17118
17289
|
if (dispatch === "subagent") {
|
|
17119
17290
|
const dispatchPrompt = buildSubagentDispatchPrompt({
|
|
17120
17291
|
tool: "strategy_review",
|
|
@@ -18125,15 +18296,15 @@ ${existing}` : entry;
|
|
|
18125
18296
|
|
|
18126
18297
|
// src/services/setup.ts
|
|
18127
18298
|
import { mkdir, writeFile as writeFile2, readFile as readFile4, readdir, access as access2, stat as stat2, chmod } from "fs/promises";
|
|
18128
|
-
import { join as
|
|
18299
|
+
import { join as join10, basename, extname, dirname as dirname3, relative } from "path";
|
|
18129
18300
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
18130
18301
|
|
|
18131
18302
|
// src/lib/detect-codebase.ts
|
|
18132
18303
|
import { existsSync as existsSync3 } from "fs";
|
|
18133
18304
|
import { readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
18134
|
-
import { join as
|
|
18305
|
+
import { join as join6 } from "path";
|
|
18135
18306
|
function detectCodebaseType(projectRoot) {
|
|
18136
|
-
if (existsSync3(
|
|
18307
|
+
if (existsSync3(join6(projectRoot, ".git"))) {
|
|
18137
18308
|
return "existing_codebase";
|
|
18138
18309
|
}
|
|
18139
18310
|
const manifests = [
|
|
@@ -18147,7 +18318,7 @@ function detectCodebaseType(projectRoot) {
|
|
|
18147
18318
|
"CMakeLists.txt"
|
|
18148
18319
|
];
|
|
18149
18320
|
for (const manifest of manifests) {
|
|
18150
|
-
if (existsSync3(
|
|
18321
|
+
if (existsSync3(join6(projectRoot, manifest))) {
|
|
18151
18322
|
return "existing_codebase";
|
|
18152
18323
|
}
|
|
18153
18324
|
}
|
|
@@ -18155,7 +18326,7 @@ function detectCodebaseType(projectRoot) {
|
|
|
18155
18326
|
const entries = readdirSync2(projectRoot).filter((f) => !f.startsWith("."));
|
|
18156
18327
|
const fileCount = entries.filter((f) => {
|
|
18157
18328
|
try {
|
|
18158
|
-
return statSync3(
|
|
18329
|
+
return statSync3(join6(projectRoot, f)).isFile();
|
|
18159
18330
|
} catch {
|
|
18160
18331
|
return false;
|
|
18161
18332
|
}
|
|
@@ -18168,20 +18339,20 @@ function detectCodebaseType(projectRoot) {
|
|
|
18168
18339
|
|
|
18169
18340
|
// src/lib/agents-bundle.ts
|
|
18170
18341
|
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
18171
|
-
import { dirname, join as
|
|
18342
|
+
import { dirname, join as join7, resolve } from "path";
|
|
18172
18343
|
import { fileURLToPath } from "url";
|
|
18173
|
-
var PROJECT_BUNDLE_REL =
|
|
18344
|
+
var PROJECT_BUNDLE_REL = join7(".agents", "skills", "papi-cycle");
|
|
18174
18345
|
function bundleDestRel(rel) {
|
|
18175
|
-
return rel === "AGENTS.md" ? "AGENTS.md" :
|
|
18346
|
+
return rel === "AGENTS.md" ? "AGENTS.md" : join7(PROJECT_BUNDLE_REL, rel);
|
|
18176
18347
|
}
|
|
18177
18348
|
function claudeSkillDestRel(rel) {
|
|
18178
|
-
return rel === "AGENTS.md" ? void 0 :
|
|
18349
|
+
return rel === "AGENTS.md" ? void 0 : join7(".claude", "skills", rel);
|
|
18179
18350
|
}
|
|
18180
18351
|
function resolveBundleDir() {
|
|
18181
18352
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
18182
18353
|
for (let i = 0; i < 5; i++) {
|
|
18183
|
-
const candidate =
|
|
18184
|
-
if (existsSync4(
|
|
18354
|
+
const candidate = join7(dir, "skills", "papi-cycle");
|
|
18355
|
+
if (existsSync4(join7(candidate, "AGENTS.md"))) return candidate;
|
|
18185
18356
|
const parent = resolve(dir, "..");
|
|
18186
18357
|
if (parent === dir) break;
|
|
18187
18358
|
dir = parent;
|
|
@@ -18193,8 +18364,8 @@ function readBundleFiles(bundleDir = resolveBundleDir()) {
|
|
|
18193
18364
|
const files = [];
|
|
18194
18365
|
const walk = (abs, rel) => {
|
|
18195
18366
|
for (const entry of readdirSync3(abs, { withFileTypes: true })) {
|
|
18196
|
-
const childAbs =
|
|
18197
|
-
const childRel = rel ?
|
|
18367
|
+
const childAbs = join7(abs, entry.name);
|
|
18368
|
+
const childRel = rel ? join7(rel, entry.name) : entry.name;
|
|
18198
18369
|
if (entry.isDirectory()) walk(childAbs, childRel);
|
|
18199
18370
|
else if (entry.isFile()) files.push({ rel: childRel, content: readFileSync3(childAbs, "utf8") });
|
|
18200
18371
|
}
|
|
@@ -18203,13 +18374,14 @@ function readBundleFiles(bundleDir = resolveBundleDir()) {
|
|
|
18203
18374
|
return files;
|
|
18204
18375
|
}
|
|
18205
18376
|
function planBundleInstall(projectRoot, projectName, opts = {}) {
|
|
18377
|
+
const canProbe = opts.skipExisting === true && projectRoot !== "";
|
|
18206
18378
|
const out = {};
|
|
18207
18379
|
for (const f of readBundleFiles()) {
|
|
18208
18380
|
const content = f.rel === "AGENTS.md" ? f.content.replace(/\{\{project_name\}\}/g, projectName) : f.content;
|
|
18209
18381
|
for (const rel of [bundleDestRel(f.rel), claudeSkillDestRel(f.rel)]) {
|
|
18210
18382
|
if (!rel) continue;
|
|
18211
|
-
const dest =
|
|
18212
|
-
if (
|
|
18383
|
+
const dest = join7(projectRoot, rel);
|
|
18384
|
+
if (canProbe && existsSync4(dest) && statSync4(dest).isFile()) continue;
|
|
18213
18385
|
out[dest] = content;
|
|
18214
18386
|
}
|
|
18215
18387
|
}
|
|
@@ -18218,19 +18390,19 @@ function planBundleInstall(projectRoot, projectName, opts = {}) {
|
|
|
18218
18390
|
|
|
18219
18391
|
// src/lib/design-bundle.ts
|
|
18220
18392
|
import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync5 } from "fs";
|
|
18221
|
-
import { dirname as dirname2, join as
|
|
18393
|
+
import { dirname as dirname2, join as join8, resolve as resolve2 } from "path";
|
|
18222
18394
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18223
18395
|
var DESIGN_ASSETS = [
|
|
18224
|
-
{ srcRel:
|
|
18225
|
-
{ srcRel:
|
|
18226
|
-
{ srcRel:
|
|
18396
|
+
{ srcRel: join8("agents", "frontend-design-engineer.md"), destRel: join8(".claude", "agents", "frontend-design-engineer.md"), executable: false },
|
|
18397
|
+
{ srcRel: join8("skills", "design-critique", "SKILL.md"), destRel: join8(".claude", "skills", "design-critique", "SKILL.md"), executable: false },
|
|
18398
|
+
{ srcRel: join8("hooks", "frontend-design-guard.sh"), destRel: join8(".claude", "hooks", "frontend-design-guard.sh"), executable: true }
|
|
18227
18399
|
];
|
|
18228
18400
|
var DESIGN_HOOK_COMMAND = ".claude/hooks/frontend-design-guard.sh";
|
|
18229
18401
|
function resolveDesignAssetsDir() {
|
|
18230
18402
|
let dir = dirname2(fileURLToPath2(import.meta.url));
|
|
18231
18403
|
for (let i = 0; i < 5; i++) {
|
|
18232
|
-
const candidate =
|
|
18233
|
-
if (existsSync5(
|
|
18404
|
+
const candidate = join8(dir, "design-assets");
|
|
18405
|
+
if (existsSync5(join8(candidate, "agents", "frontend-design-engineer.md"))) return candidate;
|
|
18234
18406
|
const parent = resolve2(dir, "..");
|
|
18235
18407
|
if (parent === dir) break;
|
|
18236
18408
|
dir = parent;
|
|
@@ -18242,9 +18414,9 @@ function planDesignInstall(projectRoot, opts = {}) {
|
|
|
18242
18414
|
if (!assetsDir) return [];
|
|
18243
18415
|
const out = [];
|
|
18244
18416
|
for (const asset of DESIGN_ASSETS) {
|
|
18245
|
-
const srcAbs =
|
|
18417
|
+
const srcAbs = join8(assetsDir, asset.srcRel);
|
|
18246
18418
|
if (!existsSync5(srcAbs)) continue;
|
|
18247
|
-
const dest = projectRoot ?
|
|
18419
|
+
const dest = projectRoot ? join8(projectRoot, asset.destRel) : asset.destRel;
|
|
18248
18420
|
if (opts.skipExisting && projectRoot && existsSync5(dest) && statSync5(dest).isFile()) continue;
|
|
18249
18421
|
out.push({ dest, content: readFileSync4(srcAbs, "utf8"), executable: asset.executable });
|
|
18250
18422
|
}
|
|
@@ -18253,9 +18425,9 @@ function planDesignInstall(projectRoot, opts = {}) {
|
|
|
18253
18425
|
|
|
18254
18426
|
// src/lib/skill-detection.ts
|
|
18255
18427
|
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync6 } from "fs";
|
|
18256
|
-
import { join as
|
|
18428
|
+
import { join as join9 } from "path";
|
|
18257
18429
|
function readPackageJson(projectRoot) {
|
|
18258
|
-
const path7 =
|
|
18430
|
+
const path7 = join9(projectRoot, "package.json");
|
|
18259
18431
|
if (!existsSync6(path7)) return null;
|
|
18260
18432
|
try {
|
|
18261
18433
|
const raw = readFileSync5(path7, "utf-8");
|
|
@@ -18279,7 +18451,7 @@ function detectsFrontendStack(projectRoot) {
|
|
|
18279
18451
|
return hasDependencyMatching(allDeps(readPackageJson(projectRoot)), FRONTEND_DEP_PATTERN);
|
|
18280
18452
|
}
|
|
18281
18453
|
function hasGitHubWorkflows(projectRoot) {
|
|
18282
|
-
const dir =
|
|
18454
|
+
const dir = join9(projectRoot, ".github", "workflows");
|
|
18283
18455
|
if (!existsSync6(dir)) return false;
|
|
18284
18456
|
try {
|
|
18285
18457
|
const entries = readdirSync4(dir);
|
|
@@ -18289,7 +18461,7 @@ function hasGitHubWorkflows(projectRoot) {
|
|
|
18289
18461
|
}
|
|
18290
18462
|
}
|
|
18291
18463
|
function envExampleMentionsStaging(projectRoot) {
|
|
18292
|
-
const path7 =
|
|
18464
|
+
const path7 = join9(projectRoot, ".env.example");
|
|
18293
18465
|
if (!existsSync6(path7)) return false;
|
|
18294
18466
|
try {
|
|
18295
18467
|
const raw = readFileSync5(path7, "utf-8");
|
|
@@ -18299,8 +18471,8 @@ function envExampleMentionsStaging(projectRoot) {
|
|
|
18299
18471
|
}
|
|
18300
18472
|
}
|
|
18301
18473
|
function hasVercelConfig(projectRoot) {
|
|
18302
|
-
if (existsSync6(
|
|
18303
|
-
const vercelDir =
|
|
18474
|
+
if (existsSync6(join9(projectRoot, "vercel.json"))) return true;
|
|
18475
|
+
const vercelDir = join9(projectRoot, ".vercel");
|
|
18304
18476
|
if (!existsSync6(vercelDir)) return false;
|
|
18305
18477
|
try {
|
|
18306
18478
|
return statSync6(vercelDir).isDirectory();
|
|
@@ -18547,6 +18719,7 @@ Active Decisions (ADs) track architectural and product choices with confidence l
|
|
|
18547
18719
|
- ADs are for product/architecture choices only, not process preferences.
|
|
18548
18720
|
- When new evidence appears, update AD confidence via \`strategy_change\`.
|
|
18549
18721
|
- Supersede rather than overwrite \u2014 old decisions stay as history.
|
|
18722
|
+
- **A contradiction is not a veto.** If an idea or task cuts against a live AD, that is a decision for the user \u2014 never your reason to drop it. Submit it with \`idea\` + \`conflicts_with_ad: "AD-N"\`: the task is created and parked behind a decision gate, surfaces in \`orient\` as waiting on the owner, and returns to the board on its own once the AD is superseded, modified, or reaffirmed. Shelving the work instead means the AD never gets tested by the evidence that would have challenged it.
|
|
18550
18723
|
- New ADs should include a \`### Reversal Trigger\` section: specify the signal that would invalidate the stance, the action to take (modify/supersede/abandon), and why writing it now prevents sunk-cost drift later.
|
|
18551
18724
|
`;
|
|
18552
18725
|
var CLAUDE_MD_TIER_2 = `
|
|
@@ -18744,6 +18917,13 @@ function formatFilesToWriteSection(collector) {
|
|
|
18744
18917
|
'- `mode: "create"` \u2014 create the file with `content`. If `skip_if_exists: true`, skip when the file already exists.',
|
|
18745
18918
|
'- `mode: "overwrite"` \u2014 replace the file fully with `content`.',
|
|
18746
18919
|
'- `mode: "append"` \u2014 append `content` to the existing file (create it first if missing).',
|
|
18920
|
+
'- `mode: "merge-json"` \u2014 the file is JSON. If it does not exist, create it with `content`.',
|
|
18921
|
+
" If it DOES exist, parse both, merge `content` INTO the existing object, and write the",
|
|
18922
|
+
" result back. Merge rules: objects merge key-by-key (recursing into nested objects);",
|
|
18923
|
+
" arrays UNION, keeping the existing order and appending only values not already present;",
|
|
18924
|
+
" scalars in `content` do not overwrite an existing value. NEVER remove, reorder or rewrite",
|
|
18925
|
+
" anything already in the file \u2014 this mode only ADDS. If the existing file is malformed",
|
|
18926
|
+
" JSON, leave it untouched and tell the user rather than replacing their file.",
|
|
18747
18927
|
""
|
|
18748
18928
|
].join("\n");
|
|
18749
18929
|
}
|
|
@@ -18775,6 +18955,7 @@ function shouldWriteClaudeMd(clientName) {
|
|
|
18775
18955
|
return !clientName || /claude/i.test(clientName);
|
|
18776
18956
|
}
|
|
18777
18957
|
async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
18958
|
+
const preparedFiles = [];
|
|
18778
18959
|
const isPg = config2.adapterType === "pg" || config2.adapterType === "proxy";
|
|
18779
18960
|
const vars = {
|
|
18780
18961
|
project_name: input.projectName,
|
|
@@ -18784,12 +18965,12 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18784
18965
|
if (!isPg) {
|
|
18785
18966
|
try {
|
|
18786
18967
|
await access2(config2.papiDir);
|
|
18787
|
-
return false;
|
|
18968
|
+
return { createdProject: false, preparedFiles };
|
|
18788
18969
|
} catch {
|
|
18789
18970
|
await mkdir(config2.papiDir, { recursive: true });
|
|
18790
18971
|
for (const [filename, template] of Object.entries(FILE_TEMPLATES)) {
|
|
18791
18972
|
const content = substitute(template, vars);
|
|
18792
|
-
await writeFile2(
|
|
18973
|
+
await writeFile2(join10(config2.papiDir, filename), content, "utf-8");
|
|
18793
18974
|
}
|
|
18794
18975
|
}
|
|
18795
18976
|
} else {
|
|
@@ -18799,7 +18980,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18799
18980
|
const templateContent = substitute(PRODUCT_BRIEF_TEMPLATE, vars);
|
|
18800
18981
|
await adapter2.updateProductBrief(templateContent);
|
|
18801
18982
|
} else {
|
|
18802
|
-
return false;
|
|
18983
|
+
return { createdProject: false, preparedFiles };
|
|
18803
18984
|
}
|
|
18804
18985
|
} catch {
|
|
18805
18986
|
}
|
|
@@ -18807,13 +18988,13 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18807
18988
|
const useCollector = config2.adapterType === "proxy";
|
|
18808
18989
|
const docsRel = "docs";
|
|
18809
18990
|
const commandsRel = ".claude/commands";
|
|
18810
|
-
const commandsDir = useCollector ? commandsRel :
|
|
18811
|
-
const docsDir = useCollector ? docsRel :
|
|
18991
|
+
const commandsDir = useCollector ? commandsRel : join10(config2.projectRoot, ".claude", "commands");
|
|
18992
|
+
const docsDir = useCollector ? docsRel : join10(config2.projectRoot, "docs");
|
|
18812
18993
|
if (!useCollector) {
|
|
18813
18994
|
await mkdir(commandsDir, { recursive: true });
|
|
18814
18995
|
await mkdir(docsDir, { recursive: true });
|
|
18815
18996
|
}
|
|
18816
|
-
const claudeMdPath = useCollector ? "CLAUDE.md" :
|
|
18997
|
+
const claudeMdPath = useCollector ? "CLAUDE.md" : join10(config2.projectRoot, "CLAUDE.md");
|
|
18817
18998
|
let claudeMdExists = false;
|
|
18818
18999
|
if (!useCollector) {
|
|
18819
19000
|
try {
|
|
@@ -18822,7 +19003,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18822
19003
|
} catch {
|
|
18823
19004
|
}
|
|
18824
19005
|
}
|
|
18825
|
-
const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` :
|
|
19006
|
+
const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join10(docsDir, "INDEX.md");
|
|
18826
19007
|
let docsIndexExists = false;
|
|
18827
19008
|
if (!useCollector) {
|
|
18828
19009
|
try {
|
|
@@ -18832,9 +19013,9 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18832
19013
|
}
|
|
18833
19014
|
}
|
|
18834
19015
|
const scaffoldFiles = {
|
|
18835
|
-
[useCollector ? `${commandsRel}/papi-audit.md` :
|
|
18836
|
-
[useCollector ? `${commandsRel}/test.md` :
|
|
18837
|
-
[useCollector ? `${docsRel}/README.md` :
|
|
19016
|
+
[useCollector ? `${commandsRel}/papi-audit.md` : join10(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
|
|
19017
|
+
[useCollector ? `${commandsRel}/test.md` : join10(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
|
|
19018
|
+
[useCollector ? `${docsRel}/README.md` : join10(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
|
|
18838
19019
|
};
|
|
18839
19020
|
if (!docsIndexExists) {
|
|
18840
19021
|
scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
|
|
@@ -18861,7 +19042,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18861
19042
|
if (useCollector) {
|
|
18862
19043
|
scaffoldFiles[".cursor/rules/papi.mdc"] = substitute(CURSOR_RULES_TEMPLATE, vars);
|
|
18863
19044
|
} else {
|
|
18864
|
-
const cursorDir =
|
|
19045
|
+
const cursorDir = join10(config2.projectRoot, ".cursor");
|
|
18865
19046
|
let cursorDetected = false;
|
|
18866
19047
|
try {
|
|
18867
19048
|
await access2(cursorDir);
|
|
@@ -18869,8 +19050,8 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18869
19050
|
} catch {
|
|
18870
19051
|
}
|
|
18871
19052
|
if (cursorDetected) {
|
|
18872
|
-
const cursorRulesDir =
|
|
18873
|
-
const cursorRulesPath =
|
|
19053
|
+
const cursorRulesDir = join10(cursorDir, "rules");
|
|
19054
|
+
const cursorRulesPath = join10(cursorRulesDir, "papi.mdc");
|
|
18874
19055
|
await mkdir(cursorRulesDir, { recursive: true });
|
|
18875
19056
|
try {
|
|
18876
19057
|
await access2(cursorRulesPath);
|
|
@@ -18888,6 +19069,9 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18888
19069
|
await writeFile2(filepath, content, "utf-8");
|
|
18889
19070
|
}
|
|
18890
19071
|
}
|
|
19072
|
+
for (const p of Object.keys(scaffoldFiles)) {
|
|
19073
|
+
preparedFiles.push(useCollector ? p : relative(config2.projectRoot, p));
|
|
19074
|
+
}
|
|
18891
19075
|
if (!useCollector && detectsFrontendStack(config2.projectRoot)) {
|
|
18892
19076
|
for (const entry of planDesignInstall(config2.projectRoot, { skipExisting: true })) {
|
|
18893
19077
|
await mkdir(dirname3(entry.dest), { recursive: true });
|
|
@@ -18912,20 +19096,38 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
|
18912
19096
|
}]);
|
|
18913
19097
|
}
|
|
18914
19098
|
if (useCollector) {
|
|
18915
|
-
collector.add(
|
|
18916
|
-
path: ".claude/settings.json",
|
|
18917
|
-
content: JSON.stringify({ permissions: { allow: [...PAPI_PERMISSIONS] } }, null, 2) + "\n",
|
|
18918
|
-
mode: "create",
|
|
18919
|
-
skip_if_exists: true
|
|
18920
|
-
});
|
|
19099
|
+
collector.add(papiSettingsCollectorEntry());
|
|
18921
19100
|
} else {
|
|
18922
19101
|
await ensurePapiPermission(config2.projectRoot);
|
|
18923
19102
|
}
|
|
18924
|
-
return true;
|
|
19103
|
+
return { createdProject: true, preparedFiles };
|
|
18925
19104
|
}
|
|
18926
19105
|
var PAPI_PERMISSIONS = ["mcp__papi__*", "mcp__plugin_papi_papi__*"];
|
|
19106
|
+
function papiSettingsCollectorEntry() {
|
|
19107
|
+
return {
|
|
19108
|
+
path: ".claude/settings.json",
|
|
19109
|
+
content: JSON.stringify(mergePapiPermissions({}), null, 2) + "\n",
|
|
19110
|
+
mode: "merge-json"
|
|
19111
|
+
};
|
|
19112
|
+
}
|
|
19113
|
+
function mergePapiPermissions(settings) {
|
|
19114
|
+
if (!settings.permissions || typeof settings.permissions !== "object") {
|
|
19115
|
+
settings.permissions = {};
|
|
19116
|
+
}
|
|
19117
|
+
const perms = settings.permissions;
|
|
19118
|
+
if (!Array.isArray(perms.allow)) {
|
|
19119
|
+
perms.allow = [];
|
|
19120
|
+
}
|
|
19121
|
+
const allow = perms.allow;
|
|
19122
|
+
for (const permission of PAPI_PERMISSIONS) {
|
|
19123
|
+
if (!allow.includes(permission)) {
|
|
19124
|
+
allow.push(permission);
|
|
19125
|
+
}
|
|
19126
|
+
}
|
|
19127
|
+
return settings;
|
|
19128
|
+
}
|
|
18927
19129
|
async function ensurePapiPermission(projectRoot) {
|
|
18928
|
-
const settingsPath =
|
|
19130
|
+
const settingsPath = join10(projectRoot, ".claude", "settings.json");
|
|
18929
19131
|
try {
|
|
18930
19132
|
let settings = {};
|
|
18931
19133
|
try {
|
|
@@ -18933,26 +19135,14 @@ async function ensurePapiPermission(projectRoot) {
|
|
|
18933
19135
|
settings = JSON.parse(existing);
|
|
18934
19136
|
} catch {
|
|
18935
19137
|
}
|
|
18936
|
-
|
|
18937
|
-
|
|
18938
|
-
}
|
|
18939
|
-
const perms = settings.permissions;
|
|
18940
|
-
if (!Array.isArray(perms.allow)) {
|
|
18941
|
-
perms.allow = [];
|
|
18942
|
-
}
|
|
18943
|
-
const allow = perms.allow;
|
|
18944
|
-
for (const permission of PAPI_PERMISSIONS) {
|
|
18945
|
-
if (!allow.includes(permission)) {
|
|
18946
|
-
allow.push(permission);
|
|
18947
|
-
}
|
|
18948
|
-
}
|
|
18949
|
-
await mkdir(join9(projectRoot, ".claude"), { recursive: true });
|
|
19138
|
+
mergePapiPermissions(settings);
|
|
19139
|
+
await mkdir(join10(projectRoot, ".claude"), { recursive: true });
|
|
18950
19140
|
await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
18951
19141
|
} catch {
|
|
18952
19142
|
}
|
|
18953
19143
|
}
|
|
18954
19144
|
async function ensureDesignHookRegistered(projectRoot) {
|
|
18955
|
-
const settingsPath =
|
|
19145
|
+
const settingsPath = join10(projectRoot, ".claude", "settings.json");
|
|
18956
19146
|
try {
|
|
18957
19147
|
let settings = {};
|
|
18958
19148
|
try {
|
|
@@ -18982,7 +19172,7 @@ async function ensureDesignHookRegistered(projectRoot) {
|
|
|
18982
19172
|
chain.push({ type: "command", command: DESIGN_HOOK_COMMAND });
|
|
18983
19173
|
}
|
|
18984
19174
|
}
|
|
18985
|
-
await mkdir(
|
|
19175
|
+
await mkdir(join10(projectRoot, ".claude"), { recursive: true });
|
|
18986
19176
|
await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
18987
19177
|
} catch {
|
|
18988
19178
|
}
|
|
@@ -19088,7 +19278,7 @@ ${conventionsText.trim()}
|
|
|
19088
19278
|
);
|
|
19089
19279
|
} else {
|
|
19090
19280
|
try {
|
|
19091
|
-
const claudeMdPath =
|
|
19281
|
+
const claudeMdPath = join10(config2.projectRoot, "CLAUDE.md");
|
|
19092
19282
|
const existing = await readFile4(claudeMdPath, "utf-8");
|
|
19093
19283
|
if (existing.includes(CONVENTIONS_SENTINEL) || existing.includes(CONVENTIONS_HEADING)) {
|
|
19094
19284
|
warnings.push(
|
|
@@ -19175,13 +19365,13 @@ async function scanCodebase(projectRoot) {
|
|
|
19175
19365
|
}
|
|
19176
19366
|
let packageJson;
|
|
19177
19367
|
try {
|
|
19178
|
-
const content = await readFile4(
|
|
19368
|
+
const content = await readFile4(join10(projectRoot, "package.json"), "utf-8");
|
|
19179
19369
|
packageJson = JSON.parse(content);
|
|
19180
19370
|
} catch {
|
|
19181
19371
|
}
|
|
19182
19372
|
let readme;
|
|
19183
19373
|
for (const name of ["README.md", "readme.md", "README.txt", "README"]) {
|
|
19184
|
-
const content = await safeReadFile(
|
|
19374
|
+
const content = await safeReadFile(join10(projectRoot, name), 5e3);
|
|
19185
19375
|
if (content) {
|
|
19186
19376
|
readme = content;
|
|
19187
19377
|
break;
|
|
@@ -19191,7 +19381,7 @@ async function scanCodebase(projectRoot) {
|
|
|
19191
19381
|
let totalFiles = topLevelFiles.length;
|
|
19192
19382
|
for (const dir of topLevelDirs) {
|
|
19193
19383
|
try {
|
|
19194
|
-
const entries = await readdir(
|
|
19384
|
+
const entries = await readdir(join10(projectRoot, dir), { withFileTypes: true });
|
|
19195
19385
|
const files = entries.filter((e) => e.isFile());
|
|
19196
19386
|
const extensions = [...new Set(files.map((f) => extname(f.name).toLowerCase()).filter(Boolean))];
|
|
19197
19387
|
totalFiles += files.length;
|
|
@@ -19274,7 +19464,7 @@ function formatCodebaseSummary(scan, sourceContents) {
|
|
|
19274
19464
|
}
|
|
19275
19465
|
async function prepareSetup(adapter2, config2, input) {
|
|
19276
19466
|
const prepareCollector = new FileWriteCollector();
|
|
19277
|
-
const createdProject = await scaffoldPapiDir(adapter2, config2, input, prepareCollector);
|
|
19467
|
+
const { createdProject, preparedFiles } = await scaffoldPapiDir(adapter2, config2, input, prepareCollector);
|
|
19278
19468
|
let existingBrief;
|
|
19279
19469
|
try {
|
|
19280
19470
|
existingBrief = await adapter2.readProductBrief();
|
|
@@ -19443,12 +19633,13 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
19443
19633
|
preScanInstruction,
|
|
19444
19634
|
newProjectInstruction,
|
|
19445
19635
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
19446
|
-
filesToWrite: prepareCollector.isEmpty() ? void 0 : prepareCollector
|
|
19636
|
+
filesToWrite: prepareCollector.isEmpty() ? void 0 : prepareCollector,
|
|
19637
|
+
preparedFiles
|
|
19447
19638
|
};
|
|
19448
19639
|
}
|
|
19449
19640
|
async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText, northStarText) {
|
|
19450
19641
|
const collector = new FileWriteCollector();
|
|
19451
|
-
const createdProject = await scaffoldPapiDir(adapter2, config2, input, collector);
|
|
19642
|
+
const { createdProject, preparedFiles } = await scaffoldPapiDir(adapter2, config2, input, collector);
|
|
19452
19643
|
let effectiveBriefText = briefText;
|
|
19453
19644
|
let briefRegenerated = false;
|
|
19454
19645
|
if (!effectiveBriefText.trim()) {
|
|
@@ -19577,7 +19768,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19577
19768
|
collector.add({ path: "CLAUDE.md", content: dogfoodSection, mode: "append" });
|
|
19578
19769
|
} else {
|
|
19579
19770
|
try {
|
|
19580
|
-
const claudeMdPath =
|
|
19771
|
+
const claudeMdPath = join10(config2.projectRoot, "CLAUDE.md");
|
|
19581
19772
|
const existing = await readFile4(claudeMdPath, "utf-8");
|
|
19582
19773
|
if (!existing.includes("Dogfood Logging")) {
|
|
19583
19774
|
await writeFile2(claudeMdPath, existing + dogfoodSection, "utf-8");
|
|
@@ -19622,7 +19813,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19622
19813
|
cursorScaffolded = true;
|
|
19623
19814
|
} else {
|
|
19624
19815
|
try {
|
|
19625
|
-
await access2(
|
|
19816
|
+
await access2(join10(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
|
|
19626
19817
|
cursorScaffolded = true;
|
|
19627
19818
|
} catch {
|
|
19628
19819
|
}
|
|
@@ -19638,16 +19829,17 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19638
19829
|
cursorScaffolded,
|
|
19639
19830
|
gitignoreNote,
|
|
19640
19831
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
19641
|
-
filesToWrite: collector.isEmpty() ? void 0 : collector
|
|
19832
|
+
filesToWrite: collector.isEmpty() ? void 0 : collector,
|
|
19833
|
+
preparedFiles
|
|
19642
19834
|
};
|
|
19643
19835
|
}
|
|
19644
19836
|
async function ensureMcpJsonGitignored(projectRoot) {
|
|
19645
19837
|
try {
|
|
19646
|
-
await access2(
|
|
19838
|
+
await access2(join10(projectRoot, ".git"));
|
|
19647
19839
|
} catch {
|
|
19648
19840
|
return void 0;
|
|
19649
19841
|
}
|
|
19650
|
-
const gitignorePath =
|
|
19842
|
+
const gitignorePath = join10(projectRoot, ".gitignore");
|
|
19651
19843
|
let existing = "";
|
|
19652
19844
|
try {
|
|
19653
19845
|
existing = await readFile4(gitignorePath, "utf-8");
|
|
@@ -19796,8 +19988,14 @@ function extractInput(args) {
|
|
|
19796
19988
|
codebaseScan: args.codebase_scan && typeof args.codebase_scan === "object" ? args.codebase_scan : void 0
|
|
19797
19989
|
};
|
|
19798
19990
|
}
|
|
19991
|
+
function harnessFilesLabel(prepared, writesClaudeMd) {
|
|
19992
|
+
const HARNESS_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
19993
|
+
const emitted = HARNESS_FILES.filter((f) => prepared?.includes(f));
|
|
19994
|
+
if (emitted.length > 0) return emitted.join(", ");
|
|
19995
|
+
return writesClaudeMd ? "AGENTS.md, CLAUDE.md" : "AGENTS.md";
|
|
19996
|
+
}
|
|
19799
19997
|
function formatSuccessResponse(result, constraints, writesClaudeMd = true) {
|
|
19800
|
-
const harnessFiles =
|
|
19998
|
+
const harnessFiles = harnessFilesLabel(result.preparedFiles, writesClaudeMd);
|
|
19801
19999
|
const prefix = result.createdProject ? `PAPI project "${result.projectName}" initialised and ` : "";
|
|
19802
20000
|
const briefRegenNote = result.briefRegenerated ? `
|
|
19803
20001
|
|
|
@@ -20213,8 +20411,8 @@ function buildPapiMetaFramingDirective(caps, inner) {
|
|
|
20213
20411
|
|
|
20214
20412
|
// src/services/build.ts
|
|
20215
20413
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
20216
|
-
import { readdirSync as readdirSync5, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as
|
|
20217
|
-
import { join as
|
|
20414
|
+
import { readdirSync as readdirSync5, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
20415
|
+
import { join as join13 } from "path";
|
|
20218
20416
|
import { splitFindings, findingKey } from "@papi-ai/shared";
|
|
20219
20417
|
|
|
20220
20418
|
// src/lib/db-only-notices.ts
|
|
@@ -20381,7 +20579,7 @@ init_git();
|
|
|
20381
20579
|
// src/services/release.ts
|
|
20382
20580
|
init_telemetry();
|
|
20383
20581
|
import { writeFile as writeFile3, readFile as readFile5 } from "fs/promises";
|
|
20384
|
-
import { join as
|
|
20582
|
+
import { join as join11 } from "path";
|
|
20385
20583
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
20386
20584
|
import { isSensitiveChangelogLine } from "@papi-ai/shared";
|
|
20387
20585
|
init_git();
|
|
@@ -20855,7 +21053,7 @@ async function createRelease(config2, branch, version, adapter2, cycleNum, optio
|
|
|
20855
21053
|
}
|
|
20856
21054
|
}
|
|
20857
21055
|
const latestTag = getLatestTag(config2.projectRoot);
|
|
20858
|
-
const changelogPath =
|
|
21056
|
+
const changelogPath = join11(config2.projectRoot, "CHANGELOG.md");
|
|
20859
21057
|
if (!latestTag) {
|
|
20860
21058
|
const initialContent = INITIAL_RELEASE_NOTES.replace("v0.1.0-alpha", version);
|
|
20861
21059
|
if (config2.adapterType === "proxy") {
|
|
@@ -21011,33 +21209,12 @@ import { execFile } from "child_process";
|
|
|
21011
21209
|
import { promisify } from "util";
|
|
21012
21210
|
var execFileAsync = promisify(execFile);
|
|
21013
21211
|
var REGISTRY_TIMEOUT_MS = 12e4;
|
|
21014
|
-
|
|
21015
|
-
|
|
21016
|
-
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
message: `Listing not updated \u2014 set MCP_REGISTRY_GITHUB_TOKEN to automate, or run manually: npx mcp-publisher publish`
|
|
21021
|
-
};
|
|
21022
|
-
}
|
|
21023
|
-
try {
|
|
21024
|
-
await execFileAsync("npx", ["mcp-publisher", "publish", "--yes"], {
|
|
21025
|
-
env: { ...process.env, GITHUB_TOKEN: token },
|
|
21026
|
-
timeout: REGISTRY_TIMEOUT_MS
|
|
21027
|
-
});
|
|
21028
|
-
return {
|
|
21029
|
-
registry: "Official MCP Registry",
|
|
21030
|
-
updated: true,
|
|
21031
|
-
message: `Listing updated to ${version}.`
|
|
21032
|
-
};
|
|
21033
|
-
} catch (err) {
|
|
21034
|
-
const detail = err instanceof Error ? err.message.slice(0, 300) : String(err).slice(0, 300);
|
|
21035
|
-
return {
|
|
21036
|
-
registry: "Official MCP Registry",
|
|
21037
|
-
updated: false,
|
|
21038
|
-
message: `Update failed \u2014 run manually: npx mcp-publisher publish. (${detail})`
|
|
21039
|
-
};
|
|
21040
|
-
}
|
|
21212
|
+
function officialMCPRegistryGuidance(version) {
|
|
21213
|
+
return {
|
|
21214
|
+
registry: "Official MCP Registry",
|
|
21215
|
+
updated: false,
|
|
21216
|
+
message: `Listing not updated (manual step). From a checkout of getpapi/papi: \`mcp-publisher login github\` (once), then \`mcp-publisher publish\` \u2014 bump server.json to ${version} first. Requires the Go CLI (brew install mcp-publisher), NOT the npm package of the same name.`
|
|
21217
|
+
};
|
|
21041
21218
|
}
|
|
21042
21219
|
async function updateSmithery(version) {
|
|
21043
21220
|
const key = process.env["SMITHERY_API_KEY"]?.trim();
|
|
@@ -21068,11 +21245,8 @@ async function updateSmithery(version) {
|
|
|
21068
21245
|
}
|
|
21069
21246
|
}
|
|
21070
21247
|
async function updateRegistryListings(version) {
|
|
21071
|
-
const [
|
|
21072
|
-
|
|
21073
|
-
updateSmithery(version)
|
|
21074
|
-
]);
|
|
21075
|
-
return [official, smithery];
|
|
21248
|
+
const [smithery] = await Promise.all([updateSmithery(version)]);
|
|
21249
|
+
return [officialMCPRegistryGuidance(version), smithery];
|
|
21076
21250
|
}
|
|
21077
21251
|
async function updateCursorDirectory(version) {
|
|
21078
21252
|
const token = process.env["CURSOR_DIRECTORY_GITHUB_TOKEN"]?.trim();
|
|
@@ -21690,21 +21864,21 @@ function detectWorktreeCollision(input) {
|
|
|
21690
21864
|
init_git();
|
|
21691
21865
|
|
|
21692
21866
|
// src/lib/build-checkpoint.ts
|
|
21693
|
-
import { createHash as
|
|
21694
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as
|
|
21695
|
-
import { join as
|
|
21867
|
+
import { createHash as createHash5 } from "crypto";
|
|
21868
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
21869
|
+
import { join as join12 } from "path";
|
|
21696
21870
|
var BUILD_CHECKPOINT_VERSION = 1;
|
|
21697
21871
|
function cwdHash(cwd) {
|
|
21698
|
-
return
|
|
21872
|
+
return createHash5("sha256").update(realpathOrSelf(cwd)).digest("hex").slice(0, 12);
|
|
21699
21873
|
}
|
|
21700
21874
|
function safeTaskId(taskId) {
|
|
21701
21875
|
return taskId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
21702
21876
|
}
|
|
21703
21877
|
function checkpointDir(cwd) {
|
|
21704
|
-
return
|
|
21878
|
+
return join12(cwd, ".papi", "state");
|
|
21705
21879
|
}
|
|
21706
21880
|
function checkpointPath(cwd, taskId) {
|
|
21707
|
-
return
|
|
21881
|
+
return join12(checkpointDir(cwd), `build-${safeTaskId(taskId)}.${cwdHash(cwd)}.json`);
|
|
21708
21882
|
}
|
|
21709
21883
|
function writeBuildCheckpoint(input) {
|
|
21710
21884
|
try {
|
|
@@ -21722,7 +21896,7 @@ function writeBuildCheckpoint(input) {
|
|
|
21722
21896
|
cwd: realpathOrSelf(input.cwd),
|
|
21723
21897
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21724
21898
|
};
|
|
21725
|
-
|
|
21899
|
+
writeFileSync4(checkpointPath(input.cwd, input.taskId), JSON.stringify(checkpoint, null, 2) + "\n", "utf-8");
|
|
21726
21900
|
} catch {
|
|
21727
21901
|
}
|
|
21728
21902
|
}
|
|
@@ -22616,15 +22790,15 @@ function writeActiveTaskScope(projectRoot, taskId, filesLikelyTouched, adapterTy
|
|
|
22616
22790
|
collector.add({ path: ".papi/active-task-scope.txt", content, mode: "overwrite" });
|
|
22617
22791
|
return;
|
|
22618
22792
|
}
|
|
22619
|
-
const papiDir =
|
|
22793
|
+
const papiDir = join13(projectRoot, ".papi");
|
|
22620
22794
|
if (!existsSync8(papiDir)) {
|
|
22621
22795
|
mkdirSync3(papiDir, { recursive: true });
|
|
22622
22796
|
}
|
|
22623
|
-
const scopePath =
|
|
22624
|
-
|
|
22797
|
+
const scopePath = join13(papiDir, "active-task-scope.txt");
|
|
22798
|
+
writeFileSync5(scopePath, content, "utf-8");
|
|
22625
22799
|
}
|
|
22626
22800
|
function clearActiveTaskScope(projectRoot) {
|
|
22627
|
-
const scopePath =
|
|
22801
|
+
const scopePath = join13(projectRoot, ".papi", "active-task-scope.txt");
|
|
22628
22802
|
if (existsSync8(scopePath)) {
|
|
22629
22803
|
unlinkSync3(scopePath);
|
|
22630
22804
|
}
|
|
@@ -23084,14 +23258,14 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
23084
23258
|
let docWarning;
|
|
23085
23259
|
try {
|
|
23086
23260
|
if (adapter2.searchDocs && hasLocalWorkspace() && await ownsLocalWorkspace(adapter2, config2.projectRoot)) {
|
|
23087
|
-
const docsDir =
|
|
23261
|
+
const docsDir = join13(config2.projectRoot, "docs");
|
|
23088
23262
|
if (existsSync8(docsDir)) {
|
|
23089
23263
|
const scanDir = (dir, depth = 0) => {
|
|
23090
23264
|
if (depth > 8) return [];
|
|
23091
23265
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
23092
23266
|
const files = [];
|
|
23093
23267
|
for (const e of entries) {
|
|
23094
|
-
const full =
|
|
23268
|
+
const full = join13(dir, e.name);
|
|
23095
23269
|
if (e.isDirectory() && !e.isSymbolicLink()) files.push(...scanDir(full, depth + 1));
|
|
23096
23270
|
else if (e.name.endsWith(".md")) files.push(full.replace(config2.projectRoot + "/", ""));
|
|
23097
23271
|
}
|
|
@@ -23116,7 +23290,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
23116
23290
|
const failed = [];
|
|
23117
23291
|
for (const docPath of unregistered) {
|
|
23118
23292
|
try {
|
|
23119
|
-
const meta = extractDocMeta(
|
|
23293
|
+
const meta = extractDocMeta(join13(config2.projectRoot, docPath), docPath, cycleNumber);
|
|
23120
23294
|
await adapter2.registerDoc({
|
|
23121
23295
|
title: meta.title,
|
|
23122
23296
|
type: meta.type,
|
|
@@ -23286,8 +23460,8 @@ ${instructions}`;
|
|
|
23286
23460
|
}
|
|
23287
23461
|
|
|
23288
23462
|
// src/tools/doc-registry.ts
|
|
23289
|
-
import { readdirSync as readdirSync6, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as
|
|
23290
|
-
import { join as
|
|
23463
|
+
import { readdirSync as readdirSync6, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
23464
|
+
import { join as join14, relative as relative2, isAbsolute as isAbsolute2, dirname as dirname4, resolve as resolve3, sep } from "path";
|
|
23291
23465
|
import { homedir as homedir3 } from "os";
|
|
23292
23466
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
23293
23467
|
import { docDeletionBlockMessage } from "@papi-ai/shared";
|
|
@@ -23527,7 +23701,7 @@ function ensureDocDurable(path7, projectRoot) {
|
|
|
23527
23701
|
\u26A0\uFE0F **Registered, but NOT durable.** ${reason}
|
|
23528
23702
|
The registry stores metadata and a summary \u2014 not the body. This doc has one copy, on disk, and a branch switch or stash can take it.
|
|
23529
23703
|
**Fix:** ${fix}`;
|
|
23530
|
-
if (!existsSync9(
|
|
23704
|
+
if (!existsSync9(join14(projectRoot, path7))) {
|
|
23531
23705
|
return warn(
|
|
23532
23706
|
`No file exists at \`${path7}\`.`,
|
|
23533
23707
|
`write the doc body to that path, then re-run doc_register.`
|
|
@@ -23641,7 +23815,7 @@ async function handleDocRegister(adapter2, args, config2) {
|
|
|
23641
23815
|
let body = supplied;
|
|
23642
23816
|
if (body === void 0 && hasLocalWorkspace()) {
|
|
23643
23817
|
try {
|
|
23644
|
-
const abs = isAbsolute2(entry.path) ? entry.path :
|
|
23818
|
+
const abs = isAbsolute2(entry.path) ? entry.path : join14(config2?.projectRoot ?? process.cwd(), entry.path);
|
|
23645
23819
|
if (existsSync9(abs)) body = readFileSync8(abs, "utf8");
|
|
23646
23820
|
} catch {
|
|
23647
23821
|
}
|
|
@@ -23715,7 +23889,7 @@ async function handleDocSearch(adapter2, args, config2) {
|
|
|
23715
23889
|
const lines = docs.map((d) => {
|
|
23716
23890
|
const actionCount = d.actions?.filter((a) => a.status === "pending").length ?? 0;
|
|
23717
23891
|
const actionNote = actionCount > 0 ? ` | ${actionCount} pending action(s)` : "";
|
|
23718
|
-
const missingNote = root && d.path && !existsSync9(
|
|
23892
|
+
const missingNote = root && d.path && !existsSync9(join14(root, d.path)) ? `
|
|
23719
23893
|
> \u26A0\uFE0F **File missing on disk** \u2014 the registry points at \`${d.path}\` but nothing is there. Recover it with \`doc_read\` (\`id_or_path: "${d.path}", write_to_disk: true\`) if its body was stored, or check \`git stash list\` for a papi-autostash entry.` : "";
|
|
23720
23894
|
return `### ${d.title}
|
|
23721
23895
|
**Type:** ${d.type} | **Status:** ${d.status} | **Cycle:** ${d.cycleCreated}${d.cycleUpdated ? `\u2192${d.cycleUpdated}` : ""}${actionNote}
|
|
@@ -23809,7 +23983,7 @@ function restoreBodyToDisk(projectRoot, docPath, body) {
|
|
|
23809
23983
|
`;
|
|
23810
23984
|
}
|
|
23811
23985
|
mkdirSync4(dirname4(target.abs), { recursive: true });
|
|
23812
|
-
|
|
23986
|
+
writeFileSync6(target.abs, body, "utf8");
|
|
23813
23987
|
return `- **Restored:** wrote ${Buffer.byteLength(body, "utf8").toLocaleString()} bytes to \`${docPath}\`
|
|
23814
23988
|
`;
|
|
23815
23989
|
} catch (err) {
|
|
@@ -23824,11 +23998,11 @@ function scanMdFiles(dir, rootDir) {
|
|
|
23824
23998
|
try {
|
|
23825
23999
|
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
23826
24000
|
for (const entry of entries) {
|
|
23827
|
-
const full =
|
|
24001
|
+
const full = join14(dir, entry.name);
|
|
23828
24002
|
if (entry.isDirectory()) {
|
|
23829
24003
|
files.push(...scanMdFiles(full, rootDir));
|
|
23830
24004
|
} else if (entry.name.endsWith(".md")) {
|
|
23831
|
-
files.push(
|
|
24005
|
+
files.push(relative2(rootDir, full).replace(/\\/g, "/"));
|
|
23832
24006
|
}
|
|
23833
24007
|
}
|
|
23834
24008
|
} catch {
|
|
@@ -23849,7 +24023,7 @@ function extractTitle(filePath) {
|
|
|
23849
24023
|
async function detectUnregisteredDocsNote(adapter2, config2) {
|
|
23850
24024
|
try {
|
|
23851
24025
|
if (!adapter2.searchDocs || !hasLocalWorkspace()) return "";
|
|
23852
|
-
const docsDir =
|
|
24026
|
+
const docsDir = join14(config2.projectRoot, "docs");
|
|
23853
24027
|
const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
|
|
23854
24028
|
if (docsFiles.length === 0) return "";
|
|
23855
24029
|
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
@@ -23875,17 +24049,17 @@ async function handleDocScan(adapter2, config2, args) {
|
|
|
23875
24049
|
const includePlans = args.include_plans ?? false;
|
|
23876
24050
|
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
23877
24051
|
const registeredPaths = new Set(registered.map((d) => d.path));
|
|
23878
|
-
const docsDir =
|
|
24052
|
+
const docsDir = join14(config2.projectRoot, "docs");
|
|
23879
24053
|
const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
|
|
23880
24054
|
const unregisteredDocs = docsFiles.filter((f) => !registeredPaths.has(f));
|
|
23881
24055
|
let unregisteredPlans = [];
|
|
23882
24056
|
if (includePlans) {
|
|
23883
|
-
const plansDir =
|
|
24057
|
+
const plansDir = join14(homedir3(), ".claude", "plans");
|
|
23884
24058
|
if (existsSync9(plansDir)) {
|
|
23885
24059
|
const planFiles = scanMdFiles(plansDir, plansDir);
|
|
23886
24060
|
unregisteredPlans = planFiles.map((f) => `plans/${f}`).filter((f) => !registeredPaths.has(f)).map((f) => ({
|
|
23887
24061
|
path: f,
|
|
23888
|
-
title: extractTitle(
|
|
24062
|
+
title: extractTitle(join14(plansDir, f.replace("plans/", "")))
|
|
23889
24063
|
}));
|
|
23890
24064
|
}
|
|
23891
24065
|
}
|
|
@@ -23896,7 +24070,7 @@ async function handleDocScan(adapter2, config2, args) {
|
|
|
23896
24070
|
if (unregisteredDocs.length > 0) {
|
|
23897
24071
|
lines.push(`## Unregistered Docs (${unregisteredDocs.length})`);
|
|
23898
24072
|
for (const f of unregisteredDocs) {
|
|
23899
|
-
const title = extractTitle(
|
|
24073
|
+
const title = extractTitle(join14(config2.projectRoot, f));
|
|
23900
24074
|
lines.push(`- \`${f}\`${title ? ` \u2014 ${title}` : ""}`);
|
|
23901
24075
|
}
|
|
23902
24076
|
}
|
|
@@ -25082,6 +25256,10 @@ var ideaTool = {
|
|
|
25082
25256
|
type: "string",
|
|
25083
25257
|
description: "What user problem does this solve? Auto-fill from problem context in notes when submitting ideas that describe a user pain point. The planner uses this to cluster backlog tasks by opportunity."
|
|
25084
25258
|
},
|
|
25259
|
+
conflicts_with_ad: {
|
|
25260
|
+
type: "string",
|
|
25261
|
+
description: 'The Active Decision this idea cuts against, e.g. "AD-12". USE THIS INSTEAD OF DROPPING THE IDEA. An AD is *active* \u2014 it can be superseded \u2014 so a contradiction is a decision for the user, never a reason for you to shelve work during research or scoping. The task is created and parked Blocked behind a decision-gate on that AD: it shows in `orient` as a decision waiting on the owner, and returns to the board automatically once the AD is superseded, modified, or reaffirmed.'
|
|
25262
|
+
},
|
|
25085
25263
|
project: {
|
|
25086
25264
|
type: "string",
|
|
25087
25265
|
description: "Project id (UUID) or slug to write this idea to, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. Use project_switch to change the session default. PASS THIS WHENEVER YOU KNOW WHICH REPO THE SESSION IS IN: on a multi-project account a connection with no project bound cannot be resolved, and PAPI will stop and ask rather than guess."
|
|
@@ -25114,7 +25292,8 @@ async function handleIdea(adapter2, config2, args) {
|
|
|
25114
25292
|
force: args.force === true,
|
|
25115
25293
|
docRef: args.doc_ref?.trim(),
|
|
25116
25294
|
type: args.type,
|
|
25117
|
-
opportunity: args.opportunity?.trim()
|
|
25295
|
+
opportunity: args.opportunity?.trim(),
|
|
25296
|
+
conflictsWithAd: args.conflicts_with_ad?.trim()
|
|
25118
25297
|
};
|
|
25119
25298
|
let target = adapter2;
|
|
25120
25299
|
let overrideNote = "";
|
|
@@ -25159,7 +25338,12 @@ Re-submit with \`notes: "... Reference: <path>"\` to link one, or ignore if none
|
|
|
25159
25338
|
} catch {
|
|
25160
25339
|
}
|
|
25161
25340
|
}
|
|
25162
|
-
|
|
25341
|
+
const conflictNote = result.adConflicts ? buildAdConflictNote(
|
|
25342
|
+
result.adConflicts.explicit ? result.adConflicts.adIds[0] : void 0,
|
|
25343
|
+
result.adConflicts.explicit ? [] : result.adConflicts.adIds,
|
|
25344
|
+
result.adConflicts.gated
|
|
25345
|
+
) : "";
|
|
25346
|
+
return textResponse(`${result.message}${overrideNote}${branchNote}${truncateWarning}${refNudge}${conflictNote}`);
|
|
25163
25347
|
}
|
|
25164
25348
|
return textResponse(`${result.message}${overrideNote}`);
|
|
25165
25349
|
}
|
|
@@ -26212,11 +26396,11 @@ init_git();
|
|
|
26212
26396
|
|
|
26213
26397
|
// src/services/reconcile.ts
|
|
26214
26398
|
import { readFileSync as readFileSync9 } from "fs";
|
|
26215
|
-
import { join as
|
|
26399
|
+
import { join as join15 } from "path";
|
|
26216
26400
|
function loadDocsIndex(projectRoot) {
|
|
26217
26401
|
if (!hasLocalWorkspace()) return "";
|
|
26218
26402
|
try {
|
|
26219
|
-
const indexPath =
|
|
26403
|
+
const indexPath = join15(projectRoot, "docs", "INDEX.md");
|
|
26220
26404
|
const raw = readFileSync9(indexPath, "utf8");
|
|
26221
26405
|
const rows = raw.split("\n").filter((l) => l.startsWith("| ["));
|
|
26222
26406
|
if (rows.length === 0) return "";
|
|
@@ -26793,7 +26977,7 @@ Produce your analysis and structured output above. Present Part 1 to the user an
|
|
|
26793
26977
|
|
|
26794
26978
|
// src/tools/review.ts
|
|
26795
26979
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
26796
|
-
import { join as
|
|
26980
|
+
import { join as join16 } from "path";
|
|
26797
26981
|
init_git();
|
|
26798
26982
|
|
|
26799
26983
|
// src/services/review.ts
|
|
@@ -26991,8 +27175,6 @@ Re-run build_execute complete with a production_verification field, then re-subm
|
|
|
26991
27175
|
}
|
|
26992
27176
|
|
|
26993
27177
|
// src/tools/review.ts
|
|
26994
|
-
var REVIEW_DISPATCH_THRESHOLD = 50 * 1024;
|
|
26995
|
-
var REVIEW_DISPATCH_CEILING = 40 * 1024;
|
|
26996
27178
|
var REVIEW_ECHO_CAP = 4e3;
|
|
26997
27179
|
function trimForEcho(text) {
|
|
26998
27180
|
if (text.length <= REVIEW_ECHO_CAP) return text;
|
|
@@ -27022,7 +27204,7 @@ ${task.buildReport}` : "### Build Report\n(none recorded)";
|
|
|
27022
27204
|
${diff}
|
|
27023
27205
|
\`\`\`` : "### Branch diff vs base\n(no diff resolved \u2014 not a git repo, no base ref, or no committed changes)";
|
|
27024
27206
|
let projectContext = "";
|
|
27025
|
-
const ctxPath =
|
|
27207
|
+
const ctxPath = join16(config2.projectRoot, ".agents", "papi-context.md");
|
|
27026
27208
|
if (existsSync10(ctxPath)) {
|
|
27027
27209
|
try {
|
|
27028
27210
|
projectContext = `### Project context (.agents/papi-context.md)
|
|
@@ -27201,7 +27383,7 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
27201
27383
|
};
|
|
27202
27384
|
}
|
|
27203
27385
|
const details = [];
|
|
27204
|
-
const papiDir =
|
|
27386
|
+
const papiDir = join16(config2.projectRoot, ".papi");
|
|
27205
27387
|
if (existsSync10(papiDir)) {
|
|
27206
27388
|
try {
|
|
27207
27389
|
const commitResult = stageDirAndCommit(
|
|
@@ -27340,7 +27522,7 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
27340
27522
|
if (typeof rawPreset === "string" && rawPreset.length > 0 && !reviewPreset) {
|
|
27341
27523
|
return errorResponse(`review_preset "${rawPreset}" is not valid. Use "gate", "full", or "security-focused".`);
|
|
27342
27524
|
}
|
|
27343
|
-
const autoDispatchOptIn = args.dispatch !== "inline" &&
|
|
27525
|
+
const autoDispatchOptIn = args.dispatch !== "inline" && isCapabilityEnabled(caps, "prReviewer");
|
|
27344
27526
|
const autoDispatchEligible = !verdict && autoDispatchOptIn;
|
|
27345
27527
|
const capabilityAutoReviewEligible = verdict === "accept" && !autoReview && autoDispatchOptIn;
|
|
27346
27528
|
let capabilityReviewSkippedNote = "";
|
|
@@ -27353,16 +27535,27 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
27353
27535
|
);
|
|
27354
27536
|
if (!dispatch.ok) {
|
|
27355
27537
|
if (explicitDispatch) return errorResponse(dispatch.error);
|
|
27356
|
-
} else if (explicitDispatch || autoDispatchEligible && dispatch.contextBytes
|
|
27538
|
+
} else if (explicitDispatch || autoDispatchEligible && shouldDispatch(dispatch.contextBytes)) {
|
|
27357
27539
|
return textResponse(dispatch.prompt);
|
|
27358
27540
|
} else if (capabilityAutoReviewEligible) {
|
|
27359
|
-
|
|
27360
|
-
|
|
27541
|
+
const delivery = planDelivery({
|
|
27542
|
+
payload: dispatch.prompt,
|
|
27543
|
+
kind: "review-context",
|
|
27544
|
+
hasPendingWrite: true,
|
|
27545
|
+
adapterType: config2.adapterType,
|
|
27546
|
+
projectId: config2.projectId,
|
|
27547
|
+
callerKey: taskId
|
|
27548
|
+
});
|
|
27549
|
+
if (delivery.mode === "inline") {
|
|
27550
|
+
return textResponse(delivery.payload);
|
|
27361
27551
|
}
|
|
27362
27552
|
const kb = (dispatch.contextBytes / 1024).toFixed(0);
|
|
27363
|
-
capabilityReviewSkippedNote = `
|
|
27553
|
+
capabilityReviewSkippedNote = delivery.mode === "spilled" ? `
|
|
27364
27554
|
|
|
27365
|
-
> \
|
|
27555
|
+
> \u{1F4C4} pr-reviewer auto-review was too large to return inline (~${kb} KB), so it was written to a file instead of being dropped (task-3030). Verdict recorded. To run the review, read:
|
|
27556
|
+
> \`${delivery.path}\`` : `
|
|
27557
|
+
|
|
27558
|
+
> \u26A0\uFE0F pr-reviewer auto-review could not be delivered \u2014 the diff/build-report (~${kb} KB) exceeds the inline ceiling and this connection has no shared filesystem to spill to. Verdict recorded. To review the diff explicitly, run \`review_submit ${taskId} build-acceptance accept dispatch:"subagent"\`.`;
|
|
27366
27559
|
}
|
|
27367
27560
|
}
|
|
27368
27561
|
if (explicitDispatch && stage !== "build-acceptance") {
|
|
@@ -28834,7 +29027,7 @@ function formatDeferredGateSection(sweep) {
|
|
|
28834
29027
|
|
|
28835
29028
|
// src/tools/agent-list.ts
|
|
28836
29029
|
import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
|
|
28837
|
-
import { join as
|
|
29030
|
+
import { join as join17 } from "path";
|
|
28838
29031
|
var NO_AGENTS_HINT = "No project sub-agents found in `.claude/agents/`. Add a `*.md` file with `name` + `description` frontmatter \u2014 see the 1926-Census marketing sub-agent for a reference implementation.";
|
|
28839
29032
|
function parseAgentFrontmatter(content) {
|
|
28840
29033
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -28846,7 +29039,7 @@ function parseAgentFrontmatter(content) {
|
|
|
28846
29039
|
return { name: nameMatch?.[1].trim(), description };
|
|
28847
29040
|
}
|
|
28848
29041
|
async function listAgents(projectRoot) {
|
|
28849
|
-
const agentsDir =
|
|
29042
|
+
const agentsDir = join17(projectRoot, ".claude", "agents");
|
|
28850
29043
|
let files;
|
|
28851
29044
|
try {
|
|
28852
29045
|
files = await readdir2(agentsDir);
|
|
@@ -28857,7 +29050,7 @@ async function listAgents(projectRoot) {
|
|
|
28857
29050
|
for (const file of files.filter((f) => f.endsWith(".md"))) {
|
|
28858
29051
|
let content;
|
|
28859
29052
|
try {
|
|
28860
|
-
content = await readFile7(
|
|
29053
|
+
content = await readFile7(join17(agentsDir, file), "utf-8");
|
|
28861
29054
|
} catch {
|
|
28862
29055
|
continue;
|
|
28863
29056
|
}
|
|
@@ -28865,7 +29058,7 @@ async function listAgents(projectRoot) {
|
|
|
28865
29058
|
agents.push({
|
|
28866
29059
|
name: meta?.name ?? file.replace(/\.md$/, ""),
|
|
28867
29060
|
description: meta?.description ?? "",
|
|
28868
|
-
path:
|
|
29061
|
+
path: join17(".claude", "agents", file)
|
|
28869
29062
|
});
|
|
28870
29063
|
}
|
|
28871
29064
|
agents.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
@@ -29006,8 +29199,8 @@ async function verifyProject(adapter2) {
|
|
|
29006
29199
|
// src/tools/orient.ts
|
|
29007
29200
|
import { execFile as execFile2 } from "child_process";
|
|
29008
29201
|
import { promisify as promisify2 } from "util";
|
|
29009
|
-
import { readFileSync as readFileSync11, writeFileSync as
|
|
29010
|
-
import { join as
|
|
29202
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync11 } from "fs";
|
|
29203
|
+
import { join as join18 } from "path";
|
|
29011
29204
|
|
|
29012
29205
|
// src/lib/exit-criteria-evaluators.ts
|
|
29013
29206
|
var TEST_ACCOUNT_PATTERN = "ftue-%@test.papi.dev";
|
|
@@ -29063,6 +29256,7 @@ var UNEVALUATABLE = {
|
|
|
29063
29256
|
"c596a082-9ad3-4089-b497-46cd189e892b": "threshold is provisional pending an owner ruling \u2014 no settled bar to evaluate against"
|
|
29064
29257
|
};
|
|
29065
29258
|
async function evaluateExitCriteria(adapter2, projectId, criteria) {
|
|
29259
|
+
const hasProject = typeof projectId === "string" && projectId.trim() !== "";
|
|
29066
29260
|
return Promise.all(criteria.map(async (c) => {
|
|
29067
29261
|
const evaluator = EVALUATORS[c.id];
|
|
29068
29262
|
if (!evaluator) {
|
|
@@ -29070,6 +29264,14 @@ async function evaluateExitCriteria(adapter2, projectId, criteria) {
|
|
|
29070
29264
|
if (!reason) return { ...c, met: c.met, autoEvaluated: false };
|
|
29071
29265
|
return c.met ? { ...c, met: true, autoEvaluated: false } : { ...c, met: null, unevaluatedReason: reason, autoEvaluated: false };
|
|
29072
29266
|
}
|
|
29267
|
+
if (!hasProject) {
|
|
29268
|
+
return {
|
|
29269
|
+
...c,
|
|
29270
|
+
met: c.met ? true : null,
|
|
29271
|
+
unevaluatedReason: "project id unavailable \u2014 this criterion is measured against a specific project and orient could not resolve one",
|
|
29272
|
+
autoEvaluated: false
|
|
29273
|
+
};
|
|
29274
|
+
}
|
|
29073
29275
|
try {
|
|
29074
29276
|
const result = await evaluator(adapter2, projectId);
|
|
29075
29277
|
return {
|
|
@@ -29080,10 +29282,11 @@ async function evaluateExitCriteria(adapter2, projectId, criteria) {
|
|
|
29080
29282
|
autoEvaluated: result.met !== null
|
|
29081
29283
|
};
|
|
29082
29284
|
} catch (err) {
|
|
29285
|
+
console.error(`[exit-criteria] evaluator for criterion ${c.id} threw:`, err);
|
|
29083
29286
|
return {
|
|
29084
29287
|
...c,
|
|
29085
29288
|
met: c.met ? true : null,
|
|
29086
|
-
unevaluatedReason:
|
|
29289
|
+
unevaluatedReason: "evaluator failed \u2014 see server logs. The stored value is shown and is unverified.",
|
|
29087
29290
|
autoEvaluated: false
|
|
29088
29291
|
};
|
|
29089
29292
|
}
|
|
@@ -29462,9 +29665,13 @@ async function getHierarchyPosition(adapter2, projectId) {
|
|
|
29462
29665
|
// task-3008: EVALUATE rather than read the hand-ticked boolean. Best-effort —
|
|
29463
29666
|
// a failing evaluator degrades that criterion to unevaluated and leaves the
|
|
29464
29667
|
// rest intact, because orient is the first call of every session.
|
|
29668
|
+
// task-3226: pass the id THROUGH, undefined and all. This used to coerce to
|
|
29669
|
+
// `''`, which is not a uuid and blew up every SQL-backed evaluator — the
|
|
29670
|
+
// coercion looked like a safe default and was the bug. evaluateExitCriteria
|
|
29671
|
+
// now reports "project id unavailable" for that case, which is the truth.
|
|
29465
29672
|
stageExitCriteria: await evaluateExitCriteria(
|
|
29466
29673
|
adapter2,
|
|
29467
|
-
projectId
|
|
29674
|
+
projectId,
|
|
29468
29675
|
activeStage.exitCriteria ?? []
|
|
29469
29676
|
).catch(() => (activeStage.exitCriteria ?? []).map((c) => ({ ...c, autoEvaluated: false })))
|
|
29470
29677
|
};
|
|
@@ -29475,7 +29682,7 @@ async function getHierarchyPosition(adapter2, projectId) {
|
|
|
29475
29682
|
var GIT_TAG_TIMEOUT_MS = 2e3;
|
|
29476
29683
|
async function checkNpmVersionDrift() {
|
|
29477
29684
|
try {
|
|
29478
|
-
const pkgPath =
|
|
29685
|
+
const pkgPath = join18(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
|
|
29479
29686
|
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
29480
29687
|
const localVersion = pkg.version;
|
|
29481
29688
|
const packageName = pkg.name;
|
|
@@ -30176,7 +30383,7 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
|
|
|
30176
30383
|
|
|
30177
30384
|
\u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
|
|
30178
30385
|
}
|
|
30179
|
-
const claudeMdPath =
|
|
30386
|
+
const claudeMdPath = join18(projectRoot, "CLAUDE.md");
|
|
30180
30387
|
if (!existsSync11(claudeMdPath)) return "";
|
|
30181
30388
|
const content = readFileSync11(claudeMdPath, "utf-8");
|
|
30182
30389
|
const additions = [];
|
|
@@ -30187,7 +30394,7 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
|
|
|
30187
30394
|
additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_2));
|
|
30188
30395
|
}
|
|
30189
30396
|
if (additions.length === 0) return "";
|
|
30190
|
-
|
|
30397
|
+
writeFileSync7(claudeMdPath, content + additions.join(""), "utf-8");
|
|
30191
30398
|
const tierNames = [];
|
|
30192
30399
|
if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1))) tierNames.push("Established (batch building, strategy reviews, AD lifecycle)");
|
|
30193
30400
|
if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T2))) tierNames.push("Mature (idea pipeline, doc registry, advanced patterns)");
|
|
@@ -30709,6 +30916,10 @@ var zoomOutTool = {
|
|
|
30709
30916
|
enum: ["prepare", "apply"],
|
|
30710
30917
|
description: '"prepare" returns the retrospective prompt. "apply" accepts your output. Defaults to "prepare" when omitted.'
|
|
30711
30918
|
},
|
|
30919
|
+
llm_response_file: {
|
|
30920
|
+
type: "string",
|
|
30921
|
+
description: 'Absolute path to a file containing the zoom-out output (mode "apply" only). LOCAL stdio servers only \u2014 on the hosted connection the server cannot read files on your machine. Use when the response is too large to pass inline (some hosts cap tool inputs around 50KB). Mutually exclusive with llm_response.'
|
|
30922
|
+
},
|
|
30712
30923
|
llm_response: {
|
|
30713
30924
|
type: "string",
|
|
30714
30925
|
description: 'Your raw output from executing the retrospective prompt (mode "apply" only).'
|
|
@@ -30730,10 +30941,12 @@ async function handleZoomOut(adapter2, config2, args) {
|
|
|
30730
30941
|
const toolMode = args.mode;
|
|
30731
30942
|
try {
|
|
30732
30943
|
if (toolMode === "apply") {
|
|
30733
|
-
const
|
|
30734
|
-
|
|
30735
|
-
|
|
30736
|
-
|
|
30944
|
+
const resolved = await resolveLlmResponse(
|
|
30945
|
+
args.llm_response,
|
|
30946
|
+
args.llm_response_file
|
|
30947
|
+
);
|
|
30948
|
+
if (!resolved.ok) return errorResponse(resolved.error);
|
|
30949
|
+
const llmResponse = resolved.llmResponse;
|
|
30737
30950
|
const cycleNumber = typeof args.cycle_number === "number" ? args.cycle_number : 0;
|
|
30738
30951
|
const result = await applyZoomOut(adapter2, llmResponse, cycleNumber);
|
|
30739
30952
|
return textResponse(
|
|
@@ -30748,16 +30961,8 @@ Retrospective saved. Use insights to inform your next \`strategy_review\` or \`p
|
|
|
30748
30961
|
}
|
|
30749
30962
|
{
|
|
30750
30963
|
const result = await prepareZoomOut(adapter2, config2.projectRoot);
|
|
30751
|
-
const
|
|
30752
|
-
const
|
|
30753
|
-
let dispatch;
|
|
30754
|
-
if (args.dispatch === "inline" || args.dispatch === "subagent") {
|
|
30755
|
-
dispatch = args.dispatch;
|
|
30756
|
-
} else if (autoDispatchEnabled && result.contextBytes > autoDispatchThreshold) {
|
|
30757
|
-
dispatch = "subagent";
|
|
30758
|
-
} else {
|
|
30759
|
-
dispatch = "inline";
|
|
30760
|
-
}
|
|
30964
|
+
const explicit = args.dispatch === "inline" ? false : args.dispatch === "subagent" ? true : void 0;
|
|
30965
|
+
const dispatch = shouldDispatch(result.contextBytes, explicit) ? "subagent" : "inline";
|
|
30761
30966
|
if (dispatch === "subagent") {
|
|
30762
30967
|
const dispatchPrompt = buildSubagentDispatchPrompt({
|
|
30763
30968
|
tool: "zoom_out",
|
|
@@ -31002,8 +31207,8 @@ ${result.userMessage}
|
|
|
31002
31207
|
import { readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
31003
31208
|
|
|
31004
31209
|
// src/services/scope-brief.ts
|
|
31005
|
-
import { writeFileSync as
|
|
31006
|
-
import { join as
|
|
31210
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
31211
|
+
import { join as join19, dirname as dirname5 } from "path";
|
|
31007
31212
|
var SCOPE_BRIEF_SYSTEM = `You are a technical scoping tool. You receive a brief-class task (too large to build directly) and decompose it into a structured scope document.
|
|
31008
31213
|
|
|
31009
31214
|
A scope document must:
|
|
@@ -31060,14 +31265,14 @@ async function applyScopeBrief(adapter2, input) {
|
|
|
31060
31265
|
}
|
|
31061
31266
|
const slug = input.taskId.replace(/[^a-z0-9-]/g, "-").toLowerCase();
|
|
31062
31267
|
const relPath = `docs/scopes/${slug}.md`;
|
|
31063
|
-
const absPath =
|
|
31268
|
+
const absPath = join19(input.projectRoot, relPath);
|
|
31064
31269
|
const docBody = addFrontmatter(docContent, task, input.cycleNumber);
|
|
31065
31270
|
const collector = new FileWriteCollector();
|
|
31066
31271
|
if (input.adapterType === "proxy") {
|
|
31067
31272
|
collector.add({ path: relPath, content: docBody, mode: "overwrite" });
|
|
31068
31273
|
} else {
|
|
31069
31274
|
mkdirSync5(dirname5(absPath), { recursive: true });
|
|
31070
|
-
|
|
31275
|
+
writeFileSync8(absPath, docBody, "utf-8");
|
|
31071
31276
|
}
|
|
31072
31277
|
const taskCount = countSubTasks(docContent);
|
|
31073
31278
|
const summary = buildSummary(task, taskCount);
|
|
@@ -31354,7 +31559,7 @@ ${formatted}`, meta));
|
|
|
31354
31559
|
|
|
31355
31560
|
// src/lib/dist-staleness.ts
|
|
31356
31561
|
import { readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
31357
|
-
import { createHash as
|
|
31562
|
+
import { createHash as createHash6 } from "crypto";
|
|
31358
31563
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31359
31564
|
var BOOT_MS = Date.now();
|
|
31360
31565
|
var DEFAULT_SKEW_MS = 2e3;
|
|
@@ -31368,7 +31573,7 @@ var SELF_PATH = (() => {
|
|
|
31368
31573
|
})();
|
|
31369
31574
|
function hashFile(path7) {
|
|
31370
31575
|
try {
|
|
31371
|
-
return
|
|
31576
|
+
return createHash6("sha256").update(readFileSync13(path7)).digest("hex");
|
|
31372
31577
|
} catch {
|
|
31373
31578
|
return null;
|
|
31374
31579
|
}
|
|
@@ -32018,19 +32223,19 @@ Its build reports, comments, and history moved with it; the cycle assignment was
|
|
|
32018
32223
|
|
|
32019
32224
|
// src/services/harness-inventory.ts
|
|
32020
32225
|
import { readdir as readdir3, readFile as readFile8, stat as stat3 } from "fs/promises";
|
|
32021
|
-
import { join as
|
|
32022
|
-
import { createHash as
|
|
32226
|
+
import { join as join20 } from "path";
|
|
32227
|
+
import { createHash as createHash7 } from "crypto";
|
|
32023
32228
|
var RECOMMENDED_HOOKS = ["stop-release-check.sh", "claude-md-size-guard.sh"];
|
|
32024
32229
|
async function computeFingerprint(root) {
|
|
32025
32230
|
const parts = [];
|
|
32026
32231
|
for (const sub of [".claude/skills", ".claude/agents", ".claude/hooks"]) {
|
|
32027
|
-
const dir =
|
|
32232
|
+
const dir = join20(root, sub);
|
|
32028
32233
|
try {
|
|
32029
32234
|
const names = (await readdir3(dir)).sort((a, b2) => a.localeCompare(b2));
|
|
32030
32235
|
for (const name of names) {
|
|
32031
32236
|
let mtime = "";
|
|
32032
32237
|
try {
|
|
32033
|
-
mtime = String(Math.floor((await stat3(
|
|
32238
|
+
mtime = String(Math.floor((await stat3(join20(dir, name))).mtimeMs));
|
|
32034
32239
|
} catch {
|
|
32035
32240
|
}
|
|
32036
32241
|
parts.push(`${sub}/${name}:${mtime}`);
|
|
@@ -32045,11 +32250,11 @@ async function computeFingerprint(root) {
|
|
|
32045
32250
|
} catch {
|
|
32046
32251
|
parts.push("manifest:none");
|
|
32047
32252
|
}
|
|
32048
|
-
return
|
|
32253
|
+
return createHash7("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
|
|
32049
32254
|
}
|
|
32050
32255
|
async function readSkillDescription(skillDir) {
|
|
32051
32256
|
try {
|
|
32052
|
-
const content = await readFile8(
|
|
32257
|
+
const content = await readFile8(join20(skillDir, "SKILL.md"), "utf-8");
|
|
32053
32258
|
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
32054
32259
|
if (!fm) return void 0;
|
|
32055
32260
|
const desc = fm[1].match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\w+:|\n---|$)/m);
|
|
@@ -32069,17 +32274,17 @@ async function scanInventory(root, toolDefs) {
|
|
|
32069
32274
|
version = loadManifest().packageVersion;
|
|
32070
32275
|
} catch {
|
|
32071
32276
|
}
|
|
32072
|
-
const skillsDir =
|
|
32277
|
+
const skillsDir = join20(root, ".claude", "skills");
|
|
32073
32278
|
try {
|
|
32074
32279
|
const dirents = await readdir3(skillsDir, { withFileTypes: true });
|
|
32075
32280
|
for (const d of dirents.filter((e) => e.isDirectory())) {
|
|
32076
32281
|
entries.push({
|
|
32077
32282
|
kind: "skill",
|
|
32078
32283
|
name: d.name,
|
|
32079
|
-
description: await readSkillDescription(
|
|
32284
|
+
description: await readSkillDescription(join20(skillsDir, d.name)),
|
|
32080
32285
|
version,
|
|
32081
32286
|
status: stale.has(d.name) ? "stale_fork" : "ok",
|
|
32082
|
-
path:
|
|
32287
|
+
path: join20(".claude", "skills", d.name)
|
|
32083
32288
|
});
|
|
32084
32289
|
}
|
|
32085
32290
|
} catch {
|
|
@@ -32095,9 +32300,9 @@ async function scanInventory(root, toolDefs) {
|
|
|
32095
32300
|
}
|
|
32096
32301
|
const present = /* @__PURE__ */ new Set();
|
|
32097
32302
|
try {
|
|
32098
|
-
for (const f of (await readdir3(
|
|
32303
|
+
for (const f of (await readdir3(join20(root, ".claude", "hooks"))).filter((n) => n.endsWith(".sh"))) {
|
|
32099
32304
|
present.add(f);
|
|
32100
|
-
entries.push({ kind: "hook", name: f, status: "ok", path:
|
|
32305
|
+
entries.push({ kind: "hook", name: f, status: "ok", path: join20(".claude", "hooks", f) });
|
|
32101
32306
|
}
|
|
32102
32307
|
} catch {
|
|
32103
32308
|
}
|
|
@@ -32543,7 +32748,7 @@ function createServer(adapter2, config2) {
|
|
|
32543
32748
|
const __pkgDir = dirname6(__pkgFilename);
|
|
32544
32749
|
let serverVersion = "unknown";
|
|
32545
32750
|
try {
|
|
32546
|
-
const pkg = JSON.parse(readFileSync14(
|
|
32751
|
+
const pkg = JSON.parse(readFileSync14(join21(__pkgDir, "..", "package.json"), "utf-8"));
|
|
32547
32752
|
serverVersion = pkg.version ?? "unknown";
|
|
32548
32753
|
} catch {
|
|
32549
32754
|
}
|
|
@@ -32562,7 +32767,7 @@ function createServer(adapter2, config2) {
|
|
|
32562
32767
|
}
|
|
32563
32768
|
const __filename = fileURLToPath4(import.meta.url);
|
|
32564
32769
|
const __dirname2 = dirname6(__filename);
|
|
32565
|
-
const skillsDir =
|
|
32770
|
+
const skillsDir = join21(__dirname2, "..", "skills");
|
|
32566
32771
|
function parseSkillFrontmatter(content) {
|
|
32567
32772
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
32568
32773
|
if (!match) return null;
|
|
@@ -32580,7 +32785,7 @@ function createServer(adapter2, config2) {
|
|
|
32580
32785
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
32581
32786
|
const prompts = [];
|
|
32582
32787
|
for (const file of mdFiles) {
|
|
32583
|
-
const content = await readFile9(
|
|
32788
|
+
const content = await readFile9(join21(skillsDir, file), "utf-8");
|
|
32584
32789
|
const meta = parseSkillFrontmatter(content);
|
|
32585
32790
|
if (meta) {
|
|
32586
32791
|
prompts.push({ name: meta.name, description: meta.description });
|
|
@@ -32596,7 +32801,7 @@ function createServer(adapter2, config2) {
|
|
|
32596
32801
|
try {
|
|
32597
32802
|
const files = await readdir4(skillsDir);
|
|
32598
32803
|
for (const file of files.filter((f) => f.endsWith(".md"))) {
|
|
32599
|
-
const content = await readFile9(
|
|
32804
|
+
const content = await readFile9(join21(skillsDir, file), "utf-8");
|
|
32600
32805
|
const meta = parseSkillFrontmatter(content);
|
|
32601
32806
|
if (meta?.name === name) {
|
|
32602
32807
|
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
@@ -32882,7 +33087,7 @@ ${usageLine(decision.usage)}`;
|
|
|
32882
33087
|
// src/transport-http.ts
|
|
32883
33088
|
init_proxy_adapter();
|
|
32884
33089
|
import { createServer as createHttpServer } from "http";
|
|
32885
|
-
import { createHash as
|
|
33090
|
+
import { createHash as createHash8 } from "crypto";
|
|
32886
33091
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
32887
33092
|
var BEARER_PREFIX = "papi_";
|
|
32888
33093
|
var BEARER_REGEX = /^(papi_|papi_oauth_)[a-f0-9]{64}$/;
|
|
@@ -33006,7 +33211,7 @@ var AUTH_REJECTED_TTL_MS = 6e4;
|
|
|
33006
33211
|
var AUTH_CACHE_MAX = 5e3;
|
|
33007
33212
|
var authVerdicts = /* @__PURE__ */ new Map();
|
|
33008
33213
|
function bearerKey(bearer) {
|
|
33009
|
-
return
|
|
33214
|
+
return createHash8("sha256").update(bearer).digest("hex");
|
|
33010
33215
|
}
|
|
33011
33216
|
function readAuthVerdict(bearer, now = Date.now()) {
|
|
33012
33217
|
const hit = authVerdicts.get(bearerKey(bearer));
|
|
@@ -33421,7 +33626,7 @@ async function dispatchRequest(args) {
|
|
|
33421
33626
|
var __dirname = dirname7(fileURLToPath5(import.meta.url));
|
|
33422
33627
|
var pkgVersion = "unknown";
|
|
33423
33628
|
try {
|
|
33424
|
-
const pkg = JSON.parse(readFileSync19(
|
|
33629
|
+
const pkg = JSON.parse(readFileSync19(join26(__dirname, "..", "package.json"), "utf-8"));
|
|
33425
33630
|
pkgVersion = pkg.version;
|
|
33426
33631
|
} catch {
|
|
33427
33632
|
}
|