@bongos/core 1.20.40 → 1.20.42

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 (74) hide show
  1. package/.bongos-core.json +265 -235
  2. package/.claude/skills/design/SKILL.md +6 -5
  3. package/docs/adr/0081-tool-agnostic-design-layer.md +1 -1
  4. package/docs/adr/0198-third-party-skill-vendoring-policy.md +2 -2
  5. package/docs/architecture.md +1 -1
  6. package/docs/file-map.md +51 -54
  7. package/docs/module-api-changelog.md +4 -0
  8. package/docs/modules-contract.md +7 -5
  9. package/docs/onboarding/slash-commands.md +1 -1
  10. package/modules/design-styles/CLAUDE.md +15 -0
  11. package/modules/design-styles/module.json +12 -0
  12. package/modules/{ui-design → design-styles}/skills/PREAMBLE.md +2 -2
  13. package/modules/{ui-design → design-styles}/skills/README.md +6 -6
  14. package/modules/{ui-design → design-styles}/skills/brandkit/SKILL.md +2 -2
  15. package/modules/{ui-design → design-styles}/skills/design-taste-frontend/SKILL.md +2 -2
  16. package/modules/{ui-design → design-styles}/skills/gpt-taste/SKILL.md +2 -2
  17. package/modules/{ui-design → design-styles}/skills/high-end-visual-design/SKILL.md +2 -2
  18. package/modules/{ui-design → design-styles}/skills/image-to-code/SKILL.md +2 -2
  19. package/modules/{ui-design → design-styles}/skills/imagegen-frontend-mobile/SKILL.md +2 -2
  20. package/modules/{ui-design → design-styles}/skills/imagegen-frontend-web/SKILL.md +2 -2
  21. package/modules/{ui-design → design-styles}/skills/impeccable/SKILL.md +3 -3
  22. package/modules/{ui-design → design-styles}/skills/industrial-brutalist-ui/SKILL.md +2 -2
  23. package/modules/{ui-design → design-styles}/skills/minimalist-ui/SKILL.md +2 -2
  24. package/modules/{ui-design → design-styles}/skills/policy.json +1 -1
  25. package/modules/{ui-design → design-styles}/skills/redesign-existing-projects/SKILL.md +2 -2
  26. package/modules/{ui-design → design-styles}/skills/stitch-design-taste/SKILL.md +2 -2
  27. package/modules/{ui-design → design-styles}/skills/style/SKILL.md +2 -2
  28. package/modules/pixel-art/CLAUDE.md +15 -0
  29. package/modules/pixel-art/module.json +12 -0
  30. package/modules/provisioning/starter-bundles.js +15 -0
  31. package/modules/ui-design/kit/serve.js +2 -0
  32. package/modules/ui-design/module.json +3 -3
  33. package/package-lock.json +2 -2
  34. package/package.json +1 -1
  35. package/release-notes.json +12 -0
  36. package/scripts/gds/doc-cli-guard.js +1 -1
  37. package/scripts/gds/fitness-ratchets.js +4 -1
  38. package/scripts/gds/fitness.js +2 -1
  39. package/scripts/gds/module-artifact.js +1 -1
  40. package/scripts/gds/module-assess-security.js +251 -0
  41. package/scripts/gds/publish-manifest.js +2 -0
  42. package/scripts/gds/skill-lint.js +30 -8
  43. package/src/bongos/module-scope-map.js +8 -2
  44. package/src/module-api.js +1 -1
  45. package/tests/claude_materialize.mjs +9 -8
  46. package/tests/module_assess_security.mjs +216 -0
  47. package/tests/module_loader.mjs +1 -1
  48. package/tests/opt_in_skill_modules.mjs +165 -0
  49. package/tests/skill_lint.mjs +26 -2
  50. package/tests/ui_design_skills.mjs +29 -17
  51. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +0 -135
  52. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/adapt.md +0 -0
  53. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/animate.md +0 -0
  54. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/audit.md +0 -0
  55. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/bolder.md +0 -0
  56. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/clarify.md +0 -0
  57. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/colorize.md +0 -0
  58. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/critique.md +0 -0
  59. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/distill.md +0 -0
  60. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/document.md +0 -0
  61. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/extract.md +0 -0
  62. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/harden.md +0 -0
  63. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/layout.md +0 -0
  64. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/onboard.md +0 -0
  65. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/optimize.md +0 -0
  66. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/polish.md +0 -0
  67. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/quieter.md +0 -0
  68. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/shape.md +0 -0
  69. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/typeset.md +0 -0
  70. /package/{.claude → modules/pixel-art}/skills/otb-character-review/SKILL.md +0 -0
  71. /package/{.claude → modules/pixel-art}/skills/otb-design-review/SKILL.md +0 -0
  72. /package/{.claude → modules/pixel-art}/skills/otb-feedback-capture/SKILL.md +0 -0
  73. /package/{.claude → modules/pixel-art}/skills/otb-figma-sync/SKILL.md +0 -0
  74. /package/{.claude → modules/pixel-art}/skills/otb-tile-generate/SKILL.md +0 -0
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. It builds the page and never generates images itself.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Paid: each image is made on the project's image key. It adds the picture files to the project.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Paid: each image is made on the project's image key. It adds the picture files to the project.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. A review changes nothing; a fix changes the page.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -65,7 +65,7 @@ The full list, grouped:
65
65
 
