@curdx/flow 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/CHANGELOG.md +33 -82
  2. package/LICENSE +1 -1
  3. package/README.md +28 -129
  4. package/dist/index.mjs +1165 -0
  5. package/package.json +33 -44
  6. package/.claude-plugin/marketplace.json +0 -48
  7. package/.claude-plugin/plugin.json +0 -52
  8. package/agent-preamble/preamble.md +0 -314
  9. package/agents/flow-adversary.md +0 -203
  10. package/agents/flow-architect.md +0 -198
  11. package/agents/flow-brownfield-analyst.md +0 -143
  12. package/agents/flow-debugger.md +0 -321
  13. package/agents/flow-edge-hunter.md +0 -289
  14. package/agents/flow-executor.md +0 -269
  15. package/agents/flow-orchestrator.md +0 -145
  16. package/agents/flow-planner.md +0 -247
  17. package/agents/flow-product-designer.md +0 -159
  18. package/agents/flow-qa-engineer.md +0 -282
  19. package/agents/flow-researcher.md +0 -166
  20. package/agents/flow-reviewer.md +0 -304
  21. package/agents/flow-security-auditor.md +0 -401
  22. package/agents/flow-triage-analyst.md +0 -272
  23. package/agents/flow-ui-researcher.md +0 -230
  24. package/agents/flow-ux-designer.md +0 -221
  25. package/agents/flow-verifier.md +0 -350
  26. package/bin/curdx-flow +0 -5
  27. package/bin/curdx-flow-state +0 -104
  28. package/bin/curdx-flow.js +0 -54
  29. package/cli/README.md +0 -104
  30. package/cli/doctor-workflow.js +0 -483
  31. package/cli/doctor.js +0 -73
  32. package/cli/help.js +0 -59
  33. package/cli/install-bundled-mcps.js +0 -37
  34. package/cli/install-companions.js +0 -19
  35. package/cli/install-context7-config.js +0 -80
  36. package/cli/install-curdx-plugin.js +0 -96
  37. package/cli/install-language.js +0 -35
  38. package/cli/install-next-steps.js +0 -29
  39. package/cli/install-options.js +0 -9
  40. package/cli/install-paths.js +0 -52
  41. package/cli/install-recommended-plugins.js +0 -104
  42. package/cli/install-required-plugins.js +0 -57
  43. package/cli/install-self-update.js +0 -62
  44. package/cli/install-workflow.js +0 -209
  45. package/cli/install.js +0 -101
  46. package/cli/lib/claude-commands.js +0 -41
  47. package/cli/lib/claude-ops.js +0 -47
  48. package/cli/lib/claude.js +0 -183
  49. package/cli/lib/config.js +0 -24
  50. package/cli/lib/doctor-claude-settings.js +0 -1186
  51. package/cli/lib/doctor-report.js +0 -978
  52. package/cli/lib/doctor-runtime-environment.js +0 -196
  53. package/cli/lib/frontmatter.js +0 -44
  54. package/cli/lib/json-schema.js +0 -57
  55. package/cli/lib/logging.js +0 -25
  56. package/cli/lib/process.js +0 -60
  57. package/cli/lib/prompts.js +0 -135
  58. package/cli/lib/runtime.js +0 -107
  59. package/cli/lib/semver.js +0 -109
  60. package/cli/lib/version.js +0 -12
  61. package/cli/protocols-body.md +0 -22
  62. package/cli/protocols.js +0 -162
  63. package/cli/registry.js +0 -123
  64. package/cli/router.js +0 -49
  65. package/cli/uninstall-actions.js +0 -360
  66. package/cli/uninstall-workflow.js +0 -146
  67. package/cli/uninstall.js +0 -42
  68. package/cli/upgrade-workflow.js +0 -80
  69. package/cli/upgrade.js +0 -91
  70. package/cli/utils.js +0 -40
  71. package/gates/adversarial-review-gate.md +0 -219
  72. package/gates/coverage-audit-gate.md +0 -182
  73. package/gates/devex-gate.md +0 -254
  74. package/gates/edge-case-gate.md +0 -194
  75. package/gates/karpathy-gate.md +0 -130
  76. package/gates/security-gate.md +0 -218
  77. package/gates/tdd-gate.md +0 -182
  78. package/gates/test-quality-gate.md +0 -59
  79. package/gates/verification-gate.md +0 -179
  80. package/hooks/hooks.json +0 -130
  81. package/hooks/scripts/common.sh +0 -237
  82. package/hooks/scripts/config-change-guard.sh +0 -94
  83. package/hooks/scripts/flow-context-watch.sh +0 -94
  84. package/hooks/scripts/inject-karpathy.sh +0 -53
  85. package/hooks/scripts/quick-mode-guard.sh +0 -69
  86. package/hooks/scripts/session-start.sh +0 -94
  87. package/hooks/scripts/session-title.sh +0 -87
  88. package/hooks/scripts/stop-watcher.sh +0 -231
  89. package/hooks/scripts/subagent-artifact-guard.sh +0 -92
  90. package/hooks/scripts/subagent-statusline.sh +0 -111
  91. package/hooks/scripts/task-lifecycle-guard.sh +0 -106
  92. package/hooks/scripts/teammate-idle-guard.sh +0 -83
  93. package/knowledge/artifact-output-discipline.md +0 -24
  94. package/knowledge/artifact-summary-contracts.md +0 -50
  95. package/knowledge/atomic-commits.md +0 -262
  96. package/knowledge/claude-code-runtime-contracts.md +0 -240
  97. package/knowledge/epic-decomposition.md +0 -307
  98. package/knowledge/execution-strategies.md +0 -303
  99. package/knowledge/karpathy-guidelines.md +0 -219
  100. package/knowledge/planning-reviews.md +0 -211
  101. package/knowledge/poc-first-workflow.md +0 -223
  102. package/knowledge/review-feedback-intake.md +0 -57
  103. package/knowledge/spec-driven-development.md +0 -180
  104. package/knowledge/systematic-debugging.md +0 -378
  105. package/knowledge/two-stage-review.md +0 -249
  106. package/knowledge/wave-execution.md +0 -403
  107. package/monitors/monitors.json +0 -8
  108. package/monitors/scripts/flow-state-monitor.sh +0 -102
  109. package/output-styles/curdx-evidence-first.md +0 -34
  110. package/output-styles/curdx-fast-mode.md +0 -42
  111. package/output-styles/curdx-spec-mode.md +0 -46
  112. package/schemas/agent-frontmatter.schema.json +0 -66
  113. package/schemas/config.schema.json +0 -134
  114. package/schemas/gate-frontmatter.schema.json +0 -30
  115. package/schemas/hooks.schema.json +0 -115
  116. package/schemas/output-style-frontmatter.schema.json +0 -22
  117. package/schemas/plugin-manifest.schema.json +0 -436
  118. package/schemas/plugin-settings.schema.json +0 -29
  119. package/schemas/skill-frontmatter.schema.json +0 -177
  120. package/schemas/spec-frontmatter.schema.json +0 -42
  121. package/schemas/spec-state.schema.json +0 -165
  122. package/settings.json +0 -8
  123. package/skills/brownfield-index/SKILL.md +0 -53
  124. package/skills/brownfield-index/references/applicability.md +0 -12
  125. package/skills/brownfield-index/references/handoff.md +0 -8
  126. package/skills/brownfield-index/references/index-contract.md +0 -10
  127. package/skills/browser-qa/SKILL.md +0 -39
  128. package/skills/browser-qa/references/handoff.md +0 -6
  129. package/skills/browser-qa/references/prerequisites.md +0 -10
  130. package/skills/browser-qa/references/qa-contract.md +0 -20
  131. package/skills/cancel/SKILL.md +0 -41
  132. package/skills/cancel/references/destructive-mode.md +0 -17
  133. package/skills/cancel/references/reporting.md +0 -18
  134. package/skills/cancel/references/state-recovery.md +0 -30
  135. package/skills/cancel/references/target-resolution.md +0 -7
  136. package/skills/debug/SKILL.md +0 -45
  137. package/skills/debug/references/context-gathering.md +0 -11
  138. package/skills/debug/references/failure-guard.md +0 -25
  139. package/skills/debug/references/intake.md +0 -12
  140. package/skills/debug/references/phase-workflow.md +0 -34
  141. package/skills/debug/references/reporting.md +0 -20
  142. package/skills/epic/SKILL.md +0 -39
  143. package/skills/epic/references/epic-artifacts.md +0 -20
  144. package/skills/epic/references/epic-intake.md +0 -9
  145. package/skills/epic/references/slice-handoff.md +0 -16
  146. package/skills/fast/SKILL.md +0 -62
  147. package/skills/fast/references/applicability.md +0 -25
  148. package/skills/fast/references/clarification.md +0 -20
  149. package/skills/fast/references/execution-contract.md +0 -56
  150. package/skills/help/SKILL.md +0 -55
  151. package/skills/help/references/dispatch.md +0 -20
  152. package/skills/help/references/overview.md +0 -39
  153. package/skills/help/references/troubleshoot.md +0 -47
  154. package/skills/help/references/workflow.md +0 -37
  155. package/skills/implement/SKILL.md +0 -104
  156. package/skills/implement/references/error-recovery.md +0 -36
  157. package/skills/implement/references/linear-execution.md +0 -43
  158. package/skills/implement/references/native-task-sync.md +0 -107
  159. package/skills/implement/references/preflight.md +0 -43
  160. package/skills/implement/references/progress-contract.md +0 -36
  161. package/skills/implement/references/state-init.md +0 -36
  162. package/skills/implement/references/stop-hook-execution.md +0 -50
  163. package/skills/implement/references/strategy-router.md +0 -38
  164. package/skills/implement/references/subagent-execution.md +0 -57
  165. package/skills/implement/references/wave-execution.md +0 -180
  166. package/skills/init/SKILL.md +0 -49
  167. package/skills/init/references/gitignore-and-health.md +0 -26
  168. package/skills/init/references/next-steps.md +0 -22
  169. package/skills/init/references/preflight.md +0 -15
  170. package/skills/init/references/scaffold-contract.md +0 -27
  171. package/skills/review/SKILL.md +0 -82
  172. package/skills/review/references/optional-passes.md +0 -48
  173. package/skills/review/references/preflight.md +0 -38
  174. package/skills/review/references/report-contract.md +0 -49
  175. package/skills/review/references/reporting.md +0 -20
  176. package/skills/review/references/stage-execution.md +0 -32
  177. package/skills/security-audit/SKILL.md +0 -47
  178. package/skills/security-audit/references/audit-contract.md +0 -21
  179. package/skills/security-audit/references/gate-handoff.md +0 -8
  180. package/skills/security-audit/references/scope-and-depth.md +0 -9
  181. package/skills/spec/SKILL.md +0 -100
  182. package/skills/spec/references/artifact-landing.md +0 -31
  183. package/skills/spec/references/phase-execution.md +0 -50
  184. package/skills/spec/references/planning-review.md +0 -31
  185. package/skills/spec/references/preflight-and-routing.md +0 -46
  186. package/skills/spec/references/reporting.md +0 -21
  187. package/skills/start/SKILL.md +0 -84
  188. package/skills/start/references/branch-routing.md +0 -51
  189. package/skills/start/references/mode-semantics.md +0 -12
  190. package/skills/start/references/preflight.md +0 -13
  191. package/skills/start/references/reporting.md +0 -20
  192. package/skills/start/references/state-seeding.md +0 -44
  193. package/skills/start/references/workflow-handoff.md +0 -26
  194. package/skills/status/SKILL.md +0 -41
  195. package/skills/status/references/gather-contract.md +0 -30
  196. package/skills/status/references/health-rules.md +0 -27
  197. package/skills/status/references/output-contract.md +0 -25
  198. package/skills/status/references/preflight.md +0 -10
  199. package/skills/status/references/recovery-hints.md +0 -18
  200. package/skills/ui-sketch/SKILL.md +0 -39
  201. package/skills/ui-sketch/references/brief-intake.md +0 -10
  202. package/skills/ui-sketch/references/iteration-handoff.md +0 -5
  203. package/skills/ui-sketch/references/variant-contract.md +0 -15
  204. package/skills/verify/SKILL.md +0 -56
  205. package/skills/verify/references/evidence-workflow.md +0 -39
  206. package/skills/verify/references/output-contract.md +0 -23
  207. package/skills/verify/references/preflight.md +0 -11
  208. package/skills/verify/references/report-handoff.md +0 -35
  209. package/skills/verify/references/strict-mode.md +0 -12
  210. package/templates/CONTEXT.md.tmpl +0 -53
  211. package/templates/PROJECT.md.tmpl +0 -59
  212. package/templates/ROADMAP.md.tmpl +0 -50
  213. package/templates/STATE.md.tmpl +0 -49
  214. package/templates/config.json.tmpl +0 -51
  215. package/templates/design.md.tmpl +0 -83
  216. package/templates/progress.md.tmpl +0 -77
  217. package/templates/requirements.md.tmpl +0 -76
  218. package/templates/research.md.tmpl +0 -83
  219. package/templates/tasks.md.tmpl +0 -107
