openplanr 2.2641.1 → 2.2641.2

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 (245) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/cli/commands/artifact.js +1 -1
  4. package/dist/cli/commands/artifact.js.map +1 -1
  5. package/dist/cli/commands/github.d.ts.map +1 -1
  6. package/dist/cli/commands/github.js +7 -10
  7. package/dist/cli/commands/github.js.map +1 -1
  8. package/dist/cli/commands/linear.js +1 -1
  9. package/dist/cli/commands/linear.js.map +1 -1
  10. package/dist/cli/commands/report.d.ts.map +1 -1
  11. package/dist/cli/commands/report.js +28 -32
  12. package/dist/cli/commands/report.js.map +1 -1
  13. package/dist/dashboard/.vite/manifest.json +1 -1
  14. package/dist/dashboard/assets/{index-DGyC7RDz.js → index-DI_TKz9J.js} +2 -2
  15. package/dist/dashboard/assets/{index-DGyC7RDz.js.map → index-DI_TKz9J.js.map} +1 -1
  16. package/dist/dashboard/dashboard-manifest.json +8 -8
  17. package/dist/dashboard/index.html +1 -1
  18. package/dist/models/schema.d.ts +0 -4
  19. package/dist/models/schema.d.ts.map +1 -1
  20. package/dist/models/schema.js +0 -2
  21. package/dist/models/schema.js.map +1 -1
  22. package/dist/models/types.d.ts +1 -4
  23. package/dist/models/types.d.ts.map +1 -1
  24. package/dist/services/credentials-service.js +1 -1
  25. package/dist/services/credentials-service.js.map +1 -1
  26. package/dist/services/distribution-service.d.ts +1 -4
  27. package/dist/services/distribution-service.d.ts.map +1 -1
  28. package/dist/services/distribution-service.js +1 -60
  29. package/dist/services/distribution-service.js.map +1 -1
  30. package/dist/services/github-service.d.ts +7 -4
  31. package/dist/services/github-service.d.ts.map +1 -1
  32. package/dist/services/github-service.js +16 -15
  33. package/dist/services/github-service.js.map +1 -1
  34. package/dist/services/linear-push-service.d.ts +3 -4
  35. package/dist/services/linear-push-service.d.ts.map +1 -1
  36. package/dist/services/linear-push-service.js +5 -6
  37. package/dist/services/linear-push-service.js.map +1 -1
  38. package/dist/services/spec-service.d.ts.map +1 -1
  39. package/dist/services/spec-service.js +34 -69
  40. package/dist/services/spec-service.js.map +1 -1
  41. package/dist/templates/rules/shared/capability-map.md.hbs +1 -1
  42. package/docs/CLI.md +10 -14
  43. package/lib/host-packages/adapter-registry.json +5 -5
  44. package/lib/host-packages/capability-map.json +8 -8
  45. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  46. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  47. package/lib/host-packages/claude/openplanr/.openplanr-content.json +80 -64
  48. package/lib/host-packages/claude/openplanr/README.md +8 -12
  49. package/lib/host-packages/claude/openplanr/agents/planr-backend.md +49 -55
  50. package/lib/host-packages/claude/openplanr/agents/planr-database.md +56 -92
  51. package/lib/host-packages/claude/openplanr/agents/planr-designer.md +35 -51
  52. package/lib/host-packages/claude/openplanr/agents/planr-devops.md +37 -51
  53. package/lib/host-packages/claude/openplanr/agents/planr-documentation.md +29 -52
  54. package/lib/host-packages/claude/openplanr/agents/planr-entity-scaffold.md +24 -23
  55. package/lib/host-packages/claude/openplanr/agents/planr-frontend.md +46 -58
  56. package/lib/host-packages/claude/openplanr/agents/planr-qa.md +4 -2
  57. package/lib/host-packages/claude/openplanr/agents/planr-specification.md +54 -51
  58. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/backend.md +21 -65
  59. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/designer.md +5 -6
  60. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/devops.md +2 -3
  61. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/doc-gen.md +3 -4
  62. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/frontend.md +19 -64
  63. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/default/specification.md +10 -12
  64. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/backend.md +19 -62
  65. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/designer.md +3 -4
  66. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/devops.md +2 -3
  67. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/doc-gen.md +2 -3
  68. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/frontend.md +16 -59
  69. package/lib/host-packages/claude/openplanr/references/agents/shared/modes/spec-driven/specification.md +9 -11
  70. package/lib/host-packages/claude/openplanr/references/pipeline/stacks/database/mongodb.md +4 -4
  71. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +24 -9
  72. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share.mjs +3 -3
  73. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  74. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +1 -1
  75. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  76. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +1 -1
  77. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  78. package/lib/host-packages/claude/openplanr/skills/artifact/SKILL.md +2 -2
  79. package/lib/host-packages/claude/openplanr/skills/artifact/references/diagram-sharing.md +1 -1
  80. package/lib/host-packages/claude/openplanr/skills/ceo-review/SKILL.md +0 -1
  81. package/lib/host-packages/claude/openplanr/skills/ceo-review/references/operate-advisor-contract.md +25 -4
  82. package/lib/host-packages/claude/openplanr/skills/chair-review/SKILL.md +6 -2
  83. package/lib/host-packages/claude/openplanr/skills/challenger-review/SKILL.md +6 -2
  84. package/lib/host-packages/claude/openplanr/skills/cmo-review/SKILL.md +0 -1
  85. package/lib/host-packages/claude/openplanr/skills/cmo-review/references/operate-advisor-contract.md +25 -4
  86. package/lib/host-packages/claude/openplanr/skills/coo-review/SKILL.md +0 -1
  87. package/lib/host-packages/claude/openplanr/skills/coo-review/references/operate-advisor-contract.md +25 -4
  88. package/lib/host-packages/claude/openplanr/skills/cpo-review/SKILL.md +0 -1
  89. package/lib/host-packages/claude/openplanr/skills/cpo-review/references/operate-advisor-contract.md +25 -4
  90. package/lib/host-packages/claude/openplanr/skills/cto-review/SKILL.md +0 -1
  91. package/lib/host-packages/claude/openplanr/skills/cto-review/references/operate-advisor-contract.md +25 -4
  92. package/lib/host-packages/claude/openplanr/skills/delegate/SKILL.md +7 -0
  93. package/lib/host-packages/claude/openplanr/skills/delegate/references/capsule-contract.md +16 -1
  94. package/lib/host-packages/claude/openplanr/skills/delegate/references/operator-guide.md +7 -5
  95. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/claude.mjs +11 -4
  96. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/native.mjs +1 -1
  97. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/backend-diagnostics.mjs +14 -48
  98. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/context.mjs +84 -22
  99. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/credentials.mjs +167 -0
  100. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/presentation.mjs +3 -2
  101. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-contract.mjs +5 -7
  102. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-preparation.mjs +2 -0
  103. package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-record.mjs +2 -3
  104. package/lib/host-packages/claude/openplanr/skills/diagram/references/diagram-sharing.md +1 -1
  105. package/lib/host-packages/claude/openplanr/skills/operate/SKILL.md +5 -3
  106. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +8 -2
  107. package/lib/host-packages/claude/openplanr/skills/sprint/SKILL.md +5 -4
  108. package/lib/host-packages/claude/openplanr/skills/sprint/references/tracker-connections.md +70 -0
  109. package/lib/host-packages/claude/openplanr/skills/status/SKILL.md +4 -3
  110. package/lib/host-packages/claude/openplanr/skills/status/references/tracker-connections.md +70 -0
  111. package/lib/host-packages/claude/openplanr/skills/sync/SKILL.md +11 -20
  112. package/lib/host-packages/claude/openplanr/skills/sync/references/tracker-connections.md +70 -0
  113. package/lib/host-packages/claude/openplanr/skills/sync/scripts/sync.mjs +2 -2
  114. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +52 -36
  115. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  116. package/lib/host-packages/cursor/openplanr/rules/planr-artifact/references/diagram-sharing.md +1 -1
  117. package/lib/host-packages/cursor/openplanr/rules/planr-artifact.mdc +2 -2
  118. package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/references/operate-advisor-contract.md +25 -4
  119. package/lib/host-packages/cursor/openplanr/rules/planr-chair-review.mdc +6 -1
  120. package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review.mdc +6 -1
  121. package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/references/operate-advisor-contract.md +25 -4
  122. package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/references/operate-advisor-contract.md +25 -4
  123. package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/references/operate-advisor-contract.md +25 -4
  124. package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/references/operate-advisor-contract.md +25 -4
  125. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/capsule-contract.md +16 -1
  126. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/operator-guide.md +7 -5
  127. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/claude.mjs +11 -4
  128. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/native.mjs +1 -1
  129. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/backend-diagnostics.mjs +14 -48
  130. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/context.mjs +84 -22
  131. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/credentials.mjs +167 -0
  132. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/presentation.mjs +3 -2
  133. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-contract.mjs +5 -7
  134. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-preparation.mjs +2 -0
  135. package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-record.mjs +2 -3
  136. package/lib/host-packages/cursor/openplanr/rules/planr-delegate.mdc +7 -0
  137. package/lib/host-packages/cursor/openplanr/rules/planr-diagram/references/diagram-sharing.md +1 -1
  138. package/lib/host-packages/cursor/openplanr/rules/planr-operate.mdc +5 -3
  139. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +8 -2
  140. package/lib/host-packages/cursor/openplanr/rules/planr-sprint/references/tracker-connections.md +70 -0
  141. package/lib/host-packages/cursor/openplanr/rules/planr-sprint.mdc +5 -4
  142. package/lib/host-packages/cursor/openplanr/rules/planr-status/references/tracker-connections.md +70 -0
  143. package/lib/host-packages/cursor/openplanr/rules/planr-status.mdc +4 -3
  144. package/lib/host-packages/cursor/openplanr/rules/planr-sync/references/tracker-connections.md +70 -0
  145. package/lib/host-packages/cursor/openplanr/rules/planr-sync/scripts/sync.mjs +2 -2
  146. package/lib/host-packages/cursor/openplanr/rules/planr-sync.mdc +11 -20
  147. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +24 -9
  148. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share.mjs +3 -3
  149. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  150. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +1 -1
  151. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  152. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +1 -1
  153. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  154. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  155. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  156. package/lib/host-packages/openai/openplanr/.openplanr-content.json +61 -45
  157. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +24 -9
  158. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share.mjs +3 -3
  159. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
  160. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +1 -1
  161. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +1 -1
  162. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +1 -1
  163. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +1 -1
  164. package/lib/host-packages/openai/openplanr/skills/artifact/SKILL.md +2 -2
  165. package/lib/host-packages/openai/openplanr/skills/artifact/references/diagram-sharing.md +1 -1
  166. package/lib/host-packages/openai/openplanr/skills/ceo-review/SKILL.md +0 -1
  167. package/lib/host-packages/openai/openplanr/skills/ceo-review/references/operate-advisor-contract.md +25 -4
  168. package/lib/host-packages/openai/openplanr/skills/chair-review/SKILL.md +6 -2
  169. package/lib/host-packages/openai/openplanr/skills/challenger-review/SKILL.md +6 -2
  170. package/lib/host-packages/openai/openplanr/skills/cmo-review/SKILL.md +0 -1
  171. package/lib/host-packages/openai/openplanr/skills/cmo-review/references/operate-advisor-contract.md +25 -4
  172. package/lib/host-packages/openai/openplanr/skills/coo-review/SKILL.md +0 -1
  173. package/lib/host-packages/openai/openplanr/skills/coo-review/references/operate-advisor-contract.md +25 -4
  174. package/lib/host-packages/openai/openplanr/skills/cpo-review/SKILL.md +0 -1
  175. package/lib/host-packages/openai/openplanr/skills/cpo-review/references/operate-advisor-contract.md +25 -4
  176. package/lib/host-packages/openai/openplanr/skills/cto-review/SKILL.md +0 -1
  177. package/lib/host-packages/openai/openplanr/skills/cto-review/references/operate-advisor-contract.md +25 -4
  178. package/lib/host-packages/openai/openplanr/skills/delegate/SKILL.md +7 -0
  179. package/lib/host-packages/openai/openplanr/skills/delegate/openplanr.skill.json +11 -0
  180. package/lib/host-packages/openai/openplanr/skills/delegate/references/capsule-contract.md +16 -1
  181. package/lib/host-packages/openai/openplanr/skills/delegate/references/operator-guide.md +7 -5
  182. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/claude.mjs +11 -4
  183. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/native.mjs +1 -1
  184. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/backend-diagnostics.mjs +14 -48
  185. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/context.mjs +84 -22
  186. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/credentials.mjs +167 -0
  187. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/presentation.mjs +3 -2
  188. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-contract.mjs +5 -7
  189. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-preparation.mjs +2 -0
  190. package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-record.mjs +2 -3
  191. package/lib/host-packages/openai/openplanr/skills/diagram/references/diagram-sharing.md +1 -1
  192. package/lib/host-packages/openai/openplanr/skills/operate/SKILL.md +5 -3
  193. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +8 -2
  194. package/lib/host-packages/openai/openplanr/skills/sprint/SKILL.md +5 -4
  195. package/lib/host-packages/openai/openplanr/skills/sprint/openplanr.skill.json +11 -0
  196. package/lib/host-packages/openai/openplanr/skills/sprint/references/tracker-connections.md +70 -0
  197. package/lib/host-packages/openai/openplanr/skills/status/SKILL.md +4 -3
  198. package/lib/host-packages/openai/openplanr/skills/status/openplanr.skill.json +11 -0
  199. package/lib/host-packages/openai/openplanr/skills/status/references/tracker-connections.md +70 -0
  200. package/lib/host-packages/openai/openplanr/skills/sync/SKILL.md +11 -20
  201. package/lib/host-packages/openai/openplanr/skills/sync/openplanr.skill.json +11 -0
  202. package/lib/host-packages/openai/openplanr/skills/sync/references/tracker-connections.md +70 -0
  203. package/lib/host-packages/openai/openplanr/skills/sync/scripts/sync.mjs +2 -2
  204. package/lib/integrations.mjs +2 -2
  205. package/package.json +2 -2
  206. package/dist/services/connectors/adapters/business-metrics.d.ts +0 -5
  207. package/dist/services/connectors/adapters/business-metrics.d.ts.map +0 -1
  208. package/dist/services/connectors/adapters/business-metrics.js +0 -41
  209. package/dist/services/connectors/adapters/business-metrics.js.map +0 -1
  210. package/dist/services/connectors/adapters/deployment-health.d.ts +0 -18
  211. package/dist/services/connectors/adapters/deployment-health.d.ts.map +0 -1
  212. package/dist/services/connectors/adapters/deployment-health.js +0 -67
  213. package/dist/services/connectors/adapters/deployment-health.js.map +0 -1
  214. package/dist/services/connectors/adapters/github.d.ts +0 -5
  215. package/dist/services/connectors/adapters/github.d.ts.map +0 -1
  216. package/dist/services/connectors/adapters/github.js +0 -31
  217. package/dist/services/connectors/adapters/github.js.map +0 -1
  218. package/dist/services/connectors/adapters/linear-jira.d.ts +0 -5
  219. package/dist/services/connectors/adapters/linear-jira.d.ts.map +0 -1
  220. package/dist/services/connectors/adapters/linear-jira.js +0 -26
  221. package/dist/services/connectors/adapters/linear-jira.js.map +0 -1
  222. package/dist/services/connectors/adapters/posthog.d.ts +0 -5
  223. package/dist/services/connectors/adapters/posthog.d.ts.map +0 -1
  224. package/dist/services/connectors/adapters/posthog.js +0 -31
  225. package/dist/services/connectors/adapters/posthog.js.map +0 -1
  226. package/dist/services/connectors/adapters/sentry.d.ts +0 -5
  227. package/dist/services/connectors/adapters/sentry.d.ts.map +0 -1
  228. package/dist/services/connectors/adapters/sentry.js +0 -31
  229. package/dist/services/connectors/adapters/sentry.js.map +0 -1
  230. package/dist/services/connectors/checkpoint-custody.d.ts +0 -45
  231. package/dist/services/connectors/checkpoint-custody.d.ts.map +0 -1
  232. package/dist/services/connectors/checkpoint-custody.js +0 -499
  233. package/dist/services/connectors/checkpoint-custody.js.map +0 -1
  234. package/dist/services/connectors/connector-registry.d.ts +0 -144
  235. package/dist/services/connectors/connector-registry.d.ts.map +0 -1
  236. package/dist/services/connectors/connector-registry.js +0 -423
  237. package/dist/services/connectors/connector-registry.js.map +0 -1
  238. package/dist/services/connectors/connector-runtime.d.ts +0 -208
  239. package/dist/services/connectors/connector-runtime.d.ts.map +0 -1
  240. package/dist/services/connectors/connector-runtime.js +0 -1305
  241. package/dist/services/connectors/connector-runtime.js.map +0 -1
  242. package/dist/services/connectors/credential-custody.d.ts +0 -96
  243. package/dist/services/connectors/credential-custody.d.ts.map +0 -1
  244. package/dist/services/connectors/credential-custody.js +0 -390
  245. package/dist/services/connectors/credential-custody.js.map +0 -1
