@uniqbit/mate-core 0.18.5 → 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/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 +177 -138
- 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 +176 -3
- 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/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 +5 -13
- 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 +4 -0
- 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 +9 -17
- 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 +48 -40
- package/src/lib/orchestrator/adapters/opencode.ts +2 -0
- 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 -137
- package/src/lib/orchestrator/projection-entries.ts +45 -14
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -0
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +45 -5
- 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 -1
- 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/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 +15 -1
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -11
- 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 -2
- 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/{claude/mate-artifact-publish → agents/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/{agents/mate-artifact-publish → claude/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 +151 -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/install.ts +24 -28
- package/src/tools/setup/engine.ts +97 -21
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +35 -0
- 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 +284 -8
- package/src/tools/setup/providers/opencode.ts +180 -1
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +126 -10
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/tools/setup/runtime-documents.ts +0 -291
|
@@ -28,6 +28,7 @@ export type ProjectionEntryId =
|
|
|
28
28
|
| "openspec-store-exclude"
|
|
29
29
|
| "openspec-store-pointer"
|
|
30
30
|
| "claude-skill-links"
|
|
31
|
+
| "claude-command-links"
|
|
31
32
|
| "workspace-document"
|
|
32
33
|
| "capability-excludes"
|
|
33
34
|
| "claude-working-settings"
|
|
@@ -41,6 +42,8 @@ export type ProjectionEntryId =
|
|
|
41
42
|
export interface ProjectionInput {
|
|
42
43
|
repoPath: string;
|
|
43
44
|
companionPath?: string;
|
|
45
|
+
/** Every companion folder the workspace document lists, in order; `[companionPath]` when absent. */
|
|
46
|
+
workspaceCompanionPaths?: string[];
|
|
44
47
|
repository?: LinkedRepository;
|
|
45
48
|
source?: CompanionSource;
|
|
46
49
|
config?: FrameworkConfig;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readWrapState } from "../../runtime/wrap-state";
|
|
2
|
+
import { GlobalConfigStore } from "./global-config-store";
|
|
3
|
+
import { reconcileWorkingRepoClaudeCommandLinks } from "./projection-claude-commands";
|
|
4
|
+
import { reconcileWorkingRepoClaudeSkillLinks } from "./projection-claude-skills";
|
|
5
|
+
import { readCompanionRegistry } from "./repository-link";
|
|
6
|
+
import type { FrameworkConfig, LinkedRepository } from "./types";
|
|
7
|
+
import { errorMessage } from "../../runtime/error-message";
|
|
8
|
+
|
|
9
|
+
export interface LinkRefreshResult {
|
|
10
|
+
/** Null when the companion's list of linked repositories could not be read. */
|
|
11
|
+
repository: LinkedRepository | null;
|
|
12
|
+
outcome: "written" | "current" | "failed";
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LinkRefreshDeps {
|
|
17
|
+
readRegistry: (companionPath: string) => Promise<LinkedRepository[]>;
|
|
18
|
+
isWrappedForClaude: (repoPath: string) => Promise<boolean>;
|
|
19
|
+
registeredCompanionPaths: () => Promise<string[]>;
|
|
20
|
+
reconcileSkillLinks: typeof reconcileWorkingRepoClaudeSkillLinks;
|
|
21
|
+
reconcileCommandLinks: typeof reconcileWorkingRepoClaudeCommandLinks;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const linkRefreshDeps: LinkRefreshDeps = {
|
|
25
|
+
readRegistry: async (companionPath) => {
|
|
26
|
+
try {
|
|
27
|
+
return (await readCompanionRegistry(companionPath)).repos;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// No registry file means no linked repositories.
|
|
30
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
isWrappedForClaude: async (repoPath) => (await readWrapState(repoPath)).runtimes.has("claude"),
|
|
35
|
+
registeredCompanionPaths: () => new GlobalConfigStore().list(),
|
|
36
|
+
reconcileSkillLinks: reconcileWorkingRepoClaudeSkillLinks,
|
|
37
|
+
reconcileCommandLinks: reconcileWorkingRepoClaudeCommandLinks,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Re-links the companion's Claude skills and commands into every linked Claude-wrapped
|
|
42
|
+
* Working Repository. Links only; runtime documents wait for the wrap pass. Failures
|
|
43
|
+
* are reported per repository, never thrown.
|
|
44
|
+
*/
|
|
45
|
+
export async function refreshWorkingClaudeLinks(
|
|
46
|
+
companionPath: string,
|
|
47
|
+
config: FrameworkConfig,
|
|
48
|
+
deps: LinkRefreshDeps = linkRefreshDeps,
|
|
49
|
+
): Promise<LinkRefreshResult[]> {
|
|
50
|
+
const enabled = config.allowedAgents?.includes("claude") ?? false;
|
|
51
|
+
const results: LinkRefreshResult[] = [];
|
|
52
|
+
let registered: string[] | undefined;
|
|
53
|
+
let repositories: LinkedRepository[];
|
|
54
|
+
try {
|
|
55
|
+
repositories = await deps.readRegistry(companionPath);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return [{ repository: null, outcome: "failed", error: errorMessage(error) }];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const repository of repositories) {
|
|
61
|
+
try {
|
|
62
|
+
if (!(await deps.isWrappedForClaude(repository.path))) continue;
|
|
63
|
+
registered ??= await deps.registeredCompanionPaths();
|
|
64
|
+
const skills = await deps.reconcileSkillLinks(
|
|
65
|
+
repository.path,
|
|
66
|
+
companionPath,
|
|
67
|
+
enabled,
|
|
68
|
+
registered,
|
|
69
|
+
);
|
|
70
|
+
const commands = await deps.reconcileCommandLinks(
|
|
71
|
+
repository.path,
|
|
72
|
+
companionPath,
|
|
73
|
+
enabled,
|
|
74
|
+
registered,
|
|
75
|
+
);
|
|
76
|
+
const written = skills === "written" || commands === "written";
|
|
77
|
+
results.push({ repository, outcome: written ? "written" : "current" });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
results.push({
|
|
80
|
+
repository,
|
|
81
|
+
outcome: "failed",
|
|
82
|
+
error: errorMessage(error),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getActiveDistribution } from "../../distribution";
|
|
2
2
|
import { FRAMEWORK_NAME } from "../../framework";
|
|
3
|
+
import { launchPassesClaudePluginFolders } from "../../runtime/claude-delivery-plan";
|
|
3
4
|
import { recordCompanionSyncUnfinished } from "../../runtime/companion-git-state";
|
|
4
5
|
import { withCompanionLock, type CompanionLockOptions } from "../../runtime/companion-lock";
|
|
5
6
|
import {
|
|
@@ -7,9 +8,11 @@ import {
|
|
|
7
8
|
syncCompanionUnattended,
|
|
8
9
|
type UnattendedSyncOutcome,
|
|
9
10
|
} from "../../runtime/companion-sync";
|
|
11
|
+
import { managedSessionKind, type SessionKind } from "../../runtime/session-kind";
|
|
10
12
|
import type { WrapState } from "../../runtime/wrap-state";
|
|
11
13
|
import { syncCompanionFiles } from "../../tools/setup";
|
|
12
14
|
import type { CapabilityPlugin, LaunchPreflightContext } from "../../tools/setup/plugin";
|
|
15
|
+
import type { ClaudePluginFolders } from "../../tools/setup/providers/claude-plugin-folders";
|
|
13
16
|
import { attendedLockOptions, syncCompanionGit } from "./companion-git-sync";
|
|
14
17
|
import { readCompanionConfig } from "./config-store";
|
|
15
18
|
import { entryFailureMessage, firstFailure, type ProjectionChangedPaths } from "./projection-types";
|
|
@@ -61,6 +64,8 @@ export interface PrepareResult {
|
|
|
61
64
|
failure?: unknown;
|
|
62
65
|
/** The wrap state the `working-target` step's pass left; a launch reads no manifest itself. */
|
|
63
66
|
wrapState?: WrapState;
|
|
67
|
+
/** The Claude plugin folders the `file-sync` step gathered; a launch gathers none itself. */
|
|
68
|
+
claudePluginDirectories?: string[];
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
export interface PrepareSessionInput {
|
|
@@ -123,7 +128,8 @@ export const sessionPrepareDeps = {
|
|
|
123
128
|
syncCompanionGit,
|
|
124
129
|
syncCompanionUnattended,
|
|
125
130
|
recordCompanionSyncUnfinished,
|
|
126
|
-
syncCompanionFiles
|
|
131
|
+
syncCompanionFiles: async (...args: Parameters<typeof syncCompanionFiles>) =>
|
|
132
|
+
(await syncCompanionFiles(...args)).claudePluginFolders,
|
|
127
133
|
projectLaunch: (input: Parameters<typeof project>[1]) => project("launch", input),
|
|
128
134
|
/**
|
|
129
135
|
* The Projection Root a Claude launch guarantees for itself. Companion
|
|
@@ -149,6 +155,7 @@ class StepLog {
|
|
|
149
155
|
readonly warnings: string[] = [];
|
|
150
156
|
failure: unknown;
|
|
151
157
|
wrapState: WrapState | undefined;
|
|
158
|
+
claudePluginDirectories: string[] | undefined;
|
|
152
159
|
stopped = false;
|
|
153
160
|
companionChanged = false;
|
|
154
161
|
|
|
@@ -176,10 +183,18 @@ async function runFileSync(
|
|
|
176
183
|
): Promise<void> {
|
|
177
184
|
const deps = sessionPrepareDeps;
|
|
178
185
|
try {
|
|
179
|
-
|
|
180
|
-
await deps.syncCompanionFiles(input.companionPath, config, input.repository.path)
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
const folders = input.repository
|
|
187
|
+
? await deps.syncCompanionFiles(input.companionPath, config, input.repository.path)
|
|
188
|
+
: await deps.syncCompanionFiles(input.companionPath, config);
|
|
189
|
+
run.claudePluginDirectories = [...folders.launch];
|
|
190
|
+
/**
|
|
191
|
+
* The Claude plan speaks for Claude sessions only; OpenCode loads no plugin
|
|
192
|
+
* folder. With a Working Repository the wrap pass reports publication;
|
|
193
|
+
* without one no wrap pass runs, so this step does.
|
|
194
|
+
*/
|
|
195
|
+
if (reportsClaudePluginFolders(input)) {
|
|
196
|
+
run.warnings.push(...folders.loadWarnings);
|
|
197
|
+
if (!input.repository) run.warnings.push(...publicationWarnings(folders));
|
|
183
198
|
}
|
|
184
199
|
run.push({ step: "file-sync", outcome: "ok" });
|
|
185
200
|
} catch (error) {
|
|
@@ -199,6 +214,24 @@ function wrapDetail(wrapped: {
|
|
|
199
214
|
return parts.length > 0 ? `updated (${parts.join("; ")})` : "updated";
|
|
200
215
|
}
|
|
201
216
|
|
|
217
|
+
/** A managed Claude launch; plain sessions' prepares and OpenCode report no plugin-folder problem. */
|
|
218
|
+
function reportsClaudePluginFolders(input: PrepareSessionInput): boolean {
|
|
219
|
+
return input.runtime === "claude" && launchPassesClaudePluginFolders(sessionKindOf(input));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** What a publication left undone, one sentence per folder. */
|
|
223
|
+
function publicationWarnings(folders: ClaudePluginFolders | undefined): string[] {
|
|
224
|
+
const publication = folders?.publication;
|
|
225
|
+
if (!publication) return [];
|
|
226
|
+
return [...publication.unpublished.map((entry) => entry.warning), ...publication.warnings];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Attended is a managed launch, with or without a Working Repository; the rest serve plain sessions. */
|
|
230
|
+
function sessionKindOf(input: PrepareSessionInput): SessionKind {
|
|
231
|
+
if (input.mode !== "attended") return "unmanaged";
|
|
232
|
+
return managedSessionKind(Boolean(input.repository));
|
|
233
|
+
}
|
|
234
|
+
|
|
202
235
|
function runtimesOf(input: PrepareSessionInput): PrepareRuntime[] {
|
|
203
236
|
return input.mode !== "attended" && input.runtimes?.length
|
|
204
237
|
? [...input.runtimes]
|
|
@@ -225,6 +258,9 @@ async function reconcileWrap(
|
|
|
225
258
|
return;
|
|
226
259
|
}
|
|
227
260
|
run.warnings.push(...wrapped.warnings);
|
|
261
|
+
if (reportsClaudePluginFolders(input)) {
|
|
262
|
+
run.warnings.push(...publicationWarnings(wrapped.claudePluginFolders));
|
|
263
|
+
}
|
|
228
264
|
run.wrapState = wrapped.wrapState;
|
|
229
265
|
run.push({
|
|
230
266
|
step: "working-target",
|
|
@@ -308,6 +344,7 @@ async function runLocal(
|
|
|
308
344
|
config,
|
|
309
345
|
repository: input.repository,
|
|
310
346
|
providerId: runtime,
|
|
347
|
+
sessionKind: sessionKindOf(input),
|
|
311
348
|
}),
|
|
312
349
|
),
|
|
313
350
|
);
|
|
@@ -401,6 +438,9 @@ export async function prepareSession(input: PrepareSessionInput): Promise<Prepar
|
|
|
401
438
|
...(run.companionChanged ? { companionChanged: true as const } : {}),
|
|
402
439
|
...(run.failure === undefined ? {} : { failure: run.failure }),
|
|
403
440
|
...(run.wrapState ? { wrapState: run.wrapState } : {}),
|
|
441
|
+
...(run.claudePluginDirectories
|
|
442
|
+
? { claudePluginDirectories: run.claudePluginDirectories }
|
|
443
|
+
: {}),
|
|
404
444
|
});
|
|
405
445
|
|
|
406
446
|
const locked = await deps.withCompanionLock(input.companionPath, lockOptions(input), async () => {
|
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CapabilityConfig,
|
|
3
|
-
CliToolConfig,
|
|
4
|
-
FrameworkConfig,
|
|
5
|
-
GitModeProfile,
|
|
6
|
-
OpenSpecSchemaProfile,
|
|
7
|
-
} from "./types";
|
|
1
|
+
import type { CapabilityConfig, CliToolConfig, FrameworkConfig, GitModeProfile } from "./types";
|
|
8
2
|
|
|
9
|
-
export type OpenSpecSchemaSelection = OpenSpecSchemaProfile | "default";
|
|
10
3
|
export type GitModeSelection = GitModeProfile | "default";
|
|
11
|
-
const DEFAULT_OPENSPEC_SCHEMA_SELECTION: OpenSpecSchemaSelection = "default";
|
|
12
4
|
export const DEFAULT_GIT_MODE_SELECTION: GitModeSelection = "default";
|
|
13
5
|
|
|
14
|
-
export interface OpenSpecSchemaCompatibility {
|
|
15
|
-
id: OpenSpecSchemaSelection;
|
|
16
|
-
label: string;
|
|
17
|
-
description: string;
|
|
18
|
-
defaultSelected: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
6
|
export interface GitModeCompatibility {
|
|
22
7
|
id: GitModeSelection;
|
|
23
8
|
label: string;
|
|
@@ -25,21 +10,6 @@ export interface GitModeCompatibility {
|
|
|
25
10
|
defaultSelected: boolean;
|
|
26
11
|
}
|
|
27
12
|
|
|
28
|
-
const BUILTIN_OPENSPEC_SCHEMA_COMPATIBILITIES: OpenSpecSchemaCompatibility[] = [
|
|
29
|
-
{
|
|
30
|
-
id: "default",
|
|
31
|
-
label: "Default (vanilla OpenSpec)",
|
|
32
|
-
description: "Use OpenSpec's built-in spec-driven workflow.",
|
|
33
|
-
defaultSelected: true,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "mate-v1",
|
|
37
|
-
label: "Mate v1",
|
|
38
|
-
description: "Use Mate's schema with proposal, design, spec, and task artifacts.",
|
|
39
|
-
defaultSelected: false,
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
|
|
43
13
|
const BUILTIN_GIT_MODE_COMPATIBILITIES: GitModeCompatibility[] = [
|
|
44
14
|
{
|
|
45
15
|
id: "default",
|
|
@@ -59,33 +29,9 @@ export interface SetupSelections {
|
|
|
59
29
|
allowedAgents: string[];
|
|
60
30
|
packageManagers: string[];
|
|
61
31
|
capabilities: CapabilityConfig[];
|
|
62
|
-
selectedOpenSpecSchema: OpenSpecSchemaSelection;
|
|
63
32
|
selectedGitMode: GitModeSelection;
|
|
64
33
|
}
|
|
65
34
|
|
|
66
|
-
export function getOpenSpecSchemaSelection(
|
|
67
|
-
capabilities: CapabilityConfig[],
|
|
68
|
-
): OpenSpecSchemaSelection {
|
|
69
|
-
const openspec = capabilities.find((capability) => capability.name === "openspec");
|
|
70
|
-
return openspec?.schemaProfile ?? DEFAULT_OPENSPEC_SCHEMA_SELECTION;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function applyOpenSpecSchemaSelection(
|
|
74
|
-
capabilities: CapabilityConfig[],
|
|
75
|
-
schemaSelection: OpenSpecSchemaSelection | undefined,
|
|
76
|
-
): CapabilityConfig[] {
|
|
77
|
-
if (schemaSelection === undefined) return capabilities;
|
|
78
|
-
|
|
79
|
-
return capabilities.map((capability) => {
|
|
80
|
-
if (capability.name !== "openspec") return capability;
|
|
81
|
-
if (schemaSelection === "default") {
|
|
82
|
-
const { schemaProfile: _schemaProfile, ...rest } = capability;
|
|
83
|
-
return rest;
|
|
84
|
-
}
|
|
85
|
-
return { ...capability, schemaProfile: schemaSelection };
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
35
|
interface SetupCompatibilityBase {
|
|
90
36
|
id: string;
|
|
91
37
|
label: string;
|
|
@@ -222,24 +168,6 @@ export function listSetupPackageManagerCompatibilities(): SetupPackageManagerCom
|
|
|
222
168
|
);
|
|
223
169
|
}
|
|
224
170
|
|
|
225
|
-
export function listSetupOpenSpecSchemaCompatibilities(
|
|
226
|
-
activeSchemaProfile?: OpenSpecSchemaSelection,
|
|
227
|
-
): OpenSpecSchemaCompatibility[] {
|
|
228
|
-
const compatibilities = [...BUILTIN_OPENSPEC_SCHEMA_COMPATIBILITIES];
|
|
229
|
-
if (
|
|
230
|
-
activeSchemaProfile !== undefined &&
|
|
231
|
-
!BUILTIN_OPENSPEC_SCHEMA_COMPATIBILITIES.some((entry) => entry.id === activeSchemaProfile)
|
|
232
|
-
) {
|
|
233
|
-
compatibilities.push({
|
|
234
|
-
id: activeSchemaProfile,
|
|
235
|
-
label: "Custom",
|
|
236
|
-
description: `Keep the project's current custom schema profile ("${activeSchemaProfile}"). Selecting Default or Mate v1 replaces it.`,
|
|
237
|
-
defaultSelected: false,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return compatibilities;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
171
|
export function listSetupGitModeCompatibilities(): GitModeCompatibility[] {
|
|
244
172
|
return [...BUILTIN_GIT_MODE_COMPATIBILITIES];
|
|
245
173
|
}
|
|
@@ -272,7 +200,6 @@ export function getDefaultSetupSelections(): SetupSelections {
|
|
|
272
200
|
allowedAgents,
|
|
273
201
|
packageManagers,
|
|
274
202
|
capabilities,
|
|
275
|
-
selectedOpenSpecSchema: DEFAULT_OPENSPEC_SCHEMA_SELECTION,
|
|
276
203
|
selectedGitMode: DEFAULT_GIT_MODE_SELECTION,
|
|
277
204
|
};
|
|
278
205
|
}
|
|
@@ -284,11 +211,27 @@ export function getSetupSelectionsFromConfig(config: FrameworkConfig): SetupSele
|
|
|
284
211
|
...new Set([...getLockedPackageManagers(), ...(config.packageManagers ?? [])]),
|
|
285
212
|
],
|
|
286
213
|
capabilities: dedupeCapabilities(config.capabilities ?? []),
|
|
287
|
-
selectedOpenSpecSchema: getOpenSpecSchemaSelection(config.capabilities ?? []),
|
|
288
214
|
selectedGitMode: config.git ?? DEFAULT_GIT_MODE_SELECTION,
|
|
289
215
|
};
|
|
290
216
|
}
|
|
291
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Re-selected capabilities keep their saved settings (such as openspec's
|
|
220
|
+
* `protectedBranches`); pickers and flags only name the capabilities.
|
|
221
|
+
*/
|
|
222
|
+
export function keepCapabilitySettings(
|
|
223
|
+
selected: CapabilityConfig[],
|
|
224
|
+
saved: CapabilityConfig[],
|
|
225
|
+
): CapabilityConfig[] {
|
|
226
|
+
const byName = new Map(saved.map((capability) => [capability.name, capability]));
|
|
227
|
+
return selected.map((capability) => {
|
|
228
|
+
// `schemaProfile` is a removed setting; a re-save drops it.
|
|
229
|
+
const { schemaProfile: _removed, ...settings } = (byName.get(capability.name) ??
|
|
230
|
+
{}) as CapabilityConfig & { schemaProfile?: unknown };
|
|
231
|
+
return { ...capability, ...settings };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
292
235
|
export function dedupeCapabilities(capabilities: CapabilityConfig[]): CapabilityConfig[] {
|
|
293
236
|
const seen = new Set<string>();
|
|
294
237
|
const result: CapabilityConfig[] = [];
|
|
@@ -58,12 +58,12 @@ export interface CliToolConfig {
|
|
|
58
58
|
redirectCwd: "companionRoot" | false;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export type OpenSpecSchemaProfile = "mate-v1" | (string & {});
|
|
62
61
|
export type GitModeProfile = "auto";
|
|
63
62
|
|
|
64
63
|
export interface CapabilityConfig {
|
|
65
64
|
name: string;
|
|
66
|
-
|
|
65
|
+
/** openspec only: branch glob patterns (`release/*`) treated as protected besides the default branch. */
|
|
66
|
+
protectedBranches?: string[];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export type FrameworkType = "working" | "companion" | "hub";
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
publishWorkingClaudePluginMarketplaces,
|
|
5
|
+
renderWorkingRuntimeDocuments,
|
|
6
|
+
} from "../../tools/setup";
|
|
7
|
+
import type { ClaudePluginFolders } from "../../tools/setup/providers/claude-plugin-folders";
|
|
4
8
|
import { readCompanionConfig } from "./config-store";
|
|
5
9
|
import { GlobalConfigStore } from "./global-config-store";
|
|
6
10
|
import { entryFailureMessage, firstFailure, type ProjectionChangedPaths } from "./projection-types";
|
|
@@ -10,6 +14,7 @@ import type { FrameworkConfig, LinkedRepository } from "./types";
|
|
|
10
14
|
import { project } from "./working-repo-projection";
|
|
11
15
|
|
|
12
16
|
export interface WrapPassDependencies {
|
|
17
|
+
publishWorkingClaudePluginMarketplaces: typeof publishWorkingClaudePluginMarketplaces;
|
|
13
18
|
renderWorkingRuntimeDocuments: typeof renderWorkingRuntimeDocuments;
|
|
14
19
|
readCompanionConfig: typeof readCompanionConfig;
|
|
15
20
|
project: typeof project;
|
|
@@ -18,6 +23,7 @@ export interface WrapPassDependencies {
|
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export const wrapPassDeps: WrapPassDependencies = {
|
|
26
|
+
publishWorkingClaudePluginMarketplaces,
|
|
21
27
|
renderWorkingRuntimeDocuments,
|
|
22
28
|
readCompanionConfig,
|
|
23
29
|
project,
|
|
@@ -47,6 +53,11 @@ export type WrapPassResult =
|
|
|
47
53
|
runtimes: ReadonlySet<WrapRuntime>;
|
|
48
54
|
/** The wrap state as this pass left it. */
|
|
49
55
|
wrapState: WrapState;
|
|
56
|
+
/**
|
|
57
|
+
* The Claude plugin folders this pass published, when it covered Claude;
|
|
58
|
+
* what publication left undone is the caller's to present, never in `warnings`.
|
|
59
|
+
*/
|
|
60
|
+
claudePluginFolders?: ClaudePluginFolders;
|
|
50
61
|
}
|
|
51
62
|
| { kind: "failed"; message: string };
|
|
52
63
|
|
|
@@ -63,6 +74,15 @@ export async function runWrapPass(
|
|
|
63
74
|
): Promise<WrapPassResult> {
|
|
64
75
|
const resolvedCompanionPath = path.resolve(companionPath);
|
|
65
76
|
const config = options.config ?? (await deps.readCompanionConfig(resolvedCompanionPath));
|
|
77
|
+
/** The render reads the marketplace files, so they are written first. */
|
|
78
|
+
const published =
|
|
79
|
+
!options.runtimes || options.runtimes.includes("claude")
|
|
80
|
+
? await deps.publishWorkingClaudePluginMarketplaces(
|
|
81
|
+
resolvedCompanionPath,
|
|
82
|
+
config,
|
|
83
|
+
repository.path,
|
|
84
|
+
)
|
|
85
|
+
: undefined;
|
|
66
86
|
const rendered = await deps.renderWorkingRuntimeDocuments(
|
|
67
87
|
resolvedCompanionPath,
|
|
68
88
|
config,
|
|
@@ -131,5 +151,6 @@ export async function runWrapPass(
|
|
|
131
151
|
],
|
|
132
152
|
runtimes,
|
|
133
153
|
wrapState,
|
|
154
|
+
...(published ? { claudePluginFolders: published } : {}),
|
|
134
155
|
};
|
|
135
156
|
}
|
package/src/lib/package-paths.ts
CHANGED
|
@@ -32,10 +32,18 @@ export function getClaudePluginRoot(): string {
|
|
|
32
32
|
return path.resolve(import.meta.dirname, "../../claude-plugin");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The `name` in the bundled Claude plugin's manifest. Claude Code identifies a
|
|
37
|
+
* plugin by its name, so a contributed folder using this one could take the
|
|
38
|
+
* place of Mate's plugin and its guardrail hooks.
|
|
39
|
+
*/
|
|
40
|
+
export const MATE_CLAUDE_PLUGIN_NAME = "mate-claude-plugin";
|
|
41
|
+
|
|
35
42
|
const CLAUDE_PLUGIN_HOOK_SHIMS = [
|
|
36
43
|
"validate-artifact-path.mjs",
|
|
37
44
|
"session-banner.mjs",
|
|
38
45
|
"session-guidance.mjs",
|
|
46
|
+
"companion-instructions.mjs",
|
|
39
47
|
] as const;
|
|
40
48
|
|
|
41
49
|
/**
|
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import type { Context } from "@opencode/plugin/promise/plugin";
|
|
4
4
|
|
|
5
|
+
import { resolveArtifactTarget } from "../runtime/artifact-refusal";
|
|
6
|
+
import { checkCommitAttribution } from "../runtime/commit-attribution-guard";
|
|
5
7
|
import {
|
|
6
8
|
companionForkRefusal,
|
|
7
9
|
syncCompanionUnattended,
|
|
@@ -10,12 +12,14 @@ import {
|
|
|
10
12
|
import { hasLaunchEnvironment } from "../runtime/env";
|
|
11
13
|
import { MATE_ENV } from "../runtime/env-names";
|
|
12
14
|
import { resolveProjection } from "../runtime/projection";
|
|
15
|
+
import { checkSecretAccess, type SecretToolCall } from "../runtime/secret-guard";
|
|
13
16
|
import { readWrapStateSync } from "../runtime/wrap-state";
|
|
14
17
|
import {
|
|
15
18
|
buildArtifactError,
|
|
19
|
+
extractPatchAdditions,
|
|
16
20
|
extractPatchPaths,
|
|
17
21
|
isArtifactPath,
|
|
18
|
-
|
|
22
|
+
isCompanionTarget,
|
|
19
23
|
shouldBlockArtifactWrite,
|
|
20
24
|
type CompanionContext,
|
|
21
25
|
} from "./companion-policy";
|
|
@@ -87,14 +91,55 @@ export async function refuseForkedCompanionWrite(
|
|
|
87
91
|
env: Record<string, string | undefined> = process.env,
|
|
88
92
|
): Promise<void> {
|
|
89
93
|
if (!filePath || !isArtifactPath(filePath)) return;
|
|
90
|
-
if (!
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
94
|
+
if (!isCompanionTarget(context, filePath)) return;
|
|
93
95
|
const refusal = await companionForkRefusal(env, context.companionPath);
|
|
94
96
|
if (refusal) throw new Error(refusal);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
/**
|
|
99
|
+
/** OpenCode tool inputs in the shape the shared secret guard reads. */
|
|
100
|
+
function secretToolCall(
|
|
101
|
+
context: CompanionContext,
|
|
102
|
+
tool: string,
|
|
103
|
+
args: Record<string, unknown>,
|
|
104
|
+
): SecretToolCall | null {
|
|
105
|
+
const text = (value: unknown) => (typeof value === "string" ? value : "");
|
|
106
|
+
const target = (value: unknown) =>
|
|
107
|
+
text(value) ? resolveArtifactTarget(text(value), context.repositoryPath) : "";
|
|
108
|
+
switch (tool) {
|
|
109
|
+
case "read":
|
|
110
|
+
return { kind: "read", paths: [target(args.filePath)] };
|
|
111
|
+
case "write":
|
|
112
|
+
return {
|
|
113
|
+
kind: "write",
|
|
114
|
+
writes: [{ path: target(args.filePath), content: text(args.content) }],
|
|
115
|
+
};
|
|
116
|
+
case "edit":
|
|
117
|
+
return {
|
|
118
|
+
kind: "write",
|
|
119
|
+
writes: [{ path: target(args.filePath), content: text(args.newString) }],
|
|
120
|
+
};
|
|
121
|
+
case "apply_patch":
|
|
122
|
+
return {
|
|
123
|
+
kind: "write",
|
|
124
|
+
writes: extractPatchAdditions(text(args.patchText)).map((addition) => ({
|
|
125
|
+
path: target(addition.path),
|
|
126
|
+
content: addition.content,
|
|
127
|
+
})),
|
|
128
|
+
};
|
|
129
|
+
case "bash":
|
|
130
|
+
return { kind: "shell", command: text(args.command) };
|
|
131
|
+
case "grep":
|
|
132
|
+
return { kind: "search", paths: [target(args.path)], globs: [text(args.include)] };
|
|
133
|
+
default:
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Rejects when the tool call touches a secret, commits with a model co-author
|
|
140
|
+
* trailer, or would write an artifact outside the companion. Checked in that
|
|
141
|
+
* order, as in the Claude hook.
|
|
142
|
+
*/
|
|
98
143
|
export async function guardToolInput(
|
|
99
144
|
context: CompanionContext,
|
|
100
145
|
tool: string,
|
|
@@ -102,6 +147,20 @@ export async function guardToolInput(
|
|
|
102
147
|
env: Record<string, string | undefined> = process.env,
|
|
103
148
|
): Promise<void> {
|
|
104
149
|
const args = (input ?? {}) as Record<string, unknown>;
|
|
150
|
+
|
|
151
|
+
const secretCall = context.companionPath ? secretToolCall(context, tool, args) : null;
|
|
152
|
+
if (secretCall) {
|
|
153
|
+
const companionRoot = path.normalize(context.companionPath);
|
|
154
|
+
const refusal = await checkSecretAccess(secretCall, {
|
|
155
|
+
companionPath: context.companionPath,
|
|
156
|
+
isCompanionPath: (filePath) => filePath.startsWith(companionRoot),
|
|
157
|
+
});
|
|
158
|
+
if (refusal) throw new Error(refusal);
|
|
159
|
+
}
|
|
160
|
+
if (tool === "bash") {
|
|
161
|
+
const refusal = checkCommitAttribution(String(args.command ?? ""));
|
|
162
|
+
if (refusal) throw new Error(refusal);
|
|
163
|
+
}
|
|
105
164
|
const filePaths =
|
|
106
165
|
tool === "write" || tool === "edit"
|
|
107
166
|
? [String(args.filePath ?? "")]
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { artifactRefusal, resolveArtifactTarget } from "../runtime/artifact-refusal";
|
|
5
6
|
import { persistedCompanionGitStalenessLines } from "../runtime/companion-sync";
|
|
6
7
|
import { resolveCompanionRuntime, type CompanionRuntimeContext } from "../runtime/env";
|
|
7
8
|
import { projectionFreshness, projectionStalenessLines } from "../runtime/freshness";
|
|
@@ -60,7 +61,7 @@ export function isArtifactPath(filePath: string): boolean {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function isProductDocumentationPath(context: CompanionContext, filePath: string): boolean {
|
|
63
|
-
const normalized =
|
|
64
|
+
const normalized = resolveArtifactTarget(filePath, context.repositoryPath);
|
|
64
65
|
const relativePath = path.relative(context.repositoryPath, normalized);
|
|
65
66
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
66
67
|
return false;
|
|
@@ -86,12 +87,10 @@ export function isProductDocumentationPath(context: CompanionContext, filePath:
|
|
|
86
87
|
return false;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
export function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return path.normalize(path.join(context.repositoryPath, filePath));
|
|
90
|
+
export function isCompanionTarget(context: CompanionContext, filePath: string): boolean {
|
|
91
|
+
return resolveArtifactTarget(filePath, context.repositoryPath).startsWith(
|
|
92
|
+
path.normalize(context.companionPath),
|
|
93
|
+
);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
export function isGitignored(context: CompanionContext, filePath: string): boolean {
|
|
@@ -99,7 +98,7 @@ export function isGitignored(context: CompanionContext, filePath: string): boole
|
|
|
99
98
|
return false;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
const normalized =
|
|
101
|
+
const normalized = resolveArtifactTarget(filePath, context.repositoryPath);
|
|
103
102
|
if (!normalized.startsWith(path.normalize(context.repositoryPath))) {
|
|
104
103
|
return false;
|
|
105
104
|
}
|
|
@@ -126,7 +125,7 @@ export function shouldBlockArtifactWrite(context: CompanionContext, filePath: st
|
|
|
126
125
|
return false;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
if (
|
|
128
|
+
if (isCompanionTarget(context, filePath)) {
|
|
130
129
|
return false;
|
|
131
130
|
}
|
|
132
131
|
|
|
@@ -155,16 +154,36 @@ export function extractPatchPaths(patchText: string): string[] {
|
|
|
155
154
|
return paths;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
/**
|
|
158
|
+
* What a patch would add to each file it touches: the `+` lines of every
|
|
159
|
+
* section, keyed by the section's target (a move's destination when present).
|
|
160
|
+
*/
|
|
161
|
+
export function extractPatchAdditions(patchText: string): { path: string; content: string }[] {
|
|
162
|
+
const additions: { path: string; content: string[] }[] = [];
|
|
163
|
+
let current: { path: string; content: string[] } | undefined;
|
|
164
|
+
for (const line of patchText.split("\n")) {
|
|
165
|
+
const marker = /^\*\*\* (Add|Update|Delete|Move to) File:\s*(.+)$/.exec(line);
|
|
166
|
+
if (marker) {
|
|
167
|
+
if (marker[1] === "Move to" && current) {
|
|
168
|
+
current.path = marker[2].trim();
|
|
169
|
+
} else {
|
|
170
|
+
current = { path: marker[2].trim(), content: [] };
|
|
171
|
+
additions.push(current);
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (current && line.startsWith("+")) current.content.push(line.slice(1));
|
|
176
|
+
}
|
|
177
|
+
return additions.map((entry) => ({ path: entry.path, content: entry.content.join("\n") }));
|
|
178
|
+
}
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
export function buildArtifactError(context: CompanionContext, filePath: string): string {
|
|
181
|
+
return artifactRefusal({
|
|
182
|
+
target: filePath,
|
|
183
|
+
resolved: resolveArtifactTarget(filePath, context.repositoryPath),
|
|
184
|
+
companion: context.companionPath,
|
|
185
|
+
repoRoot: context.repositoryPath,
|
|
186
|
+
});
|
|
168
187
|
}
|
|
169
188
|
|
|
170
189
|
/**
|
|
@@ -44,6 +44,9 @@ export function resolveOpenCodeGuidance(
|
|
|
44
44
|
if (!projection) return { guidance: null, errors: [] };
|
|
45
45
|
|
|
46
46
|
const policy = readCompanionPolicy(projection.companionPath);
|
|
47
|
-
const guidance = buildOpenCodeGuidance(
|
|
47
|
+
const guidance = buildOpenCodeGuidance(
|
|
48
|
+
policy.enabledCapabilities.map((name) => ({ name })),
|
|
49
|
+
{ secretGuardrails: policy.secrets.guardrails },
|
|
50
|
+
);
|
|
48
51
|
return { guidance: guidance.errors.length > 0 ? null : guidance, errors: guidance.errors };
|
|
49
52
|
}
|