From b329b4160d7b84182cbebde3c76b45cf609ad4ce Mon Sep 17 00:00:00 2001 From: Kurt Overmier Date: Sat, 18 Apr 2026 03:03:42 -0500 Subject: [PATCH] chore(tools): mark flow_* deprecated with 2026-05-18 sunset (closes #38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds deprecated/deprecationTarget/sunsetDate annotations to the five flow_* tool specs and prefixes each description with a [DEPRECATED — sunset YYYY-MM-DD, migrate to ] marker so MCP clients render the sunset to users. flow_create and flow_status point to their 1:1 scaffold successors; flow_summary/quality/governance point to the generic scaffold_* family since no direct counterpart exists yet. Chosen over option 1 (hard delete) and option 2 (split clone semantics): a 30-day deprecation window gives known downstream consumers a heads-up without wasting effort splitting flow_quality vs. flow_governance payloads on an interface scheduled for removal. The follow-up PR lands after 2026-05-18 and removes the specs + their route-table entries + their registry mapping in one change. Regression guards in tool-registry.test.ts: all five flow_* entries are deprecated with the shared sunset date, the 1:1 successors are correctly named, descriptions carry the deprecation marker, and no non-flow tool is marked deprecated. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/tool-registry.ts | 82 +++++++++++++++++++++++++++++++------- test/tool-registry.test.ts | 45 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/src/tool-registry.ts b/src/tool-registry.ts index 7ec4e46..716ed46 100644 --- a/src/tool-registry.ts +++ b/src/tool-registry.ts @@ -20,6 +20,12 @@ export interface GatewayToolDefinition { destructiveHint: boolean; /** Stackbilt Security Constitution: explicit risk level */ riskLevel: RiskLevel; + /** #38: tool is scheduled for removal — clients should migrate. */ + deprecated?: boolean; + /** Successor tool/family (e.g. 'scaffold_create', 'scaffold_*'). */ + deprecationTarget?: string; + /** ISO date (YYYY-MM-DD) after which this tool is removed. */ + sunsetDate?: string; }; } @@ -85,8 +91,22 @@ interface ToolSpec { gatewayName: string; description: string; inputSchema: Record; + /** #38: tool is scheduled for removal. */ + deprecated?: boolean; + /** Successor tool/family. */ + deprecationTarget?: string; + /** ISO date (YYYY-MM-DD) after which this tool is removed. */ + sunsetDate?: string; } +// ─── #38: flow_* sunset ─────────────────────────────────────── +// The flow_* family is being retired in favor of scaffold_*. Grace +// window gives downstream consumers (codebeast, CLI, etc.) ~30 days +// to migrate before the tools are deleted from the manifest. Post- +// sunset, the removal PR deletes these entries + their route-table +// mappings + their registry mapping in one go. +const FLOW_SUNSET_DATE = '2026-05-18'; + const TOOL_SPECS: ToolSpec[] = [ // ── Core pipeline: scaffold → publish → deploy ───────────── // These are the primary tools. Ordered first so Claude.ai's deferred @@ -417,14 +437,18 @@ const TOOL_SPECS: ToolSpec[] = [ }, }, - // ── Flow tools (async variant with audit trail) ──────────── - // These wrap scaffold-cast with receipt-based async retrieval. - // Use scaffold_create for synchronous generation (recommended). - // Use flow_create only when you need a persistent receipt hash - // for governance/quality auditing after the fact. + // ── Flow tools (deprecated — sunset 2026-05-18, see #38) ──── + // The flow_* family is being retired in favor of scaffold_*. + // scaffold_create is the direct replacement for flow_create (21x + // faster, returns files directly). flow_status has a 1:1 successor + // in scaffold_status. flow_summary/quality/governance have no direct + // scaffold_* counterpart yet — consumers who still need receipt-based + // governance inspection should flag it on #38 before the sunset date + // so we can decide whether to port those semantics forward. { gatewayName: 'flow_create', description: + `[DEPRECATED — sunset ${FLOW_SUNSET_DATE}, migrate to scaffold_create] ` + 'Async variant of scaffold_create — runs the same scaffold-cast but returns a receipt hash ' + 'for deferred inspection via flow_status, flow_summary, flow_quality, and flow_governance. ' + 'Use this when you need a persistent audit trail or governance review after generation. ' + @@ -452,10 +476,15 @@ const TOOL_SPECS: ToolSpec[] = [ }, required: ['intention'], }, + deprecated: true, + deprecationTarget: 'scaffold_create', + sunsetDate: FLOW_SUNSET_DATE, }, { gatewayName: 'flow_status', - description: 'Get flow status from a receipt hash. Returns verification status and creation time. Requires a hash from flow_create.', + description: + `[DEPRECATED — sunset ${FLOW_SUNSET_DATE}, migrate to scaffold_status] ` + + 'Get flow status from a receipt hash. Returns verification status and creation time. Requires a hash from flow_create.', inputSchema: { type: 'object', properties: { @@ -463,10 +492,15 @@ const TOOL_SPECS: ToolSpec[] = [ }, required: ['hash'], }, + deprecated: true, + deprecationTarget: 'scaffold_status', + sunsetDate: FLOW_SUNSET_DATE, }, { gatewayName: 'flow_summary', - description: 'Get full flow summary from a receipt hash, including all mode outputs and artifacts. Requires a hash from flow_create.', + description: + `[DEPRECATED — sunset ${FLOW_SUNSET_DATE}, migrate to scaffold_*] ` + + 'Get full flow summary from a receipt hash, including all mode outputs and artifacts. Requires a hash from flow_create.', inputSchema: { type: 'object', properties: { @@ -474,10 +508,15 @@ const TOOL_SPECS: ToolSpec[] = [ }, required: ['hash'], }, + deprecated: true, + deprecationTarget: 'scaffold_*', + sunsetDate: FLOW_SUNSET_DATE, }, { gatewayName: 'flow_quality', - description: 'Get quality and governance scores from a receipt hash. Requires a hash from flow_create.', + description: + `[DEPRECATED — sunset ${FLOW_SUNSET_DATE}, migrate to scaffold_*] ` + + 'Get quality and governance scores from a receipt hash. Requires a hash from flow_create.', inputSchema: { type: 'object', properties: { @@ -485,10 +524,15 @@ const TOOL_SPECS: ToolSpec[] = [ }, required: ['hash'], }, + deprecated: true, + deprecationTarget: 'scaffold_*', + sunsetDate: FLOW_SUNSET_DATE, }, { gatewayName: 'flow_governance', - description: 'Get governance posture from a receipt hash — determinism profile, capping, and verification. Requires a hash from flow_create.', + description: + `[DEPRECATED — sunset ${FLOW_SUNSET_DATE}, migrate to scaffold_*] ` + + 'Get governance posture from a receipt hash — determinism profile, capping, and verification. Requires a hash from flow_create.', inputSchema: { type: 'object', properties: { @@ -496,6 +540,9 @@ const TOOL_SPECS: ToolSpec[] = [ }, required: ['hash'], }, + deprecated: true, + deprecationTarget: 'scaffold_*', + sunsetDate: FLOW_SUNSET_DATE, }, ]; @@ -511,15 +558,22 @@ function buildCatalogFromSpecs(): GatewayToolDefinition[] { continue; } + const annotations: GatewayToolDefinition['annotations'] = { + readOnlyHint: riskLevel === 'READ_ONLY', + destructiveHint: riskLevel === 'DESTRUCTIVE', + riskLevel, + }; + if (spec.deprecated) { + annotations.deprecated = true; + if (spec.deprecationTarget) annotations.deprecationTarget = spec.deprecationTarget; + if (spec.sunsetDate) annotations.sunsetDate = spec.sunsetDate; + } + catalog.push({ name: spec.gatewayName, description: spec.description, inputSchema: spec.inputSchema, - annotations: { - readOnlyHint: riskLevel === 'READ_ONLY', - destructiveHint: riskLevel === 'DESTRUCTIVE', - riskLevel, - }, + annotations, }); } diff --git a/test/tool-registry.test.ts b/test/tool-registry.test.ts index 5d77dee..d6fd186 100644 --- a/test/tool-registry.test.ts +++ b/test/tool-registry.test.ts @@ -117,6 +117,51 @@ describe('buildAggregatedCatalog', () => { }); }); +describe('buildAggregatedCatalog — #38 flow_* deprecation metadata', () => { + // flow_* is being retired in favor of scaffold_*. Every flow_* entry + // must carry deprecation metadata so MCP clients can surface the + // sunset to users before the removal PR lands. Non-flow tools must + // NOT carry deprecation metadata (so the sunset signal stays + // meaningful in the catalog). + + const FLOW_TOOLS = ['flow_create', 'flow_status', 'flow_summary', 'flow_quality', 'flow_governance']; + + it('every flow_* tool is marked deprecated with the shared sunset date', () => { + const catalog = buildAggregatedCatalog(); + for (const name of FLOW_TOOLS) { + const tool = catalog.find(t => t.name === name); + expect(tool, `${name} missing from catalog`).toBeDefined(); + expect(tool!.annotations.deprecated, `${name} not deprecated`).toBe(true); + expect(tool!.annotations.sunsetDate, `${name} missing sunsetDate`).toBe('2026-05-18'); + expect(tool!.annotations.deprecationTarget, `${name} missing deprecationTarget`).toBeTruthy(); + } + }); + + it('flow_create and flow_status point to their 1:1 scaffold successors', () => { + const catalog = buildAggregatedCatalog(); + expect(catalog.find(t => t.name === 'flow_create')!.annotations.deprecationTarget).toBe('scaffold_create'); + expect(catalog.find(t => t.name === 'flow_status')!.annotations.deprecationTarget).toBe('scaffold_status'); + }); + + it('every flow_* description carries the deprecation marker', () => { + const catalog = buildAggregatedCatalog(); + for (const name of FLOW_TOOLS) { + const tool = catalog.find(t => t.name === name)!; + expect(tool.description, `${name} description missing deprecation marker`).toMatch(/\[DEPRECATED/); + expect(tool.description).toContain('2026-05-18'); + } + }); + + it('non-flow tools are NOT marked deprecated', () => { + const catalog = buildAggregatedCatalog(); + const nonFlow = catalog.filter(t => !t.name.startsWith('flow_')); + for (const tool of nonFlow) { + expect(tool.annotations.deprecated, `${tool.name} should not be deprecated`).toBeFalsy(); + expect(tool.annotations.sunsetDate, `${tool.name} should not have a sunsetDate`).toBeUndefined(); + } + }); +}); + describe('validateToolArguments', () => { it('accepts valid object arguments', () => { expect(validateToolArguments({ prompt: 'test' }, { type: 'object' })).toEqual({ valid: true });