fraim-framework 2.0.52 → 2.0.55

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 (100) hide show
  1. package/dist/registry/scripts/profile-server.js +2 -1
  2. package/dist/src/ai-manager/ai-manager.js +49 -1
  3. package/dist/src/ai-manager/phase-flow.js +68 -0
  4. package/dist/src/utils/digest-utils.js +18 -7
  5. package/dist/tests/test-debug-session.js +6 -2
  6. package/dist/tests/test-enhanced-session-init.js +6 -2
  7. package/dist/tests/test-mcp-lifecycle-methods.js +1 -2
  8. package/dist/tests/test-mcp-template-processing.js +6 -2
  9. package/dist/tests/test-modular-issue-tracking.js +6 -2
  10. package/dist/tests/test-node-compatibility.js +4 -2
  11. package/dist/tests/test-npm-install.js +4 -2
  12. package/dist/tests/test-productivity-integration.js +157 -0
  13. package/dist/tests/test-session-rehydration.js +1 -2
  14. package/dist/tests/test-telemetry.js +1 -2
  15. package/dist/tests/test-users-to-target-workflow.js +253 -0
  16. package/index.js +44 -55
  17. package/package.json +5 -5
  18. package/registry/agent-guardrails.md +62 -62
  19. package/registry/scripts/detect-tautological-tests.sh +38 -38
  20. package/registry/scripts/productivity/build-productivity-csv.mjs +242 -0
  21. package/registry/scripts/productivity/fetch-pr-details.mjs +144 -0
  22. package/registry/scripts/productivity/productivity-report.sh +147 -0
  23. package/registry/scripts/profile-server.ts +1 -1
  24. package/registry/scripts/validate-openapi-limits.ts +366 -366
  25. package/registry/scripts/validate-test-coverage.ts +280 -280
  26. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-customer-profiling.md +11 -0
  27. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-survey-scoping.md +11 -0
  28. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-platform-discovery.md +11 -0
  29. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-survey-build-linkedin.md +11 -0
  30. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-prospect-qualification.md +11 -0
  31. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-survey-build-reddit.md +11 -0
  32. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-inventory-compilation.md +11 -0
  33. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-survey-build-x.md +11 -0
  34. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase5-survey-build-facebook.md +11 -0
  35. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase6-survey-build-custom.md +11 -0
  36. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase7-survey-dispatch.md +11 -0
  37. package/registry/stubs/workflows/customer-development/templates/customer-persona-template.md +11 -0
  38. package/registry/stubs/workflows/customer-development/templates/search-strategy-template.md +11 -0
  39. package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +11 -0
  40. package/registry/stubs/workflows/customer-development/users-to-target.md +11 -0
  41. package/registry/stubs/workflows/productivity-report/productivity-report.md +11 -0
  42. package/bin/fraim.js +0 -8
  43. package/dist/registry/ai-manager-rules/design-phases/design.md +0 -108
  44. package/dist/registry/ai-manager-rules/design-phases/finalize.md +0 -60
  45. package/dist/registry/ai-manager-rules/design-phases/validate.md +0 -125
  46. package/dist/registry/ai-manager-rules/design.json +0 -97
  47. package/dist/registry/ai-manager-rules/implement-phases/code.md +0 -323
  48. package/dist/registry/ai-manager-rules/implement-phases/completeness-review.md +0 -94
  49. package/dist/registry/ai-manager-rules/implement-phases/finalize.md +0 -177
  50. package/dist/registry/ai-manager-rules/implement-phases/quality-review.md +0 -304
  51. package/dist/registry/ai-manager-rules/implement-phases/regression.md +0 -159
  52. package/dist/registry/ai-manager-rules/implement-phases/repro.md +0 -101
  53. package/dist/registry/ai-manager-rules/implement-phases/scoping.md +0 -93
  54. package/dist/registry/ai-manager-rules/implement-phases/smoke.md +0 -225
  55. package/dist/registry/ai-manager-rules/implement-phases/spike.md +0 -118
  56. package/dist/registry/ai-manager-rules/implement-phases/validate.md +0 -347
  57. package/dist/registry/ai-manager-rules/implement.json +0 -153
  58. package/dist/registry/ai-manager-rules/shared-phases/finalize.md +0 -169
  59. package/dist/registry/ai-manager-rules/spec-phases/finalize.md +0 -60
  60. package/dist/registry/ai-manager-rules/spec-phases/spec.md +0 -102
  61. package/dist/registry/ai-manager-rules/spec-phases/validate.md +0 -118
  62. package/dist/registry/ai-manager-rules/spec.json +0 -112
  63. package/dist/registry/ai-manager-rules/test.json +0 -98
  64. package/dist/registry/scripts/build-scripts-generator.js +0 -205
  65. package/dist/registry/scripts/fraim-config.js +0 -61
  66. package/dist/registry/scripts/generic-issues-api.js +0 -100
  67. package/dist/registry/scripts/openapi-generator.js +0 -664
  68. package/dist/registry/scripts/performance/profile-server.js +0 -390
  69. package/dist/src/ai-manager/evidence-validator.js +0 -309
  70. package/dist/src/fraim/issue-tracking/ado-provider.js +0 -304
  71. package/dist/src/fraim/issue-tracking/factory.js +0 -63
  72. package/dist/src/fraim/issue-tracking/github-provider.js +0 -200
  73. package/dist/src/fraim/issue-tracking/types.js +0 -7
  74. package/dist/src/fraim/issue-tracking-config.js +0 -83
  75. package/dist/src/static-website-middleware.js +0 -75
  76. package/dist/test-utils.js +0 -96
  77. package/dist/tests/esm-compat.js +0 -11
  78. package/dist/tests/test-ai-manager-phase-protocol.js +0 -147
  79. package/dist/tests/test-ai-manager.js +0 -118
  80. package/dist/tests/test-chalk-esm-issue.js +0 -159
  81. package/dist/tests/test-chalk-real-world.js +0 -265
  82. package/dist/tests/test-chalk-regression.js +0 -377
  83. package/dist/tests/test-chalk-resolution-issue.js +0 -304
  84. package/dist/tests/test-evidence-validation.js +0 -221
  85. package/dist/tests/test-first-run-interactive.js +0 -1
  86. package/dist/tests/test-fraim-install-chalk-issue.js +0 -254
  87. package/dist/tests/test-markdown-to-pdf.js +0 -454
  88. package/dist/tests/test-npm-resolution-diagnostic.js +0 -140
  89. package/dist/tests/test-pr-review-integration.js +0 -1
  90. package/dist/website/.nojekyll +0 -0
  91. package/dist/website/404.html +0 -101
  92. package/dist/website/CNAME +0 -1
  93. package/dist/website/README.md +0 -22
  94. package/dist/website/demo.html +0 -604
  95. package/dist/website/images/.gitkeep +0 -1
  96. package/dist/website/images/fraim-logo.png +0 -0
  97. package/dist/website/index.html +0 -290
  98. package/dist/website/pricing.html +0 -414
  99. package/dist/website/script.js +0 -55
  100. package/dist/website/styles.css +0 -2647
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const child_process_1 = require("child_process");
38
38
  const fs_1 = require("fs");
