@r3dlex/ai-catapult 0.1.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/bin/ai-catapult.js +229 -0
  4. package/dist/claude-plugin/.claude-plugin/marketplace.json +28 -0
  5. package/dist/claude-plugin/.claude-plugin/plugin.json +21 -0
  6. package/dist/claude-plugin/skills/ai-catapult-init/REFERENCE.md +1284 -0
  7. package/dist/claude-plugin/skills/ai-catapult-init/SKILL.md +79 -0
  8. package/dist/claude-plugin/skills/ai-catapult-init/modules/README.md +48 -0
  9. package/dist/claude-plugin/skills/ai-catapult-init/modules/archgate.md +42 -0
  10. package/dist/claude-plugin/skills/ai-catapult-init/modules/brd-prd-traceability.md +64 -0
  11. package/dist/claude-plugin/skills/ai-catapult-init/modules/cascade.md +110 -0
  12. package/dist/claude-plugin/skills/ai-catapult-init/modules/ci-policy.md +107 -0
  13. package/dist/claude-plugin/skills/ai-catapult-init/modules/documentation-blueprint.md +185 -0
  14. package/dist/claude-plugin/skills/ai-catapult-init/modules/evals.md +93 -0
  15. package/dist/claude-plugin/skills/ai-catapult-init/modules/foundation.md +19 -0
  16. package/dist/claude-plugin/skills/ai-catapult-init/modules/host-policy-automation.md +151 -0
  17. package/dist/claude-plugin/skills/ai-catapult-init/modules/language-packs.md +63 -0
  18. package/dist/claude-plugin/skills/ai-catapult-init/modules/mcp-a2a.md +63 -0
  19. package/dist/claude-plugin/skills/ai-catapult-init/modules/memory.md +102 -0
  20. package/dist/claude-plugin/skills/ai-catapult-init/modules/migration.md +107 -0
  21. package/dist/claude-plugin/skills/ai-catapult-init/modules/phases/01-discover-decide.md +33 -0
  22. package/dist/claude-plugin/skills/ai-catapult-init/modules/phases/README.md +33 -0
  23. package/dist/claude-plugin/skills/ai-catapult-init/modules/readme-documentation.md +120 -0
  24. package/dist/claude-plugin/skills/ai-catapult-init/modules/release-versioning.md +188 -0
  25. package/dist/claude-plugin/skills/ai-catapult-init/modules/skill-modernization.md +72 -0
  26. package/dist/claude-plugin/skills/ai-catapult-init/modules/sync.md +111 -0
  27. package/dist/claude-plugin/skills/ai-catapult-init/modules/topology.md +102 -0
  28. package/dist/claude-plugin/skills/ai-catapult-init/modules/traceability.md +136 -0
  29. package/dist/claude-plugin/skills/ai-catapult-init/modules/tracker-adapters.md +51 -0
  30. package/dist/claude-plugin/skills/ai-catapult-init/modules/validation.md +276 -0
  31. package/dist/claude-plugin/skills/ai-catapult-init/modules/workflow.md +45 -0
  32. package/dist/claude-plugin/skills/ai-catapult-init/templates/AGENTS.md +69 -0
  33. package/dist/claude-plugin/skills/ai-catapult-init/templates/CLAUDE.md +3 -0
  34. package/dist/claude-plugin/skills/ai-catapult-init/templates/GEMINI.md +3 -0
  35. package/dist/claude-plugin/skills/ai-catapult-init/templates/boundary-manifest.json +247 -0
  36. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/drift/backups/.gitkeep +0 -0
  37. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/drift/last-drift.json +7 -0
  38. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/evals/.gitkeep +0 -0
  39. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/evals/coverage-exceptions.json +1 -0
  40. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/handoff/.gitkeep +0 -0
  41. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/matrix.json +19 -0
  42. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/a2a-handoff.md +51 -0
  43. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/registry.json +27 -0
  44. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/observability/audit-checklist.md +32 -0
  45. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/observability/conventions.md +35 -0
  46. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/01-discover-decide/status.json +16 -0
  47. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/02-govern-plan/status.json +15 -0
  48. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/03-configure-generate/status.json +22 -0
  49. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/04-validate-handoff/status.json +18 -0
  50. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/policies/model-routing.json +29 -0
  51. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/reviews/ai-failure-modes.md +42 -0
  52. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/rules/security.md +38 -0
  53. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/rules/technical-bounds.md +38 -0
  54. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/skills/git-ops.json +6 -0
  55. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/skills/workspace-sync.json +6 -0
  56. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/architect.md +31 -0
  57. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/developer.md +31 -0
  58. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/qa-engineer.md +31 -0
  59. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/traceability/.gitkeep +0 -0
  60. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.json +42 -0
  61. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.md +52 -0
  62. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-github/workflows/ci-prek.yml +21 -0
  63. package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-rules.ts +178 -0
  64. package/dist/claude-plugin/skills/ai-catapult-init/templates/prek.toml +13 -0
  65. package/dist/codex-plugin/.codex-plugin/plugin.json +11 -0
  66. package/dist/codex-plugin/skills/ai-catapult-init/REFERENCE.md +1284 -0
  67. package/dist/codex-plugin/skills/ai-catapult-init/SKILL.md +79 -0
  68. package/dist/codex-plugin/skills/ai-catapult-init/modules/README.md +48 -0
  69. package/dist/codex-plugin/skills/ai-catapult-init/modules/archgate.md +42 -0
  70. package/dist/codex-plugin/skills/ai-catapult-init/modules/brd-prd-traceability.md +64 -0
  71. package/dist/codex-plugin/skills/ai-catapult-init/modules/cascade.md +110 -0
  72. package/dist/codex-plugin/skills/ai-catapult-init/modules/ci-policy.md +107 -0
  73. package/dist/codex-plugin/skills/ai-catapult-init/modules/documentation-blueprint.md +185 -0
  74. package/dist/codex-plugin/skills/ai-catapult-init/modules/evals.md +93 -0
  75. package/dist/codex-plugin/skills/ai-catapult-init/modules/foundation.md +19 -0
  76. package/dist/codex-plugin/skills/ai-catapult-init/modules/host-policy-automation.md +151 -0
  77. package/dist/codex-plugin/skills/ai-catapult-init/modules/language-packs.md +63 -0
  78. package/dist/codex-plugin/skills/ai-catapult-init/modules/mcp-a2a.md +63 -0
  79. package/dist/codex-plugin/skills/ai-catapult-init/modules/memory.md +102 -0
  80. package/dist/codex-plugin/skills/ai-catapult-init/modules/migration.md +107 -0
  81. package/dist/codex-plugin/skills/ai-catapult-init/modules/phases/01-discover-decide.md +33 -0
  82. package/dist/codex-plugin/skills/ai-catapult-init/modules/phases/README.md +33 -0
  83. package/dist/codex-plugin/skills/ai-catapult-init/modules/readme-documentation.md +120 -0
  84. package/dist/codex-plugin/skills/ai-catapult-init/modules/release-versioning.md +188 -0
  85. package/dist/codex-plugin/skills/ai-catapult-init/modules/skill-modernization.md +72 -0
  86. package/dist/codex-plugin/skills/ai-catapult-init/modules/sync.md +111 -0
  87. package/dist/codex-plugin/skills/ai-catapult-init/modules/topology.md +102 -0
  88. package/dist/codex-plugin/skills/ai-catapult-init/modules/traceability.md +136 -0
  89. package/dist/codex-plugin/skills/ai-catapult-init/modules/tracker-adapters.md +51 -0
  90. package/dist/codex-plugin/skills/ai-catapult-init/modules/validation.md +276 -0
  91. package/dist/codex-plugin/skills/ai-catapult-init/modules/workflow.md +45 -0
  92. package/dist/codex-plugin/skills/ai-catapult-init/templates/AGENTS.md +69 -0
  93. package/dist/codex-plugin/skills/ai-catapult-init/templates/CLAUDE.md +3 -0
  94. package/dist/codex-plugin/skills/ai-catapult-init/templates/GEMINI.md +3 -0
  95. package/dist/codex-plugin/skills/ai-catapult-init/templates/boundary-manifest.json +247 -0
  96. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/drift/backups/.gitkeep +0 -0
  97. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/drift/last-drift.json +7 -0
  98. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/evals/.gitkeep +0 -0
  99. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/evals/coverage-exceptions.json +1 -0
  100. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/handoff/.gitkeep +0 -0
  101. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/matrix.json +19 -0
  102. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/a2a-handoff.md +51 -0
  103. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/registry.json +27 -0
  104. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/observability/audit-checklist.md +32 -0
  105. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/observability/conventions.md +35 -0
  106. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/01-discover-decide/status.json +16 -0
  107. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/02-govern-plan/status.json +15 -0
  108. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/03-configure-generate/status.json +22 -0
  109. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/04-validate-handoff/status.json +18 -0
  110. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/policies/model-routing.json +29 -0
  111. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/reviews/ai-failure-modes.md +42 -0
  112. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/rules/security.md +38 -0
  113. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/rules/technical-bounds.md +38 -0
  114. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/skills/git-ops.json +6 -0
  115. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/skills/workspace-sync.json +6 -0
  116. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/architect.md +31 -0
  117. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/developer.md +31 -0
  118. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/qa-engineer.md +31 -0
  119. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/traceability/.gitkeep +0 -0
  120. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.json +42 -0
  121. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.md +52 -0
  122. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-github/workflows/ci-prek.yml +21 -0
  123. package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-rules.ts +178 -0
  124. package/dist/codex-plugin/skills/ai-catapult-init/templates/prek.toml +13 -0
  125. package/package.json +51 -0
  126. package/scripts/build-claude-plugin.sh +179 -0
  127. package/scripts/build-codex-plugin.sh +104 -0
  128. package/scripts/snapshot-dist.sh +26 -0
  129. package/setup.sh +63 -0
  130. package/skills.lock.json +6 -0
  131. package/src/install.js +380 -0
  132. package/src/scaffold.js +220 -0
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env bash
2
+ # build-codex-plugin.sh — assembles the Codex plugin into dist/codex-plugin/.
3
+ #
4
+ # Output layout:
5
+ # dist/codex-plugin/
6
+ # .codex-plugin/plugin.json — plugin manifest
7
+ # skills/ai-catapult-init/ — bundled skill copied from vendor/
8
+ #
9
+ # Deterministic: always wipes and rebuilds dist/codex-plugin/ for idempotence.
10
+ # Fail-closed: exits non-zero if vendor/skills is absent or incomplete.
11
+ #
12
+ # Accepts VENDOR_ROOT env override (for tests) — defaults to <repo>/vendor.
13
+
14
+ set -euo pipefail
15
+
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
18
+
19
+ VENDOR_ROOT="${VENDOR_ROOT:-${REPO_ROOT}/vendor}"
20
+ VENDOR_SKILLS="${VENDOR_ROOT}/skills"
21
+ SKILL_NAME="ai-catapult-init"
22
+ SKILL_SRC="${VENDOR_SKILLS}/${SKILL_NAME}"
23
+ DIST_DIR="${REPO_ROOT}/dist/codex-plugin"
24
+ PLUGIN_JSON_DIR="${DIST_DIR}/.codex-plugin"
25
+ SKILLS_DEST="${DIST_DIR}/skills"
26
+
27
+ # --- Fail closed if vendor missing ---
28
+ if [[ ! -d "${VENDOR_SKILLS}" ]]; then
29
+ echo "ERROR: vendor/skills directory not found at ${VENDOR_SKILLS}" >&2
30
+ echo " Run setup.sh first to vendor skills." >&2
31
+ exit 1
32
+ fi
33
+
34
+ if [[ ! -f "${SKILL_SRC}/SKILL.md" ]]; then
35
+ echo "ERROR: ${SKILL_SRC}/SKILL.md not found — vendor may be incomplete" >&2
36
+ echo " Run setup.sh to re-vendor skills." >&2
37
+ exit 1
38
+ fi
39
+
40
+ # --- Read version from package.json (node already required by project) ---
41
+ VERSION="$(PKG="${REPO_ROOT}/package.json" node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.PKG,'utf8')).version)")"
42
+ if [[ -z "${VERSION}" ]]; then
43
+ echo "ERROR: could not read version from package.json" >&2
44
+ exit 1
45
+ fi
46
+
47
+ echo "Building Codex plugin ai-catapult@${VERSION}..."
48
+
49
+ # --- Wipe + recreate output dirs for determinism ---
50
+ rm -rf "${DIST_DIR}"
51
+ mkdir -p "${PLUGIN_JSON_DIR}"
52
+ mkdir -p "${SKILLS_DEST}"
53
+
54
+ # --- Write plugin.json ---
55
+ cat > "${PLUGIN_JSON_DIR}/plugin.json" <<PLUGIN_JSON
56
+ {
57
+ "name": "ai-catapult",
58
+ "version": "${VERSION}",
59
+ "description": "CLI + Claude Code and Codex plugins for init-ai-repo v3 AI-SDLC governance scaffolding",
60
+ "skills": "./skills/",
61
+ "interface": {
62
+ "displayName": "ai-catapult",
63
+ "shortDescription": "Scaffold init-ai-repo v3 AI-SDLC governance structure in any repo.",
64
+ "category": "Developer Tools"
65
+ }
66
+ }
67
+ PLUGIN_JSON
68
+
69
+ # --- Copy vendored skill ---
70
+ cp -r "${SKILL_SRC}" "${SKILLS_DEST}/${SKILL_NAME}"
71
+
72
+ # --- Validate output ---
73
+ if [[ ! -f "${PLUGIN_JSON_DIR}/plugin.json" ]]; then
74
+ echo "ERROR: plugin.json was not written" >&2
75
+ exit 1
76
+ fi
77
+
78
+ # Validate JSON parses
79
+ node -e "JSON.parse(require('fs').readFileSync('${PLUGIN_JSON_DIR}/plugin.json','utf8'))" \
80
+ || { echo "ERROR: plugin.json is not valid JSON" >&2; exit 1; }
81
+
82
+ # Validate required fields
83
+ node -e "
84
+ const p = JSON.parse(require('fs').readFileSync('${PLUGIN_JSON_DIR}/plugin.json','utf8'));
85
+ const required = ['name','version','description','skills','interface'];
86
+ for (const f of required) {
87
+ if (!p[f]) { process.stderr.write('ERROR: plugin.json missing required field: ' + f + '\n'); process.exit(1); }
88
+ }
89
+ if (!p.interface.displayName) { process.stderr.write('ERROR: plugin.json interface.displayName missing\n'); process.exit(1); }
90
+ "
91
+
92
+ if [[ ! -d "${SKILLS_DEST}/${SKILL_NAME}" ]]; then
93
+ echo "ERROR: skills/${SKILL_NAME} directory not present in output" >&2
94
+ exit 1
95
+ fi
96
+
97
+ if [[ ! -f "${SKILLS_DEST}/${SKILL_NAME}/SKILL.md" ]]; then
98
+ echo "ERROR: skills/${SKILL_NAME}/SKILL.md not present in output" >&2
99
+ exit 1
100
+ fi
101
+
102
+ echo "OK: dist/codex-plugin assembled"
103
+ echo " .codex-plugin/plugin.json"
104
+ echo " skills/${SKILL_NAME}/SKILL.md"
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # snapshot-dist.sh — copy dist/ to dist-snapshot/ for stable test isolation.
3
+ #
4
+ # The install command respects AI_CATAPULT_DIST_ROOT; npm test sets it to
5
+ # dist-snapshot/ so install tests (and the finish-prompt drift-guard) always
6
+ # read from a stable snapshot rather than the live dist/ that claude-plugin
7
+ # tests wipe and rebuild concurrently.
8
+ #
9
+ # Run by: npm run pretest (before node --test)
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
15
+
16
+ SRC="${REPO_ROOT}/dist"
17
+ DEST="${REPO_ROOT}/dist-snapshot"
18
+
19
+ if [[ ! -d "${SRC}" ]]; then
20
+ echo "ERROR: dist/ not found at ${SRC} — run build scripts first" >&2
21
+ exit 1
22
+ fi
23
+
24
+ rm -rf "${DEST}"
25
+ cp -R "${SRC}" "${DEST}"
26
+ echo "OK: dist-snapshot/ created from dist/"
package/setup.sh ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # setup.sh — vendors r3dlex/skills at the SHA pinned in skills.lock.json.
3
+ # Idempotent: safe to re-run. Never commits vendor/ (it is gitignored).
4
+ # Does NOT use git submodules.
5
+
6
+ set -euo pipefail
7
+
8
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ LOCK_FILE="${REPO_ROOT}/skills.lock.json"
10
+ VENDOR_DIR="${REPO_ROOT}/vendor/skills"
11
+
12
+ # --- Read lock ---
13
+ if [[ ! -f "${LOCK_FILE}" ]]; then
14
+ echo "ERROR: ${LOCK_FILE} not found" >&2
15
+ exit 1
16
+ fi
17
+
18
+ SKILLS_REPO="$(LOCK_FILE="${LOCK_FILE}" node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.LOCK_FILE,'utf8')).repo)")"
19
+ LOCKED_SHA="$(LOCK_FILE="${LOCK_FILE}" node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.LOCK_FILE,'utf8')).sha)")"
20
+ LOCKED_REF="$(LOCK_FILE="${LOCK_FILE}" node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.LOCK_FILE,'utf8')).ref)")"
21
+
22
+ echo "skills lock: ${SKILLS_REPO}@${LOCKED_REF} (${LOCKED_SHA})"
23
+
24
+ # --- Idempotency check ---
25
+ if [[ -f "${VENDOR_DIR}/HEAD_SHA" ]]; then
26
+ CURRENT_SHA="$(tr -d '[:space:]' < "${VENDOR_DIR}/HEAD_SHA")"
27
+ if [[ "${CURRENT_SHA}" == "${LOCKED_SHA}" ]]; then
28
+ echo "vendor/skills already at ${LOCKED_SHA} — nothing to do."
29
+ exit 0
30
+ fi
31
+ echo "vendor/skills is at ${CURRENT_SHA}, re-vendoring to ${LOCKED_SHA}..."
32
+ rm -rf "${VENDOR_DIR}"
33
+ fi
34
+
35
+ # Clear any stale/partial vendor dir left by a previously interrupted run.
36
+ # (The idempotency short-circuit above already returned for a healthy, SHA-matched dir.)
37
+ [[ -e "${VENDOR_DIR}" ]] && rm -rf "${VENDOR_DIR}"
38
+
39
+ mkdir -p "$(dirname "${VENDOR_DIR}")"
40
+
41
+ # --- Clone or fetch at exact SHA ---
42
+ # Use a shallow clone of the ref, then checkout the exact SHA.
43
+ echo "Cloning ${SKILLS_REPO} (ref: ${LOCKED_REF})..."
44
+ git clone --depth=1 --branch "${LOCKED_REF}" "${SKILLS_REPO}" "${VENDOR_DIR}"
45
+
46
+ # Deepen/fetch the exact commit in case shallow tip differs (should match for main)
47
+ ACTUAL_SHA="$(git -C "${VENDOR_DIR}" rev-parse HEAD)"
48
+ if [[ "${ACTUAL_SHA}" != "${LOCKED_SHA}" ]]; then
49
+ echo "Shallow tip is ${ACTUAL_SHA}, fetching exact locked SHA ${LOCKED_SHA}..."
50
+ git -C "${VENDOR_DIR}" fetch --depth=1 origin "${LOCKED_SHA}"
51
+ git -C "${VENDOR_DIR}" checkout "${LOCKED_SHA}"
52
+ ACTUAL_SHA="$(git -C "${VENDOR_DIR}" rev-parse HEAD)"
53
+ fi
54
+
55
+ if [[ "${ACTUAL_SHA}" != "${LOCKED_SHA}" ]]; then
56
+ echo "ERROR: checked out SHA ${ACTUAL_SHA} does not match locked SHA ${LOCKED_SHA}" >&2
57
+ exit 1
58
+ fi
59
+
60
+ # Write HEAD_SHA sentinel so verify-vendor.sh can check without running git
61
+ echo "${LOCKED_SHA}" > "${VENDOR_DIR}/HEAD_SHA"
62
+
63
+ echo "OK: vendor/skills vendored at ${LOCKED_SHA}"
@@ -0,0 +1,6 @@
1
+ {
2
+ "repo": "https://github.com/r3dlex/skills.git",
3
+ "ref": "main",
4
+ "sha": "45ceec0c4f59c1b81a497ab0952c0a832aba4989",
5
+ "_note": "Pin to skills main post-Slice-2b. This SHA includes the ai-catapult-init rename and v3 templates under ai-catapult-init/templates/ plus boundary-manifest.json."
6
+ }
package/src/install.js ADDED
@@ -0,0 +1,380 @@
1
+ /**
2
+ * install.js — wire assembled plugins into Claude Code and/or Codex harnesses.
3
+ *
4
+ * Install targets:
5
+ * Claude Code: copies payload to
6
+ * ${HOME}/.claude/plugins/ai-catapult/
7
+ * then prints the two-step manual registration:
8
+ * /plugin marketplace add <payload-path>
9
+ * /plugin install ai-catapult@<marketplace-name>
10
+ *
11
+ * Codex: copies payload to
12
+ * ${CODEX_HOME}/plugins/cache/ai-catapult-local/ai-catapult/local/
13
+ * then prints the TOML block the user must add to config.toml
14
+ *
15
+ * Neither handler writes to Claude Code's internal installed_plugins.json nor
16
+ * to Codex's config.toml — those mutations are too invasive and carry corruption
17
+ * risk. We copy the payload and tell the user exactly what to do.
18
+ */
19
+
20
+ import {
21
+ existsSync,
22
+ mkdirSync,
23
+ rmSync,
24
+ cpSync,
25
+ readFileSync,
26
+ readdirSync,
27
+ } from 'node:fs';
28
+ import { spawnSync } from 'node:child_process';
29
+ import { fileURLToPath } from 'node:url';
30
+ import { dirname, join } from 'node:path';
31
+ import { homedir } from 'node:os';
32
+
33
+ const __dirname = dirname(fileURLToPath(import.meta.url));
34
+ const REPO_ROOT = join(__dirname, '..');
35
+
36
+ // DIST_ROOT may be overridden via env (used by tests to point at a stable pre-built copy)
37
+ const DIST_ROOT = process.env.AI_CATAPULT_DIST_ROOT ?? join(REPO_ROOT, 'dist');
38
+
39
+ // Marketplace / plugin identifiers (stable across installs)
40
+ const MARKETPLACE_NAME = 'ai-catapult-local';
41
+ const PLUGIN_NAME = 'ai-catapult';
42
+ const VERSION_DIR = 'local'; // version dir for local installs
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Internal helpers
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Ensure the plugin dist is present, building it if needed.
50
+ * @param {string} script - shell script filename under scripts/
51
+ * @param {string} distDir - dist output directory; rebuilds if absent
52
+ * @param {boolean} dryRun - if true, skip
53
+ */
54
+ function ensureBuilt(script, distDir, dryRun) {
55
+ if (dryRun) return;
56
+
57
+ // If DIST_ROOT was overridden via env and the distDir is missing, fail with
58
+ // a clear message rather than silently rebuilding into the wrong place.
59
+ if (process.env.AI_CATAPULT_DIST_ROOT && !existsSync(distDir)) {
60
+ throw new Error(
61
+ `AI_CATAPULT_DIST_ROOT is set but the expected dist directory is not populated.\n` +
62
+ `Expected: ${distDir}\n` +
63
+ `Run the build scripts first, or unset AI_CATAPULT_DIST_ROOT to use dist/.`,
64
+ );
65
+ }
66
+
67
+ // Check for either harness manifest to determine if already built
68
+ const claudeManifest = join(distDir, '.claude-plugin', 'plugin.json');
69
+ const codexManifest = join(distDir, '.codex-plugin', 'plugin.json');
70
+ if (existsSync(claudeManifest) || existsSync(codexManifest)) return;
71
+
72
+ // dist absent or empty — build now
73
+ const r = spawnSync('bash', [join(REPO_ROOT, 'scripts', script)], {
74
+ encoding: 'utf8',
75
+ cwd: REPO_ROOT,
76
+ timeout: 60000,
77
+ });
78
+ if (r.status !== 0) {
79
+ throw new Error(
80
+ `Plugin not built and build script failed.\n` +
81
+ `Run: bash scripts/${script}\n\n${r.stderr}\n${r.stdout}`,
82
+ );
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Check if an existing directory is a prior ai-catapult install (or empty).
88
+ * Returns true if safe to overwrite without --force.
89
+ *
90
+ * Rules:
91
+ * - Dir does not exist → safe
92
+ * - Dir exists and is empty → safe
93
+ * - Dir exists and carries our plugin.json name → safe
94
+ * - Dir exists and is non-empty without our plugin.json → NOT safe
95
+ */
96
+ function isSafeToOverwrite(dir) {
97
+ if (!existsSync(dir)) return true;
98
+
99
+ // Check for .claude-plugin/plugin.json
100
+ const claudeManifest = join(dir, '.claude-plugin', 'plugin.json');
101
+ if (existsSync(claudeManifest)) {
102
+ try {
103
+ const p = JSON.parse(readFileSync(claudeManifest, 'utf8'));
104
+ return p.name === PLUGIN_NAME;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ // Check for .codex-plugin/plugin.json
111
+ const codexManifest = join(dir, '.codex-plugin', 'plugin.json');
112
+ if (existsSync(codexManifest)) {
113
+ try {
114
+ const p = JSON.parse(readFileSync(codexManifest, 'utf8'));
115
+ return p.name === PLUGIN_NAME;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ // Dir exists but has no manifest — only safe if it is empty
122
+ try {
123
+ return readdirSync(dir).length === 0;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Wipe and recreate a directory.
131
+ */
132
+ function resetDir(dir) {
133
+ rmSync(dir, { recursive: true, force: true });
134
+ mkdirSync(dir, { recursive: true });
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Claude Code install
139
+ // ---------------------------------------------------------------------------
140
+
141
+ /**
142
+ * Install the Claude Code plugin.
143
+ *
144
+ * Copies the plugin payload to a stable path under ~/.claude/plugins/ai-catapult/
145
+ * and prints the two-step manual registration the user must run inside Claude Code.
146
+ * We do NOT write installed_plugins.json — that is a Claude Code internal file
147
+ * and hand-writing it is brittle.
148
+ *
149
+ * @param {object} opts
150
+ * @param {string} opts.claudeDir - ${HOME}/.claude/
151
+ * @param {boolean} opts.dryRun
152
+ * @param {boolean} opts.force
153
+ */
154
+ function installClaude({ claudeDir, dryRun, force }) {
155
+ // Stable payload path (not inside the cache hierarchy — avoids collision with
156
+ // Claude Code's own cache management).
157
+ const payloadPath = join(claudeDir, 'plugins', PLUGIN_NAME);
158
+
159
+ if (dryRun) {
160
+ process.stdout.write(`[dry-run] claude: would copy payload to ${payloadPath}\n`);
161
+ process.stdout.write(`[dry-run] claude: would print /plugin registration instructions\n`);
162
+ return;
163
+ }
164
+
165
+ // Safety check
166
+ if (!isSafeToOverwrite(payloadPath)) {
167
+ if (!force) {
168
+ process.stderr.write(
169
+ `Error: ${payloadPath} exists and is not a prior ai-catapult install.\n` +
170
+ `Use --force to overwrite.\n`,
171
+ );
172
+ process.exit(1);
173
+ }
174
+ process.stdout.write(`Warning: overwriting foreign plugin dir (--force)\n`);
175
+ }
176
+
177
+ // Build the plugin if dist is not already assembled
178
+ const distDir = join(DIST_ROOT, 'claude-plugin');
179
+ ensureBuilt('build-claude-plugin.sh', distDir, dryRun);
180
+
181
+ // Copy dist/claude-plugin → payloadPath
182
+ resetDir(payloadPath);
183
+ cpSync(distDir, payloadPath, { recursive: true });
184
+
185
+ // Read version from installed plugin.json
186
+ const pluginJson = join(payloadPath, '.claude-plugin', 'plugin.json');
187
+ const manifest = JSON.parse(readFileSync(pluginJson, 'utf8'));
188
+ const marketplaceName = manifest.name ?? PLUGIN_NAME;
189
+
190
+ process.stdout.write(`Installed Claude Code plugin ai-catapult@${manifest.version}\n`);
191
+ process.stdout.write(` payload: ${payloadPath}\n`);
192
+ process.stdout.write(`\nTo register the plugin in Claude Code, run these two commands inside Claude Code:\n`);
193
+ process.stdout.write(`\n /plugin marketplace add ${payloadPath}\n`);
194
+ process.stdout.write(` /plugin install ${marketplaceName}@${MARKETPLACE_NAME}\n`);
195
+ process.stdout.write(`\nThen reload Claude Code for the plugin to take effect.\n`);
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Codex install
200
+ // ---------------------------------------------------------------------------
201
+
202
+ /**
203
+ * Install the Codex plugin.
204
+ *
205
+ * Copies the plugin payload to the standard Codex cache path and prints
206
+ * the TOML block the user must add to their config.toml. We do NOT
207
+ * auto-mutate config.toml — that carries corruption risk.
208
+ *
209
+ * Codex discovers plugins via config.toml tables:
210
+ * [marketplaces.<name>] with source_type/source
211
+ * [plugins."<plugin>@<marketplace>"] with enabled = true
212
+ *
213
+ * @param {object} opts
214
+ * @param {string} opts.codexHome - ${CODEX_HOME:-~/.codex}
215
+ * @param {boolean} opts.dryRun
216
+ * @param {boolean} opts.force
217
+ */
218
+ function installCodex({ codexHome, dryRun, force }) {
219
+ const pluginRoot = join(codexHome, 'plugins', 'cache', MARKETPLACE_NAME, PLUGIN_NAME, VERSION_DIR);
220
+
221
+ if (dryRun) {
222
+ process.stdout.write(`[dry-run] codex: would copy payload to ${pluginRoot}\n`);
223
+ process.stdout.write(`[dry-run] codex: would print TOML block for config.toml registration\n`);
224
+ return;
225
+ }
226
+
227
+ // Safety check
228
+ if (!isSafeToOverwrite(pluginRoot)) {
229
+ if (!force) {
230
+ process.stderr.write(
231
+ `Error: ${pluginRoot} exists and is not a prior ai-catapult install.\n` +
232
+ `Use --force to overwrite.\n`,
233
+ );
234
+ process.exit(1);
235
+ }
236
+ process.stdout.write(`Warning: overwriting foreign plugin dir (--force)\n`);
237
+ }
238
+
239
+ // Build the plugin if dist is not already assembled
240
+ const distDir = join(DIST_ROOT, 'codex-plugin');
241
+ ensureBuilt('build-codex-plugin.sh', distDir, dryRun);
242
+
243
+ // Copy dist/codex-plugin → pluginRoot
244
+ resetDir(pluginRoot);
245
+ cpSync(distDir, pluginRoot, { recursive: true });
246
+
247
+ // Read version from installed plugin.json
248
+ const pluginJsonPath = join(pluginRoot, '.codex-plugin', 'plugin.json');
249
+ const manifest = JSON.parse(readFileSync(pluginJsonPath, 'utf8'));
250
+
251
+ process.stdout.write(`Installed Codex plugin ai-catapult@${manifest.version}\n`);
252
+ process.stdout.write(` payload: ${pluginRoot}\n`);
253
+ process.stdout.write(`\nTo register the plugin, add the following block to your Codex config.toml\n`);
254
+ process.stdout.write(`(typically at \${CODEX_HOME:-~/.codex}/config.toml):\n`);
255
+ process.stdout.write(`\n`);
256
+ process.stdout.write(`[marketplaces.${MARKETPLACE_NAME}]\n`);
257
+ process.stdout.write(`source_type = "local"\n`);
258
+ process.stdout.write(`source = "${pluginRoot}"\n`);
259
+ process.stdout.write(`\n`);
260
+ process.stdout.write(`[plugins."${PLUGIN_NAME}@${MARKETPLACE_NAME}"]\n`);
261
+ process.stdout.write(`enabled = true\n`);
262
+ process.stdout.write(`\nThen restart Codex for the plugin to take effect.\n`);
263
+ }
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // Public entry point
267
+ // ---------------------------------------------------------------------------
268
+
269
+ const INSTALL_HELP = `Usage: ai-catapult install [options]
270
+
271
+ Install the ai-catapult plugin into detected AI coding harnesses.
272
+
273
+ Detected harnesses:
274
+ Claude Code ~/.claude/ present
275
+ Codex \${CODEX_HOME:-~/.codex}/ present
276
+
277
+ Options:
278
+ --harness <claude|codex|all> Select harness(es) to install into (default: auto-detect)
279
+ --dry-run Print what would happen without writing
280
+ --force Overwrite existing dirs even if not a prior ai-catapult install
281
+ -h, --help Show this help`;
282
+
283
+ /**
284
+ * Main install handler.
285
+ * @param {string[]} argv - arguments after "install" (already sliced)
286
+ * @param {object} [envOverride] - override HOME / CODEX_HOME (for tests)
287
+ */
288
+ export function runInstall(argv, envOverride = {}) {
289
+ // Parse flags
290
+ const flags = new Map();
291
+ const positionals = [];
292
+ let i = 0;
293
+ while (i < argv.length) {
294
+ const arg = argv[i];
295
+ if (arg === '--') { positionals.push(...argv.slice(i + 1)); break; }
296
+ if (arg.startsWith('--')) {
297
+ const key = arg.slice(2);
298
+ const next = argv[i + 1];
299
+ if (next !== undefined && !next.startsWith('-')) {
300
+ flags.set(key, next);
301
+ i += 2;
302
+ } else {
303
+ flags.set(key, true);
304
+ i += 1;
305
+ }
306
+ } else if (arg.startsWith('-') && arg.length === 2) {
307
+ flags.set(arg.slice(1), true);
308
+ i += 1;
309
+ } else {
310
+ positionals.push(arg);
311
+ i += 1;
312
+ }
313
+ }
314
+
315
+ if (flags.has('help') || flags.has('h')) {
316
+ process.stdout.write(INSTALL_HELP + '\n');
317
+ process.exit(0);
318
+ }
319
+
320
+ const dryRun = flags.has('dry-run');
321
+ const force = flags.has('force');
322
+ const harnessFlag = flags.get('harness'); // 'claude' | 'codex' | 'all' | undefined
323
+
324
+ // Resolve dirs
325
+ const home = envOverride.HOME ?? process.env.HOME ?? homedir();
326
+ const codexHome = envOverride.CODEX_HOME
327
+ ?? process.env.CODEX_HOME
328
+ ?? join(home, '.codex');
329
+
330
+ const claudeDir = join(home, '.claude');
331
+ const claudeDetected = existsSync(claudeDir);
332
+ const codexDetected = existsSync(codexHome);
333
+
334
+ // Determine which harnesses to run
335
+ let runClaude = false;
336
+ let runCodex = false;
337
+
338
+ if (harnessFlag === 'claude') {
339
+ runClaude = true;
340
+ } else if (harnessFlag === 'codex') {
341
+ runCodex = true;
342
+ } else if (harnessFlag === 'all') {
343
+ runClaude = true;
344
+ runCodex = true;
345
+ } else {
346
+ // Auto-detect
347
+ runClaude = claudeDetected;
348
+ runCodex = codexDetected;
349
+ }
350
+
351
+ if (!runClaude && !runCodex) {
352
+ process.stdout.write(
353
+ 'No supported harness detected.\n' +
354
+ ' Claude Code: ~/.claude/ not found\n' +
355
+ ` Codex: ${codexHome} not found\n` +
356
+ '\nUse --harness claude|codex|all to force a harness.\n',
357
+ );
358
+ process.exit(0);
359
+ }
360
+
361
+ if (dryRun) {
362
+ process.stdout.write('[dry-run] No changes will be made.\n');
363
+ }
364
+
365
+ if (runClaude) {
366
+ if (!claudeDetected && !dryRun) {
367
+ process.stdout.write(`Skipping Claude Code: ${claudeDir} not found\n`);
368
+ } else {
369
+ installClaude({ claudeDir, dryRun, force });
370
+ }
371
+ }
372
+
373
+ if (runCodex) {
374
+ if (!codexDetected && !dryRun) {
375
+ process.stdout.write(`Skipping Codex: ${codexHome} not found\n`);
376
+ } else {
377
+ installCodex({ codexHome, dryRun, force });
378
+ }
379
+ }
380
+ }