fraim-framework 2.0.153 → 2.0.159
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/src/ai-hub/hosts.js +135 -8
- package/dist/src/ai-hub/server.js +201 -1
- package/dist/src/cli/commands/init-project.js +50 -36
- package/dist/src/cli/commands/sync.js +22 -1
- package/dist/src/cli/setup/ide-invocation-surfaces.js +2 -2
- package/dist/src/cli/utils/github-workflow-sync.js +231 -0
- package/dist/src/cli/utils/managed-agent-paths.js +1 -1
- package/dist/src/cli/utils/project-bootstrap.js +6 -3
- package/dist/src/core/ai-mentor.js +46 -37
- package/dist/src/core/config-loader.js +68 -0
- package/dist/src/core/fraim-config-schema.generated.js +267 -1
- package/dist/src/core/utils/fraim-labels.js +182 -0
- package/dist/src/core/utils/git-utils.js +22 -1
- package/dist/src/core/utils/project-fraim-paths.js +58 -0
- package/dist/src/first-run/types.js +1 -1
- package/dist/src/local-mcp-server/learning-context-builder.js +77 -52
- package/dist/src/local-mcp-server/stdio-server.js +212 -13
- package/package.json +8 -3
- package/public/ai-hub/index.html +271 -229
- package/public/ai-hub/script.js +879 -527
- package/public/ai-hub/styles.css +877 -694
- package/public/first-run/index.html +35 -35
- package/public/first-run/script.js +667 -667
|
@@ -142,6 +142,14 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
+
"githubWorkflows": {
|
|
146
|
+
"kind": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"enabled": {
|
|
149
|
+
"kind": "boolean"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
145
153
|
"validation": {
|
|
146
154
|
"kind": "object",
|
|
147
155
|
"properties": {
|
|
@@ -198,6 +206,9 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
198
206
|
},
|
|
199
207
|
"scoreThreshold": {
|
|
200
208
|
"kind": "number"
|
|
209
|
+
},
|
|
210
|
+
"globalPath": {
|
|
211
|
+
"kind": "string"
|
|
201
212
|
}
|
|
202
213
|
}
|
|
203
214
|
},
|
|
@@ -262,6 +273,215 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
262
273
|
]
|
|
263
274
|
}
|
|
264
275
|
}
|
|
276
|
+
},
|
|
277
|
+
"crm": {
|
|
278
|
+
"kind": "object",
|
|
279
|
+
"properties": {
|
|
280
|
+
"provider": {
|
|
281
|
+
"kind": "enum",
|
|
282
|
+
"values": [
|
|
283
|
+
"salesforce",
|
|
284
|
+
"customereq"
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
"integrations": {
|
|
290
|
+
"kind": "object",
|
|
291
|
+
"properties": {
|
|
292
|
+
"itsm": {
|
|
293
|
+
"kind": "object",
|
|
294
|
+
"properties": {
|
|
295
|
+
"provider": {
|
|
296
|
+
"kind": "enum",
|
|
297
|
+
"values": [
|
|
298
|
+
"servicenow"
|
|
299
|
+
]
|
|
300
|
+
},
|
|
301
|
+
"instanceUrl": {
|
|
302
|
+
"kind": "string"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"identity": {
|
|
307
|
+
"kind": "object",
|
|
308
|
+
"properties": {
|
|
309
|
+
"provider": {
|
|
310
|
+
"kind": "enum",
|
|
311
|
+
"values": [
|
|
312
|
+
"microsoft365"
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"automation": {
|
|
320
|
+
"kind": "object",
|
|
321
|
+
"properties": {
|
|
322
|
+
"support": {
|
|
323
|
+
"kind": "object",
|
|
324
|
+
"properties": {
|
|
325
|
+
"startMode": {
|
|
326
|
+
"kind": "enum",
|
|
327
|
+
"values": [
|
|
328
|
+
"manual",
|
|
329
|
+
"autonomous"
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
"defaultDecisionMode": {
|
|
333
|
+
"kind": "enum",
|
|
334
|
+
"values": [
|
|
335
|
+
"hitl",
|
|
336
|
+
"autonomous"
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
"contextResolver": {
|
|
340
|
+
"kind": "object",
|
|
341
|
+
"properties": {
|
|
342
|
+
"scriptPath": {
|
|
343
|
+
"kind": "string"
|
|
344
|
+
},
|
|
345
|
+
"arguments": {
|
|
346
|
+
"kind": "array",
|
|
347
|
+
"element": {
|
|
348
|
+
"kind": "string"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"timeoutMs": {
|
|
352
|
+
"kind": "number"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"queue": {
|
|
357
|
+
"kind": "object",
|
|
358
|
+
"properties": {
|
|
359
|
+
"provider": {
|
|
360
|
+
"kind": "enum",
|
|
361
|
+
"values": [
|
|
362
|
+
"servicenow",
|
|
363
|
+
"fixture"
|
|
364
|
+
]
|
|
365
|
+
},
|
|
366
|
+
"table": {
|
|
367
|
+
"kind": "string"
|
|
368
|
+
},
|
|
369
|
+
"assignmentGroup": {
|
|
370
|
+
"kind": "string"
|
|
371
|
+
},
|
|
372
|
+
"claimField": {
|
|
373
|
+
"kind": "string"
|
|
374
|
+
},
|
|
375
|
+
"fixturePath": {
|
|
376
|
+
"kind": "string"
|
|
377
|
+
},
|
|
378
|
+
"eligibleStates": {
|
|
379
|
+
"kind": "array",
|
|
380
|
+
"element": {
|
|
381
|
+
"kind": "string"
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
"pollIntervalSeconds": {
|
|
385
|
+
"kind": "number"
|
|
386
|
+
},
|
|
387
|
+
"successState": {
|
|
388
|
+
"kind": "string"
|
|
389
|
+
},
|
|
390
|
+
"failureState": {
|
|
391
|
+
"kind": "string"
|
|
392
|
+
},
|
|
393
|
+
"closeState": {
|
|
394
|
+
"kind": "string"
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
"requestTypes": {
|
|
399
|
+
"kind": "object",
|
|
400
|
+
"properties": {
|
|
401
|
+
"password_reset": {
|
|
402
|
+
"kind": "object",
|
|
403
|
+
"properties": {
|
|
404
|
+
"mode": {
|
|
405
|
+
"kind": "enum",
|
|
406
|
+
"values": [
|
|
407
|
+
"hitl",
|
|
408
|
+
"autonomous"
|
|
409
|
+
]
|
|
410
|
+
},
|
|
411
|
+
"requireApprovalState": {
|
|
412
|
+
"kind": "string"
|
|
413
|
+
},
|
|
414
|
+
"selector": {
|
|
415
|
+
"kind": "object",
|
|
416
|
+
"properties": {
|
|
417
|
+
"shortDescriptionContains": {
|
|
418
|
+
"kind": "array",
|
|
419
|
+
"element": {
|
|
420
|
+
"kind": "string"
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
"microsoftPasswordReset": {
|
|
426
|
+
"kind": "object",
|
|
427
|
+
"properties": {
|
|
428
|
+
"resetType": {
|
|
429
|
+
"kind": "enum",
|
|
430
|
+
"values": [
|
|
431
|
+
"temporary_generated",
|
|
432
|
+
"temporary_provided",
|
|
433
|
+
"permanent_provided"
|
|
434
|
+
]
|
|
435
|
+
},
|
|
436
|
+
"providedPasswordEnvVar": {
|
|
437
|
+
"kind": "string"
|
|
438
|
+
},
|
|
439
|
+
"forceChangePasswordNextSignIn": {
|
|
440
|
+
"kind": "boolean"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"communication": {
|
|
449
|
+
"kind": "object",
|
|
450
|
+
"properties": {
|
|
451
|
+
"channel": {
|
|
452
|
+
"kind": "enum",
|
|
453
|
+
"values": [
|
|
454
|
+
"email",
|
|
455
|
+
"sms",
|
|
456
|
+
"teams",
|
|
457
|
+
"manual-handoff"
|
|
458
|
+
]
|
|
459
|
+
},
|
|
460
|
+
"deliveryMode": {
|
|
461
|
+
"kind": "enum",
|
|
462
|
+
"values": [
|
|
463
|
+
"direct",
|
|
464
|
+
"operator-relay"
|
|
465
|
+
]
|
|
466
|
+
},
|
|
467
|
+
"recipientField": {
|
|
468
|
+
"kind": "enum",
|
|
469
|
+
"values": [
|
|
470
|
+
"requested_for",
|
|
471
|
+
"requested_by"
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
"includeTemporaryPassword": {
|
|
475
|
+
"kind": "boolean"
|
|
476
|
+
},
|
|
477
|
+
"messageTemplate": {
|
|
478
|
+
"kind": "string"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
265
485
|
}
|
|
266
486
|
},
|
|
267
487
|
"required": true
|
|
@@ -302,6 +522,8 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
|
|
|
302
522
|
"customizations.designSystem",
|
|
303
523
|
"customizations.designSystem.path",
|
|
304
524
|
"customizations.designSystem.brand",
|
|
525
|
+
"customizations.githubWorkflows",
|
|
526
|
+
"customizations.githubWorkflows.enabled",
|
|
305
527
|
"customizations.validation",
|
|
306
528
|
"customizations.validation.buildCommand",
|
|
307
529
|
"customizations.validation.testSuiteCommand",
|
|
@@ -316,6 +538,7 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
|
|
|
316
538
|
"learning",
|
|
317
539
|
"learning.lastSynthesisDate",
|
|
318
540
|
"learning.scoreThreshold",
|
|
541
|
+
"learning.globalPath",
|
|
319
542
|
"customer-communication",
|
|
320
543
|
"customer-communication.productName",
|
|
321
544
|
"customer-communication.productUrl",
|
|
@@ -329,5 +552,48 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
|
|
|
329
552
|
"stakeholderUpdate.cadence",
|
|
330
553
|
"stakeholderUpdate.historyPath",
|
|
331
554
|
"operatingReview",
|
|
332
|
-
"operatingReview.cadence"
|
|
555
|
+
"operatingReview.cadence",
|
|
556
|
+
"crm",
|
|
557
|
+
"crm.provider",
|
|
558
|
+
"integrations",
|
|
559
|
+
"integrations.itsm",
|
|
560
|
+
"integrations.itsm.provider",
|
|
561
|
+
"integrations.itsm.instanceUrl",
|
|
562
|
+
"integrations.identity",
|
|
563
|
+
"integrations.identity.provider",
|
|
564
|
+
"automation",
|
|
565
|
+
"automation.support",
|
|
566
|
+
"automation.support.startMode",
|
|
567
|
+
"automation.support.defaultDecisionMode",
|
|
568
|
+
"automation.support.contextResolver",
|
|
569
|
+
"automation.support.contextResolver.scriptPath",
|
|
570
|
+
"automation.support.contextResolver.arguments",
|
|
571
|
+
"automation.support.contextResolver.timeoutMs",
|
|
572
|
+
"automation.support.queue",
|
|
573
|
+
"automation.support.queue.provider",
|
|
574
|
+
"automation.support.queue.table",
|
|
575
|
+
"automation.support.queue.assignmentGroup",
|
|
576
|
+
"automation.support.queue.claimField",
|
|
577
|
+
"automation.support.queue.fixturePath",
|
|
578
|
+
"automation.support.queue.eligibleStates",
|
|
579
|
+
"automation.support.queue.pollIntervalSeconds",
|
|
580
|
+
"automation.support.queue.successState",
|
|
581
|
+
"automation.support.queue.failureState",
|
|
582
|
+
"automation.support.queue.closeState",
|
|
583
|
+
"automation.support.requestTypes",
|
|
584
|
+
"automation.support.requestTypes.password_reset",
|
|
585
|
+
"automation.support.requestTypes.password_reset.mode",
|
|
586
|
+
"automation.support.requestTypes.password_reset.requireApprovalState",
|
|
587
|
+
"automation.support.requestTypes.password_reset.selector",
|
|
588
|
+
"automation.support.requestTypes.password_reset.selector.shortDescriptionContains",
|
|
589
|
+
"automation.support.requestTypes.password_reset.microsoftPasswordReset",
|
|
590
|
+
"automation.support.requestTypes.password_reset.microsoftPasswordReset.resetType",
|
|
591
|
+
"automation.support.requestTypes.password_reset.microsoftPasswordReset.providedPasswordEnvVar",
|
|
592
|
+
"automation.support.requestTypes.password_reset.microsoftPasswordReset.forceChangePasswordNextSignIn",
|
|
593
|
+
"automation.support.communication",
|
|
594
|
+
"automation.support.communication.channel",
|
|
595
|
+
"automation.support.communication.deliveryMode",
|
|
596
|
+
"automation.support.communication.recipientField",
|
|
597
|
+
"automation.support.communication.includeTemporaryPassword",
|
|
598
|
+
"automation.support.communication.messageTemplate"
|
|
333
599
|
];
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FRAIM_STATUS_LABELS = exports.FRAIM_PHASE_LABELS = exports.LEGACY_FRAIM_STATUS_LABELS = void 0;
|
|
7
|
+
exports.isFraimPhaseLabel = isFraimPhaseLabel;
|
|
8
|
+
exports.isFraimStatusLabel = isFraimStatusLabel;
|
|
9
|
+
exports.isLegacyFraimStatusLabel = isLegacyFraimStatusLabel;
|
|
10
|
+
exports.normalizeFraimPhaseLabel = normalizeFraimPhaseLabel;
|
|
11
|
+
exports.normalizeFraimStatusLabel = normalizeFraimStatusLabel;
|
|
12
|
+
exports.partitionFraimLabels = partitionFraimLabels;
|
|
13
|
+
exports.mergeFraimGitHubLabels = mergeFraimGitHubLabels;
|
|
14
|
+
exports.applyIssueStatusTransition = applyIssueStatusTransition;
|
|
15
|
+
exports.applyIssuePhaseTransition = applyIssuePhaseTransition;
|
|
16
|
+
exports.applyIssuePhaseAndStatusTransition = applyIssuePhaseAndStatusTransition;
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const REQUIRED_FRAIM_PHASE_LABELS = [
|
|
20
|
+
'phase:spec',
|
|
21
|
+
'phase:design',
|
|
22
|
+
'phase:tests',
|
|
23
|
+
'phase:impl'
|
|
24
|
+
];
|
|
25
|
+
const REQUIRED_FRAIM_STATUS_LABELS = [
|
|
26
|
+
'status:wip',
|
|
27
|
+
'status:needs-review',
|
|
28
|
+
'status:complete'
|
|
29
|
+
];
|
|
30
|
+
exports.LEGACY_FRAIM_STATUS_LABELS = [
|
|
31
|
+
'status:in-progress',
|
|
32
|
+
'status:design-review-passed'
|
|
33
|
+
];
|
|
34
|
+
function normalizeLabel(label) {
|
|
35
|
+
return label.trim().toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
function findLabelsCatalogPath() {
|
|
38
|
+
let currentDir = __dirname;
|
|
39
|
+
for (let i = 0; i < 10; i++) {
|
|
40
|
+
const candidate = path_1.default.join(currentDir, 'labels.json');
|
|
41
|
+
if (fs_1.default.existsSync(candidate)) {
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
const parentDir = path_1.default.dirname(currentDir);
|
|
45
|
+
if (parentDir === currentDir) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
currentDir = parentDir;
|
|
49
|
+
}
|
|
50
|
+
const cwdCandidate = path_1.default.join(process.cwd(), 'labels.json');
|
|
51
|
+
if (fs_1.default.existsSync(cwdCandidate)) {
|
|
52
|
+
return cwdCandidate;
|
|
53
|
+
}
|
|
54
|
+
throw new Error('labels.json not found; FRAIM GitHub label helpers require the repo label catalog.');
|
|
55
|
+
}
|
|
56
|
+
function loadCatalogLabels() {
|
|
57
|
+
const catalogPath = findLabelsCatalogPath();
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(catalogPath, 'utf8'));
|
|
60
|
+
return new Set(parsed
|
|
61
|
+
.map((entry) => (typeof entry?.name === 'string' ? normalizeLabel(entry.name) : ''))
|
|
62
|
+
.filter(Boolean));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
throw new Error(`Failed to read labels.json for FRAIM GitHub label helpers: ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function requireCatalogLabels(expected, kind) {
|
|
70
|
+
const catalogLabels = loadCatalogLabels();
|
|
71
|
+
const missing = expected.filter((label) => !catalogLabels.has(label));
|
|
72
|
+
if (missing.length > 0) {
|
|
73
|
+
throw new Error(`labels.json is missing canonical FRAIM ${kind} labels: ${missing.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
return expected;
|
|
76
|
+
}
|
|
77
|
+
exports.FRAIM_PHASE_LABELS = requireCatalogLabels(REQUIRED_FRAIM_PHASE_LABELS, 'phase');
|
|
78
|
+
exports.FRAIM_STATUS_LABELS = requireCatalogLabels(REQUIRED_FRAIM_STATUS_LABELS, 'status');
|
|
79
|
+
const FRAIM_PHASE_LABEL_SET = new Set(exports.FRAIM_PHASE_LABELS);
|
|
80
|
+
const FRAIM_STATUS_LABEL_SET = new Set(exports.FRAIM_STATUS_LABELS);
|
|
81
|
+
const LEGACY_FRAIM_STATUS_LABEL_SET = new Set(exports.LEGACY_FRAIM_STATUS_LABELS);
|
|
82
|
+
function dedupeLabels(labels) {
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
const deduped = [];
|
|
85
|
+
for (const rawLabel of labels) {
|
|
86
|
+
const trimmed = rawLabel.trim();
|
|
87
|
+
if (!trimmed)
|
|
88
|
+
continue;
|
|
89
|
+
const normalized = normalizeLabel(trimmed);
|
|
90
|
+
if (seen.has(normalized))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(normalized);
|
|
93
|
+
deduped.push(trimmed);
|
|
94
|
+
}
|
|
95
|
+
return deduped;
|
|
96
|
+
}
|
|
97
|
+
function mapLegacyStatus(label) {
|
|
98
|
+
const normalized = normalizeLabel(label);
|
|
99
|
+
switch (normalized) {
|
|
100
|
+
case 'status:in-progress':
|
|
101
|
+
return 'status:wip';
|
|
102
|
+
case 'status:design-review-passed':
|
|
103
|
+
return 'status:complete';
|
|
104
|
+
default:
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isFraimPhaseLabel(label) {
|
|
109
|
+
return FRAIM_PHASE_LABEL_SET.has(normalizeLabel(label));
|
|
110
|
+
}
|
|
111
|
+
function isFraimStatusLabel(label) {
|
|
112
|
+
return FRAIM_STATUS_LABEL_SET.has(normalizeLabel(label));
|
|
113
|
+
}
|
|
114
|
+
function isLegacyFraimStatusLabel(label) {
|
|
115
|
+
return LEGACY_FRAIM_STATUS_LABEL_SET.has(normalizeLabel(label));
|
|
116
|
+
}
|
|
117
|
+
function normalizeFraimPhaseLabel(label) {
|
|
118
|
+
const normalized = normalizeLabel(label);
|
|
119
|
+
if (!FRAIM_PHASE_LABEL_SET.has(normalized)) {
|
|
120
|
+
throw new Error(`Unsupported FRAIM phase label: ${label}`);
|
|
121
|
+
}
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
function normalizeFraimStatusLabel(label) {
|
|
125
|
+
const normalized = normalizeLabel(label);
|
|
126
|
+
if (FRAIM_STATUS_LABEL_SET.has(normalized)) {
|
|
127
|
+
return normalized;
|
|
128
|
+
}
|
|
129
|
+
const mappedLegacy = mapLegacyStatus(normalized);
|
|
130
|
+
if (mappedLegacy) {
|
|
131
|
+
return mappedLegacy;
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Unsupported FRAIM status label: ${label}`);
|
|
134
|
+
}
|
|
135
|
+
function partitionFraimLabels(labels) {
|
|
136
|
+
const nonFraimLabels = [];
|
|
137
|
+
const phaseLabels = [];
|
|
138
|
+
const statusLabels = [];
|
|
139
|
+
for (const label of dedupeLabels(labels)) {
|
|
140
|
+
if (isFraimPhaseLabel(label)) {
|
|
141
|
+
phaseLabels.push(normalizeFraimPhaseLabel(label));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (isFraimStatusLabel(label) || isLegacyFraimStatusLabel(label)) {
|
|
145
|
+
statusLabels.push(normalizeFraimStatusLabel(label));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
nonFraimLabels.push(label);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
nonFraimLabels,
|
|
152
|
+
phaseLabels,
|
|
153
|
+
statusLabels
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function mergeFraimGitHubLabels(labels, updates) {
|
|
157
|
+
const { nonFraimLabels, phaseLabels, statusLabels } = partitionFraimLabels(labels);
|
|
158
|
+
const nextPhase = updates.phase
|
|
159
|
+
? normalizeFraimPhaseLabel(updates.phase)
|
|
160
|
+
: phaseLabels[0];
|
|
161
|
+
const nextStatus = updates.status
|
|
162
|
+
? normalizeFraimStatusLabel(updates.status)
|
|
163
|
+
: statusLabels[0];
|
|
164
|
+
const merged = [...nonFraimLabels];
|
|
165
|
+
if (nextPhase)
|
|
166
|
+
merged.push(nextPhase);
|
|
167
|
+
if (nextStatus)
|
|
168
|
+
merged.push(nextStatus);
|
|
169
|
+
return dedupeLabels(merged);
|
|
170
|
+
}
|
|
171
|
+
function applyIssueStatusTransition(labels, status) {
|
|
172
|
+
return mergeFraimGitHubLabels(labels, { status });
|
|
173
|
+
}
|
|
174
|
+
function applyIssuePhaseTransition(labels, phase) {
|
|
175
|
+
return mergeFraimGitHubLabels(labels, {
|
|
176
|
+
phase,
|
|
177
|
+
status: 'status:wip'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function applyIssuePhaseAndStatusTransition(labels, phase, status) {
|
|
181
|
+
return mergeFraimGitHubLabels(labels, { phase, status });
|
|
182
|
+
}
|
|
@@ -32,11 +32,32 @@ function isExplicitLocalRepoPath(repoUrl) {
|
|
|
32
32
|
trimmed.startsWith('../') ||
|
|
33
33
|
trimmed.startsWith('file://'));
|
|
34
34
|
}
|
|
35
|
+
function readExplicitPortFromEnv() {
|
|
36
|
+
for (const value of [
|
|
37
|
+
process.env.PLAYWRIGHT_FRAIM_MCP_PORT,
|
|
38
|
+
process.env.PLAYWRIGHT_WEBSITE_PORT,
|
|
39
|
+
process.env.FRAIM_MCP_PORT,
|
|
40
|
+
process.env.PORT,
|
|
41
|
+
process.env.WEBSITES_PORT
|
|
42
|
+
]) {
|
|
43
|
+
const port = Number(value);
|
|
44
|
+
if (Number.isFinite(port) && port > 0) {
|
|
45
|
+
return port;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
35
50
|
/**
|
|
36
51
|
* Gets a unique port based on the current git branch name (if it's an issue branch)
|
|
37
52
|
* Default to 15302 if not on an issue branch
|
|
38
53
|
*/
|
|
39
54
|
function getPort() {
|
|
55
|
+
// Explicit runtime ports must win so tests and subprocesses all resolve the
|
|
56
|
+
// same active server instead of drifting back to a branch-derived default.
|
|
57
|
+
const explicitPort = readExplicitPortFromEnv();
|
|
58
|
+
if (explicitPort) {
|
|
59
|
+
return explicitPort;
|
|
60
|
+
}
|
|
40
61
|
try {
|
|
41
62
|
const branchName = process.env.FRAIM_BRANCH || (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD').toString().trim();
|
|
42
63
|
// Match issue-123 or 123-feature-name or feature/123-name
|
|
@@ -50,7 +71,7 @@ function getPort() {
|
|
|
50
71
|
catch (e) {
|
|
51
72
|
// Silently fail and use default
|
|
52
73
|
}
|
|
53
|
-
return
|
|
74
|
+
return 15302;
|
|
54
75
|
}
|
|
55
76
|
/**
|
|
56
77
|
* Determines the database name based on the git branch
|
|
@@ -9,7 +9,12 @@ exports.workspaceFraimExists = workspaceFraimExists;
|
|
|
9
9
|
exports.getWorkspaceConfigPath = getWorkspaceConfigPath;
|
|
10
10
|
exports.getWorkspaceFraimPath = getWorkspaceFraimPath;
|
|
11
11
|
exports.getWorkspaceFraimDisplayPath = getWorkspaceFraimDisplayPath;
|
|
12
|
+
exports.getUserFraimDisplayPath = getUserFraimDisplayPath;
|
|
12
13
|
exports.getUserFraimDirPath = getUserFraimDirPath;
|
|
14
|
+
exports.getUserFraimLearningsDir = getUserFraimLearningsDir;
|
|
15
|
+
exports.getWorkspaceLearningsDir = getWorkspaceLearningsDir;
|
|
16
|
+
exports.getConfiguredPortableLearningsDir = getConfiguredPortableLearningsDir;
|
|
17
|
+
exports.getConfiguredPortableLearningsDisplayPath = getConfiguredPortableLearningsDisplayPath;
|
|
13
18
|
exports.getEffectiveFraimDir = getEffectiveFraimDir;
|
|
14
19
|
const fs_1 = require("fs");
|
|
15
20
|
const path_1 = require("path");
|
|
@@ -24,6 +29,9 @@ exports.WORKSPACE_SYNCED_CONTENT_DIRS = [
|
|
|
24
29
|
function normalizeRelativePath(relativePath) {
|
|
25
30
|
return relativePath.replace(/^[\\/]+/, '').replace(/[\\/]+/g, '/');
|
|
26
31
|
}
|
|
32
|
+
function normalizeDisplayPath(fullPath) {
|
|
33
|
+
return fullPath.replace(/\\/g, '/');
|
|
34
|
+
}
|
|
27
35
|
function getWorkspaceFraimDir(projectRoot = process.cwd()) {
|
|
28
36
|
return (0, path_1.join)(projectRoot, exports.WORKSPACE_FRAIM_DIRNAME);
|
|
29
37
|
}
|
|
@@ -42,6 +50,10 @@ function getWorkspaceFraimDisplayPath(relativePath = '') {
|
|
|
42
50
|
? `${exports.WORKSPACE_FRAIM_DIRNAME}/${normalized}`
|
|
43
51
|
: `${exports.WORKSPACE_FRAIM_DIRNAME}/`;
|
|
44
52
|
}
|
|
53
|
+
function getUserFraimDisplayPath(relativePath = '') {
|
|
54
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
55
|
+
return normalized.length > 0 ? `~/.fraim/${normalized}` : '~/.fraim/';
|
|
56
|
+
}
|
|
45
57
|
/**
|
|
46
58
|
* Get the user-level FRAIM directory (~/.fraim/).
|
|
47
59
|
* Can be overridden with FRAIM_USER_DIR env var for testing.
|
|
@@ -49,6 +61,52 @@ function getWorkspaceFraimDisplayPath(relativePath = '') {
|
|
|
49
61
|
function getUserFraimDirPath() {
|
|
50
62
|
return process.env.FRAIM_USER_DIR || (0, path_1.join)(os_1.default.homedir(), '.fraim');
|
|
51
63
|
}
|
|
64
|
+
function getUserFraimLearningsDir() {
|
|
65
|
+
return (0, path_1.join)(getUserFraimDirPath(), 'personalized-employee', 'learnings');
|
|
66
|
+
}
|
|
67
|
+
function getWorkspaceLearningsDir(projectRoot = process.cwd()) {
|
|
68
|
+
return getWorkspaceFraimPath(projectRoot, 'personalized-employee', 'learnings');
|
|
69
|
+
}
|
|
70
|
+
function resolveConfiguredLearningGlobalPath(rawPath) {
|
|
71
|
+
if (typeof rawPath !== 'string')
|
|
72
|
+
return null;
|
|
73
|
+
const trimmed = rawPath.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
return null;
|
|
76
|
+
if (/^[a-z]+:\/\//i.test(trimmed))
|
|
77
|
+
return null;
|
|
78
|
+
if (trimmed.startsWith('~')) {
|
|
79
|
+
const homeRelative = trimmed.slice(1).replace(/^[\\/]+/, '');
|
|
80
|
+
return (0, path_1.normalize)((0, path_1.join)(os_1.default.homedir(), homeRelative));
|
|
81
|
+
}
|
|
82
|
+
if (!(0, path_1.isAbsolute)(trimmed))
|
|
83
|
+
return null;
|
|
84
|
+
return (0, path_1.normalize)(trimmed);
|
|
85
|
+
}
|
|
86
|
+
function readWorkspaceConfig(projectRoot) {
|
|
87
|
+
try {
|
|
88
|
+
const configPath = getWorkspaceConfigPath(projectRoot);
|
|
89
|
+
if (!(0, fs_1.existsSync)(configPath))
|
|
90
|
+
return null;
|
|
91
|
+
return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function getConfiguredPortableLearningsDir(projectRoot = process.cwd()) {
|
|
98
|
+
const config = readWorkspaceConfig(projectRoot);
|
|
99
|
+
const configured = resolveConfiguredLearningGlobalPath(config?.learning?.globalPath);
|
|
100
|
+
return configured || getUserFraimLearningsDir();
|
|
101
|
+
}
|
|
102
|
+
function getConfiguredPortableLearningsDisplayPath(projectRoot = process.cwd()) {
|
|
103
|
+
const config = readWorkspaceConfig(projectRoot);
|
|
104
|
+
const configured = resolveConfiguredLearningGlobalPath(config?.learning?.globalPath);
|
|
105
|
+
if (configured) {
|
|
106
|
+
return normalizeDisplayPath(configured);
|
|
107
|
+
}
|
|
108
|
+
return getUserFraimDisplayPath('personalized-employee/learnings').replace(/\/$/, '');
|
|
109
|
+
}
|
|
52
110
|
/**
|
|
53
111
|
* Determine the effective FRAIM content root directory.
|
|
54
112
|
*
|
|
@@ -60,7 +60,7 @@ function createInitialRows() {
|
|
|
60
60
|
{ id: 'node', label: 'Node.js', status: 'pending', verb: "we'll install" },
|
|
61
61
|
{ id: 'git', label: 'git', status: 'pending', verb: "we'll install", optional: true },
|
|
62
62
|
{ id: 'fraim', label: 'FRAIM', status: 'pending', verb: "we'll set up FRAIM" },
|
|
63
|
-
{ id: 'agent', label: '
|
|
63
|
+
{ id: 'agent', label: 'Execution surfaces', status: 'pending', verb: "we'll detect execution surfaces" },
|
|
64
64
|
];
|
|
65
65
|
}
|
|
66
66
|
/**
|