@specsafe/core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/agents/adapters/base.d.ts +44 -0
  2. package/dist/agents/adapters/base.d.ts.map +1 -0
  3. package/dist/agents/adapters/base.js +164 -0
  4. package/dist/agents/adapters/base.js.map +1 -0
  5. package/dist/agents/adapters/claude-code.d.ts +14 -0
  6. package/dist/agents/adapters/claude-code.d.ts.map +1 -0
  7. package/dist/agents/adapters/claude-code.js +120 -0
  8. package/dist/agents/adapters/claude-code.js.map +1 -0
  9. package/dist/agents/adapters/copilot.d.ts +13 -0
  10. package/dist/agents/adapters/copilot.d.ts.map +1 -0
  11. package/dist/agents/adapters/copilot.js +115 -0
  12. package/dist/agents/adapters/copilot.js.map +1 -0
  13. package/dist/agents/adapters/cursor.d.ts +13 -0
  14. package/dist/agents/adapters/cursor.d.ts.map +1 -0
  15. package/dist/agents/adapters/cursor.js +105 -0
  16. package/dist/agents/adapters/cursor.js.map +1 -0
  17. package/dist/agents/adapters/gemini-cli.d.ts +13 -0
  18. package/dist/agents/adapters/gemini-cli.d.ts.map +1 -0
  19. package/dist/agents/adapters/gemini-cli.js +79 -0
  20. package/dist/agents/adapters/gemini-cli.js.map +1 -0
  21. package/dist/agents/adapters/index.d.ts +16 -0
  22. package/dist/agents/adapters/index.d.ts.map +1 -0
  23. package/dist/agents/adapters/index.js +47 -0
  24. package/dist/agents/adapters/index.js.map +1 -0
  25. package/dist/agents/adapters/opencode.d.ts +13 -0
  26. package/dist/agents/adapters/opencode.d.ts.map +1 -0
  27. package/dist/agents/adapters/opencode.js +67 -0
  28. package/dist/agents/adapters/opencode.js.map +1 -0
  29. package/dist/agents/index.d.ts +8 -0
  30. package/dist/agents/index.d.ts.map +1 -0
  31. package/dist/agents/index.js +9 -0
  32. package/dist/agents/index.js.map +1 -0
  33. package/dist/agents/registry.d.ts +70 -0
  34. package/dist/agents/registry.d.ts.map +1 -0
  35. package/dist/agents/registry.js +194 -0
  36. package/dist/agents/registry.js.map +1 -0
  37. package/dist/agents/types.d.ts +71 -0
  38. package/dist/agents/types.d.ts.map +1 -0
  39. package/dist/agents/types.js +6 -0
  40. package/dist/agents/types.js.map +1 -0
  41. package/dist/delta/merger.d.ts +36 -0
  42. package/dist/delta/merger.d.ts.map +1 -0
  43. package/dist/delta/merger.js +264 -0
  44. package/dist/delta/merger.js.map +1 -0
  45. package/dist/delta/parser.d.ts +27 -0
  46. package/dist/delta/parser.d.ts.map +1 -0
  47. package/dist/delta/parser.js +196 -0
  48. package/dist/delta/parser.js.map +1 -0
  49. package/dist/delta/types.d.ts +39 -0
  50. package/dist/delta/types.d.ts.map +1 -0
  51. package/dist/delta/types.js +6 -0
  52. package/dist/delta/types.js.map +1 -0
  53. package/dist/ears/index.d.ts +11 -0
  54. package/dist/ears/index.d.ts.map +1 -0
  55. package/dist/ears/index.js +11 -0
  56. package/dist/ears/index.js.map +1 -0
  57. package/dist/ears/parser.d.ts +22 -0
  58. package/dist/ears/parser.d.ts.map +1 -0
  59. package/dist/ears/parser.js +273 -0
  60. package/dist/ears/parser.js.map +1 -0
  61. package/dist/ears/template.d.ts +20 -0
  62. package/dist/ears/template.d.ts.map +1 -0
  63. package/dist/ears/template.js +364 -0
  64. package/dist/ears/template.js.map +1 -0
  65. package/dist/ears/types.d.ts +58 -0
  66. package/dist/ears/types.d.ts.map +1 -0
  67. package/dist/ears/types.js +6 -0
  68. package/dist/ears/types.js.map +1 -0
  69. package/dist/ears/validator.d.ts +37 -0
  70. package/dist/ears/validator.d.ts.map +1 -0
  71. package/dist/ears/validator.js +234 -0
  72. package/dist/ears/validator.js.map +1 -0
  73. package/dist/elicitation/engine.d.ts +75 -0
  74. package/dist/elicitation/engine.d.ts.map +1 -0
  75. package/dist/elicitation/engine.js +174 -0
  76. package/dist/elicitation/engine.js.map +1 -0
  77. package/dist/elicitation/flows.d.ts +18 -0
  78. package/dist/elicitation/flows.d.ts.map +1 -0
  79. package/dist/elicitation/flows.js +331 -0
  80. package/dist/elicitation/flows.js.map +1 -0
  81. package/dist/elicitation/generator.d.ts +20 -0
  82. package/dist/elicitation/generator.d.ts.map +1 -0
  83. package/dist/elicitation/generator.js +260 -0
  84. package/dist/elicitation/generator.js.map +1 -0
  85. package/dist/elicitation/index.d.ts +27 -0
  86. package/dist/elicitation/index.d.ts.map +1 -0
  87. package/dist/elicitation/index.js +29 -0
  88. package/dist/elicitation/index.js.map +1 -0
  89. package/dist/elicitation/types.d.ts +69 -0
  90. package/dist/elicitation/types.d.ts.map +1 -0
  91. package/dist/elicitation/types.js +6 -0
  92. package/dist/elicitation/types.js.map +1 -0
  93. package/dist/extensions/builtins/complexity.d.ts +7 -0
  94. package/dist/extensions/builtins/complexity.d.ts.map +1 -0
  95. package/dist/extensions/builtins/complexity.js +97 -0
  96. package/dist/extensions/builtins/complexity.js.map +1 -0
  97. package/dist/extensions/builtins/owasp.d.ts +7 -0
  98. package/dist/extensions/builtins/owasp.d.ts.map +1 -0
  99. package/dist/extensions/builtins/owasp.js +76 -0
  100. package/dist/extensions/builtins/owasp.js.map +1 -0
  101. package/dist/extensions/index.d.ts +54 -0
  102. package/dist/extensions/index.d.ts.map +1 -0
  103. package/dist/extensions/index.js +72 -0
  104. package/dist/extensions/index.js.map +1 -0
  105. package/dist/extensions/loader.d.ts +28 -0
  106. package/dist/extensions/loader.d.ts.map +1 -0
  107. package/dist/extensions/loader.js +62 -0
  108. package/dist/extensions/loader.js.map +1 -0
  109. package/dist/extensions/registry.d.ts +74 -0
  110. package/dist/extensions/registry.d.ts.map +1 -0
  111. package/dist/extensions/registry.js +159 -0
  112. package/dist/extensions/registry.js.map +1 -0
  113. package/dist/extensions/types.d.ts +70 -0
  114. package/dist/extensions/types.d.ts.map +1 -0
  115. package/dist/extensions/types.js +2 -0
  116. package/dist/extensions/types.js.map +1 -0
  117. package/dist/governance/builtins.d.ts +7 -0
  118. package/dist/governance/builtins.d.ts.map +1 -0
  119. package/dist/governance/builtins.js +105 -0
  120. package/dist/governance/builtins.js.map +1 -0
  121. package/dist/governance/constitution.d.ts +23 -0
  122. package/dist/governance/constitution.d.ts.map +1 -0
  123. package/dist/governance/constitution.js +245 -0
  124. package/dist/governance/constitution.js.map +1 -0
  125. package/dist/governance/index.d.ts +3 -0
  126. package/dist/governance/index.d.ts.map +1 -0
  127. package/dist/governance/index.js +2 -0
  128. package/dist/governance/index.js.map +1 -0
  129. package/dist/governance/template.d.ts +12 -0
  130. package/dist/governance/template.d.ts.map +1 -0
  131. package/dist/governance/template.js +84 -0
  132. package/dist/governance/template.js.map +1 -0
  133. package/dist/governance/types.d.ts +64 -0
  134. package/dist/governance/types.d.ts.map +1 -0
  135. package/dist/governance/types.js +2 -0
  136. package/dist/governance/types.js.map +1 -0
  137. package/dist/index.d.ts +23 -18
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +17 -18
  140. package/dist/index.js.map +1 -1
  141. package/dist/templates/checklist.d.ts +7 -0
  142. package/dist/templates/checklist.d.ts.map +1 -0
  143. package/dist/templates/checklist.js +131 -0
  144. package/dist/templates/checklist.js.map +1 -0
  145. package/dist/templates/delta-template.d.ts +18 -0
  146. package/dist/templates/delta-template.d.ts.map +1 -0
  147. package/dist/templates/delta-template.js +191 -0
  148. package/dist/templates/delta-template.js.map +1 -0
  149. package/dist/templates/engine.d.ts +20 -0
  150. package/dist/templates/engine.d.ts.map +1 -0
  151. package/dist/templates/engine.js +187 -0
  152. package/dist/templates/engine.js.map +1 -0
  153. package/dist/templates/types.d.ts +67 -0
  154. package/dist/templates/types.d.ts.map +1 -0
  155. package/dist/templates/types.js +5 -0
  156. package/dist/templates/types.js.map +1 -0
  157. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Global extension registry
