@kal-elsam/kairo-runtime 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 (315) hide show
  1. package/README.md +937 -0
  2. package/bin/harness.js +11 -0
  3. package/bin/kairo-runtime.js +11 -0
  4. package/bin/kairo.js +11 -0
  5. package/global-template/components/catalog.json +25 -0
  6. package/global-template/components/orchestrator/orchestrator.md +55 -0
  7. package/global-template/components/sdd-core/handoff.md +38 -0
  8. package/global-template/components/sdd-core/spec-sizing.md +38 -0
  9. package/global-template/components/sdd-core/workflow.md +31 -0
  10. package/global-template/core/orchestrator.md +55 -0
  11. package/package.json +64 -0
  12. package/prompts/HARNESS_ENFORCEMENT_UPGRADE.md +11 -0
  13. package/prompts/HARNESS_ENTERPRISE.md +33 -0
  14. package/prompts/HARNESS_INSTALLER_MASTER.md +853 -0
  15. package/prompts/HARNESS_MINIMAL.md +30 -0
  16. package/prompts/HARNESS_SPEC_SIZING_UPGRADE.md +41 -0
  17. package/prompts/HARNESS_STANDARD.md +35 -0
  18. package/prompts/HARNESS_UNIVERSAL_PARITY.md +56 -0
  19. package/repo-template/.claude/agents/adapter-parity-reviewer.md +17 -0
  20. package/repo-template/.claude/agents/architect.md +17 -0
  21. package/repo-template/.claude/agents/eval-engineer.md +17 -0
  22. package/repo-template/.claude/agents/reviewer.md +17 -0
  23. package/repo-template/.claude/agents/sdd-reviewer.md +17 -0
  24. package/repo-template/.claude/agents/sdd-spec-writer.md +17 -0
  25. package/repo-template/.claude/skills/checkpoint/SKILL.md +13 -0
  26. package/repo-template/.claude/skills/eval-design/SKILL.md +3 -0
  27. package/repo-template/.claude/skills/evals/SKILL.md +13 -0
  28. package/repo-template/.claude/skills/loop-engineering/SKILL.md +16 -0
  29. package/repo-template/.claude/skills/maintainability-review/SKILL.md +3 -0
  30. package/repo-template/.claude/skills/quality-gate-review/SKILL.md +3 -0
  31. package/repo-template/.claude/skills/sdd/SKILL.md +13 -0
  32. package/repo-template/.claude/skills/spec-complexity-classifier/SKILL.md +10 -0
  33. package/repo-template/.claude/skills/spec-escalation-review/SKILL.md +10 -0
  34. package/repo-template/.claude/skills/spec-intake/SKILL.md +10 -0
  35. package/repo-template/.claude/skills/tdd/SKILL.md +13 -0
  36. package/repo-template/.claude/skills/trust-review/SKILL.md +3 -0
  37. package/repo-template/.codex/skills/adapter-parity/SKILL.md +29 -0
  38. package/repo-template/.codex/skills/checkpoint/SKILL.md +36 -0
  39. package/repo-template/.codex/skills/eval-design/SKILL.md +3 -0
  40. package/repo-template/.codex/skills/evals/SKILL.md +28 -0
  41. package/repo-template/.codex/skills/loop-engineering/SKILL.md +24 -0
  42. package/repo-template/.codex/skills/maintainability-review/SKILL.md +3 -0
  43. package/repo-template/.codex/skills/quality-gate-review/SKILL.md +3 -0
  44. package/repo-template/.codex/skills/sdd/SKILL.md +30 -0
  45. package/repo-template/.codex/skills/spec-complexity-classifier/SKILL.md +10 -0
  46. package/repo-template/.codex/skills/spec-escalation-review/SKILL.md +10 -0
  47. package/repo-template/.codex/skills/spec-intake/SKILL.md +10 -0
  48. package/repo-template/.codex/skills/tdd/SKILL.md +27 -0
  49. package/repo-template/.codex/skills/trust-review/SKILL.md +3 -0
  50. package/repo-template/.cursor/agents/adapter-parity-reviewer.md +29 -0
  51. package/repo-template/.cursor/agents/architect.md +27 -0
  52. package/repo-template/.cursor/agents/context-engineer.md +27 -0
  53. package/repo-template/.cursor/agents/debugger.md +27 -0
  54. package/repo-template/.cursor/agents/eval-engineer.md +27 -0
  55. package/repo-template/.cursor/agents/harness-orchestrator.md +27 -0
  56. package/repo-template/.cursor/agents/loop-controller.md +17 -0
  57. package/repo-template/.cursor/agents/loop-debugger.md +16 -0
  58. package/repo-template/.cursor/agents/reviewer.md +27 -0
  59. package/repo-template/.cursor/agents/sdd/sdd-acceptance-reviewer.md +31 -0
  60. package/repo-template/.cursor/agents/sdd/sdd-implementation-guardian.md +31 -0
  61. package/repo-template/.cursor/agents/sdd/sdd-intake-analyst.md +31 -0
  62. package/repo-template/.cursor/agents/sdd/sdd-orchestrator.md +31 -0
  63. package/repo-template/.cursor/agents/sdd/sdd-spec-reviewer.md +31 -0
  64. package/repo-template/.cursor/agents/sdd/sdd-spec-writer.md +31 -0
  65. package/repo-template/.cursor/agents/sdd/sdd-task-breakdown.md +31 -0
  66. package/repo-template/.cursor/agents/sdd/sdd-technical-planner.md +31 -0
  67. package/repo-template/.cursor/agents/sdd/sdd-test-planner.md +31 -0
  68. package/repo-template/.cursor/agents/spec-intake-analyst.md +14 -0
  69. package/repo-template/.cursor/agents/test-engineer.md +27 -0
  70. package/repo-template/.cursor/commands/adapter-parity.md +34 -0
  71. package/repo-template/.cursor/commands/checkpoint.md +30 -0
  72. package/repo-template/.cursor/commands/debug.md +13 -0
  73. package/repo-template/.cursor/commands/init-eval.md +12 -0
  74. package/repo-template/.cursor/commands/init-feature.md +12 -0
  75. package/repo-template/.cursor/commands/init-harness.md +13 -0
  76. package/repo-template/.cursor/commands/loop-debug.md +3 -0
  77. package/repo-template/.cursor/commands/loop-review.md +3 -0
  78. package/repo-template/.cursor/commands/loop-run.md +5 -0
  79. package/repo-template/.cursor/commands/review.md +21 -0
  80. package/repo-template/.cursor/commands/sdd-intake.md +18 -0
  81. package/repo-template/.cursor/commands/sdd-plan.md +18 -0
  82. package/repo-template/.cursor/commands/sdd-spec.md +18 -0
  83. package/repo-template/.cursor/commands/sdd-tasks.md +18 -0
  84. package/repo-template/.cursor/commands/sdd-validate.md +18 -0
  85. package/repo-template/.cursor/commands/spec-escalate.md +9 -0
  86. package/repo-template/.cursor/commands/spec-intake.md +10 -0
  87. package/repo-template/.cursor/commands/spec-size.md +21 -0
  88. package/repo-template/.cursor/rules/adapter-parity.mdc +20 -0
  89. package/repo-template/.cursor/rules/api.mdc +12 -0
  90. package/repo-template/.cursor/rules/core.mdc +21 -0
  91. package/repo-template/.cursor/rules/enforcement.mdc +16 -0
  92. package/repo-template/.cursor/rules/evals.mdc +10 -0
  93. package/repo-template/.cursor/rules/git.mdc +19 -0
  94. package/repo-template/.cursor/rules/harness.mdc +14 -0
  95. package/repo-template/.cursor/rules/loops.mdc +17 -0
  96. package/repo-template/.cursor/rules/testing.mdc +13 -0
  97. package/repo-template/.cursor/rules/ui.mdc +13 -0
  98. package/repo-template/.cursor/skills/api-design/SKILL.md +23 -0
  99. package/repo-template/.cursor/skills/architecture/SKILL.md +23 -0
  100. package/repo-template/.cursor/skills/code-review/SKILL.md +23 -0
  101. package/repo-template/.cursor/skills/context-graph/SKILL.md +23 -0
  102. package/repo-template/.cursor/skills/debugging/SKILL.md +23 -0
  103. package/repo-template/.cursor/skills/evals/SKILL.md +23 -0
  104. package/repo-template/.cursor/skills/git-workflow/SKILL.md +23 -0
  105. package/repo-template/.cursor/skills/testing/SKILL.md +23 -0
  106. package/repo-template/.cursor/skills/ui-design/SKILL.md +23 -0
  107. package/repo-template/.gentle-ai/README.md +37 -0
  108. package/repo-template/.gentle-ai/loops/README.md +27 -0
  109. package/repo-template/.gentle-ai/loops/sdd-tdd-repair.loop.md +27 -0
  110. package/repo-template/.gentle-ai/model-routing.md +25 -0
  111. package/repo-template/.github/copilot-instructions.md +37 -0
  112. package/repo-template/.github/dependabot.yml +11 -0
  113. package/repo-template/.github/instructions/harness.instructions.md +11 -0
  114. package/repo-template/.github/workflows/harness-quality-gate.yml +67 -0
  115. package/repo-template/.github/workflows/harness-security-gate.yml +32 -0
  116. package/repo-template/.harness/spec-sizing.json +33 -0
  117. package/repo-template/.opencode/README.md +71 -0
  118. package/repo-template/.opencode/agents/adapter-parity-reviewer.md +23 -0
  119. package/repo-template/.opencode/agents/architect.md +38 -0
  120. package/repo-template/.opencode/agents/checkpoint-reviewer.md +38 -0
  121. package/repo-template/.opencode/agents/eval-engineer.md +38 -0
  122. package/repo-template/.opencode/agents/loop-controller.md +38 -0
  123. package/repo-template/.opencode/agents/loop-debugger.md +38 -0
  124. package/repo-template/.opencode/agents/model-router.md +38 -0
  125. package/repo-template/.opencode/agents/sdd-orchestrator.md +38 -0
  126. package/repo-template/.opencode/agents/sdd-spec-reviewer.md +38 -0
  127. package/repo-template/.opencode/agents/spec-intake-analyst.md +19 -0
  128. package/repo-template/.opencode/agents/tdd-engineer.md +38 -0
  129. package/repo-template/.opencode/commands/checkpoint.md +3 -0
  130. package/repo-template/.opencode/commands/eval-design.md +3 -0
  131. package/repo-template/.opencode/commands/maintainability-review.md +3 -0
  132. package/repo-template/.opencode/commands/quality-gate.md +3 -0
  133. package/repo-template/.opencode/commands/spec-escalate.md +9 -0
  134. package/repo-template/.opencode/commands/spec-intake.md +10 -0
  135. package/repo-template/.opencode/commands/spec-size.md +21 -0
  136. package/repo-template/.opencode/commands/trust-review.md +3 -0
  137. package/repo-template/.opencode/loops/ai-behavior-improvement.loop.md +15 -0
  138. package/repo-template/.opencode/loops/bug-repair.loop.md +16 -0
  139. package/repo-template/.opencode/loops/documentation-sync.loop.md +13 -0
  140. package/repo-template/.opencode/loops/feature-development.loop.md +25 -0
  141. package/repo-template/.opencode/skills/spec-complexity-classifier/SKILL.md +10 -0
  142. package/repo-template/.opencode/skills/spec-escalation-review/SKILL.md +10 -0
  143. package/repo-template/.opencode/skills/spec-intake/SKILL.md +10 -0
  144. package/repo-template/.pi/README.md +21 -0
  145. package/repo-template/.pi/prompts/checkpoint.md +11 -0
  146. package/repo-template/.pi/prompts/loop-run.md +10 -0
  147. package/repo-template/.pi/prompts/sdd-plan.md +11 -0
  148. package/repo-template/.pi/prompts/sdd-spec.md +13 -0
  149. package/repo-template/.pi/prompts/spec-escalate.md +9 -0
  150. package/repo-template/.pi/prompts/spec-intake.md +10 -0
  151. package/repo-template/.pi/prompts/spec-size.md +21 -0
  152. package/repo-template/.pi/skills/adapter-parity/SKILL.md +7 -0
  153. package/repo-template/.pi/skills/checkpoint/SKILL.md +7 -0
  154. package/repo-template/.pi/skills/eval-design/SKILL.md +3 -0
  155. package/repo-template/.pi/skills/evals/SKILL.md +7 -0
  156. package/repo-template/.pi/skills/loop-engineering/SKILL.md +7 -0
  157. package/repo-template/.pi/skills/maintainability-review/SKILL.md +3 -0
  158. package/repo-template/.pi/skills/quality-gate-review/SKILL.md +3 -0
  159. package/repo-template/.pi/skills/sdd/SKILL.md +7 -0
  160. package/repo-template/.pi/skills/spec-complexity-classifier/SKILL.md +10 -0
  161. package/repo-template/.pi/skills/spec-escalation-review/SKILL.md +10 -0
  162. package/repo-template/.pi/skills/spec-intake/SKILL.md +10 -0
  163. package/repo-template/.pi/skills/tdd/SKILL.md +7 -0
  164. package/repo-template/.pi/skills/trust-review/SKILL.md +3 -0
  165. package/repo-template/AGENT.md +8 -0
  166. package/repo-template/AGENTS.md +165 -0
  167. package/repo-template/CLAUDE.md +9 -0
  168. package/repo-template/GEMINI.md +9 -0
  169. package/repo-template/docs/ai/adapter-parity.md +135 -0
  170. package/repo-template/docs/ai/agent-workflow.md +45 -0
  171. package/repo-template/docs/ai/api.md +28 -0
  172. package/repo-template/docs/ai/architecture.md +53 -0
  173. package/repo-template/docs/ai/context-budget.md +54 -0
  174. package/repo-template/docs/ai/context-graph.md +39 -0
  175. package/repo-template/docs/ai/conventions.md +34 -0
  176. package/repo-template/docs/ai/decision-log.md +5 -0
  177. package/repo-template/docs/ai/enforcement.md +63 -0
  178. package/repo-template/docs/ai/eval-strategy.md +42 -0
  179. package/repo-template/docs/ai/evals.md +44 -0
  180. package/repo-template/docs/ai/git-workflow.md +35 -0
  181. package/repo-template/docs/ai/governance.md +61 -0
  182. package/repo-template/docs/ai/harness.md +49 -0
  183. package/repo-template/docs/ai/installer-cli.md +34 -0
  184. package/repo-template/docs/ai/loop-log.md +5 -0
  185. package/repo-template/docs/ai/loop-observability.md +57 -0
  186. package/repo-template/docs/ai/loop-policy.md +86 -0
  187. package/repo-template/docs/ai/loops.md +95 -0
  188. package/repo-template/docs/ai/maintainability-gates.md +26 -0
  189. package/repo-template/docs/ai/memory.md +71 -0
  190. package/repo-template/docs/ai/model-policy.md +105 -0
  191. package/repo-template/docs/ai/observability-runtime.md +48 -0
  192. package/repo-template/docs/ai/provider-routing.md +138 -0
  193. package/repo-template/docs/ai/quality-gates.md +48 -0
  194. package/repo-template/docs/ai/rollback-runtime.md +25 -0
  195. package/repo-template/docs/ai/security.md +12 -0
  196. package/repo-template/docs/ai/spec-driven-development.md +124 -0
  197. package/repo-template/docs/ai/spec-escalation.md +44 -0
  198. package/repo-template/docs/ai/spec-intake.md +46 -0
  199. package/repo-template/docs/ai/spec-sizing.md +207 -0
  200. package/repo-template/docs/ai/test-driven-development.md +35 -0
  201. package/repo-template/docs/ai/testing.md +28 -0
  202. package/repo-template/docs/ai/tool-adapters.md +186 -0
  203. package/repo-template/docs/ai/trust-allowlist.md +5 -0
  204. package/repo-template/docs/ai/trust-policy.md +41 -0
  205. package/repo-template/docs/ai/ui.md +17 -0
  206. package/repo-template/docs/skills/adapter-parity-review.md +46 -0
  207. package/repo-template/docs/skills/api-design.md +37 -0
  208. package/repo-template/docs/skills/architecture.md +37 -0
  209. package/repo-template/docs/skills/code-review.md +37 -0
  210. package/repo-template/docs/skills/context-graph.md +38 -0
  211. package/repo-template/docs/skills/debugging.md +37 -0
  212. package/repo-template/docs/skills/eval-design.md +12 -0
  213. package/repo-template/docs/skills/evals.md +37 -0
  214. package/repo-template/docs/skills/git-workflow.md +37 -0
  215. package/repo-template/docs/skills/look-trust-review.md +12 -0
  216. package/repo-template/docs/skills/loop-debugging.md +16 -0
  217. package/repo-template/docs/skills/loop-design.md +39 -0
  218. package/repo-template/docs/skills/loop-retrospective.md +15 -0
  219. package/repo-template/docs/skills/loop-review.md +16 -0
  220. package/repo-template/docs/skills/maintainability-review.md +12 -0
  221. package/repo-template/docs/skills/model-selection.md +36 -0
  222. package/repo-template/docs/skills/quality-gate-review.md +12 -0
  223. package/repo-template/docs/skills/spec-complexity-classifier.md +41 -0
  224. package/repo-template/docs/skills/spec-escalation-review.md +34 -0
  225. package/repo-template/docs/skills/spec-intake.md +25 -0
  226. package/repo-template/docs/skills/testing.md +37 -0
  227. package/repo-template/docs/skills/tool-adapter-sync.md +36 -0
  228. package/repo-template/docs/skills/ui-design.md +37 -0
  229. package/repo-template/docs/specs/README.md +9 -0
  230. package/repo-template/docs/specs/templates/basic-spec.md +41 -0
  231. package/repo-template/docs/specs/templates/complex-spec.md +145 -0
  232. package/repo-template/docs/specs/templates/standard-spec.md +58 -0
  233. package/repo-template/evals/README.md +11 -0
  234. package/repo-template/evals/golden/sample-golden.json +12 -0
  235. package/repo-template/evals/loop-regression/README.md +11 -0
  236. package/repo-template/evals/regression/sample-regression.json +12 -0
  237. package/repo-template/evals/schema/sample-schema.json +12 -0
  238. package/repo-template/evals/tool-calls/sample-tool-calls.json +12 -0
  239. package/repo-template/opencode.json.sample +29 -0
  240. package/repo-template/scripts/harness/backup.mjs +7 -0
  241. package/repo-template/scripts/harness/doctor.mjs +20 -0
  242. package/repo-template/scripts/harness/run-evals.mjs +27 -0
  243. package/repo-template/setup-agent-links.sh +54 -0
  244. package/scripts/check-published-release.mjs +127 -0
  245. package/scripts/check-release-commit.mjs +32 -0
  246. package/scripts/install.sh +225 -0
  247. package/scripts/installer-smoke-test.sh +155 -0
  248. package/scripts/lib/attribution-guard.mjs +59 -0
  249. package/scripts/registry-smoke-test.sh +149 -0
  250. package/scripts/smoke-test.sh +197 -0
  251. package/scripts/ux-smoke-test.sh +140 -0
  252. package/src/cli.js +625 -0
  253. package/src/doctor.js +68 -0
  254. package/src/global/adapter-context.js +27 -0
  255. package/src/global/adapter-matrix.js +38 -0
  256. package/src/global/adapters/claude.js +8 -0
  257. package/src/global/adapters/codex.js +8 -0
  258. package/src/global/adapters/cursor.js +8 -0
  259. package/src/global/adapters/opencode.js +8 -0
  260. package/src/global/agents.js +7 -0
  261. package/src/global/apply-confirmation.js +62 -0
  262. package/src/global/backups.js +54 -0
  263. package/src/global/brand/cli.js +52 -0
  264. package/src/global/brand/index.js +85 -0
  265. package/src/global/clack/setup-preview.js +48 -0
  266. package/src/global/clack/setup-wizard-constants.js +6 -0
  267. package/src/global/clack/setup-wizard.js +207 -0
  268. package/src/global/clack/theme.js +145 -0
  269. package/src/global/component-authoring.js +121 -0
  270. package/src/global/component-builders.js +43 -0
  271. package/src/global/component-distribution.js +270 -0
  272. package/src/global/component-installer.js +106 -0
  273. package/src/global/component-paths.js +22 -0
  274. package/src/global/component-registry.js +89 -0
  275. package/src/global/components/orchestrator.js +14 -0
  276. package/src/global/components/sdd-core.js +17 -0
  277. package/src/global/diff.js +233 -0
  278. package/src/global/drift.js +264 -0
  279. package/src/global/explain.js +144 -0
  280. package/src/global/global-cli.js +1145 -0
  281. package/src/global/global-doctor.js +67 -0
  282. package/src/global/global-installer.js +284 -0
  283. package/src/global/history.js +313 -0
  284. package/src/global/ink/run-setup-ink.js +77 -0
  285. package/src/global/ink/setup-app.js +278 -0
  286. package/src/global/ink/setup-routing.js +51 -0
  287. package/src/global/ink/setup-state.js +180 -0
  288. package/src/global/ink/terminal.js +13 -0
  289. package/src/global/json-output.js +22 -0
  290. package/src/global/load-component-catalog.js +41 -0
  291. package/src/global/load-workspace-component-catalog.js +123 -0
  292. package/src/global/managed-body.js +28 -0
  293. package/src/global/managed-config-adapter.js +134 -0
  294. package/src/global/managed-section.js +73 -0
  295. package/src/global/npm-registry.js +25 -0
  296. package/src/global/paths.js +22 -0
  297. package/src/global/policy.js +344 -0
  298. package/src/global/preflight.js +65 -0
  299. package/src/global/registry.js +79 -0
  300. package/src/global/report.js +110 -0
  301. package/src/global/rollback.js +126 -0
  302. package/src/global/setup.js +284 -0
  303. package/src/global/state-migration.js +65 -0
  304. package/src/global/state.js +50 -0
  305. package/src/global/status.js +94 -0
  306. package/src/global/sync.js +129 -0
  307. package/src/global/upgrade.js +145 -0
  308. package/src/harness-files.js +145 -0
  309. package/src/harness-updater.js +120 -0
  310. package/src/hash.js +5 -0
  311. package/src/manifest.js +36 -0
  312. package/src/project-detection.js +105 -0
  313. package/src/template-installer.js +72 -0
  314. package/src/text-template.js +24 -0
  315. package/src/workspace-cli.js +115 -0
