@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.
- package/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/opencode-plugin/src/add-dir.ts +76 -10
- package/opencode-plugin/src/companion.ts +1 -8
- package/opencode-plugin/src/server.ts +2 -15
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +200 -129
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +257 -78
- package/src/cli/commands/install.ts +0 -8
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/terminal-launch.ts +7 -16
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +9 -42
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +5 -2
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +10 -36
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +60 -162
- package/src/lib/orchestrator/adapters/opencode.ts +9 -24
- package/src/lib/orchestrator/config-store.ts +2 -70
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
- package/src/lib/orchestrator/projection-entries.ts +45 -15
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -2
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +49 -87
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -88
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/guidance.ts +0 -17
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +16 -15
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -28
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -6
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +273 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
- package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
- package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +93 -148
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +37 -17
- package/src/tools/setup/plugins/gitignore.ts +1 -7
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +285 -34
- package/src/tools/setup/providers/opencode.ts +180 -11
- package/src/tools/setup/registry.ts +2 -24
- package/src/tools/setup/surface-target.ts +4 -13
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +131 -18
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/opencode-plugin/src/audience-layer.ts +0 -91
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/runtime/audience-layer.ts +0 -84
- package/src/runtime/opencode-audience-layer.ts +0 -176
- package/src/tools/setup/audience-isolation.fixture.ts +0 -325
- package/src/tools/setup/audience-layer.ts +0 -207
- package/src/tools/setup/audience-registrations.ts +0 -52
- package/src/tools/setup/contribution-ledger.ts +0 -207
- package/src/tools/setup/isolation-diagnostics.ts +0 -139
- package/src/tools/setup/runtime-documents.ts +0 -291
- package/src/tools/setup/upgrade-sweep.ts +0 -153
package/wrappers/bin/openspec
CHANGED
|
@@ -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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|