@phren/cli 0.0.32 → 0.0.34
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/mcp/dist/cli/actions.js +3 -0
- package/mcp/dist/cli/config.js +3 -3
- package/mcp/dist/cli/govern.js +18 -8
- package/mcp/dist/cli/hooks-context.js +1 -1
- package/mcp/dist/cli/hooks-session.js +18 -62
- package/mcp/dist/cli/namespaces.js +1 -1
- package/mcp/dist/cli/search.js +5 -5
- package/mcp/dist/cli-hooks-prompt.js +7 -3
- package/mcp/dist/cli-hooks-session-handlers.js +3 -15
- package/mcp/dist/cli-hooks-stop.js +10 -48
- package/mcp/dist/content/archive.js +8 -20
- package/mcp/dist/content/learning.js +29 -8
- package/mcp/dist/data/access.js +13 -4
- package/mcp/dist/finding/lifecycle.js +9 -3
- package/mcp/dist/governance/audit.js +13 -5
- package/mcp/dist/governance/policy.js +13 -0
- package/mcp/dist/governance/rbac.js +1 -1
- package/mcp/dist/governance/scores.js +2 -1
- package/mcp/dist/hooks.js +52 -6
- package/mcp/dist/index.js +1 -1
- package/mcp/dist/init/init.js +66 -45
- package/mcp/dist/init/shared.js +1 -1
- package/mcp/dist/init-bootstrap.js +0 -47
- package/mcp/dist/init-fresh.js +13 -18
- package/mcp/dist/init-uninstall.js +22 -0
- package/mcp/dist/init-walkthrough.js +19 -24
- package/mcp/dist/link/doctor.js +9 -0
- package/mcp/dist/package-metadata.js +1 -1
- package/mcp/dist/phren-art.js +4 -120
- package/mcp/dist/proactivity.js +1 -1
- package/mcp/dist/project-topics.js +16 -46
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/runtime-profile.js +1 -1
- package/mcp/dist/shared/data-utils.js +25 -0
- package/mcp/dist/shared/fragment-graph.js +4 -18
- package/mcp/dist/shared/index.js +14 -10
- package/mcp/dist/shared/ollama.js +23 -5
- package/mcp/dist/shared/process.js +24 -0
- package/mcp/dist/shared/retrieval.js +7 -4
- package/mcp/dist/shared/search-fallback.js +1 -0
- package/mcp/dist/shared.js +2 -1
- package/mcp/dist/shell/render.js +1 -1
- package/mcp/dist/skill/registry.js +1 -1
- package/mcp/dist/skill/state.js +0 -3
- package/mcp/dist/task/github.js +1 -0
- package/mcp/dist/task/lifecycle.js +1 -6
- package/mcp/dist/tools/config.js +415 -400
- package/mcp/dist/tools/finding.js +390 -373
- package/mcp/dist/tools/ops.js +372 -365
- package/mcp/dist/tools/search.js +495 -487
- package/mcp/dist/tools/session.js +3 -2
- package/mcp/dist/tools/skills.js +9 -0
- package/mcp/dist/ui/page.js +1 -1
- package/mcp/dist/ui/server.js +645 -1040
- package/mcp/dist/utils.js +12 -8
- package/package.json +1 -1
- package/mcp/dist/init-dryrun.js +0 -55
- package/mcp/dist/init-migrate.js +0 -51
- package/mcp/dist/init-walkthrough-merge.js +0 -90
package/mcp/dist/tools/config.js
CHANGED
|
@@ -63,453 +63,468 @@ function getTopicConfigData(phrenPath, project) {
|
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
-
// ──
|
|
67
|
-
|
|
66
|
+
// ── Handlers ─────────────────────────────────────────────────────────────────
|
|
67
|
+
async function handleGetConfig(ctx, { domain, project }) {
|
|
68
68
|
const { phrenPath } = ctx;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"Returns both configured and effective values. When project is provided, returns " +
|
|
75
|
-
"the merged view with project overrides applied and _source annotations.",
|
|
76
|
-
inputSchema: z.object({
|
|
77
|
-
domain: z
|
|
78
|
-
.enum(["proactivity", "taskMode", "findingSensitivity", "retention", "workflow", "access", "index", "topic", "all"])
|
|
79
|
-
.optional()
|
|
80
|
-
.describe("Config domain to read. Defaults to 'all'."),
|
|
81
|
-
project: projectParam,
|
|
82
|
-
}),
|
|
83
|
-
}, async ({ domain, project }) => {
|
|
84
|
-
const d = domain ?? "all";
|
|
85
|
-
// topic domain requires a project
|
|
86
|
-
if (d === "topic") {
|
|
87
|
-
if (!project) {
|
|
88
|
-
return mcpResponse({ ok: false, error: "The 'topic' domain requires a project parameter." });
|
|
89
|
-
}
|
|
90
|
-
const err = validateProject(project);
|
|
91
|
-
if (err)
|
|
92
|
-
return mcpResponse({ ok: false, error: err });
|
|
93
|
-
const topicResult = getTopicConfigData(phrenPath, project);
|
|
94
|
-
if (!topicResult.ok)
|
|
95
|
-
return mcpResponse({ ok: false, error: topicResult.error });
|
|
96
|
-
return mcpResponse({
|
|
97
|
-
ok: true,
|
|
98
|
-
message: `Topic config for "${project}" (source: ${topicResult.data.source}).`,
|
|
99
|
-
data: topicResult.data,
|
|
100
|
-
});
|
|
69
|
+
const d = domain ?? "all";
|
|
70
|
+
// topic domain requires a project
|
|
71
|
+
if (d === "topic") {
|
|
72
|
+
if (!project) {
|
|
73
|
+
return mcpResponse({ ok: false, error: "The 'topic' domain requires a project parameter." });
|
|
101
74
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
if (d === "all" || d === "taskMode") {
|
|
124
|
-
result.taskMode = { taskMode: resolved.taskMode, _source: src("taskMode") };
|
|
125
|
-
}
|
|
126
|
-
if (d === "all" || d === "retention") {
|
|
127
|
-
result.retention = {
|
|
128
|
-
...resolved.retentionPolicy,
|
|
129
|
-
_source: hasOwnOverride(projectOverrides, "retentionPolicy") ? "project" : "global",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (d === "all" || d === "workflow") {
|
|
133
|
-
result.workflow = {
|
|
134
|
-
...resolved.workflowPolicy,
|
|
135
|
-
_source: hasOwnOverride(projectOverrides, "workflowPolicy") ? "project" : "global",
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (d === "all" || d === "proactivity") {
|
|
139
|
-
const globalSnapshot = proactivitySnapshot(phrenPath).effective;
|
|
140
|
-
const base = resolved.proactivity.base ?? globalSnapshot.proactivity;
|
|
141
|
-
const findings = resolved.proactivity.findings ?? resolved.proactivity.base ?? globalSnapshot.proactivityFindings;
|
|
142
|
-
const tasks = resolved.proactivity.tasks ?? resolved.proactivity.base ?? globalSnapshot.proactivityTask;
|
|
143
|
-
result.proactivity = {
|
|
144
|
-
base,
|
|
145
|
-
findings,
|
|
146
|
-
tasks,
|
|
147
|
-
_source: {
|
|
148
|
-
base: hasOwnOverride(projectOverrides, "proactivity") ? "project" : "global",
|
|
149
|
-
findings: hasOwnOverride(projectOverrides, "proactivityFindings")
|
|
150
|
-
? "project"
|
|
151
|
-
: hasOwnOverride(projectOverrides, "proactivity")
|
|
152
|
-
? "project"
|
|
153
|
-
: "global",
|
|
154
|
-
tasks: hasOwnOverride(projectOverrides, "proactivityTask")
|
|
155
|
-
? "project"
|
|
156
|
-
: hasOwnOverride(projectOverrides, "proactivity")
|
|
157
|
-
? "project"
|
|
158
|
-
: "global",
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
if (d === "all" || d === "index") {
|
|
163
|
-
result.index = getIndexPolicy(phrenPath);
|
|
164
|
-
}
|
|
165
|
-
return mcpResponse({
|
|
166
|
-
ok: true,
|
|
167
|
-
message: `Config for ${d === "all" ? "all domains" : d} (project: ${project}).`,
|
|
168
|
-
data: result,
|
|
169
|
-
});
|
|
75
|
+
const err = validateProject(project);
|
|
76
|
+
if (err)
|
|
77
|
+
return mcpResponse({ ok: false, error: err });
|
|
78
|
+
const topicResult = getTopicConfigData(phrenPath, project);
|
|
79
|
+
if (!topicResult.ok)
|
|
80
|
+
return mcpResponse({ ok: false, error: topicResult.error });
|
|
81
|
+
return mcpResponse({
|
|
82
|
+
ok: true,
|
|
83
|
+
message: `Topic config for "${project}" (source: ${topicResult.data.source}).`,
|
|
84
|
+
data: topicResult.data,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (project) {
|
|
88
|
+
const err = validateProject(project);
|
|
89
|
+
if (err)
|
|
90
|
+
return mcpResponse({ ok: false, error: err });
|
|
91
|
+
const resolved = mergeConfig(phrenPath, project);
|
|
92
|
+
const projectOverrides = getProjectOverrides(phrenPath, project);
|
|
93
|
+
function src(key) {
|
|
94
|
+
return hasOwnOverride(projectOverrides, key) ? "project" : "global";
|
|
170
95
|
}
|
|
171
|
-
const result = {
|
|
172
|
-
|
|
173
|
-
|
|
96
|
+
const result = {
|
|
97
|
+
_project: project,
|
|
98
|
+
_note: "Values marked _source=project override the global default.",
|
|
99
|
+
};
|
|
100
|
+
if (d === "all" || d === "findingSensitivity") {
|
|
101
|
+
const level = resolved.findingSensitivity;
|
|
102
|
+
result.findingSensitivity = {
|
|
103
|
+
level,
|
|
104
|
+
...FINDING_SENSITIVITY_CONFIG[level],
|
|
105
|
+
_source: src("findingSensitivity"),
|
|
106
|
+
};
|
|
174
107
|
}
|
|
175
108
|
if (d === "all" || d === "taskMode") {
|
|
176
|
-
|
|
177
|
-
result.taskMode = { taskMode: wf.taskMode };
|
|
178
|
-
}
|
|
179
|
-
if (d === "all" || d === "findingSensitivity") {
|
|
180
|
-
const wf = getWorkflowPolicy(phrenPath);
|
|
181
|
-
const level = wf.findingSensitivity;
|
|
182
|
-
const config = FINDING_SENSITIVITY_CONFIG[level];
|
|
183
|
-
result.findingSensitivity = { level, ...config };
|
|
109
|
+
result.taskMode = { taskMode: resolved.taskMode, _source: src("taskMode") };
|
|
184
110
|
}
|
|
185
111
|
if (d === "all" || d === "retention") {
|
|
186
|
-
result.retention =
|
|
112
|
+
result.retention = {
|
|
113
|
+
...resolved.retentionPolicy,
|
|
114
|
+
_source: hasOwnOverride(projectOverrides, "retentionPolicy") ? "project" : "global",
|
|
115
|
+
};
|
|
187
116
|
}
|
|
188
117
|
if (d === "all" || d === "workflow") {
|
|
189
|
-
result.workflow =
|
|
118
|
+
result.workflow = {
|
|
119
|
+
...resolved.workflowPolicy,
|
|
120
|
+
_source: hasOwnOverride(projectOverrides, "workflowPolicy") ? "project" : "global",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (d === "all" || d === "proactivity") {
|
|
124
|
+
const globalSnapshot = proactivitySnapshot(phrenPath).effective;
|
|
125
|
+
const base = resolved.proactivity.base ?? globalSnapshot.proactivity;
|
|
126
|
+
const findings = resolved.proactivity.findings ?? resolved.proactivity.base ?? globalSnapshot.proactivityFindings;
|
|
127
|
+
const tasks = resolved.proactivity.tasks ?? resolved.proactivity.base ?? globalSnapshot.proactivityTask;
|
|
128
|
+
result.proactivity = {
|
|
129
|
+
base,
|
|
130
|
+
findings,
|
|
131
|
+
tasks,
|
|
132
|
+
_source: {
|
|
133
|
+
base: hasOwnOverride(projectOverrides, "proactivity") ? "project" : "global",
|
|
134
|
+
findings: hasOwnOverride(projectOverrides, "proactivityFindings")
|
|
135
|
+
? "project"
|
|
136
|
+
: hasOwnOverride(projectOverrides, "proactivity")
|
|
137
|
+
? "project"
|
|
138
|
+
: "global",
|
|
139
|
+
tasks: hasOwnOverride(projectOverrides, "proactivityTask")
|
|
140
|
+
? "project"
|
|
141
|
+
: hasOwnOverride(projectOverrides, "proactivity")
|
|
142
|
+
? "project"
|
|
143
|
+
: "global",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
190
146
|
}
|
|
191
147
|
if (d === "all" || d === "index") {
|
|
192
148
|
result.index = getIndexPolicy(phrenPath);
|
|
193
149
|
}
|
|
194
150
|
return mcpResponse({
|
|
195
151
|
ok: true,
|
|
196
|
-
message: `Config for ${d === "all" ? "all domains" : d}.`,
|
|
152
|
+
message: `Config for ${d === "all" ? "all domains" : d} (project: ${project}).`,
|
|
197
153
|
data: result,
|
|
198
154
|
});
|
|
155
|
+
}
|
|
156
|
+
const result = {};
|
|
157
|
+
if (d === "all" || d === "proactivity") {
|
|
158
|
+
result.proactivity = proactivitySnapshot(phrenPath);
|
|
159
|
+
}
|
|
160
|
+
if (d === "all" || d === "taskMode") {
|
|
161
|
+
const wf = getWorkflowPolicy(phrenPath);
|
|
162
|
+
result.taskMode = { taskMode: wf.taskMode };
|
|
163
|
+
}
|
|
164
|
+
if (d === "all" || d === "findingSensitivity") {
|
|
165
|
+
const wf = getWorkflowPolicy(phrenPath);
|
|
166
|
+
const level = wf.findingSensitivity;
|
|
167
|
+
const config = FINDING_SENSITIVITY_CONFIG[level];
|
|
168
|
+
result.findingSensitivity = { level, ...config };
|
|
169
|
+
}
|
|
170
|
+
if (d === "all" || d === "retention") {
|
|
171
|
+
result.retention = getRetentionPolicy(phrenPath);
|
|
172
|
+
}
|
|
173
|
+
if (d === "all" || d === "workflow") {
|
|
174
|
+
result.workflow = getWorkflowPolicy(phrenPath);
|
|
175
|
+
}
|
|
176
|
+
if (d === "all" || d === "index") {
|
|
177
|
+
result.index = getIndexPolicy(phrenPath);
|
|
178
|
+
}
|
|
179
|
+
return mcpResponse({
|
|
180
|
+
ok: true,
|
|
181
|
+
message: `Config for ${d === "all" ? "all domains" : d}.`,
|
|
182
|
+
data: result,
|
|
199
183
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
const scope = settings.scope ?? "base";
|
|
226
|
-
if (!["base", "findings", "tasks"].includes(scope)) {
|
|
227
|
-
return mcpResponse({ ok: false, error: `Invalid scope. Must be one of: base, findings, tasks.` });
|
|
228
|
-
}
|
|
229
|
-
const s = scope;
|
|
230
|
-
if (project) {
|
|
231
|
-
const err = validateProject(project);
|
|
232
|
-
if (err)
|
|
233
|
-
return mcpResponse({ ok: false, error: err });
|
|
234
|
-
const warning = checkProjectRegistered(phrenPath, project);
|
|
235
|
-
const key = s === "base" ? "proactivity" : s === "findings" ? "proactivityFindings" : "proactivityTask";
|
|
236
|
-
updateProjectConfigOverrides(phrenPath, project, (current) => ({
|
|
237
|
-
...current,
|
|
238
|
-
[key]: level,
|
|
239
|
-
}));
|
|
240
|
-
return mcpResponse({
|
|
241
|
-
ok: true,
|
|
242
|
-
message: warning
|
|
243
|
-
? `Proactivity ${s} set to ${level} for project "${project}". WARNING: ${warning}`
|
|
244
|
-
: `Proactivity ${s} set to ${level} for project "${project}".`,
|
|
245
|
-
data: { project, scope: s, level, ...(warning ? { warning } : {}) },
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
const patch = {};
|
|
249
|
-
if (s === "base")
|
|
250
|
-
patch.proactivity = level;
|
|
251
|
-
else if (s === "findings")
|
|
252
|
-
patch.proactivityFindings = level;
|
|
253
|
-
else if (s === "tasks")
|
|
254
|
-
patch.proactivityTask = level;
|
|
255
|
-
writeGovernanceInstallPreferences(phrenPath, patch);
|
|
184
|
+
}
|
|
185
|
+
async function handleSetConfig(ctx, { domain, settings, project }) {
|
|
186
|
+
const { phrenPath } = ctx;
|
|
187
|
+
switch (domain) {
|
|
188
|
+
// ── proactivity ───────────────────────────────────────────────
|
|
189
|
+
case "proactivity": {
|
|
190
|
+
const level = settings.level;
|
|
191
|
+
if (!level || !PROACTIVITY_LEVELS.includes(level)) {
|
|
192
|
+
return mcpResponse({ ok: false, error: `Invalid proactivity level. Must be one of: ${PROACTIVITY_LEVELS.join(", ")}.` });
|
|
193
|
+
}
|
|
194
|
+
const scope = settings.scope ?? "base";
|
|
195
|
+
if (!["base", "findings", "tasks"].includes(scope)) {
|
|
196
|
+
return mcpResponse({ ok: false, error: `Invalid scope. Must be one of: base, findings, tasks.` });
|
|
197
|
+
}
|
|
198
|
+
const s = scope;
|
|
199
|
+
if (project) {
|
|
200
|
+
const err = validateProject(project);
|
|
201
|
+
if (err)
|
|
202
|
+
return mcpResponse({ ok: false, error: err });
|
|
203
|
+
const warning = checkProjectRegistered(phrenPath, project);
|
|
204
|
+
const key = s === "base" ? "proactivity" : s === "findings" ? "proactivityFindings" : "proactivityTask";
|
|
205
|
+
updateProjectConfigOverrides(phrenPath, project, (current) => ({
|
|
206
|
+
...current,
|
|
207
|
+
[key]: level,
|
|
208
|
+
}));
|
|
256
209
|
return mcpResponse({
|
|
257
210
|
ok: true,
|
|
258
|
-
message:
|
|
259
|
-
|
|
211
|
+
message: warning
|
|
212
|
+
? `Proactivity ${s} set to ${level} for project "${project}". WARNING: ${warning}`
|
|
213
|
+
: `Proactivity ${s} set to ${level} for project "${project}".`,
|
|
214
|
+
data: { project, scope: s, level, ...(warning ? { warning } : {}) },
|
|
260
215
|
});
|
|
261
216
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
217
|
+
const patch = {};
|
|
218
|
+
if (s === "base")
|
|
219
|
+
patch.proactivity = level;
|
|
220
|
+
else if (s === "findings")
|
|
221
|
+
patch.proactivityFindings = level;
|
|
222
|
+
else if (s === "tasks")
|
|
223
|
+
patch.proactivityTask = level;
|
|
224
|
+
writeGovernanceInstallPreferences(phrenPath, patch);
|
|
225
|
+
return mcpResponse({
|
|
226
|
+
ok: true,
|
|
227
|
+
message: `Proactivity ${s} set to ${level}.`,
|
|
228
|
+
data: proactivitySnapshot(phrenPath),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// ── taskMode ──────────────────────────────────────────────────
|
|
232
|
+
case "taskMode": {
|
|
233
|
+
const mode = settings.mode;
|
|
234
|
+
if (!mode || !VALID_TASK_MODES.includes(mode)) {
|
|
235
|
+
return mcpResponse({ ok: false, error: `Invalid task mode. Must be one of: ${VALID_TASK_MODES.join(", ")}.` });
|
|
236
|
+
}
|
|
237
|
+
const validMode = mode;
|
|
238
|
+
if (project) {
|
|
239
|
+
const err = validateProject(project);
|
|
240
|
+
if (err)
|
|
241
|
+
return mcpResponse({ ok: false, error: err });
|
|
242
|
+
const warning = checkProjectRegistered(phrenPath, project);
|
|
243
|
+
updateProjectConfigOverrides(phrenPath, project, (current) => ({
|
|
244
|
+
...current,
|
|
245
|
+
taskMode: validMode,
|
|
246
|
+
}));
|
|
290
247
|
return mcpResponse({
|
|
291
248
|
ok: true,
|
|
292
|
-
message:
|
|
293
|
-
|
|
249
|
+
message: warning
|
|
250
|
+
? `Task mode set to ${validMode} for project "${project}". WARNING: ${warning}`
|
|
251
|
+
: `Task mode set to ${validMode} for project "${project}".`,
|
|
252
|
+
data: { project, taskMode: validMode, ...(warning ? { warning } : {}) },
|
|
294
253
|
});
|
|
295
254
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (!result.ok) {
|
|
323
|
-
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
324
|
-
}
|
|
255
|
+
const result = updateWorkflowPolicy(phrenPath, { taskMode: validMode });
|
|
256
|
+
if (!result.ok) {
|
|
257
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
258
|
+
}
|
|
259
|
+
return mcpResponse({
|
|
260
|
+
ok: true,
|
|
261
|
+
message: `Task mode set to ${validMode}.`,
|
|
262
|
+
data: { taskMode: validMode },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
// ── findingSensitivity ────────────────────────────────────────
|
|
266
|
+
case "findingSensitivity": {
|
|
267
|
+
const level = settings.level;
|
|
268
|
+
if (!level || !VALID_FINDING_SENSITIVITY.includes(level)) {
|
|
269
|
+
return mcpResponse({ ok: false, error: `Invalid finding sensitivity. Must be one of: ${VALID_FINDING_SENSITIVITY.join(", ")}.` });
|
|
270
|
+
}
|
|
271
|
+
const validLevel = level;
|
|
272
|
+
if (project) {
|
|
273
|
+
const err = validateProject(project);
|
|
274
|
+
if (err)
|
|
275
|
+
return mcpResponse({ ok: false, error: err });
|
|
276
|
+
const warning = checkProjectRegistered(phrenPath, project);
|
|
277
|
+
updateProjectConfigOverrides(phrenPath, project, (current) => ({
|
|
278
|
+
...current,
|
|
279
|
+
findingSensitivity: validLevel,
|
|
280
|
+
}));
|
|
325
281
|
const config = FINDING_SENSITIVITY_CONFIG[validLevel];
|
|
326
282
|
return mcpResponse({
|
|
327
283
|
ok: true,
|
|
328
|
-
message:
|
|
329
|
-
|
|
284
|
+
message: warning
|
|
285
|
+
? `Finding sensitivity set to ${validLevel} for project "${project}". WARNING: ${warning}`
|
|
286
|
+
: `Finding sensitivity set to ${validLevel} for project "${project}".`,
|
|
287
|
+
data: { project, level: validLevel, ...config, ...(warning ? { warning } : {}) },
|
|
330
288
|
});
|
|
331
289
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (project) {
|
|
336
|
-
const err = validateProject(project);
|
|
337
|
-
if (err)
|
|
338
|
-
return mcpResponse({ ok: false, error: err });
|
|
339
|
-
const warning = checkProjectRegistered(phrenPath, project);
|
|
340
|
-
const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
|
|
341
|
-
const existingRetention = current.retentionPolicy ?? {};
|
|
342
|
-
const retentionPatch = { ...existingRetention };
|
|
343
|
-
if (ttlDays !== undefined)
|
|
344
|
-
retentionPatch.ttlDays = ttlDays;
|
|
345
|
-
if (retentionDays !== undefined)
|
|
346
|
-
retentionPatch.retentionDays = retentionDays;
|
|
347
|
-
if (autoAcceptThreshold !== undefined)
|
|
348
|
-
retentionPatch.autoAcceptThreshold = autoAcceptThreshold;
|
|
349
|
-
if (minInjectConfidence !== undefined)
|
|
350
|
-
retentionPatch.minInjectConfidence = minInjectConfidence;
|
|
351
|
-
if (decay !== undefined)
|
|
352
|
-
retentionPatch.decay = { ...(existingRetention.decay ?? {}), ...decay };
|
|
353
|
-
return { ...current, retentionPolicy: retentionPatch };
|
|
354
|
-
});
|
|
355
|
-
return mcpResponse({
|
|
356
|
-
ok: true,
|
|
357
|
-
message: warning
|
|
358
|
-
? `Retention policy updated for project "${project}". WARNING: ${warning}`
|
|
359
|
-
: `Retention policy updated for project "${project}".`,
|
|
360
|
-
data: { project, retentionPolicy: next.config?.retentionPolicy ?? {}, ...(warning ? { warning } : {}) },
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
const globalPatch = {};
|
|
364
|
-
if (ttlDays !== undefined)
|
|
365
|
-
globalPatch.ttlDays = ttlDays;
|
|
366
|
-
if (retentionDays !== undefined)
|
|
367
|
-
globalPatch.retentionDays = retentionDays;
|
|
368
|
-
if (autoAcceptThreshold !== undefined)
|
|
369
|
-
globalPatch.autoAcceptThreshold = autoAcceptThreshold;
|
|
370
|
-
if (minInjectConfidence !== undefined)
|
|
371
|
-
globalPatch.minInjectConfidence = minInjectConfidence;
|
|
372
|
-
if (decay !== undefined)
|
|
373
|
-
globalPatch.decay = decay;
|
|
374
|
-
const result = updateRetentionPolicy(phrenPath, globalPatch);
|
|
375
|
-
if (!result.ok) {
|
|
376
|
-
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
377
|
-
}
|
|
378
|
-
return mcpResponse({
|
|
379
|
-
ok: true,
|
|
380
|
-
message: "Retention policy updated.",
|
|
381
|
-
data: result.data,
|
|
382
|
-
});
|
|
290
|
+
const result = updateWorkflowPolicy(phrenPath, { findingSensitivity: validLevel });
|
|
291
|
+
if (!result.ok) {
|
|
292
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
383
293
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
message: warning
|
|
414
|
-
? `Workflow policy updated for project "${project}". WARNING: ${warning}`
|
|
415
|
-
: `Workflow policy updated for project "${project}".`,
|
|
416
|
-
data: { project, config: next.config ?? {}, ...(warning ? { warning } : {}) },
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
const patch = {};
|
|
420
|
-
if (lowConfidenceThreshold !== undefined)
|
|
421
|
-
patch.lowConfidenceThreshold = lowConfidenceThreshold;
|
|
422
|
-
if (riskySections !== undefined)
|
|
423
|
-
patch.riskySections = riskySections;
|
|
424
|
-
if (taskMode !== undefined)
|
|
425
|
-
patch.taskMode = taskMode;
|
|
426
|
-
if (findingSensitivity !== undefined)
|
|
427
|
-
patch.findingSensitivity = findingSensitivity;
|
|
428
|
-
const result = updateWorkflowPolicy(phrenPath, patch);
|
|
429
|
-
if (!result.ok) {
|
|
430
|
-
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
431
|
-
}
|
|
432
|
-
return mcpResponse({
|
|
433
|
-
ok: true,
|
|
434
|
-
message: "Workflow policy updated.",
|
|
435
|
-
data: result.data,
|
|
294
|
+
const config = FINDING_SENSITIVITY_CONFIG[validLevel];
|
|
295
|
+
return mcpResponse({
|
|
296
|
+
ok: true,
|
|
297
|
+
message: `Finding sensitivity set to ${validLevel}.`,
|
|
298
|
+
data: { level: validLevel, ...config },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// ── retention ─────────────────────────────────────────────────
|
|
302
|
+
case "retention": {
|
|
303
|
+
const { ttlDays, retentionDays, autoAcceptThreshold, minInjectConfidence, decay } = settings;
|
|
304
|
+
if (project) {
|
|
305
|
+
const err = validateProject(project);
|
|
306
|
+
if (err)
|
|
307
|
+
return mcpResponse({ ok: false, error: err });
|
|
308
|
+
const warning = checkProjectRegistered(phrenPath, project);
|
|
309
|
+
const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
|
|
310
|
+
const existingRetention = current.retentionPolicy ?? {};
|
|
311
|
+
const retentionPatch = { ...existingRetention };
|
|
312
|
+
if (ttlDays !== undefined)
|
|
313
|
+
retentionPatch.ttlDays = ttlDays;
|
|
314
|
+
if (retentionDays !== undefined)
|
|
315
|
+
retentionPatch.retentionDays = retentionDays;
|
|
316
|
+
if (autoAcceptThreshold !== undefined)
|
|
317
|
+
retentionPatch.autoAcceptThreshold = autoAcceptThreshold;
|
|
318
|
+
if (minInjectConfidence !== undefined)
|
|
319
|
+
retentionPatch.minInjectConfidence = minInjectConfidence;
|
|
320
|
+
if (decay !== undefined)
|
|
321
|
+
retentionPatch.decay = { ...(existingRetention.decay ?? {}), ...decay };
|
|
322
|
+
return { ...current, retentionPolicy: retentionPatch };
|
|
436
323
|
});
|
|
437
|
-
}
|
|
438
|
-
// ── index ─────────────────────────────────────────────────────
|
|
439
|
-
case "index": {
|
|
440
|
-
const { includeGlobs, excludeGlobs, includeHidden } = settings;
|
|
441
|
-
const patch = {};
|
|
442
|
-
if (includeGlobs !== undefined)
|
|
443
|
-
patch.includeGlobs = includeGlobs;
|
|
444
|
-
if (excludeGlobs !== undefined)
|
|
445
|
-
patch.excludeGlobs = excludeGlobs;
|
|
446
|
-
if (includeHidden !== undefined)
|
|
447
|
-
patch.includeHidden = includeHidden;
|
|
448
|
-
const result = updateIndexPolicy(phrenPath, patch);
|
|
449
|
-
if (!result.ok) {
|
|
450
|
-
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
451
|
-
}
|
|
452
324
|
return mcpResponse({
|
|
453
325
|
ok: true,
|
|
454
|
-
message:
|
|
455
|
-
|
|
326
|
+
message: warning
|
|
327
|
+
? `Retention policy updated for project "${project}". WARNING: ${warning}`
|
|
328
|
+
: `Retention policy updated for project "${project}".`,
|
|
329
|
+
data: { project, retentionPolicy: next.config?.retentionPolicy ?? {}, ...(warning ? { warning } : {}) },
|
|
456
330
|
});
|
|
457
331
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
332
|
+
const globalPatch = {};
|
|
333
|
+
if (ttlDays !== undefined)
|
|
334
|
+
globalPatch.ttlDays = ttlDays;
|
|
335
|
+
if (retentionDays !== undefined)
|
|
336
|
+
globalPatch.retentionDays = retentionDays;
|
|
337
|
+
if (autoAcceptThreshold !== undefined)
|
|
338
|
+
globalPatch.autoAcceptThreshold = autoAcceptThreshold;
|
|
339
|
+
if (minInjectConfidence !== undefined)
|
|
340
|
+
globalPatch.minInjectConfidence = minInjectConfidence;
|
|
341
|
+
if (decay !== undefined)
|
|
342
|
+
globalPatch.decay = decay;
|
|
343
|
+
const result = updateRetentionPolicy(phrenPath, globalPatch);
|
|
344
|
+
if (!result.ok) {
|
|
345
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
346
|
+
}
|
|
347
|
+
return mcpResponse({
|
|
348
|
+
ok: true,
|
|
349
|
+
message: "Retention policy updated.",
|
|
350
|
+
data: result.data,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
// ── workflow ──────────────────────────────────────────────────
|
|
354
|
+
case "workflow": {
|
|
355
|
+
const { lowConfidenceThreshold, riskySections, taskMode, findingSensitivity } = settings;
|
|
356
|
+
if (project) {
|
|
463
357
|
const err = validateProject(project);
|
|
464
358
|
if (err)
|
|
465
359
|
return mcpResponse({ ok: false, error: err });
|
|
466
|
-
|
|
467
|
-
if (
|
|
468
|
-
return mcpResponse({ ok: false, error: `
|
|
360
|
+
// Validate enums before entering the callback
|
|
361
|
+
if (taskMode !== undefined && !VALID_TASK_MODES.includes(taskMode)) {
|
|
362
|
+
return mcpResponse({ ok: false, error: `Invalid task mode. Must be one of: ${VALID_TASK_MODES.join(", ")}.` });
|
|
469
363
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return mcpResponse({ ok: false, error: "The 'topic' domain requires a 'topics' array in settings." });
|
|
364
|
+
if (findingSensitivity !== undefined && !VALID_FINDING_SENSITIVITY.includes(findingSensitivity)) {
|
|
365
|
+
return mcpResponse({ ok: false, error: `Invalid finding sensitivity. Must be one of: ${VALID_FINDING_SENSITIVITY.join(", ")}.` });
|
|
473
366
|
}
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
367
|
+
const warning = checkProjectRegistered(phrenPath, project);
|
|
368
|
+
const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
|
|
369
|
+
const nextConfig = { ...current };
|
|
370
|
+
const shouldUpdateWorkflowPolicy = (lowConfidenceThreshold !== undefined
|
|
371
|
+
|| riskySections !== undefined
|
|
372
|
+
|| current.workflowPolicy !== undefined);
|
|
373
|
+
if (shouldUpdateWorkflowPolicy) {
|
|
374
|
+
const existingWorkflow = current.workflowPolicy ?? {};
|
|
375
|
+
nextConfig.workflowPolicy = {
|
|
376
|
+
...existingWorkflow,
|
|
377
|
+
...(lowConfidenceThreshold !== undefined ? { lowConfidenceThreshold } : {}),
|
|
378
|
+
...(riskySections !== undefined ? { riskySections: riskySections } : {}),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (taskMode !== undefined)
|
|
382
|
+
nextConfig.taskMode = taskMode;
|
|
383
|
+
if (findingSensitivity !== undefined)
|
|
384
|
+
nextConfig.findingSensitivity = findingSensitivity;
|
|
385
|
+
return nextConfig;
|
|
386
|
+
});
|
|
387
|
+
return mcpResponse({
|
|
388
|
+
ok: true,
|
|
389
|
+
message: warning
|
|
390
|
+
? `Workflow policy updated for project "${project}". WARNING: ${warning}`
|
|
391
|
+
: `Workflow policy updated for project "${project}".`,
|
|
392
|
+
data: { project, config: next.config ?? {}, ...(warning ? { warning } : {}) },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
const patch = {};
|
|
396
|
+
if (lowConfidenceThreshold !== undefined)
|
|
397
|
+
patch.lowConfidenceThreshold = lowConfidenceThreshold;
|
|
398
|
+
if (riskySections !== undefined)
|
|
399
|
+
patch.riskySections = riskySections;
|
|
400
|
+
if (taskMode !== undefined) {
|
|
401
|
+
if (!VALID_TASK_MODES.includes(taskMode))
|
|
402
|
+
return mcpResponse({ ok: false, error: `Invalid task mode. Must be one of: ${VALID_TASK_MODES.join(", ")}.` });
|
|
403
|
+
patch.taskMode = taskMode;
|
|
404
|
+
}
|
|
405
|
+
if (findingSensitivity !== undefined) {
|
|
406
|
+
if (!VALID_FINDING_SENSITIVITY.includes(findingSensitivity))
|
|
407
|
+
return mcpResponse({ ok: false, error: `Invalid finding sensitivity. Must be one of: ${VALID_FINDING_SENSITIVITY.join(", ")}.` });
|
|
408
|
+
patch.findingSensitivity = findingSensitivity;
|
|
409
|
+
}
|
|
410
|
+
const result = updateWorkflowPolicy(phrenPath, patch);
|
|
411
|
+
if (!result.ok) {
|
|
412
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
413
|
+
}
|
|
414
|
+
return mcpResponse({
|
|
415
|
+
ok: true,
|
|
416
|
+
message: "Workflow policy updated.",
|
|
417
|
+
data: result.data,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
// ── index ─────────────────────────────────────────────────────
|
|
421
|
+
case "index": {
|
|
422
|
+
const { includeGlobs, excludeGlobs, includeHidden } = settings;
|
|
423
|
+
const patch = {};
|
|
424
|
+
if (includeGlobs !== undefined)
|
|
425
|
+
patch.includeGlobs = includeGlobs;
|
|
426
|
+
if (excludeGlobs !== undefined)
|
|
427
|
+
patch.excludeGlobs = excludeGlobs;
|
|
428
|
+
if (includeHidden !== undefined)
|
|
429
|
+
patch.includeHidden = includeHidden;
|
|
430
|
+
const result = updateIndexPolicy(phrenPath, patch);
|
|
431
|
+
if (!result.ok) {
|
|
432
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
433
|
+
}
|
|
434
|
+
return mcpResponse({
|
|
435
|
+
ok: true,
|
|
436
|
+
message: "Index policy updated.",
|
|
437
|
+
data: result.data,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// ── topic ─────────────────────────────────────────────────────
|
|
441
|
+
case "topic": {
|
|
442
|
+
if (!project) {
|
|
443
|
+
return mcpResponse({ ok: false, error: "The 'topic' domain requires a project parameter." });
|
|
444
|
+
}
|
|
445
|
+
const err = validateProject(project);
|
|
446
|
+
if (err)
|
|
447
|
+
return mcpResponse({ ok: false, error: err });
|
|
448
|
+
const projectDir = safeProjectPath(phrenPath, project);
|
|
449
|
+
if (!projectDir || !fs.existsSync(projectDir)) {
|
|
450
|
+
return mcpResponse({ ok: false, error: `Project "${project}" not found in phren.` });
|
|
451
|
+
}
|
|
452
|
+
const topics = settings.topics;
|
|
453
|
+
if (!topics || !Array.isArray(topics)) {
|
|
454
|
+
return mcpResponse({ ok: false, error: "The 'topic' domain requires a 'topics' array in settings." });
|
|
455
|
+
}
|
|
456
|
+
const topicDomain = settings.domain;
|
|
457
|
+
const normalized = topics.map((t) => ({
|
|
458
|
+
slug: t.slug,
|
|
459
|
+
label: t.label,
|
|
460
|
+
description: t.description ?? "",
|
|
461
|
+
keywords: t.keywords ?? [],
|
|
462
|
+
}));
|
|
463
|
+
// If a domain is provided, patch it onto the existing file before writing topics
|
|
464
|
+
if (topicDomain) {
|
|
465
|
+
const configPath = path.join(projectDir, "topic-config.json");
|
|
466
|
+
if (fs.existsSync(configPath)) {
|
|
467
|
+
try {
|
|
468
|
+
const existing = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
469
|
+
if (existing && typeof existing === "object") {
|
|
470
|
+
existing.domain = topicDomain;
|
|
471
|
+
fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
494
472
|
}
|
|
495
473
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
fs.writeFileSync(configPath, JSON.stringify({ version: 1, domain: topicDomain, topics: [] }, null, 2) + "\n");
|
|
474
|
+
catch {
|
|
475
|
+
// ignore read errors; writeProjectTopics will still succeed
|
|
499
476
|
}
|
|
500
477
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
478
|
+
else {
|
|
479
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
480
|
+
fs.writeFileSync(configPath, JSON.stringify({ version: 1, domain: topicDomain, topics: [] }, null, 2) + "\n");
|
|
504
481
|
}
|
|
505
|
-
return mcpResponse({
|
|
506
|
-
ok: true,
|
|
507
|
-
message: `Topic config written for "${project}" (${result.topics.length} topics).`,
|
|
508
|
-
data: { project, topics: result.topics, domain: topicDomain ?? null },
|
|
509
|
-
});
|
|
510
482
|
}
|
|
511
|
-
|
|
512
|
-
|
|
483
|
+
const result = writeProjectTopics(phrenPath, project, normalized);
|
|
484
|
+
if (!result.ok) {
|
|
485
|
+
return mcpResponse({ ok: false, error: result.error });
|
|
486
|
+
}
|
|
487
|
+
return mcpResponse({
|
|
488
|
+
ok: true,
|
|
489
|
+
message: `Topic config written for "${project}" (${result.topics.length} topics).`,
|
|
490
|
+
data: { project, topics: result.topics, domain: topicDomain ?? null },
|
|
491
|
+
});
|
|
513
492
|
}
|
|
514
|
-
|
|
493
|
+
default:
|
|
494
|
+
return mcpResponse({ ok: false, error: `Unknown config domain: ${domain}` });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// ── Registration ────────────────────────────────────────────────────────────
|
|
498
|
+
export function register(server, ctx) {
|
|
499
|
+
server.registerTool("get_config", {
|
|
500
|
+
title: "◆ phren · get config",
|
|
501
|
+
description: "Read current configuration for one or all config domains: proactivity, taskMode, " +
|
|
502
|
+
"findingSensitivity, retention (policy), workflow, access, index, topic. " +
|
|
503
|
+
"Returns both configured and effective values. When project is provided, returns " +
|
|
504
|
+
"the merged view with project overrides applied and _source annotations.",
|
|
505
|
+
inputSchema: z.object({
|
|
506
|
+
domain: z
|
|
507
|
+
.enum(["proactivity", "taskMode", "findingSensitivity", "retention", "workflow", "access", "index", "topic", "all"])
|
|
508
|
+
.optional()
|
|
509
|
+
.describe("Config domain to read. Defaults to 'all'."),
|
|
510
|
+
project: projectParam,
|
|
511
|
+
}),
|
|
512
|
+
}, (params) => handleGetConfig(ctx, params));
|
|
513
|
+
server.registerTool("set_config", {
|
|
514
|
+
title: "◆ phren · set config",
|
|
515
|
+
description: "Update configuration for a specific domain. Replaces set_proactivity, set_task_mode, " +
|
|
516
|
+
"set_finding_sensitivity, set_retention_policy, set_workflow_policy, set_index_policy, " +
|
|
517
|
+
"and set_topic_config. When project is provided, writes to that project's phren.project.yaml " +
|
|
518
|
+
"instead of global .config/.",
|
|
519
|
+
inputSchema: z.object({
|
|
520
|
+
domain: z.enum(["proactivity", "taskMode", "findingSensitivity", "retention", "workflow", "index", "topic"]),
|
|
521
|
+
settings: z.record(z.string(), z.unknown()).describe("Domain-specific settings. proactivity: { level, scope? } | taskMode: { mode } | " +
|
|
522
|
+
"findingSensitivity: { level } | retention: { ttlDays?, retentionDays?, autoAcceptThreshold?, " +
|
|
523
|
+
"minInjectConfidence?, decay? } | workflow: { lowConfidenceThreshold?, riskySections?, taskMode?, " +
|
|
524
|
+
"findingSensitivity? } | index: { includeGlobs?, excludeGlobs?, includeHidden? } | " +
|
|
525
|
+
"topic: { topics, domain? }"),
|
|
526
|
+
project: z.string().optional().describe("Project name. When provided, writes to that project's phren.project.yaml instead of global .config/. " +
|
|
527
|
+
"Required for the 'topic' domain."),
|
|
528
|
+
}),
|
|
529
|
+
}, (params) => handleSetConfig(ctx, params));
|
|
515
530
|
}
|