Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 68 additions & 14 deletions src/tool-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand Down Expand Up @@ -85,8 +91,22 @@ interface ToolSpec {
gatewayName: string;
description: string;
inputSchema: Record<string, unknown>;
/** #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
Expand Down Expand Up @@ -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. ' +
Expand Down Expand Up @@ -452,50 +476,73 @@ 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: {
hash: { type: 'string', description: 'Receipt hash from flow_create' },
},
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: {
hash: { type: 'string', description: 'Receipt hash from flow_create' },
},
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: {
hash: { type: 'string', description: 'Receipt hash from flow_create' },
},
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: {
hash: { type: 'string', description: 'Receipt hash from flow_create' },
},
required: ['hash'],
},
deprecated: true,
deprecationTarget: 'scaffold_*',
sunsetDate: FLOW_SUNSET_DATE,
},
];

Expand All @@ -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,
});
}

Expand Down
45 changes: 45 additions & 0 deletions test/tool-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Loading