66
66
  The setup step is the preamble's world load, done **once per session** and not repeated: the pack, the tokens, the root `DESIGN.md`, and `docs/project-context.template.md` for product truth. The directives that come back are the world's own named rules and its Do's and Don'ts; quote them from the files, never from memory. Then, before editing anything, inspect the target **and at least one source of existing visual truth** for it: the sheet the page loads, a sibling component, the page's states file, or a plate under the instance's assets. A surface is edited only after both have been read.
67
67
 
68
- This skill lives at `modules/ui-design/skills/impeccable/` in the core and at `.claude/skills/impeccable/` once materialised into an instance; the playbooks are always `reference/<name>.md` relative to this file. If a harness has put the file somewhere else, resolve the playbooks relative to wherever this file was read from, and fall back to the module path.
68
+ This skill lives at `modules/design-styles/skills/impeccable/` in the core and at `.claude/skills/impeccable/` once materialised into an instance; the playbooks are always `reference/<name>.md` relative to this file. If a harness has put the file somewhere else, resolve the playbooks relative to wherever this file was read from, and fall back to the module path.
69
69
 
70
70
  ## The three homes, and what they are here
71
71
 
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. It changes the page, and you check it on a preview.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. It changes the page, and you check it on a preview.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -1,5 +1,5 @@
1
1
  {
2
- "_doc": "The vendoring policy for third-party UI skills in the ui-design module (task 1003321, ADR 0198). tests/ui_design_skills.mjs enforces it; modules/ui-design/skills/README.md explains it. A skill lands under skills/vendor/<name>/ only with a PROVENANCE.md sidecar, a licence on the allowlist, a /scan-before-install verdict that is not dangerous, and the PLATFORM PREAMBLE block (PREAMBLE.md, byte-identical) at the top of its SKILL.md body. A dangerous verdict or a licence off the allowlist lands a rebuild spec, never a copy.",
2
+ "_doc": "The vendoring policy for third-party UI skills in the design-styles module (task 1003321, ADR 0198; moved out of ui-design by task 1004470). tests/ui_design_skills.mjs enforces it; modules/design-styles/skills/README.md explains it. A skill lands under skills/vendor/<name>/ only with a PROVENANCE.md sidecar, a licence on the allowlist, a /scan-before-install verdict that is not dangerous, and the PLATFORM PREAMBLE block (PREAMBLE.md, byte-identical) at the top of its SKILL.md body. A dangerous verdict or a licence off the allowlist lands a rebuild spec, never a copy.",
3
3
  "licenceAllowlist": ["Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause", "CC-BY-4.0"],
4
4
  "verdictsThatMayLand": ["clean", "caution"],
5
5
  "sidecar": "PROVENANCE.md",
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. It changes the existing pages, and you check them on a preview.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. It writes one brief document and changes no pages.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -13,8 +13,8 @@ cost: >-
13
13
  Uses your session. The new look is only used once you choose to adopt it.
14
14
  ---
15
15
 
16
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
- > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
16
+ <!-- BEGIN PLATFORM PREAMBLE · design-styles module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/design-styles/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
17
+ > **Platform preamble — read before any rule below.** This skill ships with the `design-styles` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
18
18
  >
19
19
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
20
20
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._
@@ -0,0 +1,12 @@
1
+ {
2
+ "key": "pixel-art",
3
+ "title": "Pixel-art skills (tile generation, tile and animation review, feedback capture, the Figma tile canvas)",
4
+ "description": "The five pixel-art SKILLS an instance with a pixel-art world turns on: otb-tile-generate, otb-design-review, otb-character-review, otb-feedback-capture and otb-figma-sync (task 1004470, owner ruling on blocker 1000139). They were orphans in the core's .claude/skills/: docs attributed them to art-pipeline / character-anim, neither of which has a module.json (ADR 0272), and modules/art-pipeline/ never publishes because it carries copyrighted reference maps (ADR 0098). This module carries ONLY the skill text, never a reference map, a palette, a rubric or the generation pipeline — those stay with the instance's own art-pipeline host module, which the skills read at their documented paths. default: false because most instances have no pixel-art world, and every skill description is resident in every session whether it is used or not. Declaration-only: no routes, no migrations, no port, so turning it off withdraws only these skills.",
5
+ "version": "1.0.0",
6
+ "coreVersion": "^1.19.0",
7
+ "default": false,
8
+ "maintenance": { "status": "core-maintained" },
9
+ "contributes": { "skills": ["otb-tile-generate", "otb-design-review", "otb-character-review", "otb-feedback-capture", "otb-figma-sync"] },
10
+ "provides": [],
11
+ "consumes": []
12
+ }
@@ -124,6 +124,21 @@ const OPTIONAL_MODULES = Object.freeze([
124
124
  summary: 'A Discord server that carries your build news, and lets people send ideas and bug reports back.',
125
125
  aliases: Object.freeze(['chatroom', 'talk', 'messaging', 'community', 'announcements', 'notifications']),
126
126
  }),
127
+ // The two opt-in SKILL modules of task 1004470: no routes and no page, only extra
128
+ // commands for the AI. Off by default because every command's description costs
129
+ // context in every session, used or not.
130
+ Object.freeze({
131
+ key: 'design-styles',
132
+ label: 'Design styles',
133
+ summary: 'Extra ways for the AI to style your pages: ready-made looks, a polish pass, and a way to author your own look.',
134
+ aliases: Object.freeze(['theme', 'branding', 'visual', 'taste', 'aesthetic', 'layout']),
135
+ }),
136
+ Object.freeze({
137
+ key: 'pixel-art',
138
+ label: 'Pixel-art tools',
139
+ summary: 'Commands to generate game tiles and sprites, score them against your art rules, and keep a style guide.',
140
+ aliases: Object.freeze(['tileset', 'graphics', 'artwork', 'retro', 'drawing', 'gamedev']),
141
+ }),
127
142
  ]);
