@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
|
@@ -4,6 +4,8 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
8
|
+
import { DEFAULT_SECRET_DENY_PATTERNS, type SecretPolicy } from "../../../runtime/secret-patterns";
|
|
7
9
|
import { resolveGitInfoExcludePath } from "../git-utils";
|
|
8
10
|
import type { InstallRequirement } from "../install-contract";
|
|
9
11
|
import type { CapabilityPlugin, RuntimeContributionsByRuntime } from "../plugin";
|
|
@@ -368,14 +370,40 @@ export async function isTokensaveIndexed(repoPath: string): Promise<boolean> {
|
|
|
368
370
|
return fileExists(path.join(repoPath, TOKENSAVE_STORE_DIR, TOKENSAVE_STORE_DATABASE_FILE));
|
|
369
371
|
}
|
|
370
372
|
|
|
373
|
+
function secretExclude(pattern: string): string {
|
|
374
|
+
return `**/${pattern.replace(/^\/+/, "")}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Mate owns the excludes derived from the defaults and from the companion's
|
|
379
|
+
* current extra patterns. An extra removed from config is no longer
|
|
380
|
+
* recognizable, so its exclude stays: over-excluding from an index is harmless.
|
|
381
|
+
*/
|
|
382
|
+
function reconcileSecretExcludes(existing: unknown, secrets: SecretPolicy): string[] {
|
|
383
|
+
const owned = new Set([...DEFAULT_SECRET_DENY_PATTERNS, ...secrets.deny].map(secretExclude));
|
|
384
|
+
const kept = (Array.isArray(existing) ? existing : []).filter(
|
|
385
|
+
(entry): entry is string => typeof entry === "string" && !owned.has(entry),
|
|
386
|
+
);
|
|
387
|
+
const managed = secrets.guardrails ? [...new Set(secrets.deny.map(secretExclude))] : [];
|
|
388
|
+
return [...kept, ...managed];
|
|
389
|
+
}
|
|
390
|
+
|
|
371
391
|
/**
|
|
372
392
|
* Reconciles Mate's single-graph branching posture into the working repo's
|
|
373
393
|
* git-excluded `.tokensave/config.json` and deactivates existing branch
|
|
374
394
|
* metadata. Best-effort by design: the file is created by `tokensave init`,
|
|
375
395
|
* so an absent, unreadable, or unparseable config is left untouched rather
|
|
376
396
|
* than treated as an error. Existing branch databases are never removed.
|
|
397
|
+
*
|
|
398
|
+
* With a secret policy, also reconciles the secret-file excludes: Mate's
|
|
399
|
+
* `**\/<pattern>` entries are appended while guardrails are on and removed
|
|
400
|
+
* when they are off, by exact value, so user-authored excludes keep their
|
|
401
|
+
* place. Without one, `exclude` is left as it is.
|
|
377
402
|
*/
|
|
378
|
-
export async function ensureTokensaveBranchingPosture(
|
|
403
|
+
export async function ensureTokensaveBranchingPosture(
|
|
404
|
+
repoPath: string,
|
|
405
|
+
secrets?: SecretPolicy,
|
|
406
|
+
): Promise<void> {
|
|
379
407
|
const configPath = path.join(repoPath, TOKENSAVE_STORE_DIR, TOKENSAVE_STORE_CONFIG_FILE);
|
|
380
408
|
|
|
381
409
|
let raw: string;
|
|
@@ -395,8 +423,13 @@ export async function ensureTokensaveBranchingPosture(repoPath: string): Promise
|
|
|
395
423
|
return;
|
|
396
424
|
}
|
|
397
425
|
|
|
426
|
+
const config = parsed as Record<string, unknown>;
|
|
398
427
|
const next = JSON.stringify(
|
|
399
|
-
{
|
|
428
|
+
{
|
|
429
|
+
...config,
|
|
430
|
+
...TOKENSAVE_OWNED_POSTURE,
|
|
431
|
+
...(secrets ? { exclude: reconcileSecretExcludes(config.exclude, secrets) } : {}),
|
|
432
|
+
},
|
|
400
433
|
null,
|
|
401
434
|
2,
|
|
402
435
|
);
|
|
@@ -539,7 +572,7 @@ export function createTokensavePlugin(): CapabilityPlugin {
|
|
|
539
572
|
) {
|
|
540
573
|
return;
|
|
541
574
|
}
|
|
542
|
-
await ensureTokensaveBranchingPosture(targetPath);
|
|
575
|
+
await ensureTokensaveBranchingPosture(targetPath, resolveSecretPolicy(ctx.companionPath));
|
|
543
576
|
if (ctx.mode === "setup") {
|
|
544
577
|
installTokensaveAgentIntegrations(ctx.activeProviders, targetPath);
|
|
545
578
|
}
|
|
@@ -3,6 +3,8 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
+
import { managedBlockMarkers, managedBlockStartPattern } from "../../runtime/managed-block";
|
|
7
|
+
import { removeEmptiedFile, writeAtomicThroughLink } from "../../runtime/write-atomic";
|
|
6
8
|
import type {
|
|
7
9
|
InstructionsService,
|
|
8
10
|
McpServerDescriptor,
|
|
@@ -63,13 +65,6 @@ async function saveManifest(
|
|
|
63
65
|
// Managed instruction blocks
|
|
64
66
|
// ---------------------------------------------------------------------------
|
|
65
67
|
|
|
66
|
-
function blockMarkers(frameworkName: string, blockKey: string): { start: string; end: string } {
|
|
67
|
-
return {
|
|
68
|
-
start: `<!-- ${frameworkName}:managed:${blockKey} start -->`,
|
|
69
|
-
end: `<!-- ${frameworkName}:managed:${blockKey} end -->`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
68
|
export function instructionBlockKey(pluginId: string, content: string): string {
|
|
74
69
|
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
75
70
|
return `${pluginId}:${hash}`;
|
|
@@ -82,7 +77,7 @@ export async function upsertManagedBlock(
|
|
|
82
77
|
blockKey: string,
|
|
83
78
|
content: string,
|
|
84
79
|
): Promise<void> {
|
|
85
|
-
const { start, end } =
|
|
80
|
+
const { start, end } = managedBlockMarkers(frameworkName, blockKey);
|
|
86
81
|
let existing = "";
|
|
87
82
|
try {
|
|
88
83
|
existing = await fs.readFile(filePath, "utf8");
|
|
@@ -93,7 +88,8 @@ export async function upsertManagedBlock(
|
|
|
93
88
|
if (existing.includes(start)) return;
|
|
94
89
|
const separator = existing.length === 0 || existing.endsWith("\n\n") ? "" : "\n";
|
|
95
90
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
96
|
-
|
|
91
|
+
/** A session-start hook may read the file while another session's prepare rewrites it. */
|
|
92
|
+
await writeAtomicThroughLink(filePath, existing + (existing ? separator : "") + block);
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
/**
|
|
@@ -113,11 +109,7 @@ export async function removeManagedBlocksForPlugin(
|
|
|
113
109
|
} catch {
|
|
114
110
|
return;
|
|
115
111
|
}
|
|
116
|
-
const
|
|
117
|
-
const keyPattern = new RegExp(
|
|
118
|
-
`<!-- ${escape(frameworkName)}:managed:(${escape(pluginId)}:[0-9a-f]+) start -->`,
|
|
119
|
-
"g",
|
|
120
|
-
);
|
|
112
|
+
const keyPattern = managedBlockStartPattern(frameworkName, pluginId);
|
|
121
113
|
const keys = [...existing.matchAll(keyPattern)].map((match) => match[1]);
|
|
122
114
|
for (const key of keys) {
|
|
123
115
|
if (keepKeys.has(key)) continue;
|
|
@@ -131,7 +123,7 @@ async function removeManagedBlock(
|
|
|
131
123
|
frameworkName: string,
|
|
132
124
|
blockKey: string,
|
|
133
125
|
): Promise<void> {
|
|
134
|
-
const { start, end } =
|
|
126
|
+
const { start, end } = managedBlockMarkers(frameworkName, blockKey);
|
|
135
127
|
let existing: string;
|
|
136
128
|
try {
|
|
137
129
|
existing = await fs.readFile(filePath, "utf8");
|
|
@@ -146,10 +138,10 @@ async function removeManagedBlock(
|
|
|
146
138
|
const after = existing.slice(endIdx + end.length).replace(/^\n+/, "\n");
|
|
147
139
|
const next = (before + after).replace(/^\n+/, "");
|
|
148
140
|
if (!next.trim()) {
|
|
149
|
-
await
|
|
141
|
+
await removeEmptiedFile(filePath).catch(() => {});
|
|
150
142
|
return;
|
|
151
143
|
}
|
|
152
|
-
await
|
|
144
|
+
await writeAtomicThroughLink(filePath, next);
|
|
153
145
|
}
|
|
154
146
|
|
|
155
147
|
// ---------------------------------------------------------------------------
|
|
@@ -8,10 +8,13 @@ import { ConfigError, type PluginDeclaration } from "../../../lib/orchestrator/t
|
|
|
8
8
|
|
|
9
9
|
export const AUDIENCE_ENV = "MATE_AUDIENCE";
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Mate 0.18.4 saved the base set under `audiences.base`. That shape is read
|
|
13
|
+
* back for compatibility, never written: the next save uses the root shape.
|
|
14
|
+
*/
|
|
12
15
|
export const BASE_AUDIENCE = "base";
|
|
13
16
|
|
|
14
|
-
/** The
|
|
17
|
+
/** The keys 0.18.4 moved under `audiences.base`; companion-wide keys stayed at the root. */
|
|
15
18
|
export const BASE_AUDIENCE_KEYS = ["capabilities", "plugins", "studio"] as const;
|
|
16
19
|
|
|
17
20
|
export type AudienceResolution =
|
|
@@ -23,13 +26,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
function pluginList(value: unknown): unknown[] {
|
|
26
|
-
if (
|
|
27
|
-
isRecord(value) &&
|
|
28
|
-
isRecord(value.audiences) &&
|
|
29
|
-
Object.hasOwn(value.audiences, BASE_AUDIENCE)
|
|
30
|
-
) {
|
|
31
|
-
return pluginList(normalizeBaseAudience(value));
|
|
32
|
-
}
|
|
33
29
|
return isRecord(value) && Array.isArray(value.plugins) ? value.plugins : [];
|
|
34
30
|
}
|
|
35
31
|
|
|
@@ -91,7 +87,6 @@ export function declaredAudiences(raw: unknown): string[] {
|
|
|
91
87
|
: [];
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
/** `MATE_AUDIENCE=base` selects the base set alone, the same as no audience. */
|
|
95
90
|
export function resolveAudience(raw: unknown, audience: string | null): AudienceResolution {
|
|
96
91
|
if (audience === null || audience === BASE_AUDIENCE) return { ok: true, active: null };
|
|
97
92
|
const declared = declaredAudiences(raw);
|
|
@@ -104,34 +99,7 @@ export function resolveAudience(raw: unknown, audience: string | null): Audience
|
|
|
104
99
|
* its own `policy` and `config`. An unknown audience contributes nothing.
|
|
105
100
|
*/
|
|
106
101
|
export function effectivePluginDeclarations(raw: unknown, audience: string | null): unknown[] {
|
|
107
|
-
return [...pluginList(raw), ...pluginList(audienceEntry(raw, audience))];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** One effective declaration and its source: `null` for base, else the audience name. */
|
|
111
|
-
export interface SourcedDeclaration {
|
|
112
|
-
entry: unknown;
|
|
113
|
-
audience: string | null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** `effectivePluginDeclarations`, each entry marked with the source that declared it. */
|
|
117
|
-
export function effectivePluginDeclarationSources(
|
|
118
|
-
raw: unknown,
|
|
119
|
-
audience: string | null,
|
|
120
|
-
): SourcedDeclaration[] {
|
|
121
|
-
return [
|
|
122
|
-
...pluginList(raw).map((entry) => ({ entry, audience: null })),
|
|
123
|
-
...pluginList(audienceEntry(raw, audience)).map((entry) => ({ entry, audience })),
|
|
124
|
-
];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** The declarations of one audience only, without base. */
|
|
128
|
-
export function audiencePluginDeclarations(raw: unknown, audience: string): unknown[] {
|
|
129
|
-
return pluginList(audienceEntry(raw, audience));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** The raw `audiences.<name>` entry, for fingerprinting what a layer depends on. */
|
|
133
|
-
export function audienceDeclarationEntry(raw: unknown, audience: string): unknown {
|
|
134
|
-
return audienceEntry(raw, audience);
|
|
102
|
+
return [...pluginList(normalizeBaseAudience(raw)), ...pluginList(audienceEntry(raw, audience))];
|
|
135
103
|
}
|
|
136
104
|
|
|
137
105
|
/**
|
|
@@ -140,7 +108,7 @@ export function audienceDeclarationEntry(raw: unknown, audience: string): unknow
|
|
|
140
108
|
* version. Runtime declarations must come from `effectivePluginDeclarations`.
|
|
141
109
|
*/
|
|
142
110
|
export function allPluginDeclarations(raw: unknown): unknown[] {
|
|
143
|
-
const all = [...pluginList(raw)];
|
|
111
|
+
const all = [...pluginList(normalizeBaseAudience(raw))];
|
|
144
112
|
for (const name of declaredAudiences(raw)) {
|
|
145
113
|
all.push(...pluginList(audienceEntry(raw, name)));
|
|
146
114
|
}
|
|
@@ -13,7 +13,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Raw read of the companion's
|
|
16
|
+
* Raw read of the companion's `plugins:` list. Deliberately avoids
|
|
17
17
|
* `ConfigStore.load()` — hydration runs on every invocation and must never
|
|
18
18
|
* create or migrate config files as a side effect.
|
|
19
19
|
*/
|
|
@@ -9,7 +9,7 @@ import type { Plugin } from "../plugin";
|
|
|
9
9
|
import type { PluginRegistry } from "../registry";
|
|
10
10
|
import {
|
|
11
11
|
describeUnknownAudience,
|
|
12
|
-
|
|
12
|
+
effectivePluginDeclarations,
|
|
13
13
|
readAudienceSelection,
|
|
14
14
|
readFrameworkRaw,
|
|
15
15
|
resolveAudience,
|
|
@@ -71,8 +71,7 @@ function activateDeclaredPlugin(plugin: Plugin): Plugin {
|
|
|
71
71
|
/**
|
|
72
72
|
* Registers the companion's effective plugins (base plus the active
|
|
73
73
|
* `MATE_AUDIENCE`) into the active registry — compiled-in plugins first
|
|
74
|
-
* (they are already registered), declared order after.
|
|
75
|
-
* marked with its source, so persisted passes can keep to base. Runs before cap-command detection on every invocation. No-op
|
|
74
|
+
* (they are already registered), declared order after. Runs before cap-command detection on every invocation. No-op
|
|
76
75
|
* without a companion or `plugins:` entries; all diagnostics go to stderr;
|
|
77
76
|
* never throws.
|
|
78
77
|
*/
|
|
@@ -90,12 +89,12 @@ export async function hydrateDynamicPlugins(deps: HydrateDynamicPluginsDeps = {}
|
|
|
90
89
|
if (!audience.ok) {
|
|
91
90
|
warn(`${describeUnknownAudience(audience)}; applying the base configuration only`);
|
|
92
91
|
}
|
|
93
|
-
const entries =
|
|
92
|
+
const entries = effectivePluginDeclarations(raw, audience.ok ? audience.active : null);
|
|
94
93
|
if (entries.length === 0) return;
|
|
95
94
|
|
|
96
95
|
const registry = deps.registry ?? getActiveDistribution().registry;
|
|
97
96
|
|
|
98
|
-
for (const
|
|
97
|
+
for (const entry of entries) {
|
|
99
98
|
const { declaration, error } = validateDeclaration(entry);
|
|
100
99
|
if (!declaration) {
|
|
101
100
|
if (error) warn(error);
|
|
@@ -123,8 +122,6 @@ export async function hydrateDynamicPlugins(deps: HydrateDynamicPluginsDeps = {}
|
|
|
123
122
|
registry.register({
|
|
124
123
|
plugin: activateDeclaredPlugin(result.plugin),
|
|
125
124
|
policy: declaration.policy ?? "optional",
|
|
126
|
-
audience: source,
|
|
127
|
-
packageName: declaration.package,
|
|
128
125
|
});
|
|
129
126
|
hydratedPackages.add(declaration.package);
|
|
130
127
|
}
|
|
@@ -26,45 +26,40 @@ export interface PluginInstallResult {
|
|
|
26
26
|
error?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Runs `npm <command>` in the shared plugin workspace, honoring the user's ambient
|
|
31
|
+
* registry/auth config. `--loglevel=error` keeps npm's own error in stderr for the report.
|
|
32
|
+
*/
|
|
33
|
+
function runNpm(
|
|
34
|
+
workspaceRoot: string,
|
|
35
|
+
command: string,
|
|
36
|
+
args: string[] = [],
|
|
37
|
+
): { ok: boolean; detail?: string } {
|
|
38
|
+
const result = spawnSync(
|
|
39
|
+
"npm",
|
|
40
|
+
[command, "--no-audit", "--no-fund", "--loglevel=error", ...args],
|
|
41
|
+
{ cwd: workspaceRoot, encoding: "utf8" },
|
|
42
|
+
);
|
|
35
43
|
if (result.error || result.status !== 0) {
|
|
36
44
|
return {
|
|
37
45
|
ok: false,
|
|
38
46
|
detail:
|
|
39
47
|
result.error?.message ??
|
|
40
|
-
result.stderr?.trim()
|
|
41
|
-
`npm install exited with ${result.status}`,
|
|
48
|
+
(result.stderr?.trim() || `npm ${command} exited with ${result.status}`),
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
51
|
return { ok: true };
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
/** Runs `npm install` for the shared plugin workspace. */
|
|
55
|
+
const defaultNpmInstall: NpmInstallRunner = (workspaceRoot) => runNpm(workspaceRoot, "install");
|
|
56
|
+
|
|
47
57
|
/** Version literals treated as moving targets: re-resolved via `npm update` on every run. */
|
|
48
58
|
const MOVING_VERSION_TAGS = new Set(["latest", "canary"]);
|
|
49
59
|
|
|
50
60
|
/** Runs `npm update <pkg...>` to force re-resolution of moving-tag-declared plugins every run. */
|
|
51
|
-
|
|
52
|
-
workspaceRoot
|
|
53
|
-
packages: string[],
|
|
54
|
-
): { ok: boolean; detail?: string } {
|
|
55
|
-
const result = spawnSync("npm", ["update", "--no-audit", "--no-fund", "--silent", ...packages], {
|
|
56
|
-
cwd: workspaceRoot,
|
|
57
|
-
encoding: "utf8",
|
|
58
|
-
});
|
|
59
|
-
if (result.error || result.status !== 0) {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
detail:
|
|
63
|
-
result.error?.message ?? result.stderr?.trim() ?? `npm update exited with ${result.status}`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return { ok: true };
|
|
67
|
-
}
|
|
61
|
+
const defaultNpmUpdate: NpmUpdateRunner = (workspaceRoot, packages) =>
|
|
62
|
+
runNpm(workspaceRoot, "update", packages);
|
|
68
63
|
|
|
69
64
|
async function readInstalledVersion(packageRoot: string): Promise<string | null> {
|
|
70
65
|
try {
|
|
@@ -155,14 +150,15 @@ export async function installDeclaredPlugins(
|
|
|
155
150
|
|
|
156
151
|
await writeWorkspaceManifest(workspaceRoot, desired);
|
|
157
152
|
const installOutcome = await runNpmInstall(workspaceRoot);
|
|
153
|
+
// Failure is decided from `ok`, so an empty detail can never read as success.
|
|
158
154
|
const installFailure = installOutcome.ok
|
|
159
155
|
? undefined
|
|
160
|
-
:
|
|
156
|
+
: installOutcome.detail || "npm install failed";
|
|
161
157
|
|
|
162
158
|
let updateFailure: string | undefined;
|
|
163
159
|
if (movingPackages.length > 0) {
|
|
164
160
|
const updateOutcome = await runNpmUpdate(workspaceRoot, movingPackages);
|
|
165
|
-
updateFailure = updateOutcome.ok ? undefined :
|
|
161
|
+
updateFailure = updateOutcome.ok ? undefined : updateOutcome.detail || "npm update failed";
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
const resolvedVersions = await Promise.all(
|
|
@@ -174,7 +170,7 @@ export async function installDeclaredPlugins(
|
|
|
174
170
|
return sorted.map((declaration, index) => {
|
|
175
171
|
const isMoving = MOVING_VERSION_TAGS.has(declaration.version);
|
|
176
172
|
const failure = installFailure ?? (isMoving ? updateFailure : undefined);
|
|
177
|
-
if (failure) {
|
|
173
|
+
if (failure !== undefined) {
|
|
178
174
|
return { package: declaration.package, status: "failed", error: failure };
|
|
179
175
|
}
|
|
180
176
|
const resolvedVersion = resolvedVersions[index];
|