@jstn-sdk/ma 0.1.13 → 0.14.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 (259) hide show
  1. package/.agents/plugins/marketplace.json +0 -0
  2. package/.codex/agents/Architect.toml +1 -0
  3. package/.codex/agents/Auditor.toml +1 -0
  4. package/.codex/agents/Builder.toml +1 -0
  5. package/.codex/agents/Flow.toml +1 -0
  6. package/.codex/agents/Sage.toml +1 -0
  7. package/.codex/agents/Vibe.toml +1 -0
  8. package/.codex/hooks.json +12 -2
  9. package/.codex/prompts/enforcement.md +0 -0
  10. package/.codex/prompts/onboarding.md +6 -1
  11. package/.codex/prompts/release-rules.md +0 -0
  12. package/.codex/prompts/skill-contract.md +0 -0
  13. package/COVERAGE.md +9 -9
  14. package/DEMO.md +15 -6
  15. package/LICENSE +0 -0
  16. package/README.md +127 -784
  17. package/bin/ma.js +591 -105
  18. package/data/clone-data.ledger.json +0 -0
  19. package/data/clone-data.proof.json +0 -0
  20. package/data/clone-data.rvf +0 -0
  21. package/docs/README.md +8 -2
  22. package/docs/assets/banner.png +0 -0
  23. package/docs/autonomous-tasks.md +46 -0
  24. package/docs/codex-integration.md +16 -0
  25. package/docs/getting-started.md +32 -8
  26. package/docs/installed-sdk.md +0 -0
  27. package/docs/mcp-setup.md +30 -1
  28. package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.1.json} +77 -77
  29. package/docs/qa/release-readiness-0.1.5.md +0 -0
  30. package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.1.md} +12 -12
  31. package/docs/quality.md +24 -0
  32. package/docs/reference/native-engineering-patterns.md +0 -0
  33. package/docs/reference/native-security-playbooks.md +0 -0
  34. package/docs/reference/native-source-selection.md +0 -0
  35. package/docs/reference/native-style-and-deslop.md +0 -0
  36. package/docs/release-spec.md +27 -14
  37. package/docs/skills.md +1 -1
  38. package/docs/vendor-plugin-publishing.md +49 -0
  39. package/index.js +73 -0
  40. package/mcp/collections.json +0 -0
  41. package/mcp/fallback.json +0 -0
  42. package/mcp/local/code-intel.js +185 -41
  43. package/mcp/local/context.js +180 -0
  44. package/mcp/local/memory.js +17 -6
  45. package/mcp/local/playbooks.js +0 -0
  46. package/mcp/local/quality.js +39 -0
  47. package/mcp/local/state.js +7 -3
  48. package/mcp/local/team-run.js +8 -3
  49. package/mcp/local/trace.js +0 -0
  50. package/mcp/local-capabilities.json +9 -0
  51. package/mcp/native-playbooks.json +0 -0
  52. package/mcp/servers.json +0 -0
  53. package/package.json +37 -6
  54. package/plugins/meta-architect/.app.json +1 -1
  55. package/plugins/meta-architect/.claude-plugin/plugin.json +12 -0
  56. package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
  57. package/plugins/meta-architect/.mcp.json +1 -1
  58. package/plugins/meta-architect/README.md +14 -2
  59. package/plugins/meta-architect/obsidian/main.js +137 -4
  60. package/plugins/meta-architect/obsidian/manifest.json +1 -1
  61. package/plugins/meta-architect/obsidian/styles.css +0 -0
  62. package/plugins/meta-architect/skills/align/SKILL.md +0 -0
  63. package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
  64. package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
  65. package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
  66. package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
  67. package/plugins/meta-architect/skills/build/SKILL.md +0 -0
  68. package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
  69. package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
  70. package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
  71. package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
  72. package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
  73. package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
  74. package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
  75. package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
  76. package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
  77. package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
  78. package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
  79. package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
  80. package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
  81. package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
  82. package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
  83. package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
  84. package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
  85. package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
  86. package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
  87. package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
  88. package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
  89. package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
  90. package/schemas/autonomous-task-queue.schema.json +58 -0
  91. package/schemas/handoff-packet.schema.json +36 -0
  92. package/schemas/setup-receipt.schema.json +28 -0
  93. package/schemas/skill-frontmatter.schema.json +20 -0
  94. package/schemas/support-bundle.schema.json +37 -0
  95. package/schemas/task-contract.schema.json +31 -0
  96. package/scripts/active-autonomy-hook.mjs +141 -11
  97. package/scripts/biome-staged.js +0 -0
  98. package/scripts/build-linux-packages.mjs +7 -3
  99. package/scripts/cleanup-test-fixtures.sh +55 -0
  100. package/scripts/context-hydration-hook.mjs +89 -0
  101. package/scripts/demo-smoke.js +285 -0
  102. package/scripts/doctor.js +10 -8
  103. package/scripts/install.sh +33 -4
  104. package/scripts/install.sh.sha256 +1 -0
  105. package/scripts/linux-package-lib.mjs +0 -0
  106. package/scripts/linux-package-smoke.mjs +2 -2
  107. package/scripts/package-size-check.mjs +26 -0
  108. package/scripts/plugin-build.js +314 -0
  109. package/scripts/plugin-publish.js +77 -0
  110. package/scripts/plugin-sync.js +83 -17
  111. package/scripts/prepack.js +9 -0
  112. package/scripts/ralph/prompt.md +0 -0
  113. package/scripts/release-metadata.js +0 -0
  114. package/scripts/release-sync.js +83 -7
  115. package/scripts/release-verify.js +131 -7
  116. package/scripts/setup-npmrc.js +48 -23
  117. package/scripts/skills-install.js +9 -2
  118. package/scripts/skills-manifest.js +2 -21
  119. package/scripts/skills-pack.js +2 -2
  120. package/scripts/skills-validate.js +5 -55
  121. package/skills/align/SKILL.md +0 -0
  122. package/skills/align/agents/openai.yaml +0 -0
  123. package/skills/align/references/shared-language.md +0 -0
  124. package/skills/arch/SKILL.md +0 -0
  125. package/skills/arch/agents/openai.yaml +0 -0
  126. package/skills/build/SKILL.md +0 -0
  127. package/skills/build/agents/openai.yaml +0 -0
  128. package/skills/cleanup/SKILL.md +0 -0
  129. package/skills/cleanup/agents/openai.yaml +0 -0
  130. package/skills/cleanup/references/style-and-deslop.md +0 -0
  131. package/skills/diagnose/SKILL.md +0 -0
  132. package/skills/diagnose/agents/openai.yaml +0 -0
  133. package/skills/flow/SKILL.md +0 -0
  134. package/skills/flow/agents/openai.yaml +0 -0
  135. package/skills/index.json +0 -0
  136. package/skills/maestro/SKILL.md +17 -1
  137. package/skills/maestro/agents/openai.yaml +0 -0
  138. package/skills/maestro/references/core-release-rules.md +2 -2
  139. package/skills/maestro/references/native-ingest-map.md +0 -0
  140. package/skills/sage/SKILL.md +0 -0
  141. package/skills/sage/agents/openai.yaml +0 -0
  142. package/skills/sage/references/source-selection.md +0 -0
  143. package/skills/tdd/SKILL.md +0 -0
  144. package/skills/tdd/agents/openai.yaml +0 -0
  145. package/skills/vet/SKILL.md +0 -0
  146. package/skills/vet/agents/openai.yaml +0 -0
  147. package/skills/vet/references/security-playbooks.md +0 -0
  148. package/skills/vibe/SKILL.md +0 -0
  149. package/skills/vibe/agents/openai.yaml +0 -0
  150. package/sprint/00-idea.md +0 -0
  151. package/sprint/01-architecture.md +0 -0
  152. package/sprint/02-oss-evidence.md +0 -0
  153. package/sprint/03-logic.md +0 -0
  154. package/sprint/04-security.md +0 -0
  155. package/sprint/05-dx-ux.md +0 -0
  156. package/sprint/06-build-plan.md +0 -0
  157. package/sprint/07-release.md +2 -2
  158. package/src/agents.js +281 -0
  159. package/src/bootstrap.js +272 -65
  160. package/src/build-gate.js +0 -0
  161. package/src/codex-app-server.js +291 -0
  162. package/src/decision-log.js +0 -0
  163. package/src/fs-utils.js +175 -4
  164. package/src/launcher.js +26 -23
  165. package/src/mcp-config.js +9 -1
  166. package/src/mcp-live-client.js +141 -8
  167. package/src/paths.js +6 -1
  168. package/src/policy.js +2 -2
  169. package/src/prelaunch.js +223 -0
  170. package/src/process-utils.js +62 -0
  171. package/src/quality/ai-quality-orchestrator.js +328 -0
  172. package/src/release-issue-gates.js +62 -0
  173. package/src/release-operations.js +72 -0
  174. package/src/release-state.js +0 -0
  175. package/src/runtime/active-autonomy-core.js +0 -0
  176. package/src/runtime/agent-compat.js +31 -0
  177. package/src/runtime/alignment-sentinel.js +0 -0
  178. package/src/runtime/architect-review.js +249 -11
  179. package/src/runtime/autonomous-tasks.js +585 -0
  180. package/src/runtime/build-readiness.js +0 -0
  181. package/src/runtime/code-graph-rehearse.js +0 -0
  182. package/src/runtime/codeburn-core.js +118 -0
  183. package/src/runtime/context-authority.js +40 -0
  184. package/src/runtime/context-economy-core.js +40 -0
  185. package/src/runtime/continuity-graph.js +201 -0
  186. package/src/runtime/continuity-notes.js +71 -26
  187. package/src/runtime/core-source-ingest.js +2 -2
  188. package/src/runtime/detached-provider.js +90 -13
  189. package/src/runtime/doctor-report.js +18 -0
  190. package/src/runtime/environment-awareness-core.js +0 -0
  191. package/src/runtime/exposure-catalog.js +0 -0
  192. package/src/runtime/graphify-core.js +167 -0
  193. package/src/runtime/guidance-stack.js +9 -2
  194. package/src/runtime/handoff-packets.js +92 -0
  195. package/src/runtime/headroom-core.js +104 -0
  196. package/src/runtime/helper-orchestration-core.js +0 -0
  197. package/src/runtime/hook-profiles.js +20 -0
  198. package/src/runtime/learning-loop-core.js +110 -4
  199. package/src/runtime/live-agent-verification.js +122 -0
  200. package/src/runtime/maestro-events.js +0 -0
  201. package/src/runtime/maestro-manager.js +0 -0
  202. package/src/runtime/maestro-output.js +58 -0
  203. package/src/runtime/maestro-state.js +0 -0
  204. package/src/runtime/managed-markdown.js +29 -0
  205. package/src/runtime/mcp-authority.js +147 -0
  206. package/src/runtime/mcp-policy.js +0 -0
  207. package/src/runtime/obsidian-integration-core.js +255 -17
  208. package/src/runtime/obsidian-plugin-bridge.js +344 -38
  209. package/src/runtime/orchestrator.js +0 -0
  210. package/src/runtime/pi-maestro-core.js +89 -0
  211. package/src/runtime/preferences.js +79 -0
  212. package/src/runtime/project-context.js +636 -0
  213. package/src/runtime/prompt-strategy-core.js +0 -0
  214. package/src/runtime/quorum-review.js +0 -0
  215. package/src/runtime/ralph-execution-core.js +0 -0
  216. package/src/runtime/redaction-gateway.js +218 -19
  217. package/src/runtime/runtime-state.js +94 -2
  218. package/src/runtime/schema-migrations.js +162 -0
  219. package/src/runtime/semantic-recording-core.js +0 -0
  220. package/src/runtime/signal-hooks.js +13 -0
  221. package/src/runtime/skills-registry-export.js +96 -8
  222. package/src/runtime/startup-path.js +0 -0
  223. package/src/runtime/task-contracts.js +83 -0
  224. package/src/runtime/universal-plugin-broker-core.js +373 -50
  225. package/src/runtime/workspace-intelligence-runtime.js +0 -0
  226. package/src/runtime/workspace-virtualizer.js +0 -0
  227. package/src/runtime/workspaces.js +0 -0
  228. package/src/runtime-artifacts.js +0 -0
  229. package/src/setup-lifecycle.js +359 -0
  230. package/src/skill-frontmatter.js +78 -0
  231. package/src/skill-installer.js +226 -71
  232. package/src/skills.js +309 -31
  233. package/src/state-sync.js +0 -0
  234. package/src/test-fixtures.js +543 -0
  235. package/src/tui/grid.js +67 -0
  236. package/src/tui/status-grid.js +23 -0
  237. package/support-bundle.json +114 -0
  238. package/templates/AGENTS.md +6 -2
  239. package/templates/catalog-manifest.json +0 -0
  240. package/templates/model-instructions/core.md +0 -0
  241. package/templates/model-instructions/release.md +0 -0
  242. package/templates/model-instructions/security.md +0 -0
  243. package/templates/quality/ai-quality-rules.yml +37 -0
  244. package/docs/assets/image/Screenshot(1).png +0 -0
  245. package/docs/assets/image/Screenshot(2).png +0 -0
  246. package/docs/assets/image/Screenshot(3).png +0 -0
  247. package/docs/assets/image/Screenshot(4).png +0 -0
  248. package/docs/assets/image/Screenshot(5).png +0 -0
  249. package/docs/assets/image/Screenshot(6).png +0 -0
  250. package/docs/assets/image/Screenshot(7).png +0 -0
  251. package/docs/assets/image/Screenshot(8).png +0 -0
  252. package/docs/assets/image/Screenshot(9).png +0 -0
  253. package/docs/assets/meta-architect-logo.png +0 -0
  254. package/docs/assets/meta-architect-logo.svg +0 -8
  255. package/docs/onboarding.md +0 -81
  256. package/docs/prompt-guidance-contract.md +0 -17
  257. package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
  258. package/docs/skills-publishing.md +0 -255
  259. package/scripts/postinstall.js +0 -23