@@ -0,0 +1,145 @@
1
+ import { styleText } from "node:util";
2
+ import {
3
+ AGENT_HINTS,
4
+ BRAND,
5
+ getAgentLabel,
6
+ getAgentSymbol,
7
+ TOKENS,
8
+ WIZARD_COPY
9
+ } from "../brand/index.js";
10
+ import { formatCliCommand, PREFERRED_CLI } from "../brand/cli.js";
11
+
12
+ export function brandIntroTitle() {
13
+ return WIZARD_COPY.introTitle;
14
+ }
15
+
16
+ export function paint(text, token) {
17
+ const color = TOKENS[token];
18
+ if (!color) return text;
19
+ return styleText(color, text);
20
+ }
21
+
22
+ export function formatSplashNote() {
23
+ return [
24
+ BRAND.splashLine,
25
+ "",
26
+ "Managed sections · drift repair · ~/.harness state"
27
+ ].join("\n");
28
+ }
29
+
30
+ export function formatAgentDetectCard({ adapters, detected }) {
31
+ const readyCount = detected.length;
32
+ const lines = [
33
+ `${readyCount} of ${adapters.length} agent roots found on this machine.`,
34
+ ""
35
+ ];
36
+
37
+ for (const adapter of adapters) {
38
+ const isReady = detected.includes(adapter.id);
39
+ const status = isReady ? AGENT_HINTS.ready : AGENT_HINTS.notDetected;
40
+ const marker = isReady ? paint("●", "success") : paint("○", "muted");
41
+ const label = getAgentLabel(adapter.id).padEnd(14, " ");
42
+ lines.push(` ${getAgentSymbol(adapter.id)} ${label} ${marker} ${status}`);
43
+ }
44
+
45
+ return lines.join("\n");
46
+ }
47
+
48
+ export function formatAgentMultiselectHint(adapterId, detected) {
49
+ if (detected.includes(adapterId)) {
50
+ return AGENT_HINTS.ready;
51
+ }
52
+ return AGENT_HINTS.managedLater;
53
+ }
54
+
55
+ export function formatComponentMultiselectHint(component) {
56
+ if (component.defaultEnabled) {
57
+ return "recommended";
58
+ }
59
+ return undefined;
60
+ }
61
+
62
+ function formatAgentList(agentIds) {
63
+ if (agentIds.length === 0) {
64
+ return " none";
65
+ }
66
+ return agentIds.map((id) => ` ${getAgentLabel(id)}`).join("\n");
67
+ }
68
+
69
+ function formatComponentList(components, componentCatalog) {
70
+ if (components.length === 0) {
71
+ return ` ${WIZARD_COPY.coreOnlyLabel}`;
72
+ }
73
+
74
+ const labelById = new Map(componentCatalog.map((entry) => [entry.id, entry.label]));
75
+ return components
76
+ .map((id) => ` ${labelById.get(id) ?? id}`)
77
+ .join("\n");
78
+ }
79
+
80
+ function formatManagedWrite(change) {
81
+ const verb = change.action === "create"
82
+ ? "+"
83
+ : change.action === "repair"
84
+ ? "~"
85
+ : "↻";
86
+ return ` ${verb} ${change.target}`;
87
+ }
88
+
89
+ export function formatPreviewNote({ preview, componentCatalog = [] }) {
90
+ const { preflight, agents, components } = preview;
91
+ const lines = [
92
+ paint("Agents", "accent"),
93
+ formatAgentList(agents),
94
+ "",
95
+ paint("Components", "accent"),
96
+ formatComponentList(components, componentCatalog),
97
+ "",
98
+ paint("Managed writes", "accent")
99
+ ];
100
+
101
+ if (preflight.changes.length === 0) {
102
+ lines.push(" none — already in sync");
103
+ } else {
104
+ for (const change of preflight.changes) {
105
+ lines.push(formatManagedWrite(change));
106
+ }
107
+ }
108
+
109
+ lines.push("", paint("Preserved content", "accent"));
110
+ if (preflight.preserved.length === 0) {
111
+ lines.push(" none");
112
+ } else {
113
+ for (const entry of preflight.preserved) {
114
+ lines.push(` ${entry.path}`);
115
+ }
116
+ }
117
+
118
+ return lines.join("\n");
119
+ }
120
+
121
+ export function formatResultNote(result, { dryRun = false, cliName = PREFERRED_CLI } = {}) {
122
+ const agentLabels = result.agents.map((id) => getAgentLabel(id)).join(", ");
123
+ const componentLine = result.components.length > 0
124
+ ? result.components.join(", ")
125
+ : WIZARD_COPY.coreOnlyLabel;
126
+
127
+ const lines = [
128
+ `State ${result.stateRoot}`,
129
+ `Agents ${agentLabels}`,
130
+ `Comps ${componentLine}`,
131
+ `Writes ${result.configsCreated.length} new · ${result.configsUpdated.length} updated`,
132
+ `${dryRun ? "Backup" : "Backups"} ${dryRun ? "planned" : "created"}: ${result.backups.length}`,
133
+ "",
134
+ paint("Next steps", "accent"),
135
+ dryRun
136
+ ? ` ${formatCliCommand("setup --confirm", cliName)} Apply this plan`
137
+ : ` ${formatCliCommand("status", cliName)} Check ecosystem health`,
138
+ dryRun
139
+ ? ` ${formatCliCommand("setup --dry-run", cliName)} Re-preview changes`
140
+ : ` ${formatCliCommand("doctor", cliName)} Run health checks`,
141
+ dryRun ? "" : ` ${formatCliCommand("sync", cliName)} Repair drift when needed`
142
+ ].filter((line) => line !== "");
143
+
144
+ return lines.join("\n");
145
+ }
@@ -0,0 +1,121 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { COMPONENT_IDS } from "./component-registry.js";
5
+ import {
6
+ hasWorkspaceCatalog,
7
+ loadWorkspaceComponentCatalog,
8
+ readWorkspaceCatalogDocument,
9
+ workspaceCatalogPath
10
+ } from "./load-workspace-component-catalog.js";
11
+ import { formatCliCommand } from "./brand/cli.js";
12
+
13
+ const COMPONENT_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
14
+ const DEFAULT_VERSION = "0.1.0";
15
+ const DEFAULT_ASSET = "README.md";
16
+
17
+ export function validateWorkspaceComponentsCatalog(workspaceRoot) {
18
+ const root = resolve(workspaceRoot);
19
+
20
+ if (!hasWorkspaceCatalog(root)) {
21
+ throw new Error("No workspace catalog at .harness/components/catalog.json");
22
+ }
23
+
24
+ const components = loadWorkspaceComponentCatalog(root, { bundledIds: COMPONENT_IDS });
25
+
26
+ return {
27
+ workspaceRoot: root,
28
+ catalogPath: workspaceCatalogPath(root),
29
+ components
30
+ };
31
+ }
32
+
33
+ export async function initWorkspaceComponent({ workspaceRoot, id, label }) {
34
+ const root = resolve(workspaceRoot);
35
+ const componentId = normalizeComponentId(id);
36
+ const componentLabel = normalizeLabel(label);
37
+
38
+ if (COMPONENT_IDS.includes(componentId)) {
39
+ throw new Error(`Component id "${componentId}" conflicts with a bundled component.`);
40
+ }
41
+
42
+ const componentsRoot = join(root, ".harness", "components");
43
+ const componentDir = join(componentsRoot, componentId);
44
+ const assetPath = join(componentDir, DEFAULT_ASSET);
45
+ const catalogPath = workspaceCatalogPath(root);
46
+ const catalog = hasWorkspaceCatalog(root)
47
+ ? readWorkspaceCatalogDocument(root)
48
+ : { components: [] };
49
+
50
+ if (!Array.isArray(catalog.components)) {
51
+ throw new Error("Workspace component catalog must declare a components array.");
52
+ }
53
+
54
+ if (catalog.components.some((entry) => entry.id === componentId)) {
55
+ throw new Error(`Workspace component "${componentId}" already exists.`);
56
+ }
57
+
58
+ if (existsSync(componentDir)) {
59
+ throw new Error(`Component directory already exists: .harness/components/${componentId}`);
60
+ }
61
+
62
+ const entry = {
63
+ id: componentId,
64
+ label: componentLabel,
65
+ version: DEFAULT_VERSION,
66
+ assetFiles: [DEFAULT_ASSET]
67
+ };
68
+
69
+ await mkdir(componentDir, { recursive: true });
70
+ await writeFile(assetPath, buildDefaultReadme(componentLabel, componentId));
71
+ catalog.components.push(entry);
72
+ await writeFile(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`);
73
+
74
+ return {
75
+ workspaceRoot: root,
76
+ catalogPath,
77
+ componentDir,
78
+ assetPath,
79
+ entry
80
+ };
81
+ }
82
+
83
+ function normalizeComponentId(id) {
84
+ if (typeof id !== "string" || id.trim().length === 0) {
85
+ throw new Error("Component id is required.");
86
+ }
87
+
88
+ const componentId = id.trim();
89
+
90
+ if (!COMPONENT_ID_PATTERN.test(componentId)) {
91
+ throw new Error(
92
+ `Invalid component id "${id}". Use lowercase letters, digits, and hyphens (e.g. team-rules).`
93
+ );
94
+ }
95
+
96
+ return componentId;
97
+ }
98
+
99
+ function normalizeLabel(label) {
100
+ if (typeof label !== "string" || label.trim().length === 0) {
101
+ throw new Error(`Missing --label. Use: ${formatCliCommand('components init <id> --label "My Label"')}`);
102
+ }
103
+
104
+ return label.trim();
105
+ }
106
+
107
+ function buildDefaultReadme(label, id) {
108
+ return [
109
+ `# ${label}`,
110
+ "",
111
+ `Workspace component \`${id}\`.`,
112
+ "",
113
+ "Edit this file, then run:",
114
+ "",
115
+ "```bash",
116
+ formatCliCommand("components validate"),
117
+ formatCliCommand(`install --components ${id}`),
118
+ "```",
119
+ ""
120
+ ].join("\n");
121
+ }
@@ -0,0 +1,43 @@
1
+ import { join } from "node:path";
2
+ import { buildOrchestratorManagedSection } from "./components/orchestrator.js";
3
+ import { buildSddCoreManagedSection } from "./components/sdd-core.js";
4
+
5
+ export const COMPONENT_BUILDERS = {
6
+ orchestrator: buildOrchestratorManagedSection,
7
+ "sdd-core": buildSddCoreManagedSection
8
+ };
9
+
10
+ export function resolveComponentBuilder(componentId) {
11
+ const builder = COMPONENT_BUILDERS[componentId];
12
+
13
+ if (!builder) {
14
+ throw new Error(`Missing managed section builder for component "${componentId}".`);
15
+ }
16
+
17
+ return builder;
18
+ }
19
+
20
+ export function createManagedSectionBuilder(componentId, catalogEntry) {
21
+ const builder = resolveComponentBuilder(componentId);
22
+
23
+ return (context, adapter) => builder(context, adapter, catalogEntry);
24
+ }
25
+
26
+ export function createWorkspaceManagedSectionBuilder(catalogEntry) {
27
+ return (context, adapter) => buildWorkspaceManagedSection(context, adapter, catalogEntry);
28
+ }
29
+
30
+ export function buildWorkspaceManagedSection(context, _adapter, catalogEntry) {
31
+ const baseDir = join(context.componentsDir, catalogEntry.id);
32
+ const lines = [`### ${catalogEntry.label}`, ""];
33
+
34
+ for (const asset of catalogEntry.assetFiles) {
35
+ lines.push(`- ${asset}: ${join(baseDir, asset)}`);
36
+ }
37
+
38
+ if (catalogEntry.instructions) {
39
+ lines.push("", catalogEntry.instructions);
40
+ }
41
+
42
+ return lines.join("\n");
43
+ }
@@ -0,0 +1,270 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, lstatSync } from "node:fs";
3
+ import {
4
+ copyFile,
5
+ mkdir,
6
+ mkdtemp,
7
+ readFile,
8
+ rm,
9
+ writeFile
10
+ } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { isPathInside } from "./component-paths.js";
14
+ import { COMPONENT_IDS } from "./component-registry.js";
15
+ import {
16
+ hasWorkspaceCatalog,
17
+ loadWorkspaceComponentCatalog,
18
+ readWorkspaceCatalogDocument,
19
+ workspaceCatalogPath
20
+ } from "./load-workspace-component-catalog.js";
21
+ import { formatCliCommand } from "./brand/cli.js";
22
+
23
+ export async function packWorkspaceComponent({ workspaceRoot, id, outPath }) {
24
+ const root = resolve(workspaceRoot);
25
+ const outputPath = resolve(outPath);
26
+
27
+ if (!id || typeof id !== "string") {
28
+ throw new Error("Component id is required.");
29
+ }
30
+
31
+ if (!outPath || typeof outPath !== "string") {
32
+ throw new Error(`Missing --out. Use: ${formatCliCommand("components pack <id> --out <file>")}`);
33
+ }
34
+
35
+ const components = loadWorkspaceComponentCatalog(root, { bundledIds: COMPONENT_IDS });
36
+ const component = components.find((entry) => entry.id === id);
37
+
38
+ if (!component) {
39
+ throw new Error(`Unknown workspace component "${id}".`);
40
+ }
41
+
42
+ const entry = serializeCatalogEntry(component);
43
+ const stagingDir = await mkdtemp(join(tmpdir(), "harness-pack-"));
44
+
45
+ try {
46
+ await writeFile(join(stagingDir, "catalog.json"), `${JSON.stringify({ components: [entry] }, null, 2)}\n`);
47
+
48
+ const sourceDir = join(root, ".harness", "components", component.id);
49
+ const stagedComponentDir = join(stagingDir, component.id);
50
+ await mkdir(stagedComponentDir, { recursive: true });
51
+
52
+ for (const assetFile of entry.assetFiles) {
53
+ const destination = join(stagedComponentDir, assetFile);
54
+ await mkdir(dirname(destination), { recursive: true });
55
+ await copyFile(join(sourceDir, assetFile), destination);
56
+ }
57
+
58
+ await mkdir(dirname(outputPath), { recursive: true });
59
+ runTar(["-czf", outputPath, "-C", stagingDir, "catalog.json", component.id]);
60
+
61
+ return {
62
+ outPath: outputPath,
63
+ entry
64
+ };
65
+ } finally {
66
+ await rm(stagingDir, { recursive: true, force: true });
67
+ }
68
+ }
69
+
70
+ export async function importWorkspaceComponent({ workspaceRoot, bundlePath }) {
71
+ const root = resolve(workspaceRoot);
72
+ const archivePath = resolve(bundlePath);
73
+
74
+ if (!existsSync(archivePath)) {
75
+ throw new Error(`Bundle not found: ${bundlePath}`);
76
+ }
77
+
78
+ assertSafeArchiveMembers(listTarMembers(archivePath));
79
+
80
+ const extractDir = await mkdtemp(join(tmpdir(), "harness-import-"));
81
+
82
+ try {
83
+ runTar(["-xzf", archivePath, "-C", extractDir]);
84
+
85
+ const catalogPath = join(extractDir, "catalog.json");
86
+ if (!existsSync(catalogPath)) {
87
+ throw new Error("Bundle is missing catalog.json.");
88
+ }
89
+
90
+ const catalog = JSON.parse(await readFile(catalogPath, "utf8"));
91
+ if (!Array.isArray(catalog.components) || catalog.components.length !== 1) {
92
+ throw new Error("Component bundle must declare exactly one component.");
93
+ }
94
+
95
+ const entry = catalog.components[0];
96
+ assertCatalogEntryShape(entry);
97
+
98
+ if (COMPONENT_IDS.includes(entry.id)) {
99
+ throw new Error(`Workspace component "${entry.id}" conflicts with a bundled component.`);
100
+ }
101
+
102
+ const targetCatalog = hasWorkspaceCatalog(root)
103
+ ? readWorkspaceCatalogDocument(root)
104
+ : { components: [] };
105
+
106
+ if (!Array.isArray(targetCatalog.components)) {
107
+ throw new Error("Workspace component catalog must declare a components array.");
108
+ }
109
+
110
+ if (targetCatalog.components.some((candidate) => candidate.id === entry.id)) {
111
+ throw new Error(`Workspace component "${entry.id}" already exists.`);
112
+ }
113
+
114
+ const componentsRoot = join(root, ".harness", "components");
115
+ const targetComponentDir = join(componentsRoot, entry.id);
116
+
117
+ if (existsSync(targetComponentDir)) {
118
+ throw new Error(`Component directory already exists: .harness/components/${entry.id}`);
119
+ }
120
+
121
+ const validationRoot = await mkdtemp(join(tmpdir(), "harness-import-validate-"));
122
+
123
+ try {
124
+ const validationComponentsRoot = join(validationRoot, ".harness", "components");
125
+ const validationComponentDir = join(validationComponentsRoot, entry.id);
126
+ await mkdir(validationComponentDir, { recursive: true });
127
+ await writeFile(
128
+ join(validationComponentsRoot, "catalog.json"),
129
+ `${JSON.stringify({ components: [entry] }, null, 2)}\n`
130
+ );
131
+
132
+ for (const assetFile of entry.assetFiles) {
133
+ const sourcePath = join(extractDir, entry.id, assetFile);
134
+ assertDeclaredBundleAsset(sourcePath, entry.id, assetFile, extractDir);
135
+
136
+ const destination = join(validationComponentDir, assetFile);
137
+ await mkdir(dirname(destination), { recursive: true });
138
+ await copyFile(sourcePath, destination);
139
+ }
140
+
141
+ loadWorkspaceComponentCatalog(validationRoot, { bundledIds: COMPONENT_IDS });
142
+
143
+ await mkdir(targetComponentDir, { recursive: true });
144
+
145
+ for (const assetFile of entry.assetFiles) {
146
+ const destination = join(targetComponentDir, assetFile);
147
+ await mkdir(dirname(destination), { recursive: true });
148
+ await copyFile(join(extractDir, entry.id, assetFile), destination);
149
+ }
150
+
151
+ targetCatalog.components.push(entry);
152
+ await mkdir(componentsRoot, { recursive: true });
153
+ await writeFile(workspaceCatalogPath(root), `${JSON.stringify(targetCatalog, null, 2)}\n`);
154
+
155
+ loadWorkspaceComponentCatalog(root, { bundledIds: COMPONENT_IDS });
156
+ } finally {
157
+ await rm(validationRoot, { recursive: true, force: true });
158
+ }
159
+
160
+ return {
161
+ workspaceRoot: root,
162
+ catalogPath: workspaceCatalogPath(root),
163
+ entry
164
+ };
165
+ } finally {
166
+ await rm(extractDir, { recursive: true, force: true });
167
+ }
168
+ }
169
+
170
+ function serializeCatalogEntry(component) {
171
+ const entry = {
172
+ id: component.id,
173
+ label: component.label,
174
+ version: component.version,
175
+ assetFiles: [...component.assetFiles]
176
+ };
177
+
178
+ if (component.instructions) {
179
+ entry.instructions = component.instructions;
180
+ }
181
+
182
+ if (component.adapterHints && Object.keys(component.adapterHints).length > 0) {
183
+ entry.adapterHints = { ...component.adapterHints };
184
+ }
185
+
186
+ return entry;
187
+ }
188
+
189
+ function assertCatalogEntryShape(entry) {
190
+ for (const field of ["id", "label", "version", "assetFiles"]) {
191
+ if (entry?.[field] === undefined) {
192
+ throw new Error(`Bundle catalog entry is missing "${field}".`);
193
+ }
194
+ }
195
+
196
+ if (!Array.isArray(entry.assetFiles) || entry.assetFiles.length === 0) {
197
+ throw new Error(`Bundle component "${entry.id}" must declare at least one asset file.`);
198
+ }
199
+ }
200
+
201
+ function assertDeclaredBundleAsset(sourcePath, componentId, assetFile, extractDir) {
202
+ if (!existsSync(sourcePath)) {
203
+ throw new Error(`Bundle is missing declared asset "${assetFile}".`);
204
+ }
205
+
206
+ const stats = lstatSync(sourcePath);
207
+ if (stats.isSymbolicLink()) {
208
+ throw new Error(
209
+ `Bundle component "${componentId}" asset "${assetFile}" escapes the workspace via symlink.`
210
+ );
211
+ }
212
+
213
+ if (stats.isDirectory()) {
214
+ throw new Error(`Bundle component "${componentId}" asset "${assetFile}" must reference a file.`);
215
+ }
216
+
217
+ const allowedRoot = resolve(extractDir, componentId);
218
+ if (!isPathInside(allowedRoot, sourcePath)) {
219
+ throw new Error(
220
+ `Bundle component "${componentId}" asset "${assetFile}" escapes its component directory.`
221
+ );
222
+ }
223
+ }
224
+
225
+ function listTarMembers(archivePath) {
226
+ const stdout = runTar(["-tzf", archivePath]);
227
+ return stdout
228
+ .split("\n")
229
+ .map((line) => line.trim())
230
+ .filter(Boolean);
231
+ }
232
+
233
+ function assertSafeArchiveMembers(members) {
234
+ if (members.length === 0) {
235
+ throw new Error("Bundle is empty.");
236
+ }
237
+
238
+ for (const member of members) {
239
+ const normalized = member.replaceAll("\\", "/").replace(/\/+$/, "");
240
+
241
+ if (!normalized) {
242
+ throw new Error(`Bundle contains an unsafe path "${member}".`);
243
+ }
244
+
245
+ if (
246
+ normalized.startsWith("/")
247
+ || normalized.includes("..")
248
+ || normalized.split("/").some((part) => part === ".." || part === "")
249
+ ) {
250
+ throw new Error(`Bundle contains an unsafe path "${member}".`);
251
+ }
252
+ }
253
+ }
254
+
255
+ function runTar(args) {
256
+ const result = spawnSync("tar", args, {
257
+ encoding: "utf8",
258
+ maxBuffer: 16 * 1024 * 1024
259
+ });
260
+
261
+ if (result.error) {
262
+ throw new Error(`Failed to run tar: ${result.error.message}`);
263
+ }
264
+
265
+ if (result.status !== 0) {
266
+ throw new Error((result.stderr || result.stdout || "tar failed").trim());
267
+ }
268
+
269
+ return result.stdout ?? "";
270
+ }
@@ -0,0 +1,106 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { hashBuffer } from "../hash.js";
5
+ import { resolveComponentTemplateDir } from "./component-paths.js";
6
+
7
+ export async function installComponentAssets({ packageRoot, workspaceRoot = null, paths, components, dryRun = false }) {
8
+ const coreFiles = {};
9
+
10
+ for (const component of components) {
11
+ const installed = await installComponentFiles({
12
+ packageRoot,
13
+ workspaceRoot,
14
+ paths,
15
+ component,
16
+ dryRun,
17
+ shouldRepair: () => true
18
+ });
19
+ Object.assign(coreFiles, installed);
20
+ }
21
+
22
+ return coreFiles;
23
+ }
24
+
25
+ export async function repairComponentAssets({
26
+ packageRoot,
27
+ workspaceRoot = null,
28
+ paths,
29
+ components,
30
+ state,
31
+ dryRun = false
32
+ }) {
33
+ const coreFiles = { ...state?.coreFiles };
34
+ const repaired = [];
35
+ const unchanged = [];
36
+
37
+ for (const component of components) {
38
+ const installed = await installComponentFiles({
39
+ packageRoot,
40
+ workspaceRoot,
41
+ paths,
42
+ component,
43
+ dryRun,
44
+ shouldRepair: async (relativePath, destinationPath, templateContent) => {
45
+ const expectedHash = hashBuffer(templateContent);
46
+
47
+ if (!existsSync(destinationPath)) {
48
+ repaired.push(relativePath);
49
+ return true;
50
+ }
51
+
52
+ const diskContent = await readFile(destinationPath);
53
+ const diskHash = hashBuffer(diskContent);
54
+ const trackedHash = state?.coreFiles?.[relativePath];
55
+
56
+ if (diskHash !== expectedHash || trackedHash !== expectedHash) {
57
+ repaired.push(relativePath);
58
+ return true;
59
+ }
60
+
61
+ unchanged.push(relativePath);
62
+ return false;
63
+ }
64
+ });
65
+
66
+ Object.assign(coreFiles, installed);
67
+ }
68
+
69
+ return { coreFiles, repaired, unchanged };
70
+ }
71
+
72
+ async function installComponentFiles({
73
+ packageRoot,
74
+ workspaceRoot,
75
+ paths,
76
+ component,
77
+ dryRun,
78
+ shouldRepair
79
+ }) {
80
+ const templateDir = resolveComponentTemplateDir(component, { packageRoot, workspaceRoot });
81
+ const coreFiles = {};
82
+
83
+ if (!existsSync(templateDir)) return coreFiles;
84
+
85
+ for (const assetFile of component.assetFiles) {
86
+ const sourcePath = join(templateDir, assetFile);
87
+ const relativePath = `components/${component.id}/${assetFile}`;
88
+ const destinationPath = join(paths.root, relativePath);
89
+ const content = await readFile(sourcePath);
90
+ const needsRepair = await shouldRepair(relativePath, destinationPath, content);
91
+
92
+ if (!needsRepair) {
93
+ coreFiles[relativePath] = hashBuffer(content);
94
+ continue;
95
+ }
96
+
97
+ if (!dryRun) {
98
+ await mkdir(dirname(destinationPath), { recursive: true });
99
+ await writeFile(destinationPath, content);
100
+ }
101
+
102
+ coreFiles[relativePath] = hashBuffer(content);
103
+ }
104
+
105
+ return coreFiles;
106
+ }
@@ -0,0 +1,22 @@
1
+ import { join, resolve, sep } from "node:path";
2
+
3
+ export function resolveComponentTemplateDir(component, { packageRoot, workspaceRoot }) {
4
+ if (component.source === "workspace") {
5
+ const root = component.workspaceRoot ?? workspaceRoot;
6
+
7
+ if (!root) {
8
+ throw new Error(`Workspace component "${component.id}" requires a workspace root.`);
9
+ }
10
+
11
+ return join(resolve(root), ".harness", "components", component.id);
12
+ }
13
+
14
+ return join(packageRoot, "global-template", "components", component.id);
15
+ }
16
+
17
+ export function isPathInside(parentPath, candidatePath) {
18
+ const parent = resolve(parentPath);
19
+ const candidate = resolve(candidatePath);
20
+
21
+ return candidate === parent || candidate.startsWith(`${parent}${sep}`);
22
+ }