@codazen/harmonica-mcp 3.6.1 → 3.7.0
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 +335 -335
- 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);
|
|
@@ -27255,7 +27255,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27255
27255
|
"list_notes",
|
|
27256
27256
|
"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
27257
|
{
|
|
27258
|
-
|
|
27258
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
27259
27259
|
beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project notes)"),
|
|
27260
27260
|
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
27261
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
|
|
@@ -27268,7 +27268,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27268
27268
|
cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response \u2014 use for project-wide note pagination (no beatId)"),
|
|
27269
27269
|
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
27270
|
},
|
|
27271
|
-
async ({
|
|
27271
|
+
async ({ systemId, beatId, noteType, status, revisionId, beatVersionId, sourceDocumentNoteId, significance, offset = 0, limit = 100, cursor, orderBy }) => {
|
|
27272
27272
|
if (Array.isArray(noteType) && noteType.length === 0) {
|
|
27273
27273
|
return { content: [{ type: "text", text: "noteType must not be an empty array \u2014 omit it to return all types." }], isError: true };
|
|
27274
27274
|
}
|
|
@@ -27280,7 +27280,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27280
27280
|
return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
|
|
27281
27281
|
}
|
|
27282
27282
|
try {
|
|
27283
|
-
await assertProjectInOrg(client,
|
|
27283
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27284
27284
|
} catch (err) {
|
|
27285
27285
|
const message = err instanceof Error ? err.message : String(err);
|
|
27286
27286
|
return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
|
|
@@ -27320,7 +27320,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27320
27320
|
const SOURCE_DOC_FETCH_LIMIT = 5e3;
|
|
27321
27321
|
let allNotesResult;
|
|
27322
27322
|
try {
|
|
27323
|
-
allNotesResult = await client.listAllSystemNotes(
|
|
27323
|
+
allNotesResult = await client.listAllSystemNotes(systemId, filters, SOURCE_DOC_FETCH_LIMIT);
|
|
27324
27324
|
} catch (err) {
|
|
27325
27325
|
const message = err instanceof Error ? err.message : String(err);
|
|
27326
27326
|
return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
|
|
@@ -27341,7 +27341,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27341
27341
|
} else {
|
|
27342
27342
|
let projectNotesResult;
|
|
27343
27343
|
try {
|
|
27344
|
-
projectNotesResult = await client.listAllSystemNotes(
|
|
27344
|
+
projectNotesResult = await client.listAllSystemNotes(systemId, filters, limit, cursor);
|
|
27345
27345
|
} catch (err) {
|
|
27346
27346
|
const message = err instanceof Error ? err.message : String(err);
|
|
27347
27347
|
return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
|
|
@@ -27423,12 +27423,12 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27423
27423
|
"list_documents",
|
|
27424
27424
|
"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
27425
|
{
|
|
27426
|
-
|
|
27426
|
+
systemId: external_exports.string().describe("The project ID")
|
|
27427
27427
|
},
|
|
27428
|
-
async ({
|
|
27429
|
-
await assertProjectInOrg(client,
|
|
27428
|
+
async ({ systemId }) => {
|
|
27429
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27430
27430
|
const DOCUMENT_FETCH_LIMIT = 5e3;
|
|
27431
|
-
const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(
|
|
27431
|
+
const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
|
|
27432
27432
|
const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
|
|
27433
27433
|
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
27434
|
const text = formatDocumentList(visibleDocs) + truncationNotice;
|
|
@@ -27439,16 +27439,16 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27439
27439
|
"get_note",
|
|
27440
27440
|
"Get full details of a single Note by ID",
|
|
27441
27441
|
{
|
|
27442
|
-
|
|
27443
|
-
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or
|
|
27442
|
+
systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
|
|
27443
|
+
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
|
|
27444
27444
|
noteId: external_exports.string().describe("The note ID")
|
|
27445
27445
|
},
|
|
27446
|
-
async ({
|
|
27447
|
-
if (
|
|
27448
|
-
return { content: [{ type: "text", text: "Provide
|
|
27446
|
+
async ({ systemId, orgId, noteId }) => {
|
|
27447
|
+
if (systemId && orgId) {
|
|
27448
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
|
|
27449
27449
|
}
|
|
27450
|
-
if (!
|
|
27451
|
-
return { content: [{ type: "text", text: "Provide
|
|
27450
|
+
if (!systemId && !orgId) {
|
|
27451
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
|
|
27452
27452
|
}
|
|
27453
27453
|
try {
|
|
27454
27454
|
if (orgId) {
|
|
@@ -27461,7 +27461,7 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27461
27461
|
}
|
|
27462
27462
|
return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
|
|
27463
27463
|
}
|
|
27464
|
-
await assertProjectInOrg(client,
|
|
27464
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27465
27465
|
const note = await client.getNote(noteId);
|
|
27466
27466
|
if (!note) {
|
|
27467
27467
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
@@ -27476,15 +27476,15 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27476
27476
|
);
|
|
27477
27477
|
server.tool(
|
|
27478
27478
|
"create_note",
|
|
27479
|
-
"Create a new Note. Provide exactly one of
|
|
27479
|
+
"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
27480
|
{
|
|
27481
|
-
|
|
27481
|
+
systemId: external_exports.string().optional().describe("The project ID (omit for teamspace-, movement-, or org-level note)"),
|
|
27482
27482
|
teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for project-, movement-, or org-level note)"),
|
|
27483
27483
|
movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for project-, teamspace-, or org-level note)"),
|
|
27484
27484
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).describe("The type of note"),
|
|
27485
27485
|
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
|
|
27486
|
+
beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires systemId)"),
|
|
27487
|
+
revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires systemId)"),
|
|
27488
27488
|
beatVersionId: external_exports.string().optional().describe("Beat Version ID if this note governs a specific planning increment (requires beatId)"),
|
|
27489
27489
|
rationale: external_exports.string().optional().describe("Why this note exists"),
|
|
27490
27490
|
confidence: external_exports.coerce.number().min(0).max(1).optional().describe("Confidence level for assumptions (0-1)"),
|
|
@@ -27492,20 +27492,20 @@ function registerNoteTools(server, ctx, client) {
|
|
|
27492
27492
|
dependsOnNotes: external_exports.array(external_exports.string()).optional().describe("Note IDs this note depends on (child \u2192 parent links for consolidation)"),
|
|
27493
27493
|
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
27494
|
},
|
|
27495
|
-
async ({
|
|
27495
|
+
async ({ systemId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, confidence, affectsBeats, dependsOnNotes, significance }) => {
|
|
27496
27496
|
try {
|
|
27497
|
-
if ([
|
|
27498
|
-
return { content: [{ type: "text", text: "Specify at most one of
|
|
27497
|
+
if ([systemId, teamspaceId, movementId].filter(Boolean).length > 1) {
|
|
27498
|
+
return { content: [{ type: "text", text: "Specify at most one of systemId, teamspaceId, or movementId." }], isError: true };
|
|
27499
27499
|
}
|
|
27500
27500
|
const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
|
|
27501
27501
|
let note;
|
|
27502
|
-
if (
|
|
27503
|
-
await assertProjectInOrg(client,
|
|
27504
|
-
const projectCode =
|
|
27505
|
-
const noteId = await client.getNextNoteId(projectCode,
|
|
27502
|
+
if (systemId) {
|
|
27503
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27504
|
+
const projectCode = systemId.substring(0, 4).toUpperCase();
|
|
27505
|
+
const noteId = await client.getNextNoteId(projectCode, systemId);
|
|
27506
27506
|
note = await client.createNote({
|
|
27507
27507
|
noteId,
|
|
27508
|
-
projectId,
|
|
27508
|
+
projectId: systemId,
|
|
27509
27509
|
beatId,
|
|
27510
27510
|
revisionId,
|
|
27511
27511
|
beatVersionId,
|
|
@@ -27577,8 +27577,8 @@ ${text}` }] };
|
|
|
27577
27577
|
"update_note",
|
|
27578
27578
|
"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
27579
|
{
|
|
27580
|
-
|
|
27581
|
-
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or
|
|
27580
|
+
systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
|
|
27581
|
+
orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
|
|
27582
27582
|
noteId: external_exports.string().describe("The note ID to update"),
|
|
27583
27583
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. assumption \u2192 guidance)"),
|
|
27584
27584
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status"),
|
|
@@ -27592,13 +27592,13 @@ ${text}` }] };
|
|
|
27592
27592
|
agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or reassign this note to an agent (null to clear)"),
|
|
27593
27593
|
significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Only meaningful on noteType=decision.")
|
|
27594
27594
|
},
|
|
27595
|
-
async ({
|
|
27595
|
+
async ({ systemId, orgId, noteId, noteType, status, content, rationale, response, beatId, revisionId, dependsOnNotes, humanAssignee, agentAssignee, significance }) => {
|
|
27596
27596
|
try {
|
|
27597
|
-
if (
|
|
27598
|
-
return { content: [{ type: "text", text: "Provide
|
|
27597
|
+
if (systemId && orgId) {
|
|
27598
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
|
|
27599
27599
|
}
|
|
27600
|
-
if (!
|
|
27601
|
-
return { content: [{ type: "text", text: "Provide
|
|
27600
|
+
if (!systemId && !orgId) {
|
|
27601
|
+
return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
|
|
27602
27602
|
}
|
|
27603
27603
|
if (orgId) {
|
|
27604
27604
|
if (orgId !== ctx.orgId) {
|
|
@@ -27628,7 +27628,7 @@ ${text}` }] };
|
|
|
27628
27628
|
|
|
27629
27629
|
${formatNoteDetail(updated2)}` }] };
|
|
27630
27630
|
}
|
|
27631
|
-
await assertProjectInOrg(client,
|
|
27631
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27632
27632
|
const updated = await client.updateNote(noteId, {
|
|
27633
27633
|
...noteType !== void 0 && { noteType },
|
|
27634
27634
|
status,
|
|
@@ -27659,7 +27659,7 @@ ${text}` }] };
|
|
|
27659
27659
|
"bulk_update_notes",
|
|
27660
27660
|
"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
27661
|
{
|
|
27662
|
-
|
|
27662
|
+
systemId: external_exports.string().describe("The project ID (for access control)"),
|
|
27663
27663
|
noteIds: external_exports.array(external_exports.string()).min(1).max(100).describe("Note IDs to update (max 100 per call)"),
|
|
27664
27664
|
noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify all notes to this type"),
|
|
27665
27665
|
status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status for all notes"),
|
|
@@ -27668,9 +27668,9 @@ ${text}` }] };
|
|
|
27668
27668
|
humanAssignee: humanAssigneeSchema.nullable().optional().describe("Assign or clear human assignee on all notes (null to clear)"),
|
|
27669
27669
|
agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or clear agent assignee on all notes (null to clear)")
|
|
27670
27670
|
},
|
|
27671
|
-
async ({
|
|
27671
|
+
async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
|
|
27672
27672
|
try {
|
|
27673
|
-
await assertProjectInOrg(client,
|
|
27673
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27674
27674
|
const result = await client.bulkUpdateNotes(noteIds, {
|
|
27675
27675
|
...noteType !== void 0 && { noteType },
|
|
27676
27676
|
status,
|
|
@@ -27701,15 +27701,15 @@ ${text}` }] };
|
|
|
27701
27701
|
"reassign_note",
|
|
27702
27702
|
"Reassign a misattributed note to a different beat (with audit trail)",
|
|
27703
27703
|
{
|
|
27704
|
-
|
|
27704
|
+
systemId: external_exports.string().describe("The project ID (for access control)"),
|
|
27705
27705
|
noteId: external_exports.string().describe("The note ID to reassign"),
|
|
27706
27706
|
targetBeatId: external_exports.string().describe("The beat ID to move the note to"),
|
|
27707
27707
|
targetRevisionId: external_exports.string().optional().describe("Optional revision ID to scope the note to"),
|
|
27708
27708
|
reason: external_exports.string().describe("Why this note is being reassigned")
|
|
27709
27709
|
},
|
|
27710
|
-
async ({
|
|
27710
|
+
async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
|
|
27711
27711
|
try {
|
|
27712
|
-
await assertProjectInOrg(client,
|
|
27712
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27713
27713
|
const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
|
|
27714
27714
|
if (!updated) {
|
|
27715
27715
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
@@ -27725,15 +27725,15 @@ ${text}` }] };
|
|
|
27725
27725
|
"remove_note",
|
|
27726
27726
|
"Soft-remove a misattributed note by dismissing it (with audit trail)",
|
|
27727
27727
|
{
|
|
27728
|
-
|
|
27728
|
+
systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
|
|
27729
27729
|
noteId: external_exports.string().min(1).describe("The note ID to remove"),
|
|
27730
27730
|
reason: external_exports.string().min(1).max(500).optional().describe('Why this note is being removed (defaults to "Removed via remove_note")')
|
|
27731
27731
|
},
|
|
27732
|
-
async ({
|
|
27732
|
+
async ({ systemId, noteId, reason }) => {
|
|
27733
27733
|
try {
|
|
27734
|
-
await assertProjectInOrg(client,
|
|
27734
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27735
27735
|
const note = await client.getNote(noteId);
|
|
27736
|
-
if (!note || note.projectId !==
|
|
27736
|
+
if (!note || (note.systemId ?? note.projectId) !== systemId) {
|
|
27737
27737
|
return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
|
|
27738
27738
|
}
|
|
27739
27739
|
const removeReason = reason ?? "Removed via remove_note";
|
|
@@ -27752,15 +27752,15 @@ ${text}` }] };
|
|
|
27752
27752
|
"dismiss_document",
|
|
27753
27753
|
"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
27754
|
{
|
|
27755
|
-
|
|
27755
|
+
systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
|
|
27756
27756
|
noteId: external_exports.string().min(1).describe("The document note ID to dismiss"),
|
|
27757
27757
|
reason: external_exports.string().min(1).max(500).optional().describe('Why this document is being dismissed (defaults to "Dismissed via dismiss_document")')
|
|
27758
27758
|
},
|
|
27759
|
-
async ({
|
|
27759
|
+
async ({ systemId, noteId, reason }) => {
|
|
27760
27760
|
try {
|
|
27761
|
-
await assertProjectInOrg(client,
|
|
27761
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27762
27762
|
const note = await client.getNote(noteId);
|
|
27763
|
-
if (!note || note.projectId !==
|
|
27763
|
+
if (!note || (note.systemId ?? note.projectId) !== systemId) {
|
|
27764
27764
|
return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
|
|
27765
27765
|
}
|
|
27766
27766
|
if (note.noteType !== "document") {
|
|
@@ -27785,11 +27785,11 @@ ${text}` }] };
|
|
|
27785
27785
|
"list_key_assumptions",
|
|
27786
27786
|
"Get key assumptions sorted by criticality (highest risk first)",
|
|
27787
27787
|
{
|
|
27788
|
-
|
|
27788
|
+
systemId: external_exports.string().describe("The project ID")
|
|
27789
27789
|
},
|
|
27790
|
-
async ({
|
|
27791
|
-
await assertProjectInOrg(client,
|
|
27792
|
-
const assumptions = await client.getKeyAssumptions(
|
|
27790
|
+
async ({ systemId }) => {
|
|
27791
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
27792
|
+
const assumptions = await client.getKeyAssumptions(systemId);
|
|
27793
27793
|
if (assumptions.length === 0) {
|
|
27794
27794
|
return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
|
|
27795
27795
|
}
|
|
@@ -28051,12 +28051,12 @@ ${formatTree(tree.root)}` }] };
|
|
|
28051
28051
|
heading: external_exports.string().min(1).max(200).describe('Section label, e.g. "Decision Log"'),
|
|
28052
28052
|
parentNoteId: external_exports.string().optional().describe("Parent page's Note ID; defaults to the notebook root"),
|
|
28053
28053
|
noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("Note type to collect (default: decision)"),
|
|
28054
|
-
|
|
28054
|
+
systemId: external_exports.string().optional().describe("Project scope; if omitted, the Notebook's first bound project scope is used"),
|
|
28055
28055
|
limit: external_exports.number().int().min(1).max(500).optional().describe("Max members to show (default: 25)"),
|
|
28056
28056
|
order: external_exports.enum(["newest", "oldest"]).optional().describe("Sort order (default: newest)"),
|
|
28057
28057
|
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
28058
|
},
|
|
28059
|
-
async ({ notebookId, heading, parentNoteId, noteType,
|
|
28059
|
+
async ({ notebookId, heading, parentNoteId, noteType, systemId, limit, order, significance }) => {
|
|
28060
28060
|
let notebook;
|
|
28061
28061
|
try {
|
|
28062
28062
|
notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
|
|
@@ -28069,7 +28069,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28069
28069
|
isError: true
|
|
28070
28070
|
};
|
|
28071
28071
|
}
|
|
28072
|
-
let scopeProjectId =
|
|
28072
|
+
let scopeProjectId = systemId;
|
|
28073
28073
|
if (!scopeProjectId) {
|
|
28074
28074
|
try {
|
|
28075
28075
|
const bindings = await client.getNotebookScopeBindings(notebookId);
|
|
@@ -28078,7 +28078,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28078
28078
|
}
|
|
28079
28079
|
}
|
|
28080
28080
|
if (!scopeProjectId) {
|
|
28081
|
-
return { content: [{ type: "text", text: "No project scope: pass
|
|
28081
|
+
return { content: [{ type: "text", text: "No project scope: pass systemId or bind the notebook to a project first." }], isError: true };
|
|
28082
28082
|
}
|
|
28083
28083
|
let tree;
|
|
28084
28084
|
try {
|
|
@@ -28139,12 +28139,12 @@ ${formatTree(tree.root)}` }] };
|
|
|
28139
28139
|
parentNoteId: external_exports.string().optional().describe("The page Note ID the section sits under; defaults to the notebook root"),
|
|
28140
28140
|
heading: external_exports.string().min(1).max(200).optional().describe("New section label"),
|
|
28141
28141
|
noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("New note type to collect"),
|
|
28142
|
-
|
|
28142
|
+
systemId: external_exports.string().optional().describe("New project scope"),
|
|
28143
28143
|
limit: external_exports.number().int().min(1).max(500).optional().describe("New max members to show"),
|
|
28144
28144
|
order: external_exports.enum(["newest", "oldest"]).optional().describe("New sort order"),
|
|
28145
28145
|
significance: external_exports.enum(["strategic", "structural", "implementation"]).optional().describe("New significance-tier filter (decision notes only)")
|
|
28146
28146
|
},
|
|
28147
|
-
async ({ notebookId, elementId, parentNoteId, heading, noteType,
|
|
28147
|
+
async ({ notebookId, elementId, parentNoteId, heading, noteType, systemId, limit, order, significance }) => {
|
|
28148
28148
|
let notebook;
|
|
28149
28149
|
try {
|
|
28150
28150
|
notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
|
|
@@ -28183,7 +28183,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28183
28183
|
const base = existing.query;
|
|
28184
28184
|
const mergedQuery = {
|
|
28185
28185
|
source: "notes",
|
|
28186
|
-
scope:
|
|
28186
|
+
scope: systemId !== void 0 ? `project:${systemId}` : base.scope,
|
|
28187
28187
|
noteType: noteType ?? base.noteType,
|
|
28188
28188
|
limit: limit ?? base.limit,
|
|
28189
28189
|
sortBy: order !== void 0 ? order === "oldest" ? "createdAt_asc" : "createdAt_desc" : base.sortBy,
|
|
@@ -28485,14 +28485,14 @@ function registerOnboardingTools(server, ctx, client) {
|
|
|
28485
28485
|
"The PM reviews and confirms \u2014 use create_beat for each approved Beat, create_note for any Note conversions."
|
|
28486
28486
|
].join(" "),
|
|
28487
28487
|
{
|
|
28488
|
-
|
|
28488
|
+
systemId: external_exports.string().describe("The newly created project ID"),
|
|
28489
28489
|
projectTitle: external_exports.string().describe("The project title"),
|
|
28490
28490
|
projectDescription: external_exports.string().optional().describe("Discovery context: pain points, goals, and constraints summary to inform the Beat structure"),
|
|
28491
28491
|
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
28492
|
},
|
|
28493
|
-
async ({
|
|
28494
|
-
await assertProjectInOrg(client,
|
|
28495
|
-
const result = await client.draftOnboardingBeats(
|
|
28493
|
+
async ({ systemId, projectTitle, projectDescription, analysisJson }) => {
|
|
28494
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
28495
|
+
const result = await client.draftOnboardingBeats(systemId, { projectTitle, projectDescription, analysisJson });
|
|
28496
28496
|
if (!result.suggestions || result.suggestions.length === 0) {
|
|
28497
28497
|
return {
|
|
28498
28498
|
content: [{ type: "text", text: "Failed to generate Beat suggestions. Try providing more project context." }]
|
|
@@ -28558,14 +28558,14 @@ Error: ${msg}`);
|
|
|
28558
28558
|
for (const p of projects) {
|
|
28559
28559
|
try {
|
|
28560
28560
|
const project = await client.createSystem({
|
|
28561
|
-
|
|
28561
|
+
systemId: (0, import_node_crypto.randomUUID)(),
|
|
28562
28562
|
orgId: ctx.orgId,
|
|
28563
28563
|
teamspaceId: teamspace.teamspaceId,
|
|
28564
28564
|
ownerUserId: ctx.user?.userId ?? "system",
|
|
28565
28565
|
title: p.title,
|
|
28566
28566
|
description: p.description
|
|
28567
28567
|
});
|
|
28568
|
-
createdProjects.push({
|
|
28568
|
+
createdProjects.push({ systemId: project.systemId, title: project.title });
|
|
28569
28569
|
} catch (err) {
|
|
28570
28570
|
const msg = err instanceof Error ? err.message : String(err);
|
|
28571
28571
|
lines.push(`
|
|
@@ -28578,7 +28578,7 @@ Error: ${msg}`);
|
|
|
28578
28578
|
if (createdProjects.length > 0) {
|
|
28579
28579
|
lines.push(`
|
|
28580
28580
|
Projects (${createdProjects.length}):`);
|
|
28581
|
-
for (const p of createdProjects) lines.push(` [${p.
|
|
28581
|
+
for (const p of createdProjects) lines.push(` [${p.systemId}] ${p.title}`);
|
|
28582
28582
|
}
|
|
28583
28583
|
const createdNotes = [];
|
|
28584
28584
|
const teamspaceSlug = teamspace.slug ?? teamspaceName.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -28604,7 +28604,7 @@ Projects (${createdProjects.length}):`);
|
|
|
28604
28604
|
const n = await client.createNote({
|
|
28605
28605
|
noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
|
|
28606
28606
|
// overridden server-side
|
|
28607
|
-
projectId: project.
|
|
28607
|
+
projectId: project.systemId,
|
|
28608
28608
|
noteType: note.noteType,
|
|
28609
28609
|
content: note.content,
|
|
28610
28610
|
rationale: note.rationale,
|
|
@@ -28802,7 +28802,7 @@ function registerOrganizationTools(server, ctx, client) {
|
|
|
28802
28802
|
}
|
|
28803
28803
|
const text = `System transferred.
|
|
28804
28804
|
|
|
28805
|
-
**System ID:** ${transferred.
|
|
28805
|
+
**System ID:** ${transferred.systemId}
|
|
28806
28806
|
**Title:** ${transferred.title}
|
|
28807
28807
|
**New Org:** ${transferred.orgId}`;
|
|
28808
28808
|
return { content: [{ type: "text", text }] };
|
|
@@ -28926,7 +28926,7 @@ ${d.rationale}${d.suggestions?.length ? `
|
|
|
28926
28926
|
function registerPlanQualityTools(server, ctx, client) {
|
|
28927
28927
|
const schema = {
|
|
28928
28928
|
beatVersionId: external_exports.string().describe("The Beat Version ID (e.g., bv-abc123). beat_version_quality targets Beat Versions only."),
|
|
28929
|
-
|
|
28929
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
28930
28930
|
// The completeness dimension probes the codebase, so this tool accepts the
|
|
28931
28931
|
// same repo overrides as run_check — without them the probe could only ever
|
|
28932
28932
|
// read the default branch from this surface.
|
|
@@ -28934,7 +28934,7 @@ function registerPlanQualityTools(server, ctx, client) {
|
|
|
28934
28934
|
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
28935
|
wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
|
|
28936
28936
|
};
|
|
28937
|
-
const handler = async ({ beatVersionId,
|
|
28937
|
+
const handler = async ({ beatVersionId, systemId, branch, localPath, wait }) => {
|
|
28938
28938
|
try {
|
|
28939
28939
|
const targetError = beatVersionQualityTargetError(beatVersionId ?? "");
|
|
28940
28940
|
if (targetError) {
|
|
@@ -28943,9 +28943,9 @@ function registerPlanQualityTools(server, ctx, client) {
|
|
|
28943
28943
|
isError: true
|
|
28944
28944
|
};
|
|
28945
28945
|
}
|
|
28946
|
-
await assertProjectInOrg(client,
|
|
28946
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
28947
28947
|
const opts = branch || localPath ? { branch, localPath } : void 0;
|
|
28948
|
-
const task = await client.runCheck(
|
|
28948
|
+
const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
|
|
28949
28949
|
if (!wait) {
|
|
28950
28950
|
return {
|
|
28951
28951
|
content: [{
|
|
@@ -29085,22 +29085,22 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29085
29085
|
"check_portfolio_coherence",
|
|
29086
29086
|
"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
29087
|
{
|
|
29088
|
-
|
|
29088
|
+
systemId: external_exports.string().min(1).describe("The project ID"),
|
|
29089
29089
|
wait: external_exports.boolean().optional().describe("Block until the check completes and return the report inline. Default: false.")
|
|
29090
29090
|
},
|
|
29091
|
-
async ({
|
|
29091
|
+
async ({ systemId, wait }) => {
|
|
29092
29092
|
try {
|
|
29093
|
-
await assertProjectInOrg(client,
|
|
29094
|
-
const { taskId } = await client.runCheck(
|
|
29093
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29094
|
+
const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
|
|
29095
29095
|
if (!wait) {
|
|
29096
29096
|
return {
|
|
29097
29097
|
content: [{
|
|
29098
29098
|
type: "text",
|
|
29099
29099
|
text: [
|
|
29100
|
-
`Portfolio coherence check enqueued for ${
|
|
29100
|
+
`Portfolio coherence check enqueued for ${systemId}.`,
|
|
29101
29101
|
`Task ID: ${taskId}`,
|
|
29102
29102
|
"",
|
|
29103
|
-
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with
|
|
29103
|
+
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with systemId="${systemId}" and checkType="portfolio_coherence" to view results.`
|
|
29104
29104
|
].join("\n")
|
|
29105
29105
|
}]
|
|
29106
29106
|
};
|
|
@@ -29117,17 +29117,17 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29117
29117
|
"consolidate_beats",
|
|
29118
29118
|
"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
29119
|
{
|
|
29120
|
-
|
|
29120
|
+
systemId: external_exports.string().min(1).describe("The project ID \u2014 all beatIds must belong to this project"),
|
|
29121
29121
|
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
29122
|
primaryBeatId: external_exports.string().min(1).optional().describe("Beat ID that survives \u2014 defaults to beatIds[0] if omitted"),
|
|
29123
29123
|
rationale: external_exports.string().max(2e3).optional().describe("Why these Beats are being merged \u2014 auto-generated if omitted"),
|
|
29124
29124
|
confirm: external_exports.boolean().optional().describe("false/omitted = preview; true = execute the merge")
|
|
29125
29125
|
},
|
|
29126
|
-
async ({
|
|
29126
|
+
async ({ systemId, beatIds, primaryBeatId, rationale, confirm }) => {
|
|
29127
29127
|
const archivedBeatIds = [];
|
|
29128
29128
|
let totalNotesReassigned = 0;
|
|
29129
29129
|
try {
|
|
29130
|
-
await assertProjectInOrg(client,
|
|
29130
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29131
29131
|
const uniqueBeatIds = [...new Set(beatIds)];
|
|
29132
29132
|
const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
|
|
29133
29133
|
if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
|
|
@@ -29143,19 +29143,19 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29143
29143
|
isError: true
|
|
29144
29144
|
};
|
|
29145
29145
|
}
|
|
29146
|
-
const allProjectBeats = await client.listSystemBeats(
|
|
29146
|
+
const allProjectBeats = await client.listSystemBeats(systemId);
|
|
29147
29147
|
const beatMap = new Map(allProjectBeats.map((b) => [b.beatId, b]));
|
|
29148
29148
|
const primaryBeat = beatMap.get(resolvedPrimaryId);
|
|
29149
29149
|
if (!primaryBeat) {
|
|
29150
|
-
return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${
|
|
29150
|
+
return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${systemId}".` }], isError: true };
|
|
29151
29151
|
}
|
|
29152
29152
|
const missingIds = duplicateBeatIds.filter((id) => !beatMap.has(id));
|
|
29153
29153
|
if (missingIds.length > 0) {
|
|
29154
|
-
return { content: [{ type: "text", text: `Beat(s) not found in project "${
|
|
29154
|
+
return { content: [{ type: "text", text: `Beat(s) not found in project "${systemId}": ${missingIds.join(", ")}` }], isError: true };
|
|
29155
29155
|
}
|
|
29156
29156
|
const dupBvResults = await Promise.allSettled(
|
|
29157
29157
|
duplicateBeatIds.map(
|
|
29158
|
-
(id) => client.listBeatVersions(
|
|
29158
|
+
(id) => client.listBeatVersions(systemId, { beatId: id, includeTerminal: false })
|
|
29159
29159
|
)
|
|
29160
29160
|
);
|
|
29161
29161
|
const bvRejected = dupBvResults.filter((r) => r.status === "rejected");
|
|
@@ -29328,11 +29328,11 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29328
29328
|
if (!parentBv) {
|
|
29329
29329
|
return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
|
|
29330
29330
|
}
|
|
29331
|
-
if (parentBv.beatId !== beatId ||
|
|
29331
|
+
if (parentBv.beatId !== beatId || parentBv.systemId !== beat.systemId) {
|
|
29332
29332
|
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
|
|
29333
29333
|
}
|
|
29334
29334
|
}
|
|
29335
|
-
const { revision, warnings } = await client.createRevision(beat.systemId
|
|
29335
|
+
const { revision, warnings } = await client.createRevision(beat.systemId, beatId, {
|
|
29336
29336
|
title,
|
|
29337
29337
|
description,
|
|
29338
29338
|
changeSummary,
|
|
@@ -29364,7 +29364,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29364
29364
|
}
|
|
29365
29365
|
if (parentBv && !parentBv.targetDropId) {
|
|
29366
29366
|
try {
|
|
29367
|
-
const project = await client.getSystem(parentBv.
|
|
29367
|
+
const project = await client.getSystem(parentBv.systemId);
|
|
29368
29368
|
const accountId = project?.accountId;
|
|
29369
29369
|
if (accountId) {
|
|
29370
29370
|
const draftDrops = await client.listAccountDrops(accountId, { state: "draft" });
|
|
@@ -29420,7 +29420,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
|
|
|
29420
29420
|
if (!bv) {
|
|
29421
29421
|
return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
|
|
29422
29422
|
}
|
|
29423
|
-
if (bv.beatId !== revision.beatId ||
|
|
29423
|
+
if (bv.beatId !== revision.beatId || bv.systemId !== revision.systemId) {
|
|
29424
29424
|
return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
|
|
29425
29425
|
}
|
|
29426
29426
|
}
|
|
@@ -29589,7 +29589,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
|
|
|
29589
29589
|
if (revision.scm) {
|
|
29590
29590
|
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
29591
|
} else {
|
|
29592
|
-
const project = await client.getSystem(revision.
|
|
29592
|
+
const project = await client.getSystem(revision.systemId);
|
|
29593
29593
|
if (!project?.repoOwner || !project?.repoName || !project?.repoDefaultBranch) {
|
|
29594
29594
|
lines2.push("", "\u26A0 The project has no repo configured (repoOwner/repoName/repoDefaultBranch). create_pr_for_revision would be rejected.");
|
|
29595
29595
|
} else {
|
|
@@ -29852,10 +29852,10 @@ function registerRevisionQualityTools(server, ctx, client) {
|
|
|
29852
29852
|
"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
29853
|
{
|
|
29854
29854
|
revisionId: external_exports.string().describe("The Revision ID (e.g., rev-abc123). revision_quality targets Revisions only."),
|
|
29855
|
-
|
|
29855
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
29856
29856
|
wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
|
|
29857
29857
|
},
|
|
29858
|
-
async ({ revisionId,
|
|
29858
|
+
async ({ revisionId, systemId, wait }) => {
|
|
29859
29859
|
try {
|
|
29860
29860
|
const targetError = revisionQualityTargetError(revisionId);
|
|
29861
29861
|
if (targetError) {
|
|
@@ -29864,9 +29864,9 @@ function registerRevisionQualityTools(server, ctx, client) {
|
|
|
29864
29864
|
isError: true
|
|
29865
29865
|
};
|
|
29866
29866
|
}
|
|
29867
|
-
await assertProjectInOrg(client,
|
|
29868
|
-
await assertRevisionInProject(client, revisionId,
|
|
29869
|
-
const task = await client.runCheck(
|
|
29867
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29868
|
+
await assertRevisionInProject(client, revisionId, systemId);
|
|
29869
|
+
const task = await client.runCheck(systemId, "revision_quality", revisionId);
|
|
29870
29870
|
if (!wait) {
|
|
29871
29871
|
return {
|
|
29872
29872
|
content: [{
|
|
@@ -30330,10 +30330,10 @@ async function resolveSnapshotInput(input) {
|
|
|
30330
30330
|
}
|
|
30331
30331
|
function formatImportSummary(result, targetTeamspaceId) {
|
|
30332
30332
|
if (!result.counts || result.counts.beats == null) {
|
|
30333
|
-
throw new Error(`Import of system ${result.
|
|
30333
|
+
throw new Error(`Import of system ${result.systemId} completed but returned no counts \u2014 check worker logs for a TransactionCanceledException`);
|
|
30334
30334
|
}
|
|
30335
30335
|
const lines = [
|
|
30336
|
-
`Imported system: ${result.
|
|
30336
|
+
`Imported system: ${result.systemId}`,
|
|
30337
30337
|
` Beats: ${result.counts.beats}`,
|
|
30338
30338
|
` Proposals: ${result.counts.proposals}`,
|
|
30339
30339
|
` Revisions: ${result.counts.revisions}`,
|
|
@@ -30400,8 +30400,8 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30400
30400
|
throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
|
|
30401
30401
|
}
|
|
30402
30402
|
const systemOrProject = obj["system"] ?? obj["project"];
|
|
30403
|
-
if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["
|
|
30404
|
-
throw new Error(`Snapshot${sourceHint} is missing 'system.
|
|
30403
|
+
if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["systemId"] !== "string") {
|
|
30404
|
+
throw new Error(`Snapshot${sourceHint} is missing 'system.systemId' \u2014 it may not be a valid project snapshot.`);
|
|
30405
30405
|
}
|
|
30406
30406
|
}
|
|
30407
30407
|
|
|
@@ -30414,12 +30414,12 @@ function registerSubscriptionTools(server, ctx, client) {
|
|
|
30414
30414
|
{
|
|
30415
30415
|
entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
|
|
30416
30416
|
entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
|
|
30417
|
-
|
|
30417
|
+
systemId: external_exports.string().describe("The project ID (for access control)")
|
|
30418
30418
|
},
|
|
30419
|
-
async ({ entityType, entityId,
|
|
30419
|
+
async ({ entityType, entityId, systemId }) => {
|
|
30420
30420
|
try {
|
|
30421
|
-
await assertProjectInOrg(client,
|
|
30422
|
-
await client.subscribe(ctx.user.userId, entityType, entityId,
|
|
30421
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
30422
|
+
await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
|
|
30423
30423
|
return {
|
|
30424
30424
|
content: [{
|
|
30425
30425
|
type: "text",
|
|
@@ -30653,7 +30653,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30653
30653
|
const lines = [
|
|
30654
30654
|
`System updated successfully.`,
|
|
30655
30655
|
"",
|
|
30656
|
-
`**ID:** ${updated.
|
|
30656
|
+
`**ID:** ${updated.systemId}`,
|
|
30657
30657
|
`**Title:** ${updated.title}`,
|
|
30658
30658
|
accountLine(nonEmpty, updated.accountId),
|
|
30659
30659
|
updated.strategy ? `**Strategy:** (updated)` : "",
|
|
@@ -30690,7 +30690,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30690
30690
|
text: [
|
|
30691
30691
|
"System archived successfully.",
|
|
30692
30692
|
"",
|
|
30693
|
-
`**ID:** ${updated.
|
|
30693
|
+
`**ID:** ${updated.systemId}`,
|
|
30694
30694
|
`**Title:** ${updated.title}`,
|
|
30695
30695
|
`**Status:** ${updated.status}`
|
|
30696
30696
|
].join("\n")
|
|
@@ -30725,7 +30725,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30725
30725
|
const account = normalizeCreateAccountId(accountId);
|
|
30726
30726
|
try {
|
|
30727
30727
|
const project = await client.createSystem({
|
|
30728
|
-
|
|
30728
|
+
systemId: (0, import_crypto5.randomUUID)(),
|
|
30729
30729
|
orgId: ctx.orgId,
|
|
30730
30730
|
ownerUserId: ctx.user.userId,
|
|
30731
30731
|
title,
|
|
@@ -30741,7 +30741,7 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30741
30741
|
const text = [
|
|
30742
30742
|
`System created successfully.`,
|
|
30743
30743
|
"",
|
|
30744
|
-
`**ID:** ${project.
|
|
30744
|
+
`**ID:** ${project.systemId}`,
|
|
30745
30745
|
`**Title:** ${project.title}`,
|
|
30746
30746
|
`**Status:** ${project.status}`,
|
|
30747
30747
|
project.accountId ? `**Account:** ${project.accountId}` : "",
|
|
@@ -30811,7 +30811,7 @@ function registerTeamspaceTools(server, ctx, client) {
|
|
|
30811
30811
|
];
|
|
30812
30812
|
if (tsProjects.length > 0) {
|
|
30813
30813
|
lines.push("", `Projects (${tsProjects.length}):`);
|
|
30814
|
-
for (const p of tsProjects) lines.push(` [${p.
|
|
30814
|
+
for (const p of tsProjects) lines.push(` [${p.systemId}] ${p.title} \u2014 ${p.status ?? "unknown"}`);
|
|
30815
30815
|
} else {
|
|
30816
30816
|
lines.push("Projects: (none)");
|
|
30817
30817
|
}
|
|
@@ -31073,23 +31073,23 @@ function registerValueVelocityTools(server, ctx, client) {
|
|
|
31073
31073
|
"propose_enablement_edges",
|
|
31074
31074
|
'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
31075
|
{
|
|
31076
|
-
|
|
31076
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31077
31077
|
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
31078
|
},
|
|
31079
|
-
async ({
|
|
31079
|
+
async ({ systemId, beatId }) => {
|
|
31080
31080
|
try {
|
|
31081
|
-
await assertProjectInOrg(client,
|
|
31081
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31082
31082
|
const actorId = resolveActorId(ctx);
|
|
31083
31083
|
if (!actorId) {
|
|
31084
31084
|
return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
|
|
31085
31085
|
}
|
|
31086
31086
|
if (beatId !== void 0) await assertBeatInOrg(client, beatId, ctx.orgId);
|
|
31087
|
-
const proposed = await client.proposeEnablementEdges(
|
|
31087
|
+
const proposed = await client.proposeEnablementEdges(systemId, actorId, beatId);
|
|
31088
31088
|
if (proposed.length === 0) {
|
|
31089
31089
|
return { content: [{ type: "text", text: "No enablement edges proposed (fewer than 2 Beats or no relationships found)." }] };
|
|
31090
31090
|
}
|
|
31091
31091
|
const lines = [
|
|
31092
|
-
`Proposed ${proposed.length} enablement edge(s) for project ${
|
|
31092
|
+
`Proposed ${proposed.length} enablement edge(s) for project ${systemId}${beatId ? ` scoped to ${beatId}` : ""}:`,
|
|
31093
31093
|
"",
|
|
31094
31094
|
"| Source Beat | Target Beat | Rationale |",
|
|
31095
31095
|
"|-------------|------------|-----------|"
|
|
@@ -31461,13 +31461,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31461
31461
|
"list_tasks",
|
|
31462
31462
|
"DEPRECATED: use `list_jobs`. List background jobs for a project with optional status filter.",
|
|
31463
31463
|
{
|
|
31464
|
-
|
|
31464
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31465
31465
|
status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
|
|
31466
31466
|
limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
|
|
31467
31467
|
},
|
|
31468
|
-
async ({
|
|
31469
|
-
await assertProjectInOrg(client,
|
|
31470
|
-
const tasks = await client.listSystemTasks(
|
|
31468
|
+
async ({ systemId, status, limit }) => {
|
|
31469
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31470
|
+
const tasks = await client.listSystemTasks(systemId, {
|
|
31471
31471
|
status,
|
|
31472
31472
|
limit
|
|
31473
31473
|
});
|
|
@@ -31519,13 +31519,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31519
31519
|
"list_jobs",
|
|
31520
31520
|
"List background jobs for a project with optional status filter",
|
|
31521
31521
|
{
|
|
31522
|
-
|
|
31522
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31523
31523
|
status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
|
|
31524
31524
|
limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
|
|
31525
31525
|
},
|
|
31526
|
-
async ({
|
|
31527
|
-
await assertProjectInOrg(client,
|
|
31528
|
-
const tasks = await client.listSystemTasks(
|
|
31526
|
+
async ({ systemId, status, limit }) => {
|
|
31527
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31528
|
+
const tasks = await client.listSystemTasks(systemId, {
|
|
31529
31529
|
status,
|
|
31530
31530
|
limit
|
|
31531
31531
|
});
|
|
@@ -31543,13 +31543,13 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31543
31543
|
"draft_coda",
|
|
31544
31544
|
"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
31545
|
{
|
|
31546
|
-
|
|
31546
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31547
31547
|
beatId: external_exports.string().describe("The beat ID (must have a title but no description)")
|
|
31548
31548
|
},
|
|
31549
|
-
async ({
|
|
31549
|
+
async ({ systemId, beatId }) => {
|
|
31550
31550
|
try {
|
|
31551
|
-
await assertProjectInOrg(client,
|
|
31552
|
-
const task = await client.draftCoda(
|
|
31551
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31552
|
+
const task = await client.draftCoda(systemId, beatId);
|
|
31553
31553
|
return {
|
|
31554
31554
|
content: [{
|
|
31555
31555
|
type: "text",
|
|
@@ -31557,7 +31557,7 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31557
31557
|
"Coda draft session queued.",
|
|
31558
31558
|
"",
|
|
31559
31559
|
`**Beat:** ${beatId}`,
|
|
31560
|
-
`**Project:** ${
|
|
31560
|
+
`**Project:** ${systemId}`,
|
|
31561
31561
|
`**Task ID:** ${task.taskId}`,
|
|
31562
31562
|
`**Status:** ${task.status}`,
|
|
31563
31563
|
"",
|
|
@@ -31576,20 +31576,20 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31576
31576
|
"validate_assumptions",
|
|
31577
31577
|
"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
31578
|
{
|
|
31579
|
-
|
|
31579
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31580
31580
|
noteIds: external_exports.array(external_exports.string()).optional().describe("Specific assumption note IDs to validate. If omitted, validates all unvalidated assumptions.")
|
|
31581
31581
|
},
|
|
31582
|
-
async ({
|
|
31582
|
+
async ({ systemId, noteIds }) => {
|
|
31583
31583
|
try {
|
|
31584
|
-
await assertProjectInOrg(client,
|
|
31585
|
-
const task = await client.validateAssumptions(
|
|
31584
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31585
|
+
const task = await client.validateAssumptions(systemId, noteIds);
|
|
31586
31586
|
return {
|
|
31587
31587
|
content: [{
|
|
31588
31588
|
type: "text",
|
|
31589
31589
|
text: [
|
|
31590
31590
|
"Assumption validation queued.",
|
|
31591
31591
|
"",
|
|
31592
|
-
`**Project:** ${
|
|
31592
|
+
`**Project:** ${systemId}`,
|
|
31593
31593
|
`**Scope:** ${noteIds ? `${noteIds.length} specific assumptions` : "All unvalidated assumptions"}`,
|
|
31594
31594
|
`**Task ID:** ${task.taskId}`,
|
|
31595
31595
|
`**Status:** ${task.status}`,
|
|
@@ -31610,15 +31610,15 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31610
31610
|
"start_session",
|
|
31611
31611
|
'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
31612
|
{
|
|
31613
|
-
|
|
31613
|
+
systemId: external_exports.string().min(1).describe("The project ID \u2014 the trusted scope the session is rooted at"),
|
|
31614
31614
|
message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
|
|
31615
31615
|
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
31616
|
},
|
|
31617
|
-
async ({
|
|
31617
|
+
async ({ systemId, message, revisionId }) => {
|
|
31618
31618
|
try {
|
|
31619
|
-
await assertProjectInOrg(client,
|
|
31619
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31620
31620
|
const task = await client.startSession(
|
|
31621
|
-
|
|
31621
|
+
systemId,
|
|
31622
31622
|
message,
|
|
31623
31623
|
{ name: ctx.user.name, email: ctx.user.email },
|
|
31624
31624
|
revisionId?.trim() ? { revisionId: revisionId.trim() } : void 0
|
|
@@ -31629,7 +31629,7 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31629
31629
|
text: [
|
|
31630
31630
|
"Agent session queued.",
|
|
31631
31631
|
"",
|
|
31632
|
-
`**Project:** ${
|
|
31632
|
+
`**Project:** ${systemId}`,
|
|
31633
31633
|
`**Task ID:** ${task.taskId}`,
|
|
31634
31634
|
`**Status:** ${task.status}`,
|
|
31635
31635
|
"",
|
|
@@ -31648,20 +31648,20 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31648
31648
|
"plan_revision_batch",
|
|
31649
31649
|
"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
31650
|
{
|
|
31651
|
-
|
|
31651
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31652
31652
|
revisionIds: external_exports.array(external_exports.string()).optional().describe("Specific revision IDs to plan. If omitted, auto-discovers all unplanned planning revisions.")
|
|
31653
31653
|
},
|
|
31654
|
-
async ({
|
|
31654
|
+
async ({ systemId, revisionIds }) => {
|
|
31655
31655
|
try {
|
|
31656
|
-
await assertProjectInOrg(client,
|
|
31657
|
-
const task = await client.planRevisionBatch(
|
|
31656
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31657
|
+
const task = await client.planRevisionBatch(systemId, revisionIds);
|
|
31658
31658
|
return {
|
|
31659
31659
|
content: [{
|
|
31660
31660
|
type: "text",
|
|
31661
31661
|
text: [
|
|
31662
31662
|
"Revision batch planning queued.",
|
|
31663
31663
|
"",
|
|
31664
|
-
`**Project:** ${
|
|
31664
|
+
`**Project:** ${systemId}`,
|
|
31665
31665
|
`**Scope:** ${revisionIds ? `${revisionIds.length} specific revision(s)` : "All unplanned planning revisions"}`,
|
|
31666
31666
|
`**Task ID:** ${task.taskId}`,
|
|
31667
31667
|
`**Status:** ${task.status}`,
|
|
@@ -31681,19 +31681,19 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31681
31681
|
"run_triage",
|
|
31682
31682
|
"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
31683
|
{
|
|
31684
|
-
|
|
31684
|
+
systemId: external_exports.string().describe("The project ID")
|
|
31685
31685
|
},
|
|
31686
|
-
async ({
|
|
31686
|
+
async ({ systemId }) => {
|
|
31687
31687
|
try {
|
|
31688
|
-
await assertProjectInOrg(client,
|
|
31689
|
-
const task = await client.runTriage(
|
|
31688
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31689
|
+
const task = await client.runTriage(systemId);
|
|
31690
31690
|
return {
|
|
31691
31691
|
content: [{
|
|
31692
31692
|
type: "text",
|
|
31693
31693
|
text: [
|
|
31694
31694
|
"Triage loop queued.",
|
|
31695
31695
|
"",
|
|
31696
|
-
`**Project:** ${
|
|
31696
|
+
`**Project:** ${systemId}`,
|
|
31697
31697
|
`**Task ID:** ${task.taskId}`,
|
|
31698
31698
|
`**Status:** ${task.status}`,
|
|
31699
31699
|
"",
|
|
@@ -31724,15 +31724,15 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
31724
31724
|
"Returns a job ID \u2014 use `get_job_status` to track progress."
|
|
31725
31725
|
].join("\n"),
|
|
31726
31726
|
{
|
|
31727
|
-
|
|
31727
|
+
systemId: external_exports.string().describe("The project ID"),
|
|
31728
31728
|
content: external_exports.string().describe('Document content \u2014 plain text (default) or base64-encoded binary (when encoding is "base64")'),
|
|
31729
31729
|
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
31730
|
encoding: external_exports.enum(["base64"]).optional().describe('Set to "base64" when submitting binary files (DOCX, XLSX, PPTX, PDF). Omit for plain text.')
|
|
31731
31731
|
},
|
|
31732
|
-
async ({
|
|
31732
|
+
async ({ systemId, content, filename, encoding }) => {
|
|
31733
31733
|
try {
|
|
31734
|
-
await assertProjectInOrg(client,
|
|
31735
|
-
const result = encoding === "base64" ? await client.analyzeBinaryDocument(
|
|
31734
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
31735
|
+
const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
|
|
31736
31736
|
return {
|
|
31737
31737
|
content: [{
|
|
31738
31738
|
type: "text",
|
|
@@ -32176,7 +32176,7 @@ function createHttpClient(config2) {
|
|
|
32176
32176
|
);
|
|
32177
32177
|
const project = result?.project ? {
|
|
32178
32178
|
...result.project,
|
|
32179
|
-
|
|
32179
|
+
systemId: result.project.id ?? result.project.systemId ?? result.project.projectId,
|
|
32180
32180
|
title: result.project.name ?? result.project.title,
|
|
32181
32181
|
orgId: result.project.organizationId ?? result.project.orgId
|
|
32182
32182
|
} : void 0;
|
|
@@ -32196,7 +32196,7 @@ function createHttpClient(config2) {
|
|
|
32196
32196
|
if (!raw) return void 0;
|
|
32197
32197
|
return {
|
|
32198
32198
|
...raw,
|
|
32199
|
-
|
|
32199
|
+
systemId: raw.id ?? raw.systemId ?? raw.projectId,
|
|
32200
32200
|
title: raw.name ?? raw.title,
|
|
32201
32201
|
orgId: raw.organizationId ?? raw.orgId
|
|
32202
32202
|
};
|
|
@@ -32212,17 +32212,17 @@ function createHttpClient(config2) {
|
|
|
32212
32212
|
// is created unlinked — invisible in the Account's Systems list.
|
|
32213
32213
|
...input.accountId !== void 0 && { accountId: input.accountId }
|
|
32214
32214
|
});
|
|
32215
|
-
return { ...raw,
|
|
32215
|
+
return { ...raw, systemId: raw?.id ?? raw?.systemId ?? raw?.projectId, title: raw?.name ?? raw?.title, orgId: raw?.organizationId ?? raw?.orgId };
|
|
32216
32216
|
},
|
|
32217
32217
|
updateSystem: async (projectId, updates) => {
|
|
32218
32218
|
const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}`, updates);
|
|
32219
32219
|
if (!raw) return void 0;
|
|
32220
|
-
return { ...raw,
|
|
32220
|
+
return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
|
|
32221
32221
|
},
|
|
32222
32222
|
archiveSystem: async (projectId) => {
|
|
32223
32223
|
const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}/archive`);
|
|
32224
32224
|
if (!raw) return void 0;
|
|
32225
|
-
return { ...raw,
|
|
32225
|
+
return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
|
|
32226
32226
|
},
|
|
32227
32227
|
getSystemFact: async (projectId) => {
|
|
32228
32228
|
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/facts`);
|
|
@@ -32291,7 +32291,7 @@ function createHttpClient(config2) {
|
|
|
32291
32291
|
return raw?.report;
|
|
32292
32292
|
},
|
|
32293
32293
|
createBeat: async (input) => {
|
|
32294
|
-
const raw = await request("POST", `/api/systems/${encodeURIComponent(input.
|
|
32294
|
+
const raw = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/beats`, input);
|
|
32295
32295
|
return { ...raw, beatId: raw.beatId ?? raw.id, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
|
|
32296
32296
|
},
|
|
32297
32297
|
updateBeat: async (beatId, updates) => {
|
|
@@ -32375,7 +32375,7 @@ function createHttpClient(config2) {
|
|
|
32375
32375
|
return result?.layer;
|
|
32376
32376
|
},
|
|
32377
32377
|
createSystemLayer: async (input) => {
|
|
32378
|
-
const {
|
|
32378
|
+
const { systemId, layerKey, name, description, parentLayerId, repoPatterns } = input;
|
|
32379
32379
|
const body = {
|
|
32380
32380
|
layerKey,
|
|
32381
32381
|
name,
|
|
@@ -32385,7 +32385,7 @@ function createHttpClient(config2) {
|
|
|
32385
32385
|
};
|
|
32386
32386
|
const result = await request(
|
|
32387
32387
|
"POST",
|
|
32388
|
-
`/api/systems/${encodeURIComponent(
|
|
32388
|
+
`/api/systems/${encodeURIComponent(systemId)}/layers`,
|
|
32389
32389
|
body
|
|
32390
32390
|
);
|
|
32391
32391
|
if (result === void 0) return void 0;
|
|
@@ -33000,7 +33000,7 @@ function createHttpClient(config2) {
|
|
|
33000
33000
|
return { entries: result.activities ?? [], hasMore: false };
|
|
33001
33001
|
},
|
|
33002
33002
|
createActivity: async (input) => {
|
|
33003
|
-
const result = await request("POST", `/api/systems/${encodeURIComponent(input.
|
|
33003
|
+
const result = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/activities`, {
|
|
33004
33004
|
action: input.action,
|
|
33005
33005
|
importance: input.importance,
|
|
33006
33006
|
reason: input.reason,
|
|
@@ -33185,7 +33185,7 @@ function createHttpClient(config2) {
|
|
|
33185
33185
|
{ projectId, message, ...triggeredBy && { triggeredBy }, ...trimmedRevisionId && { revisionId: trimmedRevisionId } }
|
|
33186
33186
|
);
|
|
33187
33187
|
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() };
|
|
33188
|
+
return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33189
33189
|
},
|
|
33190
33190
|
planRevisionBatch: async (projectId, revisionIds) => {
|
|
33191
33191
|
const result = await request(
|
|
@@ -33194,7 +33194,7 @@ function createHttpClient(config2) {
|
|
|
33194
33194
|
revisionIds ? { revisionIds } : {}
|
|
33195
33195
|
);
|
|
33196
33196
|
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() };
|
|
33197
|
+
return { taskId: result.taskId, taskType: "plan_revision_batch", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33198
33198
|
},
|
|
33199
33199
|
draftCoda: async (projectId, beatId) => {
|
|
33200
33200
|
const result = await request(
|
|
@@ -33203,7 +33203,7 @@ function createHttpClient(config2) {
|
|
|
33203
33203
|
{}
|
|
33204
33204
|
);
|
|
33205
33205
|
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() };
|
|
33206
|
+
return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33207
33207
|
},
|
|
33208
33208
|
composeRootCoda: async (orgId) => {
|
|
33209
33209
|
const result = await request(
|
|
@@ -33212,7 +33212,7 @@ function createHttpClient(config2) {
|
|
|
33212
33212
|
{}
|
|
33213
33213
|
);
|
|
33214
33214
|
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",
|
|
33215
|
+
return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", systemId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33216
33216
|
},
|
|
33217
33217
|
validateAssumptions: async (projectId, noteIds) => {
|
|
33218
33218
|
const result = await request(
|
|
@@ -33221,7 +33221,7 @@ function createHttpClient(config2) {
|
|
|
33221
33221
|
noteIds ? { noteIds } : {}
|
|
33222
33222
|
);
|
|
33223
33223
|
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() };
|
|
33224
|
+
return { taskId: result.taskId, taskType: "validate_assumptions", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33225
33225
|
},
|
|
33226
33226
|
runTriage: async (projectId) => {
|
|
33227
33227
|
const result = await request(
|
|
@@ -33230,7 +33230,7 @@ function createHttpClient(config2) {
|
|
|
33230
33230
|
{}
|
|
33231
33231
|
);
|
|
33232
33232
|
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() };
|
|
33233
|
+
return { taskId: result.taskId, taskType: "triage_loop", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33234
33234
|
},
|
|
33235
33235
|
baselineProject: async (projectId, options) => {
|
|
33236
33236
|
const result = await request(
|
|
@@ -33239,7 +33239,7 @@ function createHttpClient(config2) {
|
|
|
33239
33239
|
{ beatIds: options?.beatIds, runChecks: options?.runChecks ?? true, localPath: options?.localPath }
|
|
33240
33240
|
);
|
|
33241
33241
|
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() };
|
|
33242
|
+
return { taskId: result.taskId, taskType: "baseline_project", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33243
33243
|
},
|
|
33244
33244
|
runCheck: async (projectId, checkType, targetId, options) => {
|
|
33245
33245
|
const result = await request(
|
|
@@ -33248,7 +33248,7 @@ function createHttpClient(config2) {
|
|
|
33248
33248
|
{ checkType, targetId, branch: options?.branch, localPath: options?.localPath }
|
|
33249
33249
|
);
|
|
33250
33250
|
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() };
|
|
33251
|
+
return { taskId: result.taskId, taskType: "run_check", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33252
33252
|
},
|
|
33253
33253
|
// Subscriptions
|
|
33254
33254
|
subscribe: async (userId, entityType, entityId, projectId, source) => {
|
|
@@ -33325,7 +33325,7 @@ function createHttpClient(config2) {
|
|
|
33325
33325
|
},
|
|
33326
33326
|
searchByText: async (scope, query, options) => {
|
|
33327
33327
|
const params = new URLSearchParams({ q: query });
|
|
33328
|
-
if ("
|
|
33328
|
+
if ("systemId" in scope) params.set("systemId", scope.systemId);
|
|
33329
33329
|
else if ("teamspaceId" in scope) params.set("teamspaceId", scope.teamspaceId);
|
|
33330
33330
|
else params.set("orgId", scope.orgId);
|
|
33331
33331
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -34161,7 +34161,7 @@ function loadConfig() {
|
|
|
34161
34161
|
};
|
|
34162
34162
|
}
|
|
34163
34163
|
async function main() {
|
|
34164
|
-
console.error(`[harmonica-mcp] v${"3.
|
|
34164
|
+
console.error(`[harmonica-mcp] v${"3.7.0"} starting\u2026`);
|
|
34165
34165
|
const config2 = loadConfig();
|
|
34166
34166
|
const client = createHttpClient({
|
|
34167
34167
|
apiBaseUrl: config2.apiBaseUrl,
|