@jenga-ai/agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +340 -0
  3. package/agents/ai_engineer.md +113 -0
  4. package/agents/developer.md +236 -0
  5. package/agents/scrum-master.md +349 -0
  6. package/agents/scrutiny-agent.md +137 -0
  7. package/agents/solution-assessor.md +185 -0
  8. package/agents/tester.md +339 -0
  9. package/bin/jenga.js +70 -0
  10. package/hooks/copilot_session_end.sh +29 -0
  11. package/hooks/on_session_end.sh +238 -0
  12. package/hooks/prompt_router.sh +11 -0
  13. package/hooks/prompt_router_helper.js +52 -0
  14. package/hooks/session_end_helper.js +29 -0
  15. package/hooks/session_end_watcher.sh +24 -0
  16. package/lib/commands/attach.js +47 -0
  17. package/lib/commands/init.js +207 -0
  18. package/lib/commands/start.js +16 -0
  19. package/lib/commands/status.js +53 -0
  20. package/lib/config-schema.js +72 -0
  21. package/lib/inject-settings.js +61 -0
  22. package/lib/mirror.js +244 -0
  23. package/lib/resolve-project-dir.sh +47 -0
  24. package/mcp/execute-ticket/index.js +10 -0
  25. package/mcp/execute-ticket/package.json +5 -0
  26. package/mcp/help/index.js +79 -0
  27. package/mcp/help/package.json +14 -0
  28. package/mcp/router/README.md +19 -0
  29. package/mcp/router/embedder.js +23 -0
  30. package/mcp/router/index.js +204 -0
  31. package/mcp/router/matcher.js +87 -0
  32. package/mcp/router/package-lock.json +1048 -0
  33. package/mcp/router/package.json +11 -0
  34. package/mcp/router/skill-index.js +104 -0
  35. package/package.json +47 -0
  36. package/scripts/board_resolver.sh +46 -0
  37. package/scripts/e25_s01_extract_board_graph.py +292 -0
  38. package/scripts/e25_s01_generate_synthetic_board.py +90 -0
  39. package/scripts/measurement-10x.json +50 -0
  40. package/scripts/measurement-10x.txt +4 -0
  41. package/scripts/measurement-real.json +50 -0
  42. package/scripts/measurement-real.txt +4 -0
  43. package/scripts/postinstall.js +165 -0
  44. package/scripts/todo_cleanup.sh +22 -0
  45. package/scripts/todo_manager.sh +86 -0
  46. package/scripts/validate-board.sh +190 -0
  47. package/scripts/validate-story-format.sh +53 -0
  48. package/skills/brainstorm/SKILL.md +47 -0
  49. package/skills/btw/SKILL.md +42 -0
  50. package/skills/commit/SKILL.md +29 -0
  51. package/skills/commit/assets/user_instructions_template.md +22 -0
  52. package/skills/continue/SKILL.md +29 -0
  53. package/skills/convert/SKILL.md +124 -0
  54. package/skills/convert/convert_cli.py +235 -0
  55. package/skills/convert/tests/sample.csv +4 -0
  56. package/skills/convert/tests/sample.json +5 -0
  57. package/skills/convert/tests/sample.jsonl +3 -0
  58. package/skills/convert/tests/sample.yaml +18 -0
  59. package/skills/convert/tests/sample_obj.csv +2 -0
  60. package/skills/convert/tests/sample_obj.json +9 -0
  61. package/skills/deep-dive/SKILL.md +167 -0
  62. package/skills/do/SKILL.md +88 -0
  63. package/skills/do/assets/sender_template.json +12 -0
  64. package/skills/doc/SKILL.md +314 -0
  65. package/skills/doc/assets/path-objectives.yaml +38 -0
  66. package/skills/doc-sync/SKILL.md +167 -0
  67. package/skills/doc-sync/assets/default_excludes.txt +21 -0
  68. package/skills/doc-sync/assets/doc_targets.md +14 -0
  69. package/skills/dooo/SKILL.md +60 -0
  70. package/skills/error/SKILL.md +29 -0
  71. package/skills/evaluate/SKILL.md +45 -0
  72. package/skills/evaluate/assets/evaluation_invokation_template.yml +3 -0
  73. package/skills/evaluate/assets/evaluation_rapport_template.md +24 -0
  74. package/skills/examplify/SKILL.md +42 -0
  75. package/skills/help/SKILL.md +36 -0
  76. package/skills/improve/SKILL.md +55 -0
  77. package/skills/index/scripts/board-index +4 -0
  78. package/skills/index/scripts/board_index.py +615 -0
  79. package/skills/index/scripts/smoke_test.sh +86 -0
  80. package/skills/init/SKILL.md +44 -0
  81. package/skills/init/assets/.gitignore_template +15 -0
  82. package/skills/init/assets/PROJECT_SUMMARY_template.md +13 -0
  83. package/skills/init/assets/directory_structure.txt +13 -0
  84. package/skills/init/assets/test-config_template.json +4 -0
  85. package/skills/init/assets/workflow_template.json +30 -0
  86. package/skills/init/scripts/init.sh +48 -0
  87. package/skills/jbp/SKILL.md +25 -0
  88. package/skills/jenga/SKILL.md +68 -0
  89. package/skills/lgtm/SKILL.md +21 -0
  90. package/skills/mirror-public/SKILL.md +237 -0
  91. package/skills/mirror-public/assets/config.json +5 -0
  92. package/skills/mirror-public/scripts/mirror.sh +374 -0
  93. package/skills/pi-plan/SKILL.md +62 -0
  94. package/skills/pi-plan/assets/epic.json +7 -0
  95. package/skills/pi-plan/assets/story_template.md +18 -0
  96. package/skills/proceed/SKILL.md +29 -0
  97. package/skills/publish/SKILL.md +351 -0
  98. package/skills/publish/adapters/droplet.md +200 -0
  99. package/skills/publish/adapters/mobile-ios.md +114 -0
  100. package/skills/publish/adapters/npm-ci.md +223 -0
  101. package/skills/publish/adapters/npm.md +121 -0
  102. package/skills/publish/assets/ExportOptions.plist.template +19 -0
  103. package/skills/publish/assets/ci-contract.md +111 -0
  104. package/skills/publish/assets/ownership-matrix.md +17 -0
  105. package/skills/publish/assets/publish.example.json +85 -0
  106. package/skills/publish/assets/publish.example.npm-ci.json +40 -0
  107. package/skills/publish/assets/publish.example.npm.json +41 -0
  108. package/skills/publish/assets/secrets-guide.md +104 -0
  109. package/skills/publish/schemas/fixtures/npm-ci-minimal.json +17 -0
  110. package/skills/publish/schemas/fixtures/npm-ci-with-empty-secrets.json +18 -0
  111. package/skills/publish/schemas/fixtures/npm-ci-with-workflow-path.json +18 -0
  112. package/skills/publish/schemas/publish.schema.json +428 -0
  113. package/skills/publish/scripts/check_target_config.sh +96 -0
  114. package/skills/publish/scripts/droplet_pipeline.sh +208 -0
  115. package/skills/publish/scripts/generate_release_notes.sh +200 -0
  116. package/skills/publish/scripts/ios_pipeline.sh +486 -0
  117. package/skills/publish/scripts/npm_ci_pipeline.sh +225 -0
  118. package/skills/publish/scripts/npm_pipeline.sh +249 -0
  119. package/skills/publish/scripts/publish_common.sh +253 -0
  120. package/skills/publish/scripts/publish_deploy.sh +538 -0
  121. package/skills/publish/scripts/reconcile_tags.sh +135 -0
  122. package/skills/publish/scripts/run_gates.sh +616 -0
  123. package/skills/publish/scripts/setup_wizard.sh +394 -0
  124. package/skills/publish/scripts/show_history.sh +95 -0
  125. package/skills/publish/scripts/suggest_semver_bump.sh +105 -0
  126. package/skills/publish/scripts/validate_config.sh +163 -0
  127. package/skills/publish/scripts/validate_droplet_env.sh +45 -0
  128. package/skills/publish/scripts/validate_ios_env.sh +68 -0
  129. package/skills/publish/scripts/validate_npm_ci_env.sh +71 -0
  130. package/skills/publish/scripts/validate_npm_env.sh +22 -0
  131. package/skills/publish/scripts/write_ledger_entry.sh +126 -0
  132. package/skills/publish/wizards/droplet.md +275 -0
  133. package/skills/publish/wizards/mobile-ios.md +157 -0
  134. package/skills/publish/wizards/npm-ci.md +240 -0
  135. package/skills/publish/wizards/npm.md +224 -0
  136. package/skills/reconcile/SKILL.md +93 -0
  137. package/skills/reconcile/assets/report_format.md +44 -0
  138. package/skills/reconcile-origin/SKILL.md +75 -0
  139. package/skills/reconcile-origin/scripts/reconcile-origin.sh +372 -0
  140. package/skills/redo/SKILL.md +70 -0
  141. package/skills/route/SKILL.md +180 -0
  142. package/skills/self-sync/SKILL.md +73 -0
  143. package/skills/self-sync/scripts/run.js +136 -0
  144. package/skills/skillify/SKILL.md +68 -0
  145. package/skills/skillify/assets/init-new/SKILL.md +35 -0
  146. package/skills/skillify/assets/init-new/assets/.gitignore_template +15 -0
  147. package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +13 -0
  148. package/skills/skillify/assets/init-new/assets/directory_structure.txt +10 -0
  149. package/skills/skillify/assets/init-new/assets/test-config_template.json +4 -0
  150. package/skills/skillify/assets/init-new/assets/workflow_template.json +17 -0
  151. package/skills/skillify/assets/init-new/scripts/init.sh +48 -0
  152. package/skills/skillify/assets/init-old/SKILL.md +124 -0
  153. package/skills/spinoff/SKILL.md +48 -0
  154. package/skills/status/SKILL.md +33 -0
  155. package/skills/status/assets/output_format.md +41 -0
  156. package/skills/todo/SKILL.md +46 -0
  157. package/skills/todo/assets/todo_handoff_template.md +22 -0
  158. package/skills/todo/assets/todo_template.md +3 -0
  159. package/skills/train/SKILL.md +116 -0
  160. package/skills/train/assets/dashboard-templates/classifiers.html +106 -0
  161. package/skills/train/assets/dashboard-templates/nlp.html +102 -0
  162. package/skills/train/assets/dashboard-templates/transformers.html +98 -0
  163. package/skills/train/assets/results-parsers/__init__.py +9 -0
  164. package/skills/train/assets/results-parsers/classifiers.py +84 -0
  165. package/skills/train/assets/results-parsers/nlp.py +88 -0
  166. package/skills/train/assets/results-parsers/reporter.py +154 -0
  167. package/skills/train/assets/results-parsers/transformers.py +120 -0
  168. package/skills/train/train_cli.py +786 -0
  169. package/templates/EXECUTION_PLAN_TEMPLATE.md +43 -0
  170. package/templates/EXECUTION_SUMMARY_TEMPLATE.md +50 -0
  171. package/templates/JENGA_CONFIG_TEMPLATE.json +23 -0
  172. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +88 -0
  173. package/templates/SCRUM_BOARD_SCHEMA.md +311 -0
  174. package/templates/SKILL.md +16 -0
  175. package/templates/SKILL_TEMPLATE.md +28 -0
  176. package/templates/USER_INSTRUCTIONS_TEMPLATE.md +22 -0
  177. package/templates/copilot-instructions.md.tpl +55 -0
