@neurodock/cli 0.7.1 → 0.7.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 (84) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/assets/hooks/neurodock_daemon.py +2 -0
  3. package/dist/assets/hooks/proactive_guardrail.py +2 -0
  4. package/dist/assets/hooks/test_daemon_escape.py +2 -0
  5. package/dist/assets/schemas/plugin.example.yaml +193 -0
  6. package/dist/assets/schemas/plugin.minimal.yaml +30 -0
  7. package/dist/assets/schemas/plugin.schema.json +391 -0
  8. package/dist/assets/schemas/profile.example.yaml +151 -0
  9. package/dist/assets/schemas/profile.minimal.yaml +34 -0
  10. package/dist/assets/schemas/profile.schema.json +211 -0
  11. package/dist/clients/claude-code.d.ts.map +1 -1
  12. package/dist/clients/claude-code.js.map +1 -1
  13. package/dist/clients/claude-desktop.d.ts.map +1 -1
  14. package/dist/clients/claude-desktop.js.map +1 -1
  15. package/dist/clients/cursor.d.ts.map +1 -1
  16. package/dist/clients/cursor.js.map +1 -1
  17. package/dist/clients/index.d.ts.map +1 -1
  18. package/dist/clients/index.js.map +1 -1
  19. package/dist/commands/doctor.d.ts.map +1 -1
  20. package/dist/commands/doctor.js +4 -0
  21. package/dist/commands/doctor.js.map +1 -1
  22. package/dist/commands/examples.d.ts.map +1 -1
  23. package/dist/commands/examples.js +4 -0
  24. package/dist/commands/examples.js.map +1 -1
  25. package/dist/commands/host.d.ts.map +1 -1
  26. package/dist/commands/host.js +4 -0
  27. package/dist/commands/host.js.map +1 -1
  28. package/dist/commands/init.d.ts.map +1 -1
  29. package/dist/commands/init.js +8 -0
  30. package/dist/commands/init.js.map +1 -1
  31. package/dist/commands/install-all.d.ts.map +1 -1
  32. package/dist/commands/install-all.js +4 -0
  33. package/dist/commands/install-all.js.map +1 -1
  34. package/dist/commands/install-hooks.d.ts.map +1 -1
  35. package/dist/commands/install-hooks.js +4 -0
  36. package/dist/commands/install-hooks.js.map +1 -1
  37. package/dist/commands/plugin.d.ts.map +1 -1
  38. package/dist/commands/plugin.js +4 -0
  39. package/dist/commands/plugin.js.map +1 -1
  40. package/dist/commands/profile.d.ts.map +1 -1
  41. package/dist/commands/profile.js +4 -0
  42. package/dist/commands/profile.js.map +1 -1
  43. package/dist/commands/sync.d.ts.map +1 -1
  44. package/dist/commands/sync.js +4 -0
  45. package/dist/commands/sync.js.map +1 -1
  46. package/dist/commands/uninstall.d.ts.map +1 -1
  47. package/dist/commands/uninstall.js +4 -0
  48. package/dist/commands/uninstall.js.map +1 -1
  49. package/dist/commands/update.d.ts.map +1 -1
  50. package/dist/commands/update.js +4 -0
  51. package/dist/commands/update.js.map +1 -1
  52. package/dist/commands/validate.d.ts.map +1 -1
  53. package/dist/commands/validate.js +4 -0
  54. package/dist/commands/validate.js.map +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/env.d.ts.map +1 -1
  59. package/dist/lib/env.js +4 -0
  60. package/dist/lib/env.js.map +1 -1
  61. package/dist/lib/json-patch.d.ts.map +1 -1
  62. package/dist/lib/json-patch.js.map +1 -1
  63. package/dist/lib/mcp-entries.d.ts.map +1 -1
  64. package/dist/lib/mcp-entries.js +4 -0
  65. package/dist/lib/mcp-entries.js.map +1 -1
  66. package/dist/lib/paths.d.ts.map +1 -1
  67. package/dist/lib/paths.js +4 -0
  68. package/dist/lib/paths.js.map +1 -1
  69. package/dist/lib/plugin-schema.d.ts.map +1 -1
  70. package/dist/lib/plugin-schema.js +7 -0
  71. package/dist/lib/plugin-schema.js.map +1 -1
  72. package/dist/profile/defaults.d.ts.map +1 -1
  73. package/dist/profile/defaults.js +4 -0
  74. package/dist/profile/defaults.js.map +1 -1
  75. package/dist/profile/loader.d.ts.map +1 -1
  76. package/dist/profile/loader.js +4 -0
  77. package/dist/profile/loader.js.map +1 -1
  78. package/dist/profile/validator.d.ts.map +1 -1
  79. package/dist/profile/validator.js +7 -1
  80. package/dist/profile/validator.js.map +1 -1
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js +4 -0
  83. package/dist/types.js.map +1 -1
  84. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @neurodock/cli changelog
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Fixed — `npx @neurodock/cli install-all` failed on `init` with "Could not locate template: profile.example.yaml"
6
+
7
+ The 0.7.x tarball only shipped `dist/`, but `init`, `validate`, and
8
+ `plugin validate` all resolved their templates and JSON schemas through
9
+ relative paths into the workspace `packages/core/schemas/` folder. That
10
+ folder is not part of the cli tarball, so every fresh `npx` install
11
+ failed the moment `install-all` finished wiring the MCP servers and
12
+ tried to scaffold a profile.
13
+
14
+ Fix: `scripts/copy-assets.mjs` now also copies `packages/core/schemas/`
15
+ into `dist/assets/schemas/` at build time, and the three resolvers
16
+ (`commands/init.ts`, `profile/validator.ts`, `lib/plugin-schema.ts`)
17
+ check that bundled location first before falling back to the workspace
18
+ candidates used during local development. No behaviour change in the
19
+ monorepo; published tarball now contains the schemas required by every
20
+ first-run code path.
21
+
3
22
  ## 0.7.1
