@codazen/harmonica-mcp 3.6.1 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +403 -370
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21542,25 +21542,25 @@ async function validateAuth(client, orgIdentifier, isOrgSlug, userId) {
|
|
|
21542
21542
|
}
|
|
21543
21543
|
|
|
21544
21544
|
// ../../libs/harmonica-services/src/mcp/org-guard.ts
|
|
21545
|
-
async function assertProjectInOrg(client,
|
|
21546
|
-
const project = await client.getSystem(
|
|
21545
|
+
async function assertProjectInOrg(client, systemId, orgId) {
|
|
21546
|
+
const project = await client.getSystem(systemId);
|
|
21547
21547
|
if (!project) {
|
|
21548
|
-
throw new Error(`Project not found: "${
|
|
21548
|
+
throw new Error(`Project not found: "${systemId}"`);
|
|
21549
21549
|
}
|
|
21550
21550
|
if (project.orgId !== orgId) {
|
|
21551
21551
|
throw new Error(
|
|
21552
|
-
`Project "${
|
|
21552
|
+
`Project "${systemId}" does not belong to the configured organization. Access denied.`
|
|
21553
21553
|
);
|
|
21554
21554
|
}
|
|
21555
21555
|
}
|
|
21556
|
-
async function assertRevisionInProject(client, revisionId,
|
|
21556
|
+
async function assertRevisionInProject(client, revisionId, systemId) {
|
|
21557
21557
|
const revision = await client.getRevision(revisionId);
|
|
21558
21558
|
if (!revision) {
|
|
21559
21559
|
throw new Error(`Revision not found: "${revisionId}"`);
|
|
21560
21560
|
}
|
|
21561
|
-
if (revision.
|
|
21561
|
+
if (revision.systemId !== systemId) {
|
|
21562
21562
|
throw new Error(
|
|
21563
|
-
`Revision "${revisionId}" does not belong to project "${
|
|
21563
|
+
`Revision "${revisionId}" does not belong to project "${systemId}". Access denied.`
|
|
21564
21564
|
);
|
|
21565
21565
|
}
|
|
21566
21566
|
}
|
|
@@ -21575,14 +21575,14 @@ async function assertBeatInOrg(client, beatId, orgId) {
|
|
|
21575
21575
|
);
|
|
21576
21576
|
}
|
|
21577
21577
|
}
|
|
21578
|
-
async function fetchProjectInOrg(client,
|
|
21579
|
-
const project = await client.getSystem(
|
|
21578
|
+
async function fetchProjectInOrg(client, systemId, orgId) {
|
|
21579
|
+
const project = await client.getSystem(systemId);
|
|
21580
21580
|
if (!project) {
|
|
21581
|
-
throw new Error(`Project not found: "${
|
|
21581
|
+
throw new Error(`Project not found: "${systemId}"`);
|
|
21582
21582
|
}
|
|
21583
21583
|
if (project.orgId !== orgId) {
|
|
21584
21584
|
throw new Error(
|
|
21585
|
-
`Project "${
|
|
21585
|
+
`Project "${systemId}" does not belong to the configured organization. Access denied.`
|
|
21586
21586
|
);
|
|
21587
21587
|
}
|
|
21588
21588
|
return project;
|
|
@@ -21974,13 +21974,13 @@ function registerActivityTools(server, ctx, client) {
|
|
|
21974
21974
|
"list_activities",
|
|
21975
21975
|
"List project activity audit trail \u2014 shows beat changes, revision transitions, note operations, merges, splits, and reassignments. Sorted newest-first.",
|
|
21976
21976
|
{
|
|
21977
|
-
|
|
21977
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
21978
21978
|
limit: external_exports.coerce.number().min(1).max(100).optional().describe("Max activities to return (default 30)"),
|
|
21979
21979
|
importance: external_exports.enum(IMPORTANCE_VALUES).optional().describe("Filter by minimum importance level")
|
|
21980
21980
|
},
|
|
21981
|
-
async ({
|
|
21982
|
-
await assertProjectInOrg(client,
|
|
21983
|
-
const { entries, hasMore } = await client.listSystemActivities(
|
|
21981
|
+
async ({ systemId, limit, importance }) => {
|
|
21982
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
21983
|
+
const { entries, hasMore } = await client.listSystemActivities(systemId, {
|
|
21984
21984
|
limit: limit ?? 30,
|
|
21985
21985
|
minImportance: importance
|
|
21986
21986
|
});
|
|
@@ -22005,14 +22005,14 @@ function registerAnalysisTools(server, ctx, client) {
|
|
|
22005
22005
|
"cluster_entities",
|
|
22006
22006
|
"Cluster any embeddable entity type (notes, beats, revisions) by vector similarity using DBSCAN. Returns groups of related entities \u2014 useful for understanding subsystem boundaries, finding duplicates, and planning batched work.",
|
|
22007
22007
|
{
|
|
22008
|
-
|
|
22008
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
22009
22009
|
entityType: ENTITY_TYPE_SCHEMA.describe("Entity type to cluster. Default: revision"),
|
|
22010
22010
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.75).describe("DBSCAN similarity threshold (0-1). Higher = tighter clusters. Default 0.75"),
|
|
22011
22011
|
minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum cluster size. Default 3")
|
|
22012
22012
|
},
|
|
22013
|
-
async ({
|
|
22014
|
-
await assertProjectInOrg(client,
|
|
22015
|
-
const result = await client.clusterEntities(
|
|
22013
|
+
async ({ systemId, entityType, threshold, minClusterSize }) => {
|
|
22014
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
22015
|
+
const result = await client.clusterEntities(systemId, entityType, { threshold, minClusterSize });
|
|
22016
22016
|
if (result.clusters.length === 0) {
|
|
22017
22017
|
return {
|
|
22018
22018
|
content: [{ type: "text", text: `No clusters found (${result.totalAnalyzed} ${entityType}s analyzed, ${result.noise.length} unclustered).` }]
|
|
@@ -22036,15 +22036,15 @@ Analyzed: ${result.totalAnalyzed} | Clusters: ${result.clusters.length} | Noise:
|
|
|
22036
22036
|
"run_clustered_analysis",
|
|
22037
22037
|
"Run batched analysis on clustered project entities. Groups entities by vector similarity, then runs a specified analysis type (docs, completeness, tech_debt, security) per cluster. Returns aggregated results.",
|
|
22038
22038
|
{
|
|
22039
|
-
|
|
22039
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
22040
22040
|
analysisType: ANALYSIS_TYPE_SCHEMA.describe("Type of analysis to run"),
|
|
22041
22041
|
entityType: ENTITY_TYPE_SCHEMA.describe("Entity type to cluster. Default: revision"),
|
|
22042
22042
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("DBSCAN similarity threshold (0-1). Default 0.7"),
|
|
22043
22043
|
minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum cluster size. Default 2")
|
|
22044
22044
|
},
|
|
22045
|
-
async ({
|
|
22046
|
-
await assertProjectInOrg(client,
|
|
22047
|
-
const result = await client.runClusteredAnalysis(
|
|
22045
|
+
async ({ systemId, analysisType, entityType, threshold, minClusterSize }) => {
|
|
22046
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
22047
|
+
const result = await client.runClusteredAnalysis(systemId, analysisType, {
|
|
22048
22048
|
entityType,
|
|
22049
22049
|
threshold,
|
|
22050
22050
|
minClusterSize
|
|
@@ -22073,13 +22073,13 @@ function registerAuditIntegrityTools(server, ctx, client) {
|
|
|
22073
22073
|
"verify_audit_integrity",
|
|
22074
22074
|
"Verify the cryptographic integrity of audit trail records for a project. Checks SHA-256 hashes on Activity and PromptLog records to detect any post-creation tampering.",
|
|
22075
22075
|
{
|
|
22076
|
-
|
|
22076
|
+
systemId: external_exports.string().describe("The project ID to verify"),
|
|
22077
22077
|
limit: external_exports.number().optional().describe("Max records to verify (default 100)")
|
|
22078
22078
|
},
|
|
22079
|
-
async ({
|
|
22079
|
+
async ({ systemId, limit }) => {
|
|
22080
22080
|
try {
|
|
22081
|
-
await assertProjectInOrg(client,
|
|
22082
|
-
const report = await client.verifyAuditIntegrity(
|
|
22081
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
22082
|
+
const report = await client.verifyAuditIntegrity(systemId, { limit });
|
|
22083
22083
|
const lines = [
|
|
22084
22084
|
`## Audit Integrity Report`,
|
|
22085
22085
|
``,
|
|
@@ -22287,16 +22287,16 @@ Rationale: ${rationale}`;
|
|
|
22287
22287
|
"bulk_reassign_revisions",
|
|
22288
22288
|
"Reassign multiple revisions to new home Beats in a single call. Each item names a revision and its target Beat (different items can target different Beats \u2014 e.g., redistributing one Beat's revisions across several after decomposition). Each reassignment archives the original revision and creates a copy on the target Beat, identical to reassign_revision. Returns per-item success/failure so partial failures don't abort the batch. Use when migrating many revisions at once after a Beat split.",
|
|
22289
22289
|
{
|
|
22290
|
-
|
|
22290
|
+
systemId: external_exports.string().describe("The project ID (for access control \u2014 all revisions and target beats must belong to this project)"),
|
|
22291
22291
|
items: external_exports.array(external_exports.object({
|
|
22292
22292
|
revisionId: external_exports.string().describe("The revision ID to reassign"),
|
|
22293
22293
|
toBeatId: external_exports.string().describe("The target beat ID for this specific revision")
|
|
22294
22294
|
})).min(1).max(100).describe("Reassignment items (max 100 per call)"),
|
|
22295
22295
|
reason: external_exports.string().describe("Why these revisions are being reassigned (applied to all items)")
|
|
22296
22296
|
},
|
|
22297
|
-
async ({
|
|
22297
|
+
async ({ systemId, items, reason }) => {
|
|
22298
22298
|
try {
|
|
22299
|
-
await assertProjectInOrg(client,
|
|
22299
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
22300
22300
|
const result = await client.bulkReassignRevisions(items, reason, ctx.user.userId);
|
|
22301
22301
|
const lines = [
|
|
22302
22302
|
`# Bulk reassignment complete: ${result.reassigned.length} succeeded, ${result.failed.length} failed`,
|
|
@@ -22328,14 +22328,14 @@ Rationale: ${rationale}`;
|
|
|
22328
22328
|
"reassign_revision",
|
|
22329
22329
|
"Reassign a misattributed revision from one Beat to another. Creates a copy on the target Beat and archives the original. Logs a revision_reassigned activity for audit trail.",
|
|
22330
22330
|
{
|
|
22331
|
-
|
|
22331
|
+
systemId: external_exports.string().describe("The project ID (for access control)"),
|
|
22332
22332
|
revisionId: external_exports.string().describe("The revision ID to reassign"),
|
|
22333
22333
|
toBeatId: external_exports.string().describe("The target beat ID"),
|
|
22334
22334
|
reason: external_exports.string().describe("Why this revision is being reassigned")
|
|
22335
22335
|
},
|
|
22336
|
-
async ({
|
|
22336
|
+
async ({ systemId, revisionId, toBeatId, reason }) => {
|
|
22337
22337
|
try {
|
|
22338
|
-
await assertProjectInOrg(client,
|
|
22338
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
22339
22339
|
const result = await client.reassignRevision(revisionId, toBeatId, reason, ctx.user.userId);
|
|
22340
22340
|
const text = [
|
|
22341
22341
|
`Revision reassigned successfully.`,
|
|
@@ -22374,7 +22374,7 @@ function registerBeatConvergenceTools(server, ctx, client) {
|
|
|
22374
22374
|
{ beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
|
|
22375
22375
|
async ({ beatId }) => {
|
|
22376
22376
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
22377
|
-
const report = await client.getBeatConvergence(beat.systemId
|
|
22377
|
+
const report = await client.getBeatConvergence(beat.systemId, beatId);
|
|
22378
22378
|
if (!report) {
|
|
22379
22379
|
return { content: [{ type: "text", text: `No convergence data for beat "${beatId}".` }], isError: true };
|
|
22380
22380
|
}
|
|
@@ -22434,7 +22434,7 @@ function registerBeatPlanningTools(server, ctx, client) {
|
|
|
22434
22434
|
"Plan the implementation of a Beat Revision via a conversational turn. Uses the revision description and Beat notes as context. Use this during the planning or building stage to refine scope, explore implementation details, or assess readiness.",
|
|
22435
22435
|
{
|
|
22436
22436
|
revisionId: external_exports.string().describe("The revision ID to plan"),
|
|
22437
|
-
|
|
22437
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
22438
22438
|
message: external_exports.string().describe(
|
|
22439
22439
|
'Your planning message \u2014 e.g. "Review the revision scope", "What should we consider for the auth flow?", or "Does this cover the acceptance criteria?"'
|
|
22440
22440
|
),
|
|
@@ -22443,7 +22443,7 @@ function registerBeatPlanningTools(server, ctx, client) {
|
|
|
22443
22443
|
content: external_exports.string()
|
|
22444
22444
|
})).optional().describe("Previous conversation messages for context continuity")
|
|
22445
22445
|
},
|
|
22446
|
-
async ({ revisionId,
|
|
22446
|
+
async ({ revisionId, systemId, message, conversationHistory }) => {
|
|
22447
22447
|
try {
|
|
22448
22448
|
const revision = await client.getRevision(revisionId);
|
|
22449
22449
|
if (!revision) {
|
|
@@ -22454,7 +22454,7 @@ function registerBeatPlanningTools(server, ctx, client) {
|
|
|
22454
22454
|
...conversationHistory ?? [],
|
|
22455
22455
|
{ role: "user", content: message }
|
|
22456
22456
|
];
|
|
22457
|
-
const result = await client.planRevision(revisionId,
|
|
22457
|
+
const result = await client.planRevision(revisionId, systemId, messages);
|
|
22458
22458
|
return {
|
|
22459
22459
|
content: [{
|
|
22460
22460
|
type: "text",
|
|
@@ -22536,13 +22536,13 @@ ${d.rationale}${d.suggestions?.length ? `
|
|
|
22536
22536
|
function registerBeatQualityTools(server, ctx, client) {
|
|
22537
22537
|
const schema = {
|
|
22538
22538
|
beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)"),
|
|
22539
|
-
|
|
22539
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
22540
22540
|
wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
|
|
22541
22541
|
};
|
|
22542
|
-
const handler = async ({ beatId,
|
|
22542
|
+
const handler = async ({ beatId, systemId, wait }) => {
|
|
22543
22543
|
try {
|
|
22544
22544
|
await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
22545
|
-
const { taskId } = await client.checkBeatQuality(
|
|
22545
|
+
const { taskId } = await client.checkBeatQuality(systemId, beatId);
|
|
22546
22546
|
if (!wait) {
|
|
22547
22547
|
return {
|
|
22548
22548
|
content: [{
|
|
@@ -22585,12 +22585,12 @@ function registerBeatReframeTools(server, ctx, client) {
|
|
|
22585
22585
|
"Reframe a Beat's title and description to meet business-outcome quality rules \u2014 outcome-first, plain English, non-technical audience. Returns a proposed rewrite for review.",
|
|
22586
22586
|
{
|
|
22587
22587
|
beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)"),
|
|
22588
|
-
|
|
22588
|
+
systemId: external_exports.string().describe("The project ID")
|
|
22589
22589
|
},
|
|
22590
|
-
async ({ beatId,
|
|
22590
|
+
async ({ beatId, systemId }) => {
|
|
22591
22591
|
try {
|
|
22592
22592
|
await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
22593
|
-
const result = await client.reframeBeat(
|
|
22593
|
+
const result = await client.reframeBeat(systemId, beatId);
|
|
22594
22594
|
const text = formatReframeResult(beatId, result);
|
|
22595
22595
|
return { content: [{ type: "text", text }] };
|
|
22596
22596
|
} catch (err) {
|
|
@@ -22673,7 +22673,7 @@ function formatProjectSummaryTable(projects) {
|
|
|
22673
22673
|
const header = "| Project ID | Title | Status |\n|------------|-------|--------|";
|
|
22674
22674
|
const rows = projects.map((p) => {
|
|
22675
22675
|
const status = p.status.replace("_", " ");
|
|
22676
|
-
return `| ${p.
|
|
22676
|
+
return `| ${p.systemId} | ${p.title} | ${status} |`;
|
|
22677
22677
|
});
|
|
22678
22678
|
return [header, ...rows].join("\n");
|
|
22679
22679
|
}
|
|
@@ -22702,7 +22702,7 @@ function formatDeliveryPolicy(policy, unresolvedReason) {
|
|
|
22702
22702
|
function formatProjectContext(project, notes, orgCoda) {
|
|
22703
22703
|
const sections = [];
|
|
22704
22704
|
sections.push(`# System: ${project.title}`);
|
|
22705
|
-
sections.push(`**ID:** ${project.
|
|
22705
|
+
sections.push(`**ID:** ${project.systemId}`);
|
|
22706
22706
|
sections.push(`**Status:** ${project.status.replace("_", " ")}`);
|
|
22707
22707
|
if (project.repoOwner && project.repoName) {
|
|
22708
22708
|
const branch = project.repoDefaultBranch ?? "main";
|
|
@@ -22989,7 +22989,7 @@ function formatSessionDetail(session) {
|
|
|
22989
22989
|
"",
|
|
22990
22990
|
`**Type:** ${type}`,
|
|
22991
22991
|
`**Status:** ${status}`,
|
|
22992
|
-
`**Project:** ${session.systemId
|
|
22992
|
+
`**Project:** ${session.systemId}`,
|
|
22993
22993
|
`**Actor:** ${session.actor.agentPersona}${session.actor.agentRole ? ` (${session.actor.agentRole})` : ""}`
|
|
22994
22994
|
];
|
|
22995
22995
|
if (session.actor.initiator) {
|
|
@@ -23097,14 +23097,14 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23097
23097
|
"list_beats",
|
|
23098
23098
|
"List beats in a project. Use offset/limit to paginate large result sets.",
|
|
23099
23099
|
{
|
|
23100
|
-
|
|
23100
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23101
23101
|
offset: external_exports.coerce.number().int().min(0).default(0).describe("Number of beats to skip (for pagination)"),
|
|
23102
23102
|
limit: external_exports.coerce.number().int().min(1).max(500).default(100).describe("Max beats to return (default 100, max 500)"),
|
|
23103
23103
|
includeArchived: external_exports.boolean().optional().default(false).describe("Include archived and deprecated beats (excluded by default)")
|
|
23104
23104
|
},
|
|
23105
|
-
async ({
|
|
23106
|
-
await assertProjectInOrg(client,
|
|
23107
|
-
let beats = await client.listSystemBeats(
|
|
23105
|
+
async ({ systemId, offset = 0, limit = 100, includeArchived }) => {
|
|
23106
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
23107
|
+
let beats = await client.listSystemBeats(systemId);
|
|
23108
23108
|
if (!includeArchived) {
|
|
23109
23109
|
beats = beats.filter((b) => b.beatStatus !== "archived");
|
|
23110
23110
|
}
|
|
@@ -23139,11 +23139,11 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23139
23139
|
"Get the full decision lineage timeline for a beat (revisions, status changes, edits, proposals)",
|
|
23140
23140
|
{
|
|
23141
23141
|
beatId: external_exports.string().describe("The beat ID"),
|
|
23142
|
-
|
|
23142
|
+
systemId: external_exports.string().optional().describe("Project ID (auto-resolved from beat if omitted)")
|
|
23143
23143
|
},
|
|
23144
|
-
async ({ beatId,
|
|
23144
|
+
async ({ beatId, systemId }) => {
|
|
23145
23145
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
23146
|
-
const resolvedProjectId =
|
|
23146
|
+
const resolvedProjectId = systemId ?? beat.systemId;
|
|
23147
23147
|
const timeline = await client.getUnifiedBeatTimeline(resolvedProjectId, beatId);
|
|
23148
23148
|
if (timeline.length === 0) {
|
|
23149
23149
|
return { content: [{ type: "text", text: `No history for beat "${beatId}".` }] };
|
|
@@ -23156,7 +23156,7 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23156
23156
|
"create_beat",
|
|
23157
23157
|
"Create a new beat in a project. Title must be outcome-first (what changes for the user, not what we build). Description is the Beat Coda \u2014 second-person resolved expression of the capability (1-3 sentences). Avoid jargon and user-story format. Read harmonica://guidelines/beat-quality for full rules. Auto-generates beat ID if not provided.",
|
|
23158
23158
|
{
|
|
23159
|
-
|
|
23159
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23160
23160
|
title: external_exports.string().describe('Beat title \u2014 outcome-first, e.g. "At-risk shipments screen used daily by Ops"'),
|
|
23161
23161
|
description: external_exports.string().optional().describe("Beat Coda \u2014 second-person resolved expression of what the capability looks like when complete (1-3 sentences)"),
|
|
23162
23162
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
@@ -23167,13 +23167,13 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23167
23167
|
agentAssignee: agentAssigneeSchema.optional().describe('Agent assignee (e.g. { agentId: "maya", name: "Maya" })'),
|
|
23168
23168
|
branch: external_exports.string().optional().describe('Git branch for code assessment (falls back to Project.repoDefaultBranch \u2192 "main")')
|
|
23169
23169
|
},
|
|
23170
|
-
async ({
|
|
23170
|
+
async ({ systemId, title, description, tags, priority, estimatedEffort, beatId, humanAssignee, agentAssignee, branch }) => {
|
|
23171
23171
|
try {
|
|
23172
|
-
await fetchProjectInOrg(client,
|
|
23173
|
-
const resolvedBeatId = beatId ?? await client.getNextBeatId(
|
|
23172
|
+
await fetchProjectInOrg(client, systemId, ctx.orgId);
|
|
23173
|
+
const resolvedBeatId = beatId ?? await client.getNextBeatId(systemId);
|
|
23174
23174
|
const beat = await client.createBeat({
|
|
23175
23175
|
beatId: resolvedBeatId,
|
|
23176
|
-
|
|
23176
|
+
systemId,
|
|
23177
23177
|
orgId: ctx.orgId,
|
|
23178
23178
|
title,
|
|
23179
23179
|
description,
|
|
@@ -23190,7 +23190,7 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23190
23190
|
});
|
|
23191
23191
|
if (!beat) return { content: [{ type: "text", text: "Failed to create beat: store returned undefined" }], isError: true };
|
|
23192
23192
|
await logBeatActivity(client, {
|
|
23193
|
-
|
|
23193
|
+
systemId,
|
|
23194
23194
|
action: "beat_created",
|
|
23195
23195
|
beatIds: [resolvedBeatId],
|
|
23196
23196
|
beatTitles: [title],
|
|
@@ -23199,7 +23199,7 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23199
23199
|
});
|
|
23200
23200
|
let similarityWarning = "";
|
|
23201
23201
|
try {
|
|
23202
|
-
const similar = await client.findSimilarBeats(beat.beatId,
|
|
23202
|
+
const similar = await client.findSimilarBeats(beat.beatId, systemId, { threshold: 0.7, limit: 5 });
|
|
23203
23203
|
if (similar.length > 0) {
|
|
23204
23204
|
const rows = similar.map((r) => `- **${r.entityId}** (score: ${r.score.toFixed(2)}): ${r.snippet.replace(/[\n\r]/g, " ")}`).join("\n");
|
|
23205
23205
|
similarityWarning = `
|
|
@@ -23257,7 +23257,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
|
|
|
23257
23257
|
return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
|
|
23258
23258
|
}
|
|
23259
23259
|
await logBeatActivity(client, {
|
|
23260
|
-
|
|
23260
|
+
systemId: beat.systemId,
|
|
23261
23261
|
action: "beat_updated",
|
|
23262
23262
|
beatIds: [beatId],
|
|
23263
23263
|
beatTitles: [updated.title],
|
|
@@ -23279,15 +23279,15 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
|
|
|
23279
23279
|
"list_revisions",
|
|
23280
23280
|
"List revisions for a project, optionally filtered by binary status (`active` or `archived`). Archived revisions are excluded by default. Use beatId to scope results to a single beat.",
|
|
23281
23281
|
{
|
|
23282
|
-
|
|
23282
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23283
23283
|
beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project revisions)"),
|
|
23284
23284
|
status: external_exports.enum(["active", "archived"]).optional().describe("Filter by binary Revision status (default: all non-archived)"),
|
|
23285
23285
|
includeArchived: external_exports.boolean().optional().default(false).describe("Include archived revisions (excluded by default)")
|
|
23286
23286
|
},
|
|
23287
|
-
async ({
|
|
23287
|
+
async ({ systemId, beatId, status, includeArchived }) => {
|
|
23288
23288
|
try {
|
|
23289
|
-
await assertProjectInOrg(client,
|
|
23290
|
-
const revisions = beatId ? await client.listBeatRevisions(
|
|
23289
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
23290
|
+
const revisions = beatId ? await client.listBeatRevisions(systemId, beatId, { status, includeArchived }) : await client.listSystemRevisions(systemId, { status, includeArchived });
|
|
23291
23291
|
return { content: [{ type: "text", text: formatRevisionSummaryTable(revisions) }] };
|
|
23292
23292
|
} catch (err) {
|
|
23293
23293
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23327,7 +23327,7 @@ async function logBeatActivity(client, params) {
|
|
|
23327
23327
|
try {
|
|
23328
23328
|
await client.createActivity({
|
|
23329
23329
|
activityId: (0, import_crypto2.randomUUID)(),
|
|
23330
|
-
|
|
23330
|
+
systemId: params.systemId,
|
|
23331
23331
|
action: params.action,
|
|
23332
23332
|
importance: params.importance ?? "minor",
|
|
23333
23333
|
affectedBeatIds: params.beatIds,
|
|
@@ -23399,13 +23399,13 @@ function registerBeatVersionTools(server, ctx, client) {
|
|
|
23399
23399
|
"list_beat_versions",
|
|
23400
23400
|
"List Beat Versions for a Beat or Project. Beat Versions are the shippable planning increments that advance a Beat toward its goal. Use beatId to scope to a specific Beat, or omit for all Beat Versions in the project. Status is binary (active | archived); by default excludes archived.",
|
|
23401
23401
|
{
|
|
23402
|
-
|
|
23402
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23403
23403
|
beatId: external_exports.string().optional().describe("Filter to a specific Beat (omit for all Beat Versions in project)"),
|
|
23404
23404
|
status: external_exports.enum(BEAT_VERSION_STATUSES).optional().describe("Filter to a specific status (active | archived)"),
|
|
23405
23405
|
includeTerminal: external_exports.boolean().optional().describe("Include archived Beat Versions. Default: false.")
|
|
23406
23406
|
},
|
|
23407
|
-
async ({
|
|
23408
|
-
const beatVersions = await client.listBeatVersions(
|
|
23407
|
+
async ({ systemId, beatId, status, includeTerminal }) => {
|
|
23408
|
+
const beatVersions = await client.listBeatVersions(systemId, {
|
|
23409
23409
|
beatId,
|
|
23410
23410
|
status,
|
|
23411
23411
|
includeTerminal
|
|
@@ -23476,7 +23476,7 @@ function registerBeatVersionTools(server, ctx, client) {
|
|
|
23476
23476
|
return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
|
|
23477
23477
|
}
|
|
23478
23478
|
await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
23479
|
-
const beatVersion = await client.createBeatVersion(beat.systemId
|
|
23479
|
+
const beatVersion = await client.createBeatVersion(beat.systemId, beatId, {
|
|
23480
23480
|
title,
|
|
23481
23481
|
description,
|
|
23482
23482
|
scope,
|
|
@@ -23831,13 +23831,13 @@ function registerCheckTools(server, ctx, client) {
|
|
|
23831
23831
|
"list_checks",
|
|
23832
23832
|
"List persisted checks for a project, beat, beat version, or revision. Shows score trends over time. Filter by check type.",
|
|
23833
23833
|
{
|
|
23834
|
-
|
|
23834
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23835
23835
|
beatId: external_exports.string().optional().describe("List checks for this specific beat"),
|
|
23836
23836
|
beatVersionId: external_exports.string().optional().describe("List checks for this specific beat version"),
|
|
23837
23837
|
revisionId: external_exports.string().optional().describe("List checks for this specific revision"),
|
|
23838
23838
|
checkType: external_exports.enum(["beat_coda_quality", "beat_quality", "beat_version_quality", "plan_quality", "build_quality", "pii_scan", "portfolio_coherence"]).optional().describe("Filter by check type. beat_quality is the DEPRECATED alias of beat_coda_quality. plan_quality is the DEPRECATED alias of beat_version_quality. pii_scan is RETIRED from the taxonomy (PII detection moved to the Scan family, N-4E09-6809) \u2014 it is still accepted here so historical records stay readable, but no new pii_scan checks are written.")
|
|
23839
23839
|
},
|
|
23840
|
-
async ({
|
|
23840
|
+
async ({ systemId, beatId, beatVersionId, revisionId, checkType }) => {
|
|
23841
23841
|
try {
|
|
23842
23842
|
const filters = [
|
|
23843
23843
|
revisionId ? "revisionId" : null,
|
|
@@ -23916,18 +23916,18 @@ function registerCheckTools(server, ctx, client) {
|
|
|
23916
23916
|
let checks;
|
|
23917
23917
|
let scope;
|
|
23918
23918
|
if (revisionId) {
|
|
23919
|
-
checks = await client.listRevisionChecks(revisionId,
|
|
23919
|
+
checks = await client.listRevisionChecks(revisionId, systemId, checkType);
|
|
23920
23920
|
scope = `revision ${revisionId}`;
|
|
23921
23921
|
} else if (beatVersionId) {
|
|
23922
|
-
checks = await client.listBeatVersionChecks(beatVersionId,
|
|
23922
|
+
checks = await client.listBeatVersionChecks(beatVersionId, systemId, checkType);
|
|
23923
23923
|
scope = `beat_version ${beatVersionId}`;
|
|
23924
23924
|
} else if (beatId) {
|
|
23925
|
-
checks = await client.listBeatChecks(beatId,
|
|
23925
|
+
checks = await client.listBeatChecks(beatId, systemId, checkType);
|
|
23926
23926
|
scope = `beat ${beatId}`;
|
|
23927
23927
|
} else {
|
|
23928
|
-
const result = await client.listSystemChecks(
|
|
23928
|
+
const result = await client.listSystemChecks(systemId, checkType, { limit: 200 });
|
|
23929
23929
|
checks = result.checks;
|
|
23930
|
-
scope = `project ${
|
|
23930
|
+
scope = `project ${systemId}`;
|
|
23931
23931
|
}
|
|
23932
23932
|
if (checks.length === 0) {
|
|
23933
23933
|
return { content: [{ type: "text", text: `No checks found for ${scope}.` }] };
|
|
@@ -23944,15 +23944,15 @@ function registerCheckTools(server, ctx, client) {
|
|
|
23944
23944
|
"list_latest_checks",
|
|
23945
23945
|
"List the latest check per target (Beat, Beat Version, Revision) across a whole project in one call \u2014 the most recent score of each type for each entity. Use this for an at-a-glance quality snapshot of every capability; use list_checks when you need the full history of one target.",
|
|
23946
23946
|
{
|
|
23947
|
-
|
|
23947
|
+
systemId: external_exports.string().describe("The project ID")
|
|
23948
23948
|
},
|
|
23949
|
-
async ({
|
|
23949
|
+
async ({ systemId }) => {
|
|
23950
23950
|
try {
|
|
23951
|
-
const checks = await client.listLatestSystemChecks(
|
|
23951
|
+
const checks = await client.listLatestSystemChecks(systemId);
|
|
23952
23952
|
if (checks.length === 0) {
|
|
23953
|
-
return { content: [{ type: "text", text: `No checks found for project ${
|
|
23953
|
+
return { content: [{ type: "text", text: `No checks found for project ${systemId}.` }] };
|
|
23954
23954
|
}
|
|
23955
|
-
const text = formatCheckList(checks, `latest per target \xB7 project ${
|
|
23955
|
+
const text = formatCheckList(checks, `latest per target \xB7 project ${systemId}`);
|
|
23956
23956
|
return { content: [{ type: "text", text }] };
|
|
23957
23957
|
} catch (err) {
|
|
23958
23958
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23966,19 +23966,19 @@ function registerCheckTools(server, ctx, client) {
|
|
|
23966
23966
|
{
|
|
23967
23967
|
checkType: external_exports.enum(["beat_coda_quality", "beat_quality", "beat_version_quality", "plan_quality", "build_quality"]).describe("Type of check to run. beat_quality is the deprecated alias of beat_coda_quality. plan_quality is the deprecated alias of beat_version_quality."),
|
|
23968
23968
|
targetId: external_exports.string().describe("The target entity ID \u2014 a beatId for beat_coda_quality, or a Beat Version ID (bv-*) for beat_version_quality and build_quality"),
|
|
23969
|
-
|
|
23969
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
23970
23970
|
branch: external_exports.string().optional().describe("Override the git branch for code checks (default: project default branch)"),
|
|
23971
23971
|
localPath: external_exports.string().optional().describe("Absolute path to an already-cloned local copy of the repo \u2014 skips git clone entirely (useful for large repos or offline use)")
|
|
23972
23972
|
},
|
|
23973
|
-
async ({ checkType, targetId,
|
|
23973
|
+
async ({ checkType, targetId, systemId, branch, localPath }) => {
|
|
23974
23974
|
try {
|
|
23975
|
-
await assertProjectInOrg(client,
|
|
23975
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
23976
23976
|
const targetError = checkType === "build_quality" ? buildQualityTargetError(targetId) : checkType === "beat_version_quality" || checkType === "plan_quality" ? beatVersionQualityTargetError(targetId) : null;
|
|
23977
23977
|
if (targetError) {
|
|
23978
23978
|
return { content: [{ type: "text", text: targetError }], isError: true };
|
|
23979
23979
|
}
|
|
23980
23980
|
const opts = branch || localPath ? { branch, localPath } : void 0;
|
|
23981
|
-
const task = await client.runCheck(
|
|
23981
|
+
const task = await client.runCheck(systemId, checkType, targetId, opts);
|
|
23982
23982
|
const text = [
|
|
23983
23983
|
`Check enqueued.`,
|
|
23984
23984
|
`**Task ID:** ${task.taskId}`,
|
|
@@ -24070,7 +24070,7 @@ function registerClaimTools(server, ctx, client) {
|
|
|
24070
24070
|
return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
|
|
24071
24071
|
}
|
|
24072
24072
|
await assertBeatInOrg(client, revision.beatId, ctx.orgId);
|
|
24073
|
-
const result = await client.claimRevision(revision.
|
|
24073
|
+
const result = await client.claimRevision(revision.systemId, revisionId);
|
|
24074
24074
|
if (!result.success) {
|
|
24075
24075
|
return { content: [{ type: "text", text: `Failed to claim revision (${result.error.code}): ${result.error.message}` }], isError: true };
|
|
24076
24076
|
}
|
|
@@ -24104,7 +24104,7 @@ function registerClaimTools(server, ctx, client) {
|
|
|
24104
24104
|
return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
|
|
24105
24105
|
}
|
|
24106
24106
|
await assertBeatInOrg(client, revision.beatId, ctx.orgId);
|
|
24107
|
-
const result = await client.releaseRevisionClaim(revision.
|
|
24107
|
+
const result = await client.releaseRevisionClaim(revision.systemId, revisionId, reason);
|
|
24108
24108
|
if (!result.success) {
|
|
24109
24109
|
return { content: [{ type: "text", text: `Failed to release claim (${result.error.code}): ${result.error.message}` }], isError: true };
|
|
24110
24110
|
}
|
|
@@ -24204,11 +24204,11 @@ ${g.acceptanceCriteria}` : null,
|
|
|
24204
24204
|
deliverables: external_exports.string().max(2e4).optional().describe("The Track's deliverables as Markdown bullet points (parallel to acceptanceCriteria)"),
|
|
24205
24205
|
acceptanceCriteria: external_exports.string().max(2e4).optional().describe("The Track's acceptance criteria (Markdown), parallel to its deliverables"),
|
|
24206
24206
|
vuRate: external_exports.number().nonnegative().describe("Value-Unit rate (billing rate for this engagement)"),
|
|
24207
|
-
|
|
24207
|
+
systemId: external_exports.string().optional().describe("Optional project to scope this Track to"),
|
|
24208
24208
|
movementId: external_exports.string().optional().describe("Optional Movement (client SOW) to scope this Track to"),
|
|
24209
24209
|
parentGroupId: external_exports.string().optional().describe("Optional parent Track ID for nested grouping (Phase 0 scaffolding)")
|
|
24210
24210
|
};
|
|
24211
|
-
const createTrackHandler = async ({ teamspaceId, title, description, deliverables, acceptanceCriteria, vuRate,
|
|
24211
|
+
const createTrackHandler = async ({ teamspaceId, title, description, deliverables, acceptanceCriteria, vuRate, systemId, movementId, parentGroupId }) => {
|
|
24212
24212
|
await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
|
|
24213
24213
|
if (movementId !== void 0) {
|
|
24214
24214
|
await assertMovementInOrg(client, movementId, ctx.orgId);
|
|
@@ -24224,7 +24224,7 @@ ${g.acceptanceCriteria}` : null,
|
|
|
24224
24224
|
...deliverables !== void 0 && { deliverables },
|
|
24225
24225
|
...acceptanceCriteria !== void 0 && { acceptanceCriteria },
|
|
24226
24226
|
vuRate,
|
|
24227
|
-
...
|
|
24227
|
+
...systemId !== void 0 && { systemId },
|
|
24228
24228
|
...movementId !== void 0 && { movementId },
|
|
24229
24229
|
...parentGroupId !== void 0 && { parentGroupId },
|
|
24230
24230
|
createdBy: ctx.user.userId
|
|
@@ -24447,7 +24447,7 @@ function registerDownbeatTools(server, ctx, client) {
|
|
|
24447
24447
|
{ beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
|
|
24448
24448
|
async ({ beatId }) => {
|
|
24449
24449
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
24450
|
-
const downbeat = await client.setDownbeat(beat.systemId
|
|
24450
|
+
const downbeat = await client.setDownbeat(beat.systemId, beatId);
|
|
24451
24451
|
if (!downbeat) {
|
|
24452
24452
|
return { content: [{ type: "text", text: `Could not set a Downbeat for beat "${beatId}".` }], isError: true };
|
|
24453
24453
|
}
|
|
@@ -24460,7 +24460,7 @@ function registerDownbeatTools(server, ctx, client) {
|
|
|
24460
24460
|
{ beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
|
|
24461
24461
|
async ({ beatId }) => {
|
|
24462
24462
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
24463
|
-
const result = await client.listIncrementsSinceDownbeat(beat.systemId
|
|
24463
|
+
const result = await client.listIncrementsSinceDownbeat(beat.systemId, beatId);
|
|
24464
24464
|
if (!result) {
|
|
24465
24465
|
return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
|
|
24466
24466
|
}
|
|
@@ -24473,7 +24473,7 @@ function registerDownbeatTools(server, ctx, client) {
|
|
|
24473
24473
|
{ beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
|
|
24474
24474
|
async ({ beatId }) => {
|
|
24475
24475
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
24476
|
-
const report = await client.getCodaDrift(beat.systemId
|
|
24476
|
+
const report = await client.getCodaDrift(beat.systemId, beatId);
|
|
24477
24477
|
if (!report) {
|
|
24478
24478
|
return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
|
|
24479
24479
|
}
|
|
@@ -24486,7 +24486,7 @@ function registerDownbeatTools(server, ctx, client) {
|
|
|
24486
24486
|
{ beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
|
|
24487
24487
|
async ({ beatId }) => {
|
|
24488
24488
|
const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
|
|
24489
|
-
const report = await client.getRelationalDrift(beat.systemId
|
|
24489
|
+
const report = await client.getRelationalDrift(beat.systemId, beatId);
|
|
24490
24490
|
if (!report) {
|
|
24491
24491
|
return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
|
|
24492
24492
|
}
|
|
@@ -24555,17 +24555,17 @@ function registerDropQualityTools(server, ctx, client) {
|
|
|
24555
24555
|
"Run a five-dimension coherence check on a Drop (Substantial, Harmonious, Clear, Focused, Strategically Aligned), graded LLM-only on the Drop's Beat Versions and their parent Beats/Projects/Codas. By default returns a taskId immediately \u2014 set wait=true to block and receive the full scorecard inline.",
|
|
24556
24556
|
{
|
|
24557
24557
|
dropId: external_exports.string().min(1).describe("The Drop ID (e.g. drop-abc123) to grade"),
|
|
24558
|
-
|
|
24558
|
+
systemId: external_exports.string().min(1).describe("A project within this Drop \u2014 the check record is attributed to it (Drops span projects, so the result is recorded under one member project)"),
|
|
24559
24559
|
wait: external_exports.boolean().optional().describe("Block until the check completes and return the scorecard inline. Default: false.")
|
|
24560
24560
|
},
|
|
24561
|
-
async ({ dropId,
|
|
24561
|
+
async ({ dropId, systemId, wait }) => {
|
|
24562
24562
|
try {
|
|
24563
|
-
await assertProjectInOrg(client,
|
|
24563
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
24564
24564
|
const drop = await client.getDrop(dropId);
|
|
24565
24565
|
if (!drop) {
|
|
24566
24566
|
return { content: [{ type: "text", text: `Drop "${dropId}" not found.` }], isError: true };
|
|
24567
24567
|
}
|
|
24568
|
-
const { taskId } = await client.runCheck(
|
|
24568
|
+
const { taskId } = await client.runCheck(systemId, "drop_quality", dropId);
|
|
24569
24569
|
if (!wait) {
|
|
24570
24570
|
return {
|
|
24571
24571
|
content: [{
|
|
@@ -24966,9 +24966,9 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
24966
24966
|
);
|
|
24967
24967
|
server.tool(
|
|
24968
24968
|
"search",
|
|
24969
|
-
"Free-text semantic search across project, teamspace, or org scope. Provide exactly one of
|
|
24969
|
+
"Free-text semantic search across project, teamspace, or org scope. Provide exactly one of systemId / teamspaceId / orgLevel. Teamspace and org searches fan out across child projects and merge by score. Cross-project results carry the source project name.",
|
|
24970
24970
|
{
|
|
24971
|
-
|
|
24971
|
+
systemId: external_exports.string().optional().describe("Search within a single project"),
|
|
24972
24972
|
teamspaceId: external_exports.string().optional().describe("Search across all projects in a teamspace"),
|
|
24973
24973
|
orgLevel: external_exports.boolean().optional().describe("Search across every project in the configured org"),
|
|
24974
24974
|
query: external_exports.string().describe("Natural language search query"),
|
|
@@ -24979,15 +24979,15 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
24979
24979
|
noteStatus: external_exports.enum(["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"]).optional().describe("Restrict note results to a specific status (e.g. unvalidated). DynamoDB post-filter \u2014 works without re-embedding."),
|
|
24980
24980
|
beatId: external_exports.string().optional().describe("Restrict note results to those belonging to a specific beat (DynamoDB post-filter, no re-embedding needed)")
|
|
24981
24981
|
},
|
|
24982
|
-
async ({
|
|
24983
|
-
const provided = [
|
|
24982
|
+
async ({ systemId, teamspaceId, orgLevel, query, limit, includeArchived, entityType, noteType, noteStatus, beatId }) => {
|
|
24983
|
+
const provided = [systemId, teamspaceId, orgLevel].filter(Boolean).length;
|
|
24984
24984
|
if (provided !== 1) {
|
|
24985
|
-
throw new Error("Specify exactly one of
|
|
24985
|
+
throw new Error("Specify exactly one of systemId, teamspaceId, or orgLevel.");
|
|
24986
24986
|
}
|
|
24987
24987
|
let scope;
|
|
24988
|
-
if (
|
|
24989
|
-
await assertProjectInOrg(client,
|
|
24990
|
-
scope = {
|
|
24988
|
+
if (systemId) {
|
|
24989
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
24990
|
+
scope = { systemId };
|
|
24991
24991
|
} else if (teamspaceId) {
|
|
24992
24992
|
await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
|
|
24993
24993
|
scope = { teamspaceId };
|
|
@@ -25004,22 +25004,22 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
25004
25004
|
"Find Notes semantically similar to one or more Notes. Accepts a single Note ID or an array of Note IDs for batch duplicate detection. Results are grouped by source Note.",
|
|
25005
25005
|
{
|
|
25006
25006
|
noteId: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).describe("Note ID or array of Note IDs to find similar notes for"),
|
|
25007
|
-
|
|
25007
|
+
systemId: external_exports.string().describe("The project ID the notes belong to"),
|
|
25008
25008
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
|
|
25009
25009
|
limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results per source note. Default 10"),
|
|
25010
25010
|
includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated notes in results (excluded by default)"),
|
|
25011
25011
|
noteStatus: external_exports.enum(["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"]).optional().describe("Restrict results to notes with a specific status (e.g. unvalidated). DynamoDB post-filter."),
|
|
25012
25012
|
beatId: external_exports.string().optional().describe("Restrict results to notes belonging to a specific beat (DynamoDB post-filter)")
|
|
25013
25013
|
},
|
|
25014
|
-
async ({ noteId,
|
|
25015
|
-
await assertProjectInOrg(client,
|
|
25014
|
+
async ({ noteId, systemId, threshold, limit, includeArchived = false, noteStatus, beatId }) => {
|
|
25015
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25016
25016
|
const ids = Array.isArray(noteId) ? noteId : [noteId];
|
|
25017
25017
|
if (ids.length === 1) {
|
|
25018
|
-
const results = await client.findSimilarNotes(ids[0],
|
|
25018
|
+
const results = await client.findSimilarNotes(ids[0], systemId, { threshold, limit, includeArchived, noteStatus, beatId });
|
|
25019
25019
|
const text = formatSimilarityResults(results, ids[0], "Similar Notes");
|
|
25020
25020
|
return { content: [{ type: "text", text }] };
|
|
25021
25021
|
}
|
|
25022
|
-
const batchResults = await client.findSimilarNotesMulti(ids,
|
|
25022
|
+
const batchResults = await client.findSimilarNotesMulti(ids, systemId, { threshold, limit, includeArchived, noteStatus, beatId });
|
|
25023
25023
|
const sections = batchResults.map(
|
|
25024
25024
|
(br) => formatSimilarityResults(br.results, br.sourceNoteId, "Similar Notes")
|
|
25025
25025
|
);
|
|
@@ -25031,14 +25031,14 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
25031
25031
|
"Find Beats semantically similar to a given Beat. Useful for detecting duplicates, dependencies, or related capabilities.",
|
|
25032
25032
|
{
|
|
25033
25033
|
beatId: external_exports.string().describe("The beat ID to find similar beats for"),
|
|
25034
|
-
|
|
25034
|
+
systemId: external_exports.string().describe("The project ID the beat belongs to"),
|
|
25035
25035
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
|
|
25036
25036
|
limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10"),
|
|
25037
25037
|
includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated beats in results (excluded by default)")
|
|
25038
25038
|
},
|
|
25039
|
-
async ({ beatId,
|
|
25039
|
+
async ({ beatId, systemId, threshold, limit, includeArchived = false }) => {
|
|
25040
25040
|
await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
25041
|
-
const results = await client.findSimilarBeats(beatId,
|
|
25041
|
+
const results = await client.findSimilarBeats(beatId, systemId, { threshold, limit, includeArchived });
|
|
25042
25042
|
const text = formatSimilarityResults(results, beatId, "Similar Beats");
|
|
25043
25043
|
return { content: [{ type: "text", text }] };
|
|
25044
25044
|
}
|
|
@@ -25048,13 +25048,13 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
25048
25048
|
"Find Beats semantically related to a given Note. Surfaces missing affectsBeats links and helps classify unassigned Notes.",
|
|
25049
25049
|
{
|
|
25050
25050
|
noteId: external_exports.string().describe("The note ID to find related beats for"),
|
|
25051
|
-
|
|
25051
|
+
systemId: external_exports.string().describe("The project ID the note belongs to"),
|
|
25052
25052
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
|
|
25053
25053
|
limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10")
|
|
25054
25054
|
},
|
|
25055
|
-
async ({ noteId,
|
|
25056
|
-
await assertProjectInOrg(client,
|
|
25057
|
-
const results = await client.findRelatedBeats(noteId,
|
|
25055
|
+
async ({ noteId, systemId, threshold, limit }) => {
|
|
25056
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25057
|
+
const results = await client.findRelatedBeats(noteId, systemId, { threshold, limit });
|
|
25058
25058
|
const text = formatSimilarityResults(results, noteId, "Related Beats");
|
|
25059
25059
|
return { content: [{ type: "text", text }] };
|
|
25060
25060
|
}
|
|
@@ -25063,14 +25063,14 @@ function registerEmbeddingTools(server, ctx, client) {
|
|
|
25063
25063
|
"cluster_notes",
|
|
25064
25064
|
"Cluster semantically similar Notes using DBSCAN on vector embeddings. Returns clusters for review with member previews. Use this to discover natural groupings among notes and consolidate them under parent summary notes.",
|
|
25065
25065
|
{
|
|
25066
|
-
|
|
25066
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
25067
25067
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.75).describe("Cosine similarity threshold for clustering (0-1). Higher = tighter clusters. Default 0.75"),
|
|
25068
25068
|
minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum notes to form a cluster. Default 3"),
|
|
25069
25069
|
includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false (excludes them). Set true for thematic grouping pass.")
|
|
25070
25070
|
},
|
|
25071
|
-
async ({
|
|
25072
|
-
await assertProjectInOrg(client,
|
|
25073
|
-
const result = await client.clusterNotes(
|
|
25071
|
+
async ({ systemId, threshold, minClusterSize, includeLinked }) => {
|
|
25072
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25073
|
+
const result = await client.clusterNotes(systemId, { threshold, minClusterSize, excludeAlreadyLinked: !includeLinked });
|
|
25074
25074
|
if (result.clusters.length === 0) {
|
|
25075
25075
|
return { content: [{ type: "text", text: `No clusters found among ${result.totalAnalyzed} analyzed notes. Try lowering the threshold.` }] };
|
|
25076
25076
|
}
|
|
@@ -25094,14 +25094,14 @@ ${members}`;
|
|
|
25094
25094
|
"consolidate_notes",
|
|
25095
25095
|
"Two-pass note consolidation: clusters notes via DBSCAN, then sends each cluster to an LLM for contextual analysis. Returns consolidation proposals (should these be grouped under a parent summary?) without modifying data. Use cluster_notes for a quick vector-only view; use this when ready to act on consolidation.",
|
|
25096
25096
|
{
|
|
25097
|
-
|
|
25097
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
25098
25098
|
threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.65).describe("Cosine similarity threshold for clustering (0-1). Default 0.65"),
|
|
25099
25099
|
minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum notes to form a cluster. Default 2"),
|
|
25100
25100
|
includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false.")
|
|
25101
25101
|
},
|
|
25102
|
-
async ({
|
|
25103
|
-
await assertProjectInOrg(client,
|
|
25104
|
-
const result = await client.consolidateNotes(
|
|
25102
|
+
async ({ systemId, threshold, minClusterSize, includeLinked }) => {
|
|
25103
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25104
|
+
const result = await client.consolidateNotes(systemId, { threshold, minClusterSize, includeLinked });
|
|
25105
25105
|
if (result.proposals.length === 0) {
|
|
25106
25106
|
return { content: [{ type: "text", text: `No consolidation candidates found among ${result.totalAnalyzed} analyzed notes.` }] };
|
|
25107
25107
|
}
|
|
@@ -25337,18 +25337,18 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25337
25337
|
"list_system_layers",
|
|
25338
25338
|
"List the architectural Layers declared on a System (e.g. frontend, backend, data). Layers are an evaluation lens for plan/build quality checks, not a work container.",
|
|
25339
25339
|
{
|
|
25340
|
-
|
|
25340
|
+
systemId: external_exports.string().describe("The System ID")
|
|
25341
25341
|
},
|
|
25342
|
-
async ({
|
|
25342
|
+
async ({ systemId }) => {
|
|
25343
25343
|
try {
|
|
25344
|
-
await assertProjectInOrg(client,
|
|
25344
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25345
25345
|
} catch (err) {
|
|
25346
25346
|
const message = err instanceof Error ? err.message : String(err);
|
|
25347
25347
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
25348
25348
|
}
|
|
25349
25349
|
let layers;
|
|
25350
25350
|
try {
|
|
25351
|
-
layers = await client.listSystemLayers(
|
|
25351
|
+
layers = await client.listSystemLayers(systemId);
|
|
25352
25352
|
} catch (err) {
|
|
25353
25353
|
const message = err instanceof Error ? err.message : String(err);
|
|
25354
25354
|
return { content: [{ type: "text", text: `Failed to list Layers: ${message}` }], isError: true };
|
|
@@ -25361,12 +25361,12 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25361
25361
|
"get_layer",
|
|
25362
25362
|
"Get full details of a single Layer by ID, including its repo mapping and parent Layer (if it was split from one).",
|
|
25363
25363
|
{
|
|
25364
|
-
|
|
25364
|
+
systemId: external_exports.string().describe("The System ID (for access control)"),
|
|
25365
25365
|
layerId: external_exports.string().describe("The Layer ID")
|
|
25366
25366
|
},
|
|
25367
|
-
async ({
|
|
25367
|
+
async ({ systemId, layerId }) => {
|
|
25368
25368
|
try {
|
|
25369
|
-
await assertProjectInOrg(client,
|
|
25369
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25370
25370
|
} catch (err) {
|
|
25371
25371
|
const message = err instanceof Error ? err.message : String(err);
|
|
25372
25372
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -25381,8 +25381,8 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25381
25381
|
if (!layer) {
|
|
25382
25382
|
return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
|
|
25383
25383
|
}
|
|
25384
|
-
if (layer.
|
|
25385
|
-
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${
|
|
25384
|
+
if (layer.systemId !== systemId) {
|
|
25385
|
+
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
|
|
25386
25386
|
}
|
|
25387
25387
|
const text = formatLayerDetail(layer);
|
|
25388
25388
|
return { content: [{ type: "text", text }] };
|
|
@@ -25392,16 +25392,16 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25392
25392
|
"create_layer",
|
|
25393
25393
|
"Declare a new architectural Layer on a System (e.g. frontend, backend, data). Layers are an evaluation lens for plan/build quality checks. layerKey must be a lowercase slug and unique per System.",
|
|
25394
25394
|
{
|
|
25395
|
-
|
|
25395
|
+
systemId: external_exports.string().describe("The System ID"),
|
|
25396
25396
|
layerKey: external_exports.string().regex(/^[a-z]([a-z0-9-]*[a-z0-9])?$/, "layerKey must be a lowercase slug (letters, digits, hyphens; start with a letter, no trailing hyphen)").describe('Stable lowercase slug for the Layer role, e.g. "frontend" or "agent-workers"'),
|
|
25397
25397
|
name: external_exports.string().describe('Display name, e.g. "Frontend"'),
|
|
25398
25398
|
description: external_exports.string().optional().describe("Optional description of the Layer"),
|
|
25399
25399
|
parentLayerId: external_exports.string().optional().describe("Optional parent Layer to nest under (must be a Layer on the same System)"),
|
|
25400
25400
|
repoPatterns: external_exports.array(external_exports.string()).optional().describe("Optional repo path patterns owned by this Layer")
|
|
25401
25401
|
},
|
|
25402
|
-
async ({
|
|
25402
|
+
async ({ systemId, layerKey, name, description, parentLayerId, repoPatterns }) => {
|
|
25403
25403
|
try {
|
|
25404
|
-
await assertProjectInOrg(client,
|
|
25404
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25405
25405
|
} catch (err) {
|
|
25406
25406
|
const message = err instanceof Error ? err.message : String(err);
|
|
25407
25407
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -25409,7 +25409,7 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25409
25409
|
let layer;
|
|
25410
25410
|
try {
|
|
25411
25411
|
layer = await client.createSystemLayer({
|
|
25412
|
-
|
|
25412
|
+
systemId,
|
|
25413
25413
|
layerKey,
|
|
25414
25414
|
name,
|
|
25415
25415
|
...description !== void 0 && { description },
|
|
@@ -25421,7 +25421,7 @@ function registerLayerTools(server, ctx, client) {
|
|
|
25421
25421
|
return { content: [{ type: "text", text: `Failed to create Layer: ${message}` }], isError: true };
|
|
25422
25422
|
}
|
|
25423
25423
|
if (!layer) {
|
|
25424
|
-
return { content: [{ type: "text", text: `System "${
|
|
25424
|
+
return { content: [{ type: "text", text: `System "${systemId}" not found or not writable.` }], isError: true };
|
|
25425
25425
|
}
|
|
25426
25426
|
return { content: [{ type: "text", text: `Created Layer.
|
|
25427
25427
|
|
|
@@ -25432,15 +25432,15 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
25432
25432
|
"update_layer",
|
|
25433
25433
|
"Update a Layer's editable fields (name, description, repo mapping). Omitted fields are left unchanged. Archiving/reviving a Layer goes through transition_layer, not this tool. Requires write access to the System.",
|
|
25434
25434
|
{
|
|
25435
|
-
|
|
25435
|
+
systemId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
|
|
25436
25436
|
layerId: external_exports.string().describe("The Layer ID to update"),
|
|
25437
25437
|
name: external_exports.string().optional().describe("New display name"),
|
|
25438
25438
|
description: external_exports.string().optional().describe("New description"),
|
|
25439
25439
|
repoPatterns: external_exports.array(external_exports.string()).optional().describe("Replacement repo mapping (replaces the existing list)")
|
|
25440
25440
|
},
|
|
25441
|
-
async ({
|
|
25441
|
+
async ({ systemId, layerId, name, description, repoPatterns }) => {
|
|
25442
25442
|
try {
|
|
25443
|
-
await assertProjectInOrg(client,
|
|
25443
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25444
25444
|
} catch (err) {
|
|
25445
25445
|
const message = err instanceof Error ? err.message : String(err);
|
|
25446
25446
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -25453,8 +25453,8 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
25453
25453
|
return { content: [{ type: "text", text: `Failed to load Layer: ${message}` }], isError: true };
|
|
25454
25454
|
}
|
|
25455
25455
|
if (!existing) return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
|
|
25456
|
-
if (existing.
|
|
25457
|
-
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${
|
|
25456
|
+
if (existing.systemId !== systemId) {
|
|
25457
|
+
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
|
|
25458
25458
|
}
|
|
25459
25459
|
let layer;
|
|
25460
25460
|
try {
|
|
@@ -25477,14 +25477,14 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
25477
25477
|
"transition_layer",
|
|
25478
25478
|
"Archive or revive a Layer. Archiving requires a reason (audit trail); reviving sets it back to active. Requires write access to the System.",
|
|
25479
25479
|
{
|
|
25480
|
-
|
|
25480
|
+
systemId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
|
|
25481
25481
|
layerId: external_exports.string().describe("The Layer ID to transition"),
|
|
25482
25482
|
status: external_exports.enum(["active", "archived"]).describe('Target status: "archived" to retire, "active" to revive'),
|
|
25483
25483
|
reason: external_exports.string().optional().describe('Why the Layer is being archived (required when status is "archived")')
|
|
25484
25484
|
},
|
|
25485
|
-
async ({
|
|
25485
|
+
async ({ systemId, layerId, status, reason }) => {
|
|
25486
25486
|
try {
|
|
25487
|
-
await assertProjectInOrg(client,
|
|
25487
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25488
25488
|
} catch (err) {
|
|
25489
25489
|
const message = err instanceof Error ? err.message : String(err);
|
|
25490
25490
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -25497,8 +25497,8 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
25497
25497
|
return { content: [{ type: "text", text: `Failed to load Layer: ${message}` }], isError: true };
|
|
25498
25498
|
}
|
|
25499
25499
|
if (!existing) return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
|
|
25500
|
-
if (existing.
|
|
25501
|
-
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${
|
|
25500
|
+
if (existing.systemId !== systemId) {
|
|
25501
|
+
return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
|
|
25502
25502
|
}
|
|
25503
25503
|
let layer;
|
|
25504
25504
|
try {
|
|
@@ -25823,13 +25823,13 @@ function registerNextActionsTools(server, ctx, client) {
|
|
|
25823
25823
|
"get_next_actions",
|
|
25824
25824
|
"Get prioritized list of recommended next actions for a project. Analyzes Beats, Revisions, and Notes to surface the highest-priority work: P0 (urgent bugs) \u2192 P1 (deploy) \u2192 P2 (build) \u2192 P3 (plan) \u2192 P4 (compose) \u2192 P5 (maintenance).",
|
|
25825
25825
|
{
|
|
25826
|
-
|
|
25826
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
25827
25827
|
limit: external_exports.coerce.number().optional().default(10).describe("Max actions to return")
|
|
25828
25828
|
},
|
|
25829
|
-
async ({
|
|
25829
|
+
async ({ systemId, limit }) => {
|
|
25830
25830
|
try {
|
|
25831
|
-
await assertProjectInOrg(client,
|
|
25832
|
-
const actions = await client.getNextActions(
|
|
25831
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25832
|
+
const actions = await client.getNextActions(systemId, { limit });
|
|
25833
25833
|
return { content: [{ type: "text", text: formatNextActions(actions) }] };
|
|
25834
25834
|
} catch (err) {
|
|
25835
25835
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -25841,12 +25841,12 @@ function registerNextActionsTools(server, ctx, client) {
|
|
|
25841
25841
|
"list_triage_actions",
|
|
25842
25842
|
"List active triage actions from the ledger (open + acted). This is the stateful view of what needs attention \u2014 includes status tracking, task progress, and escalation state. Use this instead of get_next_actions when triage has been run.",
|
|
25843
25843
|
{
|
|
25844
|
-
|
|
25844
|
+
systemId: external_exports.string().describe("The project ID")
|
|
25845
25845
|
},
|
|
25846
|
-
async ({
|
|
25846
|
+
async ({ systemId }) => {
|
|
25847
25847
|
try {
|
|
25848
|
-
await assertProjectInOrg(client,
|
|
25849
|
-
const actions = await client.listTriageActions(
|
|
25848
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25849
|
+
const actions = await client.listTriageActions(systemId);
|
|
25850
25850
|
return { content: [{ type: "text", text: formatTriageActions(actions) }] };
|
|
25851
25851
|
} catch (err) {
|
|
25852
25852
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -25858,13 +25858,13 @@ function registerNextActionsTools(server, ctx, client) {
|
|
|
25858
25858
|
"dismiss_triage_action",
|
|
25859
25859
|
'Dismiss a triage action (human decides "not now"). The action will not be re-escalated until the underlying state changes.',
|
|
25860
25860
|
{
|
|
25861
|
-
|
|
25861
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
25862
25862
|
actionKey: external_exports.string().describe("The action key to dismiss (from list_triage_actions)")
|
|
25863
25863
|
},
|
|
25864
|
-
async ({
|
|
25864
|
+
async ({ systemId, actionKey }) => {
|
|
25865
25865
|
try {
|
|
25866
|
-
await assertProjectInOrg(client,
|
|
25867
|
-
await client.dismissTriageAction(
|
|
25866
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
25867
|
+
await client.dismissTriageAction(systemId, actionKey, ctx.user?.userId ?? "mcp");
|
|
25868
25868
|
return { content: [{ type: "text", text: `Action ${actionKey} dismissed.` }] };
|
|
25869
25869
|
} catch (err) {
|
|
25870
25870
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -26216,25 +26216,35 @@ var NoteStatusSchema = external_exports.enum([
|
|
|
26216
26216
|
]);
|
|
26217
26217
|
var SystemStatusSchema = external_exports.enum(["active", "archived", "draft"]);
|
|
26218
26218
|
var ChangeImportanceSchema = external_exports.enum(["minor", "moderate", "major"]);
|
|
26219
|
-
var BeatApiSchema = external_exports.
|
|
26220
|
-
|
|
26221
|
-
|
|
26222
|
-
|
|
26223
|
-
|
|
26224
|
-
|
|
26225
|
-
|
|
26226
|
-
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26231
|
-
|
|
26232
|
-
|
|
26233
|
-
|
|
26234
|
-
|
|
26235
|
-
|
|
26236
|
-
|
|
26237
|
-
|
|
26219
|
+
var BeatApiSchema = external_exports.preprocess(
|
|
26220
|
+
(raw) => {
|
|
26221
|
+
if (typeof raw !== "object" || raw === null) return raw;
|
|
26222
|
+
const b = raw;
|
|
26223
|
+
const id = b["systemId"] ?? b["projectId"];
|
|
26224
|
+
if (!id) return raw;
|
|
26225
|
+
return { ...b, systemId: id, projectId: id };
|
|
26226
|
+
},
|
|
26227
|
+
external_exports.object({
|
|
26228
|
+
beatId: external_exports.string(),
|
|
26229
|
+
systemId: external_exports.string(),
|
|
26230
|
+
projectId: external_exports.string(),
|
|
26231
|
+
/** Source project's title; attached by the flat /api/beats endpoint for
|
|
26232
|
+
* multi-project (teamspace/org) responses. */
|
|
26233
|
+
projectName: external_exports.string().nullish(),
|
|
26234
|
+
title: external_exports.string(),
|
|
26235
|
+
version: external_exports.number().nullish(),
|
|
26236
|
+
description: external_exports.string().nullish(),
|
|
26237
|
+
beatStatus: BeatStatusSchema.nullish(),
|
|
26238
|
+
tags: external_exports.array(external_exports.string()).nullish(),
|
|
26239
|
+
priority: external_exports.number().nullish(),
|
|
26240
|
+
estimatedEffort: external_exports.string().nullish(),
|
|
26241
|
+
humanAssignee: HumanAssigneeSchema.nullish(),
|
|
26242
|
+
agentAssignee: AgentAssigneeSchema.nullish(),
|
|
26243
|
+
revisionCount: external_exports.number().nullish(),
|
|
26244
|
+
createdAt: external_exports.string(),
|
|
26245
|
+
updatedAt: external_exports.string()
|
|
26246
|
+
}).passthrough()
|
|
26247
|
+
);
|
|
26238
26248
|
var RevisionScmSchema = external_exports.object({
|
|
26239
26249
|
provider: external_exports.literal("github"),
|
|
26240
26250
|
number: external_exports.string(),
|
|
@@ -26865,21 +26875,31 @@ var AccountSystemApiSchema = external_exports.object({
|
|
|
26865
26875
|
var AccountSystemsResponseSchema = external_exports.object({ systems: external_exports.array(AccountSystemApiSchema) }).passthrough();
|
|
26866
26876
|
var LAYER_STATUSES = ["active", "archived"];
|
|
26867
26877
|
var LayerStatusApiSchema = external_exports.enum(LAYER_STATUSES);
|
|
26868
|
-
var LayerApiSchema = external_exports.
|
|
26869
|
-
|
|
26870
|
-
|
|
26871
|
-
|
|
26872
|
-
|
|
26873
|
-
|
|
26874
|
-
|
|
26875
|
-
|
|
26876
|
-
|
|
26877
|
-
|
|
26878
|
-
|
|
26879
|
-
|
|
26880
|
-
|
|
26881
|
-
|
|
26882
|
-
|
|
26878
|
+
var LayerApiSchema = external_exports.preprocess(
|
|
26879
|
+
(raw) => {
|
|
26880
|
+
if (typeof raw !== "object" || raw === null) return raw;
|
|
26881
|
+
const l = raw;
|
|
26882
|
+
const id = l["systemId"] ?? l["projectId"];
|
|
26883
|
+
if (!id) return raw;
|
|
26884
|
+
return { ...l, systemId: id, projectId: id };
|
|
26885
|
+
},
|
|
26886
|
+
external_exports.object({
|
|
26887
|
+
layerId: external_exports.string(),
|
|
26888
|
+
systemId: external_exports.string(),
|
|
26889
|
+
projectId: external_exports.string(),
|
|
26890
|
+
layerKey: external_exports.string(),
|
|
26891
|
+
name: external_exports.string(),
|
|
26892
|
+
description: external_exports.string().nullish(),
|
|
26893
|
+
parentLayerId: external_exports.string().nullish(),
|
|
26894
|
+
repoPatterns: external_exports.array(external_exports.string()).nullish(),
|
|
26895
|
+
status: LayerStatusApiSchema,
|
|
26896
|
+
archivedAt: external_exports.string().nullish(),
|
|
26897
|
+
createdBy: external_exports.string().nullish(),
|
|
26898
|
+
version: external_exports.number().nullish(),
|
|
26899
|
+
createdAt: external_exports.string(),
|
|
26900
|
+
updatedAt: external_exports.string()
|
|
26901
|
+
}).passthrough()
|
|
26902
|
+
);
|
|
26883
26903
|
var LayersResponseSchema = external_exports.object({ layers: external_exports.array(LayerApiSchema) }).passthrough();
|
|
26884
26904
|
var LAYER_RUBRIC_SCOPES_LIST = ["account", "teamspace", "movement"];
|
|
26885
26905
|
var LayerRubricScopeApiSchema = external_exports.enum(LAYER_RUBRIC_SCOPES_LIST);
|
|
@@ -27255,7 +27275,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27255
27275
|
"list_notes",
|
|
27256
27276
|
"List Notes for a project. Without beatId: returns all notes (project-level + beat-level). With beatId: returns only notes scoped to that beat. Filterable by type, status, revisionId, beatVersionId, and significance. Use offset/limit to paginate beat-scoped results; use cursor for project-wide results.",
|
|
27257
27277
|
{
|
|
27258
|
-
|
|
27278
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
27259
27279
|
beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project notes)"),
|
|
27260
27280
|
noteType: external_exports.union([external_exports.enum(NOTE_TYPE_VALUES), external_exports.array(external_exports.enum(NOTE_TYPE_VALUES)).min(1)]).optional().describe('Filter by note type \u2014 single value or array for multi-select (e.g. ["constraint", "assumption"])'),
|
|
27261
27281
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
|
|
@@ -27268,7 +27288,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27268
27288
|
cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response \u2014 use for project-wide note pagination (no beatId)"),
|
|
27269
27289
|
orderBy: external_exports.enum(["newest", "oldest"]).optional().describe("Sort order \u2014 newest or oldest first. Applies to beat-scoped queries (beatId provided); omit to use DynamoDB scan order.")
|
|
27270
27290
|
},
|
|
27271
|
-
async ({
|
|
27291
|
+
async ({ systemId, beatId, noteType, status, revisionId, beatVersionId, sourceDocumentNoteId, significance, offset = 0, limit = 100, cursor, orderBy }) => {
|
|
27272
27292
|
if (Array.isArray(noteType) && noteType.length === 0) {
|
|
27273
27293
|
return { content: [{ type: "text", text: "noteType must not be an empty array \u2014 omit it to return all types." }], isError: true };
|
|
27274
27294
|
}
|
|
@@ -27280,7 +27300,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27280
27300
|
return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
|
|
27281
27301
|
}
|
|
27282
27302
|
try {
|
|
27283
|
-
await assertProjectInOrg(client,
|
|
27303
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27284
27304
|
} catch (err) {
|
|
27285
27305
|
const message = err instanceof Error ? err.message : String(err);
|
|
27286
27306
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -27320,7 +27340,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27320
27340
|
const SOURCE_DOC_FETCH_LIMIT = 5e3;
|
|
27321
27341
|
let allNotesResult;
|
|
27322
27342
|
try {
|
|
27323
|
-
allNotesResult = await client.listAllSystemNotes(
|
|
27343
|
+
allNotesResult = await client.listAllSystemNotes(systemId, filters, SOURCE_DOC_FETCH_LIMIT);
|
|
27324
27344
|
} catch (err) {
|
|
27325
27345
|
const message = err instanceof Error ? err.message : String(err);
|
|
27326
27346
|
return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
|
|
@@ -27341,7 +27361,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27341
27361
|
} else {
|
|
27342
27362
|
let projectNotesResult;
|
|
27343
27363
|
try {
|
|
27344
|
-
projectNotesResult = await client.listAllSystemNotes(
|
|
27364
|
+
projectNotesResult = await client.listAllSystemNotes(systemId, filters, limit, cursor);
|
|
27345
27365
|
} catch (err) {
|
|
27346
27366
|
const message = err instanceof Error ? err.message : String(err);
|
|
27347
27367
|
return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
|
|
@@ -27423,12 +27443,12 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27423
27443
|
"list_documents",
|
|
27424
27444
|
"List uploaded documents for a project with processing status, filename, child note count, and AI summary. Use get_note with a document note ID for full details.",
|
|
27425
27445
|
{
|
|
27426
|
-
|
|
27446
|
+
systemId: external_exports.string().describe("The project ID")
|
|
27427
27447
|
},
|
|
27428
|
-
async ({
|
|
27429
|
-
await assertProjectInOrg(client,
|
|
27448
|
+
async ({ systemId }) => {
|
|
27449
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27430
27450
|
const DOCUMENT_FETCH_LIMIT = 5e3;
|
|
27431
|
-
const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(
|
|
27451
|
+
const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
|
|
27432
27452
|
const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
|
|
27433
27453
|
const truncationNotice = truncated && visibleDocs.length > 0 ? "\n\n> \u26A0\uFE0F This project has more than 5000 documents. Some documents may not be shown.\n" : "";
|
|
27434
27454
|
const text = formatDocumentList(visibleDocs) + truncationNotice;
|
|
@@ -27439,16 +27459,16 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27439
27459
|
"get_note",
|
|
27440
27460
|
"Get full details of a single Note by ID",
|
|
27441
27461
|
{
|
|
27442
|
-
|
|
27443
|
-
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or
|
|
27462
|
+
systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
|
|
27463
|
+
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
|
|
27444
27464
|
noteId: external_exports.string().describe("The note ID")
|
|
27445
27465
|
},
|
|
27446
|
-
async ({
|
|
27447
|
-
if (
|
|
27448
|
-
return { content: [{ type: "text", text: "Provide
|
|
27466
|
+
async ({ systemId, orgId, noteId }) => {
|
|
27467
|
+
if (systemId && orgId) {
|
|
27468
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
|
|
27449
27469
|
}
|
|
27450
|
-
if (!
|
|
27451
|
-
return { content: [{ type: "text", text: "Provide
|
|
27470
|
+
if (!systemId && !orgId) {
|
|
27471
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
|
|
27452
27472
|
}
|
|
27453
27473
|
try {
|
|
27454
27474
|
if (orgId) {
|
|
@@ -27461,7 +27481,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27461
27481
|
}
|
|
27462
27482
|
return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
|
|
27463
27483
|
}
|
|
27464
|
-
await assertProjectInOrg(client,
|
|
27484
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27465
27485
|
const note = await client.getNote(noteId);
|
|
27466
27486
|
if (!note) {
|
|
27467
27487
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
@@ -27476,15 +27496,15 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27476
27496
|
);
|
|
27477
27497
|
server.tool(
|
|
27478
27498
|
"create_note",
|
|
27479
|
-
"Create a new Note. Provide exactly one of
|
|
27499
|
+
"Create a new Note. Provide exactly one of systemId, teamspaceId, movementId, or none (org-level): systemId scopes the note to a project (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child projects), movementId scopes it to a Movement (commercial SOW container \u2014 engagement-level knowledge), and omitting all creates an org-level note that applies across all projects.",
|
|
27480
27500
|
{
|
|
27481
|
-
|
|
27501
|
+
systemId: external_exports.string().optional().describe("The project ID (omit for teamspace-, movement-, or org-level note)"),
|
|
27482
27502
|
teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for project-, movement-, or org-level note)"),
|
|
27483
27503
|
movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for project-, teamspace-, or org-level note)"),
|
|
27484
27504
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).describe("The type of note"),
|
|
27485
27505
|
content: external_exports.string().describe("The note content"),
|
|
27486
|
-
beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires
|
|
27487
|
-
revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires
|
|
27506
|
+
beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires systemId)"),
|
|
27507
|
+
revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires systemId)"),
|
|
27488
27508
|
beatVersionId: external_exports.string().optional().describe("Beat Version ID if this note governs a specific planning increment (requires beatId)"),
|
|
27489
27509
|
rationale: external_exports.string().optional().describe("Why this note exists"),
|
|
27490
27510
|
confidence: external_exports.coerce.number().min(0).max(1).optional().describe("Confidence level for assumptions (0-1)"),
|
|
@@ -27492,20 +27512,20 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27492
27512
|
dependsOnNotes: external_exports.array(external_exports.string()).optional().describe("Note IDs this note depends on (child \u2192 parent links for consolidation)"),
|
|
27493
27513
|
significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Defaults to implementation when unset. Only meaningful on noteType=decision.")
|
|
27494
27514
|
},
|
|
27495
|
-
async ({
|
|
27515
|
+
async ({ systemId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, confidence, affectsBeats, dependsOnNotes, significance }) => {
|
|
27496
27516
|
try {
|
|
27497
|
-
if ([
|
|
27498
|
-
return { content: [{ type: "text", text: "Specify at most one of
|
|
27517
|
+
if ([systemId, teamspaceId, movementId].filter(Boolean).length > 1) {
|
|
27518
|
+
return { content: [{ type: "text", text: "Specify at most one of systemId, teamspaceId, or movementId." }], isError: true };
|
|
27499
27519
|
}
|
|
27500
27520
|
const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
|
|
27501
27521
|
let note;
|
|
27502
|
-
if (
|
|
27503
|
-
await assertProjectInOrg(client,
|
|
27504
|
-
const projectCode =
|
|
27505
|
-
const noteId = await client.getNextNoteId(projectCode,
|
|
27522
|
+
if (systemId) {
|
|
27523
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27524
|
+
const projectCode = systemId.substring(0, 4).toUpperCase();
|
|
27525
|
+
const noteId = await client.getNextNoteId(projectCode, systemId);
|
|
27506
27526
|
note = await client.createNote({
|
|
27507
27527
|
noteId,
|
|
27508
|
-
projectId,
|
|
27528
|
+
projectId: systemId,
|
|
27509
27529
|
beatId,
|
|
27510
27530
|
revisionId,
|
|
27511
27531
|
beatVersionId,
|
|
@@ -27577,8 +27597,8 @@ ${text}` }] };
|
|
|
27577
27597
|
"update_note",
|
|
27578
27598
|
"Update a Note (status, content, response, etc.). humanAssignee and agentAssignee are independent fields \u2014 both can be set simultaneously (a note can have a human owner and an agent collaborator). Set either to null to clear it.",
|
|
27579
27599
|
{
|
|
27580
|
-
|
|
27581
|
-
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or
|
|
27600
|
+
systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
|
|
27601
|
+
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
|
|
27582
27602
|
noteId: external_exports.string().describe("The note ID to update"),
|
|
27583
27603
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. assumption \u2192 guidance)"),
|
|
27584
27604
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status"),
|
|
@@ -27592,13 +27612,13 @@ ${text}` }] };
|
|
|
27592
27612
|
agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or reassign this note to an agent (null to clear)"),
|
|
27593
27613
|
significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Only meaningful on noteType=decision.")
|
|
27594
27614
|
},
|
|
27595
|
-
async ({
|
|
27615
|
+
async ({ systemId, orgId, noteId, noteType, status, content, rationale, response, beatId, revisionId, dependsOnNotes, humanAssignee, agentAssignee, significance }) => {
|
|
27596
27616
|
try {
|
|
27597
|
-
if (
|
|
27598
|
-
return { content: [{ type: "text", text: "Provide
|
|
27617
|
+
if (systemId && orgId) {
|
|
27618
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
|
|
27599
27619
|
}
|
|
27600
|
-
if (!
|
|
27601
|
-
return { content: [{ type: "text", text: "Provide
|
|
27620
|
+
if (!systemId && !orgId) {
|
|
27621
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
|
|
27602
27622
|
}
|
|
27603
27623
|
if (orgId) {
|
|
27604
27624
|
if (orgId !== ctx.orgId) {
|
|
@@ -27628,7 +27648,7 @@ ${text}` }] };
|
|
|
27628
27648
|
|
|
27629
27649
|
${formatNoteDetail(updated2)}` }] };
|
|
27630
27650
|
}
|
|
27631
|
-
await assertProjectInOrg(client,
|
|
27651
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27632
27652
|
const updated = await client.updateNote(noteId, {
|
|
27633
27653
|
...noteType !== void 0 && { noteType },
|
|
27634
27654
|
status,
|
|
@@ -27659,7 +27679,7 @@ ${text}` }] };
|
|
|
27659
27679
|
"bulk_update_notes",
|
|
27660
27680
|
"Update multiple Notes in one call, applying the same fields to all. More efficient than calling update_note N times. Returns updated notes and any per-note failures.",
|
|
27661
27681
|
{
|
|
27662
|
-
|
|
27682
|
+
systemId: external_exports.string().describe("The project ID (for access control)"),
|
|
27663
27683
|
noteIds: external_exports.array(external_exports.string()).min(1).max(100).describe("Note IDs to update (max 100 per call)"),
|
|
27664
27684
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify all notes to this type"),
|
|
27665
27685
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status for all notes"),
|
|
@@ -27668,9 +27688,9 @@ ${text}` }] };
|
|
|
27668
27688
|
humanAssignee: humanAssigneeSchema.nullable().optional().describe("Assign or clear human assignee on all notes (null to clear)"),
|
|
27669
27689
|
agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or clear agent assignee on all notes (null to clear)")
|
|
27670
27690
|
},
|
|
27671
|
-
async ({
|
|
27691
|
+
async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
|
|
27672
27692
|
try {
|
|
27673
|
-
await assertProjectInOrg(client,
|
|
27693
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27674
27694
|
const result = await client.bulkUpdateNotes(noteIds, {
|
|
27675
27695
|
...noteType !== void 0 && { noteType },
|
|
27676
27696
|
status,
|
|
@@ -27701,15 +27721,15 @@ ${text}` }] };
|
|
|
27701
27721
|
"reassign_note",
|
|
27702
27722
|
"Reassign a misattributed note to a different beat (with audit trail)",
|
|
27703
27723
|
{
|
|
27704
|
-
|
|
27724
|
+
systemId: external_exports.string().describe("The project ID (for access control)"),
|
|
27705
27725
|
noteId: external_exports.string().describe("The note ID to reassign"),
|
|
27706
27726
|
targetBeatId: external_exports.string().describe("The beat ID to move the note to"),
|
|
27707
27727
|
targetRevisionId: external_exports.string().optional().describe("Optional revision ID to scope the note to"),
|
|
27708
27728
|
reason: external_exports.string().describe("Why this note is being reassigned")
|
|
27709
27729
|
},
|
|
27710
|
-
async ({
|
|
27730
|
+
async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
|
|
27711
27731
|
try {
|
|
27712
|
-
await assertProjectInOrg(client,
|
|
27732
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27713
27733
|
const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
|
|
27714
27734
|
if (!updated) {
|
|
27715
27735
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
@@ -27725,15 +27745,15 @@ ${text}` }] };
|
|
|
27725
27745
|
"remove_note",
|
|
27726
27746
|
"Soft-remove a misattributed note by dismissing it (with audit trail)",
|
|
27727
27747
|
{
|
|
27728
|
-
|
|
27748
|
+
systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
|
|
27729
27749
|
noteId: external_exports.string().min(1).describe("The note ID to remove"),
|
|
27730
27750
|
reason: external_exports.string().min(1).max(500).optional().describe('Why this note is being removed (defaults to "Removed via remove_note")')
|
|
27731
27751
|
},
|
|
27732
|
-
async ({
|
|
27752
|
+
async ({ systemId, noteId, reason }) => {
|
|
27733
27753
|
try {
|
|
27734
|
-
await assertProjectInOrg(client,
|
|
27754
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27735
27755
|
const note = await client.getNote(noteId);
|
|
27736
|
-
if (!note || note.projectId !==
|
|
27756
|
+
if (!note || (note.systemId ?? note.projectId) !== systemId) {
|
|
27737
27757
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
27738
27758
|
}
|
|
27739
27759
|
const removeReason = reason ?? "Removed via remove_note";
|
|
@@ -27752,15 +27772,15 @@ ${text}` }] };
|
|
|
27752
27772
|
"dismiss_document",
|
|
27753
27773
|
"Soft-remove an uploaded document by dismissing it \u2014 it will no longer appear in list_documents output. Only works on document-type notes; use remove_note for non-document notes.",
|
|
27754
27774
|
{
|
|
27755
|
-
|
|
27775
|
+
systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
|
|
27756
27776
|
noteId: external_exports.string().min(1).describe("The document note ID to dismiss"),
|
|
27757
27777
|
reason: external_exports.string().min(1).max(500).optional().describe('Why this document is being dismissed (defaults to "Dismissed via dismiss_document")')
|
|
27758
27778
|
},
|
|
27759
|
-
async ({
|
|
27779
|
+
async ({ systemId, noteId, reason }) => {
|
|
27760
27780
|
try {
|
|
27761
|
-
await assertProjectInOrg(client,
|
|
27781
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27762
27782
|
const note = await client.getNote(noteId);
|
|
27763
|
-
if (!note || note.projectId !==
|
|
27783
|
+
if (!note || (note.systemId ?? note.projectId) !== systemId) {
|
|
27764
27784
|
return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
|
|
27765
27785
|
}
|
|
27766
27786
|
if (note.noteType !== "document") {
|
|
@@ -27785,11 +27805,11 @@ ${text}` }] };
|
|
|
27785
27805
|
"list_key_assumptions",
|
|
27786
27806
|
"Get key assumptions sorted by criticality (highest risk first)",
|
|
27787
27807
|
{
|
|
27788
|
-
|
|
27808
|
+
systemId: external_exports.string().describe("The project ID")
|
|
27789
27809
|
},
|
|
27790
|
-
async ({
|
|
27791
|
-
await assertProjectInOrg(client,
|
|
27792
|
-
const assumptions = await client.getKeyAssumptions(
|
|
27810
|
+
async ({ systemId }) => {
|
|
27811
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27812
|
+
const assumptions = await client.getKeyAssumptions(systemId);
|
|
27793
27813
|
if (assumptions.length === 0) {
|
|
27794
27814
|
return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
|
|
27795
27815
|
}
|
|
@@ -28051,12 +28071,12 @@ ${formatTree(tree.root)}` }] };
|
|
|
28051
28071
|
heading: external_exports.string().min(1).max(200).describe('Section label, e.g. "Decision Log"'),
|
|
28052
28072
|
parentNoteId: external_exports.string().optional().describe("Parent page's Note ID; defaults to the notebook root"),
|
|
28053
28073
|
noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("Note type to collect (default: decision)"),
|
|
28054
|
-
|
|
28074
|
+
systemId: external_exports.string().optional().describe("Project scope; if omitted, the Notebook's first bound project scope is used"),
|
|
28055
28075
|
limit: external_exports.number().int().min(1).max(500).optional().describe("Max members to show (default: 25)"),
|
|
28056
28076
|
order: external_exports.enum(["newest", "oldest"]).optional().describe("Sort order (default: newest)"),
|
|
28057
28077
|
significance: external_exports.enum(["strategic", "structural", "implementation"]).optional().describe("Only for decision notes \u2014 filter to a significance tier (e.g. strategic). Omit to include all.")
|
|
28058
28078
|
},
|
|
28059
|
-
async ({ notebookId, heading, parentNoteId, noteType,
|
|
28079
|
+
async ({ notebookId, heading, parentNoteId, noteType, systemId, limit, order, significance }) => {
|
|
28060
28080
|
let notebook;
|
|
28061
28081
|
try {
|
|
28062
28082
|
notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
|
|
@@ -28069,7 +28089,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28069
28089
|
isError: true
|
|
28070
28090
|
};
|
|
28071
28091
|
}
|
|
28072
|
-
let scopeProjectId =
|
|
28092
|
+
let scopeProjectId = systemId;
|
|
28073
28093
|
if (!scopeProjectId) {
|
|
28074
28094
|
try {
|
|
28075
28095
|
const bindings = await client.getNotebookScopeBindings(notebookId);
|
|
@@ -28078,7 +28098,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28078
28098
|
}
|
|
28079
28099
|
}
|
|
28080
28100
|
if (!scopeProjectId) {
|
|
28081
|
-
return { content: [{ type: "text", text: "No project scope: pass
|
|
28101
|
+
return { content: [{ type: "text", text: "No project scope: pass systemId or bind the notebook to a project first." }], isError: true };
|
|
28082
28102
|
}
|
|
28083
28103
|
let tree;
|
|
28084
28104
|
try {
|
|
@@ -28139,12 +28159,12 @@ ${formatTree(tree.root)}` }] };
|
|
|
28139
28159
|
parentNoteId: external_exports.string().optional().describe("The page Note ID the section sits under; defaults to the notebook root"),
|
|
28140
28160
|
heading: external_exports.string().min(1).max(200).optional().describe("New section label"),
|
|
28141
28161
|
noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("New note type to collect"),
|
|
28142
|
-
|
|
28162
|
+
systemId: external_exports.string().optional().describe("New project scope"),
|
|
28143
28163
|
limit: external_exports.number().int().min(1).max(500).optional().describe("New max members to show"),
|
|
28144
28164
|
order: external_exports.enum(["newest", "oldest"]).optional().describe("New sort order"),
|
|
28145
28165
|
significance: external_exports.enum(["strategic", "structural", "implementation"]).optional().describe("New significance-tier filter (decision notes only)")
|
|
28146
28166
|
},
|
|
28147
|
-
async ({ notebookId, elementId, parentNoteId, heading, noteType,
|
|
28167
|
+
async ({ notebookId, elementId, parentNoteId, heading, noteType, systemId, limit, order, significance }) => {
|
|
28148
28168
|
let notebook;
|
|
28149
28169
|
try {
|
|
28150
28170
|
notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
|
|
@@ -28183,7 +28203,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28183
28203
|
const base = existing.query;
|
|
28184
28204
|
const mergedQuery = {
|
|
28185
28205
|
source: "notes",
|
|
28186
|
-
scope:
|
|
28206
|
+
scope: systemId !== void 0 ? `project:${systemId}` : base.scope,
|
|
28187
28207
|
noteType: noteType ?? base.noteType,
|
|
28188
28208
|
limit: limit ?? base.limit,
|
|
28189
28209
|
sortBy: order !== void 0 ? order === "oldest" ? "createdAt_asc" : "createdAt_desc" : base.sortBy,
|
|
@@ -28485,14 +28505,14 @@ function registerOnboardingTools(server, ctx, client) {
|
|
|
28485
28505
|
"The PM reviews and confirms \u2014 use create_beat for each approved Beat, create_note for any Note conversions."
|
|
28486
28506
|
].join(" "),
|
|
28487
28507
|
{
|
|
28488
|
-
|
|
28508
|
+
systemId: external_exports.string().describe("The newly created project ID"),
|
|
28489
28509
|
projectTitle: external_exports.string().describe("The project title"),
|
|
28490
28510
|
projectDescription: external_exports.string().optional().describe("Discovery context: pain points, goals, and constraints summary to inform the Beat structure"),
|
|
28491
28511
|
analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
|
|
28492
28512
|
},
|
|
28493
|
-
async ({
|
|
28494
|
-
await assertProjectInOrg(client,
|
|
28495
|
-
const result = await client.draftOnboardingBeats(
|
|
28513
|
+
async ({ systemId, projectTitle, projectDescription, analysisJson }) => {
|
|
28514
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
28515
|
+
const result = await client.draftOnboardingBeats(systemId, { projectTitle, projectDescription, analysisJson });
|
|
28496
28516
|
if (!result.suggestions || result.suggestions.length === 0) {
|
|
28497
28517
|
return {
|
|
28498
28518
|
content: [{ type: "text", text: "Failed to generate Beat suggestions. Try providing more project context." }]
|
|
@@ -28558,14 +28578,14 @@ Error: ${msg}`);
|
|
|
28558
28578
|
for (const p of projects) {
|
|
28559
28579
|
try {
|
|
28560
28580
|
const project = await client.createSystem({
|
|
28561
|
-
|
|
28581
|
+
systemId: (0, import_node_crypto.randomUUID)(),
|
|
28562
28582
|
orgId: ctx.orgId,
|
|
28563
28583
|
teamspaceId: teamspace.teamspaceId,
|
|
28564
28584
|
ownerUserId: ctx.user?.userId ?? "system",
|
|
28565
28585
|
title: p.title,
|
|
28566
28586
|
description: p.description
|
|
28567
28587
|
});
|
|
28568
|
-
createdProjects.push({
|
|
28588
|
+
createdProjects.push({ systemId: project.systemId, title: project.title });
|
|
28569
28589
|
} catch (err) {
|
|
28570
28590
|
const msg = err instanceof Error ? err.message : String(err);
|
|
28571
28591
|
lines.push(`
|
|
@@ -28578,7 +28598,7 @@ Error: ${msg}`);
|
|
|
28578
28598
|
if (createdProjects.length > 0) {
|
|
28579
28599
|
lines.push(`
|
|
28580
28600
|
Projects (${createdProjects.length}):`);
|
|
28581
|
-
for (const p of createdProjects) lines.push(` [${p.
|
|
28601
|
+
for (const p of createdProjects) lines.push(` [${p.systemId}] ${p.title}`);
|
|
28582
28602
|
}
|
|
28583
28603
|
const createdNotes = [];
|
|
28584
28604
|
const teamspaceSlug = teamspace.slug ?? teamspaceName.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -28604,7 +28624,7 @@ Projects (${createdProjects.length}):`);
|
|
|
28604
28624
|
const n = await client.createNote({
|
|
28605
28625
|
noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
|
|
28606
28626
|
// overridden server-side
|
|
28607
|
-
projectId: project.
|
|
28627
|
+
projectId: project.systemId,
|
|
28608
28628
|
noteType: note.noteType,
|
|
28609
28629
|
content: note.content,
|
|
28610
28630
|
rationale: note.rationale,
|
|
@@ -28802,7 +28822,7 @@ function registerOrganizationTools(server, ctx, client) {
|
|
|
28802
28822
|
}
|
|
28803
28823
|
const text = `System transferred.
|
|
28804
28824
|
|
|
28805
|
-
**System ID:** ${transferred.
|
|
28825
|
+
**System ID:** ${transferred.systemId}
|
|
28806
28826
|
**Title:** ${transferred.title}
|
|
28807
28827
|
**New Org:** ${transferred.orgId}`;
|
|
28808
28828
|
return { content: [{ type: "text", text }] };
|
|
@@ -28926,7 +28946,7 @@ ${d.rationale}${d.suggestions?.length ? `
|
|
|
28926
28946
|
function registerPlanQualityTools(server, ctx, client) {
|
|
28927
28947
|
const schema = {
|
|
28928
28948
|
beatVersionId: external_exports.string().describe("The Beat Version ID (e.g., bv-abc123). beat_version_quality targets Beat Versions only."),
|
|
28929
|
-
|
|
28949
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
28930
28950
|
// The completeness dimension probes the codebase, so this tool accepts the
|
|
28931
28951
|
// same repo overrides as run_check — without them the probe could only ever
|
|
28932
28952
|
// read the default branch from this surface.
|
|
@@ -28934,7 +28954,7 @@ function registerPlanQualityTools(server, ctx, client) {
|
|
|
28934
28954
|
localPath: external_exports.string().optional().describe("Absolute path to an already-cloned local copy of the repo \u2014 the completeness probe reads it instead of cloning"),
|
|
28935
28955
|
wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
|
|
28936
28956
|
};
|
|
28937
|
-
const handler = async ({ beatVersionId,
|
|
28957
|
+
const handler = async ({ beatVersionId, systemId, branch, localPath, wait }) => {
|
|
28938
28958
|
try {
|
|
28939
28959
|
const targetError = beatVersionQualityTargetError(beatVersionId ?? "");
|
|
28940
28960
|
if (targetError) {
|
|
@@ -28943,9 +28963,9 @@ function registerPlanQualityTools(server, ctx, client) {
|
|
|
28943
28963
|
isError: true
|
|
28944
28964
|
};
|
|
28945
28965
|
}
|
|
28946
|
-
await assertProjectInOrg(client,
|
|
28966
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
28947
28967
|
const opts = branch || localPath ? { branch, localPath } : void 0;
|
|
28948
|
-
const task = await client.runCheck(
|
|
28968
|
+
const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
|
|
28949
28969
|
if (!wait) {
|
|
28950
28970
|
return {
|
|
28951
28971
|
content: [{
|
|
@@ -29085,22 +29105,22 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29085
29105
|
"check_portfolio_coherence",
|
|
29086
29106
|
"Run a portfolio coherence check on a project. Clusters Beats by semantic similarity to surface fragmentation (merge candidates) and checks what fraction of Beats name an outcome their authoriser would recognise. By default returns a taskId immediately \u2014 set wait=true to block and receive the full report inline.",
|
|
29087
29107
|
{
|
|
29088
|
-
|
|
29108
|
+
systemId: external_exports.string().min(1).describe("The project ID"),
|
|
29089
29109
|
wait: external_exports.boolean().optional().describe("Block until the check completes and return the report inline. Default: false.")
|
|
29090
29110
|
},
|
|
29091
|
-
async ({
|
|
29111
|
+
async ({ systemId, wait }) => {
|
|
29092
29112
|
try {
|
|
29093
|
-
await assertProjectInOrg(client,
|
|
29094
|
-
const { taskId } = await client.runCheck(
|
|
29113
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29114
|
+
const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
|
|
29095
29115
|
if (!wait) {
|
|
29096
29116
|
return {
|
|
29097
29117
|
content: [{
|
|
29098
29118
|
type: "text",
|
|
29099
29119
|
text: [
|
|
29100
|
-
`Portfolio coherence check enqueued for ${
|
|
29120
|
+
`Portfolio coherence check enqueued for ${systemId}.`,
|
|
29101
29121
|
`Task ID: ${taskId}`,
|
|
29102
29122
|
"",
|
|
29103
|
-
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with
|
|
29123
|
+
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with systemId="${systemId}" and checkType="portfolio_coherence" to view results.`
|
|
29104
29124
|
].join("\n")
|
|
29105
29125
|
}]
|
|
29106
29126
|
};
|
|
@@ -29117,17 +29137,17 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29117
29137
|
"consolidate_beats",
|
|
29118
29138
|
"Consolidate a fragmented Beat cluster \u2014 identified by check_portfolio_coherence \u2014 into a single VP-level capability. By default (confirm omitted or false) returns a dry-run preview: surviving Beat, Beats to archive, Notes to reassign, and any in-flight Beat Versions. Set confirm=true to execute the merge.",
|
|
29119
29139
|
{
|
|
29120
|
-
|
|
29140
|
+
systemId: external_exports.string().min(1).describe("The project ID \u2014 all beatIds must belong to this project"),
|
|
29121
29141
|
beatIds: external_exports.array(external_exports.string().min(1)).min(2).max(50).describe("All Beat IDs in the cluster (including the one that will survive)"),
|
|
29122
29142
|
primaryBeatId: external_exports.string().min(1).optional().describe("Beat ID that survives \u2014 defaults to beatIds[0] if omitted"),
|
|
29123
29143
|
rationale: external_exports.string().max(2e3).optional().describe("Why these Beats are being merged \u2014 auto-generated if omitted"),
|
|
29124
29144
|
confirm: external_exports.boolean().optional().describe("false/omitted = preview; true = execute the merge")
|
|
29125
29145
|
},
|
|
29126
|
-
async ({
|
|
29146
|
+
async ({ systemId, beatIds, primaryBeatId, rationale, confirm }) => {
|
|
29127
29147
|
const archivedBeatIds = [];
|
|
29128
29148
|
let totalNotesReassigned = 0;
|
|
29129
29149
|
try {
|
|
29130
|
-
await assertProjectInOrg(client,
|
|
29150
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29131
29151
|
const uniqueBeatIds = [...new Set(beatIds)];
|
|
29132
29152
|
const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
|
|
29133
29153
|
if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
|
|
@@ -29143,19 +29163,19 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29143
29163
|
isError: true
|
|
29144
29164
|
};
|
|
29145
29165
|
}
|
|
29146
|
-
const allProjectBeats = await client.listSystemBeats(
|
|
29166
|
+
const allProjectBeats = await client.listSystemBeats(systemId);
|
|
29147
29167
|
const beatMap = new Map(allProjectBeats.map((b) => [b.beatId, b]));
|
|
29148
29168
|
const primaryBeat = beatMap.get(resolvedPrimaryId);
|
|
29149
29169
|
if (!primaryBeat) {
|
|
29150
|
-
return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${
|
|
29170
|
+
return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${systemId}".` }], isError: true };
|
|
29151
29171
|
}
|
|
29152
29172
|
const missingIds = duplicateBeatIds.filter((id) => !beatMap.has(id));
|
|
29153
29173
|
if (missingIds.length > 0) {
|
|
29154
|
-
return { content: [{ type: "text", text: `Beat(s) not found in project "${
|
|
29174
|
+
return { content: [{ type: "text", text: `Beat(s) not found in project "${systemId}": ${missingIds.join(", ")}` }], isError: true };
|
|
29155
29175
|
}
|
|
29156
29176
|
const dupBvResults = await Promise.allSettled(
|
|
29157
29177
|
duplicateBeatIds.map(
|
|
29158
|
-
(id) => client.listBeatVersions(
|
|
29178
|
+
(id) => client.listBeatVersions(systemId, { beatId: id, includeTerminal: false })
|
|
29159
29179
|
)
|
|
29160
29180
|
);
|
|
29161
29181
|
const bvRejected = dupBvResults.filter((r) => r.status === "rejected");
|
|
@@ -29322,17 +29342,25 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29322
29342
|
if (!beat) {
|
|
29323
29343
|
return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
|
|
29324
29344
|
}
|
|
29345
|
+
const beatSystemId = beat.systemId ?? beat.projectId;
|
|
29346
|
+
if (!beatSystemId) {
|
|
29347
|
+
return { content: [{ type: "text", text: `Beat "${beatId}" has no system identifier \u2014 record may be corrupt.` }], isError: true };
|
|
29348
|
+
}
|
|
29325
29349
|
let parentBv;
|
|
29326
29350
|
if (beatVersionId !== void 0) {
|
|
29327
29351
|
parentBv = await client.getBeatVersion(beatVersionId);
|
|
29328
29352
|
if (!parentBv) {
|
|
29329
29353
|
return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
|
|
29330
29354
|
}
|
|
29331
|
-
|
|
29355
|
+
const bvSystemId = parentBv.systemId ?? parentBv.projectId;
|
|
29356
|
+
if (!bvSystemId) {
|
|
29357
|
+
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" has no system identifier \u2014 record may be corrupt.` }], isError: true };
|
|
29358
|
+
}
|
|
29359
|
+
if (parentBv.beatId !== beat.beatId || bvSystemId !== beatSystemId) {
|
|
29332
29360
|
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
|
|
29333
29361
|
}
|
|
29334
29362
|
}
|
|
29335
|
-
const { revision, warnings } = await client.createRevision(
|
|
29363
|
+
const { revision, warnings } = await client.createRevision(beatSystemId, beat.beatId, {
|
|
29336
29364
|
title,
|
|
29337
29365
|
description,
|
|
29338
29366
|
changeSummary,
|
|
@@ -29364,7 +29392,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29364
29392
|
}
|
|
29365
29393
|
if (parentBv && !parentBv.targetDropId) {
|
|
29366
29394
|
try {
|
|
29367
|
-
const project = await client.getSystem(parentBv.
|
|
29395
|
+
const project = await client.getSystem(parentBv.systemId);
|
|
29368
29396
|
const accountId = project?.accountId;
|
|
29369
29397
|
if (accountId) {
|
|
29370
29398
|
const draftDrops = await client.listAccountDrops(accountId, { state: "draft" });
|
|
@@ -29420,7 +29448,12 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29420
29448
|
if (!bv) {
|
|
29421
29449
|
return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
|
|
29422
29450
|
}
|
|
29423
|
-
|
|
29451
|
+
const bvSystemId = bv.systemId ?? bv.projectId;
|
|
29452
|
+
const revSystemId = revision.systemId ?? revision.projectId;
|
|
29453
|
+
if (!bvSystemId || !revSystemId) {
|
|
29454
|
+
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" has no system identifier \u2014 record may be corrupt.` }], isError: true };
|
|
29455
|
+
}
|
|
29456
|
+
if (bv.beatId !== revision.beatId || bvSystemId !== revSystemId) {
|
|
29424
29457
|
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
|
|
29425
29458
|
}
|
|
29426
29459
|
}
|
|
@@ -29589,7 +29622,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
|
|
|
29589
29622
|
if (revision.scm) {
|
|
29590
29623
|
lines2.push("", `\u26A0 This revision already has a linked PR (#${revision.scm.number} \u2014 ${revision.scm.url}). create_pr_for_revision would be rejected.`);
|
|
29591
29624
|
} else {
|
|
29592
|
-
const project = await client.getSystem(revision.
|
|
29625
|
+
const project = await client.getSystem(revision.systemId);
|
|
29593
29626
|
if (!project?.repoOwner || !project?.repoName || !project?.repoDefaultBranch) {
|
|
29594
29627
|
lines2.push("", "\u26A0 The project has no repo configured (repoOwner/repoName/repoDefaultBranch). create_pr_for_revision would be rejected.");
|
|
29595
29628
|
} else {
|
|
@@ -29852,10 +29885,10 @@ function registerRevisionQualityTools(server, ctx, client) {
|
|
|
29852
29885
|
"Run a five-dimension PR-readiness check on a Revision (Scoped, Traceable, Testable, Review-ready, Non-duplicating). Advisory only \u2014 no lifecycle transition or creation path gates on this score. By default returns a jobId immediately (fire-and-forget) \u2014 use get_job_status or list_checks to retrieve results. Set wait=true to block until the check completes and receive the scorecard inline. WARNING: wait=true holds the MCP session open for 30\u201360s \u2014 for bulk operations, omit wait and poll get_job_status separately.",
|
|
29853
29886
|
{
|
|
29854
29887
|
revisionId: external_exports.string().describe("The Revision ID (e.g., rev-abc123). revision_quality targets Revisions only."),
|
|
29855
|
-
|
|
29888
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
29856
29889
|
wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
|
|
29857
29890
|
},
|
|
29858
|
-
async ({ revisionId,
|
|
29891
|
+
async ({ revisionId, systemId, wait }) => {
|
|
29859
29892
|
try {
|
|
29860
29893
|
const targetError = revisionQualityTargetError(revisionId);
|
|
29861
29894
|
if (targetError) {
|
|
@@ -29864,9 +29897,9 @@ function registerRevisionQualityTools(server, ctx, client) {
|
|
|
29864
29897
|
isError: true
|
|
29865
29898
|
};
|
|
29866
29899
|
}
|
|
29867
|
-
await assertProjectInOrg(client,
|
|
29868
|
-
await assertRevisionInProject(client, revisionId,
|
|
29869
|
-
const task = await client.runCheck(
|
|
29900
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29901
|
+
await assertRevisionInProject(client, revisionId, systemId);
|
|
29902
|
+
const task = await client.runCheck(systemId, "revision_quality", revisionId);
|
|
29870
29903
|
if (!wait) {
|
|
29871
29904
|
return {
|
|
29872
29905
|
content: [{
|
|
@@ -30330,10 +30363,10 @@ async function resolveSnapshotInput(input) {
|
|
|
30330
30363
|
}
|
|
30331
30364
|
function formatImportSummary(result, targetTeamspaceId) {
|
|
30332
30365
|
if (!result.counts || result.counts.beats == null) {
|
|
30333
|
-
throw new Error(`Import of system ${result.
|
|
30366
|
+
throw new Error(`Import of system ${result.systemId} completed but returned no counts \u2014 check worker logs for a TransactionCanceledException`);
|
|
30334
30367
|
}
|
|
30335
30368
|
const lines = [
|
|
30336
|
-
`Imported system: ${result.
|
|
30369
|
+
`Imported system: ${result.systemId}`,
|
|
30337
30370
|
` Beats: ${result.counts.beats}`,
|
|
30338
30371
|
` Proposals: ${result.counts.proposals}`,
|
|
30339
30372
|
` Revisions: ${result.counts.revisions}`,
|
|
@@ -30400,8 +30433,8 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30400
30433
|
throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
|
|
30401
30434
|
}
|
|
30402
30435
|
const systemOrProject = obj["system"] ?? obj["project"];
|
|
30403
|
-
if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["
|
|
30404
|
-
throw new Error(`Snapshot${sourceHint} is missing 'system.
|
|
30436
|
+
if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["systemId"] !== "string") {
|
|
30437
|
+
throw new Error(`Snapshot${sourceHint} is missing 'system.systemId' \u2014 it may not be a valid project snapshot.`);
|
|
30405
30438
|
}
|
|
30406
30439
|
}
|
|
30407
30440
|
|
|
@@ -30414,12 +30447,12 @@ function registerSubscriptionTools(server, ctx, client) {
|
|
|
30414
30447
|
{
|
|
30415
30448
|
entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
|
|
30416
30449
|
entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
|
|
30417
|
-
|
|
30450
|
+
systemId: external_exports.string().describe("The project ID (for access control)")
|
|
30418
30451
|
},
|
|
30419
|
-
async ({ entityType, entityId,
|
|
30452
|
+
async ({ entityType, entityId, systemId }) => {
|
|
30420
30453
|
try {
|
|
30421
|
-
await assertProjectInOrg(client,
|
|
30422
|
-
await client.subscribe(ctx.user.userId, entityType, entityId,
|
|
30454
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
30455
|
+
await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
|
|
30423
30456
|
return {
|
|
30424
30457
|
content: [{
|
|
30425
30458
|
type: "text",
|
|
@@ -30653,7 +30686,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30653
30686
|
const lines = [
|
|
30654
30687
|
`System updated successfully.`,
|
|
30655
30688
|
"",
|
|
30656
|
-
`**ID:** ${updated.
|
|
30689
|
+
`**ID:** ${updated.systemId}`,
|
|
30657
30690
|
`**Title:** ${updated.title}`,
|
|
30658
30691
|
accountLine(nonEmpty, updated.accountId),
|
|
30659
30692
|
updated.strategy ? `**Strategy:** (updated)` : "",
|
|
@@ -30690,7 +30723,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30690
30723
|
text: [
|
|
30691
30724
|
"System archived successfully.",
|
|
30692
30725
|
"",
|
|
30693
|
-
`**ID:** ${updated.
|
|
30726
|
+
`**ID:** ${updated.systemId}`,
|
|
30694
30727
|
`**Title:** ${updated.title}`,
|
|
30695
30728
|
`**Status:** ${updated.status}`
|
|
30696
30729
|
].join("\n")
|
|
@@ -30725,7 +30758,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30725
30758
|
const account = normalizeCreateAccountId(accountId);
|
|
30726
30759
|
try {
|
|
30727
30760
|
const project = await client.createSystem({
|
|
30728
|
-
|
|
30761
|
+
systemId: (0, import_crypto5.randomUUID)(),
|
|
30729
30762
|
orgId: ctx.orgId,
|
|
30730
30763
|
ownerUserId: ctx.user.userId,
|
|
30731
30764
|
title,
|
|
@@ -30741,7 +30774,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30741
30774
|
const text = [
|
|
30742
30775
|
`System created successfully.`,
|
|
30743
30776
|
"",
|
|
30744
|
-
`**ID:** ${project.
|
|
30777
|
+
`**ID:** ${project.systemId}`,
|
|
30745
30778
|
`**Title:** ${project.title}`,
|
|
30746
30779
|
`**Status:** ${project.status}`,
|
|
30747
30780
|
project.accountId ? `**Account:** ${project.accountId}` : "",
|
|
@@ -30811,7 +30844,7 @@ function registerTeamspaceTools(server, ctx, client) {
|
|
|
30811
30844
|
];
|
|
30812
30845
|
if (tsProjects.length > 0) {
|
|
30813
30846
|
lines.push("", `Projects (${tsProjects.length}):`);
|
|
30814
|
-
for (const p of tsProjects) lines.push(` [${p.
|
|
30847
|
+
for (const p of tsProjects) lines.push(` [${p.systemId}] ${p.title} \u2014 ${p.status ?? "unknown"}`);
|
|
30815
30848
|
} else {
|
|
30816
30849
|
lines.push("Projects: (none)");
|
|
30817
30850
|
}
|
|
@@ -31073,23 +31106,23 @@ function registerValueVelocityTools(server, ctx, client) {
|
|
|
31073
31106
|
"propose_enablement_edges",
|
|
31074
31107
|
'Propose directed enablement edges for a project \u2014 "Beat A accelerates Beat B". The LLM analyzes project Beats and proposes which ones unlock or accelerate others. Only proposed edges are created; a human must confirm before they affect Value Velocity scores. Pass beatId to scope the pass to edges FROM one Beat \u2014 strongly recommended on a large portfolio, where an unscoped pass can exceed the request timeout. If you already know the edge you want, skip proposing and use create_enablement_edge instead.',
|
|
31075
31108
|
{
|
|
31076
|
-
|
|
31109
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31077
31110
|
beatId: external_exports.string().min(1).optional().describe("Scope the pass to edges FROM this Beat. Omit for a whole-project pass (slow on large portfolios).")
|
|
31078
31111
|
},
|
|
31079
|
-
async ({
|
|
31112
|
+
async ({ systemId, beatId }) => {
|
|
31080
31113
|
try {
|
|
31081
|
-
await assertProjectInOrg(client,
|
|
31114
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31082
31115
|
const actorId = resolveActorId(ctx);
|
|
31083
31116
|
if (!actorId) {
|
|
31084
31117
|
return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
|
|
31085
31118
|
}
|
|
31086
31119
|
if (beatId !== void 0) await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
31087
|
-
const proposed = await client.proposeEnablementEdges(
|
|
31120
|
+
const proposed = await client.proposeEnablementEdges(systemId, actorId, beatId);
|
|
31088
31121
|
if (proposed.length === 0) {
|
|
31089
31122
|
return { content: [{ type: "text", text: "No enablement edges proposed (fewer than 2 Beats or no relationships found)." }] };
|
|
31090
31123
|
}
|
|
31091
31124
|
const lines = [
|
|
31092
|
-
`Proposed ${proposed.length} enablement edge(s) for project ${
|
|
31125
|
+
`Proposed ${proposed.length} enablement edge(s) for project ${systemId}${beatId ? ` scoped to ${beatId}` : ""}:`,
|
|
31093
31126
|
"",
|
|
31094
31127
|
"| Source Beat | Target Beat | Rationale |",
|
|
31095
31128
|
"|-------------|------------|-----------|"
|
|
@@ -31461,13 +31494,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31461
31494
|
"list_tasks",
|
|
31462
31495
|
"DEPRECATED: use `list_jobs`. List background jobs for a project with optional status filter.",
|
|
31463
31496
|
{
|
|
31464
|
-
|
|
31497
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31465
31498
|
status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
|
|
31466
31499
|
limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
|
|
31467
31500
|
},
|
|
31468
|
-
async ({
|
|
31469
|
-
await assertProjectInOrg(client,
|
|
31470
|
-
const tasks = await client.listSystemTasks(
|
|
31501
|
+
async ({ systemId, status, limit }) => {
|
|
31502
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31503
|
+
const tasks = await client.listSystemTasks(systemId, {
|
|
31471
31504
|
status,
|
|
31472
31505
|
limit
|
|
31473
31506
|
});
|
|
@@ -31519,13 +31552,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31519
31552
|
"list_jobs",
|
|
31520
31553
|
"List background jobs for a project with optional status filter",
|
|
31521
31554
|
{
|
|
31522
|
-
|
|
31555
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31523
31556
|
status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
|
|
31524
31557
|
limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
|
|
31525
31558
|
},
|
|
31526
|
-
async ({
|
|
31527
|
-
await assertProjectInOrg(client,
|
|
31528
|
-
const tasks = await client.listSystemTasks(
|
|
31559
|
+
async ({ systemId, status, limit }) => {
|
|
31560
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31561
|
+
const tasks = await client.listSystemTasks(systemId, {
|
|
31529
31562
|
status,
|
|
31530
31563
|
limit
|
|
31531
31564
|
});
|
|
@@ -31543,13 +31576,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31543
31576
|
"draft_coda",
|
|
31544
31577
|
"Generate and apply a Coda (description) for a Beat that has a title but no description. Uses project context, sibling Beats, related Notes, and codebase grep to write a resolved expression of the capability. The Coda is applied directly to the Beat, making it eligible for plan_beat_versions once a beat_quality check passes. An activity is logged so the Beat owner can review and edit. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
31545
31578
|
{
|
|
31546
|
-
|
|
31579
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31547
31580
|
beatId: external_exports.string().describe("The beat ID (must have a title but no description)")
|
|
31548
31581
|
},
|
|
31549
|
-
async ({
|
|
31582
|
+
async ({ systemId, beatId }) => {
|
|
31550
31583
|
try {
|
|
31551
|
-
await assertProjectInOrg(client,
|
|
31552
|
-
const task = await client.draftCoda(
|
|
31584
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31585
|
+
const task = await client.draftCoda(systemId, beatId);
|
|
31553
31586
|
return {
|
|
31554
31587
|
content: [{
|
|
31555
31588
|
type: "text",
|
|
@@ -31557,7 +31590,7 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31557
31590
|
"Coda draft session queued.",
|
|
31558
31591
|
"",
|
|
31559
31592
|
`**Beat:** ${beatId}`,
|
|
31560
|
-
`**Project:** ${
|
|
31593
|
+
`**Project:** ${systemId}`,
|
|
31561
31594
|
`**Task ID:** ${task.taskId}`,
|
|
31562
31595
|
`**Status:** ${task.status}`,
|
|
31563
31596
|
"",
|
|
@@ -31576,20 +31609,20 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31576
31609
|
"validate_assumptions",
|
|
31577
31610
|
"Validate unvalidated assumption Notes against the codebase. Checks if assumptions are confirmed (validated), contradicted (invalidated), or have insufficient evidence (unvalidated). Low-criticality assumptions are auto-resolved; high-criticality are flagged for human review. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
31578
31611
|
{
|
|
31579
|
-
|
|
31612
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31580
31613
|
noteIds: external_exports.array(external_exports.string()).optional().describe("Specific assumption note IDs to validate. If omitted, validates all unvalidated assumptions.")
|
|
31581
31614
|
},
|
|
31582
|
-
async ({
|
|
31615
|
+
async ({ systemId, noteIds }) => {
|
|
31583
31616
|
try {
|
|
31584
|
-
await assertProjectInOrg(client,
|
|
31585
|
-
const task = await client.validateAssumptions(
|
|
31617
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31618
|
+
const task = await client.validateAssumptions(systemId, noteIds);
|
|
31586
31619
|
return {
|
|
31587
31620
|
content: [{
|
|
31588
31621
|
type: "text",
|
|
31589
31622
|
text: [
|
|
31590
31623
|
"Assumption validation queued.",
|
|
31591
31624
|
"",
|
|
31592
|
-
`**Project:** ${
|
|
31625
|
+
`**Project:** ${systemId}`,
|
|
31593
31626
|
`**Scope:** ${noteIds ? `${noteIds.length} specific assumptions` : "All unvalidated assumptions"}`,
|
|
31594
31627
|
`**Task ID:** ${task.taskId}`,
|
|
31595
31628
|
`**Status:** ${task.status}`,
|
|
@@ -31610,15 +31643,15 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31610
31643
|
"start_session",
|
|
31611
31644
|
'Start an agent session from a free-form message \u2014 like opening a Claude Code conversation pointed at your project. The message is the only instruction: describe what you want ("investigate why login fails", "implement revision rev-abc123", "what does the auth flow do?") and the agent resolves any entity references itself and does whatever the work needs \u2014 investigate and report findings, or change code and open a PR. A findings-only result with no PR is a valid outcome. Returns a job ID \u2014 use get_job_status to track progress. Requires agent sessions enabled on the project.',
|
|
31612
31645
|
{
|
|
31613
|
-
|
|
31646
|
+
systemId: external_exports.string().min(1).describe("The project ID \u2014 the trusted scope the session is rooted at"),
|
|
31614
31647
|
message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
|
|
31615
31648
|
revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions.")
|
|
31616
31649
|
},
|
|
31617
|
-
async ({
|
|
31650
|
+
async ({ systemId, message, revisionId }) => {
|
|
31618
31651
|
try {
|
|
31619
|
-
await assertProjectInOrg(client,
|
|
31652
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31620
31653
|
const task = await client.startSession(
|
|
31621
|
-
|
|
31654
|
+
systemId,
|
|
31622
31655
|
message,
|
|
31623
31656
|
{ name: ctx.user.name, email: ctx.user.email },
|
|
31624
31657
|
revisionId?.trim() ? { revisionId: revisionId.trim() } : void 0
|
|
@@ -31629,7 +31662,7 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31629
31662
|
text: [
|
|
31630
31663
|
"Agent session queued.",
|
|
31631
31664
|
"",
|
|
31632
|
-
`**Project:** ${
|
|
31665
|
+
`**Project:** ${systemId}`,
|
|
31633
31666
|
`**Task ID:** ${task.taskId}`,
|
|
31634
31667
|
`**Status:** ${task.status}`,
|
|
31635
31668
|
"",
|
|
@@ -31648,20 +31681,20 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31648
31681
|
"plan_revision_batch",
|
|
31649
31682
|
"Autonomously generate an implementation plan for planning Revisions. If revisionIds are omitted, discovers all unplanned planning Revisions and plans them all. Uses codebase-aware agent sessions when available, LLM-only fallback otherwise. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
31650
31683
|
{
|
|
31651
|
-
|
|
31684
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31652
31685
|
revisionIds: external_exports.array(external_exports.string()).optional().describe("Specific revision IDs to plan. If omitted, auto-discovers all unplanned planning revisions.")
|
|
31653
31686
|
},
|
|
31654
|
-
async ({
|
|
31687
|
+
async ({ systemId, revisionIds }) => {
|
|
31655
31688
|
try {
|
|
31656
|
-
await assertProjectInOrg(client,
|
|
31657
|
-
const task = await client.planRevisionBatch(
|
|
31689
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31690
|
+
const task = await client.planRevisionBatch(systemId, revisionIds);
|
|
31658
31691
|
return {
|
|
31659
31692
|
content: [{
|
|
31660
31693
|
type: "text",
|
|
31661
31694
|
text: [
|
|
31662
31695
|
"Revision batch planning queued.",
|
|
31663
31696
|
"",
|
|
31664
|
-
`**Project:** ${
|
|
31697
|
+
`**Project:** ${systemId}`,
|
|
31665
31698
|
`**Scope:** ${revisionIds ? `${revisionIds.length} specific revision(s)` : "All unplanned planning revisions"}`,
|
|
31666
31699
|
`**Task ID:** ${task.taskId}`,
|
|
31667
31700
|
`**Status:** ${task.status}`,
|
|
@@ -31681,19 +31714,19 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31681
31714
|
"run_triage",
|
|
31682
31715
|
"Run the autonomous triage loop for a project. Analyzes next actions, auto-executes safe actions (draft_coda, plan_revision_batch), and escalates items that need human judgment. Requires agentCapabilities.triage enabled on the project. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
31683
31716
|
{
|
|
31684
|
-
|
|
31717
|
+
systemId: external_exports.string().describe("The project ID")
|
|
31685
31718
|
},
|
|
31686
|
-
async ({
|
|
31719
|
+
async ({ systemId }) => {
|
|
31687
31720
|
try {
|
|
31688
|
-
await assertProjectInOrg(client,
|
|
31689
|
-
const task = await client.runTriage(
|
|
31721
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31722
|
+
const task = await client.runTriage(systemId);
|
|
31690
31723
|
return {
|
|
31691
31724
|
content: [{
|
|
31692
31725
|
type: "text",
|
|
31693
31726
|
text: [
|
|
31694
31727
|
"Triage loop queued.",
|
|
31695
31728
|
"",
|
|
31696
|
-
`**Project:** ${
|
|
31729
|
+
`**Project:** ${systemId}`,
|
|
31697
31730
|
`**Task ID:** ${task.taskId}`,
|
|
31698
31731
|
`**Status:** ${task.status}`,
|
|
31699
31732
|
"",
|
|
@@ -31724,15 +31757,15 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31724
31757
|
"Returns a job ID \u2014 use `get_job_status` to track progress."
|
|
31725
31758
|
].join("\n"),
|
|
31726
31759
|
{
|
|
31727
|
-
|
|
31760
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31728
31761
|
content: external_exports.string().describe('Document content \u2014 plain text (default) or base64-encoded binary (when encoding is "base64")'),
|
|
31729
31762
|
filename: external_exports.string().optional().default("document.txt").describe('Original filename with extension (e.g., "sow.pdf", "spec.docx"). Used for format detection in binary mode.'),
|
|
31730
31763
|
encoding: external_exports.enum(["base64"]).optional().describe('Set to "base64" when submitting binary files (DOCX, XLSX, PPTX, PDF). Omit for plain text.')
|
|
31731
31764
|
},
|
|
31732
|
-
async ({
|
|
31765
|
+
async ({ systemId, content, filename, encoding }) => {
|
|
31733
31766
|
try {
|
|
31734
|
-
await assertProjectInOrg(client,
|
|
31735
|
-
const result = encoding === "base64" ? await client.analyzeBinaryDocument(
|
|
31767
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31768
|
+
const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
|
|
31736
31769
|
return {
|
|
31737
31770
|
content: [{
|
|
31738
31771
|
type: "text",
|
|
@@ -32176,7 +32209,7 @@ function createHttpClient(config2) {
|
|
|
32176
32209
|
);
|
|
32177
32210
|
const project = result?.project ? {
|
|
32178
32211
|
...result.project,
|
|
32179
|
-
|
|
32212
|
+
systemId: result.project.id ?? result.project.systemId ?? result.project.projectId,
|
|
32180
32213
|
title: result.project.name ?? result.project.title,
|
|
32181
32214
|
orgId: result.project.organizationId ?? result.project.orgId
|
|
32182
32215
|
} : void 0;
|
|
@@ -32196,7 +32229,7 @@ function createHttpClient(config2) {
|
|
|
32196
32229
|
if (!raw) return void 0;
|
|
32197
32230
|
return {
|
|
32198
32231
|
...raw,
|
|
32199
|
-
|
|
32232
|
+
systemId: raw.id ?? raw.systemId ?? raw.projectId,
|
|
32200
32233
|
title: raw.name ?? raw.title,
|
|
32201
32234
|
orgId: raw.organizationId ?? raw.orgId
|
|
32202
32235
|
};
|
|
@@ -32212,17 +32245,17 @@ function createHttpClient(config2) {
|
|
|
32212
32245
|
// is created unlinked — invisible in the Account's Systems list.
|
|
32213
32246
|
...input.accountId !== void 0 && { accountId: input.accountId }
|
|
32214
32247
|
});
|
|
32215
|
-
return { ...raw,
|
|
32248
|
+
return { ...raw, systemId: raw?.id ?? raw?.systemId ?? raw?.projectId, title: raw?.name ?? raw?.title, orgId: raw?.organizationId ?? raw?.orgId };
|
|
32216
32249
|
},
|
|
32217
32250
|
updateSystem: async (projectId, updates) => {
|
|
32218
32251
|
const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}`, updates);
|
|
32219
32252
|
if (!raw) return void 0;
|
|
32220
|
-
return { ...raw,
|
|
32253
|
+
return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
|
|
32221
32254
|
},
|
|
32222
32255
|
archiveSystem: async (projectId) => {
|
|
32223
32256
|
const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}/archive`);
|
|
32224
32257
|
if (!raw) return void 0;
|
|
32225
|
-
return { ...raw,
|
|
32258
|
+
return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
|
|
32226
32259
|
},
|
|
32227
32260
|
getSystemFact: async (projectId) => {
|
|
32228
32261
|
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/facts`);
|
|
@@ -32291,7 +32324,7 @@ function createHttpClient(config2) {
|
|
|
32291
32324
|
return raw?.report;
|
|
32292
32325
|
},
|
|
32293
32326
|
createBeat: async (input) => {
|
|
32294
|
-
const raw = await request("POST", `/api/systems/${encodeURIComponent(input.
|
|
32327
|
+
const raw = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/beats`, input);
|
|
32295
32328
|
return { ...raw, beatId: raw.beatId ?? raw.id, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
|
|
32296
32329
|
},
|
|
32297
32330
|
updateBeat: async (beatId, updates) => {
|
|
@@ -32375,7 +32408,7 @@ function createHttpClient(config2) {
|
|
|
32375
32408
|
return result?.layer;
|
|
32376
32409
|
},
|
|
32377
32410
|
createSystemLayer: async (input) => {
|
|
32378
|
-
const {
|
|
32411
|
+
const { systemId, layerKey, name, description, parentLayerId, repoPatterns } = input;
|
|
32379
32412
|
const body = {
|
|
32380
32413
|
layerKey,
|
|
32381
32414
|
name,
|
|
@@ -32385,7 +32418,7 @@ function createHttpClient(config2) {
|
|
|
32385
32418
|
};
|
|
32386
32419
|
const result = await request(
|
|
32387
32420
|
"POST",
|
|
32388
|
-
`/api/systems/${encodeURIComponent(
|
|
32421
|
+
`/api/systems/${encodeURIComponent(systemId)}/layers`,
|
|
32389
32422
|
body
|
|
32390
32423
|
);
|
|
32391
32424
|
if (result === void 0) return void 0;
|
|
@@ -33000,7 +33033,7 @@ function createHttpClient(config2) {
|
|
|
33000
33033
|
return { entries: result.activities ?? [], hasMore: false };
|
|
33001
33034
|
},
|
|
33002
33035
|
createActivity: async (input) => {
|
|
33003
|
-
const result = await request("POST", `/api/systems/${encodeURIComponent(input.
|
|
33036
|
+
const result = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/activities`, {
|
|
33004
33037
|
action: input.action,
|
|
33005
33038
|
importance: input.importance,
|
|
33006
33039
|
reason: input.reason,
|
|
@@ -33182,10 +33215,10 @@ function createHttpClient(config2) {
|
|
|
33182
33215
|
const result = await request(
|
|
33183
33216
|
"POST",
|
|
33184
33217
|
`/api/sessions`,
|
|
33185
|
-
{ projectId, message, ...triggeredBy && { triggeredBy }, ...trimmedRevisionId && { revisionId: trimmedRevisionId } }
|
|
33218
|
+
{ systemId: projectId, message, ...triggeredBy && { triggeredBy }, ...trimmedRevisionId && { revisionId: trimmedRevisionId } }
|
|
33186
33219
|
);
|
|
33187
33220
|
if (!result?.taskId) throw new Error("Start session enqueue failed: no taskId returned");
|
|
33188
|
-
return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33221
|
+
return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33189
33222
|
},
|
|
33190
33223
|
planRevisionBatch: async (projectId, revisionIds) => {
|
|
33191
33224
|
const result = await request(
|
|
@@ -33194,7 +33227,7 @@ function createHttpClient(config2) {
|
|
|
33194
33227
|
revisionIds ? { revisionIds } : {}
|
|
33195
33228
|
);
|
|
33196
33229
|
if (!result?.taskId) throw new Error("Plan revision batch enqueue failed: no taskId returned");
|
|
33197
|
-
return { taskId: result.taskId, taskType: "plan_revision_batch", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33230
|
+
return { taskId: result.taskId, taskType: "plan_revision_batch", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33198
33231
|
},
|
|
33199
33232
|
draftCoda: async (projectId, beatId) => {
|
|
33200
33233
|
const result = await request(
|
|
@@ -33203,7 +33236,7 @@ function createHttpClient(config2) {
|
|
|
33203
33236
|
{}
|
|
33204
33237
|
);
|
|
33205
33238
|
if (!result?.taskId) throw new Error("Draft coda enqueue failed: no taskId returned");
|
|
33206
|
-
return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33239
|
+
return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33207
33240
|
},
|
|
33208
33241
|
composeRootCoda: async (orgId) => {
|
|
33209
33242
|
const result = await request(
|
|
@@ -33212,7 +33245,7 @@ function createHttpClient(config2) {
|
|
|
33212
33245
|
{}
|
|
33213
33246
|
);
|
|
33214
33247
|
if (!result?.taskId) throw new Error("Compose root Coda enqueue failed: no taskId returned");
|
|
33215
|
-
return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending",
|
|
33248
|
+
return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", systemId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33216
33249
|
},
|
|
33217
33250
|
validateAssumptions: async (projectId, noteIds) => {
|
|
33218
33251
|
const result = await request(
|
|
@@ -33221,7 +33254,7 @@ function createHttpClient(config2) {
|
|
|
33221
33254
|
noteIds ? { noteIds } : {}
|
|
33222
33255
|
);
|
|
33223
33256
|
if (!result?.taskId) throw new Error("Assumption validation enqueue failed: no taskId returned");
|
|
33224
|
-
return { taskId: result.taskId, taskType: "validate_assumptions", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33257
|
+
return { taskId: result.taskId, taskType: "validate_assumptions", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33225
33258
|
},
|
|
33226
33259
|
runTriage: async (projectId) => {
|
|
33227
33260
|
const result = await request(
|
|
@@ -33230,7 +33263,7 @@ function createHttpClient(config2) {
|
|
|
33230
33263
|
{}
|
|
33231
33264
|
);
|
|
33232
33265
|
if (!result?.taskId) throw new Error("Triage enqueue failed: no taskId returned");
|
|
33233
|
-
return { taskId: result.taskId, taskType: "triage_loop", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33266
|
+
return { taskId: result.taskId, taskType: "triage_loop", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33234
33267
|
},
|
|
33235
33268
|
baselineProject: async (projectId, options) => {
|
|
33236
33269
|
const result = await request(
|
|
@@ -33239,7 +33272,7 @@ function createHttpClient(config2) {
|
|
|
33239
33272
|
{ beatIds: options?.beatIds, runChecks: options?.runChecks ?? true, localPath: options?.localPath }
|
|
33240
33273
|
);
|
|
33241
33274
|
if (!result?.taskId) throw new Error("Baseline enqueue failed: no taskId returned");
|
|
33242
|
-
return { taskId: result.taskId, taskType: "baseline_project", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33275
|
+
return { taskId: result.taskId, taskType: "baseline_project", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33243
33276
|
},
|
|
33244
33277
|
runCheck: async (projectId, checkType, targetId, options) => {
|
|
33245
33278
|
const result = await request(
|
|
@@ -33248,7 +33281,7 @@ function createHttpClient(config2) {
|
|
|
33248
33281
|
{ checkType, targetId, branch: options?.branch, localPath: options?.localPath }
|
|
33249
33282
|
);
|
|
33250
33283
|
if (!result?.taskId) throw new Error("Check enqueue failed: no taskId returned");
|
|
33251
|
-
return { taskId: result.taskId, taskType: "run_check", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33284
|
+
return { taskId: result.taskId, taskType: "run_check", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33252
33285
|
},
|
|
33253
33286
|
// Subscriptions
|
|
33254
33287
|
subscribe: async (userId, entityType, entityId, projectId, source) => {
|
|
@@ -33325,7 +33358,7 @@ function createHttpClient(config2) {
|
|
|
33325
33358
|
},
|
|
33326
33359
|
searchByText: async (scope, query, options) => {
|
|
33327
33360
|
const params = new URLSearchParams({ q: query });
|
|
33328
|
-
if ("
|
|
33361
|
+
if ("systemId" in scope) params.set("systemId", scope.systemId);
|
|
33329
33362
|
else if ("teamspaceId" in scope) params.set("teamspaceId", scope.teamspaceId);
|
|
33330
33363
|
else params.set("orgId", scope.orgId);
|
|
33331
33364
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -34161,7 +34194,7 @@ function loadConfig() {
|
|
|
34161
34194
|
};
|
|
34162
34195
|
}
|
|
34163
34196
|
async function main() {
|
|
34164
|
-
console.error(`[harmonica-mcp] v${"3.
|
|
34197
|
+
console.error(`[harmonica-mcp] v${"3.7.1"} starting\u2026`);
|
|
34165
34198
|
const config2 = loadConfig();
|
|
34166
34199
|
const client = createHttpClient({
|
|
34167
34200
|
apiBaseUrl: config2.apiBaseUrl,
|