@@ -0,0 +1,104 @@
1
+ # Publish secrets guide
2
+
3
+ ## Never do this
4
+
5
+ Never commit secrets, tokens, certificates, API keys, or `.env` files into git.
6
+ Use gitignored local files or secret stores instead.
7
+
8
+ ```gitignore
9
+ .env
10
+ .env.*
11
+ *.local
12
+ ```
13
+
14
+ If a value is sensitive, keep the real value outside the repository and store only its environment-variable name in `publish.json`.
15
+
16
+ ## Local development
17
+
18
+ For local-only setup, keep secrets in a gitignored `.env` file or shell profile and load them before running `/publish`.
19
+ If you already use `direnv`, prefer a gitignored `.envrc` or sourced env file so the variables are loaded automatically when you enter the project.
20
+
21
+ Example:
22
+
23
+ ```bash
24
+ export APP_STORE_CONNECT_API_KEY_ID=ABC123XYZ
25
+ export IOS_PROVISIONING_PROFILE_UUID=11111111-2222-3333-4444-555555555555
26
+ ```
27
+
28
+ ## macOS Keychain
29
+
30
+ Use the macOS Keychain when you do not want long-lived secret values sitting in plain text files.
31
+ Store a secret:
32
+
33
+ ```bash
34
+ security add-generic-password -a "$USER" -s publish.app-store-connect.api-key-id -w '<value>'
35
+ ```
36
+
37
+ Read it later into an environment variable:
38
+
39
+ ```bash
40
+ export APP_STORE_CONNECT_API_KEY_ID="$(security find-generic-password -a "$USER" -s publish.app-store-connect.api-key-id -w)"
41
+ ```
42
+
43
+ You can repeat the same pattern for provisioning-profile references or other deploy credentials.
44
+
45
+ ## GitHub Actions / CI secrets
46
+
47
+ Store shared secrets in repository or organization secrets, then map them to job-level environment variables.
48
+ Reference them in workflow files with `${{ secrets.NAME }}`.
49
+
50
+ Example:
51
+
52
+ ```yaml
53
+ env:
54
+ APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
55
+ IOS_PROVISIONING_PROFILE_UUID: ${{ secrets.IOS_PROVISIONING_PROFILE_UUID }}
56
+ ```
57
+
58
+ Use repository secrets for project-specific values and organization secrets for centrally managed credentials that multiple repositories share.
59
+
60
+ ## Droplet (GitHub Actions → SSH)
61
+
62
+ The `droplet` adapter reads all SSH credentials from GitHub Actions secrets at workflow runtime. Your `publish.json` stores only the *names* of the secrets — never the values.
63
+
64
+ ### Required secrets
65
+
66
+ | Secret name | Value |
67
+ |---|---|
68
+ | `DROPLET_SSH_HOST` | IP address or FQDN of your target host |
69
+ | `DROPLET_SSH_USER` | SSH login username (e.g. `ubuntu`, `deploy`) |
70
+ | `DROPLET_SSH_PORT` | SSH port number (usually `22`) |
71
+ | `DROPLET_SSH_KEY` | Contents of the private key used to SSH into the host |
72
+ | `DROPLET_KNOWN_HOSTS` | Output of `ssh-keyscan -H <host>` |
73
+
74
+ ### How to add secrets
75
+
76
+ Via the `gh` CLI (recommended):
77
+
78
+ ```bash
79
+ gh secret set DROPLET_SSH_HOST --body "1.2.3.4" --repo owner/myrepo
80
+ gh secret set DROPLET_SSH_USER --body "deploy" --repo owner/myrepo
81
+ gh secret set DROPLET_SSH_PORT --body "22" --repo owner/myrepo
82
+ gh secret set DROPLET_SSH_KEY < ~/.ssh/id_deploy --repo owner/myrepo
83
+ ssh-keyscan -H 1.2.3.4 | gh secret set DROPLET_KNOWN_HOSTS --repo owner/myrepo
84
+ ```
85
+
86
+ Via the GitHub UI: **Repository → Settings → Secrets and variables → Actions → New repository secret**.
87
+
88
+ ### Generating the SSH key pair
89
+
90
+ If you don't have a dedicated deploy key:
91
+
92
+ ```bash
93
+ ssh-keygen -t ed25519 -C "deploy@myrepo" -f ~/.ssh/id_deploy -N ""
94
+ # Add the public key to the host's authorized_keys:
95
+ ssh-copy-id -i ~/.ssh/id_deploy.pub deploy@1.2.3.4
96
+ # Then add the private key to GitHub secrets (see above)
97
+ ```
98
+
99
+ Never commit the private key to the repository.
100
+
101
+ ## General rule
102
+
103
+ Secrets in `publish.json` are always environment-variable references such as `APP_STORE_CONNECT_API_KEY_ID`.
104
+ Do not put literal secret values in `publish.json`, shell scripts, commit messages, or logs.
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": 1,
3
+ "defaults": {
4
+ "history_file": "project/logs/publish-history.json",
5
+ "mandatory_checks": ["build-ok"]
6
+ },
7
+ "targets": [
8
+ {
9
+ "name": "my-pkg",
10
+ "type": "npm-ci",
11
+ "platform": "npm-ci-oidc",
12
+ "checks": {},
13
+ "npm": { "package_name": "my-pkg", "access": "public" },
14
+ "github_repo": "owner/my-pkg"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 1,
3
+ "defaults": {
4
+ "history_file": "project/logs/publish-history.json",
5
+ "mandatory_checks": ["build-ok"]
6
+ },
7
+ "targets": [
8
+ {
9
+ "name": "my-pkg",
10
+ "type": "npm-ci",
11
+ "platform": "npm-ci-oidc",
12
+ "checks": {},
13
+ "secrets": {},
14
+ "npm": { "package_name": "my-pkg", "access": "public" },
15
+ "github_repo": "owner/my-pkg"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 1,
3
+ "defaults": {
4
+ "history_file": "project/logs/publish-history.json",
5
+ "mandatory_checks": ["build-ok"]
6
+ },
7
+ "targets": [
8
+ {
9
+ "name": "my-pkg",
10
+ "type": "npm-ci",
11
+ "platform": "npm-ci-oidc",
12
+ "checks": {},
13
+ "npm": { "package_name": "my-pkg", "access": "public" },
14
+ "github_repo": "owner/my-pkg",
15
+ "workflow_path": ".github/workflows/release.yml"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,428 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://jenga.local/skills/publish/schemas/publish.schema.json",
4
+ "title": "Publish Skill Configuration",
5
+ "description": "Schema for project/configs/publish.json used by the /publish skill.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "version",
10
+ "defaults",
11
+ "targets"
12
+ ],
13
+ "properties": {
14
+ "version": {
15
+ "type": "integer",
16
+ "enum": [1]
17
+ },
18
+ "defaults": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": [
22
+ "history_file",
23
+ "mandatory_checks"
24
+ ],
25
+ "properties": {
26
+ "history_file": {
27
+ "type": "string",
28
+ "minLength": 1
29
+ },
30
+ "mandatory_checks": {
31
+ "type": "array",
32
+ "minItems": 1,
33
+ "uniqueItems": true,
34
+ "items": {
35
+ "$ref": "#/definitions/checkName"
36
+ }
37
+ },
38
+ "optional_checks": {
39
+ "type": "array",
40
+ "uniqueItems": true,
41
+ "items": {
42
+ "$ref": "#/definitions/checkName"
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "targets": {
48
+ "type": "array",
49
+ "minItems": 1,
50
+ "items": {
51
+ "$ref": "#/definitions/target"
52
+ }
53
+ }
54
+ },
55
+ "definitions": {
56
+ "checkName": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
60
+ },
61
+ "envVarRef": {
62
+ "type": "string",
63
+ "pattern": "^\\$\\{?[A-Z][A-Z0-9_]*\\}?$"
64
+ },
65
+ "gateEntry": {
66
+ "oneOf": [
67
+ {
68
+ "$ref": "#/definitions/checkName"
69
+ },
70
+ {
71
+ "type": "object",
72
+ "additionalProperties": true,
73
+ "properties": {
74
+ "name": { "$ref": "#/definitions/checkName" },
75
+ "gate": { "$ref": "#/definitions/checkName" },
76
+ "type": { "$ref": "#/definitions/checkName" }
77
+ },
78
+ "anyOf": [
79
+ { "required": ["name"] },
80
+ { "required": ["gate"] },
81
+ { "required": ["type"] }
82
+ ]
83
+ }
84
+ ]
85
+ },
86
+ "gateList": {
87
+ "type": "array",
88
+ "items": {
89
+ "$ref": "#/definitions/gateEntry"
90
+ }
91
+ },
92
+ "checksObject": {
93
+ "type": "object",
94
+ "additionalProperties": false,
95
+ "properties": {
96
+ "pre": {
97
+ "$ref": "#/definitions/gateList"
98
+ },
99
+ "post": {
100
+ "$ref": "#/definitions/gateList"
101
+ }
102
+ }
103
+ },
104
+ "iosSecretMap": {
105
+ "type": "object",
106
+ "additionalProperties": false,
107
+ "required": [
108
+ "app_store_connect_api_key_id",
109
+ "app_store_connect_issuer_id",
110
+ "app_store_connect_private_key_path",
111
+ "code_sign_identity",
112
+ "provisioning_profile_uuid"
113
+ ],
114
+ "properties": {
115
+ "app_store_connect_api_key_id": { "$ref": "#/definitions/envVarRef" },
116
+ "app_store_connect_issuer_id": { "$ref": "#/definitions/envVarRef" },
117
+ "app_store_connect_private_key_path": { "$ref": "#/definitions/envVarRef" },
118
+ "code_sign_identity": { "$ref": "#/definitions/envVarRef" },
119
+ "provisioning_profile_uuid": { "$ref": "#/definitions/envVarRef" }
120
+ }
121
+ },
122
+ "npmSecretMap": {
123
+ "type": "object",
124
+ "additionalProperties": false,
125
+ "properties": {
126
+ "NPM_TOKEN": { "$ref": "#/definitions/envVarRef" },
127
+ "NODE_AUTH_TOKEN": { "$ref": "#/definitions/envVarRef" }
128
+ },
129
+ "anyOf": [
130
+ { "required": ["NPM_TOKEN"] },
131
+ { "required": ["NODE_AUTH_TOKEN"] }
132
+ ]
133
+ },
134
+ "iosSettings": {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "required": [
138
+ "scheme",
139
+ "configuration",
140
+ "archive_path",
141
+ "export_path",
142
+ "export_method",
143
+ "bundle_id",
144
+ "team_id",
145
+ "app_store_app_id"
146
+ ],
147
+ "properties": {
148
+ "project_path": {
149
+ "type": "string",
150
+ "minLength": 1
151
+ },
152
+ "workspace_path": {
153
+ "type": "string",
154
+ "minLength": 1
155
+ },
156
+ "scheme": {
157
+ "type": "string",
158
+ "minLength": 1
159
+ },
160
+ "configuration": {
161
+ "type": "string",
162
+ "minLength": 1
163
+ },
164
+ "archive_path": {
165
+ "type": "string",
166
+ "minLength": 1
167
+ },
168
+ "export_path": {
169
+ "type": "string",
170
+ "minLength": 1
171
+ },
172
+ "export_method": {
173
+ "type": "string",
174
+ "enum": ["ad-hoc", "app-store"]
175
+ },
176
+ "bundle_id": {
177
+ "type": "string",
178
+ "minLength": 1
179
+ },
180
+ "team_id": {
181
+ "type": "string",
182
+ "pattern": "^[A-Z0-9]{6,}$"
183
+ },
184
+ "app_store_app_id": {
185
+ "type": "string",
186
+ "pattern": "^[0-9]+$"
187
+ },
188
+ "provider_short_name": {
189
+ "type": "string",
190
+ "minLength": 1
191
+ },
192
+ "destination": {
193
+ "type": "string",
194
+ "minLength": 1
195
+ },
196
+ "sdk": {
197
+ "type": "string",
198
+ "minLength": 1
199
+ }
200
+ },
201
+ "oneOf": [
202
+ { "required": ["project_path"] },
203
+ { "required": ["workspace_path"] }
204
+ ]
205
+ },
206
+ "npmSettings": {
207
+ "description": "Settings for an npm publish target. `package_name` and `access` are required; `registry` and `dist_tag` are optional with documented defaults.",
208
+ "type": "object",
209
+ "additionalProperties": false,
210
+ "required": [
211
+ "package_name",
212
+ "access"
213
+ ],
214
+ "properties": {
215
+ "package_name": {
216
+ "description": "The npm package name as it will appear in the registry. Supports scoped (`@scope/name`) and unscoped names. Must be lowercase, url-safe, and follow npm naming rules.",
217
+ "type": "string",
218
+ "minLength": 1,
219
+ "maxLength": 214,
220
+ "pattern": "^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$"
221
+ },
222
+ "access": {
223
+ "description": "Registry access level. `public` publishes an openly-installable package; `restricted` publishes a private package (paid npm/GitHub Packages).",
224
+ "type": "string",
225
+ "enum": ["public", "restricted"]
226
+ },
227
+ "registry": {
228
+ "description": "Registry URL to publish to. Defaults to the public npm registry when omitted by the pipeline.",
229
+ "type": "string",
230
+ "format": "uri",
231
+ "minLength": 1,
232
+ "default": "https://registry.npmjs.org"
233
+ },
234
+ "dist_tag": {
235
+ "description": "npm dist-tag applied to this publish (e.g. `latest`, `next`, `beta`). Defaults to `latest` when omitted by the pipeline.",
236
+ "type": "string",
237
+ "minLength": 1,
238
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
239
+ "default": "latest"
240
+ }
241
+ }
242
+ },
243
+ "dropletSecretMap": {
244
+ "description": "Secret name references for a droplet SSH target. Values are GitHub Actions secret names, not the secret values themselves.",
245
+ "type": "object",
246
+ "additionalProperties": false,
247
+ "required": [
248
+ "ssh_key_secret",
249
+ "known_hosts_secret"
250
+ ],
251
+ "properties": {
252
+ "ssh_key_secret": {
253
+ "description": "Name of the GitHub Actions secret that holds the SSH private key (e.g. DROPLET_SSH_KEY).",
254
+ "type": "string",
255
+ "minLength": 1
256
+ },
257
+ "known_hosts_secret": {
258
+ "description": "Name of the GitHub Actions secret that holds the known_hosts entry for the target host (e.g. DROPLET_KNOWN_HOSTS).",
259
+ "type": "string",
260
+ "minLength": 1
261
+ }
262
+ }
263
+ },
264
+ "dropletSettings": {
265
+ "description": "Settings for a droplet (or any SSH-reachable Linux host) deploy target. Deployment is orchestrated via a generated GitHub Actions workflow that SSHes into the host.",
266
+ "type": "object",
267
+ "additionalProperties": false,
268
+ "required": [
269
+ "host",
270
+ "ssh_user",
271
+ "deploy_path",
272
+ "github_repo",
273
+ "deploy_branch",
274
+ "start_cmd"
275
+ ],
276
+ "properties": {
277
+ "host": {
278
+ "description": "IP address or FQDN of the target host.",
279
+ "type": "string",
280
+ "minLength": 1
281
+ },
282
+ "ssh_user": {
283
+ "description": "SSH username used to connect to the target host.",
284
+ "type": "string",
285
+ "minLength": 1
286
+ },
287
+ "ssh_port": {
288
+ "description": "SSH port on the target host. Defaults to 22 when omitted.",
289
+ "type": "integer",
290
+ "default": 22
291
+ },
292
+ "deploy_path": {
293
+ "description": "Absolute path on the remote host where the repository is checked out.",
294
+ "type": "string",
295
+ "minLength": 1
296
+ },
297
+ "github_repo": {
298
+ "description": "GitHub repository in owner/name format. Used to trigger the workflow via gh CLI.",
299
+ "type": "string",
300
+ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$"
301
+ },
302
+ "deploy_branch": {
303
+ "description": "Git branch to pull and deploy on the remote host.",
304
+ "type": "string",
305
+ "minLength": 1
306
+ },
307
+ "build_cmd": {
308
+ "description": "Optional build command to run on the remote host after pulling (e.g. npm run build). Omit or leave empty to skip the build step.",
309
+ "type": "string",
310
+ "minLength": 1
311
+ },
312
+ "start_cmd": {
313
+ "description": "Command to (re)start the service on the remote host after deploy (e.g. pm2 reload ecosystem.config.js, sudo systemctl restart myapp).",
314
+ "type": "string",
315
+ "minLength": 1
316
+ }
317
+ }
318
+ },
319
+ "target": {
320
+ "type": "object",
321
+ "additionalProperties": false,
322
+ "required": [
323
+ "name",
324
+ "type",
325
+ "platform",
326
+ "checks"
327
+ ],
328
+ "properties": {
329
+ "name": {
330
+ "type": "string",
331
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
332
+ },
333
+ "type": {
334
+ "type": "string",
335
+ "enum": ["mobile-ios", "npm", "droplet", "npm-ci"]
336
+ },
337
+ "platform": {
338
+ "type": "string",
339
+ "enum": ["ios-app-store", "npm-registry", "github-packages", "droplet-ssh", "npm-ci-oidc"]
340
+ },
341
+ "checks": {
342
+ "$ref": "#/definitions/checksObject"
343
+ },
344
+ "secrets": {
345
+ "type": "object"
346
+ },
347
+ "ios": {
348
+ "$ref": "#/definitions/iosSettings"
349
+ },
350
+ "npm": {
351
+ "$ref": "#/definitions/npmSettings"
352
+ },
353
+ "droplet": {
354
+ "$ref": "#/definitions/dropletSettings"
355
+ },
356
+ "github_repo": {
357
+ "description": "GitHub repository in owner/name format. Required for npm-ci targets; used to identify the repository that hosts the trusted-publisher workflow.",
358
+ "type": "string",
359
+ "pattern": "^[^/]+/[^/]+$"
360
+ },
361
+ "workflow_path": {
362
+ "description": "Path within the repository to the GitHub Actions workflow file that publishes to npm. Defaults to .github/workflows/npm-publish.yml.",
363
+ "type": "string",
364
+ "minLength": 1,
365
+ "default": ".github/workflows/npm-publish.yml"
366
+ },
367
+ "notes": {
368
+ "type": "string"
369
+ }
370
+ },
371
+ "allOf": [
372
+ {
373
+ "if": {
374
+ "properties": { "type": { "const": "mobile-ios" } },
375
+ "required": ["type"]
376
+ },
377
+ "then": {
378
+ "required": ["ios", "secrets"],
379
+ "not": { "required": ["npm"] },
380
+ "properties": {
381
+ "platform": { "const": "ios-app-store" },
382
+ "secrets": { "$ref": "#/definitions/iosSecretMap" }
383
+ }
384
+ }
385
+ },
386
+ {
387
+ "if": {
388
+ "properties": { "type": { "const": "npm" } },
389
+ "required": ["type"]
390
+ },
391
+ "then": {
392
+ "required": ["npm", "secrets"],
393
+ "not": { "required": ["ios"] },
394
+ "properties": {
395
+ "platform": { "enum": ["npm-registry", "github-packages"] },
396
+ "secrets": { "$ref": "#/definitions/npmSecretMap" }
397
+ }
398
+ }
399
+ },
400
+ {
401
+ "if": {
402
+ "properties": { "type": { "const": "droplet" } },
403
+ "required": ["type"]
404
+ },
405
+ "then": {
406
+ "required": ["droplet", "secrets"],
407
+ "properties": {
408
+ "platform": { "const": "droplet-ssh" },
409
+ "secrets": { "$ref": "#/definitions/dropletSecretMap" }
410
+ }
411
+ }
412
+ },
413
+ {
414
+ "if": {
415
+ "properties": { "type": { "const": "npm-ci" } },
416
+ "required": ["type"]
417
+ },
418
+ "then": {
419
+ "required": ["npm", "github_repo"],
420
+ "properties": {
421
+ "platform": { "const": "npm-ci-oidc" }
422
+ }
423
+ }
424
+ }
425
+ ]
426
+ }
427
+ }
428
+ }
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+
4
+ TARGET_NAME="${1:-}"
5
+ CONFIG_PATH="${2:-}"
6
+ EXIT_CONFIG_INVALID=4
7
+
8
+ fail() {
9
+ printf 'target config check failed: %s\n' "$1" >&2
10
+ exit "$EXIT_CONFIG_INVALID"
11
+ }
12
+
13
+ [[ -n "$TARGET_NAME" ]] || fail "missing target name argument"
14
+ [[ -n "$CONFIG_PATH" ]] || fail "missing publish.json path argument"
15
+ [[ -f "$CONFIG_PATH" ]] || fail "publish.json not found at '$CONFIG_PATH'"
16
+ command -v jq >/dev/null 2>&1 || fail "jq is required"
17
+ jq -e . "$CONFIG_PATH" >/dev/null 2>&1 || fail "publish.json is not valid JSON"
18
+
19
+ TARGET_JSON="$(jq -c --arg name "$TARGET_NAME" '.targets[]? | select(.name == $name)' "$CONFIG_PATH")"
20
+ [[ -n "$TARGET_JSON" ]] || fail "target '$TARGET_NAME' was not found in '$CONFIG_PATH'"
21
+
22
+ TARGET_TYPE="$(printf '%s\n' "$TARGET_JSON" | jq -r '.type // empty')"
23
+ [[ -n "$TARGET_TYPE" ]] || fail "target '$TARGET_NAME' is missing a type"
24
+
25
+ case "$TARGET_TYPE" in
26
+ mobile-ios)
27
+ MISSING="$(printf '%s\n' "$TARGET_JSON" | jq -r '
28
+ def non_empty($value):
29
+ ($value | type) == "string" and (($value | length) > 0);
30
+ def env_ref($value):
31
+ ($value | type) == "string" and ($value | test("^\\$\\{?[A-Z][A-Z0-9_]*\\}?$"));
32
+ def missing_string($path; $value):
33
+ if non_empty($value // "") then empty else $path end;
34
+ def missing_env_ref($path; $value):
35
+ if env_ref($value // "") then empty else $path end;
36
+ [
37
+ (if .platform == "ios-app-store" then empty else "platform" end),
38
+ (if (.checks | type) == "object" then empty else "checks" end),
39
+ (if (.checks.pre | type) == "array" then empty else "checks.pre" end),
40
+ (if (.checks.post | type) == "array" then empty else "checks.post" end),
41
+ (missing_env_ref("secrets.app_store_connect_api_key_id"; .secrets.app_store_connect_api_key_id)),
42
+ (missing_env_ref("secrets.app_store_connect_issuer_id"; .secrets.app_store_connect_issuer_id)),
43
+ (missing_env_ref("secrets.app_store_connect_private_key_path"; .secrets.app_store_connect_private_key_path)),
44
+ (missing_env_ref("secrets.code_sign_identity"; .secrets.code_sign_identity)),
45
+ (missing_env_ref("secrets.provisioning_profile_uuid"; .secrets.provisioning_profile_uuid)),
46
+ (missing_string("ios.scheme"; .ios.scheme)),
47
+ (missing_string("ios.configuration"; .ios.configuration)),
48
+ (missing_string("ios.archive_path"; .ios.archive_path)),
49
+ (missing_string("ios.export_path"; .ios.export_path)),
50
+ (if (.ios.export_method == "ad-hoc" or .ios.export_method == "app-store") then empty else "ios.export_method" end),
51
+ (missing_string("ios.bundle_id"; .ios.bundle_id)),
52
+ (if ((.ios.team_id // "") | type) == "string" and ((.ios.team_id // "") | test("^[A-Z0-9]{6,}$")) then empty else "ios.team_id" end),
53
+ (if ((.ios.app_store_app_id // "") | type) == "string" and ((.ios.app_store_app_id // "") | test("^[0-9]+$")) then empty else "ios.app_store_app_id" end),
54
+ (if ((.ios.project_path // "") | length) > 0 or ((.ios.workspace_path // "") | length) > 0 then empty else "ios.project_path|ios.workspace_path" end)
55
+ ]
56
+ | map(select(. != null and . != ""))
57
+ | .[]
58
+ ' 2>/dev/null || true)"
59
+ ;;
60
+ npm-ci)
61
+ MISSING="$(printf '%s\n' "$TARGET_JSON" | jq -r '
62
+ def non_empty($value):
63
+ ($value | type) == "string" and (($value | length) > 0);
64
+ def missing_string($path; $value):
65
+ if non_empty($value // "") then empty else $path end;
66
+ [
67
+ (missing_string("github_repo"; .github_repo)),
68
+ (if (.npm | type) == "object" then empty else "npm" end),
69
+ (missing_string("npm.package_name"; .npm.package_name)),
70
+ (if ((.npm.access? // "") == "public" or (.npm.access? // "") == "restricted") then empty else "npm.access" end)
71
+ ]
72
+ | map(select(. != null and . != ""))
73
+ | .[]
74
+ ' 2>/dev/null || true)"
75
+ ;;
76
+ *)
77
+ fail "unsupported target type '$TARGET_TYPE' for target '$TARGET_NAME'"
78
+ ;;
79
+ esac
80
+
81
+ if [[ -n "$MISSING" ]]; then
82
+ MESSAGE=""
83
+ while IFS= read -r item; do
84
+ [[ -n "$item" ]] || continue
85
+ if [[ -z "$MESSAGE" ]]; then
86
+ MESSAGE="$item"
87
+ else
88
+ MESSAGE="$MESSAGE, $item"
89
+ fi
90
+ done <<EOF_MISSING
91
+ $MISSING
92
+ EOF_MISSING
93
+ fail "target '$TARGET_NAME' is incomplete. Missing or invalid: $MESSAGE"
94
+ fi
95
+
96
+ exit 0