@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Delivery Path stage contract: the action-named sequence Studio and the docs check
|
|
3
|
+
* read. Review stages come from a schema's artifacts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A skill stage runs a Mate skill; a human stage is a person reviewing one artifact. */
|
|
7
|
+
export type DeliveryStageKind = "skill" | "human";
|
|
8
|
+
|
|
9
|
+
interface StageBase {
|
|
10
|
+
/** Stable identifier, e.g. `archive` or `review-specs`. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The action the stage performs, e.g. `Archive change`. */
|
|
13
|
+
label: string;
|
|
14
|
+
kind: DeliveryStageKind;
|
|
15
|
+
/** Optional stages can be skipped and never block a later stage. */
|
|
16
|
+
optional: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type SkillStageId =
|
|
20
|
+
| "clarify-intent"
|
|
21
|
+
| "explore"
|
|
22
|
+
| "propose"
|
|
23
|
+
| "apply"
|
|
24
|
+
| "inspect-outcome"
|
|
25
|
+
| "refine-code"
|
|
26
|
+
| "archive"
|
|
27
|
+
| "publish";
|
|
28
|
+
|
|
29
|
+
export interface SkillStage extends StageBase {
|
|
30
|
+
id: SkillStageId;
|
|
31
|
+
kind: "skill";
|
|
32
|
+
/** The Mate skill that drives the stage. */
|
|
33
|
+
skill: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReviewStage extends StageBase {
|
|
37
|
+
kind: "human";
|
|
38
|
+
skill: null;
|
|
39
|
+
/** The schema artifact the person reviews. */
|
|
40
|
+
artifact: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type DeliveryStage = SkillStage | ReviewStage;
|
|
44
|
+
|
|
45
|
+
export interface DeliveryPath {
|
|
46
|
+
stages: DeliveryStage[];
|
|
47
|
+
/** False when the schema's artifacts could not be read, so reviews are unknown. */
|
|
48
|
+
reviewsKnown: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Artifact sets of the schemas Mate ships, in dependency order. */
|
|
52
|
+
export const SCHEMA_ARTIFACTS = {
|
|
53
|
+
"mate-v1": ["proposal", "specs", "design", "tasks"],
|
|
54
|
+
"mate-minimal": ["specs", "tasks"],
|
|
55
|
+
} as const satisfies Record<string, readonly string[]>;
|
|
56
|
+
|
|
57
|
+
/** A schema Mate ships. */
|
|
58
|
+
export type MateSchema = keyof typeof SCHEMA_ARTIFACTS;
|
|
59
|
+
|
|
60
|
+
/** The schema every companion is set to. */
|
|
61
|
+
export const DEFAULT_SCHEMA: MateSchema = "mate-v1";
|
|
62
|
+
|
|
63
|
+
export function isMateSchema(schema: string): schema is MateSchema {
|
|
64
|
+
return Object.hasOwn(SCHEMA_ARTIFACTS, schema);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function skillStage(id: SkillStageId, label: string, skill: string, optional = false): SkillStage {
|
|
68
|
+
return { id, label, kind: "skill", skill, optional };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const BEFORE_REVIEWS: readonly SkillStage[] = [
|
|
72
|
+
skillStage("clarify-intent", "Clarify intent", "mate-clarify-intent", true),
|
|
73
|
+
skillStage("explore", "Explore change", "mate-explore-change"),
|
|
74
|
+
skillStage("propose", "Propose change", "mate-propose-change"),
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const AFTER_REVIEWS: readonly SkillStage[] = [
|
|
78
|
+
skillStage("apply", "Apply change", "mate-apply-change"),
|
|
79
|
+
skillStage("inspect-outcome", "Inspect outcome", "mate-show-me", true),
|
|
80
|
+
skillStage("refine-code", "Refine code", "mate-simplify-code", true),
|
|
81
|
+
skillStage("archive", "Archive change", "mate-archive-change"),
|
|
82
|
+
skillStage("publish", "Publish companion artifacts", "mate-publish-artifact"),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/** Every skill a Delivery Path stage is driven by, in stage order. */
|
|
86
|
+
export const DELIVERY_STAGE_SKILLS: readonly string[] = [...BEFORE_REVIEWS, ...AFTER_REVIEWS].map(
|
|
87
|
+
(stage) => stage.skill,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
function reviewStage(artifact: string): ReviewStage {
|
|
91
|
+
return {
|
|
92
|
+
id: `review-${artifact}`,
|
|
93
|
+
label: `Review ${artifact}`,
|
|
94
|
+
kind: "human",
|
|
95
|
+
skill: null,
|
|
96
|
+
artifact,
|
|
97
|
+
optional: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The Delivery Path for one schema. `artifacts` are its artifact ids in dependency order;
|
|
103
|
+
* `undefined` means the schema could not be read, which yields the skill stages alone with
|
|
104
|
+
* `reviewsKnown: false` rather than a guessed review set.
|
|
105
|
+
*/
|
|
106
|
+
export function deliveryPath(artifacts: readonly string[] | undefined): DeliveryPath {
|
|
107
|
+
return {
|
|
108
|
+
stages: [...BEFORE_REVIEWS, ...(artifacts ?? []).map(reviewStage), ...AFTER_REVIEWS],
|
|
109
|
+
reviewsKnown: artifacts !== undefined,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
/** The only environment variable a forge token is read from. */
|
|
7
|
+
export const FORGE_TOKEN_ENV = "MATE_GIT_TOKEN";
|
|
8
|
+
|
|
9
|
+
export interface ForgeToken {
|
|
10
|
+
token: string;
|
|
11
|
+
source: typeof FORGE_TOKEN_ENV | "gh";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ForgeTokenDeps {
|
|
15
|
+
env?: Record<string, string | undefined>;
|
|
16
|
+
/** `gh auth token`, or null when the GitHub CLI is missing or signed out. */
|
|
17
|
+
ghToken?: () => Promise<string | null>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Variables `gh auth token` would report instead of the stored login. */
|
|
21
|
+
const GH_TOKEN_VARIABLES = [
|
|
22
|
+
"GH_TOKEN",
|
|
23
|
+
"GITHUB_TOKEN",
|
|
24
|
+
"GH_ENTERPRISE_TOKEN",
|
|
25
|
+
"GITHUB_ENTERPRISE_TOKEN",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** `env` without the token variables, so `gh` reports only its stored login. */
|
|
29
|
+
export function ghAuthEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
30
|
+
const scrubbed = { ...env };
|
|
31
|
+
for (const name of GH_TOKEN_VARIABLES) delete scrubbed[name];
|
|
32
|
+
return scrubbed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function defaultGhToken(): Promise<string | null> {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await execFileAsync("gh", ["auth", "token"], {
|
|
38
|
+
timeout: 10_000,
|
|
39
|
+
env: ghAuthEnv(process.env),
|
|
40
|
+
});
|
|
41
|
+
return stdout.trim() || null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The forge token from `MATE_GIT_TOKEN`, then `gh auth token`. Other variables such as
|
|
49
|
+
* `GITHUB_TOKEN` are deliberately ignored so a broadly scoped CI token is never picked up.
|
|
50
|
+
* The returned value is for the forge request only: callers never print, log, or store it.
|
|
51
|
+
*/
|
|
52
|
+
export async function resolveForgeToken(deps: ForgeTokenDeps = {}): Promise<ForgeToken | null> {
|
|
53
|
+
const env = deps.env ?? process.env;
|
|
54
|
+
const fromEnv = env[FORGE_TOKEN_ENV]?.trim();
|
|
55
|
+
if (fromEnv) return { token: fromEnv, source: FORGE_TOKEN_ENV };
|
|
56
|
+
const fromGh = (await (deps.ghToken ?? defaultGhToken)())?.trim();
|
|
57
|
+
return fromGh ? { token: fromGh, source: "gh" } : null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
|
|
3
|
+
/** A leading `---` block; group 1 is its YAML, the match ends after the closing line. */
|
|
4
|
+
export const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
5
|
+
|
|
6
|
+
/** A Markdown file's leading YAML frontmatter; null when absent, malformed, or not a mapping. */
|
|
7
|
+
export function readFrontmatter(source: string): Record<string, unknown> | null {
|
|
8
|
+
const header = FRONTMATTER_PATTERN.exec(source)?.[1];
|
|
9
|
+
if (header === undefined) return null;
|
|
10
|
+
try {
|
|
11
|
+
const parsed: unknown = parse(header);
|
|
12
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
13
|
+
? (parsed as Record<string, unknown>)
|
|
14
|
+
: null;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -40,11 +40,11 @@ export interface WarmPluginCacheResult {
|
|
|
40
40
|
|
|
41
41
|
export const opencodePluginCacheDeps = {
|
|
42
42
|
runInstall: (cwd: string, registry: string) =>
|
|
43
|
-
spawnSync(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
stdio: ["ignore", "pipe", "pipe"] as const,
|
|
47
|
-
|
|
43
|
+
spawnSync(
|
|
44
|
+
"npm",
|
|
45
|
+
["install", "--no-audit", "--no-fund", "--loglevel=error", "--registry", registry],
|
|
46
|
+
{ cwd, encoding: "utf8" as const, stdio: ["ignore", "pipe", "pipe"] as const },
|
|
47
|
+
),
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
export async function warmOpenCodePackageCache(
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Set on every `openspec` process Mate starts. Mate decides the CLI version, so
|
|
10
|
+
* OpenSpec's own "update available" hint is wrong advice here.
|
|
11
|
+
*/
|
|
12
|
+
export const OPENSPEC_ENV = {
|
|
13
|
+
OPENSPEC_TELEMETRY: "0",
|
|
14
|
+
OPENSPEC_NO_UPDATE_CHECK: "1",
|
|
15
|
+
} as const satisfies Record<string, string>;
|
|
16
|
+
|
|
17
|
+
/** The bundled CLI, with its version when its manifest is readable, or the reinstall message. */
|
|
18
|
+
export type OpenSpecLocation = { bin: string; version?: string } | { missing: string };
|
|
19
|
+
|
|
20
|
+
export interface OpenSpecRunOptions {
|
|
21
|
+
cwd: string;
|
|
22
|
+
/** `capture` (default) collects output; `inherit` streams it to the terminal. */
|
|
23
|
+
stdio?: "capture" | "inherit";
|
|
24
|
+
/** Milliseconds before the child is killed. */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
/** Base environment, `process.env` by default; {@link OPENSPEC_ENV} always wins. */
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
/** The CLI to run instead of the located one, e.g. when vendoring from a given CLI. */
|
|
29
|
+
bin?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `code` is 1 when the CLI is missing or fails to start, with Mate's message as
|
|
34
|
+
* `stderr`; `null` when the child was killed. With `stdio: "inherit"` only that
|
|
35
|
+
* message is returned, the CLI's own output went to the terminal.
|
|
36
|
+
*/
|
|
37
|
+
export interface OpenSpecRun {
|
|
38
|
+
code: number | null;
|
|
39
|
+
stdout: string;
|
|
40
|
+
stderr: string;
|
|
41
|
+
/** Set when the bundled CLI is missing, so nothing ran. */
|
|
42
|
+
missing?: true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RunOpenSpec = (args: string[], options: OpenSpecRunOptions) => Promise<OpenSpecRun>;
|
|
46
|
+
|
|
47
|
+
function readVersion(bin: string): string | undefined {
|
|
48
|
+
try {
|
|
49
|
+
const manifest = path.resolve(path.dirname(bin), "../package.json");
|
|
50
|
+
return (JSON.parse(fs.readFileSync(manifest, "utf8")) as { version: string }).version;
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finds the OpenSpec CLI shipped with mate-core the way Node resolves it, never
|
|
58
|
+
* an `openspec` on `PATH`. `resolve` exists for layout tests.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveOpenSpec(
|
|
61
|
+
resolve: (id: string) => string = require.resolve,
|
|
62
|
+
): OpenSpecLocation {
|
|
63
|
+
let entryPath: string;
|
|
64
|
+
try {
|
|
65
|
+
// The package exports only `.` (dist/index.js); the binary sits beside dist/.
|
|
66
|
+
entryPath = resolve("@fission-ai/openspec");
|
|
67
|
+
} catch {
|
|
68
|
+
return { missing: "bundled OpenSpec CLI is missing. Reinstall Mate to restore it." };
|
|
69
|
+
}
|
|
70
|
+
const bin = path.resolve(path.dirname(entryPath), "../bin/openspec.js");
|
|
71
|
+
if (!fs.existsSync(bin)) {
|
|
72
|
+
return { missing: `bundled OpenSpec CLI is missing at ${bin}. Reinstall Mate to restore it.` };
|
|
73
|
+
}
|
|
74
|
+
return { bin, version: readVersion(bin) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The one in-process seam: suites point `locate` at a stub CLI. */
|
|
78
|
+
export const openSpecCliDeps = {
|
|
79
|
+
locate: (): OpenSpecLocation => resolveOpenSpec(),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** The CLI every OpenSpec process Mate starts runs. */
|
|
83
|
+
export function locateOpenSpec(): OpenSpecLocation {
|
|
84
|
+
return openSpecCliDeps.locate();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Runs the bundled CLI with {@link OPENSPEC_ENV} over the base environment. Never rejects. */
|
|
88
|
+
export function runOpenSpec(args: string[], options: OpenSpecRunOptions): Promise<OpenSpecRun> {
|
|
89
|
+
const location = options.bin ? { bin: options.bin } : locateOpenSpec();
|
|
90
|
+
if ("missing" in location) {
|
|
91
|
+
return Promise.resolve({ code: 1, stdout: "", stderr: location.missing, missing: true });
|
|
92
|
+
}
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
// A JS entry runs on the current runtime, not its `#!/usr/bin/env node` shebang,
|
|
95
|
+
// which needs an executable bit and `node` on PATH and does not work on Windows.
|
|
96
|
+
const [command, commandArgs] = location.bin.endsWith(".js")
|
|
97
|
+
? [process.execPath, [location.bin, ...args]]
|
|
98
|
+
: [location.bin, args];
|
|
99
|
+
const child = spawn(command, commandArgs, {
|
|
100
|
+
cwd: options.cwd,
|
|
101
|
+
env: { ...(options.env ?? process.env), ...OPENSPEC_ENV },
|
|
102
|
+
stdio: options.stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
103
|
+
...(options.timeout === undefined ? {} : { timeout: options.timeout }),
|
|
104
|
+
});
|
|
105
|
+
let stdout = "";
|
|
106
|
+
let stderr = "";
|
|
107
|
+
child.stdout?.setEncoding("utf8");
|
|
108
|
+
child.stderr?.setEncoding("utf8");
|
|
109
|
+
child.stdout?.on("data", (chunk: string) => (stdout += chunk));
|
|
110
|
+
child.stderr?.on("data", (chunk: string) => (stderr += chunk));
|
|
111
|
+
child.once("error", (error) =>
|
|
112
|
+
resolve({ code: 1, stdout, stderr: `failed to run openspec: ${error.message}` }),
|
|
113
|
+
);
|
|
114
|
+
child.once("close", (code) => resolve({ code, stdout, stderr }));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
|
|
6
|
+
import { FRONTMATTER_PATTERN } from "./frontmatter";
|
|
7
|
+
|
|
8
|
+
export const BOM = "";
|
|
9
|
+
|
|
10
|
+
export interface ScopePair {
|
|
11
|
+
repository: string;
|
|
12
|
+
area: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A leading `<!-- mate:delta … -->` comment; group 1 is its YAML, the match ends after
|
|
17
|
+
* the closing `-->` line. OpenSpec skips HTML comments, so a delta carrying its metadata
|
|
18
|
+
* here passes `validate --strict`.
|
|
19
|
+
*/
|
|
20
|
+
export const DELTA_COMMENT_PATTERN =
|
|
21
|
+
/^<!--[ \t]*mate:delta[ \t]*\r?\n([\s\S]*?)\r?\n-->[ \t]*(?:\r?\n|$)/;
|
|
22
|
+
|
|
23
|
+
/** Reads `scopes` from a `mate:delta` comment, else from frontmatter (proposals, legacy deltas). */
|
|
24
|
+
export function parseScopes(source: string): ScopePair[] {
|
|
25
|
+
const text = source.startsWith(BOM) ? source.slice(BOM.length) : source;
|
|
26
|
+
const match = DELTA_COMMENT_PATTERN.exec(text) ?? FRONTMATTER_PATTERN.exec(text);
|
|
27
|
+
if (!match) return [];
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const parsed = parse(match[1]) as unknown;
|
|
31
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
32
|
+
const scopes = (parsed as Record<string, unknown>).scopes;
|
|
33
|
+
if (!Array.isArray(scopes)) return [];
|
|
34
|
+
return scopes.flatMap((entry) => {
|
|
35
|
+
if (!entry || typeof entry !== "object") return [];
|
|
36
|
+
const pair = entry as Record<string, unknown>;
|
|
37
|
+
return typeof pair.repository === "string" && typeof pair.area === "string"
|
|
38
|
+
? [{ repository: pair.repository, area: pair.area }]
|
|
39
|
+
: [];
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function specFiles(directory: string): Promise<string[]> {
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const nested = await Promise.all(
|
|
54
|
+
entries.map(async (entry) => {
|
|
55
|
+
const full = path.join(directory, entry.name);
|
|
56
|
+
if (entry.isDirectory()) return specFiles(full);
|
|
57
|
+
return entry.isFile() && entry.name.endsWith(".md") ? [full] : [];
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
return nested.flat();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readOrEmpty(file: string): Promise<string> {
|
|
64
|
+
try {
|
|
65
|
+
return await fs.readFile(file, "utf8");
|
|
66
|
+
} catch {
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Repositories a change directory (active or archived) declares in its proposal
|
|
73
|
+
* frontmatter and delta `scopes`, deduplicated case-insensitively, first spelling kept.
|
|
74
|
+
*/
|
|
75
|
+
export async function scopedRepositories(changeDir: string): Promise<string[]> {
|
|
76
|
+
const sources = [
|
|
77
|
+
await readOrEmpty(path.join(changeDir, "proposal.md")),
|
|
78
|
+
...(await Promise.all((await specFiles(path.join(changeDir, "specs"))).map(readOrEmpty))),
|
|
79
|
+
];
|
|
80
|
+
const seen = new Map<string, string>();
|
|
81
|
+
for (const pair of sources.flatMap(parseScopes)) {
|
|
82
|
+
const key = pair.repository.toLowerCase();
|
|
83
|
+
if (!seen.has(key)) seen.set(key, pair.repository);
|
|
84
|
+
}
|
|
85
|
+
return [...seen.values()];
|
|
86
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Reads the operator's `claude` arguments as tokens, for where Mate's appended prompt goes. */
|
|
2
|
+
|
|
3
|
+
export const APPEND_PROMPT_FLAG = "--append-system-prompt";
|
|
4
|
+
|
|
5
|
+
function flagArgs(args: readonly string[]): readonly string[] {
|
|
6
|
+
const terminator = args.indexOf("--");
|
|
7
|
+
return terminator === -1 ? args : args.slice(0, terminator);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The operator's own `--append-system-prompt` values, lifted out of `args`:
|
|
12
|
+
* Claude keeps only the last such flag, so each would replace Mate's Agent
|
|
13
|
+
* Guidance instead of following it. A trailing flag without a value
|
|
14
|
+
* stays, for Claude to reject. `boundary` is where a lifted flag stood before
|
|
15
|
+
* an argument that is no flag: a variadic option such as `--add-dir` before
|
|
16
|
+
* it would otherwise read that argument as its own value, so Mate's flag goes
|
|
17
|
+
* there. Only the first such place is kept: Claude reads one prompt.
|
|
18
|
+
*/
|
|
19
|
+
export function liftAppendedPrompts(args: readonly string[]): {
|
|
20
|
+
args: string[];
|
|
21
|
+
prompts: string[];
|
|
22
|
+
boundary?: number;
|
|
23
|
+
} {
|
|
24
|
+
const flags = flagArgs(args);
|
|
25
|
+
const kept: string[] = [];
|
|
26
|
+
const prompts: string[] = [];
|
|
27
|
+
const liftedAt: number[] = [];
|
|
28
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
29
|
+
const arg = flags[index]!;
|
|
30
|
+
if (arg === APPEND_PROMPT_FLAG && index + 1 < flags.length) {
|
|
31
|
+
prompts.push(flags[index + 1]!);
|
|
32
|
+
liftedAt.push(kept.length);
|
|
33
|
+
index += 1;
|
|
34
|
+
} else if (arg.startsWith(`${APPEND_PROMPT_FLAG}=`)) {
|
|
35
|
+
prompts.push(arg.slice(APPEND_PROMPT_FLAG.length + 1));
|
|
36
|
+
liftedAt.push(kept.length);
|
|
37
|
+
} else {
|
|
38
|
+
kept.push(arg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const passthrough = [...kept, ...args.slice(flags.length)];
|
|
42
|
+
const boundary = liftedAt.find(
|
|
43
|
+
(at) => at > 0 && at < passthrough.length && !passthrough[at]!.startsWith("-"),
|
|
44
|
+
);
|
|
45
|
+
return {
|
|
46
|
+
args: passthrough,
|
|
47
|
+
prompts,
|
|
48
|
+
...(boundary === undefined ? {} : { boundary }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -5,23 +5,26 @@ import path from "node:path";
|
|
|
5
5
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import { buildCompanionGuidance } from "../../../playbooks/companion-guidance";
|
|
7
7
|
import {
|
|
8
|
+
launchPluginFolders,
|
|
8
9
|
planClaudeDelivery,
|
|
9
10
|
type ClaudeDeliveryPlan,
|
|
10
|
-
type SessionKind,
|
|
11
11
|
} from "../../../runtime/claude-delivery-plan";
|
|
12
12
|
import { hookGroupKey, type ClaudeHookGroup } from "../../../runtime/claude-hook-group";
|
|
13
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
14
|
+
import { managedSessionKind } from "../../../runtime/session-kind";
|
|
13
15
|
import { NOT_WRAPPED } from "../../../runtime/wrap-state";
|
|
14
16
|
import { writeAtomic } from "../../../runtime/write-atomic";
|
|
15
17
|
import {
|
|
16
18
|
getCompanionClaudeMcpConfigPath,
|
|
17
19
|
getCompanionClaudeSettingsPath,
|
|
18
20
|
} from "../../../tools/setup/providers/claude";
|
|
21
|
+
import { definesUserClaudeAttribution } from "../../../tools/setup/providers/claude-attribution";
|
|
19
22
|
import type { ClaudeSettings } from "../../../tools/setup/providers/claude-format";
|
|
20
|
-
import { getContextModePackageRoot, validateContextModePackage } from "../../context-mode-package";
|
|
21
23
|
import { getClaudePluginRoot, validateClaudePluginAssets } from "../../package-paths";
|
|
22
24
|
import type { PrepareResult } from "../session-prepare";
|
|
23
25
|
import { LaunchPreflightError } from "../types";
|
|
24
26
|
import { type AdapterContext, LaunchAdapter } from "./base";
|
|
27
|
+
import { APPEND_PROMPT_FLAG, liftAppendedPrompts } from "./claude-args";
|
|
25
28
|
|
|
26
29
|
/** The paths are present only where the plan names that channel and the file exists. */
|
|
27
30
|
interface ClaudeDelivery {
|
|
@@ -33,6 +36,8 @@ interface ClaudeDelivery {
|
|
|
33
36
|
/** A Claude launch's context: the delivery its plan chose, and the files it chose or wrote. */
|
|
34
37
|
interface ClaudeLaunchContext extends AdapterContext {
|
|
35
38
|
delivery: ClaudeDelivery;
|
|
39
|
+
/** The Capability plugin folders this launch's own prepare gathered, in order. */
|
|
40
|
+
pluginDirectories: readonly string[];
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/** Where a launch with a Working Repository writes its derived `--settings`; git-ignored. */
|
|
@@ -44,12 +49,15 @@ export function launchSettingsPath(companionPath: string, repositoryId: string):
|
|
|
44
49
|
/**
|
|
45
50
|
* Companion settings minus `exclude`, so each wrap-carried group runs once. A
|
|
46
51
|
* file, not inline JSON (platform arg limits); per repository, so concurrent
|
|
47
|
-
* launches never race.
|
|
52
|
+
* launches never race. `--settings` outranks every file scope, so `attribution`
|
|
53
|
+
* is dropped when the user or the repository sets its own, even one added
|
|
54
|
+
* after the companion's last sync.
|
|
48
55
|
*/
|
|
49
56
|
async function writeLaunchSettings(
|
|
50
57
|
companionSettingsPath: string,
|
|
51
58
|
target: string,
|
|
52
59
|
exclude: readonly ClaudeHookGroup[],
|
|
60
|
+
repoRoot: string,
|
|
53
61
|
): Promise<void> {
|
|
54
62
|
const settings = JSON.parse(await fs.readFile(companionSettingsPath, "utf8")) as ClaudeSettings;
|
|
55
63
|
const excluded = new Set(exclude.map(hookGroupKey));
|
|
@@ -60,6 +68,9 @@ async function writeLaunchSettings(
|
|
|
60
68
|
}
|
|
61
69
|
const derived: ClaudeSettings = { ...settings, hooks };
|
|
62
70
|
if (Object.keys(hooks).length === 0) delete derived.hooks;
|
|
71
|
+
if (definesUserClaudeAttribution({ repoRoot })) {
|
|
72
|
+
delete derived.attribution;
|
|
73
|
+
}
|
|
63
74
|
|
|
64
75
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
65
76
|
await writeAtomic(target, `${JSON.stringify(derived, null, 2)}\n`);
|
|
@@ -76,7 +87,12 @@ async function settingsPathFor(
|
|
|
76
87
|
if (plan.hooks.owner === "settings") return companionSettingsPath;
|
|
77
88
|
/** Owner `wrap` is planned only for `managed-repo`, which has a repository. */
|
|
78
89
|
const settingsPath = launchSettingsPath(companionPath, repository!.id);
|
|
79
|
-
await writeLaunchSettings(
|
|
90
|
+
await writeLaunchSettings(
|
|
91
|
+
companionSettingsPath,
|
|
92
|
+
settingsPath,
|
|
93
|
+
plan.hooks.launchSettingsExclude,
|
|
94
|
+
repository!.path,
|
|
95
|
+
);
|
|
80
96
|
return settingsPath;
|
|
81
97
|
}
|
|
82
98
|
|
|
@@ -91,7 +107,7 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
|
|
|
91
107
|
*/
|
|
92
108
|
async planLaunch(context: AdapterContext, prepared: PrepareResult): Promise<ClaudeLaunchContext> {
|
|
93
109
|
const { repository, companionPath } = context;
|
|
94
|
-
const kind
|
|
110
|
+
const kind = managedSessionKind(Boolean(repository));
|
|
95
111
|
const wrap = prepared.wrapState ?? NOT_WRAPPED;
|
|
96
112
|
if (repository && !wrap.runtimes.has("claude")) {
|
|
97
113
|
throw new LaunchPreflightError(
|
|
@@ -106,7 +122,7 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
|
|
|
106
122
|
...(plan.mcp === "mcp-config" && existsSync(mcpConfigPath) ? { mcpConfigPath } : {}),
|
|
107
123
|
...(settingsPath ? { settingsPath } : {}),
|
|
108
124
|
};
|
|
109
|
-
return { ...context, delivery };
|
|
125
|
+
return { ...context, delivery, pluginDirectories: prepared.claudePluginDirectories ?? [] };
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
/** A failed Claude wrap pass names the entry that failed. */
|
|
@@ -121,7 +137,7 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
|
|
|
121
137
|
return super.launchFailure(prepared);
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
async validateLaunch(
|
|
140
|
+
async validateLaunch(_context: ClaudeLaunchContext): Promise<void> {
|
|
125
141
|
/** The bundled plugin carries the artifact-path guard; never launch without it. */
|
|
126
142
|
try {
|
|
127
143
|
validateClaudePluginAssets();
|
|
@@ -131,22 +147,14 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
|
|
|
131
147
|
{ cause: error },
|
|
132
148
|
);
|
|
133
149
|
}
|
|
134
|
-
|
|
135
|
-
if (context.capabilities.some((capability) => capability.name === "context-mode")) {
|
|
136
|
-
try {
|
|
137
|
-
await validateContextModePackage(context.companionPath);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
throw new Error(
|
|
140
|
-
`Claude context-mode plugin is unavailable: ${(error as Error).message} Repair it with \`mate companion setup\`.`,
|
|
141
|
-
{ cause: error },
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
/**
|
|
148
|
-
* Plan in, flags out; decides nothing. `--append-system-prompt
|
|
149
|
-
*
|
|
153
|
+
* Plan in, flags out; decides nothing. `--append-system-prompt` is
|
|
154
|
+
* unconditional: no settings document can carry it. It holds the Agent
|
|
155
|
+
* Guidance alone; the Companion Instructions come from the wrap's session hook.
|
|
156
|
+
* `--plugin-dir` passes the folders `launchPluginFolders` names, from the
|
|
157
|
+
* running install's bundled plugin.
|
|
150
158
|
*/
|
|
151
159
|
buildArgs(context: ClaudeLaunchContext, args: string[]): string[] {
|
|
152
160
|
const { plan, settingsPath, mcpConfigPath } = context.delivery;
|
|
@@ -156,31 +164,31 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
|
|
|
156
164
|
? ["--setting-sources", "user,project,local", "--settings", settingsPath]
|
|
157
165
|
: [];
|
|
158
166
|
const addDirArgs = plan.dirAccess === "add-dir" ? ["--add-dir", context.companionPath] : [];
|
|
167
|
+
const pluginDirArgs = launchPluginFolders(
|
|
168
|
+
plan,
|
|
169
|
+
context.pluginDirectories,
|
|
170
|
+
getClaudePluginRoot(),
|
|
171
|
+
).flatMap((dir) => ["--plugin-dir", dir]);
|
|
159
172
|
const mcpConfigArgs =
|
|
160
173
|
plan.mcp === "mcp-config" && mcpConfigPath ? ["--mcp-config", mcpConfigPath] : [];
|
|
161
|
-
const contextModeArgs = context.capabilities.some(
|
|
162
|
-
(capability) => capability.name === "context-mode",
|
|
163
|
-
)
|
|
164
|
-
? ["--plugin-dir", getContextModePackageRoot(context.companionPath)]
|
|
165
|
-
: [];
|
|
166
174
|
|
|
175
|
+
const guidance = buildCompanionGuidance({
|
|
176
|
+
...context,
|
|
177
|
+
secretGuardrails: resolveSecretPolicy(context.companionPath).guardrails,
|
|
178
|
+
});
|
|
179
|
+
const { args: passthrough, prompts, boundary } = liftAppendedPrompts(args);
|
|
180
|
+
const promptArgs = [APPEND_PROMPT_FLAG, [guidance, ...prompts].filter(Boolean).join("\n\n")];
|
|
181
|
+
const launchArgs = [...settingsArgs, ...mcpConfigArgs, ...pluginDirArgs];
|
|
182
|
+
|
|
183
|
+
if (boundary === undefined) {
|
|
184
|
+
return [...addDirArgs, ...promptArgs, ...launchArgs, ...passthrough];
|
|
185
|
+
}
|
|
167
186
|
return [
|
|
168
187
|
...addDirArgs,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
...
|
|
172
|
-
...
|
|
173
|
-
/** The bundled plugin from the running install, beside context-mode's. */
|
|
174
|
-
"--plugin-dir",
|
|
175
|
-
getClaudePluginRoot(),
|
|
176
|
-
...contextModeArgs,
|
|
177
|
-
...args,
|
|
188
|
+
...launchArgs,
|
|
189
|
+
...passthrough.slice(0, boundary),
|
|
190
|
+
...promptArgs,
|
|
191
|
+
...passthrough.slice(boundary),
|
|
178
192
|
];
|
|
179
193
|
}
|
|
180
|
-
|
|
181
|
-
extendEnvironment(_context: ClaudeLaunchContext): NodeJS.ProcessEnv {
|
|
182
|
-
return {
|
|
183
|
-
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1",
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
194
|
}
|
|
@@ -6,6 +6,7 @@ const path = await import("node:path");
|
|
|
6
6
|
|
|
7
7
|
import { MATE_ENV } from "../../../runtime/env";
|
|
8
8
|
import { validateGuidanceData, type MateGuidanceFile } from "../../../runtime/guidance";
|
|
9
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
9
10
|
|
|
10
11
|
import { validateOpenCodePluginAssets } from "../../package-paths";
|
|
11
12
|
import { readOpenCodeConfig } from "../../../tools/setup/providers/opencode-format";
|
|
@@ -118,6 +119,7 @@ export class OpenCodeAdapter extends LaunchAdapter {
|
|
|
118
119
|
private buildGuidance(context: AdapterContext): MateGuidanceFile {
|
|
119
120
|
return buildOpenCodeGuidance(context.capabilities, {
|
|
120
121
|
companionScoped: !context.repository,
|
|
122
|
+
secretGuardrails: resolveSecretPolicy(context.companionPath).guardrails,
|
|
121
123
|
});
|
|
122
124
|
}
|
|
123
125
|
|