@uniqbit/mate-core 0.15.4-canary.0 → 0.15.4-canary.11

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.
Files changed (94) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +4 -0
  2. package/claude-plugin/hooks/artifact-finish-nudge.mjs +8 -0
  3. package/claude-plugin/hooks/hooks.json +37 -0
  4. package/claude-plugin/hooks/session-banner.mjs +8 -0
  5. package/claude-plugin/hooks/ts-loader.mjs +28 -0
  6. package/claude-plugin/hooks/validate-artifact-path.mjs +8 -0
  7. package/package.json +4 -2
  8. package/src/cli/commands/artifact/finish/openspec.ts +1 -5
  9. package/src/cli/commands/cap/openspec.ts +20 -1
  10. package/src/cli/commands/companion/hub.ts +100 -0
  11. package/src/cli/commands/companion/link.ts +0 -5
  12. package/src/cli/commands/companion/tui.ts +3 -2
  13. package/src/cli/commands/doctor.ts +254 -170
  14. package/src/cli/commands/install.ts +18 -14
  15. package/src/cli/commands/launch/shared.ts +4 -4
  16. package/src/cli/commands/plugin/install.ts +106 -0
  17. package/src/cli/commands/plugin/plugin.ts +17 -0
  18. package/src/cli/commands/setup.ts +4 -6
  19. package/src/cli/commands/update.ts +9 -11
  20. package/src/cli/companion-link-wizard.tsx +22 -78
  21. package/src/cli/main.ts +57 -13
  22. package/src/cli/plugin-commands.ts +4 -4
  23. package/src/cli/repo-list-table.tsx +2 -7
  24. package/src/cli/usage.ts +7 -3
  25. package/src/distribution.ts +3 -5
  26. package/src/framework.ts +4 -36
  27. package/src/hooks/artifact-finish-nudge.ts +212 -0
  28. package/src/hooks/session-banner.ts +25 -0
  29. package/src/hooks/validate-artifact-path.ts +236 -0
  30. package/src/index.ts +4 -0
  31. package/src/lib/context-mode-package.ts +5 -3
  32. package/src/lib/install.ts +31 -43
  33. package/src/lib/orchestrator/adapters/base.ts +7 -8
  34. package/src/lib/orchestrator/adapters/claude.ts +16 -0
  35. package/src/lib/orchestrator/adapters/opencode.ts +10 -60
  36. package/src/lib/orchestrator/companion-hub.ts +402 -0
  37. package/src/lib/orchestrator/companion-store.ts +1 -35
  38. package/src/lib/orchestrator/config-store.ts +94 -7
  39. package/src/lib/orchestrator/editor.ts +4 -20
  40. package/src/lib/orchestrator/framework-context.ts +119 -24
  41. package/src/lib/orchestrator/global-config-store.ts +1 -5
  42. package/src/lib/orchestrator/launcher.ts +3 -9
  43. package/src/lib/orchestrator/migration.ts +0 -23
  44. package/src/lib/orchestrator/opencode-guidance.ts +1 -2
  45. package/src/lib/orchestrator/repo-local-registry.ts +3 -1
  46. package/src/lib/orchestrator/root-context.ts +117 -0
  47. package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
  48. package/src/lib/orchestrator/setup-preflight.ts +5 -18
  49. package/src/lib/orchestrator/types.ts +27 -19
  50. package/src/lib/orchestrator/working-repo-store.ts +9 -3
  51. package/src/lib/package-paths.ts +37 -0
  52. package/src/lib/update-checker.ts +5 -5
  53. package/src/playbooks/companion-guidance.ts +7 -7
  54. package/src/runtime/env.ts +0 -4
  55. package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +8 -8
  56. package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +3 -3
  57. package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +8 -8
  58. package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +3 -3
  59. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +1 -0
  60. package/src/templates/root/TEMPLATE_AGENTS.md +2 -0
  61. package/src/templates/root/TEMPLATE_CLAUDE.md +2 -0
  62. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3609 -0
  63. package/src/tools/setup/capabilities/context-mode.ts +57 -83
  64. package/src/tools/setup/capabilities/graphify-shared.ts +27 -0
  65. package/src/tools/setup/capabilities/graphify.ts +86 -295
  66. package/src/tools/setup/capabilities/openspec.ts +36 -29
  67. package/src/tools/setup/capabilities/react-doctor.ts +37 -37
  68. package/src/tools/setup/capabilities/rtk.ts +4 -1
  69. package/src/tools/setup/capabilities/tokensave-shared.ts +6 -0
  70. package/src/tools/setup/capabilities/tokensave.ts +51 -72
  71. package/src/tools/setup/context-services.ts +29 -0
  72. package/src/tools/setup/dynamic-plugins/host.ts +2 -1
  73. package/src/tools/setup/dynamic-plugins/hydrate.ts +17 -11
  74. package/src/tools/setup/dynamic-plugins/install.ts +27 -24
  75. package/src/tools/setup/dynamic-plugins/loader.ts +2 -10
  76. package/src/tools/setup/dynamic-plugins/paths.ts +3 -3
  77. package/src/tools/setup/dynamic-plugins/registry-hint.ts +14 -0
  78. package/src/tools/setup/engine.ts +67 -1
  79. package/src/tools/setup/mate.ts +6 -29
  80. package/src/tools/setup/plugin.ts +85 -0
  81. package/src/tools/setup/plugins/gitignore.ts +15 -16
  82. package/src/tools/setup/plugins/guidance.ts +2 -6
  83. package/src/tools/setup/policy.ts +4 -7
  84. package/src/tools/setup/providers/agent-file-sections.ts +78 -0
  85. package/src/tools/setup/providers/claude-format.ts +159 -0
  86. package/src/tools/setup/providers/claude.ts +301 -291
  87. package/src/tools/setup/providers/opencode-format.ts +146 -0
  88. package/src/tools/setup/providers/opencode.ts +186 -65
  89. package/src/tools/setup/providers/skill-tree.ts +38 -0
  90. package/src/tools/setup.ts +10 -54
  91. package/src/tui.ts +5 -0
  92. package/src/templates/capabilities/openspec-cap/claude/hooks/mate-artifact-finish.sh +0 -161
  93. package/src/templates/providers/claude/.claude/hooks/mate-session-banner +0 -28
  94. package/src/templates/providers/claude/.claude/hooks/validate-artifact-path +0 -242
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "mate",
3
+ "description": "Mate-managed Claude Code hooks: artifact-path guardrail, session banner, and the OpenSpec archive-finish nudge. Ships inside @uniqbit/mate-core and is loaded per managed launch via --plugin-dir; it is never published separately."
4
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim: hook logic lives in src/hooks/artifact-finish-nudge.ts, loaded
3
+ // through ts-loader.mjs because the installed plugin sits inside node_modules
4
+ // where node's native type stripping is disabled.
5
+ import "./ts-loader.mjs";
6
+
7
+ const { run } = await import("../../src/hooks/artifact-finish-nudge.ts");
8
+ process.exitCode = await run();
@@ -0,0 +1,37 @@
1
+ {
2
+ "description": "Mate hooks — PreToolUse artifact-path guardrail, SessionStart banner, PostToolUse OpenSpec archive-finish nudge (self-gated at runtime)",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Write|Edit|MultiEdit|Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/validate-artifact-path.mjs\""
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "SessionStart": [
16
+ {
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-banner.mjs\""
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "PostToolUse": [
26
+ {
27
+ "matcher": "Bash",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/artifact-finish-nudge.mjs\""
32
+ }
33
+ ]
34
+ }
35
+ ]
36
+ }
37
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim: hook logic lives in src/hooks/session-banner.ts, loaded through
3
+ // ts-loader.mjs because the installed plugin sits inside node_modules where
4
+ // node's native type stripping is disabled.
5
+ import "./ts-loader.mjs";
6
+
7
+ const { run } = await import("../../src/hooks/session-banner.ts");
8
+ process.exitCode = run();
@@ -0,0 +1,28 @@
1
+ // Synchronous module loader that strips TypeScript types itself. Node's
2
+ // built-in .ts loading refuses files under node_modules
3
+ // (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), which is exactly where the
4
+ // published plugin lives — so the hook shims register this loader before
5
+ // importing their src/hooks/*.ts implementation.
6
+ import fs from "node:fs";
7
+ import { registerHooks, stripTypeScriptTypes } from "node:module";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ // stripTypeScriptTypes emits an ExperimentalWarning; hooks must keep stderr
11
+ // clean because Claude Code surfaces it to the user on non-zero exits.
12
+ const emitWarning = process.emitWarning;
13
+ process.emitWarning = (warning, ...args) => {
14
+ if (String(warning).includes("stripTypeScriptTypes")) return;
15
+ emitWarning.call(process, warning, ...args);
16
+ };
17
+
18
+ registerHooks({
19
+ load(url, context, next) {
20
+ if (!url.endsWith(".ts")) return next(url, context);
21
+ const source = fs.readFileSync(fileURLToPath(url), "utf8");
22
+ return {
23
+ format: "module",
24
+ source: stripTypeScriptTypes(source),
25
+ shortCircuit: true,
26
+ };
27
+ },
28
+ });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim: hook logic lives in src/hooks/validate-artifact-path.ts, loaded
3
+ // through ts-loader.mjs because the installed plugin sits inside node_modules
4
+ // where node's native type stripping is disabled.
5
+ import "./ts-loader.mjs";
6
+
7
+ const { run } = await import("../../src/hooks/validate-artifact-path.ts");
8
+ process.exitCode = await run();
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@uniqbit/mate-core",
3
- "version": "0.15.4-canary.0",
3
+ "version": "0.15.4-canary.11",
4
4
  "description": "Core framework and plugin APIs for Mate.",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "src/",