128
143
 
129
144
  // The opt-in keys alone, for the membership tests the normalizer runs. Derived,
@@ -130,6 +130,8 @@ const STARTER_BUNDLES_FIXTURE = {
130
130
  optional: [
131
131
  { key: 'agents', label: 'Extra agents', summary: 'More AI agents alongside the ones every project gets, for reading, research and review.', aliases: ['bots', 'assistants', 'automation', 'llm', 'helpers'] },
132
132
  { key: 'discord', label: 'Discord', summary: 'A Discord server that carries your build news, and lets people send ideas and bug reports back.', aliases: ['chatroom', 'talk', 'messaging', 'community', 'announcements', 'notifications'] },
133
+ { key: 'design-styles', label: 'Design styles', summary: 'Extra ways for the AI to style your pages: ready-made looks, a polish pass, and a way to author your own look.', aliases: ['theme', 'branding', 'visual', 'taste', 'aesthetic', 'layout'] },
134
+ { key: 'pixel-art', label: 'Pixel-art tools', summary: 'Commands to generate game tiles and sprites, score them against your art rules, and keep a style guide.', aliases: ['tileset', 'graphics', 'artwork', 'retro', 'drawing', 'gamedev'] },
133
135
  ],
134
136
  };
135
137
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "key": "ui-design",
3
- "title": "UI design (the design contract, its adapters and sync, the /design playbook, the kit, the skills, the styles)",
4
- "description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; and the reserved homes for the look-before-you-ship kit (kit/), the vetted UI skills (skills/) and the style library (styles/) the sibling tasks land. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port: declaration-only like character-anim, so disabling it withdraws only the skills it contributes and touches no served surface.",
3
+ "title": "UI design (the design contract, its adapters and sync, the /design playbook, the kit, the styles)",
4
+ "description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; the look-before-you-ship kit (kit/) and the style library (styles/). The thirteen design-style skills that lived in skills/ are the opt-in design-styles module since task 1004470; /design reaches them when that module is on. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port: declaration-only like character-anim, so disabling it withdraws only the skills it contributes and touches no served surface.",
5
5
  "version": "1.0.0",
