@uniqbit/mate-core 0.18.4 → 1.0.0-canary.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 (231) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/opencode-plugin/src/add-dir.ts +76 -10
  4. package/opencode-plugin/src/companion.ts +1 -8
  5. package/opencode-plugin/src/server.ts +2 -15
  6. package/package.json +10 -2
  7. package/src/brand/art.generated.ts +69 -0
  8. package/src/brand/index.ts +12 -0
  9. package/src/brand/palette.ts +19 -0
  10. package/src/brand/svg.generated.ts +8 -0
  11. package/src/brand/terminal.ts +83 -0
  12. package/src/brand/types.ts +33 -0
  13. package/src/cli/commands/artifact/artifact.ts +8 -0
  14. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  15. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  16. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  17. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  18. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  19. package/src/cli/commands/artifact/finish/command.ts +33 -3
  20. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  21. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  22. package/src/cli/commands/artifact/finish/openspec.ts +200 -129
  23. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  24. package/src/cli/commands/artifact/pending/command.ts +31 -3
  25. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  26. package/src/cli/commands/cap/graphify.ts +52 -3
  27. package/src/cli/commands/cap/index-cmd.ts +8 -1
  28. package/src/cli/commands/cap/openspec.ts +13 -25
  29. package/src/cli/commands/companion/open.ts +56 -39
  30. package/src/cli/commands/companion/update.ts +75 -42
  31. package/src/cli/commands/doctor.ts +257 -78
  32. package/src/cli/commands/install.ts +0 -8
  33. package/src/cli/commands/plugin/install.ts +9 -4
  34. package/src/cli/commands/plugin/plugin.ts +3 -2
  35. package/src/cli/commands/report/adapter.ts +11 -2
  36. package/src/cli/commands/report/contract.ts +21 -0
  37. package/src/cli/commands/report/highlight.ts +34 -4
  38. package/src/cli/commands/report/markdown.ts +110 -0
  39. package/src/cli/commands/report/palette.ts +118 -0
  40. package/src/cli/commands/report/renderer.ts +456 -153
  41. package/src/cli/commands/report/types.ts +16 -0
  42. package/src/cli/commands/setup.ts +7 -15
  43. package/src/cli/commands/studio/client-asset.ts +16 -0
  44. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  45. package/src/cli/commands/studio/routes.ts +2 -0
  46. package/src/cli/commands/studio/selection.ts +19 -1
  47. package/src/cli/commands/studio/server.ts +42 -1
  48. package/src/cli/commands/studio/terminal-launch.ts +7 -16
  49. package/src/cli/commands/studio/views/client.ts +6 -616
  50. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  51. package/src/cli/commands/studio/views/document.tsx +137 -69
  52. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  53. package/src/cli/commands/studio/views/palette.ts +134 -0
  54. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  55. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  56. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  57. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  58. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  59. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  60. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  61. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  62. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  63. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  64. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  65. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  66. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  67. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  68. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  69. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  70. package/src/cli/commands/studio/views/styles.ts +217 -206
  71. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  72. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  73. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  74. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  75. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  76. package/src/cli/commands/wrap.ts +9 -42
  77. package/src/cli/main.ts +4 -1
  78. package/src/cli/parse-flags.ts +25 -7
  79. package/src/cli/setup-selector.tsx +38 -162
  80. package/src/cli/usage.ts +5 -2
  81. package/src/hooks/companion-instructions.ts +190 -0
  82. package/src/hooks/hook-io.ts +63 -0
  83. package/src/hooks/session-banner.ts +21 -29
  84. package/src/hooks/session-guidance.ts +10 -36
  85. package/src/hooks/session-prepare.ts +115 -28
  86. package/src/hooks/validate-artifact-path.ts +82 -24
  87. package/src/index.ts +4 -0
  88. package/src/lib/change-layout.ts +154 -0
  89. package/src/lib/components/wizard-header.tsx +3 -1
  90. package/src/lib/context-mode-package.ts +24 -11
  91. package/src/lib/delivery/archive-change.ts +154 -0
  92. package/src/lib/delivery/branches.ts +108 -0
  93. package/src/lib/delivery/environment.ts +29 -0
  94. package/src/lib/delivery/forge.ts +279 -0
  95. package/src/lib/delivery/overlap.ts +202 -0
  96. package/src/lib/delivery/record-delivery.ts +196 -0
  97. package/src/lib/delivery/record.ts +343 -0
  98. package/src/lib/delivery/refresh.ts +174 -0
  99. package/src/lib/delivery/repository-access.ts +97 -0
  100. package/src/lib/delivery/stages.ts +111 -0
  101. package/src/lib/delivery/token.ts +58 -0
  102. package/src/lib/frontmatter.ts +18 -0
  103. package/src/lib/opencode-plugin-package.ts +5 -5
  104. package/src/lib/openspec-cli.ts +116 -0
  105. package/src/lib/openspec-scopes.ts +86 -0
  106. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  107. package/src/lib/orchestrator/adapters/claude.ts +60 -162
  108. package/src/lib/orchestrator/adapters/opencode.ts +9 -24
  109. package/src/lib/orchestrator/config-store.ts +2 -70
  110. package/src/lib/orchestrator/editor.ts +9 -9
  111. package/src/lib/orchestrator/openspec-store.ts +14 -54
  112. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  113. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  114. package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
  115. package/src/lib/orchestrator/projection-entries.ts +45 -15
  116. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  117. package/src/lib/orchestrator/projection-types.ts +3 -2
  118. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  119. package/src/lib/orchestrator/session-prepare.ts +49 -87
  120. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  121. package/src/lib/orchestrator/types.ts +2 -2
  122. package/src/lib/orchestrator/wrap-pass.ts +22 -88
  123. package/src/lib/package-paths.ts +8 -0
  124. package/src/opencode/companion-hooks.ts +64 -5
  125. package/src/opencode/companion-policy.ts +37 -18
  126. package/src/opencode/projected-guidance.ts +4 -1
  127. package/src/opencode/session-prepare.ts +2 -1
  128. package/src/opencode/tui.tsx +4 -2
  129. package/src/runtime/artifact-refusal.ts +63 -0
  130. package/src/runtime/claude-delivery-plan.ts +89 -7
  131. package/src/runtime/commit-attribution-guard.ts +181 -0
  132. package/src/runtime/companion-guidance.ts +15 -3
  133. package/src/runtime/companion-instructions.ts +340 -0
  134. package/src/runtime/error-message.ts +4 -0
  135. package/src/runtime/guidance.ts +0 -17
  136. package/src/runtime/index.ts +5 -0
  137. package/src/runtime/managed-block.ts +117 -0
  138. package/src/runtime/markdown-scan.ts +266 -0
  139. package/src/runtime/policy.ts +16 -15
  140. package/src/runtime/projected-flag.ts +7 -0
  141. package/src/runtime/projected-guidance.ts +1 -0
  142. package/src/runtime/runtime-document-manifest.ts +62 -28
  143. package/src/runtime/secret-guard.ts +148 -0
  144. package/src/runtime/secret-patterns.ts +164 -0
  145. package/src/runtime/secret-scan.ts +90 -0
  146. package/src/runtime/session-kind.ts +13 -0
  147. package/src/runtime/shell-heredoc.ts +81 -0
  148. package/src/runtime/wrap-state.ts +36 -6
  149. package/src/runtime/write-atomic.ts +64 -0
  150. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  151. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  152. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  153. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  154. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  155. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  156. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  157. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  158. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  159. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  160. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  161. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  162. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  163. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  164. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  165. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  166. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  167. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  168. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  169. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  170. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  171. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  172. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  173. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  174. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  175. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  176. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  177. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  178. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  179. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  180. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  181. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  182. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  183. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  184. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  185. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  186. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  187. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  188. package/src/templates/mate-skills/openspec-references.ts +140 -0
  189. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  190. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  191. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  192. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  193. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  194. package/src/tools/setup/capabilities/graphify.ts +12 -0
  195. package/src/tools/setup/capabilities/openspec.ts +273 -270
  196. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  197. package/src/tools/setup/context-services.ts +9 -17
  198. package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
  199. package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
  200. package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
  201. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  202. package/src/tools/setup/engine.ts +93 -148
  203. package/src/tools/setup/managed-output.ts +51 -0
  204. package/src/tools/setup/mate.ts +86 -11
  205. package/src/tools/setup/plugin.ts +37 -17
  206. package/src/tools/setup/plugins/gitignore.ts +1 -7
  207. package/src/tools/setup/plugins/guidance.ts +7 -4
  208. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  209. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  210. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  211. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  212. package/src/tools/setup/providers/claude.ts +285 -34
  213. package/src/tools/setup/providers/opencode.ts +180 -11
  214. package/src/tools/setup/registry.ts +2 -24
  215. package/src/tools/setup/surface-target.ts +4 -13
  216. package/src/tools/setup/utils.ts +14 -4
  217. package/src/tools/setup.ts +131 -18
  218. package/wrappers/bin/graphify +52 -4
  219. package/wrappers/bin/openspec +33 -9
  220. package/wrappers/lib/graphify-gate.mjs +8 -0
  221. package/opencode-plugin/src/audience-layer.ts +0 -91
  222. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  223. package/src/runtime/audience-layer.ts +0 -84
  224. package/src/runtime/opencode-audience-layer.ts +0 -176
  225. package/src/tools/setup/audience-isolation.fixture.ts +0 -325
  226. package/src/tools/setup/audience-layer.ts +0 -207
  227. package/src/tools/setup/audience-registrations.ts +0 -52
  228. package/src/tools/setup/contribution-ledger.ts +0 -207
  229. package/src/tools/setup/isolation-diagnostics.ts +0 -139
  230. package/src/tools/setup/runtime-documents.ts +0 -291
  231. package/src/tools/setup/upgrade-sweep.ts +0 -153
