@codyswann/lisa 2.316.2 → 2.317.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +139 -9
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +17 -0
  8. package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +23 -0
  9. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +33 -0
  10. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -0
  11. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +17 -0
  12. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/SKILL.md +106 -0
  13. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
  14. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  15. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +137 -51
  16. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  17. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  18. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  19. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  20. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  21. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  22. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  23. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  24. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  25. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +69 -0
  26. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  27. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +150 -0
  28. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
  29. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  30. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  31. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  32. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  33. package/plugins/lisa/commands/implement.md +5 -3
  34. package/plugins/lisa/commands/setup/remote-env.md +7 -0
  35. package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +17 -0
  36. package/plugins/lisa/skills/lisa-automation-status/SKILL.md +23 -0
  37. package/plugins/lisa/skills/lisa-doctor/SKILL.md +33 -0
  38. package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -0
  39. package/plugins/lisa/skills/lisa-notion-access/SKILL.md +17 -0
  40. package/plugins/lisa/skills/lisa-remote-dispatch/SKILL.md +106 -0
  41. package/plugins/lisa/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
  42. package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  43. package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +138 -52
  44. package/plugins/lisa/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  45. package/plugins/lisa/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  46. package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  47. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  48. package/plugins/lisa/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  49. package/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  50. package/plugins/lisa/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  51. package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  52. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  53. package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +69 -0
  54. package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  55. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +150 -0
  56. package/plugins/lisa/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
  57. package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  58. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  59. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  60. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  61. package/plugins/lisa-agy/commands/lisa/implement.md +5 -3
  62. package/plugins/lisa-agy/commands/lisa/setup/remote-env.md +7 -0
  63. package/plugins/lisa-agy/plugin.json +1 -1
  64. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +17 -0
  65. package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +23 -0
  66. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +33 -0
  67. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -0
  68. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +17 -0
  69. package/plugins/lisa-agy/skills/lisa-remote-dispatch/SKILL.md +106 -0
  70. package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  71. package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +138 -52
  72. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  73. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  74. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  75. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  76. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  77. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  78. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  79. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  80. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  81. package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +69 -0
  82. package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  83. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +150 -0
  84. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  85. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  86. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  87. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  88. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  91. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-copilot/commands/lisa/implement.md +5 -3
  95. package/plugins/lisa-copilot/commands/lisa/setup/remote-env.md +7 -0
  96. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +17 -0
  97. package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +23 -0
  98. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +33 -0
  99. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -0
  100. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +17 -0
  101. package/plugins/lisa-copilot/skills/lisa-remote-dispatch/SKILL.md +106 -0
  102. package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  103. package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +138 -52
  104. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  105. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  106. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  107. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  108. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  109. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  110. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  111. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  112. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  113. package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +69 -0
  114. package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  115. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +150 -0
  116. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  117. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  118. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  119. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  120. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-cursor/commands/lisa/implement.md +5 -3
  122. package/plugins/lisa-cursor/commands/lisa/setup/remote-env.md +7 -0
  123. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +17 -0
  124. package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +23 -0
  125. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +33 -0
  126. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -0
  127. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +17 -0
  128. package/plugins/lisa-cursor/skills/lisa-remote-dispatch/SKILL.md +106 -0
  129. package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  130. package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +138 -52
  131. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  132. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  133. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  134. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  135. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  136. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  137. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  138. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  139. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  140. package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +69 -0
  141. package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  142. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +150 -0
  143. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  144. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  145. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  146. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  147. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  148. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  149. package/plugins/lisa-expo-agy/plugin.json +1 -1
  150. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  151. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  152. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  154. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  155. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  156. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  157. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  158. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  159. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  160. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  161. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  163. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  164. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  165. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  166. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  167. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  168. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  169. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  170. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  171. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  172. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  173. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  174. package/plugins/lisa-rails-agy/plugin.json +1 -1
  175. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  176. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  177. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  178. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  179. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  180. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  181. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  182. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  183. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  184. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  185. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  186. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  187. package/plugins/src/base/commands/implement.md +5 -3
  188. package/plugins/src/base/commands/setup/remote-env.md +7 -0
  189. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +17 -0
  190. package/plugins/src/base/skills/lisa-automation-status/SKILL.md +23 -0
  191. package/plugins/src/base/skills/lisa-doctor/SKILL.md +33 -0
  192. package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -0
  193. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +17 -0
  194. package/plugins/src/base/skills/lisa-remote-dispatch/SKILL.md +106 -0
  195. package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  196. package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +138 -52
  197. package/plugins/src/base/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  198. package/plugins/src/base/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  199. package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  200. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  201. package/plugins/src/base/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  202. package/plugins/src/base/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  203. package/plugins/src/base/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  204. package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  205. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  206. package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +69 -0
  207. package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  208. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +150 -0
  209. package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  210. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  211. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  212. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Decide what a remote environment already has and what it still needs.