6
6
  "coreVersion": "^1.19.0",
7
7
  "default": true,
8
8
  "maintenance": { "status": "core-maintained" },
9
- "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "style", "minimalist-ui", "industrial-brutalist-ui", "gpt-taste", "stitch-design-taste", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"], "disciplines": ["ui"] },
9
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync"], "disciplines": ["ui"] },
10
10
  "declarativeSeams": { "skills": ["design", "design-sync", "figma-design-sync"] },
11
11
  "provides": [],
12
12
  "consumes": []
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.20.40",
3
+ "version": "1.20.42",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.20.40",
9
+ "version": "1.20.42",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.20.40",
3
+ "version": "1.20.42",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -8349,5 +8349,17 @@
8349
8349
  "id": "1004466",
8350
8350
  "text": "Stop the version preview from overwriting the live hall's cookies, and make leaving a preview reliably clear it."
8351
8351
  }
8352
+ ],
8353
+ "1.20.41": [
8354
+ {
8355
+ "id": "1004470",
8356
+ "text": "Projects that don't do pixel art or design styling no longer carry 18 unused AI commands in every session (about a third less to load); projects that want them turn them on with one setting."
8357
+ }
8358
+ ],
8359
+ "1.20.42": [
8360
+ {
8361
+ "id": "1003792",
8362
+ "text": "The module store can now run a security check on each module version: it fails a version that ships a secret file, pulls code from outside the official package registry, or depends on a package with a known serious vulnerabili"
8363
+ }
8352
8364
  ]
8353
8365
  }
