@mnstry/atelier 0.2.0-alpha.1

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 (260) hide show
  1. package/CHANGELOG.md +383 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.md +262 -0
  5. package/SECURITY.md +93 -0
  6. package/TRADEMARKS.md +88 -0
  7. package/announcements/2026-08-06-announcements-channel.v1.json +12 -0
  8. package/announcements/keys/mnstry-announcements.public.v1.json +9 -0
  9. package/bin/atelier.mjs +2 -0
  10. package/bin/mnstry-atelier.mjs +3 -0
  11. package/contracts/analysis-adapter.v1.schema.json +248 -0
  12. package/contracts/atelier-action-intent.v1.schema.json +143 -0
  13. package/contracts/atelier-analysis-adapter.v1.schema.json +360 -0
  14. package/contracts/atelier-attestation.v1.schema.json +239 -0
  15. package/contracts/atelier-boundary-policy.v1.schema.json +339 -0
  16. package/contracts/atelier-claim.v1.schema.json +97 -0
  17. package/contracts/atelier-export.v1.schema.json +1015 -0
  18. package/contracts/atelier-extension-pack.v1.schema.json +290 -0
  19. package/contracts/atelier-kit-manifest.v1.schema.json +273 -0
  20. package/contracts/atelier-lock.v1.schema.json +364 -0
  21. package/contracts/atelier-migration.v1.schema.json +162 -0
  22. package/contracts/atelier-project-config.v1.schema.json +352 -0
  23. package/contracts/atelier-readiness-protocol.v1.schema.json +517 -0
  24. package/contracts/atelier-readiness-run.v1.schema.json +229 -0
  25. package/contracts/atelier-readiness.v1.schema.json +457 -0
  26. package/contracts/compat-baseline.json +10 -0
  27. package/contracts/git-promote-event.v1.schema.json +145 -0
  28. package/contracts/knowledge-source-sidecar.v1.schema.json +157 -0
  29. package/contracts/mnstry-atelier-semantic-profile.v1.json +254 -0
  30. package/docs/announcements.md +101 -0
  31. package/docs/atelier-runtime.md +34 -0
  32. package/docs/attestation.md +201 -0
  33. package/docs/continuity.md +55 -0
  34. package/docs/contract-stability.md +111 -0
  35. package/docs/distributions.md +220 -0
  36. package/docs/install.md +140 -0
  37. package/docs/knowledge-graph.md +140 -0
  38. package/docs/ontology.md +206 -0
  39. package/docs/release-engineering.md +151 -0
  40. package/docs/repo-boundary-guard.md +199 -0
  41. package/docs/tenant-readiness.md +64 -0
  42. package/docs/upgrade.md +124 -0
  43. package/fixtures/analysis/invalid/native-graph.v1.json +4 -0
  44. package/fixtures/analysis/invalid/promoted-claim.v1.json +17 -0
  45. package/fixtures/analysis/invalid/runtime-import.v1.json +20 -0
  46. package/fixtures/analysis/valid/output.claims.v1.json +17 -0
  47. package/fixtures/analysis-adapter/invalid/manifest-hidden-provider.v1.json +24 -0
  48. package/fixtures/analysis-adapter/invalid/output-frontmatter-mutation.v1.json +21 -0
  49. package/fixtures/analysis-adapter/invalid/output-native-graph.v1.json +14 -0
  50. package/fixtures/analysis-adapter/manifest.disabled.v1.json +22 -0
  51. package/fixtures/analysis-adapter/manifest.enabled-local.v1.json +23 -0
  52. package/fixtures/analysis-adapter/output.claims.v1.json +21 -0
  53. package/fixtures/atelier-action-intent/invalid/runtime-mutation-authority.v1.json +16 -0
  54. package/fixtures/atelier-action-intent/invalid/unknown-top-level-field.v1.json +17 -0
  55. package/fixtures/atelier-action-intent/invalid/writes-beyond-clipboard.v1.json +16 -0
  56. package/fixtures/atelier-action-intent/valid/copy-agent-prompt.v1.json +16 -0
  57. package/fixtures/atelier-action-intent/valid/copy-repo-path.v1.json +22 -0
  58. package/fixtures/atelier-analysis-adapter/invalid/hidden-model-provider.v1.json +24 -0
  59. package/fixtures/atelier-analysis-adapter/invalid/inert-with-execute-command.v1.json +24 -0
  60. package/fixtures/atelier-analysis-adapter/invalid/proposals-with-runtime-import.v1.json +30 -0
  61. package/fixtures/atelier-analysis-adapter/valid/manifest-inert.v1.json +24 -0
  62. package/fixtures/atelier-analysis-adapter/valid/proposals-claim-only.v1.json +27 -0
  63. package/fixtures/atelier-attestation/invalid/bad-digest.v1.json +23 -0
  64. package/fixtures/atelier-attestation/invalid/conformance-scope.v1.json +23 -0
  65. package/fixtures/atelier-attestation/invalid/missing-signature.v1.json +22 -0
  66. package/fixtures/atelier-attestation/invalid/unknown-root-field.v1.json +24 -0
  67. package/fixtures/atelier-attestation/keys/roundtrip-issuer.public.v1.json +9 -0
  68. package/fixtures/atelier-attestation/valid/rejected-with-reasons.v1.json +49 -0
  69. package/fixtures/atelier-attestation/valid/signed-admitted.v1.json +35 -0
  70. package/fixtures/atelier-attestation/valid/signed-roundtrip.v1.json +29 -0
  71. package/fixtures/atelier-attestation/valid/unsigned-advisory.v1.json +23 -0
  72. package/fixtures/atelier-claim/invalid/unknown-provider.v1.json +15 -0
  73. package/fixtures/atelier-claim/invalid/unknown-top-level-field.v1.json +16 -0
  74. package/fixtures/atelier-claim/invalid/wrong-schema-const.v1.json +15 -0
  75. package/fixtures/atelier-claim/valid/adapter-style-proposed-relation.v1.json +19 -0
  76. package/fixtures/atelier-claim/valid/codex-proposed-relation.v1.json +16 -0
  77. package/fixtures/atelier-claim/valid/manual-proposed-relation.v1.json +16 -0
  78. package/fixtures/atelier-claim/valid/readiness-proposed-relation.v1.json +18 -0
  79. package/fixtures/atelier-export/invalid/dirty-unforced-empty-taint.v1.json +19 -0
  80. package/fixtures/atelier-export/invalid/object-local-audience-visibility.v1.json +11 -0
  81. package/fixtures/atelier-export/invalid/open-object-class-owner.v1.json +23 -0
  82. package/fixtures/atelier-export/invalid/pricing-legacy-amount.v1.json +23 -0
  83. package/fixtures/atelier-export/invalid/public-disguised-internal-source-ref.v1.json +11 -0
  84. package/fixtures/atelier-export/invalid/public-nested-private-source-ref.v1.json +17 -0
  85. package/fixtures/atelier-export/invalid/public-resolved-private-source-ref.v1.json +13 -0
  86. package/fixtures/atelier-export/invalid/public-resolved-sensitive-source-ref.v1.json +13 -0
  87. package/fixtures/atelier-export/invalid/public-source-ref-missing-provenance.v1.json +20 -0
  88. package/fixtures/atelier-export/invalid/public-source-team-kg.json +12 -0
  89. package/fixtures/atelier-export/invalid/source-node-legacy-visibility.v1.json +21 -0
  90. package/fixtures/atelier-export/invalid/source-node-unknown-field.v1.json +18 -0
  91. package/fixtures/atelier-export/invalid/unresolved-public-source-ref.v1.json +11 -0
  92. package/fixtures/atelier-export/sample-studio-offer.v1.json +397 -0
  93. package/fixtures/atelier-extension-pack/invalid/bundled-slug-collision.v1.json +22 -0
  94. package/fixtures/atelier-extension-pack/invalid/escapes-root.v1.json +22 -0
  95. package/fixtures/atelier-extension-pack/invalid/posture-tampered.v1.json +22 -0
  96. package/fixtures/atelier-extension-pack/invalid/protocols/autonomous-authority.v1.json +118 -0
  97. package/fixtures/atelier-extension-pack/invalid/protocols/offer-map.v1.json +109 -0
  98. package/fixtures/atelier-extension-pack/invalid/redefines-runtime-term.v1.json +19 -0
  99. package/fixtures/atelier-extension-pack/invalid/wrong-namespace.v1.json +19 -0
  100. package/fixtures/atelier-extension-pack/valid/protocols/contract-gate.v1.json +109 -0
  101. package/fixtures/atelier-extension-pack/valid/sample-pack.v1.json +48 -0
  102. package/fixtures/atelier-kit-manifest/invalid/runtime-copy-without-canonical-role.v1.json +53 -0
  103. package/fixtures/atelier-kit-manifest/invalid/telemetry-send-path.v1.json +21 -0
  104. package/fixtures/atelier-kit-manifest/valid/mnstry-atelier.valid.v1.json +76 -0
  105. package/fixtures/atelier-lock/invalid/absolute-contract-path.v1.json +40 -0
  106. package/fixtures/atelier-lock/invalid/runtime-mutation-enabled.v1.json +41 -0
  107. package/fixtures/atelier-lock/invalid/unknown-top-level-field.v1.json +42 -0
  108. package/fixtures/atelier-lock/valid/extension-pack.v1.json +55 -0
  109. package/fixtures/atelier-lock/valid/minimal.v1.json +44 -0
  110. package/fixtures/atelier-migration/invalid/live-apply-mode.v1.json +21 -0
  111. package/fixtures/atelier-migration/invalid/missing-review-evidence.v1.json +20 -0
  112. package/fixtures/atelier-migration/invalid/runtime-mutation-enabled.v1.json +21 -0
  113. package/fixtures/atelier-migration/valid/minimal.v1.json +32 -0
  114. package/fixtures/atelier-project-config/invalid/absolute-repo-path.v1.json +11 -0
  115. package/fixtures/atelier-project-config/invalid/ext-not-object.v1.json +12 -0
  116. package/fixtures/atelier-project-config/invalid/external-kind-read-boundary.v1.json +12 -0
  117. package/fixtures/atelier-project-config/invalid/missing-repos-and-alignment.v1.json +7 -0
  118. package/fixtures/atelier-project-config/invalid/unknown-top-level-field.v1.json +14 -0
  119. package/fixtures/atelier-project-config/valid/alignment-scaffold.v1.json +15 -0
  120. package/fixtures/atelier-project-config/valid/ext-extension-packs.v1.json +36 -0
  121. package/fixtures/atelier-project-config/valid/repo-workspace.v1.json +23 -0
  122. package/fixtures/atelier-project-config/validator-only/ext-member-not-object.v1.json +14 -0
  123. package/fixtures/atelier-readiness/invalid/dry-run-only-false.v1.json +87 -0
  124. package/fixtures/atelier-readiness/invalid/support-bundle-send-path.v1.json +87 -0
  125. package/fixtures/atelier-readiness/invalid/unknown-top-level-field.v1.json +88 -0
  126. package/fixtures/atelier-readiness/valid/ready-workspace.v1.json +84 -0
  127. package/fixtures/boundary-policy/invalid/content-rule-exception-blanket-path.v1.json +67 -0
  128. package/fixtures/boundary-policy/invalid/invalid-actor-private-repo-mapping.v1.json +20 -0
  129. package/fixtures/boundary-policy/invalid/legacy-warning-missing-warning.v1.json +20 -0
  130. package/fixtures/boundary-policy/invalid/missing-repo-coverage.v1.json +19 -0
  131. package/fixtures/boundary-policy/invalid/unknown-key.v1.json +12 -0
  132. package/fixtures/boundary-policy/valid/content-rule-exception.v1.json +67 -0
  133. package/fixtures/boundary-policy/valid/legacy-warning.v1.json +33 -0
  134. package/fixtures/boundary-policy/valid/strict-private-shared.v1.json +40 -0
  135. package/fixtures/git-promote/invalid/provision-named-revocable.v1.json +29 -0
  136. package/fixtures/git-promote/private-to-team.v1.json +29 -0
  137. package/fixtures/knowledge-source-sidecar/invalid/legacy-visibility-field.v1.json +17 -0
  138. package/fixtures/knowledge-source-sidecar/invalid/missing-audience.v1.json +15 -0
  139. package/fixtures/knowledge-source-sidecar/invalid/unknown-relation-kind.v1.json +18 -0
  140. package/fixtures/knowledge-source-sidecar/valid/html-source.v1.json +18 -0
  141. package/fixtures/knowledge-source-sidecar/valid/pdf-evidence.v1.json +18 -0
  142. package/fixtures/projects/neutral-project/atelier.project.json +15 -0
  143. package/fixtures/projects/neutral-project/ops/config/repo-access.v1.json +12 -0
  144. package/fixtures/projects/neutral-project/project-knowledge.graph.json +69 -0
  145. package/fixtures/projects/sample-workspace/atelier.project.json +23 -0
  146. package/fixtures/projects/sample-workspace/boundary-policy.v1.json +59 -0
  147. package/fixtures/projects/sample-workspace/content/README.md +16 -0
  148. package/fixtures/projects/sample-workspace/content/source.html +5 -0
  149. package/fixtures/projects/sample-workspace/content/source.html.kg.json +15 -0
  150. package/fixtures/projects/sample-workspace/repo-access.v1.json +9 -0
  151. package/fixtures/projects/source-formats-workspace/atelier.project.json +23 -0
  152. package/fixtures/projects/source-formats-workspace/content/README.md +17 -0
  153. package/fixtures/projects/source-formats-workspace/content/data.json +7 -0
  154. package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +15 -0
  155. package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +15 -0
  156. package/fixtures/projects/source-formats-workspace/content/metrics.csv +3 -0
  157. package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +15 -0
  158. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml +4 -0
  159. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +15 -0
  160. package/fixtures/projects/source-formats-workspace/repo-access.v1.json +9 -0
  161. package/fixtures/readiness-protocols/extension-pack/invalid/protocol-missing-path.v1.json +20 -0
  162. package/fixtures/readiness-protocols/extension-pack/valid/with-protocol.v1.json +24 -0
  163. package/fixtures/readiness-protocols/protocol/invalid/missing-check-target.v1.json +103 -0
  164. package/fixtures/readiness-protocols/protocol/invalid/runtime-mutation-enabled.v1.json +107 -0
  165. package/fixtures/readiness-protocols/protocol/valid/minimal.v1.json +100 -0
  166. package/fixtures/readiness-protocols/run/invalid/missing-evidence.v1.json +37 -0
  167. package/fixtures/readiness-protocols/run/invalid/pass-with-blocker.v1.json +22 -0
  168. package/fixtures/readiness-protocols/run/invalid/runtime-mutation-enabled.v1.json +22 -0
  169. package/fixtures/readiness-protocols/run/valid/pass.v1.json +44 -0
  170. package/fixtures/support/invalid/leaky-support.v1.json +13 -0
  171. package/fixtures/support/valid/minimal-safe.v1.json +20 -0
  172. package/package.json +98 -0
  173. package/skills/claude/atelier-open/SKILL.md +68 -0
  174. package/skills/claude/mnstry-readiness/SKILL.md +41 -0
  175. package/skills/codex/atelier-open/SKILL.md +68 -0
  176. package/skills/codex/mnstry-readiness/SKILL.md +41 -0
  177. package/src/analysis/adapter.mjs +20 -0
  178. package/src/analysis/analysis-claim-contract.mjs +335 -0
  179. package/src/attestation/jcs.mjs +64 -0
  180. package/src/attestation/sign.mjs +305 -0
  181. package/src/boundary/content-rules.mjs +262 -0
  182. package/src/boundary/policy.mjs +806 -0
  183. package/src/check-atelier-export-contract.mjs +116 -0
  184. package/src/cli/run.mjs +283 -0
  185. package/src/collaboration/proposals.mjs +262 -0
  186. package/src/commands/analysis.mjs +8 -0
  187. package/src/commands/announcements.mjs +308 -0
  188. package/src/commands/attestation.mjs +198 -0
  189. package/src/commands/boundary.mjs +25 -0
  190. package/src/commands/config.mjs +4 -0
  191. package/src/commands/context.mjs +3 -0
  192. package/src/commands/distribution.mjs +224 -0
  193. package/src/commands/egress.mjs +11 -0
  194. package/src/commands/extension-pack.mjs +209 -0
  195. package/src/commands/feedback.mjs +3 -0
  196. package/src/commands/graph.mjs +3 -0
  197. package/src/commands/init.mjs +130 -0
  198. package/src/commands/lock.mjs +3 -0
  199. package/src/commands/project.mjs +3 -0
  200. package/src/commands/promote.mjs +4 -0
  201. package/src/commands/readiness.mjs +3 -0
  202. package/src/commands/server.mjs +6 -0
  203. package/src/commands/setup.mjs +250 -0
  204. package/src/commands/support.mjs +3 -0
  205. package/src/commands/upgrade.mjs +3 -0
  206. package/src/contracts/corpus.mjs +171 -0
  207. package/src/egress/check.mjs +45 -0
  208. package/src/egress/forbidden-egress.mjs +323 -0
  209. package/src/export/atelier-export-contract.mjs +722 -0
  210. package/src/extension-packs/loader.mjs +491 -0
  211. package/src/graph/graph.mjs +370 -0
  212. package/src/graph/knowledge-graph.mjs +887 -0
  213. package/src/harness/context-client.mjs +144 -0
  214. package/src/harness/context.mjs +27 -0
  215. package/src/index.mjs +88 -0
  216. package/src/project/config.mjs +557 -0
  217. package/src/project/file-class.mjs +113 -0
  218. package/src/project/git-ignore.mjs +31 -0
  219. package/src/project/path-match.mjs +30 -0
  220. package/src/project/repo-identity.mjs +176 -0
  221. package/src/projection/alignment-projection.mjs +169 -0
  222. package/src/projection/policy.mjs +90 -0
  223. package/src/projection/project.mjs +152 -0
  224. package/src/readiness/readiness.mjs +312 -0
  225. package/src/readiness-protocols/bundled-pack.mjs +766 -0
  226. package/src/readiness-protocols/runtime.mjs +419 -0
  227. package/src/server/local-sidecar.mjs +640 -0
  228. package/src/server/security.mjs +229 -0
  229. package/src/server/server.mjs +42 -0
  230. package/src/support/feedback-report.mjs +414 -0
  231. package/src/support/support-bundle.mjs +226 -0
  232. package/src/ui/html-primitives.mjs +104 -0
  233. package/src/upgrade/upgrade.mjs +701 -0
  234. package/src/validate-atelier-export-dry-run.mjs +75 -0
  235. package/templates/atelier.project.example.json +23 -0
  236. package/templates/distribution-workspace/.claude/launch.json +12 -0
  237. package/templates/distribution-workspace/README.md +86 -0
  238. package/templates/distribution-workspace/atelier.project.json +36 -0
  239. package/templates/distribution-workspace/boundary-policy.v1.json +61 -0
  240. package/templates/distribution-workspace/gitignore +29 -0
  241. package/templates/distribution-workspace/governance/repo-boundary-ledger.md +5 -0
  242. package/templates/distribution-workspace/project/README.md +19 -0
  243. package/templates/distribution-workspace/repo-access.v1.json +9 -0
  244. package/templates/launch.json +12 -0
  245. package/templates/private-domain-workspace/.claude/launch.json +12 -0
  246. package/templates/private-domain-workspace/README.md +50 -0
  247. package/templates/private-domain-workspace/atelier.project.json +28 -0
  248. package/templates/private-domain-workspace/boundary-policy.v1.json +54 -0
  249. package/templates/private-domain-workspace/domain/README.md +19 -0
  250. package/templates/private-domain-workspace/gitignore +29 -0
  251. package/templates/private-domain-workspace/governance/repo-boundary-ledger.md +4 -0
  252. package/templates/private-domain-workspace/repo-access.v1.json +9 -0
  253. package/templates/shared-project-workspace/.claude/launch.json +12 -0
  254. package/templates/shared-project-workspace/README.md +51 -0
  255. package/templates/shared-project-workspace/atelier.project.json +28 -0
  256. package/templates/shared-project-workspace/boundary-policy.v1.json +61 -0
  257. package/templates/shared-project-workspace/gitignore +29 -0
  258. package/templates/shared-project-workspace/governance/repo-boundary-ledger.md +5 -0
  259. package/templates/shared-project-workspace/project/README.md +19 -0
  260. package/templates/shared-project-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto'
