@plune-ai/cli 0.34.1 → 0.34.2
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/README.md +10 -0
- package/dist/cli.cjs +66 -17
- package/dist/mcp-tools.cjs +51 -2
- package/dist/mcp-tools.d.cts +47 -0
- package/dist/mcp-tools.d.ts +47 -0
- package/dist/mcp-tools.js +51 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -631,6 +631,16 @@ once; the rest it finds with `plune_discover_tools`. Every write tool takes `dry
|
|
|
631
631
|
100 items, and the agent is held to the platform's agent rules: a proposed case lands in the review
|
|
632
632
|
queue, a bulk decision needs a confirm, and nothing is deleted through MCP.
|
|
633
633
|
|
|
634
|
+
`plune_cases_generate` asks the project's AI for case proposals from pasted text (a requirement, a
|
|
635
|
+
story, a ticket's text; 20 words to 12 000 characters, up to 12 proposals, 8 by default). It spends the
|
|
636
|
+
project's AI budget, and its proposals wait in the review queue like any other until a person approves
|
|
637
|
+
them. The run is queued: the tool answers with its id, and `plune_cases_generation_get` reads where it
|
|
638
|
+
stands, what it produced and what it cost. A Jira ticket by its key is not taken over MCP: paste its
|
|
639
|
+
text. The platform's refusals (409 `duplicate_source` until `confirmDuplicates: true`, 422 for text
|
|
640
|
+
too short or too long, 429 limits, 503 / 409 when the project's AI is not set up) come back with
|
|
641
|
+
their `code` as the platform sent them. Both tools work in the server's `--project` unless the call
|
|
642
|
+
names a `projectId`.
|
|
643
|
+
|
|
634
644
|
Claude Code:
|
|
635
645
|
|
|
636
646
|
```bash
|
package/dist/cli.cjs
CHANGED
|
@@ -6698,7 +6698,7 @@ var init_logout = __esm({
|
|
|
6698
6698
|
});
|
|
6699
6699
|
|
|
6700
6700
|
// src/mcp-tools/schemas.ts
|
|
6701
|
-
var import_zod4, BATCH_MAX2, isHttp, httpLink, id, dryRun, MISSION_STATES, MISSION_DOCUMENTATION, MISSION_LINK_KINDS, missionName, missionSummary, missionUrl, missionFields, sourceTitle, sourceVersion, linkSource, fileSource, ACCESS_KINDS, questionText, questionInput, STRATEGY_SOURCE_KINDS, TEST_LEVELS, QUALITY_CHARACTERISTICS, TEST_TECHNIQUES, RISK_SCALE, itemFields, strategyItem, items, unique, linkIds, share, strategyContent, SOURCE_REF_KINDS, proposedCase, API_METHODS, proposedEndpoint, REVIEW_STATES, REVIEW_KINDS, REJECTION_CATEGORIES, entity, plannedRequest, writeOutput, batchOutput, bulkPreview;
|
|
6701
|
+
var import_zod4, BATCH_MAX2, isHttp, httpLink, id, dryRun, MISSION_STATES, MISSION_DOCUMENTATION, MISSION_LINK_KINDS, missionName, missionSummary, missionUrl, missionFields, sourceTitle, sourceVersion, linkSource, fileSource, ACCESS_KINDS, questionText, questionInput, STRATEGY_SOURCE_KINDS, TEST_LEVELS, QUALITY_CHARACTERISTICS, TEST_TECHNIQUES, RISK_SCALE, itemFields, strategyItem, items, unique, linkIds, share, strategyContent, SOURCE_REF_KINDS, proposedCase, CASE_GENERATION_TITLE_MAX, CASE_GENERATION_MAX_CASES, CASE_GENERATION_DEFAULT_CASES, CASE_GENERATION_MIN_WORDS, CASE_GENERATION_TEXT_MAX, caseGenerationBody, API_METHODS, proposedEndpoint, REVIEW_STATES, REVIEW_KINDS, REJECTION_CATEGORIES, entity, plannedRequest, writeOutput, batchOutput, bulkPreview;
|
|
6702
6702
|
var init_schemas = __esm({
|
|
6703
6703
|
"src/mcp-tools/schemas.ts"() {
|
|
6704
6704
|
"use strict";
|
|
@@ -6828,6 +6828,21 @@ var init_schemas = __esm({
|
|
|
6828
6828
|
source: import_zod4.z.strictObject({ kind: import_zod4.z.enum(SOURCE_REF_KINDS), value: import_zod4.z.string().min(1).max(100) }).optional().describe("What you read to design it"),
|
|
6829
6829
|
rationale: import_zod4.z.string().min(1).max(400).optional().describe("Why the case is worth having")
|
|
6830
6830
|
});
|
|
6831
|
+
CASE_GENERATION_TITLE_MAX = 300;
|
|
6832
|
+
CASE_GENERATION_MAX_CASES = 12;
|
|
6833
|
+
CASE_GENERATION_DEFAULT_CASES = 8;
|
|
6834
|
+
CASE_GENERATION_MIN_WORDS = 20;
|
|
6835
|
+
CASE_GENERATION_TEXT_MAX = 12e3;
|
|
6836
|
+
caseGenerationBody = {
|
|
6837
|
+
input: import_zod4.z.strictObject({
|
|
6838
|
+
kind: import_zod4.z.literal("pasted-text"),
|
|
6839
|
+
text: import_zod4.z.string().describe(`The requirement, story or ticket text, at least ${CASE_GENERATION_MIN_WORDS} words and at most ${CASE_GENERATION_TEXT_MAX} characters`),
|
|
6840
|
+
title: import_zod4.z.string().trim().max(CASE_GENERATION_TITLE_MAX).optional()
|
|
6841
|
+
}),
|
|
6842
|
+
maxCases: import_zod4.z.number().int().min(1).max(CASE_GENERATION_MAX_CASES).optional().describe(`How many proposals at most; ${CASE_GENERATION_DEFAULT_CASES} when left out`),
|
|
6843
|
+
language: import_zod4.z.string().trim().min(2).max(35).optional().describe("The cases' language; the project's AI language when left out"),
|
|
6844
|
+
confirmDuplicates: import_zod4.z.boolean().optional().describe("true: go ahead although cases or proposals from this text exist (the platform answers 409 duplicate_source first)")
|
|
6845
|
+
};
|
|
6831
6846
|
API_METHODS = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
6832
6847
|
proposedEndpoint = import_zod4.z.strictObject({
|
|
6833
6848
|
method: import_zod4.z.enum(API_METHODS),
|
|
@@ -6890,7 +6905,7 @@ function isPluneToolName(name) {
|
|
|
6890
6905
|
function defaultVisibleTools(scope) {
|
|
6891
6906
|
return PLUNE_TOOLS.filter((t) => t.visibility === "core" && !(scope === "read" && t.write)).map((t) => t.name);
|
|
6892
6907
|
}
|
|
6893
|
-
var import_zod5, MAX_VISIBLE_TOOLS, define, listPage, missionId, PLUNE_TOOLS, PLUNE_TOOL_NAMES, BY_NAME;
|
|
6908
|
+
var import_zod5, MAX_VISIBLE_TOOLS, define, listPage, missionId, projectId, PLUNE_TOOLS, PLUNE_TOOL_NAMES, BY_NAME;
|
|
6894
6909
|
var init_contract = __esm({
|
|
6895
6910
|
"src/mcp-tools/contract.ts"() {
|
|
6896
6911
|
"use strict";
|
|
@@ -6904,6 +6919,7 @@ var init_contract = __esm({
|
|
|
6904
6919
|
cursor: import_zod5.z.string().min(1).optional().describe("nextCursor of the previous page")
|
|
6905
6920
|
};
|
|
6906
6921
|
missionId = id("The mission id");
|
|
6922
|
+
projectId = import_zod5.z.string().min(1).optional().describe("The project; the one the server was started with when left out");
|
|
6907
6923
|
PLUNE_TOOLS = [
|
|
6908
6924
|
define({
|
|
6909
6925
|
name: "plune_project_context",
|
|
@@ -6999,6 +7015,26 @@ var init_contract = __esm({
|
|
|
6999
7015
|
visibility: "core",
|
|
7000
7016
|
endpoints: ["POST /v1/ingest/cairn"]
|
|
7001
7017
|
}),
|
|
7018
|
+
define({
|
|
7019
|
+
name: "plune_cases_generate",
|
|
7020
|
+
title: "Generate cases from pasted text",
|
|
7021
|
+
description: "Generate test case proposals from pasted text \u2014 a requirement, a user story, the text of a ticket \u2014 with the project's AI. It spends the project's AI budget. The proposals land in the review queue and nothing becomes a case until a person approves it. The run is queued: keep its id and poll it with plune_cases_generation_get until it is done. A Jira ticket by its key is not taken here: paste its text.",
|
|
7022
|
+
input: import_zod5.z.strictObject({ projectId, ...caseGenerationBody, dry_run: dryRun }),
|
|
7023
|
+
output: writeOutput,
|
|
7024
|
+
write: true,
|
|
7025
|
+
visibility: "core",
|
|
7026
|
+
endpoints: ["POST /v1/projects/:id/case-generations"]
|
|
7027
|
+
}),
|
|
7028
|
+
define({
|
|
7029
|
+
name: "plune_cases_generation_get",
|
|
7030
|
+
title: "Read a case generation",
|
|
7031
|
+
description: "Check where a run of cases from pasted text stands \u2014 queued, running, done, failed or cancelled \u2014 with how many proposals it made, how many it skipped, and what it cost.",
|
|
7032
|
+
input: import_zod5.z.strictObject({ projectId, generationId: id("The id the start answered with") }),
|
|
7033
|
+
output: import_zod5.z.object({ generation: entity }),
|
|
7034
|
+
write: false,
|
|
7035
|
+
visibility: "core",
|
|
7036
|
+
endpoints: ["GET /v1/projects/:id/case-generations/:generationId"]
|
|
7037
|
+
}),
|
|
7002
7038
|
define({
|
|
7003
7039
|
name: "plune_mission_create",
|
|
7004
7040
|
title: "Create a mission",
|
|
@@ -7364,10 +7400,10 @@ function createPluneToolExecutor(opts) {
|
|
|
7364
7400
|
]);
|
|
7365
7401
|
const projectList = asArray(asRecord(projects.body)["projects"]);
|
|
7366
7402
|
const only = projectList.length === 1 ? asRecord(projectList[0])["id"] : void 0;
|
|
7367
|
-
const
|
|
7403
|
+
const projectId2 = opts.projectId ?? (typeof only === "string" ? only : null);
|
|
7368
7404
|
const total = asRecord(cases.body)["total"];
|
|
7369
7405
|
return {
|
|
7370
|
-
projectId,
|
|
7406
|
+
projectId: projectId2,
|
|
7371
7407
|
projects: projectList,
|
|
7372
7408
|
suites: asArray(asRecord(suites.body)["suites"]),
|
|
7373
7409
|
cases: { total: typeof total === "number" ? total : 0 },
|
|
@@ -7436,6 +7472,18 @@ function createPluneToolExecutor(opts) {
|
|
|
7436
7472
|
const proposed = summary["proposed"];
|
|
7437
7473
|
return { dryRun: false, requested: cases.length, applied: typeof proposed === "number" ? proposed : 0, report: summary };
|
|
7438
7474
|
},
|
|
7475
|
+
async plune_cases_generate(i) {
|
|
7476
|
+
const { dry_run, projectId: projectId2, ...body } = i;
|
|
7477
|
+
const project = projectId2 ?? opts.projectId;
|
|
7478
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
7479
|
+
return write({ method: "POST", path: path19("/v1/projects/:id/case-generations", project), body: withoutUndefined(body) }, dry_run);
|
|
7480
|
+
},
|
|
7481
|
+
async plune_cases_generation_get(i) {
|
|
7482
|
+
const project = i["projectId"] ?? opts.projectId;
|
|
7483
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
7484
|
+
const res = await call({ method: "GET", path: path19("/v1/projects/:id/case-generations/:generationId", project, str2(i["generationId"])) });
|
|
7485
|
+
return { generation: asRecord(res.body) };
|
|
7486
|
+
},
|
|
7439
7487
|
async plune_mission_create(i) {
|
|
7440
7488
|
const { dry_run, ...body } = i;
|
|
7441
7489
|
return write({ method: "POST", path: "/v1/missions", body: withoutUndefined(body) }, dry_run);
|
|
@@ -7579,7 +7627,7 @@ function createPluneToolExecutor(opts) {
|
|
|
7579
7627
|
}
|
|
7580
7628
|
};
|
|
7581
7629
|
}
|
|
7582
|
-
var import_node_crypto5, CONFIRM_TTL_MS, READ_ONLY_REFUSAL, readScopeMessage, fail, zodMessage, path19, withoutUndefined, asRecord, asArray, cursorOf, rawWrites;
|
|
7630
|
+
var import_node_crypto5, CONFIRM_TTL_MS, READ_ONLY_REFUSAL, NO_PROJECT, readScopeMessage, fail, zodMessage, path19, withoutUndefined, asRecord, asArray, cursorOf, rawWrites;
|
|
7583
7631
|
var init_execute = __esm({
|
|
7584
7632
|
"src/mcp-tools/execute.ts"() {
|
|
7585
7633
|
"use strict";
|
|
@@ -7590,6 +7638,7 @@ var init_execute = __esm({
|
|
|
7590
7638
|
init_schemas();
|
|
7591
7639
|
CONFIRM_TTL_MS = 5 * 60 * 1e3;
|
|
7592
7640
|
READ_ONLY_REFUSAL = "read-only token";
|
|
7641
|
+
NO_PROJECT = "projectId is needed: the server was started without a project (--project or PLUNE_PROJECT). plune_project_context lists the projects.";
|
|
7593
7642
|
readScopeMessage = (what) => `This token has the read scope, so ${what}. Use a token with the write scope (Plune \u2192 Account \u2192 API tokens) and restart the server.`;
|
|
7594
7643
|
fail = (code, message, extra = {}) => ({
|
|
7595
7644
|
ok: false,
|
|
@@ -7704,13 +7753,13 @@ async function handleMcpServe(flags, deps) {
|
|
|
7704
7753
|
const token = (deps.loadToken ?? loadToken)();
|
|
7705
7754
|
if (token === null) throw new NotLoggedInError();
|
|
7706
7755
|
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
7707
|
-
const
|
|
7756
|
+
const projectId2 = flags.project?.trim() || process.env["PLUNE_PROJECT"]?.trim() || void 0;
|
|
7708
7757
|
const serve = deps.serve ?? (await Promise.resolve().then(() => (init_server(), server_exports))).serveStdio;
|
|
7709
7758
|
await serve({
|
|
7710
7759
|
token,
|
|
7711
7760
|
apiUrl,
|
|
7712
7761
|
version: deps.version,
|
|
7713
|
-
...
|
|
7762
|
+
...projectId2 !== void 0 ? { projectId: projectId2 } : {},
|
|
7714
7763
|
...flags.readOnly === true ? { scope: "read" } : {}
|
|
7715
7764
|
});
|
|
7716
7765
|
}
|
|
@@ -8135,7 +8184,7 @@ function readMarkdown(cwd, file) {
|
|
|
8135
8184
|
if (text2.trim() === "") usage(`${target} is empty.`);
|
|
8136
8185
|
return text2;
|
|
8137
8186
|
}
|
|
8138
|
-
function refusal(out, apiUrl,
|
|
8187
|
+
function refusal(out, apiUrl, projectId2) {
|
|
8139
8188
|
const d = out.detail;
|
|
8140
8189
|
const said = d === "" ? "" : ` (${d})`;
|
|
8141
8190
|
switch (out.status) {
|
|
@@ -8146,7 +8195,7 @@ function refusal(out, apiUrl, projectId) {
|
|
|
8146
8195
|
case 403:
|
|
8147
8196
|
return new RequirementsImportError(`The platform refused: ${d || "read-only member"}. Importing needs an owner or a member of the project.`, 1);
|
|
8148
8197
|
case 404:
|
|
8149
|
-
return /^project '.*' not found$/.test(d) ? new RequirementsImportError(`Project ${
|
|
8198
|
+
return /^project '.*' not found$/.test(d) ? new RequirementsImportError(`Project ${projectId2} was not found for this token. A token only works in the project it was minted in; pass --project <id> or use a token from that project.`, 1) : new RequirementsImportError(SUPPORT, 1);
|
|
8150
8199
|
case 409:
|
|
8151
8200
|
return new RequirementsImportError(`A requirement key was taken by another change while the import ran. Nothing was written; run it again${said}.`, 1);
|
|
8152
8201
|
case 413:
|
|
@@ -8176,15 +8225,15 @@ async function handleRequirementsImport(deps) {
|
|
|
8176
8225
|
const token = (deps.loadToken ?? loadToken)();
|
|
8177
8226
|
if (token === null) throw new RequirementsImportError('Not logged in. Run "plune login" first, or set PLUNE_TOKEN.', 2);
|
|
8178
8227
|
const client = createClient({ apiUrl, token, ...deps.fetchImpl !== void 0 ? { fetchImpl: deps.fetchImpl } : {} });
|
|
8179
|
-
const
|
|
8180
|
-
const base = `/v1/projects/${encodeURIComponent(
|
|
8228
|
+
const projectId2 = await resolveProject(client, deps.project, apiUrl);
|
|
8229
|
+
const base = `/v1/projects/${encodeURIComponent(projectId2)}/requirements/import`;
|
|
8181
8230
|
if (mapping === null) {
|
|
8182
8231
|
const out2 = await client.post(`${base}/discover`, { markdown });
|
|
8183
|
-
if (!out2.ok) throw refusal(out2, apiUrl,
|
|
8232
|
+
if (!out2.ok) throw refusal(out2, apiUrl, projectId2);
|
|
8184
8233
|
return { step: "discover", result: out2.body };
|
|
8185
8234
|
}
|
|
8186
8235
|
const out = await client.post(base, { markdown, dryRun: deps.yes !== true, ...mapping });
|
|
8187
|
-
if (!out.ok) throw refusal(out, apiUrl,
|
|
8236
|
+
if (!out.ok) throw refusal(out, apiUrl, projectId2);
|
|
8188
8237
|
return { step: "import", report: out.body };
|
|
8189
8238
|
}
|
|
8190
8239
|
function formatDiscover(file, r) {
|
|
@@ -8340,15 +8389,15 @@ async function handleRequirementsLinks(deps) {
|
|
|
8340
8389
|
const token = (deps.loadToken ?? loadToken)();
|
|
8341
8390
|
if (token === null) throw new RequirementsImportError('Not logged in. Run "plune login" first, or set PLUNE_TOKEN.', 2);
|
|
8342
8391
|
const client = createClient({ apiUrl, token, ...deps.fetchImpl !== void 0 ? { fetchImpl: deps.fetchImpl } : {} });
|
|
8343
|
-
const
|
|
8344
|
-
const base = `/v1/projects/${encodeURIComponent(
|
|
8392
|
+
const projectId2 = await resolveProject(client, deps.project, apiUrl);
|
|
8393
|
+
const base = `/v1/projects/${encodeURIComponent(projectId2)}/requirements/import`;
|
|
8345
8394
|
if (mapping === null) {
|
|
8346
8395
|
const out2 = await client.post(`${base}/discover`, { markdown });
|
|
8347
|
-
if (!out2.ok) throw refusal(out2, apiUrl,
|
|
8396
|
+
if (!out2.ok) throw refusal(out2, apiUrl, projectId2);
|
|
8348
8397
|
return { step: "discover", result: out2.body };
|
|
8349
8398
|
}
|
|
8350
8399
|
const out = await client.post(`${base}/links`, { markdown, dryRun: deps.yes !== true, ...mapping });
|
|
8351
|
-
if (!out.ok) throw refusal(out, apiUrl,
|
|
8400
|
+
if (!out.ok) throw refusal(out, apiUrl, projectId2);
|
|
8352
8401
|
return { step: "links", report: out.body };
|
|
8353
8402
|
}
|
|
8354
8403
|
function formatLinksDiscover(file, r) {
|
package/dist/mcp-tools.cjs
CHANGED
|
@@ -163,6 +163,21 @@ var proposedCase = import_zod.z.strictObject({
|
|
|
163
163
|
source: import_zod.z.strictObject({ kind: import_zod.z.enum(SOURCE_REF_KINDS), value: import_zod.z.string().min(1).max(100) }).optional().describe("What you read to design it"),
|
|
164
164
|
rationale: import_zod.z.string().min(1).max(400).optional().describe("Why the case is worth having")
|
|
165
165
|
});
|
|
166
|
+
var CASE_GENERATION_TITLE_MAX = 300;
|
|
167
|
+
var CASE_GENERATION_MAX_CASES = 12;
|
|
168
|
+
var CASE_GENERATION_DEFAULT_CASES = 8;
|
|
169
|
+
var CASE_GENERATION_MIN_WORDS = 20;
|
|
170
|
+
var CASE_GENERATION_TEXT_MAX = 12e3;
|
|
171
|
+
var caseGenerationBody = {
|
|
172
|
+
input: import_zod.z.strictObject({
|
|
173
|
+
kind: import_zod.z.literal("pasted-text"),
|
|
174
|
+
text: import_zod.z.string().describe(`The requirement, story or ticket text, at least ${CASE_GENERATION_MIN_WORDS} words and at most ${CASE_GENERATION_TEXT_MAX} characters`),
|
|
175
|
+
title: import_zod.z.string().trim().max(CASE_GENERATION_TITLE_MAX).optional()
|
|
176
|
+
}),
|
|
177
|
+
maxCases: import_zod.z.number().int().min(1).max(CASE_GENERATION_MAX_CASES).optional().describe(`How many proposals at most; ${CASE_GENERATION_DEFAULT_CASES} when left out`),
|
|
178
|
+
language: import_zod.z.string().trim().min(2).max(35).optional().describe("The cases' language; the project's AI language when left out"),
|
|
179
|
+
confirmDuplicates: import_zod.z.boolean().optional().describe("true: go ahead although cases or proposals from this text exist (the platform answers 409 duplicate_source first)")
|
|
180
|
+
};
|
|
166
181
|
var API_METHODS = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
167
182
|
var proposedEndpoint = import_zod.z.strictObject({
|
|
168
183
|
method: import_zod.z.enum(API_METHODS),
|
|
@@ -219,6 +234,7 @@ var listPage = {
|
|
|
219
234
|
cursor: import_zod2.z.string().min(1).optional().describe("nextCursor of the previous page")
|
|
220
235
|
};
|
|
221
236
|
var missionId = id("The mission id");
|
|
237
|
+
var projectId = import_zod2.z.string().min(1).optional().describe("The project; the one the server was started with when left out");
|
|
222
238
|
var PLUNE_TOOLS = [
|
|
223
239
|
define({
|
|
224
240
|
name: "plune_project_context",
|
|
@@ -314,6 +330,26 @@ var PLUNE_TOOLS = [
|
|
|
314
330
|
visibility: "core",
|
|
315
331
|
endpoints: ["POST /v1/ingest/cairn"]
|
|
316
332
|
}),
|
|
333
|
+
define({
|
|
334
|
+
name: "plune_cases_generate",
|
|
335
|
+
title: "Generate cases from pasted text",
|
|
336
|
+
description: "Generate test case proposals from pasted text \u2014 a requirement, a user story, the text of a ticket \u2014 with the project's AI. It spends the project's AI budget. The proposals land in the review queue and nothing becomes a case until a person approves it. The run is queued: keep its id and poll it with plune_cases_generation_get until it is done. A Jira ticket by its key is not taken here: paste its text.",
|
|
337
|
+
input: import_zod2.z.strictObject({ projectId, ...caseGenerationBody, dry_run: dryRun }),
|
|
338
|
+
output: writeOutput,
|
|
339
|
+
write: true,
|
|
340
|
+
visibility: "core",
|
|
341
|
+
endpoints: ["POST /v1/projects/:id/case-generations"]
|
|
342
|
+
}),
|
|
343
|
+
define({
|
|
344
|
+
name: "plune_cases_generation_get",
|
|
345
|
+
title: "Read a case generation",
|
|
346
|
+
description: "Check where a run of cases from pasted text stands \u2014 queued, running, done, failed or cancelled \u2014 with how many proposals it made, how many it skipped, and what it cost.",
|
|
347
|
+
input: import_zod2.z.strictObject({ projectId, generationId: id("The id the start answered with") }),
|
|
348
|
+
output: import_zod2.z.object({ generation: entity }),
|
|
349
|
+
write: false,
|
|
350
|
+
visibility: "core",
|
|
351
|
+
endpoints: ["GET /v1/projects/:id/case-generations/:generationId"]
|
|
352
|
+
}),
|
|
317
353
|
define({
|
|
318
354
|
name: "plune_mission_create",
|
|
319
355
|
title: "Create a mission",
|
|
@@ -607,6 +643,7 @@ function createPluneApi(opts) {
|
|
|
607
643
|
// src/mcp-tools/execute.ts
|
|
608
644
|
var CONFIRM_TTL_MS = 5 * 60 * 1e3;
|
|
609
645
|
var READ_ONLY_REFUSAL = "read-only token";
|
|
646
|
+
var NO_PROJECT = "projectId is needed: the server was started without a project (--project or PLUNE_PROJECT). plune_project_context lists the projects.";
|
|
610
647
|
var readScopeMessage = (what) => `This token has the read scope, so ${what}. Use a token with the write scope (Plune \u2192 Account \u2192 API tokens) and restart the server.`;
|
|
611
648
|
var fail = (code, message, extra = {}) => ({
|
|
612
649
|
ok: false,
|
|
@@ -701,10 +738,10 @@ function createPluneToolExecutor(opts) {
|
|
|
701
738
|
]);
|
|
702
739
|
const projectList = asArray(asRecord(projects.body)["projects"]);
|
|
703
740
|
const only = projectList.length === 1 ? asRecord(projectList[0])["id"] : void 0;
|
|
704
|
-
const
|
|
741
|
+
const projectId2 = opts.projectId ?? (typeof only === "string" ? only : null);
|
|
705
742
|
const total = asRecord(cases.body)["total"];
|
|
706
743
|
return {
|
|
707
|
-
projectId,
|
|
744
|
+
projectId: projectId2,
|
|
708
745
|
projects: projectList,
|
|
709
746
|
suites: asArray(asRecord(suites.body)["suites"]),
|
|
710
747
|
cases: { total: typeof total === "number" ? total : 0 },
|
|
@@ -773,6 +810,18 @@ function createPluneToolExecutor(opts) {
|
|
|
773
810
|
const proposed = summary["proposed"];
|
|
774
811
|
return { dryRun: false, requested: cases.length, applied: typeof proposed === "number" ? proposed : 0, report: summary };
|
|
775
812
|
},
|
|
813
|
+
async plune_cases_generate(i) {
|
|
814
|
+
const { dry_run, projectId: projectId2, ...body } = i;
|
|
815
|
+
const project = projectId2 ?? opts.projectId;
|
|
816
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
817
|
+
return write({ method: "POST", path: path("/v1/projects/:id/case-generations", project), body: withoutUndefined(body) }, dry_run);
|
|
818
|
+
},
|
|
819
|
+
async plune_cases_generation_get(i) {
|
|
820
|
+
const project = i["projectId"] ?? opts.projectId;
|
|
821
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
822
|
+
const res = await call({ method: "GET", path: path("/v1/projects/:id/case-generations/:generationId", project, str(i["generationId"])) });
|
|
823
|
+
return { generation: asRecord(res.body) };
|
|
824
|
+
},
|
|
776
825
|
async plune_mission_create(i) {
|
|
777
826
|
const { dry_run, ...body } = i;
|
|
778
827
|
return write({ method: "POST", path: "/v1/missions", body: withoutUndefined(body) }, dry_run);
|
package/dist/mcp-tools.d.cts
CHANGED
|
@@ -208,6 +208,53 @@ declare const PLUNE_TOOLS: readonly [{
|
|
|
208
208
|
write: true;
|
|
209
209
|
visibility: "core";
|
|
210
210
|
endpoints: string[];
|
|
211
|
+
}, {
|
|
212
|
+
name: string;
|
|
213
|
+
title: string;
|
|
214
|
+
description: string;
|
|
215
|
+
input: z.ZodObject<{
|
|
216
|
+
dry_run: z.ZodOptional<z.ZodBoolean>;
|
|
217
|
+
input: z.ZodObject<{
|
|
218
|
+
kind: z.ZodLiteral<"pasted-text">;
|
|
219
|
+
text: z.ZodString;
|
|
220
|
+
title: z.ZodOptional<z.ZodString>;
|
|
221
|
+
}, z.core.$strict>;
|
|
222
|
+
maxCases: z.ZodOptional<z.ZodNumber>;
|
|
223
|
+
language: z.ZodOptional<z.ZodString>;
|
|
224
|
+
confirmDuplicates: z.ZodOptional<z.ZodBoolean>;
|
|
225
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
226
|
+
}, z.core.$strict>;
|
|
227
|
+
output: z.ZodObject<{
|
|
228
|
+
dryRun: z.ZodBoolean;
|
|
229
|
+
request: z.ZodOptional<z.ZodObject<{
|
|
230
|
+
method: z.ZodString;
|
|
231
|
+
path: z.ZodString;
|
|
232
|
+
query: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
233
|
+
body: z.ZodOptional<z.ZodUnknown>;
|
|
234
|
+
}, z.core.$strip>>;
|
|
235
|
+
result: z.ZodOptional<z.ZodObject<{
|
|
236
|
+
id: z.ZodOptional<z.ZodString>;
|
|
237
|
+
}, z.core.$loose>>;
|
|
238
|
+
}, z.core.$strip>;
|
|
239
|
+
write: true;
|
|
240
|
+
visibility: "core";
|
|
241
|
+
endpoints: string[];
|
|
242
|
+
}, {
|
|
243
|
+
name: string;
|
|
244
|
+
title: string;
|
|
245
|
+
description: string;
|
|
246
|
+
input: z.ZodObject<{
|
|
247
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
248
|
+
generationId: z.ZodString;
|
|
249
|
+
}, z.core.$strict>;
|
|
250
|
+
output: z.ZodObject<{
|
|
251
|
+
generation: z.ZodObject<{
|
|
252
|
+
id: z.ZodOptional<z.ZodString>;
|
|
253
|
+
}, z.core.$loose>;
|
|
254
|
+
}, z.core.$strip>;
|
|
255
|
+
write: false;
|
|
256
|
+
visibility: "core";
|
|
257
|
+
endpoints: string[];
|
|
211
258
|
}, {
|
|
212
259
|
name: string;
|
|
213
260
|
title: string;
|
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -208,6 +208,53 @@ declare const PLUNE_TOOLS: readonly [{
|
|
|
208
208
|
write: true;
|
|
209
209
|
visibility: "core";
|
|
210
210
|
endpoints: string[];
|
|
211
|
+
}, {
|
|
212
|
+
name: string;
|
|
213
|
+
title: string;
|
|
214
|
+
description: string;
|
|
215
|
+
input: z.ZodObject<{
|
|
216
|
+
dry_run: z.ZodOptional<z.ZodBoolean>;
|
|
217
|
+
input: z.ZodObject<{
|
|
218
|
+
kind: z.ZodLiteral<"pasted-text">;
|
|
219
|
+
text: z.ZodString;
|
|
220
|
+
title: z.ZodOptional<z.ZodString>;
|
|
221
|
+
}, z.core.$strict>;
|
|
222
|
+
maxCases: z.ZodOptional<z.ZodNumber>;
|
|
223
|
+
language: z.ZodOptional<z.ZodString>;
|
|
224
|
+
confirmDuplicates: z.ZodOptional<z.ZodBoolean>;
|
|
225
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
226
|
+
}, z.core.$strict>;
|
|
227
|
+
output: z.ZodObject<{
|
|
228
|
+
dryRun: z.ZodBoolean;
|
|
229
|
+
request: z.ZodOptional<z.ZodObject<{
|
|
230
|
+
method: z.ZodString;
|
|
231
|
+
path: z.ZodString;
|
|
232
|
+
query: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
233
|
+
body: z.ZodOptional<z.ZodUnknown>;
|
|
234
|
+
}, z.core.$strip>>;
|
|
235
|
+
result: z.ZodOptional<z.ZodObject<{
|
|
236
|
+
id: z.ZodOptional<z.ZodString>;
|
|
237
|
+
}, z.core.$loose>>;
|
|
238
|
+
}, z.core.$strip>;
|
|
239
|
+
write: true;
|
|
240
|
+
visibility: "core";
|
|
241
|
+
endpoints: string[];
|
|
242
|
+
}, {
|
|
243
|
+
name: string;
|
|
244
|
+
title: string;
|
|
245
|
+
description: string;
|
|
246
|
+
input: z.ZodObject<{
|
|
247
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
248
|
+
generationId: z.ZodString;
|
|
249
|
+
}, z.core.$strict>;
|
|
250
|
+
output: z.ZodObject<{
|
|
251
|
+
generation: z.ZodObject<{
|
|
252
|
+
id: z.ZodOptional<z.ZodString>;
|
|
253
|
+
}, z.core.$loose>;
|
|
254
|
+
}, z.core.$strip>;
|
|
255
|
+
write: false;
|
|
256
|
+
visibility: "core";
|
|
257
|
+
endpoints: string[];
|
|
211
258
|
}, {
|
|
212
259
|
name: string;
|
|
213
260
|
title: string;
|
package/dist/mcp-tools.js
CHANGED
|
@@ -127,6 +127,21 @@ var proposedCase = z.strictObject({
|
|
|
127
127
|
source: z.strictObject({ kind: z.enum(SOURCE_REF_KINDS), value: z.string().min(1).max(100) }).optional().describe("What you read to design it"),
|
|
128
128
|
rationale: z.string().min(1).max(400).optional().describe("Why the case is worth having")
|
|
129
129
|
});
|
|
130
|
+
var CASE_GENERATION_TITLE_MAX = 300;
|
|
131
|
+
var CASE_GENERATION_MAX_CASES = 12;
|
|
132
|
+
var CASE_GENERATION_DEFAULT_CASES = 8;
|
|
133
|
+
var CASE_GENERATION_MIN_WORDS = 20;
|
|
134
|
+
var CASE_GENERATION_TEXT_MAX = 12e3;
|
|
135
|
+
var caseGenerationBody = {
|
|
136
|
+
input: z.strictObject({
|
|
137
|
+
kind: z.literal("pasted-text"),
|
|
138
|
+
text: z.string().describe(`The requirement, story or ticket text, at least ${CASE_GENERATION_MIN_WORDS} words and at most ${CASE_GENERATION_TEXT_MAX} characters`),
|
|
139
|
+
title: z.string().trim().max(CASE_GENERATION_TITLE_MAX).optional()
|
|
140
|
+
}),
|
|
141
|
+
maxCases: z.number().int().min(1).max(CASE_GENERATION_MAX_CASES).optional().describe(`How many proposals at most; ${CASE_GENERATION_DEFAULT_CASES} when left out`),
|
|
142
|
+
language: z.string().trim().min(2).max(35).optional().describe("The cases' language; the project's AI language when left out"),
|
|
143
|
+
confirmDuplicates: z.boolean().optional().describe("true: go ahead although cases or proposals from this text exist (the platform answers 409 duplicate_source first)")
|
|
144
|
+
};
|
|
130
145
|
var API_METHODS = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
131
146
|
var proposedEndpoint = z.strictObject({
|
|
132
147
|
method: z.enum(API_METHODS),
|
|
@@ -183,6 +198,7 @@ var listPage = {
|
|
|
183
198
|
cursor: z2.string().min(1).optional().describe("nextCursor of the previous page")
|
|
184
199
|
};
|
|
185
200
|
var missionId = id("The mission id");
|
|
201
|
+
var projectId = z2.string().min(1).optional().describe("The project; the one the server was started with when left out");
|
|
186
202
|
var PLUNE_TOOLS = [
|
|
187
203
|
define({
|
|
188
204
|
name: "plune_project_context",
|
|
@@ -278,6 +294,26 @@ var PLUNE_TOOLS = [
|
|
|
278
294
|
visibility: "core",
|
|
279
295
|
endpoints: ["POST /v1/ingest/cairn"]
|
|
280
296
|
}),
|
|
297
|
+
define({
|
|
298
|
+
name: "plune_cases_generate",
|
|
299
|
+
title: "Generate cases from pasted text",
|
|
300
|
+
description: "Generate test case proposals from pasted text \u2014 a requirement, a user story, the text of a ticket \u2014 with the project's AI. It spends the project's AI budget. The proposals land in the review queue and nothing becomes a case until a person approves it. The run is queued: keep its id and poll it with plune_cases_generation_get until it is done. A Jira ticket by its key is not taken here: paste its text.",
|
|
301
|
+
input: z2.strictObject({ projectId, ...caseGenerationBody, dry_run: dryRun }),
|
|
302
|
+
output: writeOutput,
|
|
303
|
+
write: true,
|
|
304
|
+
visibility: "core",
|
|
305
|
+
endpoints: ["POST /v1/projects/:id/case-generations"]
|
|
306
|
+
}),
|
|
307
|
+
define({
|
|
308
|
+
name: "plune_cases_generation_get",
|
|
309
|
+
title: "Read a case generation",
|
|
310
|
+
description: "Check where a run of cases from pasted text stands \u2014 queued, running, done, failed or cancelled \u2014 with how many proposals it made, how many it skipped, and what it cost.",
|
|
311
|
+
input: z2.strictObject({ projectId, generationId: id("The id the start answered with") }),
|
|
312
|
+
output: z2.object({ generation: entity }),
|
|
313
|
+
write: false,
|
|
314
|
+
visibility: "core",
|
|
315
|
+
endpoints: ["GET /v1/projects/:id/case-generations/:generationId"]
|
|
316
|
+
}),
|
|
281
317
|
define({
|
|
282
318
|
name: "plune_mission_create",
|
|
283
319
|
title: "Create a mission",
|
|
@@ -571,6 +607,7 @@ function createPluneApi(opts) {
|
|
|
571
607
|
// src/mcp-tools/execute.ts
|
|
572
608
|
var CONFIRM_TTL_MS = 5 * 60 * 1e3;
|
|
573
609
|
var READ_ONLY_REFUSAL = "read-only token";
|
|
610
|
+
var NO_PROJECT = "projectId is needed: the server was started without a project (--project or PLUNE_PROJECT). plune_project_context lists the projects.";
|
|
574
611
|
var readScopeMessage = (what) => `This token has the read scope, so ${what}. Use a token with the write scope (Plune \u2192 Account \u2192 API tokens) and restart the server.`;
|
|
575
612
|
var fail = (code, message, extra = {}) => ({
|
|
576
613
|
ok: false,
|
|
@@ -665,10 +702,10 @@ function createPluneToolExecutor(opts) {
|
|
|
665
702
|
]);
|
|
666
703
|
const projectList = asArray(asRecord(projects.body)["projects"]);
|
|
667
704
|
const only = projectList.length === 1 ? asRecord(projectList[0])["id"] : void 0;
|
|
668
|
-
const
|
|
705
|
+
const projectId2 = opts.projectId ?? (typeof only === "string" ? only : null);
|
|
669
706
|
const total = asRecord(cases.body)["total"];
|
|
670
707
|
return {
|
|
671
|
-
projectId,
|
|
708
|
+
projectId: projectId2,
|
|
672
709
|
projects: projectList,
|
|
673
710
|
suites: asArray(asRecord(suites.body)["suites"]),
|
|
674
711
|
cases: { total: typeof total === "number" ? total : 0 },
|
|
@@ -737,6 +774,18 @@ function createPluneToolExecutor(opts) {
|
|
|
737
774
|
const proposed = summary["proposed"];
|
|
738
775
|
return { dryRun: false, requested: cases.length, applied: typeof proposed === "number" ? proposed : 0, report: summary };
|
|
739
776
|
},
|
|
777
|
+
async plune_cases_generate(i) {
|
|
778
|
+
const { dry_run, projectId: projectId2, ...body } = i;
|
|
779
|
+
const project = projectId2 ?? opts.projectId;
|
|
780
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
781
|
+
return write({ method: "POST", path: path("/v1/projects/:id/case-generations", project), body: withoutUndefined(body) }, dry_run);
|
|
782
|
+
},
|
|
783
|
+
async plune_cases_generation_get(i) {
|
|
784
|
+
const project = i["projectId"] ?? opts.projectId;
|
|
785
|
+
if (project === void 0) return fail("invalid_input", NO_PROJECT);
|
|
786
|
+
const res = await call({ method: "GET", path: path("/v1/projects/:id/case-generations/:generationId", project, str(i["generationId"])) });
|
|
787
|
+
return { generation: asRecord(res.body) };
|
|
788
|
+
},
|
|
740
789
|
async plune_mission_create(i) {
|
|
741
790
|
const { dry_run, ...body } = i;
|
|
742
791
|
return write({ method: "POST", path: "/v1/missions", body: withoutUndefined(body) }, dry_run);
|
package/package.json
CHANGED