@phren/cli 0.0.10 → 0.0.12
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/README.md +11 -17
- package/mcp/dist/capabilities/cli.js +1 -1
- package/mcp/dist/capabilities/mcp.js +1 -1
- package/mcp/dist/capabilities/vscode.js +1 -1
- package/mcp/dist/capabilities/web-ui.js +1 -1
- package/mcp/dist/cli-actions.js +58 -71
- package/mcp/dist/cli-config.js +337 -131
- package/mcp/dist/cli-extract.js +3 -2
- package/mcp/dist/cli-govern.js +35 -63
- package/mcp/dist/cli-graph.js +19 -4
- package/mcp/dist/cli-hooks-globs.js +2 -1
- package/mcp/dist/cli-hooks-output.js +4 -4
- package/mcp/dist/cli-hooks-session.js +1 -1
- package/mcp/dist/cli-hooks.js +44 -35
- package/mcp/dist/cli-namespaces.js +15 -5
- package/mcp/dist/cli-search.js +2 -2
- package/mcp/dist/cli.js +1 -1
- package/mcp/dist/content-archive.js +23 -14
- package/mcp/dist/content-citation.js +13 -2
- package/mcp/dist/content-dedup.js +9 -9
- package/mcp/dist/content-learning.js +6 -4
- package/mcp/dist/content-metadata.js +10 -0
- package/mcp/dist/core-finding.js +1 -1
- package/mcp/dist/data-access.js +10 -31
- package/mcp/dist/data-tasks.js +5 -26
- package/mcp/dist/embedding.js +7 -8
- package/mcp/dist/entrypoint.js +133 -102
- package/mcp/dist/finding-impact.js +1 -32
- package/mcp/dist/finding-journal.js +1 -1
- package/mcp/dist/finding-lifecycle.js +2 -7
- package/mcp/dist/governance-locks.js +12 -5
- package/mcp/dist/governance-policy.js +156 -9
- package/mcp/dist/governance-scores.js +4 -10
- package/mcp/dist/hooks.js +62 -18
- package/mcp/dist/index.js +4 -4
- package/mcp/dist/init-config.js +4 -25
- package/mcp/dist/init-preferences.js +1 -1
- package/mcp/dist/init-setup.js +6 -55
- package/mcp/dist/init-shared.js +53 -1
- package/mcp/dist/init.js +191 -29
- package/mcp/dist/link-checksums.js +3 -2
- package/mcp/dist/link-context.js +2 -2
- package/mcp/dist/link-doctor.js +14 -57
- package/mcp/dist/link-skills.js +98 -12
- package/mcp/dist/link.js +16 -75
- package/mcp/dist/machine-identity.js +1 -9
- package/mcp/dist/mcp-config.js +247 -42
- package/mcp/dist/mcp-data.js +9 -9
- package/mcp/dist/mcp-extract-facts.js +12 -7
- package/mcp/dist/mcp-extract.js +2 -2
- package/mcp/dist/mcp-finding.js +16 -20
- package/mcp/dist/mcp-graph.js +12 -12
- package/mcp/dist/mcp-hooks.js +1 -1
- package/mcp/dist/mcp-ops.js +18 -18
- package/mcp/dist/mcp-search.js +11 -16
- package/mcp/dist/mcp-session.js +12 -2
- package/mcp/dist/memory-ui-assets.js +1 -36
- package/mcp/dist/memory-ui-graph.js +152 -50
- package/mcp/dist/memory-ui-page.js +30 -5
- package/mcp/dist/memory-ui-scripts.js +252 -63
- package/mcp/dist/memory-ui-server.js +115 -3
- package/mcp/dist/phren-core.js +2 -0
- package/mcp/dist/phren-paths.js +8 -9
- package/mcp/dist/proactivity.js +5 -5
- package/mcp/dist/profile-store.js +2 -2
- package/mcp/dist/project-config.js +64 -17
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/query-correlation.js +22 -19
- package/mcp/dist/session-checkpoints.js +14 -14
- package/mcp/dist/session-utils.js +3 -2
- package/mcp/dist/shared-data-utils.js +28 -0
- package/mcp/dist/shared-fragment-graph.js +22 -21
- package/mcp/dist/shared-governance.js +1 -1
- package/mcp/dist/shared-index.js +144 -105
- package/mcp/dist/shared-retrieval.js +21 -23
- package/mcp/dist/shared-search-fallback.js +15 -25
- package/mcp/dist/shared-sqljs.js +3 -2
- package/mcp/dist/shared.js +5 -6
- package/mcp/dist/shell-entry.js +1 -1
- package/mcp/dist/shell-input.js +63 -53
- package/mcp/dist/shell-palette.js +6 -1
- package/mcp/dist/shell-render.js +9 -5
- package/mcp/dist/shell-state-store.js +2 -5
- package/mcp/dist/shell-view.js +7 -6
- package/mcp/dist/shell.js +5 -55
- package/mcp/dist/skill-files.js +4 -10
- package/mcp/dist/skill-registry.js +3 -0
- package/mcp/dist/status.js +43 -21
- package/mcp/dist/task-hygiene.js +1 -1
- package/mcp/dist/telemetry.js +5 -4
- package/mcp/dist/update.js +1 -1
- package/mcp/dist/utils.js +4 -4
- package/package.json +2 -3
- package/skills/docs.md +11 -11
- package/starter/README.md +1 -1
- package/starter/global/CLAUDE.md +2 -2
- package/starter/global/skills/audit.md +106 -0
- package/mcp/dist/cli-hooks-retrieval.js +0 -2
- package/mcp/dist/impact-scoring.js +0 -22
- package/mcp/dist/shared-paths.js +0 -1
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Returns a <script> block with shared browser helpers used across all UI IIFEs:
|
|
3
|
+
* window._phrenEsc(s) — HTML-escape a value
|
|
4
|
+
* window._phrenAuthToken — the current auth token
|
|
5
|
+
* window._phrenAuthUrl(base) — append _auth param to a URL
|
|
6
|
+
* window._phrenAuthBody(body) — append _auth param to a form body
|
|
7
|
+
* window._phrenFetchCsrfToken(cb) — fetch the CSRF token and call cb(token)
|
|
8
|
+
*/
|
|
9
|
+
export function renderSharedWebUiHelpers(authToken) {
|
|
10
|
+
return `(function() {
|
|
11
|
+
window._phrenAuthToken = '${authToken}';
|
|
12
|
+
window._phrenEsc = function(s) {
|
|
13
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
14
|
+
};
|
|
15
|
+
window._phrenAuthUrl = function(base) {
|
|
16
|
+
var tok = window._phrenAuthToken;
|
|
17
|
+
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(tok);
|
|
18
|
+
};
|
|
19
|
+
window._phrenAuthBody = function(body) {
|
|
20
|
+
var tok = window._phrenAuthToken;
|
|
21
|
+
return body + (tok ? '&_auth=' + encodeURIComponent(tok) : '');
|
|
22
|
+
};
|
|
23
|
+
window._phrenFetchCsrfToken = function(cb) {
|
|
24
|
+
var tok = window._phrenAuthToken;
|
|
25
|
+
var url = '/api/csrf-token' + (tok ? '?_auth=' + encodeURIComponent(tok) : '');
|
|
26
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
27
|
+
};
|
|
28
|
+
})();`;
|
|
29
|
+
}
|
|
30
|
+
export function renderSkillUiEnhancementScript(_authToken) {
|
|
2
31
|
return `(function() {
|
|
3
|
-
var _skillAuthToken = '${authToken}';
|
|
4
32
|
var _skillCurrent = null;
|
|
5
33
|
var _skillEditing = false;
|
|
34
|
+
var esc = window._phrenEsc;
|
|
35
|
+
var authUrl = window._phrenAuthUrl;
|
|
36
|
+
var authBody = window._phrenAuthBody;
|
|
37
|
+
var fetchCsrfToken = window._phrenFetchCsrfToken;
|
|
6
38
|
|
|
7
|
-
function esc(s) {
|
|
8
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
9
|
-
}
|
|
10
|
-
function authUrl(base) {
|
|
11
|
-
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_skillAuthToken);
|
|
12
|
-
}
|
|
13
|
-
function authBody(body) {
|
|
14
|
-
return body + (_skillAuthToken ? '&_auth=' + encodeURIComponent(_skillAuthToken) : '');
|
|
15
|
-
}
|
|
16
|
-
function fetchCsrfToken(cb) {
|
|
17
|
-
var url = '/api/csrf-token' + (_skillAuthToken ? '?_auth=' + encodeURIComponent(_skillAuthToken) : '');
|
|
18
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
19
|
-
}
|
|
20
39
|
function renderSkillReader(content) {
|
|
21
40
|
var reader = document.getElementById('skills-reader');
|
|
22
41
|
if (!_skillCurrent || !reader) return;
|
|
@@ -166,9 +185,8 @@ export function renderSkillUiEnhancementScript(authToken) {
|
|
|
166
185
|
});
|
|
167
186
|
})();`;
|
|
168
187
|
}
|
|
169
|
-
export function renderProjectReferenceEnhancementScript(
|
|
188
|
+
export function renderProjectReferenceEnhancementScript(_authToken) {
|
|
170
189
|
return `(function() {
|
|
171
|
-
var _referenceAuthToken = '${authToken}';
|
|
172
190
|
var _referenceState = {
|
|
173
191
|
project: '',
|
|
174
192
|
topicsData: null,
|
|
@@ -177,20 +195,11 @@ export function renderProjectReferenceEnhancementScript(authToken) {
|
|
|
177
195
|
selectedKey: '',
|
|
178
196
|
editor: null
|
|
179
197
|
};
|
|
198
|
+
var esc = window._phrenEsc;
|
|
199
|
+
var authUrl = window._phrenAuthUrl;
|
|
200
|
+
var authBody = window._phrenAuthBody;
|
|
201
|
+
var fetchCsrfToken = window._phrenFetchCsrfToken;
|
|
180
202
|
|
|
181
|
-
function esc(s) {
|
|
182
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
183
|
-
}
|
|
184
|
-
function authUrl(base) {
|
|
185
|
-
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_referenceAuthToken);
|
|
186
|
-
}
|
|
187
|
-
function authBody(body) {
|
|
188
|
-
return body + (_referenceAuthToken ? '&_auth=' + encodeURIComponent(_referenceAuthToken) : '');
|
|
189
|
-
}
|
|
190
|
-
function fetchCsrfToken(cb) {
|
|
191
|
-
var url = '/api/csrf-token' + (_referenceAuthToken ? '?_auth=' + encodeURIComponent(_referenceAuthToken) : '');
|
|
192
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
193
|
-
}
|
|
194
203
|
function currentProject() {
|
|
195
204
|
var selected = document.querySelector('.project-card.selected');
|
|
196
205
|
return selected ? (selected.getAttribute('data-project') || '') : '';
|
|
@@ -585,15 +594,12 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
585
594
|
return `(function() {
|
|
586
595
|
var _tsAuthToken = '${authToken}';
|
|
587
596
|
var _allTasks = [];
|
|
597
|
+
var esc = window._phrenEsc;
|
|
588
598
|
|
|
589
599
|
function tsAuthUrl(base) {
|
|
590
600
|
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_tsAuthToken);
|
|
591
601
|
}
|
|
592
602
|
|
|
593
|
-
function esc(s) {
|
|
594
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
603
|
function priorityBadge(p) {
|
|
598
604
|
if (!p) return '';
|
|
599
605
|
var colors = { high: '#ef4444', medium: '#f59e0b', low: '#6b7280' };
|
|
@@ -839,14 +845,95 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
839
845
|
});
|
|
840
846
|
}
|
|
841
847
|
|
|
848
|
+
function getSettingsProject() {
|
|
849
|
+
var sel = document.getElementById('settings-project-select');
|
|
850
|
+
return sel ? sel.value : '';
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function postProjectOverride(project, field, value, clearField) {
|
|
854
|
+
var csrfUrl = _tsAuthToken ? tsAuthUrl('/api/csrf-token') : '/api/csrf-token';
|
|
855
|
+
fetch(csrfUrl).then(function(r) { return r.json(); }).then(function(csrfData) {
|
|
856
|
+
var payload = { project: project, field: field, value: value || '', clear: clearField ? 'true' : 'false' };
|
|
857
|
+
var body = new URLSearchParams(payload);
|
|
858
|
+
if (csrfData.token) body.set('_csrf', csrfData.token);
|
|
859
|
+
var url = _tsAuthToken ? tsAuthUrl('/api/settings/project-overrides') : '/api/settings/project-overrides';
|
|
860
|
+
return fetch(url, { method: 'POST', body: body, headers: { 'content-type': 'application/x-www-form-urlencoded' } });
|
|
861
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
862
|
+
if (!data.ok) {
|
|
863
|
+
setSettingsStatus(data.error || 'Failed to update project override', 'err');
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
_settingsLoaded = false;
|
|
867
|
+
loadSettings();
|
|
868
|
+
setSettingsStatus('Project override updated', 'ok');
|
|
869
|
+
}).catch(function(err) {
|
|
870
|
+
setSettingsStatus('Failed: ' + String(err), 'err');
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
842
874
|
function loadSettings() {
|
|
843
|
-
var
|
|
875
|
+
var selectedProject = getSettingsProject();
|
|
876
|
+
var baseUrl = '/api/settings';
|
|
877
|
+
if (selectedProject) baseUrl += '?project=' + encodeURIComponent(selectedProject);
|
|
878
|
+
var url = _tsAuthToken ? tsAuthUrl(baseUrl) : baseUrl;
|
|
879
|
+
|
|
880
|
+
// Populate project selector on first load
|
|
881
|
+
var sel = document.getElementById('settings-project-select');
|
|
882
|
+
if (sel && sel.querySelectorAll('option[data-proj]').length === 0) {
|
|
883
|
+
var configUrl = _tsAuthToken ? tsAuthUrl('/api/config') : '/api/config';
|
|
884
|
+
fetch(configUrl).then(function(r) { return r.json(); }).then(function(d) {
|
|
885
|
+
if (d.ok && d.projects && d.projects.length && sel) {
|
|
886
|
+
d.projects.forEach(function(p) {
|
|
887
|
+
var opt = document.createElement('option');
|
|
888
|
+
opt.value = p; opt.textContent = p;
|
|
889
|
+
opt.setAttribute('data-proj', '1');
|
|
890
|
+
sel.appendChild(opt);
|
|
891
|
+
});
|
|
892
|
+
if (selectedProject) sel.value = selectedProject;
|
|
893
|
+
}
|
|
894
|
+
}).catch(function() {});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
var scopeNote = document.getElementById('settings-scope-note');
|
|
898
|
+
if (scopeNote) {
|
|
899
|
+
scopeNote.textContent = selectedProject
|
|
900
|
+
? 'Showing effective config for "' + selectedProject + '". Overrides are saved to that project\'s phren.project.yaml.'
|
|
901
|
+
: 'Showing global settings. Select a project to view and edit per-project overrides.';
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Wire onChange once
|
|
905
|
+
if (sel && !sel.getAttribute('data-onchange-wired')) {
|
|
906
|
+
sel.setAttribute('data-onchange-wired', '1');
|
|
907
|
+
sel.addEventListener('change', function() {
|
|
908
|
+
_settingsLoaded = false;
|
|
909
|
+
loadSettings();
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
844
913
|
fetch(url).then(function(r) { return r.json(); }).then(function(data) {
|
|
845
914
|
if (!data.ok) {
|
|
846
915
|
setSettingsStatus(data.error || 'Failed to load settings', 'err');
|
|
847
916
|
return;
|
|
848
917
|
}
|
|
849
918
|
|
|
919
|
+
// Use merged config when a project is selected, else global
|
|
920
|
+
var effective = (selectedProject && data.merged) ? data.merged : null;
|
|
921
|
+
var rawOverrides = (selectedProject && data.overrides) ? data.overrides : null;
|
|
922
|
+
var effectiveSensitivity = effective ? effective.findingSensitivity : (data.findingSensitivity || 'balanced');
|
|
923
|
+
var effectiveTaskMode = effective ? effective.taskMode : (data.taskMode || 'auto');
|
|
924
|
+
var effectiveProactivity = data.proactivity || 'high';
|
|
925
|
+
var effectiveRetention = (effective && effective.retentionPolicy) ? effective.retentionPolicy : (data.retentionPolicy || {});
|
|
926
|
+
var effectiveWorkflow = (effective && effective.workflowPolicy) ? effective.workflowPolicy : (data.workflowPolicy || {});
|
|
927
|
+
|
|
928
|
+
var isProject = Boolean(selectedProject);
|
|
929
|
+
|
|
930
|
+
function sourceBadge(isOverride) {
|
|
931
|
+
if (!isProject) return '';
|
|
932
|
+
return isOverride
|
|
933
|
+
? '<span style="font-size:10px;font-weight:600;color:var(--warning);margin-left:6px;padding:1px 6px;border:1px solid color-mix(in srgb,var(--warning) 40%,transparent);border-radius:var(--radius-sm)">project override</span>'
|
|
934
|
+
: '<span style="font-size:10px;color:var(--text-muted);margin-left:6px;padding:1px 6px;border:1px solid var(--border);border-radius:var(--radius-sm)">global default</span>';
|
|
935
|
+
}
|
|
936
|
+
|
|
850
937
|
var findingDescriptions = {
|
|
851
938
|
high: 'Capture findings proactively, including minor observations.',
|
|
852
939
|
medium: 'Capture findings that are likely useful.',
|
|
@@ -856,56 +943,129 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
856
943
|
|
|
857
944
|
var findingsEl = document.getElementById('settings-findings');
|
|
858
945
|
if (findingsEl) {
|
|
859
|
-
var fsUi = findingStorageToUi(
|
|
946
|
+
var fsUi = findingStorageToUi(effectiveSensitivity);
|
|
860
947
|
var findingsHtml = '';
|
|
948
|
+
var fsSensOverride = rawOverrides && rawOverrides.findingSensitivity != null;
|
|
861
949
|
findingsHtml += '<div class="settings-control">';
|
|
862
|
-
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Finding sensitivity</span
|
|
950
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Finding sensitivity</span>' + sourceBadge(fsSensOverride);
|
|
951
|
+
if (isProject && fsSensOverride) {
|
|
952
|
+
findingsHtml += '<button data-ts-action="clearProjectOverride" data-field="findingSensitivity" class="settings-chip" style="font-size:11px;margin-left:auto">Clear override</button>';
|
|
953
|
+
}
|
|
954
|
+
findingsHtml += '</div>';
|
|
863
955
|
findingsHtml += '<div class="settings-chip-row">';
|
|
864
956
|
['high', 'medium', 'low', 'minimal'].forEach(function(level) {
|
|
865
957
|
var active = level === fsUi ? ' active' : '';
|
|
866
|
-
|
|
958
|
+
var action = isProject ? 'setProjectFindingSensitivity' : 'setFindingSensitivity';
|
|
959
|
+
findingsHtml += '<button data-ts-action="' + action + '" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
867
960
|
});
|
|
868
961
|
findingsHtml += '</div>';
|
|
869
962
|
findingsHtml += '<div class="settings-control-note" id="settings-fs-desc">' + esc(findingDescriptions[fsUi] || '') + '</div>';
|
|
870
963
|
findingsHtml += '</div>';
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
964
|
+
if (!isProject) {
|
|
965
|
+
findingsHtml += '<div class="settings-control">';
|
|
966
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Auto-capture</span>';
|
|
967
|
+
findingsHtml += '<button data-ts-action="toggleAutoCapture" data-enabled="' + (data.autoCaptureEnabled ? 'true' : 'false') + '" class="settings-chip' + (data.autoCaptureEnabled ? ' active' : '') + '">' + (data.autoCaptureEnabled ? 'On' : 'Off') + '</button></div>';
|
|
968
|
+
findingsHtml += '<div class="settings-control-note">Turn automatic finding capture on or off.</div>';
|
|
969
|
+
findingsHtml += '</div>';
|
|
970
|
+
findingsHtml += '<div class="settings-control">';
|
|
971
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Consolidation threshold</span><span class="badge">' + esc(String(data.consolidationEntryThreshold || 25)) + ' entries</span></div>';
|
|
972
|
+
findingsHtml += '<div class="settings-control-note">Consolidation is also recommended after 60+ days with at least 10 new entries.</div>';
|
|
973
|
+
findingsHtml += '</div>';
|
|
974
|
+
}
|
|
880
975
|
findingsEl.innerHTML = findingsHtml;
|
|
881
976
|
}
|
|
882
977
|
|
|
883
978
|
var behaviorEl = document.getElementById('settings-behavior');
|
|
884
979
|
if (behaviorEl) {
|
|
885
|
-
var taskMode =
|
|
886
|
-
var proactivity =
|
|
980
|
+
var taskMode = effectiveTaskMode || 'auto';
|
|
981
|
+
var proactivity = effectiveProactivity;
|
|
887
982
|
var behaviorHtml = '';
|
|
983
|
+
var taskModeOverride = rawOverrides && rawOverrides.taskMode != null;
|
|
888
984
|
behaviorHtml += '<div class="settings-control">';
|
|
889
|
-
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Task mode</span
|
|
985
|
+
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Task mode</span>' + sourceBadge(taskModeOverride);
|
|
986
|
+
if (isProject && taskModeOverride) {
|
|
987
|
+
behaviorHtml += '<button data-ts-action="clearProjectOverride" data-field="taskMode" class="settings-chip" style="font-size:11px;margin-left:auto">Clear override</button>';
|
|
988
|
+
}
|
|
989
|
+
behaviorHtml += '</div>';
|
|
890
990
|
behaviorHtml += '<div class="settings-chip-row">';
|
|
891
|
-
['auto', 'manual', 'off'].forEach(function(mode) {
|
|
991
|
+
['auto', 'suggest', 'manual', 'off'].forEach(function(mode) {
|
|
892
992
|
var active = mode === taskMode ? ' active' : '';
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
behaviorHtml += '</div></div>';
|
|
896
|
-
behaviorHtml += '<div class="settings-control">';
|
|
897
|
-
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Proactivity level</span></div>';
|
|
898
|
-
behaviorHtml += '<div class="settings-chip-row">';
|
|
899
|
-
['high', 'medium', 'low'].forEach(function(level) {
|
|
900
|
-
var active = level === proactivity ? ' active' : '';
|
|
901
|
-
behaviorHtml += '<button data-ts-action="setProactivity" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
993
|
+
var action = isProject ? 'setProjectTaskMode' : 'setTaskMode';
|
|
994
|
+
behaviorHtml += '<button data-ts-action="' + action + '" data-mode="' + esc(mode) + '" class="settings-chip' + active + '">' + esc(mode) + '</button>';
|
|
902
995
|
});
|
|
903
996
|
behaviorHtml += '</div></div>';
|
|
997
|
+
if (!isProject) {
|
|
998
|
+
behaviorHtml += '<div class="settings-control">';
|
|
999
|
+
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Proactivity level</span></div>';
|
|
1000
|
+
behaviorHtml += '<div class="settings-chip-row">';
|
|
1001
|
+
['high', 'medium', 'low'].forEach(function(level) {
|
|
1002
|
+
var active = level === proactivity ? ' active' : '';
|
|
1003
|
+
behaviorHtml += '<button data-ts-action="setProactivity" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
1004
|
+
});
|
|
1005
|
+
behaviorHtml += '</div></div>';
|
|
1006
|
+
}
|
|
904
1007
|
behaviorEl.innerHTML = behaviorHtml;
|
|
905
1008
|
}
|
|
906
1009
|
|
|
1010
|
+
var retentionEl = document.getElementById('settings-retention');
|
|
1011
|
+
if (retentionEl) {
|
|
1012
|
+
var ret = effectiveRetention;
|
|
1013
|
+
var retHtml = '';
|
|
1014
|
+
function retRow(label, field, value, note) {
|
|
1015
|
+
var isOverride = isProject && rawOverrides && rawOverrides.retentionPolicy && rawOverrides.retentionPolicy[field] !== undefined;
|
|
1016
|
+
retHtml += '<div class="settings-control">';
|
|
1017
|
+
retHtml += '<div class="settings-control-header"><span class="settings-control-label">' + esc(label) + '</span>' + sourceBadge(isOverride);
|
|
1018
|
+
retHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(String(value != null ? value : '—')) + '</span>';
|
|
1019
|
+
if (isProject && isOverride) {
|
|
1020
|
+
retHtml += '<button data-ts-action="clearProjectOverride" data-field="' + esc(field) + '" class="settings-chip" style="font-size:11px">Clear</button>';
|
|
1021
|
+
}
|
|
1022
|
+
retHtml += '</div>';
|
|
1023
|
+
if (note) retHtml += '<div class="settings-control-note">' + esc(note) + '</div>';
|
|
1024
|
+
if (isProject) {
|
|
1025
|
+
retHtml += '<div style="display:flex;gap:8px;align-items:center;margin-top:8px">' +
|
|
1026
|
+
'<input type="number" id="ret-input-' + esc(field) + '" value="' + esc(String(value != null ? value : '')) + '" style="width:100px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 8px;font-size:var(--text-sm);background:var(--surface);color:var(--ink)">' +
|
|
1027
|
+
'<button data-ts-action="setProjectRetention" data-field="' + esc(field) + '" class="settings-chip active" style="font-size:11px">Set</button>' +
|
|
1028
|
+
'</div>';
|
|
1029
|
+
}
|
|
1030
|
+
retHtml += '</div>';
|
|
1031
|
+
}
|
|
1032
|
+
retRow('TTL days', 'ttlDays', ret.ttlDays, 'Memories older than this are eligible for pruning.');
|
|
1033
|
+
retRow('Retention days', 'retentionDays', ret.retentionDays, 'Hard cutoff — memories past this age are removed.');
|
|
1034
|
+
retRow('Auto-accept threshold', 'autoAcceptThreshold', ret.autoAcceptThreshold, 'Confidence score (0–1) above which memories are auto-accepted.');
|
|
1035
|
+
retRow('Min inject confidence', 'minInjectConfidence', ret.minInjectConfidence, 'Minimum confidence score to inject a memory into context.');
|
|
1036
|
+
retentionEl.innerHTML = retHtml;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
var workflowEl = document.getElementById('settings-workflow');
|
|
1040
|
+
if (workflowEl) {
|
|
1041
|
+
var wf = effectiveWorkflow;
|
|
1042
|
+
var wfHtml = '';
|
|
1043
|
+
var lctOverride = isProject && rawOverrides && rawOverrides.workflowPolicy && rawOverrides.workflowPolicy.lowConfidenceThreshold !== undefined;
|
|
1044
|
+
var riskySectionsOverride = isProject && rawOverrides && rawOverrides.workflowPolicy && Array.isArray(rawOverrides.workflowPolicy.riskySections) && rawOverrides.workflowPolicy.riskySections.length > 0;
|
|
1045
|
+
wfHtml += '<div class="settings-control">';
|
|
1046
|
+
wfHtml += '<div class="settings-control-header"><span class="settings-control-label">Low confidence threshold</span>' + sourceBadge(lctOverride);
|
|
1047
|
+
wfHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(String(wf.lowConfidenceThreshold != null ? wf.lowConfidenceThreshold : '—')) + '</span>';
|
|
1048
|
+
if (isProject && lctOverride) {
|
|
1049
|
+
wfHtml += '<button data-ts-action="clearProjectOverride" data-field="lowConfidenceThreshold" class="settings-chip" style="font-size:11px">Clear</button>';
|
|
1050
|
+
}
|
|
1051
|
+
if (isProject) {
|
|
1052
|
+
wfHtml += '</div><div style="display:flex;gap:8px;align-items:center;margin-top:8px">' +
|
|
1053
|
+
'<input type="number" id="wf-input-lowConfidenceThreshold" min="0" max="1" step="0.05" value="' + esc(String(wf.lowConfidenceThreshold != null ? wf.lowConfidenceThreshold : '')) + '" style="width:100px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 8px;font-size:var(--text-sm);background:var(--surface);color:var(--ink)">' +
|
|
1054
|
+
'<button data-ts-action="setProjectWorkflow" data-field="lowConfidenceThreshold" class="settings-chip active" style="font-size:11px">Set</button>' +
|
|
1055
|
+
'</div>';
|
|
1056
|
+
} else {
|
|
1057
|
+
wfHtml += '</div>';
|
|
1058
|
+
}
|
|
1059
|
+
wfHtml += '<div class="settings-control-note">Memories below this confidence score are flagged for review.</div></div>';
|
|
1060
|
+
wfHtml += '<div class="settings-control">';
|
|
1061
|
+
wfHtml += '<div class="settings-control-header"><span class="settings-control-label">Risky sections</span>' + sourceBadge(riskySectionsOverride);
|
|
1062
|
+
wfHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(Array.isArray(wf.riskySections) ? wf.riskySections.join(', ') : '—') + '</span></div>';
|
|
1063
|
+
wfHtml += '<div class="settings-control-note">Sections that trigger approval gates when memories are written.</div></div>';
|
|
1064
|
+
workflowEl.innerHTML = wfHtml;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
907
1067
|
var integrationsEl = document.getElementById('settings-integrations');
|
|
908
|
-
if (integrationsEl) {
|
|
1068
|
+
if (integrationsEl && !isProject) {
|
|
909
1069
|
var tools = Array.isArray(data.hookTools) ? data.hookTools : [];
|
|
910
1070
|
var html = '';
|
|
911
1071
|
html += '<div class="settings-control-header" style="margin-bottom:10px"><span class="settings-control-label">Global MCP</span>';
|
|
@@ -923,6 +1083,8 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
923
1083
|
});
|
|
924
1084
|
html += '</tbody></table>';
|
|
925
1085
|
integrationsEl.innerHTML = html;
|
|
1086
|
+
} else if (integrationsEl && isProject) {
|
|
1087
|
+
integrationsEl.innerHTML = '<div style="color:var(--muted);font-size:var(--text-sm)">Integration settings are global — switch to Global scope to edit them.</div>';
|
|
926
1088
|
}
|
|
927
1089
|
}).catch(function(err) {
|
|
928
1090
|
setSettingsStatus('Failed to load settings: ' + String(err), 'err');
|
|
@@ -1063,6 +1225,33 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
1063
1225
|
else if (action === 'toggleIntegrationTool') { toggleIntegrationTool(actionEl.getAttribute('data-tool')); }
|
|
1064
1226
|
else if (action === 'showSessionDetail') { showSessionDetail(actionEl.getAttribute('data-session-id')); }
|
|
1065
1227
|
else if (action === 'backToSessionsList') { backToSessionsList(); }
|
|
1228
|
+
else if (action === 'setProjectFindingSensitivity') {
|
|
1229
|
+
var proj = getSettingsProject();
|
|
1230
|
+
var level = actionEl.getAttribute('data-level');
|
|
1231
|
+
postProjectOverride(proj, 'findingSensitivity', findingUiToStorage(level || 'medium'), false);
|
|
1232
|
+
}
|
|
1233
|
+
else if (action === 'setProjectTaskMode') {
|
|
1234
|
+
var proj = getSettingsProject();
|
|
1235
|
+
postProjectOverride(proj, 'taskMode', actionEl.getAttribute('data-mode') || 'auto', false);
|
|
1236
|
+
}
|
|
1237
|
+
else if (action === 'clearProjectOverride') {
|
|
1238
|
+
var proj = getSettingsProject();
|
|
1239
|
+
postProjectOverride(proj, actionEl.getAttribute('data-field') || '', '', true);
|
|
1240
|
+
}
|
|
1241
|
+
else if (action === 'setProjectRetention') {
|
|
1242
|
+
var proj = getSettingsProject();
|
|
1243
|
+
var field = actionEl.getAttribute('data-field') || '';
|
|
1244
|
+
var inputEl = document.getElementById('ret-input-' + field);
|
|
1245
|
+
var val = inputEl ? inputEl.value : '';
|
|
1246
|
+
postProjectOverride(proj, field, val, false);
|
|
1247
|
+
}
|
|
1248
|
+
else if (action === 'setProjectWorkflow') {
|
|
1249
|
+
var proj = getSettingsProject();
|
|
1250
|
+
var field = actionEl.getAttribute('data-field') || '';
|
|
1251
|
+
var inputEl = document.getElementById('wf-input-' + field);
|
|
1252
|
+
var val = inputEl ? inputEl.value : '';
|
|
1253
|
+
postProjectOverride(proj, field, val, false);
|
|
1254
|
+
}
|
|
1066
1255
|
});
|
|
1067
1256
|
|
|
1068
1257
|
window.setFindingSensitivity = function(level) {
|
|
@@ -1104,10 +1293,10 @@ export function renderSearchScript(authToken) {
|
|
|
1104
1293
|
var _searchProjectsLoaded = false;
|
|
1105
1294
|
|
|
1106
1295
|
function searchAuthUrl(path) {
|
|
1107
|
-
return _searchAuthToken ? path + (path.includes('?') ? '&' : '?') + '
|
|
1296
|
+
return window._phrenAuthUrl ? window._phrenAuthUrl(path) : (_searchAuthToken ? path + (path.includes('?') ? '&' : '?') + '_auth=' + encodeURIComponent(_searchAuthToken) : path);
|
|
1108
1297
|
}
|
|
1109
1298
|
|
|
1110
|
-
|
|
1299
|
+
var esc = window._phrenEsc;
|
|
1111
1300
|
|
|
1112
1301
|
function doSearch() {
|
|
1113
1302
|
var q = document.getElementById('search-query').value.trim();
|
|
@@ -12,7 +12,8 @@ import { readInstallPreferences, writeInstallPreferences, writeGovernanceInstall
|
|
|
12
12
|
import { buildGraph, collectProjectsForUI, collectSkillsForUI, getHooksData, isAllowedFilePath, readSyncSnapshot, recentAccepted, recentUsage, } from "./memory-ui-data.js";
|
|
13
13
|
import { CONSOLIDATION_ENTRY_THRESHOLD } from "./content-validate.js";
|
|
14
14
|
import { ensureTopicReferenceDoc, getProjectTopicsResponse, listProjectReferenceDocs, pinProjectTopicSuggestion, readReferenceContent, reclassifyLegacyTopicDocs, unpinProjectTopicSuggestion, writeProjectTopics, } from "./project-topics.js";
|
|
15
|
-
import { getWorkflowPolicy, updateWorkflowPolicy } from "./governance-policy.js";
|
|
15
|
+
import { getWorkflowPolicy, updateWorkflowPolicy, mergeConfig, getRetentionPolicy, getProjectConfigOverrides } from "./governance-policy.js";
|
|
16
|
+
import { updateProjectConfigOverrides } from "./project-config.js";
|
|
16
17
|
import { findSkill } from "./skill-registry.js";
|
|
17
18
|
import { setSkillEnabledAndSync } from "./skill-files.js";
|
|
18
19
|
import { listAllSessions, getSessionArtifacts } from "./mcp-session.js";
|
|
@@ -290,7 +291,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
290
291
|
repairPreexistingInstall(phrenPath);
|
|
291
292
|
}
|
|
292
293
|
catch (err) {
|
|
293
|
-
if ((process.env.PHREN_DEBUG
|
|
294
|
+
if ((process.env.PHREN_DEBUG))
|
|
294
295
|
process.stderr.write(`[phren] web-ui repair: ${errorMessage(err)}\n`);
|
|
295
296
|
}
|
|
296
297
|
const authToken = opts?.authToken;
|
|
@@ -365,7 +366,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
365
366
|
}
|
|
366
367
|
catch (err) {
|
|
367
368
|
res.writeHead(200, { "content-type": "application/json" });
|
|
368
|
-
res.end(JSON.stringify({ ok: false, error:
|
|
369
|
+
res.end(JSON.stringify({ ok: false, error: errorMessage(err) }));
|
|
369
370
|
}
|
|
370
371
|
});
|
|
371
372
|
return;
|
|
@@ -856,8 +857,13 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
856
857
|
try {
|
|
857
858
|
const prefs = readInstallPreferences(phrenPath);
|
|
858
859
|
const workflowPolicy = getWorkflowPolicy(phrenPath);
|
|
860
|
+
const retentionPolicy = getRetentionPolicy(phrenPath);
|
|
859
861
|
const hooksData = getHooksData(phrenPath);
|
|
860
862
|
const proactivityFindings = prefs.proactivityFindings || prefs.proactivity || "high";
|
|
863
|
+
const qs = url.includes("?") ? querystring.parse(url.slice(url.indexOf("?") + 1)) : {};
|
|
864
|
+
const settingsProject = String(qs.project || "");
|
|
865
|
+
const merged = settingsProject && isValidProjectName(settingsProject) ? mergeConfig(phrenPath, settingsProject) : null;
|
|
866
|
+
const overrides = settingsProject && isValidProjectName(settingsProject) ? getProjectConfigOverrides(phrenPath, settingsProject) : null;
|
|
861
867
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
862
868
|
res.end(JSON.stringify({
|
|
863
869
|
ok: true,
|
|
@@ -871,6 +877,10 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
871
877
|
hooksEnabled: hooksData.globalEnabled,
|
|
872
878
|
mcpEnabled: prefs.mcpEnabled !== false,
|
|
873
879
|
hookTools: hooksData.tools,
|
|
880
|
+
retentionPolicy,
|
|
881
|
+
workflowPolicy,
|
|
882
|
+
merged,
|
|
883
|
+
overrides,
|
|
874
884
|
}));
|
|
875
885
|
}
|
|
876
886
|
catch (err) {
|
|
@@ -975,6 +985,108 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
975
985
|
});
|
|
976
986
|
return;
|
|
977
987
|
}
|
|
988
|
+
// POST /api/settings/project-overrides — write per-project config overrides
|
|
989
|
+
if (req.method === "POST" && pathname === "/api/settings/project-overrides") {
|
|
990
|
+
void readFormBody(req, res).then((parsed) => {
|
|
991
|
+
if (!parsed)
|
|
992
|
+
return;
|
|
993
|
+
if (!requirePostAuth(req, res, url, parsed, authToken, true))
|
|
994
|
+
return;
|
|
995
|
+
if (!requireCsrf(res, parsed, csrfTokens, true))
|
|
996
|
+
return;
|
|
997
|
+
const project = String(parsed.project || "");
|
|
998
|
+
const field = String(parsed.field || "");
|
|
999
|
+
const value = String(parsed.value || "");
|
|
1000
|
+
const clearField = String(parsed.clear || "") === "true";
|
|
1001
|
+
if (!project || !isValidProjectName(project)) {
|
|
1002
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
1003
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid project name" }));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const registeredProjects = getProjectDirs(phrenPath, profile).map((d) => path.basename(d)).filter((p) => p !== "global");
|
|
1007
|
+
const isRegistered = registeredProjects.includes(project);
|
|
1008
|
+
const registrationWarning = isRegistered ? undefined : `Project '${project}' is not registered in the active profile. Config was saved but it will have no effect until the project is added with 'phren add'.`;
|
|
1009
|
+
const VALID_FIELDS = {
|
|
1010
|
+
findingSensitivity: ["minimal", "conservative", "balanced", "aggressive"],
|
|
1011
|
+
proactivity: ["high", "medium", "low"],
|
|
1012
|
+
proactivityFindings: ["high", "medium", "low"],
|
|
1013
|
+
proactivityTask: ["high", "medium", "low"],
|
|
1014
|
+
taskMode: ["off", "manual", "suggest", "auto"],
|
|
1015
|
+
};
|
|
1016
|
+
const NUMERIC_RETENTION_FIELDS = ["ttlDays", "retentionDays", "autoAcceptThreshold", "minInjectConfidence"];
|
|
1017
|
+
const NUMERIC_WORKFLOW_FIELDS = ["lowConfidenceThreshold"];
|
|
1018
|
+
try {
|
|
1019
|
+
updateProjectConfigOverrides(phrenPath, project, (current) => {
|
|
1020
|
+
const next = { ...current };
|
|
1021
|
+
if (clearField) {
|
|
1022
|
+
if (field in VALID_FIELDS)
|
|
1023
|
+
delete next[field];
|
|
1024
|
+
else if (NUMERIC_RETENTION_FIELDS.includes(field)) {
|
|
1025
|
+
if (next.retentionPolicy)
|
|
1026
|
+
delete next.retentionPolicy[field];
|
|
1027
|
+
}
|
|
1028
|
+
else if (NUMERIC_WORKFLOW_FIELDS.includes(field)) {
|
|
1029
|
+
if (next.workflowPolicy)
|
|
1030
|
+
delete next.workflowPolicy[field];
|
|
1031
|
+
}
|
|
1032
|
+
return next;
|
|
1033
|
+
}
|
|
1034
|
+
if (field in VALID_FIELDS) {
|
|
1035
|
+
const allowed = VALID_FIELDS[field];
|
|
1036
|
+
if (!allowed.includes(value))
|
|
1037
|
+
throw new Error(`Invalid value "${value}" for ${field}`);
|
|
1038
|
+
next[field] = value;
|
|
1039
|
+
}
|
|
1040
|
+
else if (NUMERIC_RETENTION_FIELDS.includes(field)) {
|
|
1041
|
+
const num = parseFloat(value);
|
|
1042
|
+
if (!Number.isFinite(num) || num < 0)
|
|
1043
|
+
throw new Error(`Invalid numeric value for ${field}`);
|
|
1044
|
+
next.retentionPolicy = { ...next.retentionPolicy, [field]: num };
|
|
1045
|
+
}
|
|
1046
|
+
else if (NUMERIC_WORKFLOW_FIELDS.includes(field)) {
|
|
1047
|
+
const num = parseFloat(value);
|
|
1048
|
+
if (!Number.isFinite(num) || num < 0 || num > 1)
|
|
1049
|
+
throw new Error(`Invalid value for ${field} (must be 0–1)`);
|
|
1050
|
+
next.workflowPolicy = { ...next.workflowPolicy, [field]: num };
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
throw new Error(`Unknown config field: ${field}`);
|
|
1054
|
+
}
|
|
1055
|
+
return next;
|
|
1056
|
+
});
|
|
1057
|
+
const merged = mergeConfig(phrenPath, project);
|
|
1058
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1059
|
+
res.end(JSON.stringify({ ok: true, config: merged }));
|
|
1060
|
+
}
|
|
1061
|
+
catch (err) {
|
|
1062
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1063
|
+
res.end(JSON.stringify({ ok: false, error: errorMessage(err) }));
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (req.method === "GET" && pathname === "/api/config") {
|
|
1069
|
+
if (!requireGetAuth(req, res, url, authToken, true))
|
|
1070
|
+
return;
|
|
1071
|
+
const qs = url.includes("?") ? querystring.parse(url.slice(url.indexOf("?") + 1)) : {};
|
|
1072
|
+
const project = String(qs.project || "");
|
|
1073
|
+
if (project && !isValidProjectName(project)) {
|
|
1074
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
1075
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid project name" }));
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
const config = mergeConfig(phrenPath, project || undefined);
|
|
1080
|
+
const projects = getProjectDirs(phrenPath, profile).map((d) => path.basename(d)).filter((p) => p !== "global");
|
|
1081
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1082
|
+
res.end(JSON.stringify({ ok: true, config, projects }));
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1086
|
+
res.end(JSON.stringify({ ok: false, error: errorMessage(err) }));
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
978
1090
|
if (req.method === "GET" && pathname === "/api/csrf-token") {
|
|
979
1091
|
if (!requireGetAuth(req, res, url, authToken, true))
|
|
980
1092
|
return;
|
package/mcp/dist/phren-core.js
CHANGED
|
@@ -24,6 +24,8 @@ export const EXTRA_ENTITY_PATTERNS = [
|
|
|
24
24
|
// ISO date references: 2025-03-11, 2025/03/11
|
|
25
25
|
{ re: /\b\d{4}[-/]\d{2}[-/]\d{2}\b/g, label: "date" },
|
|
26
26
|
];
|
|
27
|
+
/** Union of all directory names reserved by phren infrastructure — not valid project names. */
|
|
28
|
+
export const RESERVED_PROJECT_DIR_NAMES = new Set(["global", ".runtime", ".sessions", ".governance", "profiles", "templates"]);
|
|
27
29
|
// Default timeout for execFileSync calls (30s for most operations, 10s for quick probes like `which`)
|
|
28
30
|
export const EXEC_TIMEOUT_MS = 30_000;
|
|
29
31
|
export const EXEC_TIMEOUT_QUICK_MS = 10_000;
|