fraim-framework 2.0.48 → 2.0.49
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/registry/ai-manager-rules/implement-phases/implement-code.md +7 -10
- package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +14 -7
- package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +12 -8
- package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +188 -0
- package/dist/src/ai-manager/phase-flow.js +36 -8
- package/dist/src/fraim/db-service.js +46 -0
- package/dist/src/fraim-mcp-server.js +159 -6
- package/dist/tests/test-pr-review-workflow.js +10 -6
- package/dist/tests/test-session-rehydration.js +5 -1
- package/dist/tests/test-telemetry.js +4 -0
- package/package.json +3 -1
- package/registry/scripts/create-website-structure.js +17 -2
- package/registry/scripts/profile-server.ts +1 -0
- package/registry/stubs/workflows/compliance/soc2-evidence-generator.md +11 -0
- package/registry/stubs/workflows/learning/build-skillset.md +11 -0
- package/registry/stubs/workflows/legal/contract-review-analysis.md +11 -0
- package/registry/stubs/workflows/legal/saas-contract-development.md +11 -0
|
@@ -177,19 +177,16 @@ Tag tests appropriately to ensure proper test suite execution:
|
|
|
177
177
|
- **`unit`**: Tests that verify individual functions or classes in isolation
|
|
178
178
|
- **`e2e`**: End-to-end tests that verify complete user workflows
|
|
179
179
|
|
|
180
|
-
**Smoke Test Execution Rules:**
|
|
181
|
-
If ANY tests are tagged "smoke" or you're modifying core functionality:
|
|
182
|
-
1. **MANDATORY**: Run full smoke test suite: `npm run test-smoke-ci`
|
|
183
|
-
2. **MANDATORY**: Include smoke test output in evidence
|
|
184
|
-
3. **MANDATORY**: All smoke tests must pass before proceeding
|
|
185
|
-
4. **BLOCKING**: Smoke test failures block phase completion
|
|
186
|
-
|
|
187
180
|
### Step 7: Basic Compilation Check
|
|
188
181
|
|
|
189
182
|
**Verify code compiles:**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
|
|
184
|
+
**Build Commands**: Always check `.fraim/config.json` first for `customizations.validation.buildCommand`, use that if available, otherwise default to `npm run build`.
|
|
185
|
+
|
|
186
|
+
**What to Look For**:
|
|
187
|
+
- Exit code 0 (success)
|
|
188
|
+
- No TypeScript compilation errors
|
|
189
|
+
- Clean build output
|
|
193
190
|
|
|
194
191
|
**Must exit with code 0** (no errors)
|
|
195
192
|
|
|
@@ -23,16 +23,17 @@ All technical checks pass, confirming:
|
|
|
23
23
|
|
|
24
24
|
### Step 0: Read Project Validation Commands 📖
|
|
25
25
|
|
|
26
|
-
**FIRST**:
|
|
26
|
+
**FIRST**: Check your project's custom commands:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
#
|
|
30
|
-
cat .fraim/config.json
|
|
31
|
-
```
|
|
29
|
+
# Check for custom build command
|
|
30
|
+
cat .fraim/config.json | grep -A 3 "buildCommand"
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
# Check for custom smoke test command
|
|
33
|
+
cat .fraim/config.json | grep -A 3 "smokeTestCommand"
|
|
34
|
+
```
|
|
34
35
|
|
|
35
|
-
**Use
|
|
36
|
+
**Use the configured commands if they exist, otherwise use the defaults shown below.**
|
|
36
37
|
|
|
37
38
|
### Step 1: Build Compilation Check ✅
|
|
38
39
|
|
|
@@ -43,7 +44,9 @@ Look for `customizations.validation.buildCommand` and `customizations.validation
|
|
|
43
44
|
|
|
44
45
|
**Commands to Run** (use customizations.validation.buildCommand from config if available):
|
|
45
46
|
```bash
|
|
46
|
-
#
|
|
47
|
+
# Use project-specific build command from .fraim/config.json if available:
|
|
48
|
+
# - Look for customizations.validation.buildCommand
|
|
49
|
+
# - Use that command if available, otherwise default to:
|
|
47
50
|
npm run build
|
|
48
51
|
```
|
|
49
52
|
|
|
@@ -164,6 +167,7 @@ npm run build && npm test && git status
|
|
|
164
167
|
|
|
165
168
|
### Technical Standards
|
|
166
169
|
- Read `.fraim/config.json` for the architecture document path (`customizations.architectureDoc`), then read the local architecture document to understand the full technical standards
|
|
170
|
+
- **Config Commands**: Always check `.fraim/config.json` first for `customizations.validation.buildCommand` and `customizations.validation.smokeTestCommand`, use those if available, otherwise use defaults
|
|
167
171
|
- Use Successful Debugging Patterns from `rules/successful-debugging-patterns.md` via `get_fraim_file`
|
|
168
172
|
- When using git commands directly (if MCP tools unavailable), read `rules/git-safe-commands.md` via `get_fraim_file`
|
|
169
173
|
|
|
@@ -181,6 +185,9 @@ npm run build && npm test && git status
|
|
|
181
185
|
|
|
182
186
|
**Run smoke tests specifically:**
|
|
183
187
|
```bash
|
|
188
|
+
# Use project-specific command from .fraim/config.json if available
|
|
189
|
+
# Check: cat .fraim/config.json | grep "smokeTestCommand"
|
|
190
|
+
# Use configured command or default to:
|
|
184
191
|
npm run test-smoke-ci
|
|
185
192
|
```
|
|
186
193
|
|
|
@@ -18,6 +18,10 @@ This is THE critical phase where you prove your implementation actually works. N
|
|
|
18
18
|
|
|
19
19
|
## RULES FOR THIS PHASE
|
|
20
20
|
|
|
21
|
+
### Config-Aware Testing
|
|
22
|
+
**CRITICAL**: Always check `.fraim/config.json` first for project-specific test commands:
|
|
23
|
+
- `customizations.validation.testSuiteCommand` - Use this for comprehensive testing (default: `npm test`)
|
|
24
|
+
|
|
21
25
|
### Success Criteria
|
|
22
26
|
Read `registry/rules/agent-success-criteria.md` via `get_fraim_file` for the complete framework. Focus on:
|
|
23
27
|
- **Integrity** (honest reporting of validation results - never claim something works if you didn't test it)
|
|
@@ -86,17 +90,17 @@ grep -r "as any" src/ || echo "✅ No type bypassing found"
|
|
|
86
90
|
|
|
87
91
|
**Commands to Run** (check .fraim/config.json for project-specific commands first):
|
|
88
92
|
```bash
|
|
89
|
-
# 1. FIRST:
|
|
90
|
-
cat .fraim/config.json | grep -A
|
|
93
|
+
# 1. FIRST: Check for custom test commands
|
|
94
|
+
cat .fraim/config.json | grep -A 5 "validation"
|
|
91
95
|
|
|
92
|
-
# 2. Run
|
|
96
|
+
# 2. Run comprehensive test suite for current work
|
|
97
|
+
# Use customizations.validation.testSuiteCommand if available, otherwise:
|
|
93
98
|
npm test
|
|
94
99
|
|
|
95
|
-
# 3.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
npm run test-all-ci
|
|
100
|
+
# 3. Run targeted tests for your specific changes (if applicable)
|
|
101
|
+
# Example: If you modified user authentication, run auth-related tests
|
|
102
|
+
# npm run test -- --grep "auth"
|
|
103
|
+
# OR: npm run test test-{issue-number}-*.ts
|
|
100
104
|
```
|
|
101
105
|
|
|
102
106
|
**What to Look For**:
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Phase: address-pr-feedback
|
|
2
|
+
|
|
3
|
+
## INTENT
|
|
4
|
+
To systematically address all PR feedback items by making targeted changes, replying to comments, and preparing for re-validation.
|
|
5
|
+
|
|
6
|
+
## OUTCOME
|
|
7
|
+
All PR feedback addressed with:
|
|
8
|
+
- Targeted changes made for each feedback item
|
|
9
|
+
- Feedback file updated with resolutions
|
|
10
|
+
- PR comments replied to with explanations
|
|
11
|
+
- Changes validated and ready for re-review
|
|
12
|
+
|
|
13
|
+
## RULES FOR THIS PHASE
|
|
14
|
+
|
|
15
|
+
### Success Criteria
|
|
16
|
+
Read `registry/rules/agent-success-criteria.md` via `get_fraim_file` for the complete framework. Focus especially on:
|
|
17
|
+
- **Integrity** (document all changes made)
|
|
18
|
+
- **Correctness** (address feedback accurately)
|
|
19
|
+
- **Completeness** (address every UNADDRESSED item)
|
|
20
|
+
|
|
21
|
+
### Simplicity Principles
|
|
22
|
+
Read `registry/rules/simplicity.md` via `get_fraim_file` for complete guidelines. Critical for feedback phase:
|
|
23
|
+
- **Targeted Changes**: Make minimal changes to address specific feedback
|
|
24
|
+
- **Manual Validation**: Test changes manually before updating PR
|
|
25
|
+
- **Clear Communication**: Reply to each comment with clear explanations
|
|
26
|
+
|
|
27
|
+
## PRINCIPLES
|
|
28
|
+
- **Feedback-Driven**: Address only the specific issues raised in PR comments
|
|
29
|
+
- **Minimal Changes**: Avoid scope creep or unnecessary refactoring
|
|
30
|
+
- **Clear Communication**: Explain resolutions clearly to reviewers
|
|
31
|
+
- **Validation**: Ensure changes work before updating PR
|
|
32
|
+
|
|
33
|
+
## 📋 WORKFLOW
|
|
34
|
+
|
|
35
|
+
### Step 1: Read PR Feedback File
|
|
36
|
+
|
|
37
|
+
**MANDATORY**: Read the feedback file created by `wait-for-pr-review`:
|
|
38
|
+
- File location: `docs/evidence/{issue_number}-{workflow_type}-feedback.md`
|
|
39
|
+
- Identify all UNADDRESSED feedback items
|
|
40
|
+
- Understand the context and requirements for each item
|
|
41
|
+
|
|
42
|
+
### Step 2: Address Each Feedback Item
|
|
43
|
+
|
|
44
|
+
**For each UNADDRESSED item:**
|
|
45
|
+
|
|
46
|
+
1. **Understand the Feedback**:
|
|
47
|
+
- Read the comment carefully
|
|
48
|
+
- Identify what specific change is requested
|
|
49
|
+
- Determine the scope of the change needed
|
|
50
|
+
|
|
51
|
+
2. **Make Targeted Changes**:
|
|
52
|
+
- Make minimal, focused changes to address the specific feedback
|
|
53
|
+
- Avoid unrelated changes or improvements
|
|
54
|
+
- Keep changes as small as possible while fully addressing the feedback
|
|
55
|
+
|
|
56
|
+
3. **Update Feedback File**:
|
|
57
|
+
- Change status from UNADDRESSED to ADDRESSED
|
|
58
|
+
- Document your resolution approach
|
|
59
|
+
- Include details of what you changed
|
|
60
|
+
|
|
61
|
+
**Feedback File Update Format**:
|
|
62
|
+
```markdown
|
|
63
|
+
### Comment X - ADDRESSED
|
|
64
|
+
- **Author**: reviewer_name
|
|
65
|
+
- **Type**: pr_comment|review_comment|review
|
|
66
|
+
- **File**: file_path (if applicable)
|
|
67
|
+
- **Line**: line_number (if applicable)
|
|
68
|
+
- **Comment**: Original feedback text
|
|
69
|
+
- **Status**: ADDRESSED
|
|
70
|
+
- **Resolution**: Detailed explanation of how you addressed this feedback
|
|
71
|
+
- **Changes Made**: Specific files/lines modified
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Step 3: Validate Changes
|
|
75
|
+
|
|
76
|
+
**Test your changes:**
|
|
77
|
+
- Run compilation check: `npx tsc --noEmit --skipLibCheck`
|
|
78
|
+
- Run relevant tests to ensure no regressions
|
|
79
|
+
- Test manually if the feedback involves UI/API behavior
|
|
80
|
+
- Verify the specific issue mentioned in feedback is resolved
|
|
81
|
+
|
|
82
|
+
### Step 4: Reply to PR Comments
|
|
83
|
+
|
|
84
|
+
**For each addressed comment:**
|
|
85
|
+
- Reply to the original PR comment
|
|
86
|
+
- Explain what you changed and how it addresses their concern
|
|
87
|
+
- Reference specific files/lines if helpful
|
|
88
|
+
- Be clear and professional in your communication
|
|
89
|
+
|
|
90
|
+
**Example reply format**:
|
|
91
|
+
```
|
|
92
|
+
✅ **Addressed**: [Brief description of what you changed]
|
|
93
|
+
|
|
94
|
+
**Changes made**:
|
|
95
|
+
- [Specific change 1]
|
|
96
|
+
- [Specific change 2]
|
|
97
|
+
|
|
98
|
+
**Files modified**: `path/to/file.ts`, `path/to/test.ts`
|
|
99
|
+
|
|
100
|
+
This should resolve the [specific issue mentioned]. Please let me know if you need any clarification!
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 5: Commit and Push Changes
|
|
104
|
+
|
|
105
|
+
**Commit your changes:**
|
|
106
|
+
- Use clear commit messages that reference the feedback
|
|
107
|
+
- Example: `Address PR feedback: Fix error handling in auth service`
|
|
108
|
+
- Push changes to the PR branch
|
|
109
|
+
|
|
110
|
+
## 📸 EVIDENCE REQUIREMENTS
|
|
111
|
+
|
|
112
|
+
You MUST provide concrete evidence for all claims:
|
|
113
|
+
|
|
114
|
+
1. **All Feedback Addressed**: Every UNADDRESSED item in feedback file is now ADDRESSED
|
|
115
|
+
2. **Changes Made**: Specific code changes made for each feedback item
|
|
116
|
+
3. **Validation Complete**: Tests pass and changes work as expected
|
|
117
|
+
4. **PR Updated**: Changes committed, pushed, and comments replied to
|
|
118
|
+
|
|
119
|
+
## VALIDATION
|
|
120
|
+
|
|
121
|
+
### Phase Complete When:
|
|
122
|
+
- ✅ All UNADDRESSED items in feedback file are now ADDRESSED
|
|
123
|
+
- ✅ Feedback file updated with resolution details for each item
|
|
124
|
+
- ✅ Targeted changes made to address each specific feedback
|
|
125
|
+
- ✅ Changes validated (compilation, tests, manual testing)
|
|
126
|
+
- ✅ PR comments replied to with clear explanations
|
|
127
|
+
- ✅ Changes committed and pushed to PR branch
|
|
128
|
+
|
|
129
|
+
### Phase Incomplete If:
|
|
130
|
+
- ❌ Any UNADDRESSED items remain in feedback file
|
|
131
|
+
- ❌ Changes don't actually address the feedback given
|
|
132
|
+
- ❌ Tests failing after changes
|
|
133
|
+
- ❌ Compilation errors introduced
|
|
134
|
+
- ❌ PR comments not replied to
|
|
135
|
+
- ❌ Changes not committed/pushed
|
|
136
|
+
|
|
137
|
+
### Report Back:
|
|
138
|
+
When you complete this phase, call:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
seekCoachingOnNextStep({
|
|
142
|
+
workflowType: "{workflow_type}",
|
|
143
|
+
issueNumber: "{issue_number}",
|
|
144
|
+
currentPhase: "address-pr-feedback",
|
|
145
|
+
status: "complete",
|
|
146
|
+
findings: {
|
|
147
|
+
feedbackItemsAddressed: {total_items_addressed},
|
|
148
|
+
changesValidated: true
|
|
149
|
+
},
|
|
150
|
+
evidence: {
|
|
151
|
+
feedbackFileUpdated: "All UNADDRESSED items now ADDRESSED with resolutions",
|
|
152
|
+
changesMade: "Brief summary of changes made",
|
|
153
|
+
validationResults: "Compilation and tests pass",
|
|
154
|
+
prCommentsReplied: "Replied to all PR comments with explanations"
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If feedback addressing incomplete, iterate:
|
|
160
|
+
```javascript
|
|
161
|
+
seekCoachingOnNextStep({
|
|
162
|
+
workflowType: "{workflow_type}",
|
|
163
|
+
issueNumber: "{issue_number}",
|
|
164
|
+
currentPhase: "address-pr-feedback",
|
|
165
|
+
status: "incomplete",
|
|
166
|
+
findings: {
|
|
167
|
+
uncertainties: ["Issues encountered", "What needs to be fixed"]
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## SCRIPTS
|
|
173
|
+
|
|
174
|
+
**Compile TypeScript:**
|
|
175
|
+
```bash
|
|
176
|
+
npx tsc --noEmit --skipLibCheck
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Run tests:**
|
|
180
|
+
```bash
|
|
181
|
+
npm test
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Check git status:**
|
|
185
|
+
```bash
|
|
186
|
+
git status
|
|
187
|
+
git diff
|
|
188
|
+
```
|
|
@@ -61,6 +61,25 @@ const TEST_PHASE_FLOW = [
|
|
|
61
61
|
* @returns Next phase or null if at end of workflow
|
|
62
62
|
*/
|
|
63
63
|
function getNextPhase(currentPhase, workflowType, issueType) {
|
|
64
|
+
// Special handling for address-pr-feedback phase
|
|
65
|
+
if (currentPhase === 'address-pr-feedback') {
|
|
66
|
+
// Success path: After addressing feedback, go back to appropriate validation phase
|
|
67
|
+
if (workflowType === 'implement') {
|
|
68
|
+
return 'implement-validate';
|
|
69
|
+
}
|
|
70
|
+
else if (workflowType === 'spec') {
|
|
71
|
+
return 'spec-completeness-review';
|
|
72
|
+
}
|
|
73
|
+
else if (workflowType === 'design') {
|
|
74
|
+
return 'design-completeness-review';
|
|
75
|
+
}
|
|
76
|
+
else if (workflowType === 'test') {
|
|
77
|
+
return 'test-validate';
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw new Error(`Unknown workflow type: ${workflowType}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
64
83
|
let flow;
|
|
65
84
|
if (workflowType === 'implement') {
|
|
66
85
|
if (!issueType) {
|
|
@@ -121,10 +140,11 @@ function isPhaseValidForWorkflow(phase, workflowType, issueType) {
|
|
|
121
140
|
* Get the phase to return to when a phase fails
|
|
122
141
|
* @param failedPhase - The phase that failed
|
|
123
142
|
* @param workflowType - Type of workflow
|
|
124
|
-
* @param issueType - Type of issue (
|
|
143
|
+
* @param issueType - Type of issue (currently unused - failure handling is the same for bug/feature)
|
|
125
144
|
* @returns Phase to return to
|
|
126
145
|
*/
|
|
127
|
-
function getPhaseOnFailure(failedPhase, workflowType, issueType
|
|
146
|
+
function getPhaseOnFailure(failedPhase, workflowType, issueType // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
147
|
+
) {
|
|
128
148
|
if (workflowType === 'implement') {
|
|
129
149
|
// Implement workflow failure handling
|
|
130
150
|
switch (failedPhase) {
|
|
@@ -147,9 +167,11 @@ function getPhaseOnFailure(failedPhase, workflowType, issueType) {
|
|
|
147
167
|
case 'submit-pr':
|
|
148
168
|
return 'implement-completeness-review'; // PR submission issues, review completeness
|
|
149
169
|
case 'wait-for-pr-review':
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
// PR feedback should go to address-pr-feedback phase
|
|
171
|
+
return 'address-pr-feedback';
|
|
172
|
+
case 'address-pr-feedback':
|
|
173
|
+
// If addressing PR feedback fails, retry the same phase
|
|
174
|
+
return 'address-pr-feedback';
|
|
153
175
|
default:
|
|
154
176
|
return 'implement-code'; // Default fallback
|
|
155
177
|
}
|
|
@@ -164,7 +186,9 @@ function getPhaseOnFailure(failedPhase, workflowType, issueType) {
|
|
|
164
186
|
case 'submit-pr':
|
|
165
187
|
return 'spec-completeness-review';
|
|
166
188
|
case 'wait-for-pr-review':
|
|
167
|
-
return '
|
|
189
|
+
return 'address-pr-feedback'; // PR feedback goes to address phase
|
|
190
|
+
case 'address-pr-feedback':
|
|
191
|
+
return 'address-pr-feedback'; // Retry addressing feedback
|
|
168
192
|
default:
|
|
169
193
|
return 'spec-spec';
|
|
170
194
|
}
|
|
@@ -179,7 +203,9 @@ function getPhaseOnFailure(failedPhase, workflowType, issueType) {
|
|
|
179
203
|
case 'submit-pr':
|
|
180
204
|
return 'design-completeness-review';
|
|
181
205
|
case 'wait-for-pr-review':
|
|
182
|
-
return '
|
|
206
|
+
return 'address-pr-feedback'; // PR feedback goes to address phase
|
|
207
|
+
case 'address-pr-feedback':
|
|
208
|
+
return 'address-pr-feedback'; // Retry addressing feedback
|
|
183
209
|
default:
|
|
184
210
|
return 'design-design';
|
|
185
211
|
}
|
|
@@ -194,7 +220,9 @@ function getPhaseOnFailure(failedPhase, workflowType, issueType) {
|
|
|
194
220
|
case 'submit-pr':
|
|
195
221
|
return 'test-validate';
|
|
196
222
|
case 'wait-for-pr-review':
|
|
197
|
-
return '
|
|
223
|
+
return 'address-pr-feedback'; // PR feedback goes to address phase
|
|
224
|
+
case 'address-pr-feedback':
|
|
225
|
+
return 'address-pr-feedback'; // Retry addressing feedback
|
|
198
226
|
default:
|
|
199
227
|
return 'test-test';
|
|
200
228
|
}
|
|
@@ -18,11 +18,17 @@ class FraimDbService {
|
|
|
18
18
|
// Use fraim_ prefix for standalone server collections
|
|
19
19
|
this.keysCollection = this.db.collection('fraim_api_keys');
|
|
20
20
|
this.sessionsCollection = this.db.collection('fraim_telemetry_sessions');
|
|
21
|
+
this.signupsCollection = this.db.collection('fraim_website_signups');
|
|
22
|
+
this.salesCollection = this.db.collection('fraim_sales_inquiries');
|
|
21
23
|
// Create indexes
|
|
22
24
|
await this.keysCollection.createIndex({ key: 1 }, { unique: true });
|
|
23
25
|
await this.sessionsCollection.createIndex({ sessionId: 1 }, { unique: true });
|
|
24
26
|
await this.sessionsCollection.createIndex({ userId: 1 });
|
|
25
27
|
await this.sessionsCollection.createIndex({ lastActive: -1 });
|
|
28
|
+
await this.signupsCollection.createIndex({ email: 1 }, { unique: true });
|
|
29
|
+
await this.signupsCollection.createIndex({ timestamp: -1 });
|
|
30
|
+
await this.salesCollection.createIndex({ email: 1 });
|
|
31
|
+
await this.salesCollection.createIndex({ timestamp: -1 });
|
|
26
32
|
console.log(`✅ Connected to Fraim DB: ${dbName}`);
|
|
27
33
|
}
|
|
28
34
|
async createSession(session) {
|
|
@@ -82,6 +88,46 @@ class FraimDbService {
|
|
|
82
88
|
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
83
89
|
return `${prefix}${random}`;
|
|
84
90
|
}
|
|
91
|
+
async createWebsiteSignup(signup) {
|
|
92
|
+
if (!this.signupsCollection)
|
|
93
|
+
throw new Error('DB not connected');
|
|
94
|
+
try {
|
|
95
|
+
await this.signupsCollection.insertOne(signup);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// Handle duplicate email gracefully
|
|
99
|
+
if (error.code === 11000) {
|
|
100
|
+
throw new Error('Email already registered');
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async getWebsiteSignups(limit = 100) {
|
|
106
|
+
if (!this.signupsCollection)
|
|
107
|
+
throw new Error('DB not connected');
|
|
108
|
+
return await this.signupsCollection.find({})
|
|
109
|
+
.sort({ timestamp: -1 })
|
|
110
|
+
.limit(limit)
|
|
111
|
+
.toArray();
|
|
112
|
+
}
|
|
113
|
+
async getSignupByEmail(email) {
|
|
114
|
+
if (!this.signupsCollection)
|
|
115
|
+
throw new Error('DB not connected');
|
|
116
|
+
return await this.signupsCollection.findOne({ email });
|
|
117
|
+
}
|
|
118
|
+
async createSalesInquiry(inquiry) {
|
|
119
|
+
if (!this.salesCollection)
|
|
120
|
+
throw new Error('DB not connected');
|
|
121
|
+
await this.salesCollection.insertOne(inquiry);
|
|
122
|
+
}
|
|
123
|
+
async getSalesInquiries(limit = 100) {
|
|
124
|
+
if (!this.salesCollection)
|
|
125
|
+
throw new Error('DB not connected');
|
|
126
|
+
return await this.salesCollection.find({})
|
|
127
|
+
.sort({ timestamp: -1 })
|
|
128
|
+
.limit(limit)
|
|
129
|
+
.toArray();
|
|
130
|
+
}
|
|
85
131
|
async close() {
|
|
86
132
|
await this.client.close();
|
|
87
133
|
}
|
|
@@ -226,6 +226,10 @@ class FraimMCPServer {
|
|
|
226
226
|
next();
|
|
227
227
|
}
|
|
228
228
|
async versionCheck(req, res, next) {
|
|
229
|
+
// Skip version check in test environment
|
|
230
|
+
if (process.env.NODE_ENV === 'test') {
|
|
231
|
+
return next();
|
|
232
|
+
}
|
|
229
233
|
// Only check for /mcp, /files routes, or root POST (MCP) where AI agents are active
|
|
230
234
|
const isMcpTraffic = req.path.startsWith('/mcp') ||
|
|
231
235
|
req.path.startsWith('/files') ||
|
|
@@ -277,8 +281,8 @@ class FraimMCPServer {
|
|
|
277
281
|
* Middleware to authenticate requests via API key
|
|
278
282
|
*/
|
|
279
283
|
async authMiddleware(req, res, next) {
|
|
280
|
-
// Skip auth for public health check
|
|
281
|
-
if (req.path === '/health' || req.path.startsWith('/admin')) {
|
|
284
|
+
// Skip auth for public health check, admin routes, website signup, and sales inquiries
|
|
285
|
+
if (req.path === '/health' || req.path.startsWith('/admin') || req.path === '/api/signup' || req.path === '/api/sales') {
|
|
282
286
|
return next();
|
|
283
287
|
}
|
|
284
288
|
const apiKey = req.headers['x-api-key'] || req.query['api-key'];
|
|
@@ -372,12 +376,30 @@ class FraimMCPServer {
|
|
|
372
376
|
const isActive = await this.sessionManager.updateActivity(apiKey);
|
|
373
377
|
if (!isActive) {
|
|
374
378
|
console.log(`⛔ Telemetry blocked request: ${req.method} ${req.path} (No Session for ${apiKey})`);
|
|
375
|
-
// Enforce Handshake
|
|
379
|
+
// Enforce Handshake with clear instructions for agents
|
|
376
380
|
res.status(400).json({
|
|
377
381
|
jsonrpc: '2.0',
|
|
378
382
|
error: {
|
|
379
383
|
code: -32600,
|
|
380
|
-
message:
|
|
384
|
+
message: `⛔ Session Not Started. REQUIRED ACTION: Call 'fraim_connect' tool first with these parameters:
|
|
385
|
+
|
|
386
|
+
{
|
|
387
|
+
"agent": {
|
|
388
|
+
"name": "YourAgentName",
|
|
389
|
+
"model": "your-model-name"
|
|
390
|
+
},
|
|
391
|
+
"machine": {
|
|
392
|
+
"hostname": "use os.hostname()",
|
|
393
|
+
"platform": "use process.platform"
|
|
394
|
+
},
|
|
395
|
+
"repo": {
|
|
396
|
+
"url": "git remote URL",
|
|
397
|
+
"owner": "repo owner",
|
|
398
|
+
"name": "repo name"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
After successful fraim_connect, all other FRAIM tools will work. This is required for telemetry and session management.`
|
|
381
403
|
},
|
|
382
404
|
id: req.body?.id || null
|
|
383
405
|
});
|
|
@@ -510,6 +532,13 @@ class FraimMCPServer {
|
|
|
510
532
|
else if (pathLower.includes('script') || normalizedPath.startsWith('scripts/') || normalizedPath.startsWith('fraim/scripts/')) {
|
|
511
533
|
type = 'script';
|
|
512
534
|
}
|
|
535
|
+
else if (normalizedPath.includes('ai-manager-rules/') && normalizedPath.includes('-phases/')) {
|
|
536
|
+
type = 'phase';
|
|
537
|
+
const parts = normalizedPath.split('/');
|
|
538
|
+
if (parts.length > 2) {
|
|
539
|
+
category = parts[parts.length - 2]; // e.g., 'implement-phases', 'spec-phases'
|
|
540
|
+
}
|
|
541
|
+
}
|
|
513
542
|
// Extract keywords from filename
|
|
514
543
|
const keywords = this.extractKeywords(entry.name, normalizedPath);
|
|
515
544
|
const metadata = {
|
|
@@ -768,6 +797,120 @@ class FraimMCPServer {
|
|
|
768
797
|
const success = await this.dbService.revokeApiKey(key);
|
|
769
798
|
res.json({ revoked: success });
|
|
770
799
|
});
|
|
800
|
+
// Website signup endpoint (public, no auth required)
|
|
801
|
+
this.app.post('/api/signup', async (req, res) => {
|
|
802
|
+
try {
|
|
803
|
+
const { email, company, useCase, source } = req.body;
|
|
804
|
+
if (!email) {
|
|
805
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
806
|
+
}
|
|
807
|
+
// Basic email validation
|
|
808
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
809
|
+
if (!emailRegex.test(email)) {
|
|
810
|
+
return res.status(400).json({ error: 'Invalid email format' });
|
|
811
|
+
}
|
|
812
|
+
// Get client IP and user agent for analytics
|
|
813
|
+
const ipAddress = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for'];
|
|
814
|
+
const userAgent = req.headers['user-agent'];
|
|
815
|
+
const signup = {
|
|
816
|
+
email: email.toLowerCase().trim(),
|
|
817
|
+
company: company?.trim() || undefined,
|
|
818
|
+
useCase: useCase || undefined,
|
|
819
|
+
source: source || 'website',
|
|
820
|
+
timestamp: new Date(),
|
|
821
|
+
ipAddress,
|
|
822
|
+
userAgent
|
|
823
|
+
};
|
|
824
|
+
await this.dbService.createWebsiteSignup(signup);
|
|
825
|
+
console.log(`✅ New website signup: ${email} (${company || 'No company'}) - ${useCase || 'No use case'}`);
|
|
826
|
+
res.json({
|
|
827
|
+
success: true,
|
|
828
|
+
message: 'Successfully joined the waitlist!'
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
console.error('❌ Website signup error:', error);
|
|
833
|
+
if (error.message === 'Email already registered') {
|
|
834
|
+
return res.status(409).json({
|
|
835
|
+
error: 'Email already registered',
|
|
836
|
+
message: 'This email is already on our waitlist.'
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
res.status(500).json({
|
|
840
|
+
error: 'Internal server error',
|
|
841
|
+
message: 'Failed to process signup. Please try again.'
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
// Sales inquiry endpoint (public, no auth required)
|
|
846
|
+
this.app.post('/api/sales', async (req, res) => {
|
|
847
|
+
try {
|
|
848
|
+
const { email, company, projectDetails, teamSize, timeline, budget, source } = req.body;
|
|
849
|
+
if (!email || !company || !projectDetails) {
|
|
850
|
+
return res.status(400).json({
|
|
851
|
+
error: 'Missing required fields',
|
|
852
|
+
message: 'Email, company, project details are required'
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
// Basic email validation
|
|
856
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
857
|
+
if (!emailRegex.test(email)) {
|
|
858
|
+
return res.status(400).json({ error: 'Invalid email format' });
|
|
859
|
+
}
|
|
860
|
+
// Get client IP and user agent for analytics
|
|
861
|
+
const ipAddress = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for'];
|
|
862
|
+
const userAgent = req.headers['user-agent'];
|
|
863
|
+
const inquiry = {
|
|
864
|
+
email: email.toLowerCase().trim(),
|
|
865
|
+
company: company.trim(),
|
|
866
|
+
projectDetails: projectDetails.trim(),
|
|
867
|
+
teamSize: teamSize?.trim() || undefined,
|
|
868
|
+
timeline: timeline?.trim() || undefined,
|
|
869
|
+
budget: budget?.trim() || undefined,
|
|
870
|
+
source: source || 'website',
|
|
871
|
+
timestamp: new Date(),
|
|
872
|
+
ipAddress,
|
|
873
|
+
userAgent
|
|
874
|
+
};
|
|
875
|
+
await this.dbService.createSalesInquiry(inquiry);
|
|
876
|
+
console.log(`💼 New sales inquiry: ${email} from ${company} - Team: ${teamSize}`);
|
|
877
|
+
res.json({
|
|
878
|
+
success: true,
|
|
879
|
+
message: 'Sales inquiry submitted successfully! We\'ll be in touch soon.'
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
catch (error) {
|
|
883
|
+
console.error('❌ Sales inquiry error:', error);
|
|
884
|
+
res.status(500).json({
|
|
885
|
+
error: 'Internal server error',
|
|
886
|
+
message: 'Failed to process sales inquiry. Please try again.'
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
// Get signups endpoint (admin only)
|
|
891
|
+
this.app.get('/admin/signups', async (req, res) => {
|
|
892
|
+
try {
|
|
893
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
894
|
+
const signups = await this.dbService.getWebsiteSignups(limit);
|
|
895
|
+
res.json({ signups, total: signups.length });
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
console.error('❌ Failed to get signups:', error);
|
|
899
|
+
res.status(500).json({ error: 'Failed to retrieve signups' });
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
// Get sales inquiries endpoint (admin only)
|
|
903
|
+
this.app.get('/admin/sales', async (req, res) => {
|
|
904
|
+
try {
|
|
905
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
906
|
+
const inquiries = await this.dbService.getSalesInquiries(limit);
|
|
907
|
+
res.json({ inquiries, total: inquiries.length });
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
console.error('❌ Failed to get sales inquiries:', error);
|
|
911
|
+
res.status(500).json({ error: 'Failed to retrieve sales inquiries' });
|
|
912
|
+
}
|
|
913
|
+
});
|
|
771
914
|
// MCP endpoint - POST for actual MCP requests
|
|
772
915
|
// Handle MCP requests on both /mcp (standard) and / (easier config)
|
|
773
916
|
this.app.post(['/', '/mcp'], async (req, res) => {
|
|
@@ -868,12 +1011,22 @@ Call this before fetching any workflow or file to understand the system.`,
|
|
|
868
1011
|
},
|
|
869
1012
|
{
|
|
870
1013
|
name: 'fraim_connect',
|
|
871
|
-
description:
|
|
1014
|
+
description: `🚨 REQUIRED FIRST CALL: Handshake to start a FRAIM session. MUST be called at the start of every session.
|
|
1015
|
+
|
|
1016
|
+
⚠️ CRITICAL: If you get a "Session Not Started" error from any FRAIM tool, call this tool first.
|
|
1017
|
+
|
|
872
1018
|
Registers your environment (machine/repo/agent) for telemetry and unlocks other tools.
|
|
873
1019
|
|
|
874
1020
|
**Agent Information**: REQUIRED - Provide your actual agent name and model (validated against known agents).
|
|
875
1021
|
**Machine Information**: Use os.hostname() and process.platform - required for telemetry.
|
|
876
|
-
**Repository Information**:
|
|
1022
|
+
**Repository Information**: Extract and send this information from .fraim/config.json.
|
|
1023
|
+
|
|
1024
|
+
Example call:
|
|
1025
|
+
{
|
|
1026
|
+
"agent": {"name": "Claude", "model": "claude-3.5-sonnet"},
|
|
1027
|
+
"machine": {"hostname": "my-machine", "platform": "darwin"},
|
|
1028
|
+
"repo": {"url": "https://github.com/owner/repo", "owner": "owner", "name": "repo"}
|
|
1029
|
+
}`,
|
|
877
1030
|
inputSchema: {
|
|
878
1031
|
type: 'object',
|
|
879
1032
|
properties: {
|
|
@@ -43,9 +43,9 @@ async function testPRReviewFailureHandling() {
|
|
|
43
43
|
// Test submit-pr failure
|
|
44
44
|
const submitPRFailure = (0, phase_flow_js_1.getPhaseOnFailure)('submit-pr', 'implement', 'bug');
|
|
45
45
|
node_assert_1.strict.equal(submitPRFailure, 'implement-completeness-review', 'submit-pr failure should return to completeness-review');
|
|
46
|
-
// Test wait-for-pr-review failure (
|
|
46
|
+
// Test wait-for-pr-review failure (should go to address-pr-feedback)
|
|
47
47
|
const waitPRFailure = (0, phase_flow_js_1.getPhaseOnFailure)('wait-for-pr-review', 'implement', 'bug');
|
|
48
|
-
node_assert_1.strict.equal(waitPRFailure, '
|
|
48
|
+
node_assert_1.strict.equal(waitPRFailure, 'address-pr-feedback', 'wait-for-pr-review failure should go to address-pr-feedback');
|
|
49
49
|
// Test all workflow types have new phases
|
|
50
50
|
const specPhases = (0, phase_flow_js_1.getPhasesForWorkflow)('spec');
|
|
51
51
|
(0, node_assert_1.strict)(specPhases.includes('submit-pr'), 'Spec workflow should include submit-pr');
|
|
@@ -191,10 +191,14 @@ async function testRealWorldPRReviewScenario() {
|
|
|
191
191
|
node_assert_1.strict.equal(finalPhase, null, 'wait-for-pr-review should be final phase when approved');
|
|
192
192
|
// Test PR review with changes requested (failure scenario)
|
|
193
193
|
const failurePhase = (0, phase_flow_js_1.getPhaseOnFailure)('wait-for-pr-review', workflowType, issueType);
|
|
194
|
-
node_assert_1.strict.equal(failurePhase, '
|
|
195
|
-
// Test iteration: after
|
|
196
|
-
let iterationPhase = '
|
|
197
|
-
|
|
194
|
+
node_assert_1.strict.equal(failurePhase, 'address-pr-feedback', 'PR changes requested should route to address-pr-feedback');
|
|
195
|
+
// Test iteration: after addressing feedback, go through validation again
|
|
196
|
+
let iterationPhase = 'address-pr-feedback';
|
|
197
|
+
// After addressing feedback, should go to validation phase
|
|
198
|
+
const nextAfterFeedback = (0, phase_flow_js_1.getNextPhase)(iterationPhase, workflowType, issueType);
|
|
199
|
+
node_assert_1.strict.equal(nextAfterFeedback, 'implement-validate', 'After address-pr-feedback should go to implement-validate');
|
|
200
|
+
iterationPhase = nextAfterFeedback;
|
|
201
|
+
const iterationFlow = ['implement-smoke', 'implement-regression', 'implement-completeness-review', 'submit-pr', 'wait-for-pr-review'];
|
|
198
202
|
for (const expectedNext of iterationFlow) {
|
|
199
203
|
const nextPhase = (0, phase_flow_js_1.getNextPhase)(iterationPhase, workflowType, issueType);
|
|
200
204
|
node_assert_1.strict.equal(nextPhase, expectedNext, `Iteration: after ${iterationPhase} should go to ${expectedNext}`);
|
|
@@ -92,7 +92,11 @@ async function testRehydrationAndExemptions() {
|
|
|
92
92
|
params: {
|
|
93
93
|
name: 'fraim_connect',
|
|
94
94
|
arguments: {
|
|
95
|
-
|
|
95
|
+
agent: {
|
|
96
|
+
name: 'Claude',
|
|
97
|
+
model: 'test-model'
|
|
98
|
+
},
|
|
99
|
+
machine: { hostname: 'repro-host', platform: 'test' },
|
|
96
100
|
repo: { url: 'http://github.com/repro' }
|
|
97
101
|
}
|
|
98
102
|
}
|
|
@@ -94,6 +94,10 @@ async function testTelemetryFlow() {
|
|
|
94
94
|
params: {
|
|
95
95
|
name: 'fraim_connect',
|
|
96
96
|
arguments: {
|
|
97
|
+
agent: {
|
|
98
|
+
name: 'Claude',
|
|
99
|
+
model: 'test-model'
|
|
100
|
+
},
|
|
97
101
|
machine: { hostname: 'test-host', platform: 'test-os' },
|
|
98
102
|
repo: { url: 'http://github.com/test/repo' }
|
|
99
103
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.49",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
"test": "node scripts/test-with-server.js",
|
|
16
16
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
17
17
|
"dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
|
|
18
|
+
"serve:website": "node fraim-pro/serve.js",
|
|
18
19
|
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts",
|
|
19
20
|
"manage-keys": "tsx scripts/fraim/manage-keys.ts",
|
|
21
|
+
"view-signups": "tsx scripts/view-signups.ts",
|
|
20
22
|
"fraim:init": "npm run build && node bin/fraim.js init",
|
|
21
23
|
"fraim:sync": "node bin/fraim.js sync",
|
|
22
24
|
"postinstall": "fraim sync || echo 'FRAIM setup skipped.'",
|
|
@@ -10,7 +10,21 @@
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Read configuration from .fraim/config.json if available
|
|
14
|
+
function loadConfig() {
|
|
15
|
+
const configPath = path.join(process.cwd(), '.fraim', 'config.json');
|
|
16
|
+
if (fs.existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.warn('Warning: Could not parse .fraim/config.json, using defaults');
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createWebsiteStructure(projectName, outputDir = process.cwd()) {
|
|
14
28
|
const websiteDir = path.join(outputDir, projectName);
|
|
15
29
|
|
|
16
30
|
// Create main directory
|
|
@@ -531,9 +545,10 @@ Thumbs.db
|
|
|
531
545
|
|
|
532
546
|
// CLI usage
|
|
533
547
|
if (require.main === module) {
|
|
548
|
+
const config = loadConfig();
|
|
534
549
|
const args = process.argv.slice(2);
|
|
535
550
|
const projectName = args[0];
|
|
536
|
-
const outputDir = args[1] ||
|
|
551
|
+
const outputDir = args[1] || process.cwd();
|
|
537
552
|
|
|
538
553
|
if (!projectName) {
|
|
539
554
|
console.log('Usage: node create-website-structure.js <project-name> [output-directory]');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: soc2-evidence-generator
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("soc2-evidence-generator")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Generate comprehensive SOC2 compliance evidence packages by automatically collecting, documenting, and formatting evidence from project systems to demonstrate adherence to Trust Service Criteria during annual audits.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: build-skillset
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("build-skillset")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Create new project-specific skills through conversation with the user. This workflow allows agents to learn and document custom workflows, processes, and knowledge that are specific to the current project or team.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: contract-review-analysis
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("contract-review-analysis")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Provide thorough, strategic legal contract review that identifies risks, strengthens protections, and optimizes business positioning. This skill enables comprehensive analysis of SOWs, NDAs, Terms & Conditions, and other legal documents to protect interests while maintaining business relationships.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: saas-contract-development
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("saas-contract-development")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Create comprehensive, legally protective contract packages for SaaS engagements including SOWs, Terms & Conditions, and strategic pricing negotiations. This skill enables rapid development of professional-grade legal documents that protect IP, limit liability, and position for business growth.
|