@@ -50,16 +50,25 @@ if [ -z "$_companion" ]; then
50
50
  fi
51
51
  [ -n "$_companion" ] || { echo "openspec: no mate context here — run \`mate wrap\` in this repository" >&2; exit 1; }
52
52
 
53
+ # The OpenSpec CLI Mate ships with, found the way Node resolves it: upward from
54
+ # the package root, hoisted or nested under mate-core (distribution packages).
55
+ # Never an openspec on PATH, so a global install of another version is ignored.
56
+ # Physical path: the runtime directory reaches wrappers/ through a symlink.
53
57
  _real=""
54
- IFS=:
55
- for _dir in $PATH; do
56
- [ "$_dir" = "$_this_dir" ] && continue
57
- [ -x "$_dir/openspec" ] || continue
58
- _real="$_dir/openspec"
59
- break
58
+ _dir="$(cd -P "$_this_dir" && cd ../.. && pwd -P)"
59
+ while [ -z "$_real" ]; do
60
+ for _candidate in \
61
+ "$_dir/node_modules/@fission-ai/openspec/bin/openspec.js" \
62
+ "$_dir/node_modules/@uniqbit/mate-core/node_modules/@fission-ai/openspec/bin/openspec.js"; do
63
+ if [ -f "$_candidate" ]; then
64
+ _real="$_candidate"
65
+ break 2
66
+ fi
67
+ done
68
+ [ "$_dir" = "/" ] && break
69
+ _dir="$(dirname "$_dir")"
60
70
  done