@@ -1,1186 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
-
5
- const PROJECT_ONLY_IGNORED_SETTINGS = [
6
- {
7
- key: "autoMemoryDirectory",
8
- kind: "ignored-project-setting",
9
- message: "autoMemoryDirectory is not accepted in project settings; move it to user or local settings",
10
- },
11
- {
12
- key: "autoMode",
13
- kind: "ignored-project-setting",
14
- message: "autoMode is not read from shared project settings; move it to user or local settings",
15
- },
16
- {
17
- key: "useAutoModeDuringPlan",
18
- kind: "ignored-project-setting",
19
- message: "useAutoModeDuringPlan is not read from shared project settings; move it to user or local settings",
20
- },
21
- {
22
- key: "sshConfigs",
23
- kind: "ignored-project-setting",
24
- message: "sshConfigs is only read from user or managed settings; remove it from shared project settings",
25
- },
26
- {
27
- key: "teammateMode",
28
- kind: "invalid-project-setting",
29
- message: "teammateMode belongs in the global ~/.claude.json config, not project settings.json",
30
- },
31
- ];
32
-
33
- const MANAGED_ONLY_SETTINGS = [
34
- "allowedChannelPlugins",
35
- "allowedMcpServers",
36
- "allowManagedHooksOnly",
37
- "allowManagedMcpServersOnly",
38
- "allowManagedPermissionRulesOnly",
39
- "blockedMarketplaces",
40
- "channelsEnabled",
41
- "deniedMcpServers",
42
- "forceRemoteSettingsRefresh",
43
- "pluginTrustMessage",
44
- "strictKnownMarketplaces",
45
- ];
46
-
47
- const SHARED_SCRIPT_SETTINGS = [
48
- {
49
- key: "apiKeyHelper",
50
- kind: "shared-script-setting",
51
- message: "apiKeyHelper runs a local script from shared project settings",
52
- },
53
- {
54
- key: "awsAuthRefresh",
55
- kind: "shared-script-setting",
56
- message: "awsAuthRefresh runs a local credential-refresh script from shared project settings",
57
- },
58
- {
59
- key: "awsCredentialExport",
60
- kind: "shared-script-setting",
61
- message: "awsCredentialExport runs a local credential-export script from shared project settings",
62
- },
63
- {
64
- key: "otelHeadersHelper",
65
- kind: "shared-script-setting",
66
- message: "otelHeadersHelper runs a local telemetry script from shared project settings",
67
- },
68
- ];
69
-
70
- const CURDX_FLOW_AGENT_TYPES = [
71
- "flow-adversary",
72
- "flow-architect",
73
- "flow-brownfield-analyst",
74
- "flow-debugger",
75
- "flow-edge-hunter",
76
- "flow-executor",
77
- "flow-orchestrator",
78
- "flow-planner",
79
- "flow-product-designer",
80
- "flow-qa-engineer",
81
- "flow-researcher",
82
- "flow-reviewer",
83
- "flow-security-auditor",
84
- "flow-triage-analyst",
85
- "flow-ui-researcher",
86
- "flow-ux-designer",
87
- "flow-verifier",
88
- ];
89
-
90
- const CURDX_FLOW_REQUIRED_MODEL_ALIASES = ["sonnet", "opus"];
91
- const CURDX_FLOW_REQUIRED_TOOLS = [
92
- "Agent",
93
- "AskUserQuestion",
94
- "Bash",
95
- "Monitor",
96
- "Read",
97
- "Write",
98
- "Edit",
99
- "Grep",
100
- "Glob",
101
- ];
102
- const CURDX_FLOW_PLUGIN_ID = "curdx-flow@curdx-flow-marketplace";
103
- const CURDX_FLOW_PLUGIN_OPTIONS = {
104
- autonomous_blocking: {
105
- type: "boolean",
106
- default: true,
107
- },
108
- daily_dependency_check: {
109
- type: "boolean",
110
- default: true,
111
- },
112
- monitor_interval_seconds: {
113
- type: "number",
114
- default: 8,
115
- integer: true,
116
- min: 3,
117
- max: 60,
118
- },
119
- };
120
- const CURDX_FLOW_REQUIRED_PLUGIN_IDS = ["context7-plugin@context7-marketplace"];
121
- const HTTP_HOOK_SETTINGS = ["allowedHttpHookUrls", "httpHookAllowedEnvVars"];
122
- const PERSISTED_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
123
- const ENV_EFFORT_LEVELS = [...PERSISTED_EFFORT_LEVELS, "max", "auto"];
124
- export const CURDX_FLOW_RUNTIME_CONSUMERS = {
125
- autonomous_blocking: {
126
- envVar: "CLAUDE_PLUGIN_OPTION_AUTONOMOUS_BLOCKING",
127
- consumer: "hooks/scripts/stop-watcher.sh",
128
- summary: "controls whether the Stop hook blocks Claude from stopping while execute work remains",
129
- },
130
- daily_dependency_check: {
131
- envVar: "CLAUDE_PLUGIN_OPTION_DAILY_DEPENDENCY_CHECK",
132
- consumer: "hooks/scripts/session-start.sh",
133
- summary: "controls the once-per-day recommended companion plugin reminder",
134
- },
135
- monitor_interval_seconds: {
136
- envVar: "CLAUDE_PLUGIN_OPTION_MONITOR_INTERVAL_SECONDS",
137
- consumer: "monitors/scripts/flow-state-monitor.sh",
138
- summary: "controls the polling interval for the interactive flow-state monitor",
139
- },
140
- };
141
-
142
- function resolveManagedSettingsRoot({ platform = process.platform, env = process.env } = {}) {
143
- if (platform === "darwin") {
144
- return "/Library/Application Support/ClaudeCode";
145
- }
146
-
147
- if (platform === "linux") {
148
- return "/etc/claude-code";
149
- }
150
-
151
- if (platform === "win32") {
152
- return path.join(env.ProgramFiles || "C:\\Program Files", "ClaudeCode");
153
- }
154
-
155
- return null;
156
- }
157
-
158
- function envFlagEnabled(value) {
159
- if (value === true || value === 1) return true;
160
- if (typeof value !== "string") return false;
161
- return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
162
- }
163
-
164
- function normalizedEnvValue(value) {
165
- if (typeof value !== "string") return null;
166
- const normalized = value.trim();
167
- return normalized.length > 0 ? normalized : null;
168
- }
169
-
170
- function parsePermissionRule(rule) {
171
- if (typeof rule !== "string") return null;
172
- const match = rule.match(/^([A-Za-z][A-Za-z0-9]*)(?:\((.*)\))?$/);
173
- if (!match) return null;
174
- return {
175
- tool: match[1],
176
- matcher: match[2] ?? null,
177
- };
178
- }
179
-
180
- function isBroadToolDeny(rule) {
181
- const parsed = parsePermissionRule(rule);
182
- if (!parsed || !CURDX_FLOW_REQUIRED_TOOLS.includes(parsed.tool)) return false;
183
- return parsed.matcher === null || parsed.matcher === "*" || parsed.matcher === "**";
184
- }
185
-
186
- function isCurdxFlowAgentDeny(rule) {
187
- const parsed = parsePermissionRule(rule);
188
- if (!parsed || parsed.tool !== "Agent") return false;
189
- if (parsed.matcher === null || parsed.matcher === "*" || parsed.matcher === "**") return true;
190
- return CURDX_FLOW_AGENT_TYPES.some(
191
- (agentType) => parsed.matcher === agentType || parsed.matcher === `curdx-flow:${agentType}`
192
- );
193
- }
194
-
195
- function disabledPluginIdsFromSettings(enabledPlugins) {
196
- if (!enabledPlugins || typeof enabledPlugins !== "object" || Array.isArray(enabledPlugins)) {
197
- return [];
198
- }
199
-
200
- return Object.entries(enabledPlugins)
201
- .filter(([, enabled]) => enabled === false)
202
- .map(([pluginId]) => pluginId);
203
- }
204
-
205
- function isNonArrayObject(value) {
206
- return value && typeof value === "object" && !Array.isArray(value);
207
- }
208
-
209
- function hasProjectBlockingSandboxPath(values) {
210
- if (!Array.isArray(values)) return false;
211
-
212
- return values.some((value) => {
213
- if (typeof value !== "string") return false;
214
- const token = value.trim().replace(/\/\*{1,2}$/, "").replace(/\/$/, "");
215
- return [".", ".flow", "./.flow", ".git", "./.git"].includes(token);
216
- });
217
- }
218
-
219
- function pushScopedWarning(target, kind, message, scope = "project") {
220
- const warning = { kind, message };
221
- if (scope !== "project") warning.scope = scope;
222
- target.push(warning);
223
- }
224
-
225
- function createCurdxFlowPluginOptionsState() {
226
- const definitions = Object.entries(CURDX_FLOW_PLUGIN_OPTIONS).map(([key, value]) => ({
227
- key,
228
- type: value.type,
229
- default: value.default,
230
- min: value.min,
231
- max: value.max,
232
- integer: value.integer === true,
233
- }));
234
-
235
- const repoEffective = Object.fromEntries(
236
- definitions.map((definition) => [
237
- definition.key,
238
- { value: definition.default, source: "default" },
239
- ])
240
- );
241
-
242
- return {
243
- pluginId: CURDX_FLOW_PLUGIN_ID,
244
- definitions,
245
- managed: {
246
- overrides: {},
247
- files: [],
248
- },
249
- user: {
250
- pluginConfigsPresent: false,
251
- pluginConfigPresent: false,
252
- optionsPresent: false,
253
- overrides: {},
254
- },
255
- project: {
256
- pluginConfigsPresent: false,
257
- pluginConfigPresent: false,
258
- optionsPresent: false,
259
- overrides: {},
260
- },
261
- local: {
262
- pluginConfigsPresent: false,
263
- pluginConfigPresent: false,
264
- optionsPresent: false,
265
- overrides: {},
266
- },
267
- repoEffective,
268
- machineEffective: structuredClone(repoEffective),
269
- };
270
- }
271
-
272
- function pluginScopeLabel(scope) {
273
- if (scope === "managed") return "managed settings";
274
- if (scope === "local") return "settings.local.json";
275
- if (scope === "user") return "~/.claude/settings.json";
276
- return "settings.json";
277
- }
278
-
279
- function invalidSettingKindForScope(scope) {
280
- if (scope === "managed") return "invalid-managed-setting";
281
- if (scope === "local") return "invalid-local-setting";
282
- if (scope === "user") return "invalid-user-setting";
283
- return "invalid-project-setting";
284
- }
285
-
286
- function applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scope) {
287
- pluginOptionsState.machineEffective[key] = {
288
- value,
289
- source: scope,
290
- };
291
-
292
- if (scope === "project" || scope === "local") {
293
- pluginOptionsState.repoEffective[key] = {
294
- value,
295
- source: scope,
296
- };
297
- }
298
- }
299
-
300
- function buildCurdxFlowRuntimeProjection(pluginOptionsState) {
301
- return pluginOptionsState.definitions.map((definition) => {
302
- const effective = pluginOptionsState.machineEffective[definition.key] || {
303
- value: definition.default,
304
- source: "default",
305
- };
306
- const runtime = CURDX_FLOW_RUNTIME_CONSUMERS[definition.key] || {};
307
- const details = [];
308
-
309
- if (definition.key === "autonomous_blocking") {
310
- details.push(
311
- effective.value === false
312
- ? "stop-hook continuation is disabled; Claude may stop at turn end even when execute tasks remain"
313
- : "stop-hook continuation is enabled; CurDX-Flow can block turn end while execute tasks remain"
314
- );
315
- } else if (definition.key === "daily_dependency_check") {
316
- details.push(
317
- effective.value === false
318
- ? "SessionStart plugin reminder is disabled on this machine"
319
- : "SessionStart plugin reminder runs at most once per day on this machine"
320
- );
321
- } else if (definition.key === "monitor_interval_seconds") {
322
- details.push(`flow-state monitor polls every ${effective.value} second(s) when Monitor is available`);
323
- }
324
-
325
- return {
326
- key: definition.key,
327
- envVar: runtime.envVar || `CLAUDE_PLUGIN_OPTION_${definition.key.toUpperCase()}`,
328
- consumer: runtime.consumer || "plugin subprocess",
329
- summary: runtime.summary || "runtime consumer",
330
- value: effective.value,
331
- source: effective.source,
332
- details,
333
- };
334
- });
335
- }
336
-
337
- function auditCurdxFlowPluginOptions(
338
- parsed,
339
- warnings,
340
- pluginOptionsState,
341
- { scope = "project", settingsLabel = pluginScopeLabel(scope), targetOverrides = null } = {}
342
- ) {
343
- if (!isNonArrayObject(parsed) || !("pluginConfigs" in parsed)) {
344
- return;
345
- }
346
-
347
- const target = scope === "managed"
348
- ? pluginOptionsState.managed
349
- : scope === "local"
350
- ? pluginOptionsState.local
351
- : scope === "user"
352
- ? pluginOptionsState.user
353
- : pluginOptionsState.project;
354
- target.pluginConfigsPresent = true;
355
-
356
- if (!isNonArrayObject(parsed.pluginConfigs)) {
357
- pushScopedWarning(
358
- warnings,
359
- invalidSettingKindForScope(scope),
360
- `pluginConfigs in ${settingsLabel} must be an object keyed by plugin@marketplace id`,
361
- scope
362
- );
363
- return;
364
- }
365
-
366
- const pluginConfig = parsed.pluginConfigs[CURDX_FLOW_PLUGIN_ID];
367
- if (pluginConfig === undefined) {
368
- return;
369
- }
370
-
371
- target.pluginConfigPresent = true;
372
-
373
- if (!isNonArrayObject(pluginConfig)) {
374
- pushScopedWarning(
375
- warnings,
376
- invalidSettingKindForScope(scope),
377
- `pluginConfigs[${CURDX_FLOW_PLUGIN_ID}] in ${settingsLabel} must be an object when set`,
378
- scope
379
- );
380
- return;
381
- }
382
-
383
- if (!("options" in pluginConfig)) {
384
- return;
385
- }
386
-
387
- target.optionsPresent = true;
388
-
389
- if (!isNonArrayObject(pluginConfig.options)) {
390
- pushScopedWarning(
391
- warnings,
392
- invalidSettingKindForScope(scope),
393
- `pluginConfigs[${CURDX_FLOW_PLUGIN_ID}].options in ${settingsLabel} must be an object when set`,
394
- scope
395
- );
396
- return;
397
- }
398
-
399
- for (const [key, value] of Object.entries(pluginConfig.options)) {
400
- const definition = CURDX_FLOW_PLUGIN_OPTIONS[key];
401
-
402
- if (!definition) {
403
- pushScopedWarning(
404
- warnings,
405
- "unknown-plugin-option",
406
- `unknown CurDX-Flow plugin option in ${settingsLabel}: ${key}`,
407
- scope
408
- );
409
- continue;
410
- }
411
-
412
- if (definition.type === "boolean" && typeof value !== "boolean") {
413
- pushScopedWarning(
414
- warnings,
415
- invalidSettingKindForScope(scope),
416
- `CurDX-Flow plugin option ${key} in ${settingsLabel} must be boolean`,
417
- scope
418
- );
419
- continue;
420
- }
421
-
422
- if (definition.type === "number") {
423
- if (typeof value !== "number" || Number.isNaN(value)) {
424
- pushScopedWarning(
425
- warnings,
426
- invalidSettingKindForScope(scope),
427
- `CurDX-Flow plugin option ${key} in ${settingsLabel} must be a number`,
428
- scope
429
- );
430
- continue;
431
- }
432
-
433
- if (definition.integer && !Number.isInteger(value)) {
434
- pushScopedWarning(
435
- warnings,
436
- invalidSettingKindForScope(scope),
437
- `CurDX-Flow plugin option ${key} in ${settingsLabel} must be an integer`,
438
- scope
439
- );
440
- continue;
441
- }
442
-
443
- if (typeof definition.min === "number" && value < definition.min) {
444
- pushScopedWarning(
445
- warnings,
446
- invalidSettingKindForScope(scope),
447
- `CurDX-Flow plugin option ${key} in ${settingsLabel} must be between ${definition.min} and ${definition.max}`,
448
- scope
449
- );
450
- continue;
451
- }
452
-
453
- if (typeof definition.max === "number" && value > definition.max) {
454
- pushScopedWarning(
455
- warnings,
456
- invalidSettingKindForScope(scope),
457
- `CurDX-Flow plugin option ${key} in ${settingsLabel} must be between ${definition.min} and ${definition.max}`,
458
- scope
459
- );
460
- continue;
461
- }
462
- }
463
-
464
- const overrideTarget = targetOverrides || target.overrides;
465
- overrideTarget[key] = value;
466
- if (scope === "managed") {
467
- target.overrides[key] = value;
468
- }
469
- applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scope);
470
- }
471
- }
472
-
473
- async function readManagedClaudeSettings(
474
- pluginOptionsState,
475
- warnings,
476
- {
477
- platform = process.platform,
478
- env = process.env,
479
- managedSettingsDir = resolveManagedSettingsRoot({ platform, env }),
480
- } = {}
481
- ) {
482
- const state = {
483
- supported: Boolean(managedSettingsDir),
484
- rootPath: managedSettingsDir,
485
- basePath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.json") : null,
486
- dropInPath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.d") : null,
487
- exists: false,
488
- baseExists: false,
489
- dropInDirExists: false,
490
- files: [],
491
- };
492
-
493
- if (!managedSettingsDir) {
494
- return state;
495
- }
496
-
497
- const fileQueue = [];
498
-
499
- try {
500
- const stat = await fs.stat(state.basePath);
501
- if (stat.isFile()) {
502
- state.baseExists = true;
503
- state.exists = true;
504
- fileQueue.push({
505
- label: "managed-settings.json",
506
- path: state.basePath,
507
- });
508
- }
509
- } catch {}
510
-
511
- try {
512
- const stat = await fs.stat(state.dropInPath);
513
- if (stat.isDirectory()) {
514
- state.dropInDirExists = true;
515
- const entries = (await fs.readdir(state.dropInPath))
516
- .filter((name) => !name.startsWith(".") && name.endsWith(".json"))
517
- .sort();
518
- for (const name of entries) {
519
- const absPath = path.join(state.dropInPath, name);
520
- const entryStat = await fs.stat(absPath);
521
- if (!entryStat.isFile()) continue;
522
- state.exists = true;
523
- fileQueue.push({
524
- label: `managed-settings.d/${name}`,
525
- path: absPath,
526
- });
527
- }
528
- }
529
- } catch {}
530
-
531
- for (const file of fileQueue) {
532
- const entry = {
533
- label: file.label,
534
- path: file.path,
535
- invalid: false,
536
- parseError: null,
537
- overrides: {},
538
- };
539
-
540
- try {
541
- const parsed = JSON.parse(await fs.readFile(file.path, "utf-8"));
542
- auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, {
543
- scope: "managed",
544
- settingsLabel: file.label,
545
- targetOverrides: entry.overrides,
546
- });
547
- } catch (error) {
548
- entry.invalid = true;
549
- entry.parseError = error?.message || String(error);
550
- }
551
-
552
- state.files.push(entry);
553
- pluginOptionsState.managed.files.push({
554
- label: entry.label,
555
- path: entry.path,
556
- overrides: { ...entry.overrides },
557
- invalid: entry.invalid,
558
- parseError: entry.parseError,
559
- });
560
- }
561
-
562
- return state;
563
- }
564
-
565
- function auditLocalClaudeSettings(parsed, warnings) {
566
- const scope = "local";
567
- const permissions = parsed?.permissions && typeof parsed.permissions === "object"
568
- ? parsed.permissions
569
- : {};
570
- const deny = Array.isArray(permissions.deny) ? permissions.deny : [];
571
-
572
- if ("enabledPlugins" in parsed && (
573
- !parsed.enabledPlugins ||
574
- typeof parsed.enabledPlugins !== "object" ||
575
- Array.isArray(parsed.enabledPlugins)
576
- )) {
577
- pushScopedWarning(
578
- warnings,
579
- "invalid-local-setting",
580
- "enabledPlugins in settings.local.json must be an object keyed by plugin@marketplace id",
581
- scope
582
- );
583
- }
584
-
585
- const disabledPluginIds = disabledPluginIdsFromSettings(parsed.enabledPlugins);
586
- if (disabledPluginIds.includes(CURDX_FLOW_PLUGIN_ID)) {
587
- pushScopedWarning(
588
- warnings,
589
- "flow-runtime-blocker",
590
- `settings.local.json disables CurDX-Flow for this machine: ${CURDX_FLOW_PLUGIN_ID}`,
591
- scope
592
- );
593
- }
594
-
595
- for (const pluginId of CURDX_FLOW_REQUIRED_PLUGIN_IDS) {
596
- if (disabledPluginIds.includes(pluginId)) {
597
- pushScopedWarning(
598
- warnings,
599
- "required-plugin-disabled",
600
- `settings.local.json disables required CurDX-Flow companion plugin: ${pluginId}`,
601
- scope
602
- );
603
- }
604
- }
605
-
606
- if (permissions.defaultMode === "bypassPermissions") {
607
- pushScopedWarning(
608
- warnings,
609
- "bypass-default-mode",
610
- 'permissions.defaultMode in settings.local.json is "bypassPermissions"',
611
- scope
612
- );
613
- }
614
-
615
- if (permissions.defaultMode === "dontAsk") {
616
- pushScopedWarning(
617
- warnings,
618
- "flow-runtime-blocker",
619
- 'permissions.defaultMode in settings.local.json is "dontAsk"; CurDX-Flow clarification and Agent dispatch prompts may be auto-denied',
620
- scope
621
- );
622
- }
623
-
624
- for (const rule of deny) {
625
- if (isCurdxFlowAgentDeny(rule)) {
626
- pushScopedWarning(
627
- warnings,
628
- "flow-runtime-blocker",
629
- `settings.local.json deny rule blocks CurDX-Flow subagent dispatch: ${rule}`,
630
- scope
631
- );
632
- continue;
633
- }
634
-
635
- if (isBroadToolDeny(rule)) {
636
- pushScopedWarning(
637
- warnings,
638
- "flow-runtime-blocker",
639
- `settings.local.json deny rule blocks a tool CurDX-Flow workflows require: ${rule}`,
640
- scope
641
- );
642
- }
643
- }
644
-
645
- if (parsed.env && typeof parsed.env === "object" && !Array.isArray(parsed.env)) {
646
- if (envFlagEnabled(parsed.env.CLAUDE_CODE_SIMPLE)) {
647
- pushScopedWarning(
648
- warnings,
649
- "flow-runtime-blocker",
650
- "settings.local.json env.CLAUDE_CODE_SIMPLE=1 enables bare/simple mode, disabling CurDX-Flow plugin, hook, skill, MCP, and CLAUDE.md discovery",
651
- scope
652
- );
653
- }
654
-
655
- if (envFlagEnabled(parsed.env.CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT)) {
656
- pushScopedWarning(
657
- warnings,
658
- "local-runtime-setting",
659
- "settings.local.json env.CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=1 forces the minimal Claude system prompt for local sessions",
660
- scope
661
- );
662
- }
663
- }
664
-
665
- if (parsed.disableAllHooks === true) {
666
- pushScopedWarning(
667
- warnings,
668
- "flow-runtime-blocker",
669
- "settings.local.json disableAllHooks disables CurDX-Flow stop/recovery hooks and plugin subagent status lines",
670
- scope
671
- );
672
- }
673
-
674
- if (parsed.disableSkillShellExecution === true) {
675
- pushScopedWarning(
676
- warnings,
677
- "skill-shell-disabled",
678
- "settings.local.json disableSkillShellExecution disables inline shell expansion in project/plugin skills and commands",
679
- scope
680
- );
681
- }
682
-
683
- if (Array.isArray(parsed.availableModels)) {
684
- const missing = CURDX_FLOW_REQUIRED_MODEL_ALIASES.filter(
685
- (modelAlias) => !parsed.availableModels.includes(modelAlias)
686
- );
687
- if (missing.length > 0) {
688
- pushScopedWarning(
689
- warnings,
690
- "flow-runtime-blocker",
691
- `settings.local.json availableModels excludes CurDX-Flow agent model aliases: ${missing.join(", ")}`,
692
- scope
693
- );
694
- }
695
- }
696
-
697
- if ("agent" in parsed) {
698
- if (typeof parsed.agent !== "string" || parsed.agent.trim().length === 0) {
699
- pushScopedWarning(
700
- warnings,
701
- "invalid-local-setting",
702
- "agent in settings.local.json must be a non-empty subagent name when set",
703
- scope
704
- );
705
- } else {
706
- pushScopedWarning(
707
- warnings,
708
- "flow-runtime-blocker",
709
- `settings.local.json routes the main thread through subagent "${parsed.agent}", overriding CurDX-Flow prompt, tools, and model`,
710
- scope
711
- );
712
- }
713
- }
714
-
715
- if ("sandbox" in parsed) {
716
- if (!isNonArrayObject(parsed.sandbox)) {
717
- pushScopedWarning(
718
- warnings,
719
- "invalid-local-setting",
720
- "sandbox in settings.local.json must be an object when set",
721
- scope
722
- );
723
- } else {
724
- if (parsed.sandbox.failIfUnavailable === true) {
725
- pushScopedWarning(
726
- warnings,
727
- "flow-runtime-blocker",
728
- "settings.local.json sandbox.failIfUnavailable can prevent Claude Code startup on hosts where sandboxing is unavailable",
729
- scope
730
- );
731
- }
732
-
733
- const filesystem = isNonArrayObject(parsed.sandbox.filesystem)
734
- ? parsed.sandbox.filesystem
735
- : {};
736
- if (hasProjectBlockingSandboxPath(filesystem.denyRead)) {
737
- pushScopedWarning(
738
- warnings,
739
- "flow-runtime-blocker",
740
- "settings.local.json sandbox.filesystem.denyRead blocks .flow/.git or the project root, which CurDX-Flow must inspect",
741
- scope
742
- );
743
- }
744
- if (hasProjectBlockingSandboxPath(filesystem.denyWrite)) {
745
- pushScopedWarning(
746
- warnings,
747
- "flow-runtime-blocker",
748
- "settings.local.json sandbox.filesystem.denyWrite blocks .flow/.git or the project root, which CurDX-Flow must update",
749
- scope
750
- );
751
- }
752
- }
753
- }
754
- }
755
-
756
- export async function readProjectClaudeSettings(
757
- cwd = process.cwd(),
758
- {
759
- homeDir = os.homedir(),
760
- platform = process.platform,
761
- env = process.env,
762
- managedSettingsDir = resolveManagedSettingsRoot({ platform, env }),
763
- } = {}
764
- ) {
765
- const settingsPath = path.join(cwd, ".claude", "settings.json");
766
- const localSettingsPath = path.join(cwd, ".claude", "settings.local.json");
767
- const userSettingsPath = path.join(homeDir, ".claude", "settings.json");
768
- const state = {
769
- managedSettings: {
770
- supported: Boolean(managedSettingsDir),
771
- rootPath: managedSettingsDir,
772
- basePath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.json") : null,
773
- dropInPath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.d") : null,
774
- exists: false,
775
- baseExists: false,
776
- dropInDirExists: false,
777
- files: [],
778
- },
779
- managedWarnings: [],
780
- userExists: false,
781
- userInvalid: false,
782
- userParseError: null,
783
- userWarnings: [],
784
- exists: false,
785
- localExists: false,
786
- invalid: false,
787
- parseError: null,
788
- warnings: [],
789
- localInvalid: false,
790
- localParseError: null,
791
- localWarnings: [],
792
- pluginOptions: createCurdxFlowPluginOptionsState(),
793
- pluginRuntimeProjection: [],
794
- };
795
-
796
- try {
797
- const userStat = await fs.stat(userSettingsPath);
798
- state.userExists = userStat.isFile();
799
- } catch {
800
- state.userExists = false;
801
- }
802
-
803
- try {
804
- const localStat = await fs.stat(localSettingsPath);
805
- state.localExists = localStat.isFile();
806
- } catch {
807
- state.localExists = false;
808
- }
809
-
810
- if (state.userExists) {
811
- try {
812
- const userParsed = JSON.parse(await fs.readFile(userSettingsPath, "utf-8"));
813
- auditCurdxFlowPluginOptions(userParsed, state.userWarnings, state.pluginOptions, { scope: "user" });
814
- } catch (error) {
815
- state.userInvalid = true;
816
- state.userParseError = error?.message || String(error);
817
- }
818
- }
819
-
820
- let parsed;
821
- try {
822
- const stat = await fs.stat(settingsPath);
823
- if (!stat.isFile()) {
824
- parsed = null;
825
- } else {
826
- state.exists = true;
827
- parsed = JSON.parse(await fs.readFile(settingsPath, "utf-8"));
828
- }
829
- } catch (error) {
830
- if (error?.code !== "ENOENT") {
831
- state.invalid = true;
832
- state.parseError = error.message;
833
- return state;
834
- }
835
- }
836
-
837
- if (state.exists) {
838
- const permissions = parsed?.permissions && typeof parsed.permissions === "object"
839
- ? parsed.permissions
840
- : {};
841
- const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
842
- const deny = Array.isArray(permissions.deny) ? permissions.deny : [];
843
-
844
- auditCurdxFlowPluginOptions(parsed, state.warnings, state.pluginOptions, { scope: "project" });
845
-
846
- if ("enabledPlugins" in parsed && (
847
- !parsed.enabledPlugins ||
848
- typeof parsed.enabledPlugins !== "object" ||
849
- Array.isArray(parsed.enabledPlugins)
850
- )) {
851
- pushScopedWarning(
852
- state.warnings,
853
- "invalid-project-setting",
854
- "enabledPlugins must be an object keyed by plugin@marketplace id"
855
- );
856
- }
857
-
858
- const disabledPluginIds = disabledPluginIdsFromSettings(parsed.enabledPlugins);
859
- if (disabledPluginIds.includes(CURDX_FLOW_PLUGIN_ID)) {
860
- state.warnings.push({
861
- kind: "flow-runtime-blocker",
862
- message: `enabledPlugins disables CurDX-Flow in this project: ${CURDX_FLOW_PLUGIN_ID}`,
863
- });
864
- }
865
-
866
- for (const pluginId of CURDX_FLOW_REQUIRED_PLUGIN_IDS) {
867
- if (disabledPluginIds.includes(pluginId)) {
868
- state.warnings.push({
869
- kind: "required-plugin-disabled",
870
- message: `enabledPlugins disables required CurDX-Flow companion plugin: ${pluginId}`,
871
- });
872
- }
873
- }
874
-
875
- if (permissions.defaultMode === "bypassPermissions") {
876
- state.warnings.push({
877
- kind: "bypass-default-mode",
878
- message: 'permissions.defaultMode is "bypassPermissions"',
879
- });
880
- }
881
-
882
- if (permissions.defaultMode === "dontAsk") {
883
- state.warnings.push({
884
- kind: "flow-runtime-blocker",
885
- message: 'permissions.defaultMode is "dontAsk"; CurDX-Flow clarification and Agent dispatch prompts may be auto-denied',
886
- });
887
- }
888
-
889
- if (permissions.skipDangerousModePermissionPrompt === true) {
890
- state.warnings.push({
891
- kind: "ignored-project-setting",
892
- message: "permissions.skipDangerousModePermissionPrompt is ignored in project settings; move it to user or local settings",
893
- });
894
- }
895
-
896
- for (const rule of allow) {
897
- if (rule === "Bash" || rule === "Bash(*)" || rule === "*" || rule === "Read(**)") {
898
- state.warnings.push({
899
- kind: "broad-allow-rule",
900
- message: `overbroad allow rule: ${rule}`,
901
- });
902
- }
903
- }
904
-
905
- for (const rule of deny) {
906
- if (isCurdxFlowAgentDeny(rule)) {
907
- state.warnings.push({
908
- kind: "flow-runtime-blocker",
909
- message: `deny rule blocks CurDX-Flow subagent dispatch: ${rule}`,
910
- });
911
- continue;
912
- }
913
-
914
- if (isBroadToolDeny(rule)) {
915
- state.warnings.push({
916
- kind: "flow-runtime-blocker",
917
- message: `deny rule blocks a tool CurDX-Flow workflows require: ${rule}`,
918
- });
919
- }
920
- }
921
-
922
- const sensitiveDenyPatterns = ["Read(./.env)", "Read(./.env.*)"];
923
- for (const pattern of sensitiveDenyPatterns) {
924
- if (!deny.includes(pattern)) {
925
- state.warnings.push({
926
- kind: "missing-sensitive-deny",
927
- message: `missing recommended deny rule: ${pattern}`,
928
- });
929
- }
930
- }
931
-
932
- for (const setting of PROJECT_ONLY_IGNORED_SETTINGS) {
933
- if (setting.key in parsed) {
934
- state.warnings.push({
935
- kind: setting.kind,
936
- message: setting.message,
937
- });
938
- }
939
- }
940
-
941
- for (const key of MANAGED_ONLY_SETTINGS) {
942
- if (key in parsed) {
943
- state.warnings.push({
944
- kind: "managed-only-setting",
945
- message: `${key} only applies from managed settings; remove it from shared project settings`,
946
- });
947
- }
948
- }
949
-
950
- for (const setting of SHARED_SCRIPT_SETTINGS) {
951
- if (setting.key in parsed) {
952
- state.warnings.push({
953
- kind: setting.kind,
954
- message: setting.message,
955
- });
956
- }
957
- }
958
-
959
- if (parsed.fileSuggestion?.type === "command" && parsed.fileSuggestion.command) {
960
- state.warnings.push({
961
- kind: "shared-script-setting",
962
- message: "fileSuggestion.command runs a local script from shared project settings",
963
- });
964
- }
965
-
966
- if (parsed.statusLine?.type === "command" && parsed.statusLine.command) {
967
- state.warnings.push({
968
- kind: "shared-script-setting",
969
- message: "statusLine.command runs a local script from shared project settings",
970
- });
971
- }
972
-
973
- if (parsed.env && typeof parsed.env === "object" && !Array.isArray(parsed.env)) {
974
- state.warnings.push({
975
- kind: "shared-env-setting",
976
- message: "env injects shared environment variables into every session; avoid machine-specific values or secrets",
977
- });
978
-
979
- if (envFlagEnabled(parsed.env.CLAUDE_CODE_SIMPLE)) {
980
- state.warnings.push({
981
- kind: "flow-runtime-blocker",
982
- message: "env.CLAUDE_CODE_SIMPLE=1 enables bare/simple mode, disabling CurDX-Flow plugin, hook, skill, MCP, and CLAUDE.md discovery",
983
- });
984
- }
985
-
986
- if (envFlagEnabled(parsed.env.CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT)) {
987
- state.warnings.push({
988
- kind: "shared-env-setting",
989
- message: "env.CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=1 forces the minimal Claude system prompt for every collaborator session",
990
- });
991
- }
992
-
993
- const effortEnv = normalizedEnvValue(parsed.env.CLAUDE_CODE_EFFORT_LEVEL);
994
- if (effortEnv && !ENV_EFFORT_LEVELS.includes(effortEnv)) {
995
- state.warnings.push({
996
- kind: "invalid-project-setting",
997
- message: `env.CLAUDE_CODE_EFFORT_LEVEL must be one of ${ENV_EFFORT_LEVELS.join(", ")}`,
998
- });
999
- } else if (effortEnv === "low" || effortEnv === "medium") {
1000
- state.warnings.push({
1001
- kind: "low-effort-project-setting",
1002
- message: `env.CLAUDE_CODE_EFFORT_LEVEL=${effortEnv} lowers reasoning for every collaborator session`,
1003
- });
1004
- }
1005
-
1006
- if ("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in parsed.env) {
1007
- state.warnings.push({
1008
- kind: "shared-env-setting",
1009
- message: "env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS shares an experimental Claude runtime flag across this project",
1010
- });
1011
- }
1012
- }
1013
-
1014
- if (parsed.disableAllHooks === true) {
1015
- state.warnings.push({
1016
- kind: "flow-runtime-blocker",
1017
- message: "disableAllHooks disables CurDX-Flow stop/recovery hooks and plugin subagent status lines",
1018
- });
1019
- }
1020
-
1021
- if (parsed.disableSkillShellExecution === true) {
1022
- state.warnings.push({
1023
- kind: "skill-shell-disabled",
1024
- message: "disableSkillShellExecution disables inline shell expansion in project/plugin skills and commands",
1025
- });
1026
- }
1027
-
1028
- for (const key of HTTP_HOOK_SETTINGS) {
1029
- if (!(key in parsed)) continue;
1030
- if (!Array.isArray(parsed[key])) {
1031
- state.warnings.push({
1032
- kind: "invalid-project-setting",
1033
- message: `${key} must be an array when set in settings.json`,
1034
- });
1035
- continue;
1036
- }
1037
- if (parsed[key].length === 0) {
1038
- state.warnings.push({
1039
- kind: "shared-hook-policy",
1040
- message: `${key} is an empty allowlist; matching HTTP hook behavior is blocked`,
1041
- });
1042
- }
1043
- }
1044
-
1045
- if (Array.isArray(parsed.availableModels)) {
1046
- const missing = CURDX_FLOW_REQUIRED_MODEL_ALIASES.filter(
1047
- (modelAlias) => !parsed.availableModels.includes(modelAlias)
1048
- );
1049
- if (missing.length > 0) {
1050
- state.warnings.push({
1051
- kind: "flow-runtime-blocker",
1052
- message: `availableModels excludes CurDX-Flow agent model aliases: ${missing.join(", ")}`,
1053
- });
1054
- }
1055
- }
1056
-
1057
- if ("agent" in parsed) {
1058
- if (typeof parsed.agent !== "string" || parsed.agent.trim().length === 0) {
1059
- state.warnings.push({
1060
- kind: "invalid-project-setting",
1061
- message: "agent must be a non-empty subagent name when set in settings.json",
1062
- });
1063
- } else {
1064
- state.warnings.push({
1065
- kind: "flow-runtime-blocker",
1066
- message: `agent routes the main thread through subagent "${parsed.agent}", overriding CurDX-Flow prompt, tools, and model`,
1067
- });
1068
- }
1069
- }
1070
-
1071
- if ("effortLevel" in parsed) {
1072
- if (parsed.effortLevel === "max") {
1073
- state.warnings.push({
1074
- kind: "invalid-project-setting",
1075
- message:
1076
- "effortLevel in settings.json only supports low, medium, high, or xhigh; use /effort max or CLAUDE_CODE_EFFORT_LEVEL for session-only max",
1077
- });
1078
- } else if (!PERSISTED_EFFORT_LEVELS.includes(parsed.effortLevel)) {
1079
- state.warnings.push({
1080
- kind: "invalid-project-setting",
1081
- message:
1082
- "effortLevel in settings.json must be one of low, medium, high, or xhigh",
1083
- });
1084
- } else if (parsed.effortLevel === "low" || parsed.effortLevel === "medium") {
1085
- state.warnings.push({
1086
- kind: "low-effort-project-setting",
1087
- message: `effortLevel ${parsed.effortLevel} is shared at project scope; CurDX-Flow planning/review turns may need higher reasoning`,
1088
- });
1089
- }
1090
- }
1091
-
1092
- if ("sandbox" in parsed) {
1093
- if (!isNonArrayObject(parsed.sandbox)) {
1094
- state.warnings.push({
1095
- kind: "invalid-project-setting",
1096
- message: "sandbox must be an object when set in settings.json",
1097
- });
1098
- } else {
1099
- if (parsed.sandbox.failIfUnavailable === true) {
1100
- state.warnings.push({
1101
- kind: "flow-runtime-blocker",
1102
- message: "sandbox.failIfUnavailable can prevent Claude Code startup on hosts where sandboxing is unavailable",
1103
- });
1104
- }
1105
- if (parsed.sandbox.allowUnsandboxedCommands === false) {
1106
- state.warnings.push({
1107
- kind: "shared-sandbox-policy",
1108
- message: "sandbox.allowUnsandboxedCommands=false disables the unsandboxed Bash escape hatch for this project",
1109
- });
1110
- }
1111
-
1112
- const filesystem = isNonArrayObject(parsed.sandbox.filesystem)
1113
- ? parsed.sandbox.filesystem
1114
- : {};
1115
- if (hasProjectBlockingSandboxPath(filesystem.denyRead)) {
1116
- state.warnings.push({
1117
- kind: "flow-runtime-blocker",
1118
- message: "sandbox.filesystem.denyRead blocks .flow/.git or the project root, which CurDX-Flow must inspect",
1119
- });
1120
- }
1121
- if (hasProjectBlockingSandboxPath(filesystem.denyWrite)) {
1122
- state.warnings.push({
1123
- kind: "flow-runtime-blocker",
1124
- message: "sandbox.filesystem.denyWrite blocks .flow/.git or the project root, which CurDX-Flow must update",
1125
- });
1126
- }
1127
-
1128
- const network = isNonArrayObject(parsed.sandbox.network)
1129
- ? parsed.sandbox.network
1130
- : {};
1131
- if (Array.isArray(network.allowedDomains) && network.allowedDomains.length === 0) {
1132
- state.warnings.push({
1133
- kind: "shared-sandbox-policy",
1134
- message: "sandbox.network.allowedDomains is empty; sandboxed commands have no outbound network access",
1135
- });
1136
- }
1137
- }
1138
- }
1139
-
1140
- if (parsed.enableAllProjectMcpServers === true) {
1141
- state.warnings.push({
1142
- kind: "shared-mcp-auto-approve",
1143
- message: "enableAllProjectMcpServers auto-approves every project MCP server",
1144
- });
1145
- }
1146
-
1147
- if (Array.isArray(parsed.enabledMcpjsonServers) && parsed.enabledMcpjsonServers.length > 0) {
1148
- state.warnings.push({
1149
- kind: "shared-mcp-auto-approve",
1150
- message: `enabledMcpjsonServers auto-approves project MCP servers: ${parsed.enabledMcpjsonServers.join(", ")}`,
1151
- });
1152
- }
1153
-
1154
- if ("includeCoAuthoredBy" in parsed) {
1155
- state.warnings.push({
1156
- kind: "deprecated-setting",
1157
- message: "includeCoAuthoredBy is deprecated; migrate to attribution",
1158
- });
1159
- }
1160
- }
1161
-
1162
- if (state.localExists) {
1163
- try {
1164
- const localParsed = JSON.parse(await fs.readFile(localSettingsPath, "utf-8"));
1165
- auditCurdxFlowPluginOptions(localParsed, state.localWarnings, state.pluginOptions, { scope: "local" });
1166
- auditLocalClaudeSettings(localParsed, state.localWarnings);
1167
- } catch (error) {
1168
- state.localInvalid = true;
1169
- state.localParseError = error?.message || String(error);
1170
- }
1171
- }
1172
-
1173
- state.managedSettings = await readManagedClaudeSettings(
1174
- state.pluginOptions,
1175
- state.managedWarnings,
1176
- {
1177
- platform,
1178
- env,
1179
- managedSettingsDir,
1180
- }
1181
- );
1182
-
1183
- state.pluginRuntimeProjection = buildCurdxFlowRuntimeProjection(state.pluginOptions);
1184
-
1185
- return state;
1186
- }