@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
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
import { execFile as execFileCb } from "node:child_process";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
3
|
|
|
6
|
-
import {
|
|
4
|
+
import { Document, isMap, isScalar, isSeq, parseDocument, type YAMLMap, type YAMLSeq } from "yaml";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
locateOpenSpec,
|
|
8
|
+
runOpenSpec,
|
|
9
|
+
type OpenSpecLocation,
|
|
10
|
+
type RunOpenSpec,
|
|
11
|
+
} from "../../../lib/openspec-cli";
|
|
7
12
|
import { getWrapperBinPath } from "../../../lib/package-paths";
|
|
8
|
-
import { fetchPublicPackageVersion } from "../../../lib/public-npm";
|
|
9
|
-
import { isNewer, isPinnedDeployment } from "../../../lib/update-checker";
|
|
10
13
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
11
|
-
import {
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
getSharedSkillsDir,
|
|
25
|
-
isSharedSkillRootActive,
|
|
26
|
-
removeLegacyOpenCodeSkills,
|
|
27
|
-
removeSharedSkills,
|
|
28
|
-
} from "../providers/agents-skill-root";
|
|
29
|
-
|
|
30
|
-
export { MATE_ARTIFACT_SKILLS, MATE_SKILLS } from "../mate";
|
|
31
|
-
|
|
32
|
-
const execFileAsync = promisify(execFileCb);
|
|
14
|
+
import { removeRecorded } from "../managed-output";
|
|
15
|
+
import type {
|
|
16
|
+
CapabilityPlugin,
|
|
17
|
+
ManagedOutputRecorder,
|
|
18
|
+
RuntimeContributionsByRuntime,
|
|
19
|
+
SetupContext,
|
|
20
|
+
} from "../plugin";
|
|
21
|
+
import { mergeDir, pruneEmptyAncestors } from "../utils";
|
|
22
|
+
import { applyMateSkills, MATE_SKILLS, readSkillAuthor, teardownMateSkills } from "../mate";
|
|
23
|
+
import { getSharedSkillsDir, isSharedSkillRootActive } from "../providers/agents-skill-root";
|
|
24
|
+
|
|
25
|
+
export { MATE_SKILLS } from "../mate";
|
|
33
26
|
|
|
34
27
|
export const OPENSPEC_SKILLS = [
|
|
35
28
|
"openspec-explore",
|
|
@@ -40,11 +33,30 @@ export const OPENSPEC_SKILLS = [
|
|
|
40
33
|
"openspec-update-change",
|
|
41
34
|
] as const;
|
|
42
35
|
|
|
36
|
+
/** Mate skills without a pre-seeded `Skill(...)` permission. */
|
|
37
|
+
const UNPERMITTED_MATE_SKILLS: ReadonlySet<string> = new Set(["mate-create-report"]);
|
|
38
|
+
|
|
43
39
|
/** Written by the OpenSpec `agents` tool beside its skills in the shared root. */
|
|
44
40
|
const OPENSPEC_AGENTS_MARKER = ".openspec-target";
|
|
45
41
|
|
|
46
|
-
/**
|
|
47
|
-
const
|
|
42
|
+
/** Command ids the OpenSpec CLI writes as `.claude/commands/opsx/<id>.md` and `.opencode/commands/opsx-<id>.md`. */
|
|
43
|
+
const OPENSPEC_COMMAND_IDS = [
|
|
44
|
+
"explore",
|
|
45
|
+
"new",
|
|
46
|
+
"continue",
|
|
47
|
+
"apply",
|
|
48
|
+
"update",
|
|
49
|
+
"ff",
|
|
50
|
+
"sync",
|
|
51
|
+
"archive",
|
|
52
|
+
"bulk-archive",
|
|
53
|
+
"verify",
|
|
54
|
+
"onboard",
|
|
55
|
+
"propose",
|
|
56
|
+
] as const;
|
|
57
|
+
|
|
58
|
+
/** Skill roots earlier Mate versions let the OpenSpec CLI generate workflow skills into. */
|
|
59
|
+
const GENERATED_SKILL_ROOTS = [".claude", ".agents", ".opencode"] as const;
|
|
48
60
|
|
|
49
61
|
/** Every Mate-owned schema is installed for availability; only one is ever activated as the companion default. */
|
|
50
62
|
const MATE_SCHEMA_NAMES = ["mate-v1", "mate-minimal"] as const;
|
|
@@ -56,15 +68,26 @@ const MATE_SCHEMA_SOURCE_ROOT = path.join(
|
|
|
56
68
|
const MATE_CONVENTIONS_SOURCE = path.join(MATE_SCHEMA_SOURCE_ROOT, "openspec-conventions.yaml");
|
|
57
69
|
|
|
58
70
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
71
|
+
* Identifies Mate's own `rules.specs` entry, so a later release can replace its
|
|
72
|
+
* text instead of adding a second entry.
|
|
61
73
|
*/
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
export const MATE_SPECS_RULE_MARKER = "[mate] ";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Delivered through `rules.specs`, the one config hook OpenSpec's archive and
|
|
78
|
+
* sync workflows read when writing main specs. Propose reads it for delta specs
|
|
79
|
+
* too, so the text binds main specs only.
|
|
80
|
+
*/
|
|
81
|
+
export const MATE_SPECS_RULE = `${MATE_SPECS_RULE_MARKER}A main spec at openspec/specs/<capability>/spec.md MUST begin with flat frontmatter: type: spec, capability: <capability>, repository: <org/repository> (one scalar), areas: [<area>, ...] (a flat list in the delta's scope order), and tags: [openspec/spec]. Project repository and areas from the delta's paired scopes; never copy scopes, change, or delta tags onto a main spec. Delta specs under openspec/changes/<name>/specs/ keep their \`mate:delta\` comment. Full rules: openspec/mate-conventions.yaml.`;
|
|
82
|
+
|
|
83
|
+
/** What Mate writes besides its entry; anything beyond the two is user content. */
|
|
84
|
+
const MANAGED_CONFIG_SCHEMA_ONLY = "schema: mate-v1";
|
|
85
|
+
|
|
86
|
+
/** The schema a fresh `openspec init` writes into config.yaml. */
|
|
87
|
+
const OPENSPEC_INIT_SCHEMA = "spec-driven";
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
/** Unfolded output keeps the Mate entry on one line and user scalars unwrapped. */
|
|
90
|
+
const CONFIG_TO_STRING = { lineWidth: 0 } as const;
|
|
68
91
|
|
|
69
92
|
/**
|
|
70
93
|
* Store metadata `openspec store register` writes beside the companion. Its id
|
|
@@ -73,47 +96,10 @@ const OPENSPEC_INSTALL_COMMAND = "npm install -g @fission-ai/openspec@latest";
|
|
|
73
96
|
*/
|
|
74
97
|
const OPENSPEC_GITIGNORE_ENTRIES = [".openspec-store/"];
|
|
75
98
|
|
|
76
|
-
type OpenSpecTool =
|
|
77
|
-
(typeof OPENSPEC_TOOLS_BY_RUNTIME)[keyof typeof OPENSPEC_TOOLS_BY_RUNTIME][number];
|
|
78
|
-
type OpenSpecRunCommand = typeof runCommandSilently;
|
|
79
|
-
|
|
80
99
|
export interface OpenSpecPluginDeps {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
isCommandOnPath?: typeof isCommandOnPath;
|
|
84
|
-
confirm?: typeof confirm;
|
|
85
|
-
pathValue?: () => string;
|
|
100
|
+
runOpenSpec?: RunOpenSpec;
|
|
101
|
+
locateOpenSpec?: () => OpenSpecLocation;
|
|
86
102
|
wrapperBinPath?: () => string;
|
|
87
|
-
getInstalledVersion?: (pathValue: string) => Promise<string | undefined>;
|
|
88
|
-
fetchLatestVersion?: () => Promise<string | undefined>;
|
|
89
|
-
isPinnedDeployment?: () => boolean;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function defaultGetInstalledVersion(pathValue: string): Promise<string | undefined> {
|
|
93
|
-
const bin = resolveCommandOnPath("openspec", pathValue);
|
|
94
|
-
if (!bin) return undefined;
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const { stdout } = await execFileAsync(bin, ["--version"], { timeout: 10_000 });
|
|
98
|
-
return stdout.match(/\d+\.\d+\.\d+/)?.[0];
|
|
99
|
-
} catch {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function defaultFetchLatestVersion(): Promise<string | undefined> {
|
|
105
|
-
try {
|
|
106
|
-
return await fetchPublicPackageVersion("@fission-ai/openspec");
|
|
107
|
-
} catch {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function deriveOpenSpecTools(activeProviders: string[]): OpenSpecTool[] {
|
|
113
|
-
const active = new Set(activeProviders);
|
|
114
|
-
return Object.entries(OPENSPEC_TOOLS_BY_RUNTIME).flatMap(([runtime, tools]) =>
|
|
115
|
-
active.has(runtime) ? tools : [],
|
|
116
|
-
);
|
|
117
103
|
}
|
|
118
104
|
|
|
119
105
|
function getClaudeSkillsDir(companionPath: string): string {
|
|
@@ -132,179 +118,241 @@ function getMateConventionsPath(companionPath: string): string {
|
|
|
132
118
|
return path.join(companionPath, "openspec", "mate-conventions.yaml");
|
|
133
119
|
}
|
|
134
120
|
|
|
135
|
-
async function teardownClaudeSkillRoot(
|
|
121
|
+
async function teardownClaudeSkillRoot(
|
|
122
|
+
companionPath: string,
|
|
123
|
+
recorder?: ManagedOutputRecorder,
|
|
124
|
+
): Promise<void> {
|
|
136
125
|
const skillsDir = getClaudeSkillsDir(companionPath);
|
|
137
126
|
for (const skill of OPENSPEC_SKILLS) {
|
|
138
127
|
try {
|
|
139
|
-
await
|
|
128
|
+
await removeRecorded(path.join(skillsDir, skill), "openspec", recorder);
|
|
140
129
|
} catch {
|
|
141
130
|
/* not present */
|
|
142
131
|
}
|
|
143
132
|
}
|
|
144
|
-
await teardownMateSkills(skillsDir, companionPath);
|
|
133
|
+
await teardownMateSkills(skillsDir, companionPath, recorder);
|
|
145
134
|
await pruneEmptyAncestors(skillsDir, companionPath);
|
|
146
135
|
}
|
|
147
136
|
|
|
148
|
-
async function teardownSharedSkillRoot(
|
|
149
|
-
await removeSharedSkills(companionPath, [...MANAGED_SKILL_NAMES, OPENSPEC_AGENTS_MARKER]);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Converges earlier layouts and drops the `opencode` tool's duplicate workflow skills. */
|
|
153
|
-
async function removeLegacyOpenCodeCopies(companionPath: string): Promise<void> {
|
|
154
|
-
await removeLegacyOpenCodeSkills(companionPath, MANAGED_SKILL_NAMES);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function applyMateOpenspecSkills(
|
|
137
|
+
async function teardownSharedSkillRoot(
|
|
158
138
|
companionPath: string,
|
|
159
|
-
|
|
139
|
+
recorder?: ManagedOutputRecorder,
|
|
160
140
|
): Promise<void> {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await applyMateSkills(getSharedSkillsDir(companionPath), "agents");
|
|
141
|
+
const skillsDir = getSharedSkillsDir(companionPath);
|
|
142
|
+
await teardownMateSkills(skillsDir, companionPath, recorder);
|
|
143
|
+
for (const name of [...OPENSPEC_SKILLS, OPENSPEC_AGENTS_MARKER]) {
|
|
144
|
+
await removeRecorded(path.join(skillsDir, name), "openspec", recorder);
|
|
166
145
|
}
|
|
146
|
+
await pruneEmptyAncestors(skillsDir, companionPath);
|
|
167
147
|
}
|
|
168
148
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
149
|
+
/** Converges the earlier layout that deployed Mate skills into `.opencode/skills`. */
|
|
150
|
+
async function removeLegacyOpenCodeCopies(
|
|
151
|
+
companionPath: string,
|
|
152
|
+
recorder?: ManagedOutputRecorder,
|
|
172
153
|
): Promise<void> {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
} catch (error) {
|
|
181
|
-
process.stderr.write(
|
|
182
|
-
`openspec config reset skipped: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
await runCommand("openspec", ["init", "--tools", tools.join(","), "--force", ctx.companionPath], {
|
|
187
|
-
cwd: ctx.companionPath,
|
|
188
|
-
});
|
|
189
|
-
await runCommand("openspec", ["update", "--force", ctx.companionPath], {
|
|
190
|
-
cwd: ctx.companionPath,
|
|
191
|
-
});
|
|
154
|
+
await teardownMateSkills(
|
|
155
|
+
path.join(companionPath, ".opencode", "skills"),
|
|
156
|
+
companionPath,
|
|
157
|
+
recorder,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
192
160
|
|
|
193
|
-
|
|
194
|
-
|
|
161
|
+
/** The `openspec-*` skills in `skillsDir` OpenSpec wrote: the known names, or `author: openspec`. */
|
|
162
|
+
async function generatedOpenSpecSkills(skillsDir: string): Promise<string[]> {
|
|
163
|
+
const names = (await fs.readdir(skillsDir).catch(() => [])).filter((name) =>
|
|
164
|
+
name.startsWith("openspec-"),
|
|
165
|
+
);
|
|
166
|
+
const generated = await Promise.all(
|
|
167
|
+
names.map(async (name) => {
|
|
168
|
+
const dir = path.join(skillsDir, name);
|
|
169
|
+
const known = (OPENSPEC_SKILLS as readonly string[]).includes(name);
|
|
170
|
+
return known || (await readSkillAuthor(dir)) === "openspec" ? [dir] : [];
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
return generated.flat();
|
|
195
174
|
}
|
|
196
175
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
OpenSpecPluginDeps,
|
|
202
|
-
| "installCommand"
|
|
203
|
-
| "pathValue"
|
|
204
|
-
| "getInstalledVersion"
|
|
205
|
-
| "fetchLatestVersion"
|
|
206
|
-
| "isPinnedDeployment"
|
|
207
|
-
>
|
|
208
|
-
>,
|
|
176
|
+
/** Removes OpenSpec-generated output by name or authorship; user and third-party entries stay. */
|
|
177
|
+
async function removeGeneratedOpenSpecOutput(
|
|
178
|
+
companionPath: string,
|
|
179
|
+
recorder?: ManagedOutputRecorder,
|
|
209
180
|
): Promise<void> {
|
|
210
|
-
|
|
211
|
-
|
|
181
|
+
const skills = await Promise.all(
|
|
182
|
+
GENERATED_SKILL_ROOTS.map((root) =>
|
|
183
|
+
generatedOpenSpecSkills(path.join(companionPath, root, "skills")),
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
const targets = [
|
|
187
|
+
...skills.flat(),
|
|
188
|
+
path.join(getSharedSkillsDir(companionPath), OPENSPEC_AGENTS_MARKER),
|
|
189
|
+
...OPENSPEC_COMMAND_IDS.flatMap((id) => [
|
|
190
|
+
path.join(companionPath, ".claude", "commands", "opsx", `${id}.md`),
|
|
191
|
+
path.join(companionPath, ".opencode", "commands", `opsx-${id}.md`),
|
|
192
|
+
]),
|
|
193
|
+
];
|
|
194
|
+
await Promise.all(targets.map((target) => removeRecorded(target, "openspec", recorder)));
|
|
195
|
+
// Sequential: the parent directories share ancestors.
|
|
196
|
+
for (const dir of new Set(targets.map((target) => path.dirname(target)))) {
|
|
197
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
198
|
+
await pruneEmptyAncestors(dir, companionPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
212
201
|
|
|
213
|
-
|
|
214
|
-
if (
|
|
202
|
+
async function applyMateOpenspecSkills(ctx: SetupContext): Promise<void> {
|
|
203
|
+
if (ctx.activeProviders.includes("claude")) {
|
|
204
|
+
await applyMateSkills(getClaudeSkillsDir(ctx.companionPath), "claude", ctx.managedOutput);
|
|
205
|
+
}
|
|
206
|
+
if (isSharedSkillRootActive(ctx.activeProviders)) {
|
|
207
|
+
await applyMateSkills(getSharedSkillsDir(ctx.companionPath), "agents", ctx.managedOutput);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Initializes OpenSpec without tool output, so it writes only its `openspec/`
|
|
213
|
+
* scaffolding: Mate carries the workflow text in its own skills.
|
|
214
|
+
*/
|
|
215
|
+
async function initializeOpenSpec(ctx: SetupContext, run: RunOpenSpec): Promise<void> {
|
|
216
|
+
const args = ["init", "--tools", "none", "--force", ctx.companionPath];
|
|
217
|
+
const result = await run(args, { cwd: ctx.companionPath });
|
|
218
|
+
if (result.code !== 0) {
|
|
219
|
+
// A missing bundled CLI answers its reinstall message as stderr.
|
|
220
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
221
|
+
throw new Error(`openspec ${args.join(" ")} exited with code ${result.code}: ${detail}`);
|
|
222
|
+
}
|
|
223
|
+
await removeGeneratedOpenSpecOutput(ctx.companionPath, ctx.managedOutput);
|
|
224
|
+
}
|
|
218
225
|
|
|
219
|
-
|
|
220
|
-
|
|
226
|
+
function isMateSpecsRule(item: unknown): boolean {
|
|
227
|
+
return (
|
|
228
|
+
isScalar(item) &&
|
|
229
|
+
typeof item.value === "string" &&
|
|
230
|
+
item.value.startsWith(MATE_SPECS_RULE_MARKER)
|
|
221
231
|
);
|
|
232
|
+
}
|
|
222
233
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
/** The config's `rules.specs` sequence, `null` when absent, or `undefined` when a shape is foreign. */
|
|
235
|
+
function readSpecsRules(doc: Document): YAMLSeq | null | undefined {
|
|
236
|
+
if (doc.contents !== null && !isMap(doc.contents)) return undefined;
|
|
237
|
+
const rules = doc.get("rules", true);
|
|
238
|
+
if (rules === undefined || rules === null) return null;
|
|
239
|
+
if (!isMap(rules)) return undefined;
|
|
240
|
+
const specs = rules.get("specs", true);
|
|
241
|
+
if (specs === undefined || specs === null) return null;
|
|
242
|
+
return isSeq(specs) ? specs : undefined;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Removes Mate's `rules.specs` entry and any `rules.specs` list or `rules` map
|
|
247
|
+
* that leaves empty. Returns whether the document changed.
|
|
248
|
+
*/
|
|
249
|
+
function removeMateSpecsRule(doc: Document): boolean {
|
|
250
|
+
const specs = readSpecsRules(doc);
|
|
251
|
+
if (!specs) return false;
|
|
252
|
+
const before = specs.items.length;
|
|
253
|
+
specs.items = specs.items.filter((item) => !isMateSpecsRule(item));
|
|
254
|
+
if (specs.items.length === before) return false;
|
|
255
|
+
if (specs.items.length === 0) doc.deleteIn(["rules", "specs"]);
|
|
256
|
+
const rules = doc.get("rules", true);
|
|
257
|
+
if (isMap(rules) && rules.items.length === 0) doc.delete("rules");
|
|
258
|
+
return true;
|
|
230
259
|
}
|
|
231
260
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
| "getInstalledVersion"
|
|
242
|
-
| "fetchLatestVersion"
|
|
243
|
-
| "isPinnedDeployment"
|
|
244
|
-
>
|
|
245
|
-
>,
|
|
246
|
-
): Promise<boolean> {
|
|
247
|
-
if (deps.isCommandOnPath("openspec", deps.pathValue())) {
|
|
248
|
-
await updateOpenSpecIfOutdated(ctx, deps);
|
|
261
|
+
/**
|
|
262
|
+
* Ensures exactly one current Mate entry in `rules.specs`: appended when
|
|
263
|
+
* missing, replaced in place when outdated. Returns whether the document changed.
|
|
264
|
+
*/
|
|
265
|
+
function upsertMateSpecsRule(doc: Document): boolean {
|
|
266
|
+
if (doc.contents === null) doc.contents = doc.createNode({}) as YAMLMap;
|
|
267
|
+
const specs = readSpecsRules(doc);
|
|
268
|
+
if (specs === null) {
|
|
269
|
+
doc.setIn(["rules", "specs"], doc.createNode([MATE_SPECS_RULE]));
|
|
249
270
|
return true;
|
|
250
271
|
}
|
|
272
|
+
if (!specs) return false;
|
|
251
273
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return false;
|
|
274
|
+
const index = specs.items.findIndex(isMateSpecsRule);
|
|
275
|
+
if (index === -1) {
|
|
276
|
+
specs.add(doc.createNode(MATE_SPECS_RULE));
|
|
277
|
+
return true;
|
|
257
278
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return false;
|
|
279
|
+
let changed = false;
|
|
280
|
+
const current = specs.items[index];
|
|
281
|
+
if (isScalar(current) && current.value !== MATE_SPECS_RULE) {
|
|
282
|
+
current.value = MATE_SPECS_RULE;
|
|
283
|
+
changed = true;
|
|
264
284
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
process.stderr.write(
|
|
270
|
-
`openspec: install skipped - install manually with \`${OPENSPEC_INSTALL_COMMAND}\`\n`,
|
|
271
|
-
);
|
|
272
|
-
return false;
|
|
285
|
+
const deduplicated = specs.items.filter((item, i) => i === index || !isMateSpecsRule(item));
|
|
286
|
+
if (deduplicated.length !== specs.items.length) {
|
|
287
|
+
specs.items = deduplicated;
|
|
288
|
+
changed = true;
|
|
273
289
|
}
|
|
290
|
+
return changed;
|
|
291
|
+
}
|
|
274
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Removes Mate's own config content: its `rules.specs` entry always, and the
|
|
295
|
+
* file only when what remains is exactly what Mate writes. User content stays.
|
|
296
|
+
*/
|
|
297
|
+
async function removeManagedOpenSpecConfig(companionPath: string): Promise<void> {
|
|
298
|
+
const configPath = getOpenSpecConfigPath(companionPath);
|
|
299
|
+
let content: string;
|
|
275
300
|
try {
|
|
276
|
-
await
|
|
277
|
-
cwd: ctx.companionPath,
|
|
278
|
-
});
|
|
301
|
+
content = await fs.readFile(configPath, "utf8");
|
|
279
302
|
} catch {
|
|
280
|
-
|
|
281
|
-
`openspec: install failed - install manually with \`${OPENSPEC_INSTALL_COMMAND}\`\n`,
|
|
282
|
-
);
|
|
283
|
-
return false;
|
|
303
|
+
return;
|
|
284
304
|
}
|
|
285
305
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
290
|
-
|
|
306
|
+
let remaining = content;
|
|
307
|
+
if (content.trim() !== MANAGED_CONFIG_SCHEMA_ONLY) {
|
|
308
|
+
const doc = parseDocument(content);
|
|
309
|
+
if (doc.errors.length > 0 || !removeMateSpecsRule(doc)) return;
|
|
310
|
+
remaining = doc.toString(CONFIG_TO_STRING);
|
|
291
311
|
}
|
|
292
312
|
|
|
293
|
-
|
|
313
|
+
if (remaining.trim() === MANAGED_CONFIG_SCHEMA_ONLY) {
|
|
314
|
+
await fs.rm(configPath, { force: true });
|
|
315
|
+
await pruneEmptyAncestors(path.dirname(configPath), companionPath);
|
|
316
|
+
} else {
|
|
317
|
+
await fs.writeFile(configPath, remaining, "utf8");
|
|
318
|
+
}
|
|
294
319
|
}
|
|
295
320
|
|
|
296
|
-
/**
|
|
297
|
-
async function
|
|
321
|
+
/** Writes a fresh Mate config, or merges Mate's entry into an existing one; writes only on change. */
|
|
322
|
+
async function ensureManagedOpenSpecConfig(companionPath: string): Promise<void> {
|
|
298
323
|
const configPath = getOpenSpecConfigPath(companionPath);
|
|
324
|
+
let content: string | undefined;
|
|
299
325
|
try {
|
|
300
|
-
|
|
301
|
-
if (content.trim() === "schema: mate-v1") {
|
|
302
|
-
await fs.rm(configPath, { force: true });
|
|
303
|
-
await pruneEmptyAncestors(path.dirname(configPath), companionPath);
|
|
304
|
-
}
|
|
326
|
+
content = await fs.readFile(configPath, "utf8");
|
|
305
327
|
} catch {
|
|
306
|
-
/* no
|
|
328
|
+
/* no config yet */
|
|
307
329
|
}
|
|
330
|
+
|
|
331
|
+
if (content === undefined) {
|
|
332
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
333
|
+
const fresh = new Document({ schema: "mate-v1", rules: { specs: [MATE_SPECS_RULE] } });
|
|
334
|
+
await fs.writeFile(configPath, fresh.toString(CONFIG_TO_STRING), "utf8");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// config.yaml is user-editable once created; only Mate's entry and a default `schema` are touched.
|
|
339
|
+
const doc = parseDocument(content);
|
|
340
|
+
if (doc.errors.length > 0) {
|
|
341
|
+
process.stderr.write(
|
|
342
|
+
`openspec: left openspec/config.yaml unchanged, it is not valid YAML: ${doc.errors[0].message.split("\n")[0]}\n`,
|
|
343
|
+
);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const ruleChanged = upsertMateSpecsRule(doc);
|
|
347
|
+
// Mate's `mate-v1` replaces a missing schema or the one `openspec init` writes; any other is a user choice.
|
|
348
|
+
const current = isMap(doc.contents) ? doc.get("schema") : undefined;
|
|
349
|
+
const schemaChanged =
|
|
350
|
+
isMap(doc.contents) &&
|
|
351
|
+
(current === undefined || current === null || current === OPENSPEC_INIT_SCHEMA);
|
|
352
|
+
if (schemaChanged) doc.set("schema", "mate-v1");
|
|
353
|
+
if (!schemaChanged && !ruleChanged) return;
|
|
354
|
+
const merged = doc.toString(CONFIG_TO_STRING);
|
|
355
|
+
if (merged !== content) await fs.writeFile(configPath, merged, "utf8");
|
|
308
356
|
}
|
|
309
357
|
|
|
310
358
|
async function teardownOpenSpecSchema(companionPath: string): Promise<void> {
|
|
@@ -333,37 +381,14 @@ async function reconcileOpenSpecSchema(ctx: SetupContext): Promise<void> {
|
|
|
333
381
|
);
|
|
334
382
|
await fs.copyFile(MATE_CONVENTIONS_SOURCE, getMateConventionsPath(ctx.companionPath));
|
|
335
383
|
|
|
336
|
-
|
|
337
|
-
// activation stays opt-in through the companion-level setup selection.
|
|
338
|
-
if (getOpenSpecSchemaSelection(ctx.config.capabilities ?? []) !== "mate-v1") {
|
|
339
|
-
await removeManagedOpenSpecConfig(ctx.companionPath);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const configPath = getOpenSpecConfigPath(ctx.companionPath);
|
|
344
|
-
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
345
|
-
|
|
346
|
-
// config.yaml is user-editable once created; never overwrite it on re-sync.
|
|
347
|
-
const configExists = await fs
|
|
348
|
-
.access(configPath)
|
|
349
|
-
.then(() => true)
|
|
350
|
-
.catch(() => false);
|
|
351
|
-
if (configExists) return;
|
|
352
|
-
|
|
353
|
-
await fs.writeFile(configPath, "schema: mate-v1\n", "utf8");
|
|
384
|
+
await ensureManagedOpenSpecConfig(ctx.companionPath);
|
|
354
385
|
}
|
|
355
386
|
|
|
356
387
|
export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityPlugin {
|
|
357
388
|
const depsWithDefaults = {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
isCommandOnPath: deps.isCommandOnPath ?? isCommandOnPath,
|
|
361
|
-
confirm: deps.confirm ?? confirm,
|
|
362
|
-
pathValue: deps.pathValue ?? (() => process.env.PATH ?? ""),
|
|
389
|
+
runOpenSpec: deps.runOpenSpec ?? runOpenSpec,
|
|
390
|
+
locateOpenSpec: deps.locateOpenSpec ?? locateOpenSpec,
|
|
363
391
|
wrapperBinPath: deps.wrapperBinPath ?? getWrapperBinPath,
|
|
364
|
-
getInstalledVersion: deps.getInstalledVersion ?? defaultGetInstalledVersion,
|
|
365
|
-
fetchLatestVersion: deps.fetchLatestVersion ?? defaultFetchLatestVersion,
|
|
366
|
-
isPinnedDeployment: deps.isPinnedDeployment ?? (() => isPinnedDeployment()),
|
|
367
392
|
};
|
|
368
393
|
|
|
369
394
|
return {
|
|
@@ -381,19 +406,9 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
|
|
|
381
406
|
return {
|
|
382
407
|
claude: {
|
|
383
408
|
permissionEntries: [
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
"Skill(openspec-archive-change)",
|
|
388
|
-
"Skill(mate-artifact-publish)",
|
|
389
|
-
"Skill(mate-openspec-backfill)",
|
|
390
|
-
"Skill(mate-interview-me)",
|
|
391
|
-
"Skill(mate-grill-me)",
|
|
392
|
-
"Skill(mate-grilling)",
|
|
393
|
-
"Skill(mate-grill-with-docs)",
|
|
394
|
-
"Skill(mate-domain-modeling)",
|
|
395
|
-
"Skill(mate-show-me)",
|
|
396
|
-
"Skill(mate-simplify-code)",
|
|
409
|
+
...MATE_SKILLS.filter((skill) => !UNPERMITTED_MATE_SKILLS.has(skill)).map(
|
|
410
|
+
(skill) => `Skill(${skill})`,
|
|
411
|
+
),
|
|
397
412
|
"Bash(openspec:*)",
|
|
398
413
|
`Bash(${FRAMEWORK_NAME} cap graphify:*)`,
|
|
399
414
|
`Bash(${path.join(depsWithDefaults.wrapperBinPath(), "openspec")}:*)`,
|
|
@@ -403,54 +418,42 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
|
|
|
403
418
|
},
|
|
404
419
|
gitignoreEntries: () => OPENSPEC_GITIGNORE_ENTRIES,
|
|
405
420
|
persistGitignoreEntries: true,
|
|
406
|
-
getInstallRequirements: (): InstallRequirement[] => [
|
|
407
|
-
{
|
|
408
|
-
id: "capability:openspec",
|
|
409
|
-
label: "OpenSpec CLI",
|
|
410
|
-
group: "companion",
|
|
411
|
-
source: "OpenSpec capability",
|
|
412
|
-
command: OPENSPEC_INSTALL_COMMAND,
|
|
413
|
-
fingerprint: `openspec:${OPENSPEC_INSTALL_COMMAND}`,
|
|
414
|
-
detect: () => depsWithDefaults.isCommandOnPath("openspec", depsWithDefaults.pathValue()),
|
|
415
|
-
install: () =>
|
|
416
|
-
depsWithDefaults.installCommand("npm", ["install", "-g", "@fission-ai/openspec@latest"]),
|
|
417
|
-
verify: () => depsWithDefaults.isCommandOnPath("openspec", depsWithDefaults.pathValue()),
|
|
418
|
-
},
|
|
419
|
-
],
|
|
420
421
|
async apply(ctx) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
422
|
+
// OpenSpec output is committed in the companion, so only setup and
|
|
423
|
+
// `mate companion update` touch it; a launch must leave it alone.
|
|
424
|
+
if (ctx.mode === "setup" || ctx.regenerateOpenSpec === true) {
|
|
425
|
+
await initializeOpenSpec(ctx, depsWithDefaults.runOpenSpec);
|
|
426
|
+
} else {
|
|
427
|
+
const location = depsWithDefaults.locateOpenSpec();
|
|
428
|
+
if ("missing" in location) process.stderr.write(`openspec: ${location.missing}\n`);
|
|
428
429
|
}
|
|
429
|
-
await
|
|
430
|
+
await applyMateOpenspecSkills(ctx);
|
|
431
|
+
await removeLegacyOpenCodeCopies(ctx.companionPath, ctx.managedOutput);
|
|
430
432
|
await reconcileOpenSpecSchema(ctx);
|
|
431
433
|
},
|
|
432
434
|
async teardown(ctx) {
|
|
433
|
-
await teardownClaudeSkillRoot(ctx.companionPath);
|
|
434
|
-
await teardownSharedSkillRoot(ctx.companionPath);
|
|
435
|
-
await removeLegacyOpenCodeCopies(ctx.companionPath);
|
|
435
|
+
await teardownClaudeSkillRoot(ctx.companionPath, ctx.managedOutput);
|
|
436
|
+
await teardownSharedSkillRoot(ctx.companionPath, ctx.managedOutput);
|
|
437
|
+
await removeLegacyOpenCodeCopies(ctx.companionPath, ctx.managedOutput);
|
|
438
|
+
await removeGeneratedOpenSpecOutput(ctx.companionPath, ctx.managedOutput);
|
|
436
439
|
await teardownOpenSpecSchema(ctx.companionPath);
|
|
437
440
|
},
|
|
438
441
|
forProvider: {
|
|
439
442
|
claude: {
|
|
440
|
-
// Publication is explicit (`mate-artifact
|
|
443
|
+
// Publication is explicit (`mate-publish-artifact`), so openspec
|
|
441
444
|
// contributes no archive-triggered Claude hook and no hook state.
|
|
442
445
|
async apply() {},
|
|
443
446
|
async teardown(ctx: SetupContext) {
|
|
444
|
-
await teardownClaudeSkillRoot(ctx.companionPath);
|
|
447
|
+
await teardownClaudeSkillRoot(ctx.companionPath, ctx.managedOutput);
|
|
445
448
|
},
|
|
446
449
|
},
|
|
447
450
|
opencode: {
|
|
448
451
|
async apply() {},
|
|
449
452
|
async teardown(ctx: SetupContext) {
|
|
450
453
|
if (!isSharedSkillRootActive(ctx.activeProviders)) {
|
|
451
|
-
await teardownSharedSkillRoot(ctx.companionPath);
|
|
454
|
+
await teardownSharedSkillRoot(ctx.companionPath, ctx.managedOutput);
|
|
452
455
|
}
|
|
453
|
-
await removeLegacyOpenCodeCopies(ctx.companionPath);
|
|
456
|
+
await removeLegacyOpenCodeCopies(ctx.companionPath, ctx.managedOutput);
|
|
454
457
|
},
|
|
455
458
|
},
|
|
456
459
|
},
|