@curdx/flow 2.3.11 → 3.1.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 (210) hide show
  1. package/CHANGELOG.md +21 -34
  2. package/LICENSE +1 -1
  3. package/README.md +28 -79
  4. package/dist/index.mjs +995 -0
  5. package/package.json +33 -42
  6. package/.claude-plugin/marketplace.json +0 -48
  7. package/.claude-plugin/plugin.json +0 -70
  8. package/agent-preamble/preamble.md +0 -314
  9. package/agents/flow-adversary.md +0 -202
  10. package/agents/flow-architect.md +0 -197
  11. package/agents/flow-brownfield-analyst.md +0 -142
  12. package/agents/flow-debugger.md +0 -321
  13. package/agents/flow-edge-hunter.md +0 -288
  14. package/agents/flow-executor.md +0 -269
  15. package/agents/flow-orchestrator.md +0 -145
  16. package/agents/flow-planner.md +0 -246
  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 -165
  20. package/agents/flow-reviewer.md +0 -303
  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 -229
  24. package/agents/flow-ux-designer.md +0 -221
  25. package/agents/flow-verifier.md +0 -349
  26. package/bin/curdx-flow +0 -5
  27. package/bin/curdx-flow.js +0 -54
  28. package/cli/README.md +0 -104
  29. package/cli/doctor-workflow.js +0 -483
  30. package/cli/doctor.js +0 -73
  31. package/cli/help.js +0 -59
  32. package/cli/install-bundled-mcps.js +0 -37
  33. package/cli/install-companions.js +0 -19
  34. package/cli/install-context7-config.js +0 -80
  35. package/cli/install-curdx-plugin.js +0 -96
  36. package/cli/install-language.js +0 -35
  37. package/cli/install-next-steps.js +0 -29
  38. package/cli/install-options.js +0 -9
  39. package/cli/install-paths.js +0 -52
  40. package/cli/install-recommended-plugins.js +0 -104
  41. package/cli/install-required-plugins.js +0 -57
  42. package/cli/install-self-update.js +0 -62
  43. package/cli/install-workflow.js +0 -209
  44. package/cli/install.js +0 -101
  45. package/cli/lib/claude-commands.js +0 -41
  46. package/cli/lib/claude-ops.js +0 -47
  47. package/cli/lib/claude.js +0 -183
  48. package/cli/lib/config.js +0 -24
  49. package/cli/lib/doctor-claude-settings.js +0 -1186
  50. package/cli/lib/doctor-report.js +0 -978
  51. package/cli/lib/doctor-runtime-environment.js +0 -196
  52. package/cli/lib/frontmatter.js +0 -44
  53. package/cli/lib/json-schema.js +0 -57
  54. package/cli/lib/logging.js +0 -25
  55. package/cli/lib/process.js +0 -60
  56. package/cli/lib/prompts.js +0 -135
  57. package/cli/lib/runtime.js +0 -107
  58. package/cli/lib/semver.js +0 -109
  59. package/cli/lib/version.js +0 -12
  60. package/cli/protocols-body.md +0 -22
  61. package/cli/protocols.js +0 -162
  62. package/cli/registry.js +0 -123
  63. package/cli/router.js +0 -49
  64. package/cli/uninstall-actions.js +0 -360
  65. package/cli/uninstall-workflow.js +0 -146
  66. package/cli/uninstall.js +0 -42
  67. package/cli/upgrade-workflow.js +0 -80
  68. package/cli/upgrade.js +0 -91
  69. package/cli/utils.js +0 -40
  70. package/gates/adversarial-review-gate.md +0 -219
  71. package/gates/coverage-audit-gate.md +0 -182
  72. package/gates/devex-gate.md +0 -254
  73. package/gates/edge-case-gate.md +0 -194
  74. package/gates/karpathy-gate.md +0 -130
  75. package/gates/security-gate.md +0 -218
  76. package/gates/tdd-gate.md +0 -182
  77. package/gates/test-quality-gate.md +0 -59
  78. package/gates/verification-gate.md +0 -179
  79. package/hooks/hooks.json +0 -58
  80. package/hooks/scripts/common.sh +0 -46
  81. package/hooks/scripts/inject-karpathy.sh +0 -53
  82. package/hooks/scripts/quick-mode-guard.sh +0 -68
  83. package/hooks/scripts/session-start.sh +0 -90
  84. package/hooks/scripts/stop-watcher.sh +0 -230
  85. package/hooks/scripts/subagent-artifact-guard.sh +0 -159
  86. package/hooks/scripts/subagent-statusline.sh +0 -105
  87. package/knowledge/artifact-output-discipline.md +0 -24
  88. package/knowledge/artifact-summary-contracts.md +0 -50
  89. package/knowledge/atomic-commits.md +0 -262
  90. package/knowledge/claude-code-runtime-contracts.md +0 -219
  91. package/knowledge/epic-decomposition.md +0 -307
  92. package/knowledge/execution-strategies.md +0 -303
  93. package/knowledge/karpathy-guidelines.md +0 -219
  94. package/knowledge/planning-reviews.md +0 -211
  95. package/knowledge/poc-first-workflow.md +0 -223
  96. package/knowledge/review-feedback-intake.md +0 -57
  97. package/knowledge/spec-driven-development.md +0 -180
  98. package/knowledge/systematic-debugging.md +0 -378
  99. package/knowledge/two-stage-review.md +0 -249
  100. package/knowledge/wave-execution.md +0 -403
  101. package/monitors/monitors.json +0 -8
  102. package/monitors/scripts/flow-state-monitor.sh +0 -99
  103. package/output-styles/curdx-evidence-first.md +0 -34
  104. package/schemas/agent-frontmatter.schema.json +0 -63
  105. package/schemas/config.schema.json +0 -134
  106. package/schemas/gate-frontmatter.schema.json +0 -30
  107. package/schemas/hooks.schema.json +0 -115
  108. package/schemas/output-style-frontmatter.schema.json +0 -22
  109. package/schemas/plugin-manifest.schema.json +0 -436
  110. package/schemas/plugin-settings.schema.json +0 -29
  111. package/schemas/skill-frontmatter.schema.json +0 -177
  112. package/schemas/spec-frontmatter.schema.json +0 -42
  113. package/schemas/spec-state.schema.json +0 -147
  114. package/settings.json +0 -7
  115. package/skills/brownfield-index/SKILL.md +0 -53
  116. package/skills/brownfield-index/references/applicability.md +0 -12
  117. package/skills/brownfield-index/references/handoff.md +0 -8
  118. package/skills/brownfield-index/references/index-contract.md +0 -10
  119. package/skills/browser-qa/SKILL.md +0 -39
  120. package/skills/browser-qa/references/handoff.md +0 -6
  121. package/skills/browser-qa/references/prerequisites.md +0 -10
  122. package/skills/browser-qa/references/qa-contract.md +0 -20
  123. package/skills/cancel/SKILL.md +0 -41
  124. package/skills/cancel/references/destructive-mode.md +0 -17
  125. package/skills/cancel/references/reporting.md +0 -18
  126. package/skills/cancel/references/state-recovery.md +0 -30
  127. package/skills/cancel/references/target-resolution.md +0 -7
  128. package/skills/debug/SKILL.md +0 -45
  129. package/skills/debug/references/context-gathering.md +0 -11
  130. package/skills/debug/references/failure-guard.md +0 -25
  131. package/skills/debug/references/intake.md +0 -12
  132. package/skills/debug/references/phase-workflow.md +0 -34
  133. package/skills/debug/references/reporting.md +0 -20
  134. package/skills/epic/SKILL.md +0 -39
  135. package/skills/epic/references/epic-artifacts.md +0 -20
  136. package/skills/epic/references/epic-intake.md +0 -9
  137. package/skills/epic/references/slice-handoff.md +0 -16
  138. package/skills/fast/SKILL.md +0 -62
  139. package/skills/fast/references/applicability.md +0 -25
  140. package/skills/fast/references/clarification.md +0 -20
  141. package/skills/fast/references/execution-contract.md +0 -56
  142. package/skills/help/SKILL.md +0 -55
  143. package/skills/help/references/dispatch.md +0 -20
  144. package/skills/help/references/overview.md +0 -39
  145. package/skills/help/references/troubleshoot.md +0 -47
  146. package/skills/help/references/workflow.md +0 -37
  147. package/skills/implement/SKILL.md +0 -96
  148. package/skills/implement/references/error-recovery.md +0 -36
  149. package/skills/implement/references/linear-execution.md +0 -32
  150. package/skills/implement/references/preflight.md +0 -43
  151. package/skills/implement/references/progress-contract.md +0 -32
  152. package/skills/implement/references/state-init.md +0 -33
  153. package/skills/implement/references/stop-hook-execution.md +0 -36
  154. package/skills/implement/references/strategy-router.md +0 -38
  155. package/skills/implement/references/subagent-execution.md +0 -43
  156. package/skills/implement/references/wave-execution.md +0 -162
  157. package/skills/init/SKILL.md +0 -49
  158. package/skills/init/references/gitignore-and-health.md +0 -26
  159. package/skills/init/references/next-steps.md +0 -22
  160. package/skills/init/references/preflight.md +0 -15
  161. package/skills/init/references/scaffold-contract.md +0 -27
  162. package/skills/review/SKILL.md +0 -82
  163. package/skills/review/references/optional-passes.md +0 -48
  164. package/skills/review/references/preflight.md +0 -38
  165. package/skills/review/references/report-contract.md +0 -49
  166. package/skills/review/references/reporting.md +0 -20
  167. package/skills/review/references/stage-execution.md +0 -32
  168. package/skills/security-audit/SKILL.md +0 -47
  169. package/skills/security-audit/references/audit-contract.md +0 -21
  170. package/skills/security-audit/references/gate-handoff.md +0 -8
  171. package/skills/security-audit/references/scope-and-depth.md +0 -9
  172. package/skills/spec/SKILL.md +0 -100
  173. package/skills/spec/references/artifact-landing.md +0 -31
  174. package/skills/spec/references/phase-execution.md +0 -50
  175. package/skills/spec/references/planning-review.md +0 -31
  176. package/skills/spec/references/preflight-and-routing.md +0 -46
  177. package/skills/spec/references/reporting.md +0 -21
  178. package/skills/start/SKILL.md +0 -84
  179. package/skills/start/references/branch-routing.md +0 -51
  180. package/skills/start/references/mode-semantics.md +0 -12
  181. package/skills/start/references/preflight.md +0 -13
  182. package/skills/start/references/reporting.md +0 -20
  183. package/skills/start/references/state-seeding.md +0 -44
  184. package/skills/start/references/workflow-handoff.md +0 -26
  185. package/skills/status/SKILL.md +0 -41
  186. package/skills/status/references/gather-contract.md +0 -27
  187. package/skills/status/references/health-rules.md +0 -27
  188. package/skills/status/references/output-contract.md +0 -24
  189. package/skills/status/references/preflight.md +0 -10
  190. package/skills/status/references/recovery-hints.md +0 -18
  191. package/skills/ui-sketch/SKILL.md +0 -39
  192. package/skills/ui-sketch/references/brief-intake.md +0 -10
  193. package/skills/ui-sketch/references/iteration-handoff.md +0 -5
  194. package/skills/ui-sketch/references/variant-contract.md +0 -15
  195. package/skills/verify/SKILL.md +0 -56
  196. package/skills/verify/references/evidence-workflow.md +0 -39
  197. package/skills/verify/references/output-contract.md +0 -23
  198. package/skills/verify/references/preflight.md +0 -11
  199. package/skills/verify/references/report-handoff.md +0 -35
  200. package/skills/verify/references/strict-mode.md +0 -12
  201. package/templates/CONTEXT.md.tmpl +0 -53
  202. package/templates/PROJECT.md.tmpl +0 -59
  203. package/templates/ROADMAP.md.tmpl +0 -50
  204. package/templates/STATE.md.tmpl +0 -49
  205. package/templates/config.json.tmpl +0 -51
  206. package/templates/design.md.tmpl +0 -83
  207. package/templates/progress.md.tmpl +0 -77
  208. package/templates/requirements.md.tmpl +0 -76
  209. package/templates/research.md.tmpl +0 -83
  210. 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
- }