61
- unset IFS
62
- [ -n "$_real" ] || { echo "openspec wrapper: real binary not found in PATH" >&2; exit 1; }
71
+ [ -n "$_real" ] || { echo "openspec: bundled OpenSpec CLI is missing. Reinstall Mate to restore it." >&2; exit 1; }
63
72
  command="${1-}"
64
73
  if [ "$command" = "templates" ]; then
65
74
  has_schema=0
@@ -133,4 +142,19 @@ case "$command" in
133
142
  _mate_use_store_pointer
134
143
  ;;
135
144
  esac
136
- exec env -C "$_cwd" "$_real" "$@"
145
+ # A JS entry's `#!/usr/bin/env node` shebang needs an executable bit and `node`
146
+ # on PATH; without either (Bun-only installs) it runs on an explicit runtime.
147
+ _shebang=""
148
+ [ "${_real%.js}" != "$_real" ] && { IFS= read -r _shebang < "$_real" || true; }
149
+ if [ -n "$_shebang" ] && {
150
+ [ ! -x "$_real" ] ||
151
+ { [ "$_shebang" = "#!/usr/bin/env node" ] && ! command -v node >/dev/null 2>&1; }
152
+ }; then
153
+ _runtime="$(command -v node || command -v bun || true)"
154
+ [ -n "$_runtime" ] || { echo "openspec: neither node nor bun is on PATH to run $_real" >&2; exit 1; }
155
+ set -- "$_real" "$@"
156
+ _real="$_runtime"
157
+ fi
158
+ # Mate decides the OpenSpec version, so OpenSpec's update hint is wrong advice
159
+ # here; both variables reach the delegated binary only.
160
+ exec env -C "$_cwd" OPENSPEC_TELEMETRY=0 OPENSPEC_NO_UPDATE_CHECK=1 "$_real" "$@"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim: the gate lives in src/tools/setup/capabilities/graphify-gate.ts,
3
+ // loaded through the plugin's ts-loader because the installed package sits
4
+ // inside node_modules where node's native type stripping is disabled.
5
+ import "../../claude-plugin/hooks/ts-loader.mjs";
6
+
7
+ const { runGraphifyGate } = await import("../../src/tools/setup/capabilities/graphify-gate.ts");
8
+ process.exitCode = await runGraphifyGate(process.argv.slice(2));
@@ -1,91 +0,0 @@
1
- import type { Context } from "@opencode/plugin/promise/plugin";
2
-
3
- import { repositoryWrappedForOpenCode } from "../../src/opencode/companion-hooks";
4
- import { currentAudienceLayer } from "../../src/runtime/audience-layer";
5
- import { MATE_ENV } from "../../src/runtime/env";
6
- import { parseGuidanceContent } from "../../src/runtime/guidance";
7
- import {
8
- readOpenCodeAudienceLayer,
9
- type OpenCodeLayerContents,
10
- } from "../../src/runtime/opencode-audience-layer";
11
- import { readWrapStateSync } from "../../src/runtime/wrap-state";
12
- import type { LaunchEnvironment } from "./launch-environment";
13
-
14
- export interface TrustedAudienceLayer {
15
- root: string;
16
- contents: OpenCodeLayerContents;
17
- }
18
-
19
- /**
20
- * The audience layer named by a payload the plugin trusts. `env` is the
21
- * launch environment `pluginLaunchEnvironment` decided on, so a masked
22
- * payload, a bare session and the shared service all read as `{}` here and
23
- * never apply a layer.
24
- */
25
- export async function readTrustedAudienceLayer(
26
- env: LaunchEnvironment,
27
- ): Promise<TrustedAudienceLayer | null> {
28
- const payload = env[MATE_ENV.guidanceJson];
29
- if (!payload?.trim()) return null;
30
- const root = parseGuidanceContent(payload).guidance.audienceLayer;
31
- if (!root) return null;
32
- return { root, contents: await readOpenCodeAudienceLayer(root) };
33
- }
34
-
35
- /**
36
- * The layer of the audience the session's checkout is wrapped for. The wrap is
37
- * resolved from the session's own directory, so the shared service serves each
38
- * checkout its own audience; the environment never selects one.
39
- */
40
- export async function readWrappedAudienceLayer(
41
- directory: string,
42
- companionPath: string,
43
- ): Promise<TrustedAudienceLayer | null> {
44
- const repoRoot = repositoryWrappedForOpenCode(directory);
45
- if (!repoRoot) return null;
46
- const audience = readWrapStateSync(repoRoot).audience;
47
- if (!audience) return null;
48
- const root = currentAudienceLayer(companionPath, audience);
49
- return root ? { root, contents: await readOpenCodeAudienceLayer(root) } : null;
50
- }
51
-
52
- /**
53
- * Applies a layer's MCP servers, skills and agents through the plugin API. A
54
- * new agent id is created by updating it; its prompt goes in `system`.
55
- */
56
- export async function registerAudienceLayer(
57
- api: Context,
58
- layer: OpenCodeLayerContents,
59
- ): Promise<void> {
60
- if (layer.mcpServers.length > 0) {
61
- await api.mcp.transform((editor) => {
62
- for (const server of layer.mcpServers) editor.set(server.name, server.config as never);
63
- });
64
- }
65
-
66
- if (layer.skills.length > 0) {
67
- await api.skill.transform((editor) => {
68
- for (const skill of layer.skills) {
69
- if (editor.get(skill.id)) {
70
- editor.update(skill.id, (current) => Object.assign(current, skill));
71
- } else {
72
- editor.add(skill as never);
73
- }
74
- }
75
- });
76
- }
77
-
78
- if (layer.agents.length > 0) {
79
- await api.agent.transform((editor) => {
80
- for (const agent of layer.agents) {
81
- editor.update(agent.id, (current) => {
82
- const target = current as Record<string, unknown>;
83
- target.name ??= agent.id;
84
- if (agent.description) target.description = agent.description;
85
- target.mode = agent.mode;
86
- target.system = agent.system;
87
- });
88
- }
89
- });
90
- }
91
- }
@@ -1,190 +0,0 @@
1
- /** @jsxImportSource hono/jsx */
2
-
3
- import type { StudioCompanionPayload } from "../../payload";
4
- import {
5
- workflowPlan,
6
- type WorkflowBranch,
7
- type WorkflowStep,
8
- type WorkflowStepBadge,
9
- } from "./steps";
10
-
11
- type WorkflowPlan = ReturnType<typeof workflowPlan>;
12
-
13
- export function WorkflowTranscript({ payload }: { payload: StudioCompanionPayload }) {
14
- return <CommandTranscript plan={workflowPlan(payload.skills)} />;
15
- }
16
-
17
- function CommandTranscript({ plan }: { plan: WorkflowPlan }) {
18
- return (
19
- <section className="workflow-console" aria-label="Workflow execution map">
20
- <header className="workflow-console-header">
21
- <div className="workflow-console-header-title">
22
- <span>Workflow map</span>
23
- <strong>Companion delivery path</strong>
24
- </div>
25
- <span>Read-only guide</span>
26
- </header>
27
- <div className="workflow-console-body">
28
- <ConsoleLine step={plan.start} marker="01" />
29
- <div className="workflow-console-context">
30
- <span>precondition</span>
31
- <div>
32
- <p>The desired change, its scope, and the risk involved are clear.</p>
33
- <small>If any of that is unclear, pre-explore above resolves it first.</small>
34
- </div>
35
- </div>
36
- <div className="workflow-console-schema" data-workflow-schema-root>
37
- <div
38
- className="workflow-console-tabs"
39
- data-workflow-schema-switch
40
- role="tablist"
41
- aria-label="Planning depth"
42
- >
43
- {plan.branches.map((branch, index) => (
44
- <button
45
- key={branch.profile}
46
- type="button"
47
- role="tab"
48
- data-workflow-schema-profile={branch.profile}
49
- aria-selected={index === 0}
50
- >
51
- <strong>{branch.label}</strong>
52
- <small>{index === 0 ? "full planning" : "minimal planning"}</small>
53
- </button>
54
- ))}
55
- </div>
56
- <div className="workflow-console-panels">
57
- {plan.branches.map((branch, branchIndex) => (
58
- <section
59
- key={branch.profile}
60
- data-workflow-schema-panel={branch.profile}
61
- data-active={branchIndex === 0 ? "true" : "false"}
62
- role="tabpanel"
63
- >
64
- <div className="workflow-console-branch-rule">
65
- <p className="workflow-console-branch-rule-description">
66
- {planningRule(branch.profile)}
67
- </p>
68
- </div>
69
- {branch.steps.map((step, index) => (
70
- <div key={step.id}>
71
- {step.sessionBreakBefore ? <WorkflowSessionBreak /> : null}
72
- <ConsoleLine
73
- step={step}
74
- marker={`${branchIndex === 0 ? "F" : "M"}${String(index + 1).padStart(2, "0")}`}
75
- />
76
- </div>
77
- ))}
78
- </section>
79
- ))}
80
- </div>
81
- </div>
82
- <div className="workflow-console-divider">paths rejoin</div>
83
- {plan.shared.map((step) => (
84
- <div key={step.id}>
85
- {step.sessionBreakBefore ? <WorkflowSessionBreak /> : null}
86
- <ConsoleLine step={step} marker="+" />
87
- </div>
88
- ))}
89
- <div className="workflow-console-divider">feature is on production</div>
90
- <ConsoleLine step={plan.finish} marker="END" />
91
- </div>
92
- </section>
93
- );
94
- }
95
-
96
- function planningRule(profile: WorkflowBranch["profile"]): string {
97
- return profile === "mate-v1"
98
- ? "Use when intent is clear, but the solution, repository evidence, or risk still needs exploration."
99
- : "Use only when intent and scope are clear, the change is low-risk, and it stays within one Area.";
100
- }
101
-
102
- function ConsoleLine({ step, marker }: { step: WorkflowStep; marker: string }) {
103
- const hasAlternatives = Boolean(step.alternatives?.length);
104
-
105
- return (
106
- <article className="workflow-console-line">
107
- <span className="workflow-console-gutter">{marker}</span>
108
- <div className="workflow-console-line-body">
109
- <div className="workflow-console-step-title">
110
- <strong>{step.title}</strong>
111
- <WorkflowBadges badges={step.badges} />
112
- </div>
113
- {!hasAlternatives ? (
114
- step.kind === "review" ? (
115
- <div className="workflow-console-command workflow-console-review">
116
- <span className="workflow-console-command-label">Review</span>
117
- <code>{step.prompt}</code>
118
- </div>
119
- ) : (
120
- <div className="workflow-console-command">
121
- <span className="workflow-console-command-label">Run</span>
122
- <code>{step.prompt}</code>
123
- <WorkflowCopy text={step.copyPrompt} label={`${step.title} prompt`} />
124
- </div>
125
- )
126
- ) : null}
127
- {hasAlternatives ? <WorkflowOptions step={step} /> : null}
128
- <div className="workflow-console-explanation">
129
- <div className="workflow-console-detail">
130
- <span>What</span>
131
- <p>{step.what}</p>
132
- </div>
133
- <div className="workflow-console-detail">
134
- <span>Why</span>
135
- <small>{step.why}</small>
136
- </div>
137
- </div>
138
- {!hasAlternatives ? <WorkflowOptions step={step} /> : null}
139
- </div>
140
- </article>
141
- );
142
- }
143
-
144
- function WorkflowOptions({ step }: { step: WorkflowStep }) {
145
- if (!step.alternatives?.length) return null;
146
- return (
147
- <div className="workflow-option-list">
148
- {step.alternatives.map((alternative) => (
149
- <div key={alternative.name} className="workflow-option">
150
- <div className="workflow-console-command">
151
- <span className="workflow-console-command-label">Option</span>
152
- <code>{alternative.prompt}</code>
153
- <WorkflowCopy text={alternative.copyPrompt} label={`${alternative.name} prompt`} />
154
- </div>
155
- <small>{alternative.description}</small>
156
- </div>
157
- ))}
158
- </div>
159
- );
160
- }
161
-
162
- function WorkflowCopy({ text, label }: { text: string; label: string }) {
163
- return (
164
- <button type="button" className="runway-step-copy" data-copy={text} data-copy-label={label}>
165
- Copy
166
- </button>
167
- );
168
- }
169
-
170
- function WorkflowSessionBreak() {
171
- return <div className="workflow-session-break">New session / clean chat</div>;
172
- }
173
-
174
- function WorkflowBadges({ badges }: { badges: WorkflowStepBadge[] }) {
175
- return (
176
- <span className="workflow-badges">
177
- {badges.map((badge) => (
178
- <em key={badge} className={`runway-step-badge runway-step-badge-${badge}`}>
179
- {badgeLabel(badge)}
180
- </em>
181
- ))}
182
- </span>
183
- );
184
- }
185
-
186
- function badgeLabel(badge: WorkflowStepBadge): string {
187
- if (badge === "human-in-loop") return "Review";
188
- if (badge === "openspec") return "OpenSpec";
189
- return badge;
190
- }
@@ -1,84 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
-
5
- import { FRAMEWORK_NAME } from "./framework";
6
- import { mateHome } from "./runtime-directory";
7
-
8
- /**
9
- * Audience Layer locations. A layer holds one audience's plugin contributions
10
- * for one companion, rendered for managed launches only. It lives under Mate's
11
- * machine-local state, never inside a repository: a layer inside the companion
12
- * would be readable by every session with companion access, and could carry
13
- * values resolved from plugin configuration.
14
- *
15
- * <state>/audience-layers/<companion-key>/<audience>/
16
- * <fingerprint>/ one complete render; never changed once renamed into place
17
- * current symlink to the newest complete render
18
- *
19
- * A launch attaches the fingerprint directory itself, so a later re-render
20
- * never changes what a running session reads.
21
- */
22
-
23
- /** Mirrors `AUDIENCE_NAME_PATTERN`; repeated here because `runtime/` imports nothing from core. */
24
- const SAFE_AUDIENCE = /^[a-z][a-z0-9-]{0,63}$/;
25
-
26
- export const CURRENT_LAYER_LINK = "current";
27
-
28
- export function audienceLayersRoot(homeDir?: string): string {
29
- return path.join(mateHome(homeDir), "state", "audience-layers");
30
- }
31
-
32
- /** A short, stable key for one companion checkout: a hash of its resolved path. */
33
- export function companionLayerKey(companionPath: string): string {
34
- return crypto.createHash("sha256").update(path.resolve(companionPath)).digest("hex").slice(0, 16);
35
- }
36
-
37
- /** The directory holding one audience's layers for one companion. */
38
- export function audienceLayerDir(
39
- companionPath: string,
40
- audience: string,
41
- homeDir?: string,
42
- ): string {
43
- if (!SAFE_AUDIENCE.test(audience)) {
44
- throw new Error(`"${audience}" is not a valid audience name for a layer directory`);
45
- }
46
- return path.join(audienceLayersRoot(homeDir), companionLayerKey(companionPath), audience);
47
- }
48
-
49
- /** The newest complete layer of `audience`, or `null` when none was rendered on this machine. */
50
- export function currentAudienceLayer(
51
- companionPath: string,
52
- audience: string,
53
- homeDir?: string,
54
- ): string | null {
55
- try {
56
- const dir = audienceLayerDir(companionPath, audience, homeDir);
57
- const layer = path.join(dir, fs.readlinkSync(path.join(dir, CURRENT_LAYER_LINK)));
58
- return fs.existsSync(layer) ? layer : null;
59
- } catch {
60
- return null;
61
- }
62
- }
63
-
64
- const MANAGED_MARKER = new RegExp(
65
- `^<!-- ${FRAMEWORK_NAME}:managed:[^\\n]* (?:start|end) -->$`,
66
- "gm",
67
- );
68
-
69
- /** A layer guidance document's text, managed-block markers removed; empty when absent. */
70
- export function layerGuidanceText(text: string): string {
71
- return text
72
- .replace(MANAGED_MARKER, "")
73
- .replace(/\n{3,}/g, "\n\n")
74
- .trim();
75
- }
76
-
77
- /** `file` (`CLAUDE.md`, `AGENTS.md`) of the layer at `root`, markers removed; empty when absent. */
78
- export function readLayerGuidanceSync(root: string, file: string): string {
79
- try {
80
- return layerGuidanceText(fs.readFileSync(path.join(root, file), "utf8"));
81
- } catch {
82
- return "";
83
- }
84
- }
@@ -1,176 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- import { layerGuidanceText } from "./audience-layer";
5
- import { OPENCODE_MCP_SERVERS_AT } from "./runtime-document-manifest";
6
-
7
- /**
8
- * Reads an audience layer the OpenCode Runtime Surface wrote, as what the Mate
9
- * OpenCode plugin applies through the plugin API. The surface re-exports it as
10
- * its own reader; it lives under `runtime/` only so the import-isolated plugin
11
- * can reach it. The plugin parses no OpenCode document itself.
12
- */
13
-
14
- export interface LayerMcpServer {
15
- name: string;
16
- /** The entry exactly as the surface wrote it under `mcp.servers`. */
17
- config: Record<string, unknown>;
18
- }
19
-
20
- export interface LayerSkill {
21
- id: string;
22
- name: string;
23
- description?: string;
24
- /** The skill's `SKILL.md`. */
25
- path: string;
26
- content: string;
27
- }
28
-
29
- export type LayerAgentMode = "subagent" | "primary" | "all";
30
-
31
- export interface LayerAgent {
32
- id: string;
33
- description?: string;
34
- mode: LayerAgentMode;
35
- /** The agent's prompt: the definition's body. */
36
- system: string;
37
- }
38
-
39
- export interface OpenCodeLayerContents {
40
- mcpServers: LayerMcpServer[];
41
- skills: LayerSkill[];
42
- agents: LayerAgent[];
43
- /** The layer's guidance sections, managed-block markers removed. */
44
- guidance: string;
45
- }
46
-
47
- function isRecord(value: unknown): value is Record<string, unknown> {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
-
51
- function splitFrontmatter(content: string): { fields: string; body: string } {
52
- const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
53
- return match
54
- ? { fields: match[1]!, body: content.slice(match[0].length) }
55
- : { fields: "", body: content };
56
- }
57
-
58
- function frontmatterScalarFromFields(fields: string, key: string): string | undefined {
59
- const value = fields.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1]?.trim();
60
- return value?.replace(
61
- /^(?:"([\s\S]*)"|'([\s\S]*)')$/,
62
- (_all, doubleQuoted?: string, singleQuoted?: string) => doubleQuoted ?? singleQuoted ?? "",
63
- );
64
- }
65
-
66
- /** One scalar of a simple YAML frontmatter, quotes removed. */
67
- export function frontmatterScalar(content: string, key: string): string | undefined {
68
- return frontmatterScalarFromFields(splitFrontmatter(content).fields, key);
69
- }
70
-
71
- /** An id OpenCode accepts for a skill or agent name. */
72
- export function contributionId(name: string, fallback: string): string {
73
- return (
74
- name
75
- .toLowerCase()
76
- .replace(/[^a-z0-9_-]+/g, "-")
77
- .replace(/^-+|-+$/g, "") || fallback
78
- );
79
- }
80
-
81
- async function findSkillFiles(directory: string): Promise<string[]> {
82
- try {
83
- const entries = await fs.readdir(directory, { withFileTypes: true });
84
- const nested = await Promise.all(
85
- entries.map((entry) => {
86
- const entryPath = path.join(directory, entry.name);
87
- if (entry.isFile() && entry.name === "SKILL.md") return Promise.resolve([entryPath]);
88
- return entry.isDirectory() ? findSkillFiles(entryPath) : Promise.resolve([]);
89
- }),
90
- );
91
- return nested.flat().toSorted();
92
- } catch {
93
- return [];
94
- }
95
- }
96
-
97
- /** Every `SKILL.md` under `roots`; an unreadable skill is skipped, never fatal. */
98
- export async function loadSkillInfos(roots: readonly string[]): Promise<LayerSkill[]> {
99
- const files = (await Promise.all(roots.map(findSkillFiles))).flat();
100
- const skills = await Promise.all(
101
- files.map(async (filePath): Promise<LayerSkill | null> => {
102
- try {
103
- const content = await fs.readFile(filePath, "utf8");
104
- const { fields } = splitFrontmatter(content);
105
- const name =
106
- frontmatterScalarFromFields(fields, "name") || path.basename(path.dirname(filePath));
107
- const description = frontmatterScalarFromFields(fields, "description");
108
- return {
109
- id: contributionId(name, "mate-skill"),
110
- name,
111
- ...(description ? { description } : {}),
112
- path: filePath,
113
- content,
114
- };
115
- } catch {
116
- return null;
117
- }
118
- }),
119
- );
120
- return skills.filter((skill) => skill !== null);
121
- }
122
-
123
- async function readMcpServers(root: string): Promise<LayerMcpServer[]> {
124
- const parsed = await fs
125
- .readFile(path.join(root, ".opencode", "opencode.json"), "utf8")
126
- .then((text) => JSON.parse(text) as unknown)
127
- .catch(() => null);
128
- let servers: unknown = parsed;
129
- for (const key of OPENCODE_MCP_SERVERS_AT) servers = isRecord(servers) ? servers[key] : undefined;
130
- if (!isRecord(servers)) return [];
131
- return Object.entries(servers)
132
- .filter((entry): entry is [string, Record<string, unknown>] => isRecord(entry[1]))
133
- .map(([name, config]) => ({ name, config }))
134
- .toSorted((left, right) => left.name.localeCompare(right.name));
135
- }
136
-
137
- function agentMode(value: string | undefined): LayerAgentMode {
138
- return value === "subagent" || value === "primary" ? value : "all";
139
- }
140
-
141
- async function readAgents(root: string): Promise<LayerAgent[]> {
142
- const dir = path.join(root, ".opencode", "agents");
143
- const names = await fs.readdir(dir).catch(() => [] as string[]);
144
- const agents = await Promise.all(
145
- names
146
- .filter((name) => name.endsWith(".md"))
147
- .toSorted()
148
- .map(async (name): Promise<LayerAgent | null> => {
149
- const content = await fs.readFile(path.join(dir, name), "utf8").catch(() => null);
150
- if (content === null) return null;
151
- const { fields, body } = splitFrontmatter(content);
152
- const description = frontmatterScalarFromFields(fields, "description");
153
- return {
154
- id: contributionId(path.basename(name, ".md"), "mate-agent"),
155
- ...(description ? { description } : {}),
156
- mode: agentMode(frontmatterScalarFromFields(fields, "mode")),
157
- system: body.trim(),
158
- };
159
- }),
160
- );
161
- return agents.filter((agent) => agent !== null);
162
- }
163
-
164
- async function readGuidance(root: string): Promise<string> {
165
- return layerGuidanceText(await fs.readFile(path.join(root, "AGENTS.md"), "utf8").catch(() => ""));
166
- }
167
-
168
- export async function readOpenCodeAudienceLayer(root: string): Promise<OpenCodeLayerContents> {
169
- const [mcpServers, skills, agents, guidance] = await Promise.all([
170
- readMcpServers(root),
171
- loadSkillInfos([path.join(root, ".agents", "skills")]),
172
- readAgents(root),
173
- readGuidance(root),
174
- ]);
175
- return { mcpServers, skills, agents, guidance };
176
- }