8
8
  "wrappers/",
9
+ "claude-plugin/",
9
10
  "!src/**/*.test.ts",
10
11
  "!src/**/*.test.tsx"
11
12
  ],
@@ -14,7 +15,8 @@
14
15
  ".": "./src/index.ts",
15
16
  "./plugins": "./src/plugins.ts",
16
17
  "./runtime": "./src/runtime/index.ts",
17
- "./opencode": "./src/opencode/index.ts"
18
+ "./opencode": "./src/opencode/index.ts",
19
+ "./tui": "./src/tui.ts"
18
20
  },
19
21
  "publishConfig": {
20
22
  "access": "public",
@@ -61,10 +61,7 @@ async function capSync(context: FinishContext): Promise<boolean> {
61
61
  const previous = process.exitCode;
62
62
  process.exitCode = 0;
63
63
  const previousEnv = new Map(
64
- ["MATE_ARTIFACT_PATH", "MATE_REPO_PATH", "MATE_REPO_ID", "MATE_REPO_PROFILE"].map((key) => [
65
- key,
66
- process.env[key],
67
- ]),
64
+ ["MATE_ARTIFACT_PATH", "MATE_REPO_PATH", "MATE_REPO_ID"].map((key) => [key, process.env[key]]),
68
65
  );
69
66
 
70
67
  // Pass both sides of the companion/working-repo boundary explicitly. Capability
@@ -73,7 +70,6 @@ async function capSync(context: FinishContext): Promise<boolean> {
73
70
  if (context.repository) {
74
71
  process.env.MATE_REPO_PATH = context.repository.path;
75
72
  process.env.MATE_REPO_ID = context.repository.id;
76
- process.env.MATE_REPO_PROFILE = context.repository.profile;
77
73
  }
78
74
  try {
79
75
  await runIndexCapCommand([]);
@@ -79,7 +79,26 @@ export async function runOpenspecCapCommand(
79
79
  readOpenSpecProjectSchema(companionPath),
80
80
  );
81
81
 
82
- const result = spawnSync("openspec", forwardedArgs, { cwd, stdio: "inherit" });
82
+ if (forwardedArgs[0] === "init" || forwardedArgs[0] === "update") {
83
+ const reset = spawnSync("openspec", ["config", "reset", "--all", "-y"], {
84
+ cwd,
85
+ stdio: "inherit",
86
+ });
87
+ if (reset.error) {
88
+ process.stderr.write(`Failed to reset openspec config: ${reset.error.message}\n`);
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+ if (reset.status !== 0) {
93
+ process.exitCode = reset.status ?? 1;
94
+ return;
95
+ }
96
+ }
97
+
98
+ const result = spawnSync("openspec", forwardedArgs, {
99
+ cwd,
100
+ stdio: "inherit",
101
+ });
83
102
  if (result.error) {
84
103
  process.stderr.write(`Failed to run openspec: ${result.error.message}\n`);
85
104
  process.exitCode = 1;
@@ -0,0 +1,100 @@
1
+ import readline from "node:readline/promises";
2
+
3
+ import {
4
+ addHubMember,
5
+ discoverHubSource,
6
+ initializeCompanionHub,
7
+ syncHub,
8
+ updateHubPlugins,
9
+ } from "../../../lib/orchestrator/companion-hub";
10
+ import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
11
+
12
+ function positionalArgs(argv: string[]): string[] {
13
+ const values: string[] = [];
14
+ const valueFlags = new Set(["id", "path", "url", "companion", "ref"]);
15
+ for (let index = 0; index < argv.length; index += 1) {
16
+ const value = argv[index]!;
17
+ if (!value.startsWith("--")) {
18
+ values.push(value);
19
+ continue;
20
+ }
21
+ if (valueFlags.has(value.slice(2))) index += 1;
22
+ }
23
+ return values;
24
+ }
25
+
26
+ function flagValue(argv: string[], name: string): string | undefined {
27
+ const index = argv.indexOf(`--${name}`);
28
+ return index >= 0 ? argv[index + 1] : undefined;
29
+ }
30
+
31
+ async function selectRegisteredCompanion(): Promise<string> {
32
+ const companions = await new GlobalConfigStore().list();
33
+ if (companions.length === 0) throw new Error("No registered companions are available.");
34
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
35
+ throw new Error("hub add requires a source path or Git URL outside a TTY.");
36
+ }
37
+ const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
38
+ try {
39
+ console.log("Registered companions:");
40
+ companions.forEach((candidate, index) => console.log(` ${index + 1}. ${candidate}`));
41
+ const answer = await prompt.question("Select a companion: ");
42
+ const selected = companions[Number.parseInt(answer, 10) - 1];
43
+ if (!selected) throw new Error("Invalid companion selection.");
44
+ return selected;
45
+ } finally {
46
+ prompt.close();
47
+ }
48
+ }
49
+
50
+ async function runHubInit(argv: string[]): Promise<void> {
51
+ const folder = positionalArgs(argv)[0] ?? process.cwd();
52
+ const hubPath = await initializeCompanionHub(folder);
53
+ await updateHubPlugins(hubPath);
54
+ console.log(`Initialized companion hub: ${hubPath}`);
55
+ }
56
+
57
+ async function runHubAdd(argv: string[]): Promise<void> {
58
+ const sourceArg =
59
+ positionalArgs(argv)[0] ?? flagValue(argv, "url") ?? flagValue(argv, "companion");
60
+ const source = discoverHubSource(sourceArg ?? (await selectRegisteredCompanion()));
61
+ const ref = flagValue(argv, "ref");
62
+ if (ref && source.kind === "git") source.ref = ref;
63
+ const member = await addHubMember(process.cwd(), source, {
64
+ id: flagValue(argv, "id"),
65
+ memberPath: flagValue(argv, "path"),
66
+ });
67
+ console.log(`Added hub member ${member.id}: ${member.path}`);
68
+ }
69
+
70
+ async function runHubSync(argv: string[]): Promise<void> {
71
+ const results = await syncHub(process.cwd());
72
+ const plugins = await updateHubPlugins(process.cwd());
73
+ if (argv.includes("--json")) {
74
+ console.log(JSON.stringify({ companions: results, plugins }));
75
+ return;
76
+ }
77
+ for (const result of results) console.log(`${result.id}: ${result.status} (${result.message})`);
78
+ for (const plugin of plugins) {
79
+ const detail = plugin.status === "failed" ? `: ${plugin.error ?? "failed"}` : "";
80
+ console.log(`hub plugin ${plugin.package}: ${plugin.status}${detail}`);
81
+ }
82
+ if (plugins.some((plugin) => plugin.status === "failed")) process.exitCode = 1;
83
+ }
84
+
85
+ export async function runHubCommand(argv: string[]): Promise<void> {
86
+ const [subcommand, ...rest] = argv;
87
+ switch (subcommand) {
88
+ case "init":
89
+ await runHubInit(rest);
90
+ return;
91
+ case "add":
92
+ await runHubAdd(rest);
93
+ return;
94
+ case "sync":
95
+ await runHubSync(rest);
96
+ return;
97
+ default:
98
+ throw new Error("Usage: mate hub <init|add|sync>");
99
+ }
100
+ }
@@ -10,7 +10,6 @@ import {
10
10
  type CompanionLinkWizardOptions,
11
11
  } from "../../companion-link-wizard";
12
12
  import { extractRepoName } from "../artifact/extract-repo-name";
13
- import { defaultConfig } from "../../../lib/orchestrator/config-store";
14
13
  import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
15
14
  import { CompanionResolver } from "../../../lib/orchestrator/companion-resolver";
16
15
  import { inspectSetupPreflight } from "../../../lib/orchestrator/setup-preflight";
@@ -131,7 +130,6 @@ export async function runCompanionLinkCommandWithDeps(
131
130
 
132
131
  const cwd = path.resolve(process.cwd());
133
132
  const existingMatch = await resolveCompanion(cwd);
134
- const profiles = Object.keys(defaultConfig().profiles);
135
133
  const companionsDir = companionsHomeDir();
136
134
  const candidatePaths = new Set(await listCompanions());
137
135
  if (existingMatch) candidatePaths.add(existingMatch.companionPath);
@@ -148,7 +146,6 @@ export async function runCompanionLinkCommandWithDeps(
148
146
  ).filter((candidate): candidate is string => candidate !== null);
149
147
 
150
148
  const inputs = await selectInputs({
151
- profiles,
152
149
  existingCompanions,
153
150
  });
154
151
  if (!inputs) {
@@ -241,7 +238,6 @@ export async function runCompanionLinkCommandWithDeps(
241
238
  const repository: LinkedRepository = {
242
239
  id: path.basename(cwd),
243
240
  path: cwd,
244
- profile: inputs.profile,
245
241
  };
246
242
 
247
243
  const registerRepository =
@@ -292,6 +288,5 @@ export async function runCompanionLinkCommandWithDeps(
292
288
  console.log(` Companion: ${companionPath}`);
293
289
  }
294
290
  console.log(` Source: ${persistedSource}`);
295
- console.log(` Profile: ${repository.profile}`);
296
291
  console.log(" Workspace: run `mate companion open` to open the workspace in your editor");
297
292
  }
@@ -2,12 +2,14 @@ import { spawn } from "node:child_process";
2
2
  import { accessSync } from "node:fs";
3
3
 
4
4
  import { getActiveDistribution } from "../../../distribution";
5
+ import { FRAMEWORK_NAME } from "../../../framework";
5
6
  import { resolveFrameworkContext } from "../../../lib/orchestrator/framework-context";
6
7
  import { findRepoLocalLinkedRepository } from "../../../lib/orchestrator/repo-local-registry";
7
8
  import { ensureUnambiguousCompanion } from "../launch/shared";
8
9
 
9
10
  export function renderBanner(repoPath: string, companionPath: string): string {
10
- const { name, version } = getActiveDistribution().config;
11
+ const { version } = getActiveDistribution().config;
12
+ const name = FRAMEWORK_NAME;
11
13
  const lines = [`${name} v${version}`, "", `repo: ${repoPath}`, `${name}: ${companionPath}`];
12
14
  const contentWidth = Math.max(...lines.map((l) => l.length));
13
15
  const pad = (text: string) => `│ ${text.padEnd(contentWidth)} │`;
@@ -79,7 +81,6 @@ export async function runCompanionTuiCommand(): Promise<void> {
79
81
  if (repository) {
80
82
  process.env.MATE_REPO_PATH = repository.path;
81
83
  process.env.MATE_REPO_ID = repository.id;
82
- process.env.MATE_REPO_PROFILE = repository.profile;
83
84
  }
84
85
 
85
86
  console.log(renderBanner(repoPath, companionPath));