@@ -39,7 +39,7 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..');
39
39
  const DOC_CLI_ALLOW = new Map([
40
40
  ['.claude/skills/paint/SKILL.md::scripts/gds/art-stage.js',
41
41
  'art-stage.js ships with the art-pipeline HOST module, which publish-manifest.js excludes from core — absent from a core-only checkout by design; the skill says so inline.'],
42
- ['.claude/skills/otb-tile-generate/SKILL.md::scripts/gds/art-stage.js',
42
+ ['modules/pixel-art/skills/otb-tile-generate/SKILL.md::scripts/gds/art-stage.js',
43
43
  'same as paint/SKILL.md — the art staging CLI travels with the excluded art-pipeline module, not the portable core.'],
44
44
  ['docs/session-logs/2026-09-10-task-1003507-the-box-that-could-not-be-reaped.md::scripts/gds/box.js',
45
45
  'a dated session log — the historical record, never rewritten — of work on a CLI the dev-box removal deleted (task 1003893); it describes what was run then, not what to run now.'],
@@ -421,7 +421,10 @@ function apiAliasCallerMetrics() {
421
421
  function skillListingMetrics() {
422
422
  try {
423
423
  const lint = require('./skill-lint.js');
424
- return { skill_listing_chars: lint.lintSkills(lint.listSkillFiles()).listingChars };
424
+ // Resident skills only (task 1004470): a skill owned by a module this checkout leaves
425
+ // off (pixel-art, design-styles — both default: false) is in no session's listing here.
426
+ const files = lint.listSkillFiles();
427
+ return { skill_listing_chars: lint.lintSkills(files, { resident: lint.residentSkillFiles(undefined, { files }) }).listingChars };
425
428
  } catch (e) {
426
429
  // Fail open, but never SILENTLY: an unenforced ratchet that says nothing is
427
430
  // indistinguishable from a passing one, which is how a budget stops being a
@@ -1198,7 +1198,8 @@ function checkSkillFrontmatter() {
1198
1198
  return { name: 'skill frontmatter parses (task 1003548)', ok: true, hardFail: false, violations, warnings: [`skill-lint unavailable: ${e.message}`], note: 'linter missing — check skipped.' };
1199
1199
  }
1200
1200
  const files = lint.listSkillFiles(INSTANCE_ROOT);
1201
- const report = lint.lintSkills(files);
1201
+ // Lint every file; count the listing over the skills RESIDENT here (task 1004470).
1202
+ const report = lint.lintSkills(files, { resident: lint.residentSkillFiles(INSTANCE_ROOT, { files }) });
1202
1203
  const rel = (f) => path.relative(INSTANCE_ROOT, f).replace(/\\/g, '/');
1203
1204
  const long = [];
1204
1205
  for (const r of report.results) {
@@ -216,4 +216,4 @@ async function verifyModuleArtifact(tgz, { key, maxBytes = MAX_TARBALL_BYTES, in
216
216
  return out;
217
217
  }
218
218
 
219
- module.exports = { MAX_TARBALL_BYTES, packModule, verifyModuleArtifact };
219
+ module.exports = { MAX_TARBALL_BYTES, packModule, verifyModuleArtifact, deniedFiles };
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ // scripts/gds/module-assess-security.js — the Security part of a module's
3
+ // assessment: a pass/fail GATE over one published store version, recorded as a
4
+ // module_assessment_signals row (task 1003792; ADR 0343 D2; table from core_265).
5
+ //
6
+ // WHY a gate and not a score. ADR 0343 D2: a version that fails the security
7
+ // check is not listed, whatever else it scores, and security is never averaged
8
+ // in — so a module cannot buy back a vulnerability with good tests. The outcome
9
+ // is therefore only ever passed / failed, or not_scored when the check could not
10
+ // be completed (which is NOT a pass: a gate that cannot run must not open).
11
+ //
12
+ // The check reuses what the project already enforces rather than re-typing it:
13
+ // 1. the publish denylist — module-artifact.js deniedFiles (the repo-wide
14
+ // matchesDeny list + credential-named files; ADR 0107 §4). Publish already
15
+ // refuses these; re-checking here means a rule added later still reaches
16
+ // versions published before it.
17
+ // 2. dependency SOURCES — every entry in module.json `dependencies` (ADR 0138)
18
+ // must be a plain registry package name with a plain semver range or dist-
19
+ // tag. That is an ALLOWLIST (registrySpec below), deliberately stricter than
20
+ // a list of bad protocols: a git/http/file, aliased or GitHub-shorthand spec
21
+ // — or anything else unrecognised — installs code from somewhere nobody
22
+ // vetted, and fails the gate. (It mirrors the artifact-scan floor's grading,
23
+ // but a core script may not require a module's files, so it is its own
24
+ // small rule.) Floating ranges and wildcards are only noted.
25
+ // 3. KNOWN VULNERABILITIES in those dependencies — only when every spec passed
26
+ // check 2, so nothing a module names is ever fetched from a place of its
27
+ // choosing and no `file:` path is ever resolved on this host. npm resolves the ranges in a
28
+ // throwaway directory (--package-lock-only --ignore-scripts: registry
29
+ // metadata only, no package code downloaded or run), then `npm audit`; any
30
+ // high or critical advisory fails the gate (the threshold scripts/security/
31
+ // dep-audit.js uses for the core, whose parser this reuses).
32
+ // And one input that is reported, never failed on: the `maintenance` posture
33
+ // (ADR 0166). "Undeclared", "deprecated" or "orphaned" is something a buyer
34
+ // should see, but it is upkeep, not a vulnerability — that call is recorded in
35
+ // the row's detail so the hall (task 1003799) can surface it.
36
+ //
37
+ // Nothing here executes the module's own code, so unlike module-assess-tests.js
38
+ // this is safe to run on the control plane.
39
+ //
40
+ // node scripts/gds/module-assess-security.js <store_module_versions.id> run + record
41
+ // node scripts/gds/module-assess-security.js --dir modules/<key> dry run (prints; no DB)
42
+
43
+ const fs = require('node:fs');
44
+ const os = require('node:os');
45
+ const path = require('node:path');
46
+ const { execFile } = require('node:child_process');
47
+ const { deniedFiles, verifyModuleArtifact } = require('./module-artifact');
48
+ const { extractNpmFindings } = require('../security/dep-audit');
49
+
50
+ const AUDIT_TIMEOUT_MS = 120_000;
51
+ // npm is a .cmd shim on Windows, which execFile cannot start without a shell; go
52
+ // through cmd.exe explicitly rather than shell:true (the args are fixed constants).
53
+ const npmCommand = (args) => (process.platform === 'win32'
54
+ ? ['cmd.exe', ['/d', '/s', '/c', 'npm', ...args]]
55
+ : ['npm', args]);
56
+
57
+ // Run one npm command in `cwd`; resolve { ok, stdout, stderr, error }. Never rejects.
58
+ function npmRun(args, cwd, { exec = execFile, timeoutMs = AUDIT_TIMEOUT_MS } = {}) {
59
+ return new Promise((resolve) => {
60
+ const [cmd, argv] = npmCommand(args);
61
+ exec(cmd, argv, { cwd, timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, windowsHide: true },
62
+ (err, stdout, stderr) => resolve({ err, stdout: String(stdout || ''), stderr: String(stderr || '') }));
63
+ });
64
+ }
65
+
66
+ // Audit a dependency map { name: range } for known high/critical advisories.
67
+ // Resolves { ok: true, findings } or { ok: false, reason } when npm could not
68
+ // resolve or audit (offline, unknown package, npm missing) — the caller treats
69
+ // that as not_scored, never as a pass.
70
+ async function auditDependencies(deps, { exec, timeoutMs } = {}) {
71
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'module-assess-security-'));
72
+ try {
73
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'module-assess-audit', private: true, version: '0.0.0', dependencies: deps }));
74
+ const lock = await npmRun(['install', '--package-lock-only', '--ignore-scripts', '--no-audit', '--no-fund'], dir, { exec, timeoutMs });
75
+ if (lock.err || !fs.existsSync(path.join(dir, 'package-lock.json'))) {
76
+ return { ok: false, reason: 'npm could not resolve the declared dependencies' };
77
+ }
78
+ // npm audit exits 1 when it finds anything; the JSON on stdout is the answer either way.
79
+ const audit = await npmRun(['audit', '--json'], dir, { exec, timeoutMs });
80
+ let parsed;
81
+ try { parsed = JSON.parse(audit.stdout || ''); } catch { return { ok: false, reason: 'npm audit produced no readable report' }; }
82
+ if (parsed && parsed.error) return { ok: false, reason: 'npm audit could not reach the advisory database' };
83
+ return { ok: true, findings: extractNpmFindings(parsed) };
84
+ } finally {
85
+ fs.rmSync(dir, { recursive: true, force: true });
86
+ }
87
+ }
88
+
89
+ // npm's package-name rule (scoped or not, lowercase, no path segments).
90
+ const NPM_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
91
+ // A semver range: versions, x/* wildcards, ^ ~ comparators, hyphen and || unions,
92
+ // prerelease/build suffixes. No ':' '/' '#' '@' — so no protocol, path, alias or ref.
93
+ const RANGE_RE = /^[0-9A-Za-z.*^~<>=|\s+-]+$/;
94
+ const DIST_TAGS = new Set(['latest', 'next']);
95
+
96
+ // Grade one declared dependency. { ok: false } means it would install from
97
+ // somewhere other than the public registry (or cannot be read as a registry
98
+ // spec): the gate fails and it is never handed to npm. { ok: true, kind } is a
99
+ // registry spec; kind names how much the registry decides at install time.
100
+ function registrySpec(name, spec) {
101
+ const s = String(spec).trim();
102
+ if (!NPM_NAME_RE.test(name)) return { ok: false, kind: 'invalid package name' };
103
+ if (DIST_TAGS.has(s) || s === '*' || s === '' || /^[xX]$/.test(s)) return { ok: true, kind: 'wildcard' };
104
+ if (/^[a-z+]+:/i.test(s)) return { ok: false, kind: 'non-registry source' };
105
+ // Every whitespace/||-separated token must start with a comparator or a digit/x.
106
+ const tokens = s.split(/\s*\|\|\s*|\s+/).filter(Boolean);
107
+ if (!RANGE_RE.test(s) || !tokens.every((t) => t === '-' || /^(?:[\^~]|[<>]=?|=)?\s*[0-9xX*]/.test(t))) {
108
+ return { ok: false, kind: 'not a registry range' };
109
+ }
110
+ if (/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(s)) return { ok: true, kind: 'pinned' };
111
+ return { ok: true, kind: 'floating range' };
112
+ }
113
+
114
+ // The maintenance posture as a buyer would read it (ADR 0166).
115
+ function maintenancePosture(moduleJson) {
116
+ const m = moduleJson && moduleJson.maintenance;
117
+ return m && typeof m.status === 'string' ? m.status : 'undeclared';
118
+ }
119
+
120
+ // The gate over one version's files ([{ path, buf }], module-relative) and its
121
+ // parsed module.json. Returns the signal fields { outcome, score, sample_size,
122
+ // reason, detail }. `audit` is injectable so tests need no network.
123
+ async function assessSecurity(key, files, moduleJson, { audit = auditDependencies } = {}) {
124
+ const failures = [];
125
+ const notes = [];
126
+
127
+ const denied = deniedFiles(key, files.map((f) => f.path));
128
+ if (denied.length) failures.push({ check: 'denylist', files: denied });
129
+
130
+ const deps = (moduleJson && moduleJson.dependencies) || {};
131
+ const names = Object.keys(deps).sort();
132
+ const sources = [];
133
+ for (const name of names) {
134
+ const graded = registrySpec(name, deps[name]);
135
+ const entry = { package: name.slice(0, 214), spec: String(deps[name]).slice(0, 200), kind: graded.kind };
136
+ if (!graded.ok) sources.push(entry);
137
+ else if (graded.kind === 'wildcard') notes.push({ check: 'dependency_wildcard', ...entry });
138
+ else if (graded.kind === 'floating range') notes.push({ check: 'dependency_range', ...entry });
139
+ }
140
+ if (sources.length) failures.push({ check: 'dependency_source', dependencies: sources });
141
+
142
+ // Audit only an all-registry list: a failed source already fails the gate, and
143
+ // handing it to npm would make this host fetch (or resolve a file: path) of the
144
+ // module's choosing.
145
+ let advisories = [];
146
+ if (names.length && !sources.length) {
147
+ const a = await audit(deps);
148
+ if (!a.ok) {
149
+ return {
150
+ outcome: 'not_scored', score: null, sample_size: names.length,
151
+ reason: `the dependency audit could not run: ${a.reason}`,
152
+ detail: { failures, notes, maintenance: maintenancePosture(moduleJson) },
153
+ };
154
+ }
155
+ advisories = a.findings.map((f) => ({ package: f.package, severity: f.severity, id: f.id, summary: f.summary, fix: f.fix }));
156
+ if (advisories.length) failures.push({ check: 'vulnerable_dependency', advisories });
157
+ }
158
+
159
+ const maintenance = maintenancePosture(moduleJson);
160
+ if (maintenance !== 'maintained' && maintenance !== 'core-maintained') {
161
+ notes.push({ check: 'maintenance', status: maintenance });
162
+ }
163
+
164
+ const detail = { failures, notes, maintenance, dependencies_checked: names.length };
165
+ if (failures.length) {
166
+ return { outcome: 'failed', score: null, sample_size: names.length, reason: `failed: ${failures.map((f) => f.check).join(', ')}`, detail };
167
+ }
168
+ return {
169
+ outcome: 'passed', score: null, sample_size: names.length,
170
+ reason: names.length ? `passed: ${names.length} dependency(ies) from the registry, no high or critical advisory` : 'passed: no denied files and no dependencies',
171
+ detail,
172
+ };
173
+ }
174
+
175
+ // Append one Security row. Never UPDATEs (core_265 is append-only).
176
+ async function insertSecuritySignal(versionId, s, { db }) {
177
+ const { rows: [row] } = await db.query(
178
+ `INSERT INTO module_assessment_signals (version_id, part, outcome, score, sample_size, reason, detail)
179
+ VALUES ($1, 'security', $2, $3, $4, $5, $6)
180
+ RETURNING id, version_id, part, outcome, score, sample_size, reason, measured_at`,
181
+ [versionId, s.outcome, s.score, s.sample_size, s.reason, JSON.stringify(s.detail || {})]);
182
+ return row;
183
+ }
184
+
185
+ // Read and verify one published version, run the gate, record the result. A
186
+ // tarball that is missing or fails verification is not_scored — the gate could
187
+ // not be run, so it stays closed.
188
+ async function recordSecuritySignal(versionId, { db, storeDir, audit } = {}) {
189
+ const pool = db || require('../../src/bongos/pool').pool;
190
+ const { versionArtifactFile } = require('../../src/bongos/module-store');
191
+ const { rows: [ver] } = await pool.query(
192
+ 'SELECT id, module_key, version, artifact_path FROM store_module_versions WHERE id = $1', [versionId]);
193
+ if (!ver) return { ok: false, code: 'version_not_found', message: `no store_module_versions row ${versionId}` };
194
+
195
+ let signal;
196
+ try {
197
+ const tgz = await fs.promises.readFile(versionArtifactFile(ver, storeDir ? { dir: storeDir } : {}));
198
+ const v = await verifyModuleArtifact(tgz, { key: ver.module_key, includeFiles: true });
199
+ if (v.ok) {
200
+ signal = await assessSecurity(ver.module_key, v.files, v.moduleJson, { audit });
201
+ } else if (v.code === 'denied_content') {
202
+ signal = { outcome: 'failed', score: null, sample_size: null, reason: 'failed: denylist', detail: { failures: [{ check: 'denylist' }] } };
203
+ } else {
204
+ signal = { outcome: 'not_scored', score: null, sample_size: null, reason: `the tarball failed verification (${v.code})`, detail: {} };
205
+ }
206
+ } catch (e) {
207
+ signal = { outcome: 'not_scored', score: null, sample_size: null, reason: `the check could not run (${e.code || 'error'})`, detail: {} };
208
+ }
209
+ const row = await insertSecuritySignal(ver.id, signal, { db: pool });
210
+ return { ok: true, version: ver, signal: row };
211
+ }
212
+
213
+ function readModuleDir(dir) {
214
+ const out = [];
215
+ const walk = (rel) => {
216
+ for (const e of fs.readdirSync(path.join(dir, rel), { withFileTypes: true })) {
217
+ if (e.name === 'node_modules' || e.name === '.git') continue;
218
+ const r = rel ? `${rel}/${e.name}` : e.name;
219
+ if (e.isDirectory()) walk(r);
220
+ else if (e.isFile()) out.push({ path: r, buf: fs.readFileSync(path.join(dir, r)) });
221
+ }
222
+ };
223
+ walk('');
224
+ return out;
225
+ }
226
+
227
+ async function main(argv) {
228
+ const i = argv.indexOf('--dir');
229
+ if (i !== -1) {
230
+ const dir = path.resolve(argv[i + 1] || '');
231
+ const moduleJson = JSON.parse(fs.readFileSync(path.join(dir, 'module.json'), 'utf8'));
232
+ console.log(JSON.stringify(await assessSecurity(moduleJson.key, readModuleDir(dir), moduleJson), null, 2));
233
+ return 0;
234
+ }
235
+ const id = argv[0];
236
+ if (!/^\d+$/.test(String(id || ''))) {
237
+ console.error('usage: node scripts/gds/module-assess-security.js <store_module_versions.id> | --dir modules/<key>');
238
+ return 2;
239
+ }
240
+ const res = await recordSecuritySignal(id);
241
+ if (!res.ok) { console.error(res.message); return 1; }
242
+ const s = res.signal;
243
+ console.log(`${res.version.module_key} ${res.version.version}: security ${s.outcome} — ${s.reason} (signal ${s.id})`);
244
+ return 0;
245
+ }
246
+
247
+ if (require.main === module) {
248
+ main(process.argv.slice(2)).then((code) => process.exit(code), (e) => { console.error(e.stack || e.message); process.exit(1); });
249
+ }
250
+
251
+ module.exports = { assessSecurity, auditDependencies, recordSecuritySignal, insertSecuritySignal, maintenancePosture, registrySpec };
@@ -65,6 +65,8 @@ const PUBLISH_ALLOWLIST = [
65
65
  'modules/specialities/', // role sub-specialities (task 1003855, ADR 0296). default:true, so it MUST be here, and the copy-desk asymmetry above applies verbatim: the settings panel ships regardless because modules/hall-ui/ is allowlisted as one subtree, so omitting this line would put a "Your specialities" section in every instance's settings page whose every call 404s.
66
66
  'modules/copy-desk/', // the artist's loop over user-facing TEXT (goal 1000074, ADR 0178). default:true, so it MUST be here — and note the ASYMMETRY that makes omitting it worse than useless: its hall surface ships regardless, because modules/hall-ui/ is allowlisted as one subtree. A released core carrying the page and the nav entry but not the routes behind them puts a tab in every instance's hall whose every call 404s. The module and its page are only ever added or removed together; tests/publish_manifest.mjs holds that pair.
67
67
  'modules/ui-design/', // the design contract + its adapters/CLIs + the /design playbook (task 1003324, ADR 0197). default:true, so it MUST be here: a released core without it would materialize skills (design, design-sync, figma-design-sync) whose CLIs and adapters do not exist.
68
+ 'modules/design-styles/', // the thirteen opt-in design-style skills split out of ui-design (task 1004470). default:false, but MUST be here: shipped is not enabled, and a released core without it hands an instance that turns it on an "unknown module" with nothing to materialize. Skill text only.
69
+ 'modules/pixel-art/', // the five opt-in pixel-art skills (task 1004470), which shipped from .claude/skills/ before this. default:false. SKILL.md text only — never a reference map, a palette or generated art: those are modules/art-pipeline/, which CONTENT_EXCLUDE keeps out below for the copyright reason (ADR 0098).
68
70
  'modules/hall-ui/',
69
71
  'modules/status-ui/',
70
72
  'modules/public-landing/', // the front door (task 1003322, ADR 0218) — the repo-root public-landing/ entry this replaces was always here, and for the same reason: default:true, and it is the only thing an instance serves at its apex root. Omit it and a released core answers the game-less 404 tail on its own home page.