4
+ *
5
+ * Remote environments arrive with a base image that already provides much of
6
+ * what a project needs, so the manifest has two entry kinds rather than one:
7
+ *
8
+ * require — assert present, fail setup with a clear message if missing
9
+ * install — provision it, pinned and checksummed
10
+ *
11
+ * The distinction is not bookkeeping. A base image is **not a contract**: the
12
+ * vendor can change it. A project quietly depending on a preinstalled tool
13
+ * should break loudly at setup when that happens, rather than mysteriously
14
+ * mid-task weeks later. `require` is what converts an implicit assumption into
15
+ * an explicit check.
16
+ *
17
+ * Planning is separated from execution so the whole decision table is testable
18
+ * without a container, a network, or a real binary anywhere.
19
+ * @module toolchain
20
+ */
21
+
22
+ /**
23
+ * Compare dotted version strings numerically.
24
+ *
25
+ * String comparison gets this wrong in the case that matters: "10" sorts before
26
+ * "9", so a base-image bump to a newer major would read as a downgrade and pass
27
+ * a minimum-version check it should fail.
28
+ * @param {string} a Left version.
29
+ * @param {string} b Right version.
30
+ * @returns {number} Negative, zero, or positive.
31
+ */
32
+ export function compareVersions(a, b) {
33
+ const left = String(a).split(".").map(Number);
34
+ const right = String(b).split(".").map(Number);
35
+ for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
36
+ const diff = (left[i] || 0) - (right[i] || 0);
37
+ if (diff) return diff;
38
+ }
39
+ return 0;
40
+ }
41
+
42
+ /**
43
+ * Pull a dotted version out of whatever a tool prints for `--version`.
44
+ *
45
+ * Tools are wildly inconsistent here ("bws 2.1.0", "v20.11.0", "Python 3.12.1"),
46
+ * and normalising with a regex is more honest than maintaining a per-tool parser
47
+ * table that silently rots.
48
+ * @param {string} output Raw `--version` output.
49
+ * @returns {string|null} The version, or null when none is recognisable.
50
+ */
51
+ export function extractVersion(output) {
52
+ const match = /(\d+(?:\.\d+)*)/.exec(String(output ?? ""));
53
+ return match ? match[1] : null;
54
+ }
55
+
56
+ /**
57
+ * Classify one required tool against what the environment actually provides.
58
+ * @param {object} tool Manifest entry.
59
+ * @param {{version: string|null, present: boolean}} found Probe result.
60
+ * @returns {{name: string, action: string, reason: string}} The decision.
61
+ */
62
+ function planRequired(tool, found) {
63
+ if (!found.present) {
64
+ return {
65
+ name: tool.name,
66
+ action: "missing",
67
+ reason:
68
+ `${tool.name} is required but not present. The base image is not a ` +
69
+ `contract — if it used to provide this, the vendor has changed it. ` +
70
+ `Either add an install entry or pin a different image.`,
71
+ };
72
+ }
73
+ if (
74
+ tool.minVersion &&
75
+ compareVersions(found.version ?? "0", tool.minVersion) < 0
76
+ ) {
77
+ return {
78
+ name: tool.name,
79
+ action: "missing",
80
+ reason:
81
+ `${tool.name} ${found.version ?? "(unknown)"} is older than the ` +
82
+ `required ${tool.minVersion}. Presence alone is not enough when a ` +
83
+ `project depends on a specific version.`,
84
+ };
85
+ }
86
+ return {
87
+ name: tool.name,
88
+ action: "present",
89
+ reason: `${tool.name} ${found.version ?? ""}`.trim(),
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Classify one installable tool against what is already on disk.
95
+ *
96
+ * Detect first, install second. Skipping a matching version is what makes setup
97
+ * and maintenance the same script, and it is the cheap path when a container
98
+ * resumes from cache rather than being built fresh.
99
+ * @param {object} tool Manifest entry.
100
+ * @param {{version: string|null, present: boolean}} found Probe result.
101
+ * @returns {{name: string, action: string, reason: string}} The decision.
102
+ */
103
+ function planInstallable(tool, found) {
104
+ if (!tool.version) {
105
+ return {
106
+ name: tool.name,
107
+ action: "invalid",
108
+ reason: `${tool.name} has no pinned version. An unpinned install is not reproducible.`,
109
+ };
110
+ }
111
+ if (found.present && found.version === tool.version) {
112
+ return {
113
+ name: tool.name,
114
+ action: "skip",
115
+ reason: `${tool.name} ${tool.version} already installed`,
116
+ };
117
+ }
118
+ return {
119
+ name: tool.name,
120
+ action: "install",
121
+ reason: found.present
122
+ ? `${tool.name} ${found.version ?? "(unknown)"} does not match pin ${tool.version}`
123
+ : `${tool.name} ${tool.version} not installed`,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Produce the complete plan for a toolchain manifest.
129
+ * @param {{require?: object[], install?: object[]}} tools Manifest.
130
+ * @param {(name: string) => {version: string|null, present: boolean}} probe Version probe.
131
+ * @returns {Array<{name: string, action: string, reason: string}>} Ordered decisions.
132
+ */
133
+ export function planToolchain(tools, probe) {
134
+ const plan = [];
135
+ for (const tool of tools.require ?? [])
136
+ plan.push(planRequired(tool, probe(tool.name)));
137
+ for (const tool of tools.install ?? [])
138
+ plan.push(planInstallable(tool, probe(tool.name)));
139
+ return plan;
140
+ }
141
+
142
+ /**
143
+ * Reject a manifest entry that could install something unverifiable.
144
+ *
145
+ * A pinned version with no checksum still trusts whatever the URL serves today.
146
+ * Requiring both, changed together in one reviewed commit, is what makes an
147
+ * unexpected archive fail before installation rather than after.
148
+ * @param {object} tool Manifest entry.
149
+ */
150
+ export function assertPinned(tool) {
151
+ if (tool.install === "release-zip") {
152
+ if (!tool.url || !tool.sha256) {
153
+ throw new Error(
154
+ `${tool.name}: a release-zip install needs both url and sha256.\n` +
155
+ `A version bump must move the checksum in the same reviewed commit.`
156
+ );
157
+ }
158
+ return;
159
+ }
160
+ if (tool.install === "npm-global") {
161
+ if (!tool.package)
162
+ throw new Error(`${tool.name}: npm-global install needs a package`);
163
+ return;
164
+ }
165
+ throw new Error(
166
+ `${tool.name}: unknown install method "${tool.install}".\n` +
167
+ `Supported: release-zip, npm-global.`
168
+ );
169
+ }
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prove a remote environment is actually configured the way it was meant to be.
4
+ *
5
+ * This runs identically whatever provisioned the environment — an API, a driven
6
+ * browser, or a human pasting emitted config. That is the point: trust comes
7
+ * from the read-back, not from the mechanism, so the weakest provisioning tier
8
+ * ends up as trustworthy as the strongest.
9
+ *
10
+ * Every assertion is made **without printing a value**. Presence, mode, and
11
+ * shape are all checkable without revealing content, and an environment proof
12
+ * that leaks the thing it is proving is not a proof worth having.
13
+ *
14
+ * One rule this encodes the hard way: never verify against a vendor's own UI.
15
+ * A dashboard counter reported zero tasks for an environment that had
16
+ * demonstrably completed one, because the underlying records carried no
17
+ * environment identifier. Assert against durable state instead.
18
+ *
19
+ * Usage:
20
+ * verify-remote-env.mjs
21
+ * @module verify-remote-env
22
+ */
23
+
24
+ import { execFileSync } from "node:child_process";
25
+ import { existsSync, statSync } from "node:fs";
26
+
27
+ import { readRemoteEnvConfig } from "./setup-remote-env.mjs";
28
+ import { extractVersion } from "./toolchain.mjs";
29
+
30
+ /** Collected results, so every check runs before anything reports failure. */
31
+ const results = [];
32
+
33
+ /**
34
+ * Record one assertion.
35
+ * @param {boolean} ok Whether it held.
36
+ * @param {string} label What was checked.
37
+ * @param {string} [detail] Extra context, never a secret value.
38
+ */
39
+ function check(ok, label, detail = "") {
40
+ results.push({ ok, label, detail });
41
+ }
42
+
43
+ /**
44
+ * Assert a path exists with an exact permission mode.
45
+ *
46
+ * The mode is the assertion, not merely existence. A world-readable secrets
47
+ * file is present and wrong, and "present" is what a weaker check would report.
48
+ * @param {string} path Filesystem path.
49
+ * @param {number} mode Expected permission bits.
50
+ * @param {string} label What this path is.
51
+ */
52
+ function checkMode(path, mode, label) {
53
+ if (!existsSync(path)) {
54
+ check(false, label, "absent");
55
+ return;
56
+ }
57
+ const actual = statSync(path).mode & 0o777;
58
+ check(
59
+ actual === mode,
60
+ label,
61
+ actual === mode
62
+ ? `mode ${mode.toString(8)}`
63
+ : `mode ${actual.toString(8)}, expected ${mode.toString(8)}`
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Run a sibling skill's script and capture its output.
69
+ * @param {string[]} args Node arguments.
70
+ * @returns {{ok: boolean, out: string}} Result.
71
+ */
72
+ function node(args) {
73
+ try {
74
+ const out = execFileSync("node", args, {
75
+ encoding: "utf8",
76
+ stdio: ["ignore", "pipe", "pipe"],
77
+ });
78
+ return { ok: true, out: out.trim() };
79
+ } catch (err) {
80
+ return { ok: false, out: String(err.stderr ?? err.message).trim() };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Assert each declared tool is present at its pinned or minimum version.
86
+ * @param {object} tools Toolchain manifest.
87
+ */
88
+ function verifyToolchain(tools) {
89
+ for (const tool of tools.require ?? []) {
90
+ try {
91
+ const out = execFileSync(tool.name, ["--version"], {
92
+ encoding: "utf8",
93
+ stdio: ["ignore", "pipe", "ignore"],
94
+ });
95
+ check(true, `tool ${tool.name}`, extractVersion(out) ?? "present");
96
+ } catch {
97
+ check(false, `tool ${tool.name}`, "required but not present");
98
+ }
99
+ }
100
+ for (const tool of tools.install ?? []) {
101
+ try {
102
+ const out = execFileSync(tool.name, ["--version"], {
103
+ encoding: "utf8",
104
+ stdio: ["ignore", "pipe", "ignore"],
105
+ });
106
+ const found = extractVersion(out);
107
+ check(
108
+ found === tool.version,
109
+ `tool ${tool.name}`,
110
+ `${found} (pin ${tool.version})`
111
+ );
112
+ } catch {
113
+ check(
114
+ false,
115
+ `tool ${tool.name}`,
116
+ `pinned ${tool.version} but not installed`
117
+ );
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Assert the working tree is clean.
124
+ *
125
+ * A remote environment that starts dirty will carry unrelated changes into
126
+ * whatever it produces, and the resulting diff is no longer attributable to the
127
+ * task that ran.
128
+ */
129
+ function verifyCleanCheckout() {
130
+ try {
131
+ const out = execFileSync("git", ["status", "--porcelain"], {
132
+ encoding: "utf8",
133
+ stdio: ["ignore", "pipe", "ignore"],
134
+ });
135
+ check(
136
+ out.trim() === "",
137
+ "clean checkout",
138
+ out.trim() ? "working tree is dirty" : ""
139
+ );
140
+ } catch {
141
+ check(false, "clean checkout", "not a git repository");
142
+ }
143
+ }
144
+
145
+ function main() {
146
+ const cfg = readRemoteEnvConfig();
147
+ const secretsDir = process.argv[2];
148
+
149
+ verifyToolchain(cfg.tools);
150
+
151
+ const surface = node([
152
+ new URL(
153
+ "../../lisa-secrets-access/scripts/resolve-secret.mjs",
154
+ import.meta.url
155
+ ).pathname,
156
+ "surface",
157
+ ]);
158
+ check(surface.ok, "surface detected", surface.out);
159
+
160
+ if (secretsDir) {
161
+ checkMode(secretsDir, 0o700, "secrets directory");
162
+ checkMode(`${secretsDir}/secrets.env`, 0o600, "values file");
163
+ checkMode(`${secretsDir}/secret-notes.json`, 0o600, "notes file");
164
+ }
165
+
166
+ verifyCleanCheckout();
167
+
168
+ let failed = 0;
169
+ for (const r of results) {
170
+ if (!r.ok) failed += 1;
171
+ console.log(
172
+ ` ${r.ok ? "ok " : "FAIL"} ${r.label.padEnd(28)} ${r.detail}`
173
+ );
174
+ }
175
+ if (failed) throw new Error(`${failed} environment check(s) failed`);
176
+ console.log("\nRemote environment verified without revealing any value.");
177
+ }
178
+
179
+ if (import.meta.url === `file://${process.argv[1]}`) {
180
+ try {
181
+ main();
182
+ } catch (err) {
183
+ console.error(err.message);
184
+ process.exit(1);
185
+ }
186
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.316.2",
3
+ "version": "2.317.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"