39
39
  const path_1 = require("path");
40
+ const path = __importStar(require("path"));
40
41
  function loadClientConfig() {
41
42
  const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
42
43
  if (!(0, fs_1.existsSync)(configPath)) {
@@ -175,7 +176,7 @@ async function getSystemInformation(appName) {
175
176
  try {
176
177
  const response = await axios.post(`https://${appName}.scm.azurewebsites.net/api/command`, {
177
178
  command: command.cmd,
178
- dir: '/home'
179
+ dir: path.join('/', 'home')
179
180
  }, {
180
181
  headers: { Authorization: `Bearer ${token}` },
181
182
  timeout: 5000
@@ -77,7 +77,7 @@ Please provide a valid issue number to get specific guidance for your workflow.
77
77
  Example: \`seekCoachingOnNextStep({ workflowType: "${args.workflowType}", issueNumber: "123", currentPhase: "${args.currentPhase}", status: "${args.status}" })\``;
78
78
  }
79
79
  // Validate workflow type
80
- const validWorkflowTypes = ['implement', 'spec', 'design', 'test'];
80
+ const validWorkflowTypes = ['implement', 'spec', 'design', 'test', 'customer-development', 'user-survey'];
81
81
  if (!validWorkflowTypes.includes(args.workflowType)) {
82
82
  console.log(`❌ AI Coach: Invalid workflow type: ${args.workflowType}`);
83
83
  throw new Error(`Invalid workflow type: ${args.workflowType}. Valid types: ${validWorkflowTypes.join(', ')}`);
@@ -225,6 +225,14 @@ ${nextPhaseInstructions}
225
225
  - ✅ **Validation**: Verified test coverage and quality
226
226
  - ✅ **Submit PR**: Prepared tests for integration
227
227
  - ✅ **PR Review**: Successfully completed test review process`;
228
+ case 'user-survey':
229
+ return `- ✅ **Scoping**: Defined research goals and target audience
230
+ - ✅ **LinkedIn Build**: Generated professional LinkedIn survey content
231
+ - ✅ **Reddit Build**: Targeted relevant communities with specific copy
232
+ - ✅ **X/Twitter Build**: Created high-engagement short-form content
233
+ - ✅ **Facebook Build**: Formulated community-focused content
234
+ - ✅ **Custom Platforms**: Addressed supplementary platform needs
235
+ - ✅ **Dispatch**: Successfully executed survey distribution via automation`;
228
236
  default:
229
237
  return `- ✅ **${workflowType}**: Completed all phases successfully
230
238
  - ✅ **Validation**: Verified work quality and completeness
@@ -351,6 +359,42 @@ Remember: Take your time and follow each step carefully. The phase guidance cont
351
359
  else if (workflowType === 'implement') {
352
360
  phasePath = `ai-manager-rules/implement-phases/${phase}.md`;
353
361
  }
362
+ else if (workflowType === 'customer-development') {
363
+ // Customer development phases are in workflows directory
364
+ // Map phase names to file names
365
+ const phaseFileMap = {
366
+ 'customer-profiling': 'phase1-customer-profiling.md',
367
+ 'platform-discovery': 'phase2-platform-discovery.md',
368
+ 'prospect-qualification': 'phase3-prospect-qualification.md',
369
+ 'inventory-compilation': 'phase4-inventory-compilation.md'
370
+ };
371
+ const fileName = phaseFileMap[phase];
372
+ if (fileName) {
373
+ phasePath = `workflows/customer-development/ai-coach-phases/${fileName}`;
374
+ }
375
+ else {
376
+ phasePath = `workflows/customer-development/ai-coach-phases/${phase}.md`;
377
+ }
378
+ }
379
+ else if (workflowType === 'user-survey') {
380
+ // User survey phases
381
+ const phaseFileMap = {
382
+ 'survey-scoping': 'phase1-survey-scoping.md',
383
+ 'survey-build-linkedin': 'phase2-survey-build-linkedin.md',
384
+ 'survey-build-reddit': 'phase3-survey-build-reddit.md',
385
+ 'survey-build-x': 'phase4-survey-build-x.md',
386
+ 'survey-build-facebook': 'phase5-survey-build-facebook.md',
387
+ 'survey-build-custom': 'phase6-survey-build-custom.md',
388
+ 'survey-dispatch': 'phase7-survey-dispatch.md'
389
+ };
390
+ const fileName = phaseFileMap[phase];
391
+ if (fileName) {
392
+ phasePath = `workflows/customer-development/ai-coach-phases/${fileName}`;
393
+ }
394
+ else {
395
+ phasePath = `workflows/customer-development/ai-coach-phases/${phase}.md`;
396
+ }
397
+ }
354
398
  else {
355
399
  phasePath = `ai-manager-rules/${workflowType}-phases/${phase}.md`;
356
400
  }
@@ -426,6 +470,10 @@ Available phases for ${workflowType} workflow: ${this.getAvailablePhasesForWorkf
426
470
  return 'design-design, design-completeness-review, submit-pr, wait-for-pr-review';
427
471
  case 'test':
428
472
  return 'test-test, test-validate, submit-pr, wait-for-pr-review';
473
+ case 'customer-development':
474
+ return 'customer-profiling, platform-discovery, prospect-qualification, inventory-compilation';
475
+ case 'user-survey':
476
+ return 'survey-scoping, survey-build-linkedin, survey-build-reddit, survey-build-x, survey-build-facebook, survey-build-custom, survey-dispatch';
429
477
  default:
430
478
  return 'unknown';
431
479
  }
@@ -59,6 +59,21 @@ const TEST_PHASE_FLOW = [
59
59
  'wait-for-pr-review',
60
60
  'retrospective',
61
61
  ];
62
+ const CUSTOMER_DEVELOPMENT_PHASE_FLOW = [
63
+ 'customer-profiling',
64
+ 'platform-discovery',
65
+ 'prospect-qualification',
66
+ 'inventory-compilation',
67
+ ];
68
+ const USER_SURVEY_PHASE_FLOW = [
69
+ 'survey-scoping',
70
+ 'survey-build-linkedin',
71
+ 'survey-build-reddit',
72
+ 'survey-build-x',
73
+ 'survey-build-facebook',
74
+ 'survey-build-custom',
75
+ 'survey-dispatch',
76
+ ];
62
77
  /**
63
78
  * Get the next phase in the workflow
64
79
  * @param currentPhase - Current phase
@@ -102,6 +117,12 @@ function getNextPhase(currentPhase, workflowType, issueType) {
102
117
  else if (workflowType === 'test') {
103
118
  flow = TEST_PHASE_FLOW;
104
119
  }
120
+ else if (workflowType === 'customer-development') {
121
+ flow = CUSTOMER_DEVELOPMENT_PHASE_FLOW;
122
+ }
123
+ else if (workflowType === 'user-survey') {
124
+ flow = USER_SURVEY_PHASE_FLOW;
125
+ }
105
126
  else {
106
127
  throw new Error(`Unknown workflow type: ${workflowType}`);
107
128
  }
@@ -137,9 +158,18 @@ function isPhaseValidForWorkflow(phase, workflowType, issueType) {
137
158
  else if (workflowType === 'test') {
138
159
  flow = TEST_PHASE_FLOW;
139
160
  }
161
+ else if (workflowType === 'customer-development') {
162
+ flow = CUSTOMER_DEVELOPMENT_PHASE_FLOW;
163
+ }
164
+ else if (workflowType === 'user-survey') {
165
+ flow = USER_SURVEY_PHASE_FLOW;
166
+ }
140
167
  else {
141
168
  return false;
142
169
  }
170
+ if (phase === 'address-pr-feedback') {
171
+ return ['implement', 'spec', 'design', 'test'].includes(workflowType);
172
+ }
143
173
  return flow.includes(phase);
144
174
  }
145
175
  /**
@@ -244,6 +274,38 @@ function getPhaseOnFailure(failedPhase, workflowType, issueType // eslint-disabl
244
274
  return 'test-test';
245
275
  }
246
276
  }
277
+ else if (workflowType === 'customer-development') {
278
+ // Customer development workflow failure handling
279
+ switch (failedPhase) {
280
+ case 'customer-profiling':
281
+ return 'customer-profiling'; // Start over at profiling
282
+ case 'platform-discovery':
283
+ return 'customer-profiling'; // Go back to refine customer profile
284
+ case 'prospect-qualification':
285
+ return 'platform-discovery'; // Go back to find more prospects
286
+ case 'inventory-compilation':
287
+ return 'prospect-qualification'; // Go back to improve qualification
288
+ default:
289
+ return 'customer-profiling';
290
+ }
291
+ }
292
+ else if (workflowType === 'user-survey') {
293
+ // User survey workflow failure handling
294
+ switch (failedPhase) {
295
+ case 'survey-scoping':
296
+ return 'survey-scoping';
297
+ case 'survey-build-linkedin':
298
+ case 'survey-build-reddit':
299
+ case 'survey-build-x':
300
+ case 'survey-build-facebook':
301
+ case 'survey-build-custom':
302
+ return 'survey-scoping'; // Go back to revise questions
303
+ case 'survey-dispatch':
304
+ return 'survey-build-linkedin'; // Go back to check builds
305
+ default:
306
+ return 'survey-scoping';
307
+ }
308
+ }
247
309
  // Unknown workflow, return first phase of implement as fallback
248
310
  return 'implement-scoping';
249
311
  }
@@ -269,6 +331,12 @@ function getPhasesForWorkflow(workflowType, issueType) {
269
331
  else if (workflowType === 'test') {
270
332
  return [...TEST_PHASE_FLOW];
271
333
  }
334
+ else if (workflowType === 'customer-development') {
335
+ return [...CUSTOMER_DEVELOPMENT_PHASE_FLOW];
336
+ }
337
+ else if (workflowType === 'user-survey') {
338
+ return [...USER_SURVEY_PHASE_FLOW];
339
+ }
272
340
  else {
273
341
  throw new Error(`Unknown workflow type: ${workflowType}`);
274
342
  }
@@ -21,14 +21,25 @@ async function generateDigest(targetPath) {
21
21
  return crypto_1.default.createHash('md5').update(content).digest('hex');
22
22
  }
23
23
  if (stats.isDirectory()) {
24
- const files = fs_1.default.readdirSync(targetPath, { recursive: true });
25
- const hashes = [];
26
- for (const file of files.sort()) {
27
- const fullPath = path_1.default.join(targetPath, file);
28
- if (fs_1.default.statSync(fullPath).isFile()) {
29
- const content = fs_1.default.readFileSync(fullPath);
30
- hashes.push(crypto_1.default.createHash('md5').update(content).digest('hex'));
24
+ const getAllFiles = (dir) => {
25
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
26
+ let files = [];
27
+ for (const entry of entries) {
28
+ const fullPath = path_1.default.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ files = files.concat(getAllFiles(fullPath));
31
+ }
32
+ else {
33
+ files.push(fullPath);
34
+ }
31
35
  }
36
+ return files;
37
+ };
38
+ const allFiles = getAllFiles(targetPath);
39
+ const hashes = [];
40
+ for (const file of allFiles.sort()) {
41
+ const content = fs_1.default.readFileSync(file);
42
+ hashes.push(crypto_1.default.createHash('md5').update(content).digest('hex'));
32
43
  }
33
44
  return crypto_1.default.createHash('md5').update(hashes.join('')).digest('hex');
34
45
  }
@@ -122,9 +122,13 @@ async function testDebugSession() {
122
122
  // Run if called directly
123
123
  if (require.main === module) {
124
124
  testDebugSession()
125
- .then(success => process.exit(success ? 0 : 1))
125
+ .then(success => {
126
+ if (!success) {
127
+ throw new Error('Test failed');
128
+ }
129
+ })
126
130
  .catch(err => {
127
131
  console.error('Test runner error:', err);
128
- process.exit(1);
132
+ throw err;
129
133
  });
130
134
  }
@@ -176,9 +176,13 @@ async function testEnhancedSessionInit() {
176
176
  // Run if called directly
177
177
  if (require.main === module) {
178
178
  testEnhancedSessionInit()
179
- .then(success => process.exit(success ? 0 : 1))
179
+ .then(success => {
180
+ if (!success) {
181
+ throw new Error('Test failed');
182
+ }
183
+ })
180
184
  .catch(err => {
181
185
  console.error('Test runner error:', err);
182
- process.exit(1);
186
+ throw err;
183
187
  });
184
188
  }
@@ -234,8 +234,7 @@ const testCases = [
234
234
  }
235
235
  ];
236
236
  (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'MCP Lifecycle Methods & Bootstrap Tools')
237
- .then(() => process.exit(0))
238
237
  .catch((err) => {
239
238
  console.error('Test runner failed:', err);
240
- process.exit(1);
239
+ throw err;
241
240
  });
@@ -148,9 +148,13 @@ async function testMcpTemplateProcessing() {
148
148
  // Run if called directly
149
149
  if (require.main === module) {
150
150
  testMcpTemplateProcessing()
151
- .then(success => process.exit(success ? 0 : 1))
151
+ .then(success => {
152
+ if (!success) {
153
+ throw new Error('Test failed');
154
+ }
155
+ })
152
156
  .catch(err => {
153
157
  console.error('Test runner error:', err);
154
- process.exit(1);
158
+ throw err;
155
159
  });
156
160
  }
@@ -153,9 +153,13 @@ async function testMultiProviderSupport() {
153
153
  // Run if called directly
154
154
  if (require.main === module) {
155
155
  testMultiProviderSupport()
156
- .then(success => process.exit(success ? 0 : 1))
156
+ .then(success => {
157
+ if (!success) {
158
+ throw new Error('Test failed');
159
+ }
160
+ })
157
161
  .catch(err => {
158
162
  console.error('Test runner error:', err);
159
- process.exit(1);
163
+ throw err;
160
164
  });
161
165
  }
@@ -89,5 +89,7 @@ const testCases = nodeVersions.map(v => ({
89
89
  tags: ['node', 'compatibility']
90
90
  }));
91
91
  (0, test_utils_1.runTests)(testCases, async (t) => await testInitOnNodeVersion(t.version), 'Node Compatibility Matrix')
92
- .then(() => process.exit(0))
93
- .catch(() => process.exit(1));
92
+ .catch((err) => {
93
+ console.error('Test runner failed:', err);
94
+ throw err;
95
+ });
@@ -62,5 +62,7 @@ const testCases = nodeVersions.map(v => ({
62
62
  tags: ['npm', 'compatibility']
63
63
  }));
64
64
  (0, test_utils_1.runTests)(testCases, async (t) => await testCleanNpmInstall(t.version), 'NPM Configuration Matrix')
65
- .then(() => process.exit(0))
66
- .catch(() => process.exit(1));
65
+ .catch((err) => {
66
+ console.error('Test runner failed:', err);
67
+ throw err;
68
+ });
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * Test suite for productivity report integration
4
+ * Tests the workflow and associated scripts integration
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const node_test_1 = require("node:test");
11
+ const node_assert_1 = __importDefault(require("node:assert"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ // Test configuration
15
+ const WORKFLOW_PATH = 'registry/workflows/productivity-report/productivity-report.md';
16
+ const SCRIPTS_DIR = 'registry/scripts/productivity';
17
+ const EXPECTED_SCRIPTS = [
18
+ 'productivity-report.sh',
19
+ 'build-productivity-csv.mjs',
20
+ 'fetch-pr-details.mjs'
21
+ ];
22
+ (0, node_test_1.test)('Productivity Report Workflow Integration', async (t) => {
23
+ await t.test('workflow file exists and is accessible', () => {
24
+ node_assert_1.default.ok(fs_1.default.existsSync(WORKFLOW_PATH), `Workflow file should exist at ${WORKFLOW_PATH}`);
25
+ const content = fs_1.default.readFileSync(WORKFLOW_PATH, 'utf-8');
26
+ node_assert_1.default.ok(content.length > 0, 'Workflow file should not be empty');
27
+ node_assert_1.default.ok(content.includes('# Productivity Report Workflow'), 'Should contain proper workflow header');
28
+ node_assert_1.default.ok(content.includes('**Category:** Performance Analysis'), 'Should be categorized as Performance Analysis');
29
+ });
30
+ await t.test('workflow content is generalized', () => {
31
+ const content = fs_1.default.readFileSync(WORKFLOW_PATH, 'utf-8');
32
+ // Should not contain Ashley-Calendar-AI specific references
33
+ node_assert_1.default.ok(!content.includes('mathursrus/Ashley-Calendar-AI'), 'Should not contain hardcoded Ashley-Calendar-AI repository reference');
34
+ // Should contain generalized instructions
35
+ node_assert_1.default.ok(content.includes('any GitHub repository'), 'Should mention compatibility with any GitHub repository');
36
+ node_assert_1.default.ok(content.includes('automatically detects'), 'Should mention auto-detection capability');
37
+ node_assert_1.default.ok(content.includes('scripts/productivity/'), 'Should reference correct FRAIM script location');
38
+ });
39
+ await t.test('scripts directory exists with correct structure', () => {
40
+ node_assert_1.default.ok(fs_1.default.existsSync(SCRIPTS_DIR), `Scripts directory should exist at ${SCRIPTS_DIR}`);
41
+ const dirStats = fs_1.default.statSync(SCRIPTS_DIR);
42
+ node_assert_1.default.ok(dirStats.isDirectory(), 'Should be a directory');
43
+ });
44
+ await t.test('all expected scripts are present', () => {
45
+ for (const scriptName of EXPECTED_SCRIPTS) {
46
+ const scriptPath = path_1.default.join(SCRIPTS_DIR, scriptName);
47
+ node_assert_1.default.ok(fs_1.default.existsSync(scriptPath), `Script ${scriptName} should exist at ${scriptPath}`);
48
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
49
+ node_assert_1.default.ok(content.length > 0, `Script ${scriptName} should not be empty`);
50
+ }
51
+ });
52
+ await t.test('main script is generalized and repository-agnostic', () => {
53
+ const scriptPath = path_1.default.join(SCRIPTS_DIR, 'productivity-report.sh');
54
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
55
+ // Should not contain hardcoded repository
56
+ node_assert_1.default.ok(!content.includes('mathursrus/Ashley-Calendar-AI'), 'Main script should not contain hardcoded repository reference');
57
+ // Should contain repository detection logic
58
+ node_assert_1.default.ok(content.includes('detect_repository()'), 'Should contain repository detection function');
59
+ node_assert_1.default.ok(content.includes('git remote get-url origin'), 'Should use git remote for auto-detection');
60
+ // Should handle environment variables
61
+ node_assert_1.default.ok(content.includes('REPO_FULL'), 'Should support REPO_FULL environment variable');
62
+ node_assert_1.default.ok(content.includes('REPO_OWNER') && content.includes('REPO_NAME'), 'Should support REPO_OWNER/REPO_NAME environment variables');
63
+ // Should have proper error handling
64
+ node_assert_1.default.ok(content.includes('validate_repo_format'), 'Should validate repository format');
65
+ node_assert_1.default.ok(content.includes('gh auth status'), 'Should check GitHub CLI authentication');
66
+ });
67
+ await t.test('fetch-pr-details script is generalized', () => {
68
+ const scriptPath = path_1.default.join(SCRIPTS_DIR, 'fetch-pr-details.mjs');
69
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
70
+ // Should not contain hardcoded repository
71
+ node_assert_1.default.ok(!content.includes('mathursrus/Ashley-Calendar-AI'), 'PR details script should not contain hardcoded repository');
72
+ // Should contain repository detection
73
+ node_assert_1.default.ok(content.includes('detectRepository()'), 'Should contain repository detection function');
74
+ node_assert_1.default.ok(content.includes('process.env.REPO_FULL'), 'Should check REPO_FULL environment variable');
75
+ // Should have proper error handling
76
+ node_assert_1.default.ok(content.includes('Invalid repository format'), 'Should validate repository format');
77
+ node_assert_1.default.ok(content.includes('timeout:'), 'Should have timeout handling for API calls');
78
+ });
79
+ await t.test('build-csv script is generalized', () => {
80
+ const scriptPath = path_1.default.join(SCRIPTS_DIR, 'build-productivity-csv.mjs');
81
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
82
+ // Should be repository-agnostic (no hardcoded repos)
83
+ node_assert_1.default.ok(!content.includes('mathursrus/Ashley-Calendar-AI'), 'CSV builder should not contain hardcoded repository');
84
+ // Should have proper error handling
85
+ node_assert_1.default.ok(content.includes('Data directory not found'), 'Should handle missing data directory');
86
+ node_assert_1.default.ok(content.includes('Warning:'), 'Should have warning messages for data issues');
87
+ // Should handle file locking
88
+ node_assert_1.default.ok(content.includes('productivity-report-new.csv'), 'Should handle locked files with alternative naming');
89
+ });
90
+ await t.test('scripts have proper Node.js shebang and are executable', () => {
91
+ const nodeScripts = ['build-productivity-csv.mjs', 'fetch-pr-details.mjs'];
92
+ for (const scriptName of nodeScripts) {
93
+ const scriptPath = path_1.default.join(SCRIPTS_DIR, scriptName);
94
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
95
+ node_assert_1.default.ok(content.startsWith('#!/usr/bin/env node'), `${scriptName} should have proper Node.js shebang`);
96
+ }
97
+ // Check bash script shebang
98
+ const bashScriptPath = path_1.default.join(SCRIPTS_DIR, 'productivity-report.sh');
99
+ const bashContent = fs_1.default.readFileSync(bashScriptPath, 'utf-8');
100
+ node_assert_1.default.ok(bashContent.startsWith('#!/bin/bash'), 'Bash script should have proper bash shebang');
101
+ });
102
+ await t.test('workflow references correct script locations', () => {
103
+ const content = fs_1.default.readFileSync(WORKFLOW_PATH, 'utf-8');
104
+ // Should reference FRAIM registry location
105
+ node_assert_1.default.ok(content.includes('~/.fraim/scripts/productivity/'), 'Should reference correct FRAIM script registry location');
106
+ // Should mention all three scripts
107
+ for (const scriptName of EXPECTED_SCRIPTS) {
108
+ node_assert_1.default.ok(content.includes(scriptName), `Should mention script ${scriptName}`);
109
+ }
110
+ // Should have proper usage instructions
111
+ node_assert_1.default.ok(content.includes('bash ~/.fraim/scripts/productivity/productivity-report.sh'), 'Should provide correct usage command');
112
+ });
113
+ await t.test('integration maintains FRAIM patterns', () => {
114
+ // Check that existing FRAIM structure is preserved
115
+ node_assert_1.default.ok(fs_1.default.existsSync('registry/scripts'), 'Existing scripts directory should be preserved');
116
+ node_assert_1.default.ok(fs_1.default.existsSync('registry/workflows'), 'Existing workflows directory should be preserved');
117
+ node_assert_1.default.ok(fs_1.default.existsSync('registry/workflows/productivity-report'), 'Productivity report workflows directory should exist');
118
+ // Check that new structure follows FRAIM patterns
119
+ const workflowContent = fs_1.default.readFileSync(WORKFLOW_PATH, 'utf-8');
120
+ node_assert_1.default.ok(workflowContent.includes('**Category:**'), 'Should follow FRAIM workflow format with category');
121
+ node_assert_1.default.ok(workflowContent.includes('## OVERVIEW'), 'Should follow FRAIM workflow format with OVERVIEW section');
122
+ node_assert_1.default.ok(workflowContent.includes('## WHEN TO USE'), 'Should follow FRAIM workflow format with WHEN TO USE section');
123
+ });
124
+ await t.test('scripts contain proper documentation and help', () => {
125
+ const mainScriptPath = path_1.default.join(SCRIPTS_DIR, 'productivity-report.sh');
126
+ const content = fs_1.default.readFileSync(mainScriptPath, 'utf-8');
127
+ // Should have usage information
128
+ node_assert_1.default.ok(content.includes('Usage:'), 'Main script should contain usage information');
129
+ node_assert_1.default.ok(content.includes('export REPO_FULL'), 'Should document environment variable usage');
130
+ // Should have dependency information
131
+ node_assert_1.default.ok(content.includes('gh CLI'), 'Should mention GitHub CLI dependency');
132
+ node_assert_1.default.ok(content.includes('node'), 'Should mention Node.js dependency');
133
+ node_assert_1.default.ok(content.includes('jq'), 'Should mention jq dependency');
134
+ });
135
+ });
136
+ // Integration test with MCP server (if available)
137
+ (0, node_test_1.test)('MCP Server Integration', async (t) => {
138
+ await t.test('workflow is accessible via get_fraim_workflow pattern', () => {
139
+ // This test verifies the file is in the correct location for MCP access
140
+ const expectedPath = 'registry/workflows/productivity-report/productivity-report.md';
141
+ node_assert_1.default.ok(fs_1.default.existsSync(expectedPath), 'Workflow should be accessible at expected MCP path');
142
+ // Verify it follows the expected workflow format
143
+ const content = fs_1.default.readFileSync(expectedPath, 'utf-8');
144
+ node_assert_1.default.ok(content.includes('# Productivity Report Workflow'), 'Should have proper workflow header for MCP access');
145
+ });
146
+ await t.test('scripts are in registry location for FRAIM access', () => {
147
+ // Verify scripts are in the correct registry location
148
+ const registryPath = 'registry/scripts/productivity';
149
+ node_assert_1.default.ok(fs_1.default.existsSync(registryPath), 'Scripts should be in FRAIM registry location');
150
+ // Verify all scripts are accessible
151
+ for (const scriptName of EXPECTED_SCRIPTS) {
152
+ const scriptPath = path_1.default.join(registryPath, scriptName);
153
+ node_assert_1.default.ok(fs_1.default.existsSync(scriptPath), `Script ${scriptName} should be accessible in registry`);
154
+ }
155
+ });
156
+ });
157
+ console.log('✅ Productivity integration tests completed');
@@ -142,8 +142,7 @@ const testCases = [
142
142
  }
143
143
  ];
144
144
  (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'Fraim Session System')
145
- .then(() => process.exit(0))
146
145
  .catch((err) => {
147
146
  console.error('Test runner failed:', err);
148
- process.exit(1);
147
+ throw err;
149
148
  });
@@ -187,8 +187,7 @@ const testCases = [
187
187
  }
188
188
  ];
189
189
  (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'Fraim Telemetry System')
190
- .then(() => process.exit(0))
191
190
  .catch((err) => {
192
191
  console.error('Test runner failed:', err);
193
- process.exit(1);
192
+ throw err;
194
193
  });