@@ -18,9 +18,16 @@ const PROVIDER_VARIABLES = [
18
18
  'CLAUDE_CODE_USE_VERTEX',
19
19
  'CLAUDE_CODE_USE_FOUNDRY',
20
20
  ];
21
+ const ROUTING_VARIABLES = ['ANTHROPIC_BASE_URL', ...PROVIDER_VARIABLES];
21
22
 
22
- // These values stay in trusted memory. Callers expose only source labels and origins.
23
- export async function readClaudeSettingsEnvironment(profile, env, cwd, options = {}) {
23
+ function routingVariables(env) {
24
+ return Object.fromEntries(
25
+ ROUTING_VARIABLES.filter((name) => Object.hasOwn(env, name)).map((name) => [name, env[name]]),
26
+ );
27
+ }
28
+
29
+ // Keeps only routing variables from settings files; callers expose source labels and origins.
30
+ async function readClaudeRoutingSettings(profile, env, cwd, options = {}) {
24
31
  const managedRoot =
25
32
  options.managedRoot ??
26
33
  (process.platform === 'darwin'
@@ -100,7 +107,7 @@ export async function readClaudeSettingsEnvironment(profile, env, cwd, options =
100
107
  throw new AdapterError('E_DESTINATION_UNKNOWN', 'Claude routing environment is malformed.');
101
108
  inspected.push({
102
109
  source,
103
- env: settings.env ?? {},
110
+ env: routingVariables(settings.env ?? {}),
104
111
  ...(settings.policyHelper ? { opaqueRouting: true } : {}),
105
112
  });
106
113
  } catch (error) {
@@ -122,7 +129,7 @@ export async function readClaudeSettingsEnvironment(profile, env, cwd, options =
122
129
  }
123
130
 
124
131
  export async function resolveClaudeRouting(profile, env, cwd, options) {
125
- const settings = await readClaudeSettingsEnvironment(profile, env, cwd, options);
132
+ const settings = await readClaudeRoutingSettings(profile, env, cwd, options);
126
133
  const sources = [{ source: 'parent environment', env }, ...settings];
127
134
  const candidates = [];
128
135
  const providerSources = [];
@@ -122,7 +122,7 @@ export function safeNativeDiagnostic(value, destination) {
122
122
  }
123
123
 
124
124
  const ROUTING_SOURCE =
125
- /^(?:parent environment|user settings|project settings|project local settings|managed file settings|managed fragment settings(?: \d{1,2})?)(?:: (?:ANTHROPIC_BASE_URL|CLAUDE_CODE_USE_BEDROCK|CLAUDE_CODE_USE_VERTEX|CLAUDE_CODE_USE_FOUNDRY|policyHelper|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY))?$/u;
125
+ /^(?:parent environment|user settings|project settings|project local settings|managed file settings|managed fragment settings(?: \d{1,2})?)(?:: (?:ANTHROPIC_BASE_URL|CLAUDE_CODE_USE_BEDROCK|CLAUDE_CODE_USE_VERTEX|CLAUDE_CODE_USE_FOUNDRY|policyHelper))?$/u;
126
126
 
127
127
  function safeRoutingDestination(value) {
128
128
  if (value?.class === 'native-managed' && value.origin === 'native-managed')
@@ -1,40 +1,8 @@
1
1
  // Local probes are diagnostics; native CLI execution remains authoritative.
2
2
 
3
- import { readClaudeSettingsEnvironment, resolveClaudeRouting } from './adapters/claude.mjs';
3
+ import { resolveClaudeRouting } from './adapters/claude.mjs';
4
4
  import { AdapterError, validateDestination } from './adapters/generic.mjs';
5
5
 
6
- async function localProbeToken(profile, env, cwd) {
7
- const sources = [{ source: 'parent environment', env }];
8
- const names = ['LM_STUDIO_API_KEY', 'LM_API_TOKEN'];
9
- // Only Claude routing confirmed to this local origin may present its Anthropic token.
10
- if (profile.kind === 'claude') {
11
- sources.push(...(await readClaudeSettingsEnvironment(profile, env, cwd)));
12
- names.push('ANTHROPIC_AUTH_TOKEN');
13
- }
14
- for (const name of names) {
15
- const candidates = sources.filter(
16
- (source) => source.env[name] !== undefined && source.env[name] !== '',
17
- );
18
- if (!candidates.length) continue;
19
- const values = candidates.map((source) => source.env[name]);
20
- if (
21
- values.some(
22
- (value) => typeof value !== 'string' || value.length >= 4096 || /[\r\n]/u.test(value),
23
- ) ||
24
- new Set(values).size !== 1
25
- )
26
- throw new AdapterError(
27
- 'E_BACKEND_AUTH_CONFIG',
28
- 'Local metadata authentication could not be confirmed.',
29
- {
30
- sources: candidates.map((source) => `${source.source}: ${name}`),
31
- },
32
- );
33
- return values[0];
34
- }
35
- return null;
36
- }
37
-
38
6
  async function withinDeadline(operation, signal) {
39
7
  if (signal.aborted)
40
8
  throw new AdapterError('E_BACKEND_TIMEOUT', 'Local model metadata inspection timed out.');
@@ -106,9 +74,8 @@ export async function inspectLocalBackend(
106
74
  const timer = setTimeout(() => controller.abort(), timeoutMs);
107
75
  const signal = controller.signal;
108
76
  try {
109
- let token;
110
- try {
111
- if (profile.kind === 'claude') {
77
+ if (profile.kind === 'claude') {
78
+ try {
112
79
  const resolved = await resolveClaudeRouting(profile, env, cwd);
113
80
  if (
114
81
  resolved.destination.class !== 'local' ||
@@ -122,22 +89,21 @@ export async function inspectLocalBackend(
122
89
  routing: resolved.routing,
123
90
  },
124
91
  );
92
+ } catch (error) {
93
+ return {
94
+ status: 'not-checked',
95
+ modelStatus: 'unverified',
96
+ selectedModel,
97
+ visibleModels: [],
98
+ diagnostic: { code: error.code ?? 'E_DESTINATION_UNKNOWN', ...error.details },
99
+ };
125
100
  }
126
- token = await localProbeToken(profile, env, cwd);
127
- } catch (error) {
128
- return {
129
- status: 'not-checked',
130
- modelStatus: 'unverified',
131
- selectedModel,
132
- visibleModels: [],
133
- diagnostic: { code: error.code ?? 'E_BACKEND_AUTH_CONFIG', ...error.details },
134
- };
135
101
  }
102
+ // Sends no credentials: a server that requires sign-in reports authentication-required.
136
103
  const readModels = async (path) => {
137
104
  const response = await withinDeadline(
138
105
  fetchImpl(new URL(path, profile.destination.origin), {
139
106
  method: 'GET',
140
- ...(token ? { headers: { Authorization: `Bearer ${token}` } } : {}),
141
107
  signal,
142
108
  redirect: 'error',
143
109
  }),
@@ -269,9 +235,9 @@ export function profileReadiness(destination, backend) {
269
235
  if (backend.status === 'authentication-required') {
270
236
  return {
271
237
  state: 'authentication-required',
272
- dispatchable: false,
238
+ dispatchable: true,
273
239
  nextAction:
274
- 'Provide the local server token through an allowed environment variable, then probe again.',
240
+ 'The local server requires sign-in, so its models were not checked. The delegated CLI signs in with its own configuration.',
275
241
  };
276
242
  }
277
243
  if (backend.modelStatus === 'not-listed') {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { lstat, mkdir, readdir, readFile, realpath, stat, writeFile } from 'node:fs/promises';
3
3
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { classifyCredentials, credentialSyntax, resolveFindings } from './credentials.mjs';
4
5
 
5
6
  export const CAPSULE_SCHEMA_VERSION = '1.0.0';
6
7
 
@@ -12,19 +13,14 @@ const DEFAULT_LIMITS = Object.freeze({
12
13
  const SECRET_NAME =
13
14
  /^(?:\.env(?:\..*)?|\.envrc|\.netrc|\.git-credentials|\.npmrc|\.pypirc|id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?|credentials(?:\.[^.]+)?|service[-_]?account(?:\.[^.]+)?|.*\.(?:pem|p12|pfx|key|tfvars)(?:\.json)?)$/iu;
14
15
  const SECRET_SEGMENT = /^(?:\.ssh|\.aws|\.gnupg|\.kube)$/iu;
15
- const SECRET_VALUE =
16
- /-----BEGIN (?:[A-Z ]* )?PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9_]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b|\bsk-(?:proj-)?[A-Za-z0-9_-]{24,}\b|\bxox[baprs]-[A-Za-z0-9-]{20,}\b|"private_key"\s*:\s*"-----BEGIN/iu;
17
- const SECRET_ASSIGNMENT =
18
- /(?:^|[^A-Za-z0-9_])(?:[A-Za-z_][A-Za-z0-9_-]*?)?(?:API[_-]?KEY|ACCESS[_-]?TOKEN|AUTH[_-]?TOKEN|CLIENT[_-]?SECRET|PRIVATE[_-]?KEY|PASSWORD|TOKEN|SECRET(?:[_-]?KEY)?)["']?\s*[:=]\s*(["'`])((?:(?!\1|\$\{)[^\r\n\\]|\\.){16,})\1/giu;
19
- const SECRET_CONFIG_ASSIGNMENT =
20
- /^[ \t]*(?:export[ \t]+)?(?:[A-Za-z_][A-Za-z0-9_-]*?)?(?:API[_-]?KEY|ACCESS[_-]?TOKEN|AUTH[_-]?TOKEN|CLIENT[_-]?SECRET|PRIVATE[_-]?KEY|PASSWORD|TOKEN|SECRET(?:[_-]?KEY)?)[ \t]*[:=][ \t]*(?!process\.env(?:\.|\[)|import\.meta\.env(?:\.|\[)|Deno\.env\.|os\.environ|env\.)([^\s"'`#()\[\]{}$]{16,})[ \t]*(?:#[^\r\n]*)?\r?$/gimu;
21
- const CREDENTIAL_PLACEHOLDER =
22
- /^(?:example(?:[-_].*)?|placeholder(?:[-_].*)?|your[-_].*|(?:change|replace)[-_]me(?:[-_].*)?|(?:dummy|fake|mock|test|never[-_]return)[-_](?:api[-_]?key|access[-_]?token|auth[-_]?token|client[-_]?secret|password|token|secret(?:[-_]?key)?))$/iu;
23
16
  const TASK_ID = /^(?:T|TASK|QT)-\d{3,}$/u;
24
17
  const STORY_ID = /^US-\d{3,}$/u;
25
18
  const SPEC_ID = /^SPEC-\d{3,}$/u;
26
19
  const FEATURE_ID = /^FEAT-\d{3,}$/u;
27
20
  const EPIC_ID = /^EPIC-\d{3,}$/u;
21
+ // Credential findings of an optional omission, keyed by the omission entry. The delegate reads
22
+ // the written capsule, so they reach only the parent's preview.
23
+ const OMISSION_FINDINGS = new WeakMap();
28
24
 
29
25
  export class CapsuleError extends Error {
30
26
  constructor(code, message, details = {}) {
@@ -68,14 +64,9 @@ function isSecretPath(path) {
68
64
  return parts.some((part) => SECRET_SEGMENT.test(part)) || SECRET_NAME.test(parts.at(-1));
69
65
  }
70
66
 
67
+ /** True when free text holds credential material; source files are classified by their syntax. */
71
68
  export function containsSecret(bytes) {
72
- const content = bytes.toString('utf8');
73
- if (SECRET_VALUE.test(content)) return true;
74
- for (const match of content.matchAll(SECRET_ASSIGNMENT))
75
- if (!CREDENTIAL_PLACEHOLDER.test(match[2])) return true;
76
- for (const match of content.matchAll(SECRET_CONFIG_ASSIGNMENT))
77
- if (!CREDENTIAL_PLACEHOLDER.test(match[1])) return true;
78
- return false;
69
+ return classifyCredentials(bytes).findings.length > 0;
79
70
  }
80
71
 
81
72
  export function assertCredentialFreeText(
@@ -84,10 +75,34 @@ export function assertCredentialFreeText(
84
75
  ) {
85
76
  if (typeof value !== 'string')
86
77
  throw new CapsuleError('E_CAPSULE_INPUT', `${label} must be text.`);
87
- if (containsSecret(Buffer.from(value, 'utf8')))
78
+ const { contentDigest, findings } = classifyCredentials(Buffer.from(value, 'utf8'), {
79
+ label,
80
+ resolvable: false,
81
+ });
82
+ if (findings.length)
88
83
  throw new CapsuleError(
89
84
  code,
90
85
  `${label} contains credential material; use native configuration or credential environment references instead.`,
86
+ { findings, contentDigest },
87
+ );
88
+ return value;
89
+ }
90
+
91
+ function credentialResolutionList(value) {
92
+ if (
93
+ !Array.isArray(value) ||
94
+ value.length > 64 ||
95
+ value.some(
96
+ (resolution) =>
97
+ !resolution ||
98
+ typeof resolution !== 'object' ||
99
+ !/^cred_[a-f0-9]{16}$/u.test(resolution.id ?? '') ||
100
+ !/^sha256:[a-f0-9]{64}$/u.test(resolution.contentDigest ?? ''),
101
+ )
102
+ )
103
+ throw new CapsuleError(
104
+ 'E_CAPSULE_INPUT',
105
+ 'Credential resolutions must list at most 64 { id, contentDigest } entries from reported findings.',
91
106
  );
92
107
  return value;
93
108
  }
@@ -312,8 +327,22 @@ export async function buildContextCapsule({
312
327
  optionalFiles = [],
313
328
  readOnlyRepositories = [],
314
329
  limits,
330
+ credentialResolutions = [],
315
331
  } = {}) {
316
332
  if (!repositoryRoot) throw new CapsuleError('E_CAPSULE_SOURCE', 'repositoryRoot is required.');
333
+ const resolutions = credentialResolutionList(credentialResolutions);
334
+ const appliedResolutions = [];
335
+ const credentialCheck = (bytes, origin) => {
336
+ const classification = classifyCredentials(bytes, origin);
337
+ return {
338
+ ...resolveFindings(classification, resolutions),
339
+ contentDigest: classification.contentDigest,
340
+ };
341
+ };
342
+ const recordResolutions = ({ applied, contentDigest }, source) => {
343
+ for (const { id, rule, location } of applied)
344
+ appliedResolutions.push({ id, contentDigest, ...source, rule, location });
345
+ };
317
346
  if (request !== undefined && typeof request !== 'string')
318
347
  throw new CapsuleError('E_CAPSULE_INPUT', 'A direct request must be text.');
319
348
  if (Boolean(taskSelector) === Boolean(request?.trim())) {
@@ -354,8 +383,18 @@ export async function buildContextCapsule({
354
383
  if (request && Buffer.byteLength(request, 'utf8') > bounds.maxFileBytes) {
355
384
  throw new CapsuleError('E_CAPSULE_LIMIT', 'The direct request exceeds the capsule text limit.');
356
385
  }
357
- if (request && containsSecret(Buffer.from(request, 'utf8'))) {
358
- throw new CapsuleError('E_CAPSULE_SECRET', 'The direct request contains credential material.');
386
+ if (request) {
387
+ const credentials = credentialCheck(Buffer.from(request, 'utf8'), { label: 'request' });
388
+ if (credentials.remaining.length)
389
+ throw new CapsuleError(
390
+ 'E_CAPSULE_SECRET',
391
+ 'The direct request contains credential material.',
392
+ {
393
+ findings: credentials.remaining,
394
+ contentDigest: credentials.contentDigest,
395
+ },
396
+ );
397
+ recordResolutions(credentials, { source: 'request' });
359
398
  }
360
399
  const roots = await sourceRoots(repositoryRoot, readOnlyRepositories);
361
400
  const project = roots.get('project').physical;
@@ -381,9 +420,16 @@ export async function buildContextCapsule({
381
420
  return prior;
382
421
  }
383
422
  const optionalOmission = (reason) => {
384
- if (required) throw new CapsuleError(reason.code, reason.message, { repositoryKey, path });
423
+ if (required)
424
+ throw new CapsuleError(reason.code, reason.message, {
425
+ repositoryKey,
426
+ path,
427
+ ...reason.details,
428
+ });
385
429
  if (!omitted.has(key)) {
386
- omissions.push({ repositoryKey, path, role, reason: reason.code });
430
+ const omission = { repositoryKey, path, role, reason: reason.code };
431
+ if (reason.details) OMISSION_FINDINGS.set(omission, reason.details);
432
+ omissions.push(omission);
387
433
  omitted.add(key);
388
434
  }
389
435
  return null;
@@ -467,11 +513,20 @@ export async function buildContextCapsule({
467
513
  message: `Required source exceeds capsule limits: ${path}`,
468
514
  });
469
515
  }
470
- if (containsSecret(bytes))
516
+ // A link reads as its target's syntax, or as configuration when either path is configuration.
517
+ const syntaxes = [path, relative(allowed, physical).split(sep).join('/')].map(credentialSyntax);
518
+ const credentials = credentialCheck(bytes, {
519
+ path,
520
+ label: `${repositoryKey}/${path}`,
521
+ syntax: syntaxes.includes('config') ? 'config' : syntaxes[1],
522
+ });
523
+ if (credentials.remaining.length)
471
524
  return optionalOmission({
472
525
  code: 'E_CAPSULE_SECRET',
473
526
  message: `Credential material detected in source: ${path}`,
527
+ details: { findings: credentials.remaining, contentDigest: credentials.contentDigest },
474
528
  });
529
+ recordResolutions(credentials, { repositoryKey, path });
475
530
  const copy = {
476
531
  repositoryKey,
477
532
  path,
@@ -686,6 +741,7 @@ export async function buildContextCapsule({
686
741
  inventory,
687
742
  files,
688
743
  omissions,
744
+ ...(appliedResolutions.length ? { credentialResolutions: appliedResolutions } : {}),
689
745
  planning: {
690
746
  logicalPath: join(project, '.planr'),
691
747
  physicalPath: roots.get('project').planning,
@@ -731,7 +787,13 @@ export function previewContextCapsule(capsule) {
731
787
  mode: capsule.mode,
732
788
  selector: capsule.selector,
733
789
  inventory: capsule.inventory,
734
- omissions: capsule.omissions,
790
+ omissions: capsule.omissions.map((omission) => ({
791
+ ...omission,
792
+ ...OMISSION_FINDINGS.get(omission),
793
+ })),
794
+ ...(capsule.credentialResolutions
795
+ ? { credentialResolutions: capsule.credentialResolutions }
796
+ : {}),
735
797
  blockers: [],
736
798
  planning: capsule.planning ?? null,
737
799
  sourceKeys: capsule.sourceKeys,
@@ -0,0 +1,167 @@
1
+ // Credential classification for delegated context. Findings never carry the matched value.
2
+ import { createHash } from 'node:crypto';
3
+
4
+ /**
5
+ * Recognizable credential formats; rejected everywhere, including tests and placeholders.
6
+ * Case-sensitive: each prefix has a fixed case, and `github_pat_` and `sk-` bodies need an
7
+ * uppercase letter or digit.
8
+ */
9
+ export const CREDENTIAL_FORMAT =
10
+ /-----BEGIN (?:[A-Z ]* )?PRIVATE KEY(?: BLOCK)?-----|"private_key"\s*:\s*"-----BEGIN|(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}\b|(?<![A-Za-z0-9])gh[pousr]_[A-Za-z0-9]{20,}(?![A-Za-z0-9])|(?<![A-Za-z0-9])github_pat_(?=[A-Za-z0-9_]*[A-Z0-9])[A-Za-z0-9_]{20,}\b|(?<![A-Za-z0-9])sk-(?:proj-)?(?=[A-Za-z0-9_-]*[A-Z0-9])[A-Za-z0-9_-]{24,}\b|(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{20,}/u;
11
+
12
+ // `word` is the credential word findings report as `key`; the rest of a name may hold a value.
13
+ const CREDENTIAL_NAME =
14
+ '(?<name>(?:[A-Za-z_][A-Za-z0-9_-]*?)?(?<word>API[_-]?KEY|ACCESS[_-]?TOKEN|AUTH[_-]?TOKEN|CLIENT[_-]?SECRET|PRIVATE[_-]?KEY|PASSWORD|TOKEN|SECRET(?:[_-]?KEY)?))';
15
+ const QUOTED_ASSIGNMENT = new RegExp(
16
+ `(?:^|[^A-Za-z0-9_])${CREDENTIAL_NAME}["']?\\s*[:=]\\s*(?<quote>["'\`])(?<value>(?:(?!\\k<quote>|\\$\\{)[^\\r\\n\\\\]|\\\\.){16,})\\k<quote>`,
17
+ 'giu',
18
+ );
19
+ const UNQUOTED_ASSIGNMENT = new RegExp(
20
+ `^[ \\t]*(?<exported>export[ \\t]+)?${CREDENTIAL_NAME}(?<before>[ \\t]*)(?<operator>[:=])(?<after>[ \\t]*)(?!process\\.env(?:\\.|\\[)|import\\.meta\\.env(?:\\.|\\[)|Deno\\.env\\.|os\\.environ|env\\.)(?<value>[^\\s"'\`#()\\[\\]{}$]{16,})[ \\t]*(?:#[^\\r\\n]*)?\\r?$`,
21
+ 'gimu',
22
+ );
23
+ const PLACEHOLDER =
24
+ /^(?:example(?:[-_].*)?|placeholder(?:[-_].*)?|your[-_].*|(?:change|replace)[-_]me(?:[-_].*)?|(?:dummy|fake|mock|test|never[-_]return)[-_](?:api[-_]?key|access[-_]?token|auth[-_]?token|client[-_]?secret|password|token|secret(?:[-_]?key)?))$/iu;
25
+ // A lowercase marker followed only by words and short numbers, one of them a credential noun.
26
+ const DESCRIBED_PLACEHOLDER =
27
+ /^(?:dummy|fake|mock|test|fixture|synthetic|never[-_]return)(?:[-_. ](?:[a-z]+|\d{1,4}))+[-_.]?$/u;
28
+ const CREDENTIAL_NOUN = /[-_. ](?:secret|key|token|password|credential)s?(?=[-_. ]|$)/u;
29
+ const SHELL_NAME = /^[A-Z][A-Z0-9_]*$/u;
30
+ const IDENTIFIER_WORD = /[A-Z]{2,}(?![a-z])\d{0,2}|[A-Z]?[a-z]+\d{0,2}|[A-Z]\d{0,2}|\d{1,2}/gu;
31
+
32
+ const CODE_EXTENSIONS = new Set(
33
+ 'c cc cjs cpp cs cts dart go groovy h hpp java js jsx kt kts lua m mjs mm mts php py rb rs scala svelte swift ts tsx vue'.split(
34
+ ' ',
35
+ ),
36
+ );
37
+ const CONFIG_EXTENSIONS = new Set(
38
+ 'bash cfg conf env fish hcl ini json json5 jsonc properties ps1 sh tf toml xml yaml yml zsh'.split(
39
+ ' ',
40
+ ),
41
+ );
42
+ const CONFIG_NAMES = new Set(['dockerfile', 'makefile', 'procfile']);
43
+
44
+ /** The syntax that decides whether an unquoted value is a literal: code, config, or free text. */
45
+ export function credentialSyntax(path) {
46
+ if (typeof path !== 'string' || !path) return 'text';
47
+ const name = path.split('/').at(-1).toLowerCase();
48
+ if (CONFIG_NAMES.has(name) || name.startsWith('.env')) return 'config';
49
+ const extension = name.includes('.') ? name.split('.').at(-1) : '';
50
+ if (CODE_EXTENSIONS.has(extension)) return 'code';
51
+ if (CONFIG_EXTENSIONS.has(extension)) return 'config';
52
+ return 'text';
53
+ }
54
+
55
+ function sha256(value) {
56
+ return createHash('sha256').update(value).digest('hex');
57
+ }
58
+
59
+ function position(content, index) {
60
+ const before = content.slice(0, index);
61
+ return { line: before.split('\n').length, column: index - before.lastIndexOf('\n') };
62
+ }
63
+
64
+ // Reads like a code identifier: whole words, digits only at word ends, at most one one-letter word.
65
+ function codeIdentifier(value) {
66
+ if (!/^[A-Za-z_$][\w$]*$/u.test(value) || value.length > 64) return false;
67
+ let singleLetters = 0;
68
+ for (const part of value.split(/[_$]+/u).filter(Boolean)) {
69
+ const words = part.match(IDENTIFIER_WORD) ?? [];
70
+ if (words.join('') !== part) return false;
71
+ singleLetters += words.filter((word) => word.replace(/\d+$/u, '').length <= 1).length;
72
+ }
73
+ return singleLetters <= 1;
74
+ }
75
+
76
+ function memberPath(value) {
77
+ const segments = value.replace(/!$/u, '').split(/[!?]?\./u);
78
+ return segments.length > 1 && segments.every(codeIdentifier);
79
+ }
80
+
81
+ // An unquoted value is a literal unless its syntax makes it a reference, type or expression.
82
+ // Multi-line strings in code read like text, so an unterminated identifier stays a literal.
83
+ function unquotedLiteral(raw, operator, syntax) {
84
+ if (syntax === 'config' || (syntax === 'text' && operator === '=')) return true;
85
+ const terminated = /[;,]$/u.test(raw);
86
+ const value = terminated ? raw.slice(0, -1) : raw;
87
+ if (memberPath(value)) return !(terminated || syntax === 'code');
88
+ return !(terminated && codeIdentifier(value));
89
+ }
90
+
91
+ // `export NAME=` is a shell line in any file, and so is an uppercase `NAME=value` without spaces
92
+ // unless it ends in `,` like a keyword argument.
93
+ function shellAssignment({ exported, name, before, operator, after, value }) {
94
+ if (exported) return true;
95
+ return operator === '=' && !before && !after && SHELL_NAME.test(name) && !value.endsWith(',');
96
+ }
97
+
98
+ function placeholder(value) {
99
+ return (
100
+ PLACEHOLDER.test(value) || (DESCRIBED_PLACEHOLDER.test(value) && CREDENTIAL_NOUN.test(value))
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Classifies credential material in `bytes` from `origin` ({ path?, label?, syntax?, resolvable? }).
106
+ * Findings report location, rule, classification, confidence and explanation, never values.
107
+ * `resolvable: false` marks text that no resolution can admit, such as a correction.
108
+ */
109
+ export function classifyCredentials(bytes, origin = {}) {
110
+ const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(String(bytes), 'utf8');
111
+ const content = buffer.toString('utf8');
112
+ const label = origin.label ?? origin.path ?? 'text';
113
+ const syntax = origin.syntax ?? credentialSyntax(origin.path);
114
+ const resolvable = origin.resolvable !== false;
115
+ const findings = [];
116
+ const add = (rule, index, key, details) => {
117
+ const { line, column } = position(content, index);
118
+ findings.push({
119
+ id: `cred_${sha256(`${label}\n${rule}\n${line}:${column}\n${key ?? ''}`).slice(0, 16)}`,
120
+ rule,
121
+ ...details,
122
+ location: { ...(origin.path ? { path: origin.path } : {}), line, column },
123
+ ...(key ? { key } : {}),
124
+ });
125
+ };
126
+ for (const match of content.matchAll(new RegExp(CREDENTIAL_FORMAT.source, 'gu')))
127
+ add('credential-format', match.index, null, {
128
+ classification: 'credential',
129
+ confidence: 'high',
130
+ resolvable: false,
131
+ explanation:
132
+ 'A recognizable credential or private key format cannot be delegated; remove it from the source.',
133
+ });
134
+ const literal = ({ index, 0: text, groups: { name, word } }) =>
135
+ add('credential-assignment', index + text.indexOf(name), word, {
136
+ classification: 'possible-credential',
137
+ confidence: 'medium',
138
+ resolvable,
139
+ explanation: `${word} is assigned a literal that is neither a reference nor a recognized placeholder; ${resolvable ? 'resolve this finding only if the value is not a credential' : 'remove the value or replace it with a reference'}.`,
140
+ });
141
+ for (const match of content.matchAll(QUOTED_ASSIGNMENT))
142
+ if (!placeholder(match.groups.value)) literal(match);
143
+ for (const match of content.matchAll(UNQUOTED_ASSIGNMENT)) {
144
+ const { operator, value } = match.groups;
145
+ const lineSyntax = shellAssignment(match.groups) ? 'config' : syntax;
146
+ if (unquotedLiteral(value, operator, lineSyntax) && !placeholder(value.replace(/[;,]$/u, '')))
147
+ literal(match);
148
+ }
149
+ return { contentDigest: `sha256:${sha256(buffer)}`, syntax, findings };
150
+ }
151
+
152
+ /**
153
+ * Splits findings into those an explicit resolution covers and the rest. A resolution names one
154
+ * resolvable finding and the exact content digest it was recorded for; changed bytes invalidate it.
155
+ */
156
+ export function resolveFindings(classification, resolutions = []) {
157
+ const accepted = new Set(
158
+ resolutions
159
+ .filter((resolution) => resolution?.contentDigest === classification.contentDigest)
160
+ .map((resolution) => resolution.id),
161
+ );
162
+ const applied = [];
163
+ const remaining = [];
164
+ for (const finding of classification.findings)
165
+ (finding.resolvable && accepted.has(finding.id) ? applied : remaining).push(finding);
166
+ return { applied, remaining };
167
+ }
@@ -39,7 +39,7 @@ export function preparationPresentation(preview) {
39
39
  return {
40
40
  phase: 'preview',
41
41
  headline: `${taskLabel(preview.selector)} is ready for review before dispatch.`,
42
- context: `${preview.inventory.length} copied file(s); ${preview.omissions.length} omission(s).`,
42
+ context: `${preview.inventory.length} copied file(s); ${preview.omissions.length} omission(s); ${preview.credentialResolutions?.length ?? 0} accepted credential resolution(s).`,
43
43
  writableRepository: preview.writableRepository,
44
44
  worktreePath: preview.worktreePath,
45
45
  selectedPaths: preview.selectedPaths,
@@ -49,6 +49,7 @@ export function preparationPresentation(preview) {
49
49
  : 'Legacy run has no recorded integration scope; supply explicit paths at review. The worktree is not a filesystem sandbox.',
50
50
  inventory: preview.inventory,
51
51
  omissions: preview.omissions,
52
+ credentialResolutions: preview.credentialResolutions ?? [],
52
53
  blockers: preview.blockers ?? [],
53
54
  planning: preview.planning ?? null,
54
55
  helper: preview.helper ?? null,
@@ -61,7 +62,7 @@ export function preparationPresentation(preview) {
61
62
  destination: preview.destination,
62
63
  ...(preview.executionPolicy ? { executionPolicy: preview.executionPolicy } : {}),
63
64
  nextAction:
64
- 'Review required sources, optional omissions, planning location, integration scope, and destination before dispatch.',
65
+ 'Review required sources, optional omissions, accepted credential resolutions, planning location, integration scope, and destination before dispatch.',
65
66
  };
66
67
  }
67
68
 
@@ -2,6 +2,7 @@
2
2
  import { createHash } from 'node:crypto';
3
3
  import { isAbsolute, sep } from 'node:path';
4
4
  import { assertCredentialFreeText } from './context.mjs';
5
+ import { CREDENTIAL_FORMAT } from './credentials.mjs';
5
6
  import { updateRunRecord } from './run-record.mjs';
6
7
 
7
8
  const MAX_HANDOFF_TEXT = 4096;
@@ -16,9 +17,6 @@ export const SHA256 = /^[a-f0-9]{64}$/u;
16
17
 
17
18
  const RESULT_STATES = new Set(['completed', 'blocked', 'question']);
18
19
 
19
- const CREDENTIAL =
20
- /-----BEGIN (?:[A-Z ]* )?PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9_]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b|\bsk-(?:proj-)?[A-Za-z0-9_-]{24,}\b|\bxox[baprs]-[A-Za-z0-9-]{20,}/iu;
21
-
22
20
  export class DelegateRunError extends Error {
23
21
  constructor(code, message, runId = null, details = null) {
24
22
  super(message);
@@ -68,7 +66,7 @@ export function boundedText(value, label) {
68
66
  typeof value !== 'string' ||
69
67
  !value.trim() ||
70
68
  Buffer.byteLength(value, 'utf8') > MAX_HANDOFF_TEXT ||
71
- CREDENTIAL.test(value)
69
+ CREDENTIAL_FORMAT.test(value)
72
70
  ) {
73
71
  throw new DelegateRunError(
74
72
  'E_DELEGATE_INPUT',
@@ -152,7 +150,7 @@ function validQuestion(question) {
152
150
  typeof question.text !== 'string' ||
153
151
  !question.text.trim() ||
154
152
  Buffer.byteLength(question.text, 'utf8') > MAX_HANDOFF_TEXT ||
155
- CREDENTIAL.test(question.text) ||
153
+ CREDENTIAL_FORMAT.test(question.text) ||
156
154
  (question.options !== undefined &&
157
155
  (!Array.isArray(question.options) ||
158
156
  question.options.length > 12 ||
@@ -160,7 +158,7 @@ function validQuestion(question) {
160
158
  (option) =>
161
159
  typeof option !== 'string' ||
162
160
  Buffer.byteLength(option, 'utf8') > 256 ||
163
- CREDENTIAL.test(option),
161
+ CREDENTIAL_FORMAT.test(option),
164
162
  )))
165
163
  )
166
164
  return false;
@@ -181,7 +179,7 @@ export function resultShape(result, expectedSessionId = null) {
181
179
  ) {
182
180
  return null;
183
181
  }
184
- if (CREDENTIAL.test(result.summary)) return null;
182
+ if (CREDENTIAL_FORMAT.test(result.summary)) return null;
185
183
  if (result.status === 'blocked' && !result.summary.trim()) return null;
186
184
  if (result.status === 'question' && !validQuestion(result.question)) return null;
187
185
  if (
@@ -143,6 +143,7 @@ export async function prepareDelegateRun({
143
143
  selectedFiles = [],
144
144
  optionalFiles = [],
145
145
  readOnlyRepositories = [],
146
+ credentialResolutions = [],
146
147
  selectedPaths,
147
148
  scopePaths,
148
149
  preservePaths = [],
@@ -199,6 +200,7 @@ export async function prepareDelegateRun({
199
200
  selectedFiles,
200
201
  optionalFiles,
201
202
  readOnlyRepositories,
203
+ credentialResolutions,
202
204
  });
203
205
  const integrationScopePaths = scopePaths === undefined ? null : integrationPaths(scopePaths);
204
206
  let identity = profileIdentity(prepared);
@@ -14,6 +14,7 @@ import {
14
14
  import { homedir } from 'node:os';
15
15
  import { join, resolve, sep } from 'node:path';
16
16
  import { promisify } from 'node:util';
17
+ import { CREDENTIAL_FORMAT } from './credentials.mjs';
17
18
  import { captureFileState } from './custody.mjs';
18
19
 
19
20
  export const RUN_RECORD_SCHEMA_VERSION = '2.0.0';
@@ -21,8 +22,6 @@ export const MAX_RUN_RECORD_BYTES = 64 * 1024;
21
22
  export const CLOSED_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
22
23
 
23
24
  const RUN_ID = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,99}$/u;
24
- const CREDENTIAL =
25
- /-----BEGIN (?:[A-Z ]* )?PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9_]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b|\bsk-(?:proj-)?[A-Za-z0-9_-]{24,}\b|\bxox[baprs]-[A-Za-z0-9-]{20,}/iu;
26
25
 
27
26
  const execute = promisify(execFile);
28
27
 
@@ -74,7 +73,7 @@ function encoded(record) {
74
73
  if (bytes.length > MAX_RUN_RECORD_BYTES) {
75
74
  throw new RunRecordError('E_RUN_LIMIT', 'Delegated run record exceeds its private size limit.');
76
75
  }
77
- if (CREDENTIAL.test(bytes.toString('utf8'))) {
76
+ if (CREDENTIAL_FORMAT.test(bytes.toString('utf8'))) {
78
77
  throw new RunRecordError('E_RUN_CREDENTIAL', 'Credential material cannot enter a run record.');
79
78
  }
80
79
  return bytes;
@@ -44,6 +44,13 @@ Capacity and duration remain unverified without matching measurements. For a lar
44
44
  local task, select only relevant sources or split the task before a new preparation;
45
45
  never silently omit, truncate or replace a required source with a summary.
46
46
 
47
+ If `prepare` reports `E_CAPSULE_SECRET`, show its masked findings (path, line, rule,
48
+ confidence); never edit, redact or drop a required source to pass the check. For a
49
+ resolvable finding, ask whether that exact value is a credential. Only when the user
50
+ confirms it is not, prepare again with `credentialResolutions` naming that finding's
51
+ `id` and `contentDigest`. Recognizable credential formats, private keys and credential
52
+ files are never resolvable: ask the user to remove the credential from the source.
53
+
47
54
  Call `prepare` with the engine/profile, context and explicit integration scope.
48
55
  Show its engine, model selection, known provider or native-managed routing,
49
56
  trusted configuration, working directory and context inventory once before
@@ -13,7 +13,7 @@ review. Preview the exact diagram title, source revision, elements, connections,
13
13
  destination and retention before creating it. Use `--yes` only when the user has
14
14
  already authorized that publication. The stable URL and generated access token
15
15
  are separate; copy the token through the local studio's **Share diagram** dialog,
16
- never into a URL, prompt, ordinary output or repository file. Owner credentials
16
+ never into a URL, prompt, ordinary output or repository file. Owner keys
17
17
  are saved privately outside the project. The review remains available until the
18
18
  owner revokes or deletes it and works while the local studio is stopped.
19
19
 
@@ -46,9 +46,11 @@ matches. Never overwrite a prior cycle implicitly.
46
46
  ```
47
47
 
48
48
  `cycle.md` records the effective scope, roster, repository revision when relevant, and the outcome
49
- of each lens. If the directory is ignored by Git, continue locally and mention that in the report;
50
- do not ask the user to change ignore rules. Report an exact filesystem error only when the cycle
51
- workspace cannot be created or written.
49
+ of each lens. An advisor lens run on its own creates the same layout with a one-lens roster and an
50
+ unsynthesized `board-report.md`, so every review the dashboard lists has one shape.
51
+ If the directory is ignored by Git, continue locally and mention that in the report; do not ask the
52
+ user to change ignore rules. Report an exact filesystem error only when the cycle workspace cannot be
53
+ created or written.
52
54
 
53
55
  ## Build shared context once
54
56