3
+ */
4
+ class ExtensionRegistryClass {
5
+ extensions = new Map();
6
+ hooks = new Map();
7
+ /**
8
+ * Register an extension
9
+ * @param extension - Extension to register
10
+ * @throws Error if extension with same ID already exists
11
+ */
12
+ register(extension) {
13
+ if (this.extensions.has(extension.id)) {
14
+ throw new Error(`Extension with ID "${extension.id}" is already registered`);
15
+ }
16
+ this.extensions.set(extension.id, extension);
17
+ // Register hooks
18
+ for (const [phase, handler] of Object.entries(extension.hooks)) {
19
+ if (typeof handler === 'function') {
20
+ const registration = {
21
+ extensionId: extension.id,
22
+ phase: phase,
23
+ handler,
24
+ priority: 0,
25
+ };
26
+ const phaseHooks = this.hooks.get(phase) || [];
27
+ phaseHooks.push(registration);
28
+ phaseHooks.sort((a, b) => (a.priority || 0) - (b.priority || 0));
29
+ this.hooks.set(phase, phaseHooks);
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Unregister an extension
35
+ * @param extensionId - ID of extension to unregister
36
+ * @returns true if extension was found and removed
37
+ */
38
+ unregister(extensionId) {
39
+ const extension = this.extensions.get(extensionId);
40
+ if (!extension) {
41
+ return false;
42
+ }
43
+ // Remove all hooks for this extension
44
+ for (const [phase, registrations] of this.hooks.entries()) {
45
+ const filtered = registrations.filter(r => r.extensionId !== extensionId);
46
+ if (filtered.length > 0) {
47
+ this.hooks.set(phase, filtered);
48
+ }
49
+ else {
50
+ this.hooks.delete(phase);
51
+ }
52
+ }
53
+ this.extensions.delete(extensionId);
54
+ return true;
55
+ }
56
+ /**
57
+ * Get an extension by ID
58
+ * @param extensionId - Extension ID
59
+ * @returns Extension or undefined if not found
60
+ */
61
+ get(extensionId) {
62
+ return this.extensions.get(extensionId);
63
+ }
64
+ /**
65
+ * Get all registered extensions
66
+ * @returns Array of all extensions
67
+ */
68
+ list() {
69
+ return Array.from(this.extensions.values());
70
+ }
71
+ /**
72
+ * Get enabled extensions only
73
+ * @returns Array of enabled extensions
74
+ */
75
+ listEnabled() {
76
+ return this.list().filter(ext => ext.enabled !== false);
77
+ }
78
+ /**
79
+ * Check if an extension exists
80
+ * @param extensionId - Extension ID
81
+ * @returns true if extension is registered
82
+ */
83
+ has(extensionId) {
84
+ return this.extensions.has(extensionId);
85
+ }
86
+ /**
87
+ * Enable an extension
88
+ * @param extensionId - Extension ID
89
+ * @returns true if extension was found and enabled
90
+ */
91
+ enable(extensionId) {
92
+ const extension = this.extensions.get(extensionId);
93
+ if (!extension) {
94
+ return false;
95
+ }
96
+ extension.enabled = true;
97
+ return true;
98
+ }
99
+ /**
100
+ * Disable an extension
101
+ * @param extensionId - Extension ID
102
+ * @returns true if extension was found and disabled
103
+ */
104
+ disable(extensionId) {
105
+ const extension = this.extensions.get(extensionId);
106
+ if (!extension) {
107
+ return false;
108
+ }
109
+ extension.enabled = false;
110
+ return true;
111
+ }
112
+ /**
113
+ * Get hooks for a specific phase
114
+ * @param phase - Hook phase
115
+ * @returns Array of hook registrations for this phase
116
+ */
117
+ getHooks(phase) {
118
+ return this.hooks.get(phase) || [];
119
+ }
120
+ /**
121
+ * Execute all hooks for a phase
122
+ * @param phase - Hook phase
123
+ * @param context - Extension context
124
+ * @returns Array of results from all hooks
125
+ */
126
+ async executeHooks(phase, context) {
127
+ const phaseHooks = this.getHooks(phase);
128
+ const results = [];
129
+ for (const hook of phaseHooks) {
130
+ const extension = this.extensions.get(hook.extensionId);
131
+ // Skip disabled extensions
132
+ if (!extension || extension.enabled === false) {
133
+ continue;
134
+ }
135
+ try {
136
+ const result = await hook.handler(context);
137
+ results.push(result);
138
+ }
139
+ catch (error) {
140
+ results.push({
141
+ success: false,
142
+ message: `Extension "${hook.extensionId}" failed`,
143
+ errors: [error instanceof Error ? error.message : String(error)],
144
+ });
145
+ }
146
+ }
147
+ return results;
148
+ }
149
+ /**
150
+ * Clear all extensions (useful for testing)
151
+ */
152
+ clear() {
153
+ this.extensions.clear();
154
+ this.hooks.clear();
155
+ }
156
+ }
157
+ // Export singleton instance
158
+ export const ExtensionRegistry = new ExtensionRegistryClass();
159
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/extensions/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import type { Spec } from '../types.js';
2
+ /**
3
+ * Extension hook phases in the SpecSafe workflow
4
+ */
5
+ export type ExtensionHook = 'pre-validate' | 'post-validate' | 'pre-generate' | 'post-generate' | 'pre-commit';
6
+ /**
7
+ * Context provided to extension hooks
8
+ */
9
+ export interface ExtensionContext {
10
+ /** The spec being processed */
11
+ spec: Spec;
12
+ /** Current phase of execution */
13
+ phase: ExtensionHook;
14
+ /** Additional metadata */
15
+ metadata?: Record<string, unknown>;
16
+ }
17
+ /**
18
+ * Result returned by extension hooks
19
+ */
20
+ export interface ExtensionResult {
21
+ /** Whether the extension check passed */
22
+ success: boolean;
23
+ /** Optional message describing the result */
24
+ message?: string;
25
+ /** Optional suggestions for improvement */
26
+ suggestions?: string[];
27
+ /** Optional warnings (non-blocking) */
28
+ warnings?: string[];
29
+ /** Optional errors (blocking) */
30
+ errors?: string[];
31
+ /** Additional data returned by the extension */
32
+ data?: Record<string, unknown>;
33
+ }
34
+ /**
35
+ * Extension definition
36
+ */
37
+ export interface Extension {
38
+ /** Unique identifier for the extension */
39
+ id: string;
40
+ /** Human-readable name */
41
+ name: string;
42
+ /** Description of what the extension does */
43
+ description: string;
44
+ /** Extension version */
45
+ version: string;
46
+ /** Author/maintainer */
47
+ author?: string;
48
+ /** Hooks this extension provides */
49
+ hooks: {
50
+ [K in ExtensionHook]?: (context: ExtensionContext) => Promise<ExtensionResult> | ExtensionResult;
51
+ };
52
+ /** Whether the extension is enabled */
53
+ enabled?: boolean;
54
+ /** Extension configuration */
55
+ config?: Record<string, unknown>;
56
+ }
57
+ /**
58
+ * Hook registration entry
59
+ */
60
+ export interface HookRegistration {
61
+ /** Extension that registered this hook */
62
+ extensionId: string;
63
+ /** Hook phase */
64
+ phase: ExtensionHook;
65
+ /** Hook handler function */
66
+ handler: (context: ExtensionContext) => Promise<ExtensionResult> | ExtensionResult;
67
+ /** Priority (lower = runs first) */
68
+ priority?: number;
69
+ }
70
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/extensions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,eAAe,GACf,cAAc,GACd,eAAe,GACf,YAAY,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,iCAAiC;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,KAAK,EAAE;SACJ,CAAC,IAAI,aAAa,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;KACjG,CAAC;IACF,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,4BAA4B;IAC5B,OAAO,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACnF,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/extensions/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { Principle, Gate, GatePhase } from './types.js';
2
+ export declare const BUILTIN_PRINCIPLES: Principle[];
3
+ export declare const BUILTIN_GATES: Gate[];
4
+ export declare function getBuiltinPrinciple(id: string): Principle | undefined;
5
+ export declare function getBuiltinGate(id: string): Gate | undefined;
6
+ export declare function getGatesForPhase(phase: GatePhase): Gate[];
7
+ //# sourceMappingURL=builtins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/governance/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAC;AAIpF,eAAO,MAAM,kBAAkB,EAAE,SAAS,EAOzC,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IAAI,EA4E/B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAErE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAE3D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAEzD"}
@@ -0,0 +1,105 @@
1
+ import { validateRequirements } from '../ears/validator.js';
2
+ export const BUILTIN_PRINCIPLES = [
3
+ { id: 'tdd-mandatory', name: 'TDD Mandatory', description: 'All specs must have test scenarios defined for each requirement', severity: 'error', immutable: true, metadata: { rationale: 'Test-driven development ensures testability and clear acceptance criteria', tags: ['testing', 'tdd', 'quality'] } },
4
+ { id: 'security-review-required', name: 'Security Review Required', description: 'Specs must include a security considerations section', severity: 'warning', immutable: true, metadata: { rationale: 'Security should be considered from the specification phase', tags: ['security', 'compliance'] } },
5
+ { id: 'max-spec-complexity', name: 'Maximum Spec Complexity', description: 'Spec must not exceed complexity threshold (max 10 requirements)', severity: 'warning', immutable: false, metadata: { rationale: 'Complex specs are harder to implement and test; break them down', tags: ['complexity', 'maintainability'] } },
6
+ { id: 'require-acceptance-criteria', name: 'Require Acceptance Criteria', description: 'All requirements must have clearly defined acceptance criteria', severity: 'error', immutable: true, metadata: { rationale: 'Acceptance criteria define "done" and enable effective testing', tags: ['testing', 'requirements'] } },
7
+ { id: 'no-spec-without-review', name: 'No Spec Without Review', description: 'Spec must have a reviewer assigned before moving to test phase', severity: 'warning', immutable: false, metadata: { rationale: 'Peer review catches issues early and improves spec quality', tags: ['review', 'quality'] } },
8
+ { id: 'require-ears-format', name: 'Require EARS Format', description: 'Requirements must follow EARS (Easy Approach to Requirements Syntax) patterns', severity: 'warning', immutable: false, metadata: { rationale: 'EARS patterns improve requirement clarity and testability', tags: ['requirements', 'ears', 'standards'] } },
9
+ ];
10
+ function checkViolation(spec, principle, condition, message, suggestion) {
11
+ return condition ? { principle, message, severity: principle.severity, spec, context: { suggestion } } : null;
12
+ }
13
+ export const BUILTIN_GATES = [
14
+ {
15
+ id: 'spec-phase-gate',
16
+ name: 'Spec Phase Gate',
17
+ phase: 'spec',
18
+ description: 'Validates specs before moving to test phase',
19
+ principles: ['tdd-mandatory', 'require-acceptance-criteria', 'max-spec-complexity', 'require-ears-format'],
20
+ check: (spec, principles) => {
21
+ const violations = [];
22
+ for (const p of principles) {
23
+ if (p.id === 'tdd-mandatory') {
24
+ const bad = spec.requirements.filter(r => !r.scenarios || r.scenarios.length === 0);
25
+ if (bad.length > 0) {
26
+ const v = checkViolation(spec, p, true, `Requirements without test scenarios: ${bad.map(r => r.id).join(', ')}`, 'Add at least one test scenario (Given/When/Then) for each requirement');
27
+ if (v)
28
+ violations.push(v);
29
+ }
30
+ }
31
+ if (p.id === 'require-acceptance-criteria') {
32
+ // Check for meaningful requirement text (minimum 20 characters)
33
+ const bad = spec.requirements.filter(r => !r.text || r.text.trim().length < 20);
34
+ if (bad.length > 0) {
35
+ const v = checkViolation(spec, p, true, `Requirements with insufficient acceptance criteria: ${bad.map(r => r.id).join(', ')}`, 'Provide detailed acceptance criteria text (minimum 20 characters) describing what constitutes "done"');
36
+ if (v)
37
+ violations.push(v);
38
+ }
39
+ }
40
+ if (p.id === 'max-spec-complexity') {
41
+ const v = checkViolation(spec, p, spec.requirements.length > 10, `Spec has ${spec.requirements.length} requirements (max: 10)`, 'Consider breaking this spec into multiple smaller specs');
42
+ if (v)
43
+ violations.push(v);
44
+ }
45
+ if (p.id === 'require-ears-format') {
46
+ const result = validateRequirements(spec);
47
+ const v = checkViolation(spec, p, result.score < 70, `EARS compliance score: ${result.score}% (min: 70%)`, result.recommendation);
48
+ if (v)
49
+ violations.push(v);
50
+ }
51
+ }
52
+ const errors = violations.filter(v => v.severity === 'error');
53
+ return {
54
+ passed: errors.length === 0,
55
+ violations,
56
+ gate: BUILTIN_GATES[0],
57
+ timestamp: new Date(),
58
+ summary: errors.length === 0 ? `✓ Spec phase gate passed` : `✗ Spec phase gate failed: ${errors.length} error(s), ${violations.length - errors.length} warning(s)`,
59
+ };
60
+ },
61
+ },
62
+ {
63
+ id: 'test-phase-gate',
64
+ name: 'Test Phase Gate',
65
+ phase: 'test',
66
+ description: 'Validates before moving to code phase',
67
+ principles: ['no-spec-without-review', 'security-review-required'],
68
+ check: (spec, principles) => {
69
+ const violations = [];
70
+ for (const p of principles) {
71
+ if (p.id === 'no-spec-without-review') {
72
+ const hasReviewer = spec.metadata && 'reviewer' in spec.metadata && spec.metadata.reviewer;
73
+ const v = checkViolation(spec, p, !hasReviewer, 'No reviewer assigned to this spec', 'Add a reviewer field to spec metadata before moving to test phase');
74
+ if (v)
75
+ violations.push(v);
76
+ }
77
+ if (p.id === 'security-review-required') {
78
+ const descLower = spec.description.toLowerCase();
79
+ const hasPositive = ['## security', '# security', 'security considerations:', 'authentication:', 'authorization:', 'access control', 'encryption', 'security requirements'].some(term => descLower.includes(term));
80
+ const v = checkViolation(spec, p, !hasPositive, 'Spec lacks security considerations section', 'Add a section describing security considerations, auth requirements, and data protection');
81
+ if (v)
82
+ violations.push(v);
83
+ }
84
+ }
85
+ const errors = violations.filter(v => v.severity === 'error');
86
+ return {
87
+ passed: errors.length === 0,
88
+ violations,
89
+ gate: BUILTIN_GATES[1],
90
+ timestamp: new Date(),
91
+ summary: errors.length === 0 ? `✓ Test phase gate passed` : `✗ Test phase gate failed: ${errors.length} error(s), ${violations.length - errors.length} warning(s)`,
92
+ };
93
+ },
94
+ },
95
+ ];
96
+ export function getBuiltinPrinciple(id) {
97
+ return BUILTIN_PRINCIPLES.find(p => p.id === id);
98
+ }
99
+ export function getBuiltinGate(id) {
100
+ return BUILTIN_GATES.find(g => g.id === id);
101
+ }
102
+ export function getGatesForPhase(phase) {
103
+ return BUILTIN_GATES.filter(g => g.phase === phase);
104
+ }
105
+ //# sourceMappingURL=builtins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtins.js","sourceRoot":"","sources":["../../src/governance/builtins.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Constitution, Principle, Gate, GateResult, ConstitutionLoadOptions, ValidationOptions } from './types.js';
2
+ import type { Spec } from '../types.js';
3
+ export declare class ConstitutionManager {
4
+ private constitution;
5
+ private projectDir;
6
+ constructor(projectDir?: string);
7
+ load(options?: ConstitutionLoadOptions): Promise<Constitution>;
8
+ private loadFromMarkdown;
9
+ private loadFromConfig;
10
+ private createDefault;
11
+ private mergeBuiltins;
12
+ private validateConstitution;
13
+ addPrinciple(principle: Principle): void;
14
+ removePrinciple(id: string): void;
15
+ listPrinciples(): Principle[];
16
+ getPrinciple(id: string): Principle | undefined;
17
+ validate(spec: Spec, options?: ValidationOptions): Promise<GateResult[]>;
18
+ getConstitution(): Constitution | null;
19
+ hasPrinciple(id: string): boolean;
20
+ getGatesForPhase(phase: string): Gate[];
21
+ save(): Promise<void>;
22
+ }
23
+ //# sourceMappingURL=constitution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constitution.d.ts","sourceRoot":"","sources":["../../src/governance/constitution.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACxH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAAsB;IAIxC,IAAI,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,YAAY,CAAC;YA0B1D,gBAAgB;YAqChB,cAAc;IAwB5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,oBAAoB;IAc5B,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAOxC,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IASjC,cAAc,IAAI,SAAS,EAAE;IAK7B,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAKzC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAyBlF,eAAe,IAAI,YAAY,GAAG,IAAI;IAItC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIjC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAKjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAkE5B"}
@@ -0,0 +1,245 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { BUILTIN_PRINCIPLES, BUILTIN_GATES } from './builtins.js';
5
+ export class ConstitutionManager {
6
+ constitution = null;
7
+ projectDir;
8
+ constructor(projectDir = process.cwd()) {
9
+ this.projectDir = projectDir;
10
+ }
11
+ async load(options = {}) {
12
+ const { includeBuiltins = true, validate = true } = options;
13
+ const constitutionMdPath = join(this.projectDir, '.specsafe', 'constitution.md');
14
+ if (existsSync(constitutionMdPath)) {
15
+ this.constitution = await this.loadFromMarkdown(constitutionMdPath);
16
+ }
17
+ else {
18
+ const configPath = join(this.projectDir, 'specsafe.config.json');
19
+ if (existsSync(configPath)) {
20
+ this.constitution = await this.loadFromConfig(configPath);
21
+ }
22
+ else {
23
+ this.constitution = this.createDefault();
24
+ }
25
+ }
26
+ if (includeBuiltins) {
27
+ this.mergeBuiltins();
28
+ }
29
+ if (validate) {
30
+ this.validateConstitution();
31
+ }
32
+ return this.constitution;
33
+ }
34
+ async loadFromMarkdown(path) {
35
+ const content = await readFile(path, 'utf-8');
36
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
37
+ if (!frontmatterMatch) {
38
+ throw new Error('Constitution markdown must have YAML frontmatter');
39
+ }
40
+ const frontmatter = frontmatterMatch[1];
41
+ const principles = [];
42
+ // Split on '- id:' but preserve it by including it in the match
43
+ const principleBlocks = frontmatter.split(/(?=\n- id:)/);
44
+ for (const block of principleBlocks) {
45
+ if (!block.trim() || !block.includes('- id:'))
46
+ continue;
47
+ const lines = block.split('\n');
48
+ const principle = {};
49
+ for (const line of lines) {
50
+ const trimmed = line.trim();
51
+ if (trimmed.startsWith('- id:'))
52
+ principle.id = trimmed.substring(5).trim();
53
+ else if (trimmed.startsWith('id:'))
54
+ principle.id = trimmed.substring(3).trim();
55
+ else if (trimmed.startsWith('name:'))
56
+ principle.name = trimmed.substring(5).trim();
57
+ else if (trimmed.startsWith('description:'))
58
+ principle.description = trimmed.substring(12).trim();
59
+ else if (trimmed.startsWith('severity:'))
60
+ principle.severity = trimmed.substring(9).trim();
61
+ else if (trimmed.startsWith('immutable:'))
62
+ principle.immutable = trimmed.substring(10).trim() === 'true';
63
+ }
64
+ if (principle.id && principle.name && principle.description) {
65
+ principles.push({ id: principle.id, name: principle.name, description: principle.description, severity: principle.severity || 'warning', immutable: principle.immutable ?? false });
66
+ }
67
+ }
68
+ return { principles, gates: [], metadata: { projectName: 'unknown', version: '1.0.0', createdAt: new Date(), updatedAt: new Date() } };
69
+ }
70
+ async loadFromConfig(path) {
71
+ const content = await readFile(path, 'utf-8');
72
+ const config = JSON.parse(content);
73
+ const constitution = config.constitution || this.createDefault();
74
+ if (constitution.metadata) {
75
+ if (constitution.metadata.createdAt)
76
+ constitution.metadata.createdAt = new Date(constitution.metadata.createdAt);
77
+ if (constitution.metadata.updatedAt)
78
+ constitution.metadata.updatedAt = new Date(constitution.metadata.updatedAt);
79
+ }
80
+ // Reconstruct gates from JSON - plain objects lack the check function
81
+ // Replace with matching builtin gates or remove invalid ones
82
+ if (constitution.gates && Array.isArray(constitution.gates)) {
83
+ constitution.gates = constitution.gates
84
+ .map((gate) => {
85
+ const builtinGate = BUILTIN_GATES.find(bg => bg.id === gate.id);
86
+ return builtinGate || null;
87
+ })
88
+ .filter((gate) => gate !== null);
89
+ }
90
+ return constitution;
91
+ }
92
+ createDefault() {
93
+ return { principles: [], gates: [], metadata: { projectName: 'unknown', version: '1.0.0', createdAt: new Date(), updatedAt: new Date(), description: 'Project governance constitution' } };
94
+ }
95
+ mergeBuiltins() {
96
+ if (!this.constitution)
97
+ return;
98
+ for (const builtinPrinciple of BUILTIN_PRINCIPLES) {
99
+ const exists = this.constitution.principles.some(p => p.id === builtinPrinciple.id);
100
+ if (!exists)
101
+ this.constitution.principles.push(builtinPrinciple);
102
+ }
103
+ for (const builtinGate of BUILTIN_GATES) {
104
+ const exists = this.constitution.gates.some(g => g.id === builtinGate.id);
105
+ if (!exists)
106
+ this.constitution.gates.push(builtinGate);
107
+ }
108
+ }
109
+ validateConstitution() {
110
+ if (!this.constitution)
111
+ throw new Error('No constitution loaded');
112
+ const principleIds = new Set();
113
+ for (const principle of this.constitution.principles) {
114
+ if (principleIds.has(principle.id))
115
+ throw new Error(`Duplicate principle ID: ${principle.id}`);
116
+ principleIds.add(principle.id);
117
+ }
118
+ const gateIds = new Set();
119
+ for (const gate of this.constitution.gates) {
120
+ if (gateIds.has(gate.id))
121
+ throw new Error(`Duplicate gate ID: ${gate.id}`);
122
+ gateIds.add(gate.id);
123
+ }
124
+ }
125
+ addPrinciple(principle) {
126
+ if (!this.constitution)
127
+ throw new Error('No constitution loaded');
128
+ if (this.constitution.principles.some(p => p.id === principle.id))
129
+ throw new Error(`Principle with ID "${principle.id}" already exists`);
130
+ this.constitution.principles.push(principle);
131
+ this.constitution.metadata.updatedAt = new Date();
132
+ }
133
+ removePrinciple(id) {
134
+ if (!this.constitution)
135
+ throw new Error('No constitution loaded');
136
+ const principle = this.constitution.principles.find(p => p.id === id);
137
+ if (!principle)
138
+ throw new Error(`Principle with ID "${id}" not found`);
139
+ if (principle.immutable)
140
+ throw new Error(`Principle "${id}" is immutable and cannot be removed`);
141
+ this.constitution.principles = this.constitution.principles.filter(p => p.id !== id);
142
+ this.constitution.metadata.updatedAt = new Date();
143
+ }
144
+ listPrinciples() {
145
+ if (!this.constitution)
146
+ throw new Error('No constitution loaded');
147
+ return [...this.constitution.principles];
148
+ }
149
+ getPrinciple(id) {
150
+ if (!this.constitution)
151
+ throw new Error('No constitution loaded');
152
+ return this.constitution.principles.find(p => p.id === id);
153
+ }
154
+ async validate(spec, options = {}) {
155
+ if (!this.constitution)
156
+ throw new Error('No constitution loaded. Call load() first.');
157
+ const { phase = spec.stage, failFast = false, includeWarnings = true } = options;
158
+ const results = [];
159
+ const applicableGates = this.constitution.gates.filter(g => g.phase === phase);
160
+ for (const gate of applicableGates) {
161
+ const gatePrinciples = gate.principles.map(id => this.constitution.principles.find(p => p.id === id)).filter((p) => p !== undefined);
162
+ const result = await Promise.resolve(gate.check(spec, gatePrinciples));
163
+ results.push(result);
164
+ if (failFast && !result.passed) {
165
+ const hasErrors = result.violations.some(v => v.severity === 'error');
166
+ if (hasErrors)
167
+ break;
168
+ }
169
+ }
170
+ if (!includeWarnings) {
171
+ for (const result of results) {
172
+ result.violations = result.violations.filter(v => v.severity === 'error');
173
+ }
174
+ }
175
+ return results;
176
+ }
177
+ getConstitution() {
178
+ return this.constitution;
179
+ }
180
+ hasPrinciple(id) {
181
+ return this.constitution?.principles.some(p => p.id === id) ?? false;
182
+ }
183
+ getGatesForPhase(phase) {
184
+ if (!this.constitution)
185
+ return [];
186
+ return this.constitution.gates.filter(g => g.phase === phase);
187
+ }
188
+ async save() {
189
+ if (!this.constitution)
190
+ throw new Error('No constitution loaded');
191
+ const { writeFile, mkdir } = await import('fs/promises');
192
+ const specsafeDir = join(this.projectDir, '.specsafe');
193
+ const constitutionPath = join(specsafeDir, 'constitution.md');
194
+ await mkdir(specsafeDir, { recursive: true });
195
+ // Only save custom principles (exclude built-ins)
196
+ const customPrinciples = this.constitution.principles.filter(p => !BUILTIN_PRINCIPLES.some(bp => bp.id === p.id));
197
+ const now = new Date().toISOString();
198
+ const quoteValue = (val) => JSON.stringify(val);
199
+ let content = `---
200
+ # SpecSafe Project Constitution
201
+ projectName: ${quoteValue(this.constitution.metadata.projectName)}
202
+ author: ${quoteValue(this.constitution.metadata.author || 'Unknown')}
203
+ version: ${quoteValue(this.constitution.metadata.version)}
204
+ createdAt: ${this.constitution.metadata.createdAt.toISOString()}
205
+ updatedAt: ${now}
206
+
207
+ principles:
208
+ `;
209
+ for (const principle of customPrinciples) {
210
+ content += `- id: ${principle.id}\n`;
211
+ content += ` name: ${quoteValue(principle.name)}\n`;
212
+ content += ` description: ${quoteValue(principle.description)}\n`;
213
+ content += ` severity: ${principle.severity}\n`;
214
+ content += ` immutable: ${principle.immutable}\n`;
215
+ if (principle.metadata?.rationale) {
216
+ content += ` rationale: ${quoteValue(principle.metadata.rationale)}\n`;
217
+ }
218
+ content += `\n`;
219
+ }
220
+ content += `---
221
+
222
+ # Project Constitution: ${this.constitution.metadata.projectName}
223
+
224
+ ${this.constitution.metadata.description || 'Project governance constitution'}
225
+
226
+ ## Custom Principles
227
+
228
+ `;
229
+ for (const principle of customPrinciples) {
230
+ const lockEmoji = principle.immutable ? '🔒' : '🔓';
231
+ const severityEmoji = principle.severity === 'error' ? '🚫' : '⚠️';
232
+ content += `### ${lockEmoji} ${principle.name}\n\n`;
233
+ content += `**ID:** \`${principle.id}\` \n`;
234
+ content += `**Severity:** ${severityEmoji} ${principle.severity.toUpperCase()} \n`;
235
+ content += `**Immutable:** ${principle.immutable ? 'Yes' : 'No'} \n\n`;
236
+ content += `${principle.description}\n\n`;
237
+ if (principle.metadata?.rationale) {
238
+ content += `**Rationale:** ${principle.metadata.rationale}\n\n`;
239
+ }
240
+ content += `---\n\n`;
241
+ }
242
+ await writeFile(constitutionPath, content);
243
+ }
244
+ }
245
+ //# sourceMappingURL=constitution.js.map