@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.
- package/dist/agents/adapters/base.d.ts +44 -0
- package/dist/agents/adapters/base.d.ts.map +1 -0
- package/dist/agents/adapters/base.js +164 -0
- package/dist/agents/adapters/base.js.map +1 -0
- package/dist/agents/adapters/claude-code.d.ts +14 -0
- package/dist/agents/adapters/claude-code.d.ts.map +1 -0
- package/dist/agents/adapters/claude-code.js +120 -0
- package/dist/agents/adapters/claude-code.js.map +1 -0
- package/dist/agents/adapters/copilot.d.ts +13 -0
- package/dist/agents/adapters/copilot.d.ts.map +1 -0
- package/dist/agents/adapters/copilot.js +115 -0
- package/dist/agents/adapters/copilot.js.map +1 -0
- package/dist/agents/adapters/cursor.d.ts +13 -0
- package/dist/agents/adapters/cursor.d.ts.map +1 -0
- package/dist/agents/adapters/cursor.js +105 -0
- package/dist/agents/adapters/cursor.js.map +1 -0
- package/dist/agents/adapters/gemini-cli.d.ts +13 -0
- package/dist/agents/adapters/gemini-cli.d.ts.map +1 -0
- package/dist/agents/adapters/gemini-cli.js +79 -0
- package/dist/agents/adapters/gemini-cli.js.map +1 -0
- package/dist/agents/adapters/index.d.ts +16 -0
- package/dist/agents/adapters/index.d.ts.map +1 -0
- package/dist/agents/adapters/index.js +47 -0
- package/dist/agents/adapters/index.js.map +1 -0
- package/dist/agents/adapters/opencode.d.ts +13 -0
- package/dist/agents/adapters/opencode.d.ts.map +1 -0
- package/dist/agents/adapters/opencode.js +67 -0
- package/dist/agents/adapters/opencode.js.map +1 -0
- package/dist/agents/index.d.ts +8 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +9 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/registry.d.ts +70 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +194 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/types.d.ts +71 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +6 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/delta/merger.d.ts +36 -0
- package/dist/delta/merger.d.ts.map +1 -0
- package/dist/delta/merger.js +264 -0
- package/dist/delta/merger.js.map +1 -0
- package/dist/delta/parser.d.ts +27 -0
- package/dist/delta/parser.d.ts.map +1 -0
- package/dist/delta/parser.js +196 -0
- package/dist/delta/parser.js.map +1 -0
- package/dist/delta/types.d.ts +39 -0
- package/dist/delta/types.d.ts.map +1 -0
- package/dist/delta/types.js +6 -0
- package/dist/delta/types.js.map +1 -0
- package/dist/ears/index.d.ts +11 -0
- package/dist/ears/index.d.ts.map +1 -0
- package/dist/ears/index.js +11 -0
- package/dist/ears/index.js.map +1 -0
- package/dist/ears/parser.d.ts +22 -0
- package/dist/ears/parser.d.ts.map +1 -0
- package/dist/ears/parser.js +273 -0
- package/dist/ears/parser.js.map +1 -0
- package/dist/ears/template.d.ts +20 -0
- package/dist/ears/template.d.ts.map +1 -0
- package/dist/ears/template.js +364 -0
- package/dist/ears/template.js.map +1 -0
- package/dist/ears/types.d.ts +58 -0
- package/dist/ears/types.d.ts.map +1 -0
- package/dist/ears/types.js +6 -0
- package/dist/ears/types.js.map +1 -0
- package/dist/ears/validator.d.ts +37 -0
- package/dist/ears/validator.d.ts.map +1 -0
- package/dist/ears/validator.js +234 -0
- package/dist/ears/validator.js.map +1 -0
- package/dist/elicitation/engine.d.ts +75 -0
- package/dist/elicitation/engine.d.ts.map +1 -0
- package/dist/elicitation/engine.js +174 -0
- package/dist/elicitation/engine.js.map +1 -0
- package/dist/elicitation/flows.d.ts +18 -0
- package/dist/elicitation/flows.d.ts.map +1 -0
- package/dist/elicitation/flows.js +331 -0
- package/dist/elicitation/flows.js.map +1 -0
- package/dist/elicitation/generator.d.ts +20 -0
- package/dist/elicitation/generator.d.ts.map +1 -0
- package/dist/elicitation/generator.js +260 -0
- package/dist/elicitation/generator.js.map +1 -0
- package/dist/elicitation/index.d.ts +27 -0
- package/dist/elicitation/index.d.ts.map +1 -0
- package/dist/elicitation/index.js +29 -0
- package/dist/elicitation/index.js.map +1 -0
- package/dist/elicitation/types.d.ts +69 -0
- package/dist/elicitation/types.d.ts.map +1 -0
- package/dist/elicitation/types.js +6 -0
- package/dist/elicitation/types.js.map +1 -0
- package/dist/extensions/builtins/complexity.d.ts +7 -0
- package/dist/extensions/builtins/complexity.d.ts.map +1 -0
- package/dist/extensions/builtins/complexity.js +97 -0
- package/dist/extensions/builtins/complexity.js.map +1 -0
- package/dist/extensions/builtins/owasp.d.ts +7 -0
- package/dist/extensions/builtins/owasp.d.ts.map +1 -0
- package/dist/extensions/builtins/owasp.js +76 -0
- package/dist/extensions/builtins/owasp.js.map +1 -0
- package/dist/extensions/index.d.ts +54 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +72 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/loader.d.ts +28 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +62 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/registry.d.ts +74 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +159 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/types.d.ts +70 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/types.js +2 -0
- package/dist/extensions/types.js.map +1 -0
- package/dist/governance/builtins.d.ts +7 -0
- package/dist/governance/builtins.d.ts.map +1 -0
- package/dist/governance/builtins.js +105 -0
- package/dist/governance/builtins.js.map +1 -0
- package/dist/governance/constitution.d.ts +23 -0
- package/dist/governance/constitution.d.ts.map +1 -0
- package/dist/governance/constitution.js +245 -0
- package/dist/governance/constitution.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/template.d.ts +12 -0
- package/dist/governance/template.d.ts.map +1 -0
- package/dist/governance/template.js +84 -0
- package/dist/governance/template.js.map +1 -0
- package/dist/governance/types.d.ts +64 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +2 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +23 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -18
- package/dist/index.js.map +1 -1
- package/dist/templates/checklist.d.ts +7 -0
- package/dist/templates/checklist.d.ts.map +1 -0
- package/dist/templates/checklist.js +131 -0
- package/dist/templates/checklist.js.map +1 -0
- package/dist/templates/delta-template.d.ts +18 -0
- package/dist/templates/delta-template.d.ts.map +1 -0
- package/dist/templates/delta-template.js +191 -0
- package/dist/templates/delta-template.js.map +1 -0
- package/dist/templates/engine.d.ts +20 -0
- package/dist/templates/engine.d.ts.map +1 -0
- package/dist/templates/engine.js +187 -0
- package/dist/templates/engine.js.map +1 -0
- package/dist/templates/types.d.ts +67 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/templates/types.js +5 -0
- package/dist/templates/types.js.map +1 -0
- 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 @@
|
|
|
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
|