4
23
 
5
24
  ### Fixed — Windows: daemon autostart no longer flashes a black console window
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ # Copyright (c) 2026 NeuroDock contributors.
2
4
  """NeuroDock proactive-guardrail daemon (Phase 3).
3
5
 
4
6
  A long-running poller that watches the same state files the Phase 1
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ # Copyright (c) 2026 NeuroDock contributors.
2
4
  """NeuroDock proactive guardrail — Claude Code hook (self-contained).
3
5
 
4
6
  Bundled with `@neurodock/cli`; copied to `~/.neurodock/hooks/` by
@@ -1,3 +1,5 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (c) 2026 NeuroDock contributors.
1
3
  """Tests for the M5 notification-text escape functions in neurodock_daemon.py.
2
4
 
3
5
  Security regression test: verify that shell-injection characters in title/
@@ -0,0 +1,193 @@
1
+ # ==============================================================================
2
+ # WELCOME — THIS IS A WORKED EXAMPLE OF A PLUGIN MANIFEST
3
+ # ==============================================================================
4
+ # Plugins are how NeuroDock grows without us having to change the core. This
5
+ # file shows every field you can put in your own plugin's "plugin.yaml" file.
6
+ #
7
+ # You don't need to use every field. Most of them are optional. Copy this
8
+ # file as a starting point and delete anything you don't need.
9
+ #
10
+ # WHERE THE CONTRACT LIVES (for the curious):
11
+ # packages/core/schemas/plugin.schema.json — the machine-checkable rules
12
+ # docs/decisions/0007-plugin-protocol.md — the "why" behind the rules
13
+ #
14
+ # HOW PLUGINS GET FOUND:
15
+ # When NeuroDock starts, it looks in two places for plugins:
16
+ # 1. plugins/<your-plugin>/plugin.yaml (lives inside this repo)
17
+ # 2. ~/.local/share/neurodock/plugins/<your-plugin>/plugin.yaml
18
+ # (lives on your own computer, just for you)
19
+ # No central server. No sign-up. No registry to publish to.
20
+ #
21
+ # WILL UPGRADING BREAK MY PLUGIN?
22
+ # No. If we add new fields in a future version, older versions of NeuroDock
23
+ # will leave them alone instead of stripping them out. You can edit your
24
+ # plugin on a new install, swap back to an old one, and not lose anything.
25
+ # ==============================================================================
26
+
27
+ # Leave this as "0.1.0". It just tells NeuroDock which version of the plugin
28
+ # format you're using. If you're newer than NeuroDock, you'll get a warning,
29
+ # not an error.
30
+ schema_version: "0.1.0"
31
+
32
+ # ------------------------------------------------------------------------------
33
+ # 1. WHO THIS PLUGIN IS (Required)
34
+ # ------------------------------------------------------------------------------
35
+ # A short, unique name. Lowercase, dashes between words.
36
+ # Tip: starting it with your handle (e.g. "org-eobrien-...") helps avoid
37
+ # clashes with other contributors' plugins. Not required.
38
+ name: "ie-corporate-translation"
39
+
40
+ # What kind of plugin is this? Pick one:
41
+ # skill — adds a Claude skill (a markdown file with triggers)
42
+ # mcp-server — adds a new MCP server with new tools
43
+ # profile — adds a profile preset users can copy
44
+ # translation-pack — adds translation prompts for a domain (legal, sales...)
45
+ # language-pack — adds prompts for a non-US-English workplace culture
46
+ # theme — adds a visual theme for UI surfaces
47
+ type: "language-pack"
48
+
49
+ # Your plugin's version number. Use semver (e.g. "0.2.1"). Bumping the
50
+ # patch (third) number means "small fix, nothing broke". Bumping the minor
51
+ # (middle) means "added a new feature, nothing broke". Bumping the major
52
+ # (first) means "I changed something that might break people". Try not to
53
+ # bump the major in 0.1.x unless you really have to.
54
+ version: "0.2.1"
55
+
56
+ # A one-sentence description of what this plugin does. This is what people
57
+ # see in plugin listings. Don't make clinical claims here — say what the
58
+ # plugin DOES, not what it TREATS.
59
+ description: "Hiberno-English to neutral corporate English for ND professionals — direct decoding of indirect Irish workplace register without flattening voice."
60
+
61
+ # ------------------------------------------------------------------------------
62
+ # 2. WHO MADE IT (Optional but appreciated)
63
+ # ------------------------------------------------------------------------------
64
+ # Listing at least one maintainer helps when something needs fixing.
65
+ # "role" is whatever makes sense — maintainer, reviewer, lived-experience reviewer.
66
+ authors:
67
+ - name: "Eoin O'Brien"
68
+ handle: "@eobrien"
69
+ role: "maintainer"
70
+ - name: "Síofra Ní Bhriain"
71
+ handle: "@sionb"
72
+ role: "reviewer" # lived-experience reviewer
73
+
74
+ # ------------------------------------------------------------------------------
75
+ # 3. WHO IS THIS PLUGIN FOR? (Optional)
76
+ # ------------------------------------------------------------------------------
77
+ # Which neurotypes does this plugin help? Leave the list empty (or remove
78
+ # this block) if it works for everyone — most plugins do.
79
+ #
80
+ # If a user's profile lists any of these neurotypes, NeuroDock turns this
81
+ # plugin on for them automatically. If not, they can still turn it on by
82
+ # hand — your plugin isn't blocked, just not on by default.
83
+ neurotypes:
84
+ - "adhd"
85
+ - "audhd"
86
+ - "asd"
87
+
88
+ # Which spoken-language / workplace cultures is this plugin for?
89
+ # REQUIRED if type is "language-pack" or "translation-pack".
90
+ # Use BCP 47 tags ("en-IE" = Irish English, "ja-JP" = Japanese, etc.)
91
+ locale:
92
+ - "en-IE"
93
+ - "en-GB" # Irish-English register often shows up in UK workplaces too
94
+
95
+ # ------------------------------------------------------------------------------
96
+ # 4. WHAT THIS PLUGIN NEEDS TO RUN (Optional)
97
+ # ------------------------------------------------------------------------------
98
+ # If your plugin needs a specific version of NeuroDock or another MCP server,
99
+ # say so here. NeuroDock will skip loading your plugin (cleanly) if any of
100
+ # these aren't around.
101
+ requires:
102
+ substrate_version: ">=0.1.0"
103
+ mcp_servers:
104
+ - name: "mcp-translation"
105
+ version: ">=0.1.0"
106
+ skills: []
107
+ plugins: []
108
+
109
+ # ------------------------------------------------------------------------------
110
+ # 5. WHAT YOUR PLUGIN PROVIDES
111
+ # ------------------------------------------------------------------------------
112
+ # This is the actual content your plugin ships. Each entry points at a file
113
+ # inside your plugin's directory. All paths are relative — your plugin can't
114
+ # reach outside its own folder. That's a deliberate safety rule.
115
+ provides:
116
+ - type: "language-prompt-override"
117
+ id: "translate-incoming-en-ie"
118
+ path: "./prompts/translate-incoming.md"
119
+ # You can narrow when a single file applies, separate from the
120
+ # whole-plugin neurotypes/locale settings above.
121
+ applies_to:
122
+ locale: ["en-IE", "en-GB"]
123
+
124
+ - type: "language-prompt-override"
125
+ id: "check-tone-en-ie"
126
+ path: "./prompts/check-tone.md"
127
+
128
+ - type: "language-prompt-override"
129
+ id: "brief-meeting-en-ie"
130
+ path: "./prompts/brief-meeting.md"
131
+
132
+ - type: "translation-prompt"
133
+ id: "decode-grand"
134
+ path: "./prompts/idioms/decode-grand.md"
135
+ # "Grand" in Hiberno-English can mean anything from "excellent" to
136
+ # "this is not fine and we are not going to discuss it further." It
137
+ # earns its own prompt.
138
+
139
+ # ------------------------------------------------------------------------------
140
+ # 6. WHERE THIS PLUGIN CAME FROM (Required)
141
+ # ------------------------------------------------------------------------------
142
+ # This is the "trust" block. It tells NeuroDock how much social process has
143
+ # touched this plugin so it knows whether to prompt the user before activating.
144
+ #
145
+ # Levels:
146
+ # official — published by the NeuroDock maintainer; installs silently
147
+ # verified — signed against the maintainer keyring; installs silently
148
+ # community — your own plugin, author-signed; user gets a one-time prompt
149
+ # experimental — unsigned; NeuroDock refuses by default unless user opts in
150
+ #
151
+ # Most contributor plugins are "community". That's a good thing.
152
+ trust:
153
+ level: "community"
154
+ signature: |
155
+ -----BEGIN PGP SIGNATURE-----
156
+ (detached signature over sha256(plugin.yaml + provides[].path contents))
157
+ -----END PGP SIGNATURE-----
158
+ keyring_fingerprint: "A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678"
159
+ source_url: "https://github.com/eobrien/ie-corporate-translation"
160
+
161
+ # ------------------------------------------------------------------------------
162
+ # 7. LICENSE (Required)
163
+ # ------------------------------------------------------------------------------
164
+ # Your plugin's licence. Must be one of the allowed-list licences (they all
165
+ # play nicely with AGPL-3.0). If you pick one that isn't on the list,
166
+ # NeuroDock will refuse to load the plugin with a clear "license_not_allowed"
167
+ # error so nothing silently fails.
168
+ license: "AGPL-3.0-or-later"
169
+
170
+ # ------------------------------------------------------------------------------
171
+ # 8. WHERE PEOPLE CAN LEARN MORE (Optional)
172
+ # ------------------------------------------------------------------------------
173
+ homepage: "https://hiberno-translate.example.org"
174
+ repository: "https://github.com/eobrien/ie-corporate-translation"
175
+ keywords:
176
+ - "translation"
177
+ - "hiberno-english"
178
+ - "corporate"
179
+ - "indirect-speech"
180
+
181
+ # ------------------------------------------------------------------------------
182
+ # 9. LIFECYCLE HOOKS (Optional, advanced)
183
+ # ------------------------------------------------------------------------------
184
+ # Scripts that run on install/uninstall. For safety, NeuroDock will only
185
+ # run these for "official" or "verified" plugins right now. You can declare
186
+ # them for a community plugin — they'll just sit there politely ignored
187
+ # until the safer hook sandbox lands.
188
+ hooks:
189
+ on_install: "./install.sh" # idempotent; no network, no writing outside the plugin dir
190
+ on_uninstall: "./uninstall.sh" # clean up anything you created locally
191
+
192
+ # Reserved for future use. Leave empty.
193
+ compatibility: {}
@@ -0,0 +1,30 @@
1
+ # ==============================================================================
2
+ # THE SMALLEST POSSIBLE PLUGIN MANIFEST
3
+ # ==============================================================================
4
+ # This file contains ONLY the required fields for a NeuroDock plugin. Every
5
+ # other field is optional. If you're just starting out, this is the shape to
6
+ # copy.
7
+ #
8
+ # For the full version with every option explained, see:
9
+ # packages/core/schemas/plugin.example.yaml
10
+ #
11
+ # For the design rationale behind why these fields exist:
12
+ # docs/decisions/0007-plugin-protocol.md
13
+ #
14
+ # The "neurodock plugin scaffold" command would write something close to
15
+ # this file. Every required field is present, no optional ones are.
16
+ # ==============================================================================
17
+
18
+ schema_version: "0.1.0"
19
+ name: "minimal-example"
20
+ type: "skill"
21
+ version: "0.1.0"
22
+ description: "Minimal valid plugin manifest — replace with your one-sentence summary."
23
+ license: "AGPL-3.0-or-later"
24
+ trust:
25
+ level: "experimental"
26
+ # Heads up: with trust.level = "experimental" NeuroDock will refuse to
27
+ # load this plugin by default — that's a safety feature, not a bug.
28
+ # To get it loading, do one of:
29
+ # (a) bump trust.level to "community" and add a "source_url", or
30
+ # (b) opt in just this once: `neurodock plugin trust <name> --once`
@@ -0,0 +1,391 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.neurodock.org/plugin/v0.1.0/plugin.schema.json",
4
+ "title": "NeuroDock Plugin Manifest",
5
+ "description": "Manifest contract for every plugin discovered under plugins/<name>/plugin.yaml or ~/.neurodock/plugins/<name>/plugin.yaml. Declares plugin type, version, neurotype targeting, dependencies, provided assets, trust level, license, and lifecycle hooks. Forward-compatible: unknown keys at every nesting level MUST be preserved by loaders.",
6
+ "$comment": "Discovery is filesystem-based in v0.1.0: the substrate walks plugins/*/plugin.yaml (in-repo) and $XDG_DATA_HOME/neurodock/plugins/*/plugin.yaml (per-user) at init. The Phase 3 federated registry at plugins.neurodock.org is opt-in and indexes signed manifests; the protocol does NOT depend on a central registry. Loaders MUST preserve unknown top-level and nested keys when round-tripping a manifest so a v0.1.0 install can read and re-emit a v0.2.0 manifest without data loss. Asset paths in `provides[].path` and `hooks.*` are sandboxed: the resolved absolute path MUST remain inside the plugin's directory after normalisation. Symlinks pointing outside the plugin dir MUST be rejected by the loader.",
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "required": [
10
+ "schema_version",
11
+ "name",
12
+ "type",
13
+ "version",
14
+ "description",
15
+ "license",
16
+ "trust"
17
+ ],
18
+ "properties": {
19
+ "schema_version": {
20
+ "type": "string",
21
+ "description": "Required. Plugin manifest schema version this file targets. Loaders SHOULD warn but never fail when the version is newer than what they understand; they MUST still load known fields and preserve unknown ones. Use the same semver shape as the profile schema.",
22
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
23
+ "examples": ["0.1.0"]
24
+ },
25
+ "name": {
26
+ "type": "string",
27
+ "description": "Required. Globally unique within the plugin's `type`. Kebab-case, ASCII lowercase, digits, and hyphens. MUST start with a letter. Reverse-DNS prefixes (e.g. 'org-eobrien-') are RECOMMENDED for community plugins to avoid collisions; not enforced in v0.1.0 to keep contribution friction low.",
28
+ "pattern": "^[a-z][a-z0-9-]{1,62}[a-z0-9]$",
29
+ "minLength": 3,
30
+ "maxLength": 64,
31
+ "examples": ["ie-corporate-translation", "adhd-pomodoro", "mcp-calendar"]
32
+ },
33
+ "type": {
34
+ "type": "string",
35
+ "description": "Required. Plugin type. Six values in v0.1.0. New types are an ADDITIVE forward-compat change (no major bump); v0.1.0 loaders encountering an unknown type MUST skip the plugin with a structured 'unknown_plugin_type' warning rather than erroring.",
36
+ "enum": [
37
+ "skill",
38
+ "mcp-server",
39
+ "profile",
40
+ "translation-pack",
41
+ "language-pack",
42
+ "theme"
43
+ ],
44
+ "examples": ["language-pack", "skill"]
45
+ },
46
+ "version": {
47
+ "type": "string",
48
+ "description": "Required. Plugin version. Strict semver (no pre-release qualifiers in v0.1.0). Patch and minor bumps within v0.1.x MUST be additive-only; renames, removals, and required-field additions are major bumps.",
49
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
50
+ "examples": ["0.2.1", "1.0.0"]
51
+ },
52
+ "description": {
53
+ "type": "string",
54
+ "description": "Required. One-sentence human description shown in plugin listings. Must not make clinical claims (per ETHICS commitment 1).",
55
+ "minLength": 10,
56
+ "maxLength": 280,
57
+ "examples": [
58
+ "Hiberno-English to neutral corporate English translation prompts for ND professionals."
59
+ ]
60
+ },
61
+ "authors": {
62
+ "type": "array",
63
+ "description": "Optional. Plugin authors. At least one entry with role 'maintainer' is RECOMMENDED for community plugins so the substrate can surface a contact in trust prompts. Empty / absent is acceptable for in-repo first-party plugins authored by the maintainer.",
64
+ "minItems": 0,
65
+ "items": {
66
+ "type": "object",
67
+ "additionalProperties": true,
68
+ "required": ["name"],
69
+ "properties": {
70
+ "name": {
71
+ "type": "string",
72
+ "minLength": 1,
73
+ "maxLength": 120,
74
+ "description": "Display name or handle."
75
+ },
76
+ "handle": {
77
+ "type": "string",
78
+ "maxLength": 64,
79
+ "description": "Optional. GitHub-style '@handle' (or other platform-prefixed handle).",
80
+ "examples": ["@eobrien"]
81
+ },
82
+ "email": {
83
+ "type": "string",
84
+ "format": "email",
85
+ "description": "Optional. Contact email. Plugins published through the federated registry MAY omit this; in-repo plugins SHOULD omit it (use the GitHub handle instead). Never required."
86
+ },
87
+ "role": {
88
+ "type": "string",
89
+ "enum": ["maintainer", "contributor", "reviewer"],
90
+ "default": "contributor",
91
+ "description": "Role in this plugin's authorship. 'maintainer' = accepts issues, signs releases; 'contributor' = wrote part of it; 'reviewer' = clinical or who signed off."
92
+ }
93
+ }
94
+ }
95
+ },
96
+ "neurotypes": {
97
+ "type": "array",
98
+ "description": "Subset of the values allowed by profile.identity.neurotypes that this plugin targets. The substrate auto-activates a plugin only when this array intersects the user's profile.identity.neurotypes (non-empty intersection). Empty array (the default when omitted) means the plugin is neurotype-agnostic and auto-activates for every user.",
99
+ "minItems": 0,
100
+ "uniqueItems": true,
101
+ "items": {
102
+ "type": "string",
103
+ "enum": [
104
+ "adhd",
105
+ "asd",
106
+ "audhd",
107
+ "ocd",
108
+ "dyslexia",
109
+ "dyspraxia",
110
+ "tourette",
111
+ "other"
112
+ ]
113
+ },
114
+ "default": [],
115
+ "examples": [["adhd"], ["adhd", "audhd"], []]
116
+ },
117
+ "locale": {
118
+ "type": "array",
119
+ "description": "BCP 47 locale tags this plugin targets. REQUIRED for type 'language-pack' and 'translation-pack' (loader rejects manifest when empty for those types); ignored for other types. Substrate prefers locale-matched plugins over locale-agnostic ones when both apply.",
120
+ "minItems": 0,
121
+ "uniqueItems": true,
122
+ "items": {
123
+ "type": "string",
124
+ "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{2,4})*(-[a-zA-Z0-9]{1,8})*$"
125
+ },
126
+ "examples": [["en-IE", "en-GB"], ["ja-JP"], ["de-DE"]]
127
+ },
128
+ "requires": {
129
+ "type": "object",
130
+ "description": "Optional. Hard runtime requirements. The substrate refuses to activate the plugin when any requirement is unmet; refusal is loud (structured warning + log entry) rather than silent. Circular requirement chains (A requires B requires A) MUST be detected at load time and reported via the 'plugin_requirement_cycle' error.",
131
+ "additionalProperties": true,
132
+ "properties": {
133
+ "substrate_version": {
134
+ "type": "string",
135
+ "description": "Required substrate (`@neurodock/core`) version range. Standard semver-range syntax (`>=`, `<`, `~`, `^`, `||`). Defaults to '>=0.1.0' when omitted.",
136
+ "examples": [">=0.1.0", ">=0.1.0 <0.3.0"]
137
+ },
138
+ "mcp_servers": {
139
+ "type": "array",
140
+ "description": "MCP servers the plugin's assets call into. Each entry is matched against the substrate's installed-server registry by name; the version range is matched against the server's package version.",
141
+ "items": {
142
+ "type": "object",
143
+ "additionalProperties": true,
144
+ "required": ["name"],
145
+ "properties": {
146
+ "name": {
147
+ "type": "string",
148
+ "description": "MCP server name as published, e.g. 'mcp-translation'.",
149
+ "examples": ["mcp-translation", "mcp-cognitive-graph"]
150
+ },
151
+ "version": {
152
+ "type": "string",
153
+ "description": "Semver range. Defaults to '>=0.1.0' when omitted.",
154
+ "examples": [">=0.1.0"]
155
+ }
156
+ }
157
+ }
158
+ },
159
+ "skills": {
160
+ "type": "array",
161
+ "description": "Other skills this plugin depends on at runtime (by skill name as declared in the depended-on skill's SKILL.md frontmatter or plugin.yaml `name`).",
162
+ "items": {
163
+ "type": "object",
164
+ "additionalProperties": true,
165
+ "required": ["name"],
166
+ "properties": {
167
+ "name": {
168
+ "type": "string",
169
+ "examples": ["adhd-daily-planner"]
170
+ },
171
+ "version": {
172
+ "type": "string",
173
+ "examples": [">=0.1.0"]
174
+ }
175
+ }
176
+ }
177
+ },
178
+ "plugins": {
179
+ "type": "array",
180
+ "description": "Other plugins this plugin depends on. By `name` (and optional `type` disambiguator when the name is reused across types). Circular `requires.plugins` chains are rejected at load.",
181
+ "items": {
182
+ "type": "object",
183
+ "additionalProperties": true,
184
+ "required": ["name"],
185
+ "properties": {
186
+ "name": { "type": "string" },
187
+ "type": {
188
+ "type": "string",
189
+ "enum": [
190
+ "skill",
191
+ "mcp-server",
192
+ "profile",
193
+ "translation-pack",
194
+ "language-pack",
195
+ "theme"
196
+ ]
197
+ },
198
+ "version": { "type": "string" }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ },
204
+ "provides": {
205
+ "type": "array",
206
+ "description": "Assets this plugin exposes to the substrate. Every entry has an `id` (unique within the manifest), a sub-`type` describing what kind of asset it is, and a `path` relative to the plugin's directory. Paths are sandboxed: after normalisation the resolved absolute path MUST remain inside the plugin's directory; '..' traversal and absolute paths are rejected by the loader.",
207
+ "minItems": 0,
208
+ "items": {
209
+ "type": "object",
210
+ "additionalProperties": true,
211
+ "required": ["type", "id", "path"],
212
+ "properties": {
213
+ "type": {
214
+ "type": "string",
215
+ "description": "Asset sub-type. The substrate dispatches discovery on this value: 'skill' → registered with the skill loader; 'mcp-server-binary' → registered with the MCP supervisor; 'translation-prompt' → registered with mcp-translation's prompt library; 'language-prompt-override' → shadows the matching default prompt for the manifest's `locale`; 'profile-preset' → exposed under `profiles/presets/`; 'theme-bundle' → registered with the design system. Unknown values are preserved on round-trip but skipped by the registrar with a structured 'unknown_asset_type' warning.",
216
+ "enum": [
217
+ "skill",
218
+ "mcp-server-binary",
219
+ "translation-prompt",
220
+ "language-prompt-override",
221
+ "profile-preset",
222
+ "theme-bundle"
223
+ ]
224
+ },
225
+ "id": {
226
+ "type": "string",
227
+ "description": "Identifier unique within this manifest's `provides[]`. Kebab-case. The substrate exposes the asset under '<plugin.name>/<provides.id>'.",
228
+ "pattern": "^[a-z][a-z0-9-]{0,62}[a-z0-9]$",
229
+ "examples": ["translate-hiberno-direct", "tone-keigo-customer"]
230
+ },
231
+ "path": {
232
+ "type": "string",
233
+ "description": "Path to the asset, RELATIVE to the plugin directory. MUST start with './' or a non-leading-slash subdirectory. Loader normalises and rejects any path resolving outside the plugin directory or pointing through a symlink that does so.",
234
+ "pattern": "^(\\./)?[^/\\\\][^\\\\:*?\"<>|]*$",
235
+ "examples": [
236
+ "./prompts/translate-hiberno-direct.md",
237
+ "prompts/tone-warm.md"
238
+ ]
239
+ },
240
+ "applies_to": {
241
+ "type": "object",
242
+ "description": "Optional. Activation predicate for this specific asset, evaluated against the user's profile. Defaults to inheriting the manifest-level `neurotypes` + `locale`. An asset's predicate, when present, overrides the manifest-level intersection for that asset only.",
243
+ "additionalProperties": true,
244
+ "properties": {
245
+ "neurotypes": {
246
+ "type": "array",
247
+ "uniqueItems": true,
248
+ "items": { "type": "string" }
249
+ },
250
+ "locale": {
251
+ "type": "array",
252
+ "uniqueItems": true,
253
+ "items": { "type": "string" }
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ },
260
+ "trust": {
261
+ "type": "object",
262
+ "description": "Required. Provenance and trust declaration. Four-tier ladder. 'official' (NeuroDock Maintainer-published) and 'verified' (signed by a verified contributor whose key sits in the maintainer-maintained keyring) install without user prompting. 'community' (author-signed; provenance recorded but not vouched) and 'experimental' (unsigned) prompt the user per profile preference. The substrate's default profile setting is 'prompt for community, refuse for experimental'.",
263
+ "additionalProperties": true,
264
+ "required": ["level"],
265
+ "properties": {
266
+ "level": {
267
+ "type": "string",
268
+ "enum": ["official", "verified", "community", "experimental"],
269
+ "description": "Trust tier. See trust ladder above."
270
+ },
271
+ "signature": {
272
+ "type": "string",
273
+ "description": "Optional in v0.1.0; required for 'official' and 'verified' once signature verification ships in Phase 3. ASCII-armored PGP detached signature over sha256(plugin.yaml + concatenation of provides[].path file contents in declaration order). Verification implementation is deferred; v0.1.0 schemas reserve the field and store it on round-trip.",
274
+ "examples": ["-----BEGIN PGP SIGNATURE----- ..."]
275
+ },
276
+ "keyring_fingerprint": {
277
+ "type": "string",
278
+ "description": "Optional. PGP fingerprint of the key that produced `signature`. Lets the substrate look the key up in the maintainer keyring (for 'verified') or in a local trust-on-first-use store (for 'community').",
279
+ "pattern": "^[A-F0-9 ]{40,60}$",
280
+ "examples": ["A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678"]
281
+ },
282
+ "source_url": {
283
+ "type": "string",
284
+ "format": "uri",
285
+ "description": "Required for 'community' and 'experimental'. URL of the canonical source the user can audit before trusting the plugin (typically the public repository).",
286
+ "examples": ["https://github.com/eobrien/ie-corporate-translation"]
287
+ }
288
+ }
289
+ },
290
+ "license": {
291
+ "type": "string",
292
+ "description": "Required. SPDX license identifier. v0.1.0 whitelist enforces AGPL-3.0-or-later-compatible licenses ONLY. Plugins with a non-listed license refuse to load and surface a 'license_not_allowed' error. The whitelist is conservative on purpose: the substrate is AGPL-3.0-or-later and plugins are distributed alongside it, so we keep license boundaries clean. The whitelist may be extended in additive minor bumps via ADR.",
293
+ "enum": [
294
+ "AGPL-3.0-or-later",
295
+ "AGPL-3.0-only",
296
+ "GPL-3.0-or-later",
297
+ "GPL-3.0-only",
298
+ "LGPL-3.0-or-later",
299
+ "MIT",
300
+ "Apache-2.0",
301
+ "BSD-3-Clause",
302
+ "BSD-2-Clause",
303
+ "ISC",
304
+ "MPL-2.0",
305
+ "CC0-1.0"
306
+ ],
307
+ "examples": ["AGPL-3.0-or-later", "MIT"]
308
+ },
309
+ "homepage": {
310
+ "type": "string",
311
+ "format": "uri",
312
+ "description": "Optional. Public homepage for the plugin (project page, docs site, etc.)."
313
+ },
314
+ "repository": {
315
+ "type": "string",
316
+ "format": "uri",
317
+ "description": "Optional. Source-code repository. RECOMMENDED for any plugin with trust level 'community' or 'experimental' so users can audit the source."
318
+ },
319
+ "keywords": {
320
+ "type": "array",
321
+ "description": "Optional. Discovery keywords. Federated registry surfaces these in search.",
322
+ "items": { "type": "string", "maxLength": 32 },
323
+ "maxItems": 16,
324
+ "examples": [["translation", "hiberno-english", "corporate"]]
325
+ },
326
+ "hooks": {
327
+ "type": "object",
328
+ "description": "Optional. Lifecycle hooks. Constrained: hooks may invoke ONLY shell scripts shipped inside the plugin directory, and those scripts run under an allow-list of operations (read-only filesystem access inside the plugin dir; no network; no writes outside the plugin dir; no privilege escalation; no fork/exec of unallowed binaries). The substrate's hook executor enforces the sandbox; manifests that declare hooks but whose scripts violate the allow-list at runtime have their hooks aborted and a structured 'hook_sandbox_violation' error logged. v0.1.0 ships with an executor that simply REFUSES to run hooks unless trust.level in {'official', 'verified'}; community plugins MAY declare hooks but they will be ignored until the sandbox lands in Phase 3.",
329
+ "additionalProperties": true,
330
+ "properties": {
331
+ "on_install": {
332
+ "type": "string",
333
+ "description": "Path (relative to plugin directory) to a shell script run once after plugin discovery. Same sandboxing as `provides[].path`. Idempotent: the substrate may re-run on_install if the previous run failed or if the plugin version changed.",
334
+ "pattern": "^(\\./)?[^/\\\\][^\\\\:*?\"<>|]*$",
335
+ "examples": ["./install.sh"]
336
+ },
337
+ "on_uninstall": {
338
+ "type": "string",
339
+ "description": "Path to a shell script run before plugin removal. Same sandboxing as `on_install`. The substrate runs the hook even when the user is uninstalling because a previous install failed.",
340
+ "pattern": "^(\\./)?[^/\\\\][^\\\\:*?\"<>|]*$"
341
+ }
342
+ }
343
+ },
344
+ "compatibility": {
345
+ "type": "object",
346
+ "description": "Optional. Forward-compatibility declarations. Reserved for future use; v0.1.0 loaders ignore unknown sub-keys but preserve them on round-trip.",
347
+ "additionalProperties": true
348
+ }
349
+ },
350
+ "examples": [
351
+ {
352
+ "schema_version": "0.1.0",
353
+ "name": "ie-corporate-translation",
354
+ "type": "language-pack",
355
+ "version": "0.2.1",
356
+ "description": "Hiberno-English to neutral corporate English translation prompts for ND professionals.",
357
+ "authors": [
358
+ { "name": "Eoin O'Brien", "handle": "@eobrien", "role": "maintainer" }
359
+ ],
360
+ "neurotypes": ["adhd", "audhd", "asd"],
361
+ "locale": ["en-IE", "en-GB"],
362
+ "requires": {
363
+ "substrate_version": ">=0.1.0",
364
+ "mcp_servers": [{ "name": "mcp-translation", "version": ">=0.1.0" }]
365
+ },
366
+ "provides": [
367
+ {
368
+ "type": "language-prompt-override",
369
+ "id": "translate-incoming-en-ie",
370
+ "path": "./prompts/translate-incoming.md"
371
+ }
372
+ ],
373
+ "trust": {
374
+ "level": "community",
375
+ "source_url": "https://github.com/eobrien/ie-corporate-translation"
376
+ },
377
+ "license": "AGPL-3.0-or-later",
378
+ "repository": "https://github.com/eobrien/ie-corporate-translation",
379
+ "keywords": ["translation", "hiberno-english", "corporate"]
380
+ },
381
+ {
382
+ "schema_version": "0.1.0",
383
+ "name": "minimal-example",
384
+ "type": "skill",
385
+ "version": "0.1.0",
386
+ "description": "Minimal valid plugin manifest example.",
387
+ "trust": { "level": "official" },
388
+ "license": "AGPL-3.0-or-later"
389
+ }
390
+ ]
391
+ }