4
+ import fs from 'node:fs'
5
+ import path from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { resolveProjectConfig } from '../project/config.mjs'
8
+
9
+ export const SUPPORT_BUNDLE_PREVIEW_SCHEMA = 'mnstry.atelier-support-bundle-preview@v1'
10
+
11
+ export const DEFAULT_SUPPORT_FIELDS = [
12
+ 'nodeVersion',
13
+ 'platform',
14
+ 'packageVersion',
15
+ 'command',
16
+ 'localErrorSummary',
17
+ ]
18
+
19
+ export const BANNED_KEY_PATTERNS = [
20
+ /prompt/i,
21
+ /secret/i,
22
+ /token/i,
23
+ /password/i,
24
+ /private/i,
25
+ /email/i,
26
+ /remote/i,
27
+ /url/i,
28
+ ]
29
+
30
+ export const BANNED_VALUE_PATTERNS = [
31
+ { type: 'absolute-path', re: /(?:^|[\s"'])\/(?:Users|home)\/[^/\s"']+\/[^\s"']+/ },
32
+ { type: 'repo-file-path', re: /(?:^|[\s"'])(?:[a-z0-9._-]+)\/(?:APP|app|src|docs|private|secrets?)\/[^\s"']+/i },
33
+ { type: 'kg-node-id', re: /(?:^|[\s"'])[a-z][a-z0-9._-]*:[a-z0-9._:-]*[a-z][a-z0-9._:-]*/i },
34
+ { type: 'git-remote', re: /\b(?:git@|https:\/\/github\.com\/)[^\s"']+/i },
35
+ // Bounded on purpose. The unbounded local-part and domain runs backtracked
36
+ // catastrophically on large non-email input: a few hundred KB of
37
+ // address-shaped characters with no "@" took about a minute to reject, which
38
+ // would turn any capped file attachment into a stall. RFC 5321 caps the
39
+ // local part at 64 octets and the domain at 255, so every real address
40
+ // still matches.
41
+ { type: 'email', re: /\b[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,255}\.[A-Z]{2,}\b/i },
42
+ { type: 'hostname-or-url', re: /\b(?:https?:\/\/|wss?:\/\/)(?!localhost\b|127\.0\.0\.1\b|\[?::1\]?)[^\s"']+/i },
43
+ { type: 'secret', re: /\b(?:sk|pk|ghp|gho|pat|xoxb|AKIA)[-_A-Za-z0-9]{12,}\b/ },
44
+ // Every PEM private-key header, not only the bare PKCS#8 one. The previous
45
+ // form allowed exactly one word between BEGIN and KEY, so an ssh-keygen
46
+ // default key (OPENSSH PRIVATE KEY) and the RSA/EC/DSA/ENCRYPTED and PGP
47
+ // "PRIVATE KEY BLOCK" headers all passed the scan untouched. Kept identical
48
+ // to the structural pattern in scripts/structural-patterns.mjs.
49
+ // The literal must not match itself: this file ships in the tarball and
50
+ // release:audit scans it with that structural pattern.
51
+ // Keys in this kit are JWK, not PEM: the PEM patterns above never saw the
52
+ // one key format atelier itself writes. Both patterns match key material
53
+ // rather than prose about it — a document may discuss privateKeyJwk, and
54
+ // 40 is the floor for a real scalar (Ed25519 and P-256 are both 43
55
+ // base64url characters), so shorter identifiers under a member named d
56
+ // do not fire, and a redacted example in documentation stays attachable. Escaped JSON evades text patterns, which is why a context
57
+ // file that parses as JSON is walked structurally as well.
58
+ { type: 'jwk-private-key', re: /"d"\s*:\s*"[A-Za-z0-9_-]{40,}"/ },
59
+ { type: 'jwk-private-key-document', re: /"privateKeyJwk"\s*:\s*\{[^{}]*"d"\s*:\s*"[A-Za-z0-9_-]{40,}"/ },
60
+ { type: 'private-key', re: /BEGIN (?:[A-Z0-9]+ ){0,4}PRIVATE KEY(?: BLOCK)?|BEGIN (?:RSA|OPENSSH) KEY/ },
61
+ ]
62
+
63
+ function stable(value) {
64
+ if (Array.isArray(value)) return value.map(stable)
65
+ if (value && typeof value === 'object') {
66
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]))
67
+ }
68
+ return value
69
+ }
70
+
71
+ function hashPayload(payload) {
72
+ const clone = { ...payload }
73
+ delete clone.hash
74
+ return crypto.createHash('sha256').update(JSON.stringify(stable(clone))).digest('hex')
75
+ }
76
+
77
+ function count(value) {
78
+ return Array.isArray(value) ? value.length : 0
79
+ }
80
+
81
+ function visit(value, fn, pathParts = []) {
82
+ fn(value, pathParts)
83
+ if (Array.isArray(value)) {
84
+ value.forEach((item, index) => visit(item, fn, [...pathParts, String(index)]))
85
+ } else if (value && typeof value === 'object') {
86
+ for (const [key, item] of Object.entries(value)) {
87
+ visit(item, fn, [...pathParts, key])
88
+ }
89
+ }
90
+ }
91
+
92
+ export function validateSupportBundlePayload(value) {
93
+ const errors = []
94
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
95
+ return ['support bundle payload must be an object']
96
+ }
97
+ if (value.schema !== SUPPORT_BUNDLE_PREVIEW_SCHEMA) {
98
+ errors.push(`schema must be ${SUPPORT_BUNDLE_PREVIEW_SCHEMA}`)
99
+ }
100
+ if (value.sendPath !== false) errors.push('sendPath must be false')
101
+ if (value.background !== false && value.background != null) errors.push('background must be false when present')
102
+ if (value.telemetry && value.telemetry !== 'none') errors.push('telemetry must be none')
103
+
104
+ visit(value, (item, parts) => {
105
+ const key = parts.at(-1) || ''
106
+ if (key && BANNED_KEY_PATTERNS.some((pattern) => pattern.test(key))) {
107
+ errors.push(`banned key ${parts.join('.')}`)
108
+ }
109
+ if (typeof item === 'string') {
110
+ for (const { type, re } of BANNED_VALUE_PATTERNS) {
111
+ if (re.test(item)) errors.push(`banned value ${type} at ${parts.join('.') || '<root>'}`)
112
+ }
113
+ }
114
+ })
115
+ return errors
116
+ }
117
+
118
+ export function buildSupportBundlePreview({
119
+ state = 'support_bundle',
120
+ fields = DEFAULT_SUPPORT_FIELDS,
121
+ artifacts = null,
122
+ generatedAt = new Date(0).toISOString(),
123
+ } = {}) {
124
+ const payload = {
125
+ schema: SUPPORT_BUNDLE_PREVIEW_SCHEMA,
126
+ state,
127
+ telemetry: 'none',
128
+ sendPath: false,
129
+ background: false,
130
+ generatedAt,
131
+ fields: [...fields],
132
+ artifacts: state === 'none' ? [] : (artifacts || [
133
+ {
134
+ kind: 'local-diagnostic',
135
+ label: 'operator-reviewed-summary',
136
+ count: count(fields),
137
+ },
138
+ ]),
139
+ }
140
+ return {
141
+ ...payload,
142
+ hash: hashPayload(payload),
143
+ }
144
+ }
145
+
146
+ export function supportBundlePreview(project = null) {
147
+ const projectShape = project ? ['schema', 'source', 'repos.length'] : DEFAULT_SUPPORT_FIELDS
148
+ return buildSupportBundlePreview({
149
+ state: 'support_bundle',
150
+ fields: projectShape,
151
+ artifacts: [
152
+ {
153
+ kind: 'local-diagnostic',
154
+ label: project ? 'project-config-shape' : 'operator-reviewed-summary',
155
+ count: count(projectShape),
156
+ },
157
+ ],
158
+ })
159
+ }
160
+
161
+ export function readSupportBundleJson(file) {
162
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
163
+ }
164
+
165
+ export function writeOrPrintSupportBundle(payload, out = null) {
166
+ const text = `${JSON.stringify(payload, null, 2)}\n`
167
+ if (!out) {
168
+ process.stdout.write(text)
169
+ return
170
+ }
171
+ fs.mkdirSync(path.dirname(out), { recursive: true })
172
+ fs.writeFileSync(out, text, { mode: 0o600 })
173
+ }
174
+
175
+ export function parseSupportBundleArgs(argv = process.argv.slice(2)) {
176
+ const args = { state: 'support_bundle', out: null, projectArgs: [] }
177
+ for (const arg of argv) {
178
+ if (arg === '--none') args.state = 'none'
179
+ else if (arg.startsWith('--state=')) args.state = arg.slice('--state='.length)
180
+ else if (arg.startsWith('--out=')) args.out = arg.slice('--out='.length)
181
+ else if (arg === '--help' || arg === '-h') args.help = true
182
+ else args.projectArgs.push(arg)
183
+ }
184
+ return args
185
+ }
186
+
187
+ function usage() {
188
+ return [
189
+ 'Usage: node src/support/support-bundle.mjs [--none|--state=support_bundle] [--out=FILE] [--project-config=FILE]',
190
+ '',
191
+ 'Builds a local preview only. It never sends or uploads support data.',
192
+ ].join('\n')
193
+ }
194
+
195
+ export function runSupportCommand(argv = process.argv.slice(2)) {
196
+ const args = parseSupportBundleArgs(argv)
197
+ if (args.help) {
198
+ console.log(usage())
199
+ return 0
200
+ }
201
+ const project = resolveProjectConfig({ argv: args.projectArgs })
202
+ const payload = args.state === 'none'
203
+ ? buildSupportBundlePreview({ state: 'none' })
204
+ : supportBundlePreview(project)
205
+ const errors = validateSupportBundlePayload(payload)
206
+ if (errors.length) {
207
+ for (const error of errors) console.error(`[atelier-support-bundle] ${error}`)
208
+ return 1
209
+ }
210
+ writeOrPrintSupportBundle(payload, args.out)
211
+ return 0
212
+ }
213
+
214
+ function main() {
215
+ try {
216
+ process.exitCode = runSupportCommand()
217
+ } catch (error) {
218
+ console.error(`[atelier-support-bundle] ${error instanceof Error ? error.message : String(error)}`)
219
+ process.exitCode = 1
220
+ }
221
+ }
222
+
223
+ const currentFile = fileURLToPath(import.meta.url)
224
+ if (process.argv[1] && path.resolve(process.argv[1]) === currentFile) {
225
+ main()
226
+ }
@@ -0,0 +1,104 @@
1
+ export function htmlEscape(value) {
2
+ return String(value ?? '')
3
+ .replaceAll('&', '&amp;')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;')
6
+ .replaceAll('"', '&quot;')
7
+ .replaceAll("'", '&#39;')
8
+ }
9
+
10
+ export function htmlAttr(name, value) {
11
+ if (value == null || value === false) return ''
12
+ if (value === true) return ` ${name}`
13
+ return ` ${name}="${htmlEscape(value)}"`
14
+ }
15
+
16
+ export function htmlAttrs(attrs = {}) {
17
+ return Object.entries(attrs).map(([name, value]) => htmlAttr(name, value)).join('')
18
+ }
19
+
20
+ export function renderPageShell({ title = 'MNSTRY Atelier', body = '' } = {}) {
21
+ return [
22
+ '<!doctype html>',
23
+ '<html lang="en">',
24
+ '<head>',
25
+ '<meta charset="utf-8">',
26
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
27
+ `<title>${htmlEscape(title)}</title>`,
28
+ '<style>',
29
+ ':root{color-scheme:light dark;font-family:Inter,ui-sans-serif,system-ui,sans-serif}',
30
+ 'body{margin:0;padding:32px;background:#f7f7f4;color:#181816}',
31
+ 'main{max-width:980px;margin:0 auto}',
32
+ 'a{color:#245f73}',
33
+ '.list{display:grid;gap:12px;margin-top:24px}',
34
+ '.item{border:1px solid #d8d8d0;border-radius:8px;padding:16px;background:#fff}',
35
+ '.meta{color:#666;font-size:13px}',
36
+ 'pre{white-space:pre-wrap;background:#1f2424;color:#f5f5ee;border-radius:8px;padding:16px;overflow:auto}',
37
+ '.notice{border-left:4px solid #245f73;padding:12px 16px;background:#edf6f7}',
38
+ '</style>',
39
+ '</head>',
40
+ '<body>',
41
+ '<main>',
42
+ body,
43
+ '</main>',
44
+ '</body>',
45
+ '</html>',
46
+ ].join('')
47
+ }
48
+
49
+ export function renderProposalListPageHtml(records = []) {
50
+ const items = records.length
51
+ ? records.map((record) => {
52
+ const proposal = record.proposal || {}
53
+ return [
54
+ '<article class="item">',
55
+ `<h2><a href="/proposals/${htmlEscape(proposal.id)}">${htmlEscape(proposal.id)}</a></h2>`,
56
+ `<p class="meta">${htmlEscape(proposal.status || 'unknown')} · ${htmlEscape(proposal.path || '')}</p>`,
57
+ `<p>${htmlEscape(proposal.action || '')}</p>`,
58
+ '</article>',
59
+ ].join('')
60
+ }).join('')
61
+ : '<p class="meta">No local proposals.</p>'
62
+
63
+ return renderPageShell({
64
+ title: 'Atelier Proposals',
65
+ body: [
66
+ '<h1>Atelier Proposals</h1>',
67
+ '<p class="notice">Review records are local handoffs. No source writes or browser apply endpoint are exposed.</p>',
68
+ `<section class="list">${items}</section>`,
69
+ ].join(''),
70
+ })
71
+ }
72
+
73
+ export function renderProposalDetailPageHtml(record) {
74
+ if (!record) {
75
+ return renderPageShell({
76
+ title: 'Proposal Not Found',
77
+ body: '<h1>Proposal Not Found</h1>',
78
+ })
79
+ }
80
+ const proposal = record.proposal || {}
81
+ const copyable = record.copyable || null
82
+ const copyBlock = copyable
83
+ ? [
84
+ '<h2>Copy Handoff</h2>',
85
+ '<p class="notice">Accepted Atelier proposal output is copy-only. There is no browser apply endpoint.</p>',
86
+ `<pre>${htmlEscape(copyable.agentInstructions || '')}</pre>`,
87
+ copyable.diff ? `<pre>${htmlEscape(copyable.diff)}</pre>` : '',
88
+ ].join('')
89
+ : ''
90
+
91
+ return renderPageShell({
92
+ title: `Review ${proposal.id || 'Proposal'}`,
93
+ body: [
94
+ `<h1>Review ${htmlEscape(proposal.id || 'Proposal')}</h1>`,
95
+ `<p class="meta">${htmlEscape(proposal.status || 'unknown')} · ${htmlEscape(proposal.path || '')}</p>`,
96
+ '<p class="notice">No source writes or apply endpoint are available from this review page.</p>',
97
+ '<h2>Proposal</h2>',
98
+ `<pre>${htmlEscape(JSON.stringify(proposal, null, 2))}</pre>`,
99
+ record.diff ? '<h2>Diff</h2>' : '',
100
+ record.diff ? `<pre>${htmlEscape(record.diff)}</pre>` : '',
101
+ copyBlock,
102
+ ].join(''),
103
+ })
104
+ }