@specforge/canary-cli 0.1.10 → 0.1.12
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/cli/commands/scaffold/agent-types.js +1 -1
- package/dist/cli/commands/scaffold/agent-types.js.map +1 -1
- package/dist/cli/config/agent-teams.types.d.ts +35 -12
- package/dist/cli/config/agent-teams.types.d.ts.map +1 -1
- package/dist/cli/config/agent-teams.types.js.map +1 -1
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts +21 -3
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts.map +1 -1
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js +114 -48
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js.map +1 -1
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts +22 -3
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts.map +1 -1
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js +225 -160
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js.map +1 -1
- package/dist/cli/templates/agents/content/core/sfag-work-resolver.d.ts +16 -0
- package/dist/cli/templates/agents/content/core/sfag-work-resolver.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-work-resolver.js +199 -0
- package/dist/cli/templates/agents/content/core/sfag-work-resolver.js.map +1 -0
- package/dist/cli/templates/agents/index.d.ts.map +1 -1
- package/dist/cli/templates/agents/index.js +2 -0
- package/dist/cli/templates/agents/index.js.map +1 -1
- package/dist/cli/templates/content/sf-reset.d.ts +5 -2
- package/dist/cli/templates/content/sf-reset.d.ts.map +1 -1
- package/dist/cli/templates/content/sf-reset.js +41 -27
- package/dist/cli/templates/content/sf-reset.js.map +1 -1
- package/dist/lib/prompt-generator.d.ts +15 -3
- package/dist/lib/prompt-generator.d.ts.map +1 -1
- package/dist/lib/prompt-generator.js +14 -6
- package/dist/lib/prompt-generator.js.map +1 -1
- package/dist/tools/core/__tests__/git-injection.test.d.ts +2 -0
- package/dist/tools/core/__tests__/git-injection.test.d.ts.map +1 -0
- package/dist/tools/core/git-injection.d.ts +50 -0
- package/dist/tools/core/git-injection.d.ts.map +1 -0
- package/dist/tools/core/git-injection.js +74 -0
- package/dist/tools/core/git-injection.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +68 -81
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +0 -6
- package/dist/validation/index.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/templates/agents/content/core/sfag-orchestrator.ts +135 -51
- package/src/cli/templates/agents/content/core/sfag-ticket-implementer.ts +247 -163
- package/src/cli/templates/agents/content/core/sfag-work-resolver.ts +211 -0
- package/src/cli/templates/agents/index.ts +2 -0
- package/src/cli/templates/content/sf-reset.ts +45 -28
- package/src/cli/templates/skills/specforge-orchestrator.md +14 -9
- package/src/cli/templates/skills/specforge-worker.md +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,YAAY,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAGV,YAAY,EACb,MAAM,yBAAyB,CAAC;AAMjC,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,0BAA0B,EAC1B,WAAW,EACX,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,MAAM,EACN,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,MAAM,EACP,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAMpE,YAAY,EACV,YAAY,EACZ,uBAAuB,EACvB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,0BAA0B,EAC1B,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,YAAY,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAGV,YAAY,EACb,MAAM,yBAAyB,CAAC;AAMjC,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,0BAA0B,EAC1B,WAAW,EACX,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,MAAM,EACN,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,MAAM,EACP,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAMpE,YAAY,EACV,YAAY,EACZ,uBAAuB,EACvB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,0BAA0B,EAC1B,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAWpE,YAAY,EACV,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAUlC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,CAAC,MAAM,GAAG,mBAAmB,CAAC,EAAE,GACzC,mBAAmB,EAAE,CAWvB;AAMD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC;IAC/C,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,4CAA4C;IAC5C,aAAa,EAAE,aAAa,CAAC;IAC7B,6BAA6B;IAC7B,YAAY,EAAE;QACZ,qDAAqD;QACrD,SAAS,EAAE,uBAAuB,EAAE,CAAC;QACrC,kCAAkC;QAClC,MAAM,EAAE,uBAAuB,EAAE,CAAC;KACnC,CAAC;IACF,sBAAsB;IACtB,cAAc,EAAE,oBAAoB,EAAE,CAAC;IACvC,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,8CAA8C;IAC9C,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,iDAAiD;IACjD,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,oCAAoC;IACpC,kBAAkB,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAMD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,aAAa,GACb,KAAK,GACL,QAAQ,GACR,YAAY,GACZ,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,UAAU,GACV,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAMrD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * SpecForge MCP Type Definitions\n *\n * Most wire-protocol types live in `@specforge/types` (the canonical source\n * of truth). This file re-exports them under stable CLI names and defines a\n * small set of CLI-internal shapes: work sessions, implementation context,\n * discovery/attempt summaries, git-link records, and structured acceptance\n * criteria with normalization helpers.\n */\n\n// -----------------------------------------------------------------------------\n// Schema enums and primitive types — re-exported from @specforge/types/schema\n// -----------------------------------------------------------------------------\n\nexport type {\n SpecificationStatus,\n EpicStatus,\n TicketStatus,\n DependencyType,\n} from '@specforge/types/schema';\n\nimport type {\n SpecificationStatus,\n EpicStatus,\n TicketStatus,\n} from '@specforge/types/schema';\n\n// -----------------------------------------------------------------------------\n// Runtime entity shapes — re-exported from @specforge/types/runtime\n// -----------------------------------------------------------------------------\n\nexport type {\n Project,\n Specification,\n NamingConventions,\n SpecificationCodeStandards,\n ReturnTypes,\n Epic,\n EpicSharedPatterns,\n EpicCommonFiles,\n Ticket,\n TicketTechnicalDetails,\n TicketDependency,\n} from '@specforge/types/runtime';\n\nimport type {\n Specification,\n Epic,\n Ticket,\n} from '@specforge/types/runtime';\n\nexport type { TicketImplementation } from '@specforge/types/schema';\n\n// -----------------------------------------------------------------------------\n// API wire types — re-exported from @specforge/types/api\n// -----------------------------------------------------------------------------\n\nexport type {\n ListResponse,\n DependencyTicketSummary,\n SpecificationStatusReport,\n EpicStatusReport,\n ImplementationSummary,\n TimeReport,\n BlockersReport,\n TicketUpdate,\n BulkUpdateRequest,\n BulkUpdateResult,\n BulkCreateTicketDefinition,\n BulkCreateDependencyDefinition,\n BulkCreateTicketsRequest,\n BulkCreatedTicketInfo,\n BulkCreateTicketsResult,\n BulkDependencyDefinition,\n BulkAddDependenciesRequest,\n BulkAddDependenciesResult,\n LookupProjectRequest,\n LookupProjectResult,\n LookupSpecificationRequest,\n LookupSpecificationResult,\n LookupEpicRequest,\n LookupEpicResult,\n LookupTicketRequest,\n LookupTicketResult,\n} from '@specforge/types/api';\n\nimport type { DependencyTicketSummary } from '@specforge/types/api';\n\n// =============================================================================\n// CLI-internal types below — no canonical equivalent in @specforge/types\n// =============================================================================\n\n// -----------------------------------------------------------------------------\n// Structured acceptance criteria (TS-001)\n// -----------------------------------------------------------------------------\n\n/**\n * Structured acceptance criterion with validation state.\n */\nexport interface AcceptanceCriterion {\n id: string;\n description: string;\n validated: boolean;\n validatedAt?: string;\n validatedBy?: string;\n notes?: string;\n}\n\n/**\n * Normalizes acceptance criteria from legacy string[] format to structured\n * `AcceptanceCriterion[]`. Provides backwards compatibility for old data\n * stored as string arrays.\n */\nexport function normalizeAcceptanceCriteria(\n criteria: (string | AcceptanceCriterion)[]\n): AcceptanceCriterion[] {\n return criteria.map((c, index) => {\n if (typeof c === 'string') {\n return {\n id: `ac-${index}-${Date.now()}`,\n description: c,\n validated: false,\n };\n }\n return c;\n });\n}\n\n// -----------------------------------------------------------------------------\n// Implementation context (returned by get_implementation_context)\n// -----------------------------------------------------------------------------\n\nexport interface RelatedTicketSummary {\n id: string;\n title: string;\n status: TicketStatus;\n sameEpic: boolean;\n}\n\nexport interface TestResultSummary {\n id: string;\n testType?: string;\n allPassed: boolean;\n passed: number;\n failed: number;\n total: number;\n runAt: string;\n}\n\nexport interface PreviousAttempt {\n attemptNumber: number;\n startedAt: string;\n endedAt?: string;\n result: 'completed' | 'failed' | 'in_progress';\n testResults: TestResultSummary[];\n summary?: string;\n}\n\nexport interface DiscoverySummary {\n id: string;\n type: string;\n severity: string;\n status: string;\n title: string;\n description: string;\n createdAt: string;\n}\n\nexport interface ActiveSessionSummary {\n id: string;\n status: string;\n startedAt: string;\n config: unknown;\n ticketsCompleted: string[];\n ticketsFailed: string[];\n ticketsSkipped: string[];\n totalActiveMinutes: number;\n}\n\n/**\n * Implementation context returned by get_implementation_context.\n */\nexport interface ImplementationContext {\n /** The ticket being implemented */\n ticket: Ticket;\n /** The epic containing the ticket */\n epic: Epic;\n /** The specification containing the epic */\n specification: Specification;\n /** Dependency information */\n dependencies: {\n /** Tickets that must be completed before this one */\n blockedBy: DependencyTicketSummary[];\n /** Tickets waiting on this one */\n blocks: DependencyTicketSummary[];\n };\n /** Related tickets */\n relatedTickets: RelatedTicketSummary[];\n /** Patterns and conventions from the project */\n patterns?: Record<string, unknown>;\n /** Active implementation session (Phase 5) */\n activeSession: ActiveSessionSummary | null;\n /** Previous implementation attempts (Phase 5) */\n previousAttempts: PreviousAttempt[];\n /** Related discoveries (Phase 5) */\n relatedDiscoveries: DiscoverySummary[];\n}\n\n// -----------------------------------------------------------------------------\n// Work session state (session-scoped tables)\n// -----------------------------------------------------------------------------\n\nexport interface WorkSession {\n id: string;\n ticketId: string;\n specificationId: string;\n projectId: string;\n epicId: string;\n implementationSessionId?: string;\n status: 'active' | 'paused' | 'completed' | 'failed';\n startedAt: string;\n pausedAt?: string;\n completedAt?: string;\n actualHours?: number;\n filesCreated?: string[];\n filesModified?: string[];\n filesDeleted?: string[];\n testsPassed?: boolean;\n retryCount: number;\n retryLog?: unknown;\n actions?: unknown;\n completionAttempts?: unknown;\n tokensUsed?: { inputTokens: number; outputTokens: number };\n costUsd?: number;\n durationMs?: number;\n commitSha?: string;\n modelUsed?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TicketAcceptanceCriterion {\n id: string;\n ticketId: string;\n description: string;\n order: number;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport interface WorkSessionACValidation {\n id: string;\n workSessionId: string;\n criterionId: string;\n validated: boolean;\n validatedAt?: string;\n notes?: string;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport interface TicketImplementationStep {\n id: string;\n ticketId: string;\n text: string;\n order: number;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport interface WorkSessionStepCompletion {\n id: string;\n workSessionId: string;\n stepId: string;\n completed: boolean;\n completedAt?: string;\n notes?: string;\n createdAt?: string;\n updatedAt?: string;\n}\n\n// -----------------------------------------------------------------------------\n// Test result types\n// -----------------------------------------------------------------------------\n\nexport type TestType =\n | 'unit'\n | 'integration'\n | 'e2e'\n | 'manual'\n | 'typescript'\n | 'lint'\n | 'build'\n | 'typecheck'\n | 'format'\n | 'security'\n | 'custom';\n\nexport interface WorkSessionTestResult {\n id: string;\n workSessionId: string;\n testType: TestType;\n passed: boolean;\n totalTests: number;\n passedTests: number;\n failedTests: number;\n skippedTests: number;\n coverage?: number;\n errorDetails?: string;\n command?: string;\n exitCode?: number;\n durationMs?: number;\n errorCount?: number;\n warningCount?: number;\n summary?: string;\n suites?: Array<{\n name: string;\n passed: number;\n failed: number;\n skipped?: number;\n duration?: number;\n }>;\n runAt: string;\n}\n\n/** @deprecated Use WorkSessionTestResult instead */\nexport type TicketTestResult = WorkSessionTestResult;\n\n// -----------------------------------------------------------------------------\n// Git integration\n// -----------------------------------------------------------------------------\n\nexport type LinkType = 'commit' | 'pull_request' | 'branch';\n\nexport interface TicketLink {\n id: string;\n ticketId: string;\n linkType: LinkType;\n url: string;\n sha?: string;\n prNumber?: number;\n title?: string;\n status?: string;\n createdAt: string;\n}\n\n// -----------------------------------------------------------------------------\n// Silence unused-import warnings for enum aliases referenced only by JSDoc\n// -----------------------------------------------------------------------------\n// (Each of these is re-exported above; the local `import type` pulls them\n// in so that embedded JSDoc cross-references resolve in editors.)\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype _ReferencedEnums = SpecificationStatus | EpicStatus;\n"],"mappings":"AAiHO,SAAS,4BACd,UACuB;AACvB,SAAO,SAAS,IAAI,CAAC,GAAG,UAAU;AAChC,QAAI,OAAO,MAAM,UAAU;AACzB,aAAO;AAAA,QACL,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;AAAA,QAC7B,aAAa;AAAA,QACb,WAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * SpecForge MCP Type Definitions\n *\n * Most wire-protocol types live in `@specforge/types` (the canonical source\n * of truth). This file re-exports them under stable CLI names and defines a\n * small set of CLI-internal shapes: work sessions, implementation context,\n * discovery/attempt summaries, git-link records, and structured acceptance\n * criteria with normalization helpers.\n */\n\n// -----------------------------------------------------------------------------\n// Schema enums and primitive types — re-exported from @specforge/types/schema\n// -----------------------------------------------------------------------------\n\nexport type {\n SpecificationStatus,\n EpicStatus,\n TicketStatus,\n DependencyType,\n} from '@specforge/types/schema';\n\nimport type {\n SpecificationStatus,\n EpicStatus,\n TicketStatus,\n} from '@specforge/types/schema';\n\n// -----------------------------------------------------------------------------\n// Runtime entity shapes — re-exported from @specforge/types/runtime\n// -----------------------------------------------------------------------------\n\nexport type {\n Project,\n Specification,\n NamingConventions,\n SpecificationCodeStandards,\n ReturnTypes,\n Epic,\n EpicSharedPatterns,\n EpicCommonFiles,\n Ticket,\n TicketTechnicalDetails,\n TicketDependency,\n} from '@specforge/types/runtime';\n\nimport type {\n Specification,\n Epic,\n Ticket,\n} from '@specforge/types/runtime';\n\nexport type { TicketImplementation } from '@specforge/types/schema';\n\n// -----------------------------------------------------------------------------\n// API wire types — re-exported from @specforge/types/api\n// -----------------------------------------------------------------------------\n\nexport type {\n ListResponse,\n DependencyTicketSummary,\n SpecificationStatusReport,\n EpicStatusReport,\n ImplementationSummary,\n TimeReport,\n BlockersReport,\n TicketUpdate,\n BulkUpdateRequest,\n BulkUpdateResult,\n BulkCreateTicketDefinition,\n BulkCreateDependencyDefinition,\n BulkCreateTicketsRequest,\n BulkCreatedTicketInfo,\n BulkCreateTicketsResult,\n BulkDependencyDefinition,\n BulkAddDependenciesRequest,\n BulkAddDependenciesResult,\n LookupProjectRequest,\n LookupProjectResult,\n LookupSpecificationRequest,\n LookupSpecificationResult,\n LookupEpicRequest,\n LookupEpicResult,\n LookupTicketRequest,\n LookupTicketResult,\n} from '@specforge/types/api';\n\nimport type { DependencyTicketSummary } from '@specforge/types/api';\n\n// -----------------------------------------------------------------------------\n// Canonical work-session child-state types — re-exported from\n// @specforge/session-types (the single source of truth after the M14\n// convergence + the M15.1a DDB rename). These replace the former divergent\n// CLI-local AC-validation / step-completion shapes (which had `validated`/\n// `completed` and were missing `ticketId`/`projectId`): one name + one shape\n// end-to-end (`checked`/`done` + `ticketId`/`projectId`), no aliases.\n// -----------------------------------------------------------------------------\n\nexport type {\n WorkSessionAcceptanceCheck,\n WorkSessionImplStepCompletion,\n} from '@specforge/session-types';\n\n// =============================================================================\n// CLI-internal types below — no canonical equivalent in @specforge/types\n// =============================================================================\n\n// -----------------------------------------------------------------------------\n// Structured acceptance criteria (TS-001)\n// -----------------------------------------------------------------------------\n\n/**\n * Structured acceptance criterion with validation state.\n */\nexport interface AcceptanceCriterion {\n id: string;\n description: string;\n validated: boolean;\n validatedAt?: string;\n validatedBy?: string;\n notes?: string;\n}\n\n/**\n * Normalizes acceptance criteria from legacy string[] format to structured\n * `AcceptanceCriterion[]`. Provides backwards compatibility for old data\n * stored as string arrays.\n */\nexport function normalizeAcceptanceCriteria(\n criteria: (string | AcceptanceCriterion)[]\n): AcceptanceCriterion[] {\n return criteria.map((c, index) => {\n if (typeof c === 'string') {\n return {\n id: `ac-${index}-${Date.now()}`,\n description: c,\n validated: false,\n };\n }\n return c;\n });\n}\n\n// -----------------------------------------------------------------------------\n// Implementation context (returned by get_implementation_context)\n// -----------------------------------------------------------------------------\n\nexport interface RelatedTicketSummary {\n id: string;\n title: string;\n status: TicketStatus;\n sameEpic: boolean;\n}\n\nexport interface TestResultSummary {\n id: string;\n testType?: string;\n allPassed: boolean;\n passed: number;\n failed: number;\n total: number;\n runAt: string;\n}\n\nexport interface PreviousAttempt {\n attemptNumber: number;\n startedAt: string;\n endedAt?: string;\n result: 'completed' | 'failed' | 'in_progress';\n testResults: TestResultSummary[];\n summary?: string;\n}\n\nexport interface DiscoverySummary {\n id: string;\n type: string;\n severity: string;\n status: string;\n title: string;\n description: string;\n createdAt: string;\n}\n\nexport interface ActiveSessionSummary {\n id: string;\n status: string;\n startedAt: string;\n config: unknown;\n ticketsCompleted: string[];\n ticketsFailed: string[];\n ticketsSkipped: string[];\n totalActiveMinutes: number;\n}\n\n/**\n * Implementation context returned by get_implementation_context.\n */\nexport interface ImplementationContext {\n /** The ticket being implemented */\n ticket: Ticket;\n /** The epic containing the ticket */\n epic: Epic;\n /** The specification containing the epic */\n specification: Specification;\n /** Dependency information */\n dependencies: {\n /** Tickets that must be completed before this one */\n blockedBy: DependencyTicketSummary[];\n /** Tickets waiting on this one */\n blocks: DependencyTicketSummary[];\n };\n /** Related tickets */\n relatedTickets: RelatedTicketSummary[];\n /** Patterns and conventions from the project */\n patterns?: Record<string, unknown>;\n /** Active implementation session (Phase 5) */\n activeSession: ActiveSessionSummary | null;\n /** Previous implementation attempts (Phase 5) */\n previousAttempts: PreviousAttempt[];\n /** Related discoveries (Phase 5) */\n relatedDiscoveries: DiscoverySummary[];\n}\n\n// -----------------------------------------------------------------------------\n// Work session state (session-scoped tables)\n// -----------------------------------------------------------------------------\n\nexport interface WorkSession {\n id: string;\n ticketId: string;\n specificationId: string;\n projectId: string;\n epicId: string;\n implementationSessionId?: string;\n status: 'active' | 'paused' | 'completed' | 'failed';\n startedAt: string;\n pausedAt?: string;\n completedAt?: string;\n actualHours?: number;\n filesCreated?: string[];\n filesModified?: string[];\n filesDeleted?: string[];\n testsPassed?: boolean;\n retryCount: number;\n retryLog?: unknown;\n actions?: unknown;\n completionAttempts?: unknown;\n tokensUsed?: { inputTokens: number; outputTokens: number };\n costUsd?: number;\n durationMs?: number;\n commitSha?: string;\n modelUsed?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TicketAcceptanceCriterion {\n id: string;\n ticketId: string;\n description: string;\n order: number;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport interface TicketImplementationStep {\n id: string;\n ticketId: string;\n text: string;\n order: number;\n createdAt?: string;\n updatedAt?: string;\n}\n\n// -----------------------------------------------------------------------------\n// Test result types\n// -----------------------------------------------------------------------------\n\nexport type TestType =\n | 'unit'\n | 'integration'\n | 'e2e'\n | 'manual'\n | 'typescript'\n | 'lint'\n | 'build'\n | 'typecheck'\n | 'format'\n | 'security'\n | 'custom';\n\nexport interface WorkSessionTestResult {\n id: string;\n workSessionId: string;\n testType: TestType;\n passed: boolean;\n totalTests: number;\n passedTests: number;\n failedTests: number;\n skippedTests: number;\n coverage?: number;\n errorDetails?: string;\n command?: string;\n exitCode?: number;\n durationMs?: number;\n errorCount?: number;\n warningCount?: number;\n summary?: string;\n suites?: Array<{\n name: string;\n passed: number;\n failed: number;\n skipped?: number;\n duration?: number;\n }>;\n runAt: string;\n}\n\n/** @deprecated Use WorkSessionTestResult instead */\nexport type TicketTestResult = WorkSessionTestResult;\n\n// -----------------------------------------------------------------------------\n// Git integration\n// -----------------------------------------------------------------------------\n\nexport type LinkType = 'commit' | 'pull_request' | 'branch';\n\nexport interface TicketLink {\n id: string;\n ticketId: string;\n linkType: LinkType;\n url: string;\n sha?: string;\n prNumber?: number;\n title?: string;\n status?: string;\n createdAt: string;\n}\n\n// -----------------------------------------------------------------------------\n// Silence unused-import warnings for enum aliases referenced only by JSDoc\n// -----------------------------------------------------------------------------\n// (Each of these is re-exported above; the local `import type` pulls them\n// in so that embedded JSDoc cross-references resolve in editors.)\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype _ReferencedEnums = SpecificationStatus | EpicStatus;\n"],"mappings":"AA+HO,SAAS,4BACd,UACuB;AACvB,SAAO,SAAS,IAAI,CAAC,GAAG,UAAU;AAChC,QAAI,OAAO,MAAM,UAAU;AACzB,aAAO;AAAA,QACL,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;AAAA,QAC7B,aAAa;AAAA,QACb,WAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,eAAe,EACf,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAG/B,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,MAAM;gBAFnB,OAAO,EAAE,MAAM,EACR,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM;CAKtB;AAED;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAGxB,UAAU,EAAE,MAAM;IAClB,SAAS,CAAC,EAAE,MAAM;gBAFzB,OAAO,EAAE,MAAM,EACR,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAUnE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EAAE,GACtB,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAeN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACrD,IAAI,CAsBN;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,MAAc,GAAG,IAAI,CAS1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAUnE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAMD,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,eAAe,EACf,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAG/B,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,MAAM;gBAFnB,OAAO,EAAE,MAAM,EACR,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM;CAKtB;AAED;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAGxB,UAAU,EAAE,MAAM;IAClB,SAAS,CAAC,EAAE,MAAM;gBAFzB,OAAO,EAAE,MAAM,EACR,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAUnE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EAAE,GACtB,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAeN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACrD,IAAI,CAsBN;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,MAAc,GAAG,IAAI,CAS1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAUnE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAMD,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAoLtD,CAAC;AAMF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,gBAAgB,CAkB7D;AA2BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAiCpD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,IAAI,CAKN"}
|
package/dist/validation/index.js
CHANGED
|
@@ -211,9 +211,6 @@ const toolValidators = {
|
|
|
211
211
|
if (args.filesDeleted !== void 0) {
|
|
212
212
|
validateArray(args.filesDeleted, "filesDeleted");
|
|
213
213
|
}
|
|
214
|
-
if (args.blockReason !== void 0) {
|
|
215
|
-
validateStringLength(args.blockReason, "blockReason", 1, 5e3);
|
|
216
|
-
}
|
|
217
214
|
if (args.clearBlockReason !== void 0 && typeof args.clearBlockReason !== "boolean") {
|
|
218
215
|
throw new Error("clearBlockReason must be a boolean");
|
|
219
216
|
}
|
|
@@ -254,9 +251,6 @@ const toolValidators = {
|
|
|
254
251
|
// Utilities
|
|
255
252
|
feedback: () => {
|
|
256
253
|
},
|
|
257
|
-
reset_work_session: (args) => {
|
|
258
|
-
validateRequired(args.specificationId, "specificationId");
|
|
259
|
-
},
|
|
260
254
|
link_pull_request: (args) => {
|
|
261
255
|
validateRequired(args.ticketId, "ticketId");
|
|
262
256
|
if (!args.prNumber && !args.prUrl) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/validation/index.ts"],"sourcesContent":["/**\n * Validation Utilities for MCP Tools\n *\n * Provides comprehensive input validation and error handling for all MCP tools.\n * Validation is run before each tool call to ensure proper argument types and formats.\n */\n\n// Re-export ticket validation utilities\nexport {\n type TicketComplexity,\n type WarningSeverity,\n type StrictnessLevel,\n type ValidationWarning,\n type TicketValidationResult,\n type ComplexityRules,\n type TicketValidationInput,\n validationRules,\n validateTicket,\n filterWarningsByStrictness,\n getComplexityRules,\n isValidComplexity,\n getComplexityDescription,\n suggestComplexity,\n} from './ticket-validation.js';\n\n/**\n * Custom error class for validation failures\n * Includes field name and error code for structured error handling\n */\nexport class ValidationError extends Error {\n constructor(\n message: string,\n public field: string,\n public code: string\n ) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * API Error class for handling HTTP errors from the backend\n */\nexport class ApiError extends Error {\n constructor(\n message: string,\n public statusCode: number,\n public errorCode?: string\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n// ============================================================================\n// Basic Validators\n// ============================================================================\n\n/**\n * Validate that a value is present (not null, undefined, or empty string)\n */\nexport function validateRequired(value: unknown, fieldName: string): void {\n if (value === undefined || value === null || value === '') {\n throw new ValidationError(\n `${fieldName} is required`,\n fieldName,\n 'REQUIRED'\n );\n }\n}\n\n/**\n * Validate that a value is a valid UUID format\n */\nexport function validateUUID(value: string, fieldName: string): void {\n validateRequired(value, fieldName);\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(value)) {\n throw new ValidationError(\n `${fieldName} must be a valid UUID`,\n fieldName,\n 'INVALID_UUID'\n );\n }\n}\n\n/**\n * Validate that a value is one of the allowed enum values\n */\nexport function validateEnum(\n value: string,\n fieldName: string,\n allowedValues: string[]\n): void {\n validateRequired(value, fieldName);\n if (!allowedValues.includes(value)) {\n throw new ValidationError(\n `${fieldName} must be one of: ${allowedValues.join(', ')}`,\n fieldName,\n 'INVALID_ENUM'\n );\n }\n}\n\n/**\n * Validate that a number is within a range\n */\nexport function validateRange(\n value: number,\n fieldName: string,\n min: number,\n max: number\n): void {\n if (typeof value !== 'number' || isNaN(value)) {\n throw new ValidationError(\n `${fieldName} must be a number`,\n fieldName,\n 'INVALID_NUMBER'\n );\n }\n if (value < min || value > max) {\n throw new ValidationError(\n `${fieldName} must be between ${min} and ${max}`,\n fieldName,\n 'OUT_OF_RANGE'\n );\n }\n}\n\n/**\n * Validate that a value is an array, with optional item validation\n */\nexport function validateArray(\n value: unknown,\n fieldName: string,\n itemValidator?: (item: unknown, index: number) => void\n): void {\n if (!Array.isArray(value)) {\n throw new ValidationError(\n `${fieldName} must be an array`,\n fieldName,\n 'INVALID_ARRAY'\n );\n }\n if (itemValidator) {\n value.forEach((item, index) => {\n try {\n itemValidator(item, index);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ValidationError(\n `${fieldName}[${index}]: ${message}`,\n `${fieldName}[${index}]`,\n 'INVALID_ITEM'\n );\n }\n });\n }\n}\n\n/**\n * Validate a 40-character hex SHA\n */\nexport function validateSha(value: string, fieldName: string = 'sha'): void {\n validateRequired(value, fieldName);\n if (!/^[a-f0-9]{40}$/i.test(value)) {\n throw new ValidationError(\n `${fieldName} must be a 40-character hex string`,\n fieldName,\n 'INVALID_SHA'\n );\n }\n}\n\n/**\n * Validate an ISO 8601 date string\n */\nexport function validateDate(value: string, fieldName: string): void {\n validateRequired(value, fieldName);\n const date = new Date(value);\n if (isNaN(date.getTime())) {\n throw new ValidationError(\n `${fieldName} must be a valid ISO 8601 date`,\n fieldName,\n 'INVALID_DATE'\n );\n }\n}\n\n/**\n * Validate a string length\n */\nexport function validateStringLength(\n value: string,\n fieldName: string,\n minLength: number,\n maxLength: number\n): void {\n if (typeof value !== 'string') {\n throw new ValidationError(\n `${fieldName} must be a string`,\n fieldName,\n 'INVALID_STRING'\n );\n }\n if (value.length < minLength || value.length > maxLength) {\n throw new ValidationError(\n `${fieldName} must be between ${minLength} and ${maxLength} characters`,\n fieldName,\n 'INVALID_LENGTH'\n );\n }\n}\n\n// ============================================================================\n// Tool-Specific Validators\n// ============================================================================\n\ntype ValidatorFn = (args: Record<string, unknown>) => void;\n\n/**\n * Validators for each tool (22 tools)\n * These are applied before the tool handler is called\n */\nexport const toolValidators: Record<string, ValidatorFn> = {\n // Queries\n get: (args) => {\n validateRequired(args.type, 'type');\n validateEnum(\n args.type as string,\n 'type',\n ['project', 'specification', 'epic', 'ticket', 'implementation_session']\n );\n },\n\n list: (args) => {\n validateRequired(args.type, 'type');\n validateEnum(\n args.type as string,\n 'type',\n ['projects', 'specifications', 'epics', 'tickets']\n );\n },\n\n search: (args) => {\n if (!args.query && !args.files && !args.tags && !args.relatedTo) {\n throw new ValidationError(\n 'At least one filter is required: query, files, tags, or relatedTo',\n 'query',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.query) {\n validateStringLength(args.query as string, 'query', 1, 500);\n }\n if (args.limit !== undefined) {\n validateRange(args.limit as number, 'limit', 1, 100);\n }\n if (args.offset !== undefined) {\n validateRange(args.offset as number, 'offset', 0, 10000);\n }\n },\n\n get_next_actionable_tickets: (args) => {\n // Either specificationId or projectId is required\n if (!args.specificationId && !args.projectId) {\n throw new ValidationError(\n 'Either specificationId or projectId is required',\n 'specificationId/projectId',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.limit !== undefined) {\n validateRange(args.limit as number, 'limit', 1, 100);\n }\n },\n\n get_blocked_tickets: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n get_report: (args) => {\n validateRequired(args.type, 'type');\n validateRequired(args.scope, 'scope');\n validateRequired(args.scopeId, 'scopeId');\n },\n\n // Lifecycle. Planning verbs inject identity from local config (M9): SPS takes\n // an optional specificationId (falls back to config), APS takes only\n // `operation` (sessionId injected), CPS takes nothing (sessionId injected).\n // So no specificationId/sessionId requirements here — the handlers resolve\n // and validate them.\n start_planning_session: () => {\n /* specificationId optional — handler falls back to config */\n },\n\n action_planning_session: (args) => {\n validateRequired(args.operation, 'operation');\n },\n\n complete_planning_session: () => {\n /* no args — sessionId injected from config */\n },\n\n start_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n },\n\n action_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n if (args.filesCreated !== undefined) {\n validateArray(args.filesCreated, 'filesCreated');\n }\n if (args.filesModified !== undefined) {\n validateArray(args.filesModified, 'filesModified');\n }\n if (args.filesDeleted !== undefined) {\n validateArray(args.filesDeleted, 'filesDeleted');\n }\n if (args.blockReason !== undefined) {\n validateStringLength(args.blockReason as string, 'blockReason', 1, 5000);\n }\n if (args.clearBlockReason !== undefined && typeof args.clearBlockReason !== 'boolean') {\n throw new Error('clearBlockReason must be a boolean');\n }\n },\n\n complete_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n validateRequired(args.summary, 'summary');\n if (args.summary) {\n validateStringLength(args.summary as string, 'summary', 1, 10000);\n }\n if (args.filesModified !== undefined) {\n validateArray(args.filesModified, 'filesModified');\n }\n if (args.filesCreated !== undefined) {\n validateArray(args.filesCreated, 'filesCreated');\n }\n if (args.filesDeleted !== undefined) {\n validateArray(args.filesDeleted, 'filesDeleted');\n }\n },\n\n start_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n action_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n complete_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n reopen_specification: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n // Mutation\n create_specification: (args) => {\n validateRequired(args.projectId, 'projectId');\n validateRequired(args.title, 'title');\n },\n\n // Utilities\n feedback: () => {\n // Validated by feedback handler\n },\n\n reset_work_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n link_pull_request: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n if (!args.prNumber && !args.prUrl) {\n throw new ValidationError(\n 'Either prNumber or prUrl must be provided',\n 'prNumber',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.prNumber !== undefined && typeof args.prNumber !== 'number') {\n throw new ValidationError(\n 'prNumber must be a number',\n 'prNumber',\n 'INVALID_NUMBER'\n );\n }\n if (args.prUrl !== undefined) {\n const url = args.prUrl as string;\n if (!url.includes('github.com') && !url.includes('gitlab.com') && !url.includes('bitbucket.org')) {\n throw new ValidationError(\n 'prUrl must be a valid GitHub, GitLab, or Bitbucket URL',\n 'prUrl',\n 'INVALID_URL'\n );\n }\n }\n },\n\n // Orchestration\n get_critical_path: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n get_dependency_tree: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n};\n\n// ============================================================================\n// Error Transformation\n// ============================================================================\n\n/**\n * MCP error response format\n */\nexport interface MCPErrorResponse {\n content: Array<{\n type: 'text';\n text: string;\n }>;\n isError: true;\n}\n\n/**\n * Transform an error into MCP error response format\n */\nexport function formatMCPError(error: Error): MCPErrorResponse {\n let message = error.message;\n\n if (error instanceof ValidationError) {\n message = `Validation error for '${error.field}': ${error.message} (${error.code})`;\n } else if (error instanceof ApiError) {\n message = formatApiErrorMessage(error);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: message,\n },\n ],\n isError: true,\n };\n}\n\n/**\n * Format API error message based on status code\n */\nfunction formatApiErrorMessage(error: ApiError): string {\n switch (error.statusCode) {\n case 400:\n return `Bad request: ${error.message}`;\n case 401:\n return 'Authentication failed. Check your API key.';\n case 403:\n return 'Permission denied. Your API key may lack required permissions.';\n case 404:\n return `Resource not found: ${error.message}`;\n case 429:\n return 'Rate limit exceeded. Please wait before retrying.';\n case 500:\n case 502:\n case 503:\n case 504:\n return 'Server error. Please try again later.';\n default:\n return error.message;\n }\n}\n\n/**\n * Transform HTTP/network errors into ApiError\n */\nexport function transformError(error: unknown): Error {\n if (error instanceof ValidationError || error instanceof ApiError) {\n return error;\n }\n\n if (error instanceof Error) {\n // Network errors\n if (error.message.includes('ECONNREFUSED')) {\n return new ApiError(\n 'Unable to connect to SpecForge API. Check your network connection.',\n 0,\n 'NETWORK_ERROR'\n );\n }\n if (error.message.includes('ETIMEDOUT')) {\n return new ApiError(\n 'Request timed out. Please try again.',\n 0,\n 'TIMEOUT'\n );\n }\n if (error.message.includes('ECONNRESET')) {\n return new ApiError(\n 'Connection was reset. Please try again.',\n 0,\n 'CONNECTION_RESET'\n );\n }\n\n return error;\n }\n\n return new Error(String(error));\n}\n\n/**\n * Run validation for a specific tool\n * @throws ValidationError if validation fails\n */\nexport function validateToolArgs(\n toolName: string,\n args: Record<string, unknown>\n): void {\n const validator = toolValidators[toolName];\n if (validator) {\n validator(args);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/validation/index.ts"],"sourcesContent":["/**\n * Validation Utilities for MCP Tools\n *\n * Provides comprehensive input validation and error handling for all MCP tools.\n * Validation is run before each tool call to ensure proper argument types and formats.\n */\n\n// Re-export ticket validation utilities\nexport {\n type TicketComplexity,\n type WarningSeverity,\n type StrictnessLevel,\n type ValidationWarning,\n type TicketValidationResult,\n type ComplexityRules,\n type TicketValidationInput,\n validationRules,\n validateTicket,\n filterWarningsByStrictness,\n getComplexityRules,\n isValidComplexity,\n getComplexityDescription,\n suggestComplexity,\n} from './ticket-validation.js';\n\n/**\n * Custom error class for validation failures\n * Includes field name and error code for structured error handling\n */\nexport class ValidationError extends Error {\n constructor(\n message: string,\n public field: string,\n public code: string\n ) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * API Error class for handling HTTP errors from the backend\n */\nexport class ApiError extends Error {\n constructor(\n message: string,\n public statusCode: number,\n public errorCode?: string\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n// ============================================================================\n// Basic Validators\n// ============================================================================\n\n/**\n * Validate that a value is present (not null, undefined, or empty string)\n */\nexport function validateRequired(value: unknown, fieldName: string): void {\n if (value === undefined || value === null || value === '') {\n throw new ValidationError(\n `${fieldName} is required`,\n fieldName,\n 'REQUIRED'\n );\n }\n}\n\n/**\n * Validate that a value is a valid UUID format\n */\nexport function validateUUID(value: string, fieldName: string): void {\n validateRequired(value, fieldName);\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(value)) {\n throw new ValidationError(\n `${fieldName} must be a valid UUID`,\n fieldName,\n 'INVALID_UUID'\n );\n }\n}\n\n/**\n * Validate that a value is one of the allowed enum values\n */\nexport function validateEnum(\n value: string,\n fieldName: string,\n allowedValues: string[]\n): void {\n validateRequired(value, fieldName);\n if (!allowedValues.includes(value)) {\n throw new ValidationError(\n `${fieldName} must be one of: ${allowedValues.join(', ')}`,\n fieldName,\n 'INVALID_ENUM'\n );\n }\n}\n\n/**\n * Validate that a number is within a range\n */\nexport function validateRange(\n value: number,\n fieldName: string,\n min: number,\n max: number\n): void {\n if (typeof value !== 'number' || isNaN(value)) {\n throw new ValidationError(\n `${fieldName} must be a number`,\n fieldName,\n 'INVALID_NUMBER'\n );\n }\n if (value < min || value > max) {\n throw new ValidationError(\n `${fieldName} must be between ${min} and ${max}`,\n fieldName,\n 'OUT_OF_RANGE'\n );\n }\n}\n\n/**\n * Validate that a value is an array, with optional item validation\n */\nexport function validateArray(\n value: unknown,\n fieldName: string,\n itemValidator?: (item: unknown, index: number) => void\n): void {\n if (!Array.isArray(value)) {\n throw new ValidationError(\n `${fieldName} must be an array`,\n fieldName,\n 'INVALID_ARRAY'\n );\n }\n if (itemValidator) {\n value.forEach((item, index) => {\n try {\n itemValidator(item, index);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ValidationError(\n `${fieldName}[${index}]: ${message}`,\n `${fieldName}[${index}]`,\n 'INVALID_ITEM'\n );\n }\n });\n }\n}\n\n/**\n * Validate a 40-character hex SHA\n */\nexport function validateSha(value: string, fieldName: string = 'sha'): void {\n validateRequired(value, fieldName);\n if (!/^[a-f0-9]{40}$/i.test(value)) {\n throw new ValidationError(\n `${fieldName} must be a 40-character hex string`,\n fieldName,\n 'INVALID_SHA'\n );\n }\n}\n\n/**\n * Validate an ISO 8601 date string\n */\nexport function validateDate(value: string, fieldName: string): void {\n validateRequired(value, fieldName);\n const date = new Date(value);\n if (isNaN(date.getTime())) {\n throw new ValidationError(\n `${fieldName} must be a valid ISO 8601 date`,\n fieldName,\n 'INVALID_DATE'\n );\n }\n}\n\n/**\n * Validate a string length\n */\nexport function validateStringLength(\n value: string,\n fieldName: string,\n minLength: number,\n maxLength: number\n): void {\n if (typeof value !== 'string') {\n throw new ValidationError(\n `${fieldName} must be a string`,\n fieldName,\n 'INVALID_STRING'\n );\n }\n if (value.length < minLength || value.length > maxLength) {\n throw new ValidationError(\n `${fieldName} must be between ${minLength} and ${maxLength} characters`,\n fieldName,\n 'INVALID_LENGTH'\n );\n }\n}\n\n// ============================================================================\n// Tool-Specific Validators\n// ============================================================================\n\ntype ValidatorFn = (args: Record<string, unknown>) => void;\n\n/**\n * Validators for each tool (22 tools)\n * These are applied before the tool handler is called\n */\nexport const toolValidators: Record<string, ValidatorFn> = {\n // Queries\n get: (args) => {\n validateRequired(args.type, 'type');\n validateEnum(\n args.type as string,\n 'type',\n ['project', 'specification', 'epic', 'ticket', 'implementation_session']\n );\n },\n\n list: (args) => {\n validateRequired(args.type, 'type');\n validateEnum(\n args.type as string,\n 'type',\n ['projects', 'specifications', 'epics', 'tickets']\n );\n },\n\n search: (args) => {\n if (!args.query && !args.files && !args.tags && !args.relatedTo) {\n throw new ValidationError(\n 'At least one filter is required: query, files, tags, or relatedTo',\n 'query',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.query) {\n validateStringLength(args.query as string, 'query', 1, 500);\n }\n if (args.limit !== undefined) {\n validateRange(args.limit as number, 'limit', 1, 100);\n }\n if (args.offset !== undefined) {\n validateRange(args.offset as number, 'offset', 0, 10000);\n }\n },\n\n get_next_actionable_tickets: (args) => {\n // Either specificationId or projectId is required\n if (!args.specificationId && !args.projectId) {\n throw new ValidationError(\n 'Either specificationId or projectId is required',\n 'specificationId/projectId',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.limit !== undefined) {\n validateRange(args.limit as number, 'limit', 1, 100);\n }\n },\n\n get_blocked_tickets: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n get_report: (args) => {\n validateRequired(args.type, 'type');\n validateRequired(args.scope, 'scope');\n validateRequired(args.scopeId, 'scopeId');\n },\n\n // Lifecycle. Planning verbs inject identity from local config (M9): SPS takes\n // an optional specificationId (falls back to config), APS takes only\n // `operation` (sessionId injected), CPS takes nothing (sessionId injected).\n // So no specificationId/sessionId requirements here — the handlers resolve\n // and validate them.\n start_planning_session: () => {\n /* specificationId optional — handler falls back to config */\n },\n\n action_planning_session: (args) => {\n validateRequired(args.operation, 'operation');\n },\n\n complete_planning_session: () => {\n /* no args — sessionId injected from config */\n },\n\n start_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n },\n\n action_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n if (args.filesCreated !== undefined) {\n validateArray(args.filesCreated, 'filesCreated');\n }\n if (args.filesModified !== undefined) {\n validateArray(args.filesModified, 'filesModified');\n }\n if (args.filesDeleted !== undefined) {\n validateArray(args.filesDeleted, 'filesDeleted');\n }\n if (args.clearBlockReason !== undefined && typeof args.clearBlockReason !== 'boolean') {\n throw new Error('clearBlockReason must be a boolean');\n }\n },\n\n complete_work_session: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n validateRequired(args.summary, 'summary');\n if (args.summary) {\n validateStringLength(args.summary as string, 'summary', 1, 10000);\n }\n if (args.filesModified !== undefined) {\n validateArray(args.filesModified, 'filesModified');\n }\n if (args.filesCreated !== undefined) {\n validateArray(args.filesCreated, 'filesCreated');\n }\n if (args.filesDeleted !== undefined) {\n validateArray(args.filesDeleted, 'filesDeleted');\n }\n },\n\n start_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n action_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n complete_review_session: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n reopen_specification: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n // Mutation\n create_specification: (args) => {\n validateRequired(args.projectId, 'projectId');\n validateRequired(args.title, 'title');\n },\n\n // Utilities\n feedback: () => {\n // Validated by feedback handler\n },\n\n link_pull_request: (args) => {\n validateRequired(args.ticketId, 'ticketId');\n if (!args.prNumber && !args.prUrl) {\n throw new ValidationError(\n 'Either prNumber or prUrl must be provided',\n 'prNumber',\n 'REQUIRED_ONE_OF'\n );\n }\n if (args.prNumber !== undefined && typeof args.prNumber !== 'number') {\n throw new ValidationError(\n 'prNumber must be a number',\n 'prNumber',\n 'INVALID_NUMBER'\n );\n }\n if (args.prUrl !== undefined) {\n const url = args.prUrl as string;\n if (!url.includes('github.com') && !url.includes('gitlab.com') && !url.includes('bitbucket.org')) {\n throw new ValidationError(\n 'prUrl must be a valid GitHub, GitLab, or Bitbucket URL',\n 'prUrl',\n 'INVALID_URL'\n );\n }\n }\n },\n\n // Orchestration\n get_critical_path: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n\n get_dependency_tree: (args) => {\n validateRequired(args.specificationId, 'specificationId');\n },\n};\n\n// ============================================================================\n// Error Transformation\n// ============================================================================\n\n/**\n * MCP error response format\n */\nexport interface MCPErrorResponse {\n content: Array<{\n type: 'text';\n text: string;\n }>;\n isError: true;\n}\n\n/**\n * Transform an error into MCP error response format\n */\nexport function formatMCPError(error: Error): MCPErrorResponse {\n let message = error.message;\n\n if (error instanceof ValidationError) {\n message = `Validation error for '${error.field}': ${error.message} (${error.code})`;\n } else if (error instanceof ApiError) {\n message = formatApiErrorMessage(error);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: message,\n },\n ],\n isError: true,\n };\n}\n\n/**\n * Format API error message based on status code\n */\nfunction formatApiErrorMessage(error: ApiError): string {\n switch (error.statusCode) {\n case 400:\n return `Bad request: ${error.message}`;\n case 401:\n return 'Authentication failed. Check your API key.';\n case 403:\n return 'Permission denied. Your API key may lack required permissions.';\n case 404:\n return `Resource not found: ${error.message}`;\n case 429:\n return 'Rate limit exceeded. Please wait before retrying.';\n case 500:\n case 502:\n case 503:\n case 504:\n return 'Server error. Please try again later.';\n default:\n return error.message;\n }\n}\n\n/**\n * Transform HTTP/network errors into ApiError\n */\nexport function transformError(error: unknown): Error {\n if (error instanceof ValidationError || error instanceof ApiError) {\n return error;\n }\n\n if (error instanceof Error) {\n // Network errors\n if (error.message.includes('ECONNREFUSED')) {\n return new ApiError(\n 'Unable to connect to SpecForge API. Check your network connection.',\n 0,\n 'NETWORK_ERROR'\n );\n }\n if (error.message.includes('ETIMEDOUT')) {\n return new ApiError(\n 'Request timed out. Please try again.',\n 0,\n 'TIMEOUT'\n );\n }\n if (error.message.includes('ECONNRESET')) {\n return new ApiError(\n 'Connection was reset. Please try again.',\n 0,\n 'CONNECTION_RESET'\n );\n }\n\n return error;\n }\n\n return new Error(String(error));\n}\n\n/**\n * Run validation for a specific tool\n * @throws ValidationError if validation fails\n */\nexport function validateToolArgs(\n toolName: string,\n args: Record<string, unknown>\n): void {\n const validator = toolValidators[toolName];\n if (validator) {\n validator(args);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specforge/canary-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "MCP server for SpecForge - AI agent integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"inquirer": "^9.3.8",
|
|
18
18
|
"minimatch": "^10.1.1",
|
|
19
19
|
"ora": "^8.2.0",
|
|
20
|
+
"@specforge/session-types": "0.2.47",
|
|
20
21
|
"@specforge/types": "1.0.2"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"tsx": "^4.7.0",
|
|
30
31
|
"typescript": "^5.0.0",
|
|
31
32
|
"vitest": "4.0.18",
|
|
32
|
-
"@specforge/lifecycle": "0.1.
|
|
33
|
+
"@specforge/lifecycle": "0.1.71"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=18.0.0"
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"bundleDependencies": [
|
|
55
56
|
"@specforge/types"
|
|
56
57
|
],
|
|
57
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "f475f360ff5a0e4b61e5cd2f1eb3995572020566",
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "tsup && tsc --emitDeclarationOnly --outDir dist",
|
|
60
61
|
"typecheck": "tsc --noEmit",
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SFAG-Orchestrator Agent Template
|
|
2
|
+
* SFAG-Orchestrator Agent Template v3 (M23.5)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Coordinates the AUTONOMOUS MULTI-AGENT work model:
|
|
5
|
+
*
|
|
6
|
+
* - N concurrent sfag-ticket-implementer workers → N WorkSessions under ONE
|
|
7
|
+
* spec-wide ImplementationSession. The FIRST worker's start_work_session
|
|
8
|
+
* creates that ImplementationSession (first-write-wins); every later SWS
|
|
9
|
+
* attaches its WorkSession to the same session.
|
|
10
|
+
* - The orchestrator assigns tickets respecting the DAG (dependency-free
|
|
11
|
+
* `ready` tickets only) and dispatches workers up to the configured
|
|
12
|
+
* concurrency; as tickets reach `done`, the readiness cascade unblocks
|
|
13
|
+
* dependents and the orchestrator dispatches the newly-ready.
|
|
14
|
+
* - There is NO review/dismissal coordination in the work chain (the review
|
|
15
|
+
* lifecycle is dormant). Blockers/discoveries are RECORDED by workers and
|
|
16
|
+
* handed to the sfag-work-resolver agent (human-in-the-loop); the human's
|
|
17
|
+
* `resolve_discovery` (web app) unblocks a blocking discovery.
|
|
18
|
+
*
|
|
19
|
+
* The orchestrator uses only SHIPPED read ops (get_dependency_tree,
|
|
20
|
+
* get_critical_path, get_next_actionable_tickets, get_implementation_status,
|
|
21
|
+
* get_blocked_tickets, get_pending_discoveries). The agent-teams ops
|
|
22
|
+
* (get_epic_dependency_graph, get_implementation_plan, report_completion) are
|
|
23
|
+
* deferred to 0.2.0+ and are NOT referenced here.
|
|
6
24
|
*/
|
|
7
25
|
|
|
8
26
|
import type { AgentTemplate } from '../../../../commands/scaffold/agent-types.js';
|
|
9
27
|
|
|
10
28
|
export const SFAG_ORCHESTRATOR: AgentTemplate = {
|
|
11
29
|
name: 'sfag-orchestrator',
|
|
12
|
-
description: 'Decompose complex tasks and coordinate
|
|
13
|
-
triggerDescription: `Use this agent when a task spans multiple domains and requires coordination between specialized agents. The orchestrator decides WHAT to delegate, to WHOM, and in WHAT ORDER.
|
|
30
|
+
description: 'Decompose complex tasks and coordinate autonomous multi-agent implementation',
|
|
31
|
+
triggerDescription: `Use this agent when a task spans multiple domains and requires coordination between specialized agents. The orchestrator decides WHAT to delegate, to WHOM, and in WHAT ORDER — and it runs a fleet of autonomous ticket-implementers concurrently, respecting the dependency graph.
|
|
14
32
|
|
|
15
33
|
<example>
|
|
16
34
|
Context: User requests a full feature that needs spec + implementation + tests
|
|
@@ -19,9 +37,9 @@ assistant: "This spans multiple domains. Launching sfag-orchestrator to decompos
|
|
|
19
37
|
</example>
|
|
20
38
|
|
|
21
39
|
<example>
|
|
22
|
-
Context: User has a
|
|
23
|
-
user: "
|
|
24
|
-
assistant: "
|
|
40
|
+
Context: User has a spec with many ready tickets and wants them built in parallel
|
|
41
|
+
user: "Toca a implementação toda dessa spec, em paralelo onde der"
|
|
42
|
+
assistant: "Launching sfag-orchestrator to dispatch autonomous workers across the ready tickets, respecting the DAG."
|
|
25
43
|
</example>
|
|
26
44
|
|
|
27
45
|
<example>
|
|
@@ -35,7 +53,9 @@ assistant: "Launching sfag-orchestrator to coordinate a multi-perspective analys
|
|
|
35
53
|
memory: 'project',
|
|
36
54
|
content: `# SpecForge Orchestrator Agent
|
|
37
55
|
|
|
38
|
-
You are the brain. You don't write code. You don't write specs. You decide WHO does WHAT and WHEN,
|
|
56
|
+
You are the brain. You don't write code. You don't write specs. You decide WHO does WHAT and WHEN,
|
|
57
|
+
then you make it happen. For implementation you run a FLEET of autonomous workers concurrently —
|
|
58
|
+
you dispatch, you watch, you re-dispatch. You never implement.
|
|
39
59
|
|
|
40
60
|
## Context Bootstrapping
|
|
41
61
|
|
|
@@ -44,7 +64,7 @@ Before any decision, read the project context from the local config:
|
|
|
44
64
|
Read .specforge.json from project root → extract:
|
|
45
65
|
- project.id → projectId
|
|
46
66
|
- activeSpecification.id → specificationId (may be null if no spec exists yet)
|
|
47
|
-
- agentTeams config (
|
|
67
|
+
- agentTeams config (strategy, maxParallelEpics, maxTicketsPerTeam, branchPrefix, timeoutMinutes)
|
|
48
68
|
\`\`\`
|
|
49
69
|
All tool calls that need projectId/specificationId use these values. No session store, no get_working_context.
|
|
50
70
|
|
|
@@ -53,8 +73,31 @@ All tool calls that need projectId/specificationId use these values. No session
|
|
|
53
73
|
| Agent | What it does | When to use |
|
|
54
74
|
|-------|-------------|-------------|
|
|
55
75
|
| **sfag-spec-creator** | Dense interrogation → SpecForge spec | When requirements are unclear or no spec exists |
|
|
56
|
-
| **sfag-ticket-implementer** | Lifecycle-tracked ticket implementation | When a spec exists and tickets are ready |
|
|
57
76
|
| **sfag-package-researcher** | Web research for packages/APIs/docs | When external knowledge is needed before implementation |
|
|
77
|
+
| **sfag-ticket-implementer** | Autonomous ticket implementation over the work lifecycle (SWS/AWS/CWS) | When a spec exists and tickets are \`ready\` — dispatch ONE worker per ready ticket |
|
|
78
|
+
| **sfag-work-resolver** | Human-in-the-loop triage of blockers/discoveries | When a worker records a blocking discovery or the DAG stalls on blocked tickets |
|
|
79
|
+
|
|
80
|
+
## The autonomous multi-agent work model
|
|
81
|
+
|
|
82
|
+
This is how implementation runs. Internalize it before dispatching anything.
|
|
83
|
+
|
|
84
|
+
- **N workers → N WorkSessions → ONE ImplementationSession.** You dispatch several
|
|
85
|
+
\`sfag-ticket-implementer\` workers at once, one per \`ready\` ticket. Each worker opens its own
|
|
86
|
+
WorkSession with \`start_work_session\`. The **first** SWS for the spec creates the spec-wide
|
|
87
|
+
**ImplementationSession** (first-write-wins); every later worker's SWS attaches its WorkSession to
|
|
88
|
+
that same ImplementationSession. You do not create the ImplementationSession — the first worker does.
|
|
89
|
+
- **Each worker is fully autonomous.** It picks up its ticket, runs the whole SWS → action_work_session
|
|
90
|
+
→ complete_work_session loop, records every dimension through the assay, commits, and finalizes
|
|
91
|
+
\`active → done\` with no human touch. You do not step inside a worker's loop.
|
|
92
|
+
- **Isolate the workers.** Give each worker its own git worktree/branch (use the \`branchPrefix\` from
|
|
93
|
+
config, e.g. \`ticket/<ref>\`) so concurrent sessions don't collide on the worktree. SWS enforces a
|
|
94
|
+
clean worktree per session.
|
|
95
|
+
- **Respect the DAG.** Only \`ready\` (dependency-free) tickets are dispatchable. When a worker completes
|
|
96
|
+
a ticket, the readiness cascade unblocks its dependents (\`pending → ready\`); you then dispatch the
|
|
97
|
+
newly-ready ones. Never dispatch a ticket whose dependencies aren't \`done\`.
|
|
98
|
+
- **No review coordination.** The review lifecycle is dormant — there is no reviewer to wait on, no
|
|
99
|
+
approval/dismissal gate to coordinate. A worker self-completes through the CWS gates. Do NOT wait for
|
|
100
|
+
a review step; it does not exist in the work chain.
|
|
58
101
|
|
|
59
102
|
## Decision Tree
|
|
60
103
|
|
|
@@ -68,30 +111,65 @@ When a task arrives, follow this tree:
|
|
|
68
111
|
|
|
69
112
|
### 2. Does the task require external package/API knowledge?
|
|
70
113
|
|
|
71
|
-
**YES →** Launch \`sfag-package-researcher\` BEFORE implementation. Feed research output into
|
|
114
|
+
**YES →** Launch \`sfag-package-researcher\` BEFORE implementation. Feed research output into the tickets.
|
|
72
115
|
|
|
73
116
|
**NO →** Continue to step 3.
|
|
74
117
|
|
|
75
|
-
### 3. Are tickets created and ready
|
|
118
|
+
### 3. Are tickets created and \`ready\`?
|
|
76
119
|
|
|
77
|
-
**NO →**
|
|
120
|
+
**NO →** If the spec needs more tickets, route back to \`sfag-spec-creator\` for ticket creation. If
|
|
121
|
+
tickets exist but none are \`ready\`, diagnose the DAG:
|
|
78
122
|
\`\`\`
|
|
79
|
-
get_blocked_tickets({ specificationId })
|
|
80
123
|
get_dependency_tree({ specificationId })
|
|
124
|
+
get_blocked_tickets({ specificationId })
|
|
81
125
|
\`\`\`
|
|
126
|
+
If tickets are \`blocked\`, that is a resolver job (step 5) — not something you implement around.
|
|
82
127
|
|
|
83
|
-
**YES →**
|
|
128
|
+
**YES →** Continue to step 4 and dispatch workers.
|
|
84
129
|
|
|
85
|
-
### 4.
|
|
130
|
+
### 4. Dispatch the worker fleet
|
|
86
131
|
|
|
87
|
-
|
|
132
|
+
Read the DAG and the current dispatch state:
|
|
88
133
|
\`\`\`
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
get_dependency_tree({ specificationId }) // the dependency graph
|
|
135
|
+
get_critical_path({ specificationId }) // longest chain — sequence priority
|
|
136
|
+
get_next_actionable_tickets({ specificationId, limit }) // the ready tickets to dispatch NOW
|
|
137
|
+
get_implementation_status({ projectId, specificationId, status: "active" }) // who is already running
|
|
91
138
|
\`\`\`
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
139
|
+
Then dispatch:
|
|
140
|
+
- Launch one \`sfag-ticket-implementer\` per \`ready\` ticket, each in its own worktree/branch.
|
|
141
|
+
- Bound concurrency by the config: at most \`maxParallelEpics\` epics in flight and \`maxTicketsPerTeam\`
|
|
142
|
+
tickets per epic team. If the strategy is \`single\`, run one worker at a time; \`parallel\` runs
|
|
143
|
+
independent epics concurrently; \`phased\` runs the DAG in dependency-ordered phases; \`auto\` picks
|
|
144
|
+
based on the graph (parallel when tickets are independent, phased when there are cross-epic deps).
|
|
145
|
+
- Prioritize tickets on the critical path — they gate the most downstream work.
|
|
146
|
+
|
|
147
|
+
### 5. Coordinate around blockers/discoveries → hand to the resolver
|
|
148
|
+
|
|
149
|
+
A worker that hits something it can't get past **records a blocking discovery** — that IS the block
|
|
150
|
+
(the ticket → \`blocked\`, the WorkSession pauses) — and then moves on to the next \`ready\` ticket. You
|
|
151
|
+
do NOT resolve blockers and you do NOT unblock tickets. Instead:
|
|
152
|
+
\`\`\`
|
|
153
|
+
get_implementation_status({ projectId, specificationId, status: "blocked" }) // blocked sessions
|
|
154
|
+
get_implementation_status({ projectId, specificationId, status: "paused" }) // paused / awaiting-human
|
|
155
|
+
get_blocked_tickets({ specificationId })
|
|
156
|
+
get_pending_discoveries({ specificationId })
|
|
157
|
+
\`\`\`
|
|
158
|
+
When blockers/discoveries pile up (or the DAG stalls with ready tickets exhausted but work \`blocked\`),
|
|
159
|
+
**hand them to \`sfag-work-resolver\`**. That agent triages each one WITH the human and — for a blocking
|
|
160
|
+
discovery — points the human at \`resolve_discovery\` in the web app, which flips the ticket
|
|
161
|
+
\`blocked → pending\`; the cascade then re-derives it \`→ ready\`. \`resolve_discovery\` is a webapp action,
|
|
162
|
+
not a tool you can call.
|
|
163
|
+
|
|
164
|
+
### 6. Keep the fleet full
|
|
165
|
+
|
|
166
|
+
Loop until the spec is done:
|
|
167
|
+
1. Poll \`get_implementation_status({ status: "active" })\` + \`get_next_actionable_tickets(...)\`.
|
|
168
|
+
2. For every worker slot free (under the concurrency bound), dispatch the next \`ready\` ticket.
|
|
169
|
+
3. When a ticket finalizes \`→ done\`, the cascade unblocks its dependents — dispatch those next.
|
|
170
|
+
4. Send anything \`blocked\`/\`paused\` to \`sfag-work-resolver\`; re-dispatch once it's \`ready\` again
|
|
171
|
+
(SWS re-attaches the paused WorkSession and applies the human's resolution).
|
|
172
|
+
When every spec ticket is \`done\`, the last CWS finalizes the ImplementationSession and the spec → done.
|
|
95
173
|
|
|
96
174
|
## Coordination Patterns
|
|
97
175
|
|
|
@@ -101,14 +179,16 @@ sfag-spec-creator (interrogation → spec + epics + tickets)
|
|
|
101
179
|
↓
|
|
102
180
|
sfag-package-researcher (if unknown packages involved)
|
|
103
181
|
↓
|
|
104
|
-
sfag-ticket-implementer (
|
|
182
|
+
sfag-ticket-implementer × N (autonomous fleet over the ready tickets, DAG-ordered)
|
|
183
|
+
↓ (on any blocker)
|
|
184
|
+
sfag-work-resolver (triage with human → resolve_discovery in web app → re-dispatch)
|
|
105
185
|
\`\`\`
|
|
106
186
|
|
|
107
187
|
### Pattern B: Add to Existing Spec
|
|
108
188
|
\`\`\`
|
|
109
189
|
Check spec status → create new epic/tickets if needed
|
|
110
190
|
↓
|
|
111
|
-
sfag-ticket-implementer (new tickets only)
|
|
191
|
+
sfag-ticket-implementer × N (new ready tickets only)
|
|
112
192
|
\`\`\`
|
|
113
193
|
|
|
114
194
|
### Pattern C: Research-First Implementation
|
|
@@ -117,49 +197,53 @@ sfag-package-researcher (gather docs, patterns, gotchas)
|
|
|
117
197
|
↓
|
|
118
198
|
Feed research into ticket notes/context
|
|
119
199
|
↓
|
|
120
|
-
sfag-ticket-implementer (implement with research context)
|
|
200
|
+
sfag-ticket-implementer × N (implement with research context)
|
|
121
201
|
\`\`\`
|
|
122
202
|
|
|
123
|
-
### Pattern D:
|
|
124
|
-
When tickets are independent (no dependency chain):
|
|
203
|
+
### Pattern D: Parallel Fleet
|
|
204
|
+
When ready tickets are independent (no dependency chain between them):
|
|
125
205
|
\`\`\`
|
|
126
|
-
sfag-ticket-implementer (ticket A) ─┐
|
|
127
|
-
sfag-ticket-implementer (ticket B) ─┼→
|
|
128
|
-
sfag-ticket-implementer (ticket C) ─┘
|
|
206
|
+
sfag-ticket-implementer (ticket A, worktree A) ─┐
|
|
207
|
+
sfag-ticket-implementer (ticket B, worktree B) ─┼→ each SWS attaches to the one ImplementationSession
|
|
208
|
+
sfag-ticket-implementer (ticket C, worktree C) ─┘ poll get_implementation_status until all done
|
|
129
209
|
\`\`\`
|
|
130
210
|
|
|
131
211
|
## Your Responsibilities
|
|
132
212
|
|
|
133
213
|
### Before Delegation
|
|
134
|
-
- Understand the full scope of the request
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
- Load relevant context for the agents you're about to launch
|
|
214
|
+
- Understand the full scope of the request.
|
|
215
|
+
- Read SpecForge state: existing specs, the DAG, ticket statuses, blockers, open discoveries.
|
|
216
|
+
- Pick the strategy (single / parallel / phased / auto) from config and the graph shape.
|
|
217
|
+
- Load relevant context for the agents you're about to launch.
|
|
138
218
|
|
|
139
219
|
### During Execution
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
- Maintain the execution plan — update
|
|
220
|
+
- Keep the worker fleet full up to the concurrency bound; dispatch newly-ready tickets as dependents unblock.
|
|
221
|
+
- Poll \`get_implementation_status\` to track which WorkSessions are active / blocked / paused.
|
|
222
|
+
- Route every blocker/discovery to \`sfag-work-resolver\`; never implement around it and never unblock yourself.
|
|
223
|
+
- Maintain the execution plan — update it as the readiness cascade shifts the ready set.
|
|
144
224
|
|
|
145
225
|
### After Completion
|
|
146
|
-
- Verify all
|
|
147
|
-
- Report summary to user: what was done, what's
|
|
148
|
-
- Suggest next steps if work remains
|
|
226
|
+
- Verify all tickets reached \`done\` (\`get_implementation_status\`, \`get_next_actionable_tickets\` empty).
|
|
227
|
+
- Report a summary to the user: what was done, what's still \`blocked\`/awaiting the human, what's next.
|
|
149
228
|
|
|
150
229
|
## What You Are NOT
|
|
151
230
|
|
|
152
|
-
- You are NOT an implementer. Don't write code.
|
|
153
|
-
- You are NOT a spec creator. Don't interrogate requirements. Delegate to spec-creator
|
|
154
|
-
- You are NOT a researcher. Don't search the web. Delegate to package-researcher
|
|
155
|
-
- You
|
|
231
|
+
- You are NOT an implementer. Don't write code. Dispatch \`sfag-ticket-implementer\` workers.
|
|
232
|
+
- You are NOT a spec creator. Don't interrogate requirements. Delegate to \`sfag-spec-creator\`.
|
|
233
|
+
- You are NOT a researcher. Don't search the web. Delegate to \`sfag-package-researcher\`.
|
|
234
|
+
- You are NOT a resolver. You never resolve discoveries or unblock tickets — that's \`sfag-work-resolver\`
|
|
235
|
+
plus the human's \`resolve_discovery\` in the web app.
|
|
236
|
+
- You are NOT a reviewer. The review lifecycle is dormant; there is no review/dismissal step to run.
|
|
237
|
+
- You ARE the one who plans, sequences the DAG, keeps the fleet full, and ensures nothing stalls silently.
|
|
156
238
|
|
|
157
239
|
## Anti-Patterns
|
|
158
240
|
|
|
159
|
-
- ❌ Don't launch
|
|
160
|
-
- ❌ Don't
|
|
161
|
-
- ❌ Don't
|
|
162
|
-
- ❌ Don't
|
|
163
|
-
- ❌ Don't
|
|
241
|
+
- ❌ Don't launch a worker without a spec. Spec-creator goes first.
|
|
242
|
+
- ❌ Don't dispatch a ticket out of dependency order. Only \`ready\` (dependency-free) tickets are dispatchable.
|
|
243
|
+
- ❌ Don't run workers in the same worktree. Give each its own worktree/branch or SWS collides on git-clean.
|
|
244
|
+
- ❌ Don't create the ImplementationSession yourself. The first worker's SWS creates it (first-write-wins).
|
|
245
|
+
- ❌ Don't wait for a review/approval step — there isn't one. Workers self-complete through the CWS gates.
|
|
246
|
+
- ❌ Don't resolve or unblock a discovery yourself. Hand it to \`sfag-work-resolver\`; the human unblocks in the web app.
|
|
247
|
+
- ❌ Don't silently swallow a stall. If ready tickets run out while work is \`blocked\`, surface it and route to the resolver.
|
|
164
248
|
`,
|
|
165
249
|
};
|