@@ -1,13 +1,19 @@
1
+ import crypto from "node:crypto";
1
2
  import fs from "node:fs/promises";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
5
+ import { getAgent } from "./agents.js";
4
6
  import { getBundledDocsPath, packageRoot } from "./paths.js";
5
7
 
6
8
  function resolveCodexHome() {
7
9
  return process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
8
10
  }
9
11
 
10
- function resolveSkillInstallRoot() {
12
+ function resolveSkillInstallRoot(agentType = "codex") {
13
+ const agent = getAgent(agentType);
14
+ if (agentType !== "codex" && agent.globalSkillsDir?.startsWith("~/")) {
15
+ return path.join(os.homedir(), agent.globalSkillsDir.slice(2));
16
+ }
11
17
  return path.join(resolveCodexHome(), "skills");
12
18
  }
13
19
 
@@ -15,14 +21,16 @@ function resolveSupportBundleRoot() {
15
21
  return path.join(resolveCodexHome(), "meta-architect-sdk");
16
22
  }
17
23
 
18
- async function copyDir(src, dest) {
24
+ async function copyDir(src, dest, { omitIndex = false } = {}) {
19
25
  await fs.mkdir(dest, { recursive: true });
20
26
  const entries = await fs.readdir(src, { withFileTypes: true });
21
27
  for (const entry of entries) {
28
+ if (omitIndex && entry.name === "index.json") continue;
22
29
  const srcPath = path.join(src, entry.name);
23
30
  const destPath = path.join(dest, entry.name);
24
- if (entry.isDirectory()) {
25
- await copyDir(srcPath, destPath);
31
+ const sourceStat = entry.isSymbolicLink() ? await fs.stat(srcPath) : entry;
32
+ if (sourceStat.isDirectory()) {
33
+ await copyDir(srcPath, destPath, { omitIndex });
26
34
  } else {
27
35
  await fs.copyFile(srcPath, destPath);
28
36
  }
@@ -38,8 +46,55 @@ async function pathExists(targetPath) {
38
46
  }
39
47
  }
40
48
 
41
- export function getSkillInstallRoot() {
42
- return resolveSkillInstallRoot();
49
+ async function listFiles(root, current = root, output = []) {
50
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
51
+ for (const entry of entries) {
52
+ if (entry.name === ".ma-install-receipt.json" || entry.name === ".ma-backups") continue;
53
+ const absolute = path.join(current, entry.name);
54
+ if (entry.isDirectory()) await listFiles(root, absolute, output);
55
+ else if (entry.isFile()) output.push(path.relative(root, absolute));
56
+ }
57
+ return output.sort();
58
+ }
59
+
60
+ async function treeHash(root) {
61
+ const hash = crypto.createHash("sha256");
62
+ for (const relative of await listFiles(root)) {
63
+ hash.update(relative);
64
+ hash.update(await fs.readFile(path.join(root, relative)));
65
+ }
66
+ return hash.digest("hex");
67
+ }
68
+
69
+ export async function readInstallReceipt(root, kind = "skills") {
70
+ try {
71
+ return JSON.parse(await fs.readFile(installReceiptPath(root, kind), "utf8"));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function installReceiptPath(root, kind = "skills") {
78
+ if (kind === "skills" && path.basename(root) === "skills") {
79
+ return path.join(path.dirname(root), ".ma", "codex-skills-install-receipt.json");
80
+ }
81
+ return path.join(root, ".ma-install-receipt.json");
82
+ }
83
+
84
+ async function writeInstallReceipt(root, receipt, kind = "skills") {
85
+ const receiptPath = installReceiptPath(root, kind);
86
+ await fs.mkdir(path.dirname(receiptPath), { recursive: true });
87
+ await fs.writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
88
+ }
89
+
90
+ async function backupPath(root, source, label) {
91
+ const backupRoot = path.join(root, ".ma-backups", `${Date.now()}-${label}`);
92
+ await copyPath(source, backupRoot);
93
+ return backupRoot;
94
+ }
95
+
96
+ export function getSkillInstallRoot(agentType = "codex") {
97
+ return resolveSkillInstallRoot(agentType);
43
98
  }
44
99
 
45
100
  export function getSupportBundleRoot() {
@@ -55,24 +110,59 @@ export async function loadSkillManifest() {
55
110
  export async function installSkills({ targetRoot = getSkillInstallRoot() } = {}) {
56
111
  const skills = await loadSkillManifest();
57
112
  await fs.mkdir(targetRoot, { recursive: true });
113
+ const previous = await readInstallReceipt(targetRoot, "skills");
114
+ const managed = new Set(previous?.managedPaths ?? []);
58
115
 
59
116
  const installed = [];
117
+ const conflicts = [];
118
+ const backups = [];
60
119
  for (const skill of skills) {
61
120
  const src = path.join(packageRoot, skill.path);
62
121
  const dest = path.join(targetRoot, path.basename(skill.path));
63
- await fs.rm(dest, { recursive: true, force: true });
64
- await copyDir(src, dest);
65
- installed.push({ name: skill.name, dest });
122
+ if (await pathExists(dest)) {
123
+ if (!managed.has(path.basename(skill.path))) {
124
+ conflicts.push({ name: skill.name, dest, reason: "existing-unmanaged-path" });
125
+ continue;
126
+ }
127
+ backups.push({
128
+ name: skill.name,
129
+ path: await backupPath(targetRoot, dest, path.basename(skill.path)),
130
+ });
131
+ await fs.rm(dest, { recursive: true, force: true });
132
+ }
133
+ await copyDir(src, dest, {
134
+ omitIndex: dest.endsWith(path.join("plugins", "meta-architect", "skills")),
135
+ });
136
+ installed.push({ name: skill.name, dest, hash: await treeHash(dest) });
66
137
  }
67
138
 
68
- return { targetRoot, installed };
139
+ await writeInstallReceipt(
140
+ targetRoot,
141
+ {
142
+ schemaVersion: "1.0.0",
143
+ record_type: "codex_skill_install_receipt",
144
+ packageName: "@jstn-sdk/ma",
145
+ packageVersion: JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"))
146
+ .version,
147
+ installedAt: new Date().toISOString(),
148
+ managedPaths: installed.map((entry) => path.basename(entry.dest)),
149
+ installed,
150
+ conflicts,
151
+ backups,
152
+ },
153
+ "skills",
154
+ );
155
+
156
+ return { targetRoot, installed, conflicts, backups };
69
157
  }
70
158
 
71
159
  async function copyPath(src, dest) {
72
160
  const stat = await fs.stat(src);
73
161
  if (stat.isDirectory()) {
74
162
  await fs.rm(dest, { recursive: true, force: true });
75
- await copyDir(src, dest);
163
+ await copyDir(src, dest, {
164
+ omitIndex: dest.endsWith(path.join("plugins", "meta-architect", "skills")),
165
+ });
76
166
  return "directory";
77
167
  }
78
168
 
@@ -81,80 +171,143 @@ async function copyPath(src, dest) {
81
171
  return "file";
82
172
  }
83
173
 
84
- export async function installSupportBundle({ targetRoot = getSupportBundleRoot() } = {}) {
85
- const assets = [
86
- {
87
- name: "mcp",
88
- src: path.join(packageRoot, "mcp"),
89
- dest: path.join(targetRoot, "mcp"),
90
- },
91
- {
92
- name: "sprint",
93
- src: path.join(packageRoot, "sprint"),
94
- dest: path.join(targetRoot, "sprint"),
95
- },
96
- {
97
- name: "prompts",
98
- src: path.join(packageRoot, ".codex", "prompts"),
99
- dest: path.join(targetRoot, "prompts"),
100
- },
101
- {
102
- name: "scripts",
103
- src: path.join(packageRoot, "scripts"),
104
- dest: path.join(targetRoot, "scripts"),
105
- },
106
- {
107
- name: "plugin",
108
- src: path.join(packageRoot, "plugins", "meta-architect"),
109
- dest: path.join(targetRoot, "plugins", "meta-architect"),
110
- },
111
- {
112
- name: "templates",
113
- src: path.join(packageRoot, "templates"),
114
- dest: path.join(targetRoot, "templates"),
115
- },
116
- {
117
- name: "docs-readme",
118
- src: getBundledDocsPath("README.md"),
119
- dest: path.join(targetRoot, "docs", "README.md"),
120
- },
121
- ];
122
-
123
- const optionalAssets = [
124
- {
125
- name: "docs-reference",
126
- src: getBundledDocsPath("reference"),
127
- dest: path.join(targetRoot, "docs", "reference"),
128
- },
129
- ];
130
-
131
- for (const asset of optionalAssets) {
132
- if (await pathExists(asset.src)) {
133
- assets.push(asset);
174
+ export async function loadSupportBundleManifest() {
175
+ const manifest = JSON.parse(
176
+ await fs.readFile(path.join(packageRoot, "support-bundle.json"), "utf8"),
177
+ );
178
+ if (
179
+ manifest.schemaVersion !== "1.0.0" ||
180
+ !/^\d+\.\d+\.\d+$/.test(manifest.bundleVersion) ||
181
+ manifest.compatibility?.managedBy !== "@jstn-sdk/ma" ||
182
+ typeof manifest.compatibility?.node !== "string"
183
+ ) {
184
+ throw new Error("Invalid support bundle manifest version");
185
+ }
186
+ if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
187
+ throw new Error("Support bundle manifest must define assets");
188
+ }
189
+ for (const asset of manifest.assets) {
190
+ if (
191
+ typeof asset.name !== "string" ||
192
+ typeof asset.source !== "string" ||
193
+ typeof asset.destination !== "string" ||
194
+ !["file", "directory"].includes(asset.type) ||
195
+ typeof asset.required !== "boolean"
196
+ ) {
197
+ throw new Error(`Invalid support bundle asset: ${asset.name ?? "(unnamed)"}`);
134
198
  }
135
199
  }
200
+ return manifest;
201
+ }
202
+
203
+ export async function installSupportBundle({ targetRoot = getSupportBundleRoot() } = {}) {
204
+ const pkg = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
205
+ const bundle = await loadSupportBundleManifest();
206
+ if (bundle.bundleVersion !== pkg.version) {
207
+ throw new Error(
208
+ `Support bundle manifest ${bundle.bundleVersion} does not match package ${pkg.version}`,
209
+ );
210
+ }
136
211
 
137
212
  await fs.mkdir(targetRoot, { recursive: true });
213
+ const previous = await readInstallReceipt(targetRoot, "support");
214
+ const managed = new Set(previous?.managedPaths ?? []);
138
215
  const installed = [];
139
- for (const asset of assets) {
140
- await copyPath(asset.src, asset.dest);
141
- installed.push({ name: asset.name, dest: asset.dest });
216
+ const conflicts = [];
217
+ const backups = [];
218
+ for (const asset of bundle.assets) {
219
+ const source =
220
+ asset.source === "docs/README.md"
221
+ ? getBundledDocsPath("README.md")
222
+ : asset.source === "docs/reference"
223
+ ? getBundledDocsPath("reference")
224
+ : path.join(packageRoot, asset.source);
225
+ if (!(await pathExists(source))) {
226
+ if (asset.required) throw new Error(`Missing required support bundle asset: ${asset.source}`);
227
+ continue;
228
+ }
229
+ const destination = path.join(targetRoot, asset.destination);
230
+ if (await pathExists(destination)) {
231
+ if (!managed.has(asset.destination)) {
232
+ conflicts.push({ name: asset.name, dest: destination, reason: "existing-unmanaged-path" });
233
+ continue;
234
+ }
235
+ backups.push({
236
+ name: asset.name,
237
+ path: await backupPath(targetRoot, destination, asset.name),
238
+ });
239
+ }
240
+ await copyPath(source, destination);
241
+ installed.push({
242
+ name: asset.name,
243
+ class: asset.class,
244
+ dest: destination,
245
+ relative: asset.destination,
246
+ });
142
247
  }
143
248
 
144
- const pkg = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
145
249
  const manifest = {
250
+ schemaVersion: bundle.schemaVersion,
251
+ bundleVersion: bundle.bundleVersion,
146
252
  packageName: pkg.name,
147
253
  packageVersion: pkg.version,
148
254
  installedAt: new Date().toISOString(),
149
255
  root: targetRoot,
150
256
  assets: installed,
257
+ conflicts,
258
+ backups,
151
259
  };
152
260
  await fs.writeFile(
153
261
  path.join(targetRoot, "asset-manifest.json"),
154
262
  `${JSON.stringify(manifest, null, 2)}\n`,
155
263
  );
264
+ await writeInstallReceipt(
265
+ targetRoot,
266
+ {
267
+ ...manifest,
268
+ record_type: "codex_support_bundle_install_receipt",
269
+ managedPaths: installed.map((asset) => asset.relative),
270
+ conflicts,
271
+ backups,
272
+ },
273
+ "support",
274
+ );
156
275
 
157
- return { targetRoot, installed };
276
+ return { targetRoot, installed, conflicts, backups };
277
+ }
278
+
279
+ export async function rollbackInstalledAssets({ targetRoot, kind = "skills" } = {}) {
280
+ const root = targetRoot ?? (kind === "skills" ? getSkillInstallRoot() : getSupportBundleRoot());
281
+ const receipt = await readInstallReceipt(root, kind);
282
+ if (!receipt) return { status: "nothing-to-rollback", removed: [], restored: [], skipped: [] };
283
+ const restored = [];
284
+ const removed = [];
285
+ const skipped = [];
286
+ for (const entry of receipt.installed ?? []) {
287
+ const relative = entry.relative ?? path.basename(entry.dest);
288
+ const dest = path.isAbsolute(entry.dest) ? entry.dest : path.join(root, relative);
289
+ if (!(await pathExists(dest))) continue;
290
+ if (entry.hash && (await treeHash(dest)) !== entry.hash) {
291
+ skipped.push({ path: dest, reason: "modified-after-install" });
292
+ continue;
293
+ }
294
+ await fs.rm(dest, { recursive: true, force: true });
295
+ removed.push(dest);
296
+ }
297
+ for (const backup of receipt.backups ?? []) {
298
+ if (!(await pathExists(backup.path))) continue;
299
+ const relative =
300
+ receipt.installed?.find((entry) => entry.name === backup.name)?.relative ?? backup.name;
301
+ const dest = path.join(root, relative);
302
+ await fs.rm(dest, { recursive: true, force: true });
303
+ await copyPath(backup.path, dest);
304
+ restored.push(dest);
305
+ }
306
+ if (kind === "support") {
307
+ await fs.rm(path.join(root, "asset-manifest.json"), { force: true });
308
+ }
309
+ if (skipped.length === 0) await fs.rm(installReceiptPath(root, kind), { force: true });
310
+ return { status: skipped.length ? "partial" : "rolled-back", removed, restored, skipped };
158
311
  }
159
312
 
160
313
  export async function areSkillsInstalled({ targetRoot = getSkillInstallRoot() } = {}) {
@@ -173,6 +326,8 @@ export async function areSkillsInstalled({ targetRoot = getSkillInstallRoot() }
173
326
  }
174
327
 
175
328
  export async function isSupportBundleInstalled({ targetRoot = getSupportBundleRoot() } = {}) {
329
+ const pkg = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
330
+ const bundle = await loadSupportBundleManifest();
176
331
  const requiredFiles = [
177
332
  path.join(targetRoot, "asset-manifest.json"),
178
333
  path.join(targetRoot, "mcp", "servers.json"),
@@ -205,16 +360,16 @@ export async function isSupportBundleInstalled({ targetRoot = getSupportBundleRo
205
360
 
206
361
  const manifestPath = path.join(targetRoot, "asset-manifest.json");
207
362
  const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
363
+ if (manifest.schemaVersion !== "1.0.0" || manifest.bundleVersion !== pkg.version) {
364
+ return false;
365
+ }
208
366
  const assetNames = new Set((manifest.assets ?? []).map((asset) => asset.name));
209
367
 
210
- for (const requiredAssetName of ["mcp", "docs-readme"]) {
211
- if (!assetNames.has(requiredAssetName)) {
368
+ for (const requiredAsset of bundle.assets.filter((asset) => asset.required)) {
369
+ if (!assetNames.has(requiredAsset.name)) {
212
370
  return false;
213
371
  }
214
372
  }
215
- if ((await pathExists(getBundledDocsPath("reference"))) && !assetNames.has("docs-reference")) {
216
- return false;
217
- }
218
373
 
219
374
  return true;
220
375
  }