@h-rig/core 0.0.6-alpha.176 → 0.0.6-alpha.177

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.
@@ -20,12 +20,11 @@
20
20
  * NOTE: registries are instantiated here. The validatorRegistry is consumed
21
21
  * by CLI/harness validation and completion verification. Task dispatch and
22
22
  * closeout read/update the configured plugin task source directly, with
23
- * materialized task-config kept only as an explicit compatibility export.
23
+ * materialized compatibility data kept behind plugin-owned seams.
24
24
  */
25
- import type { RepoRegistry, RigConfig } from "@rig/contracts";
25
+ import type { AgentRoleRegistry, RepoRegistry, RigConfig } from "@rig/contracts";
26
26
  import type { PluginHost } from "./plugin-host";
27
27
  import { type TaskFieldRegistry, type TaskSourceRegistry } from "./plugin-host-registries";
28
- import type { AgentRoleRegistry } from "@rig/contracts";
29
28
  import { type ValidatorRegistry } from "./validator-registry";
30
29
  /** All registries populated from the declarative RigPlugin contributions. */
31
30
  export interface PluginHostContext {
@@ -225,7 +225,7 @@ function createPluginHost(plugins) {
225
225
  // packages/core/src/load-config.ts
226
226
  import { existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync as readFileSync2, readdirSync, rmSync, statSync } from "fs";
227
227
  import { isBuiltin } from "module";
228
- import { dirname, isAbsolute, join as join2, relative, resolve } from "path";
228
+ import { basename, dirname, isAbsolute, join as join2, relative, resolve } from "path";
229
229
  import { pathToFileURL } from "url";
230
230
  import { Schema as Schema3 } from "effect";
231
231
  import { RigConfig as RigConfig3 } from "@rig/contracts";
@@ -292,8 +292,11 @@ function parseDeclarativeFile(path) {
292
292
  }
293
293
  function loadDeclarativeConfig(path) {
294
294
  const data = parseDeclarativeFile(path);
295
- const standardSection = data.standard && typeof data.standard === "object" && !Array.isArray(data.standard) ? data.standard : {};
296
- const useStandard = standardSection.enabled !== false;
295
+ const standardSection = data.standard && typeof data.standard === "object" && !Array.isArray(data.standard) ? data.standard : null;
296
+ if (standardSection?.enabled !== true && standardSection?.enabled !== false) {
297
+ throw new Error(`Declarative config ${path} must explicitly set [standard] enabled = true or false.`);
298
+ }
299
+ const useStandard = standardSection.enabled === true;
297
300
  let plugins = [];
298
301
  if (useStandard) {
299
302
  const resolver = getStandardPluginsResolver();
@@ -335,19 +338,27 @@ function packageNameAndSubpath(specifier) {
335
338
  function exportTargetFromEntry(entry) {
336
339
  if (typeof entry === "string")
337
340
  return entry;
338
- if (entry && typeof entry === "object" && !Array.isArray(entry)) {
341
+ if (Array.isArray(entry)) {
342
+ for (const candidate of entry) {
343
+ const target = exportTargetFromEntry(candidate);
344
+ if (target)
345
+ return target;
346
+ }
347
+ return null;
348
+ }
349
+ if (entry && typeof entry === "object") {
339
350
  const conditions = entry;
340
- for (const key of ["bun", "import", "default", "require"]) {
341
- if (typeof conditions[key] === "string")
342
- return conditions[key];
351
+ for (const key of ["bun", "node", "import", "default", "require"]) {
352
+ const target = exportTargetFromEntry(conditions[key]);
353
+ if (target)
354
+ return target;
343
355
  }
344
356
  }
345
357
  return null;
346
358
  }
347
359
  function patternExportTarget(record, subpath) {
348
- for (const [pattern, entry] of Object.entries(record)) {
349
- if (!pattern.includes("*"))
350
- continue;
360
+ const entries = Object.entries(record).filter(([pattern]) => pattern.includes("*")).sort(([a], [b]) => b.replace("*", "").length - a.replace("*", "").length);
361
+ for (const [pattern, entry] of entries) {
351
362
  const [prefix = "", suffix = ""] = pattern.split("*");
352
363
  if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
353
364
  continue;
@@ -372,6 +383,49 @@ function exportTargetFromPackageJson(pkg, subpath) {
372
383
  return target;
373
384
  return subpath === "." && typeof pkg.module === "string" ? pkg.module : subpath === "." && typeof pkg.main === "string" ? pkg.main : null;
374
385
  }
386
+ function patternImportTarget(record, specifier) {
387
+ for (const [pattern, entry] of Object.entries(record)) {
388
+ if (!pattern.includes("*"))
389
+ continue;
390
+ const [prefix = "", suffix = ""] = pattern.split("*");
391
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix))
392
+ continue;
393
+ const replacement = specifier.slice(prefix.length, specifier.length - suffix.length);
394
+ const target = exportTargetFromEntry(entry);
395
+ if (target)
396
+ return target.replace("*", replacement);
397
+ }
398
+ return null;
399
+ }
400
+ function resolvePackagePrivateImport(specifier, importer, projectRoot) {
401
+ if (!specifier.startsWith("#") || !importer || !isAbsolute(importer))
402
+ return null;
403
+ let dir = dirname(importer);
404
+ const stop = resolve(projectRoot);
405
+ while (isWithinDir(dir, stop)) {
406
+ const packageJsonPath = join2(dir, "package.json");
407
+ if (existsSync2(packageJsonPath)) {
408
+ try {
409
+ const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
410
+ const imports = pkg.imports;
411
+ if (imports && typeof imports === "object" && !Array.isArray(imports)) {
412
+ const record = imports;
413
+ const target = exportTargetFromEntry(record[specifier]) ?? patternImportTarget(record, specifier);
414
+ if (target)
415
+ return resolveModulePath(join2(dir, target));
416
+ }
417
+ } catch {
418
+ return null;
419
+ }
420
+ return null;
421
+ }
422
+ const parent = dirname(dir);
423
+ if (parent === dir)
424
+ return null;
425
+ dir = parent;
426
+ }
427
+ return null;
428
+ }
375
429
  function resolvePackageDirFromBunStore(packageName, nodeModulesDir) {
376
430
  const storeDir = join2(nodeModulesDir, ".bun");
377
431
  if (!existsSync2(storeDir))
@@ -446,8 +500,15 @@ function resolvePackageExportFromDir(packageDir, subpath) {
446
500
  if (existsSync2(packageJsonPath)) {
447
501
  try {
448
502
  const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
449
- const target = exportTargetFromPackageJson(pkg, subpath);
450
- if (target) {
503
+ const targets = [
504
+ exportTargetFromPackageJson(pkg, subpath),
505
+ ...subpath === "." ? [typeof pkg.module === "string" ? pkg.module : null, typeof pkg.main === "string" ? pkg.main : null] : []
506
+ ];
507
+ const seen = new Set;
508
+ for (const target of targets) {
509
+ if (!target || seen.has(target))
510
+ continue;
511
+ seen.add(target);
451
512
  const resolved = resolveModulePath(join2(packageDir, target));
452
513
  if (resolved)
453
514
  return resolved;
@@ -535,10 +596,17 @@ async function importConfigViaRuntimeBundleUnserialized(configPath) {
535
596
  }
536
597
  const RUNTIME_ONLY_EXTERNAL_PACKAGES = new Set(["effect", "mupdf", "fastembed", "onnxruntime-node", "markit-ai"]);
537
598
  const configDir = dirname(configPath);
599
+ const configProjectRoot = basename(configDir) === ".rig" ? dirname(configDir) : configDir;
538
600
  const UNRESOLVED_NAMESPACE = "rig-config-unresolved";
601
+ const ABSOLUTE_EXTERNAL_NAMESPACE = "rig-config-absolute-external";
539
602
  const unresolvedLocalPlugin = {
540
603
  name: "rig-config-unresolved-local",
541
604
  setup(build) {
605
+ build.onLoad({ filter: /[\\/]@oh-my-pi[\\/]pi-coding-agent[\\/]src[\\/]export[\\/]html[\\/](?:template\.css|template\.html|template\.js|tool-views\.generated\.js)$/ }, (args) => ({
606
+ loader: "js",
607
+ contents: `export default ${JSON.stringify(readFileSync2(args.path, "utf8"))};
608
+ `
609
+ }));
542
610
  build.onLoad({ filter: /\.(?:html|txt)$/ }, (args) => ({
543
611
  loader: "js",
544
612
  contents: `export default ${JSON.stringify(readFileSync2(args.path, "utf8"))};
@@ -548,32 +616,77 @@ async function importConfigViaRuntimeBundleUnserialized(configPath) {
548
616
  const directFilePath = resolvedFilePath(args.path, configDir);
549
617
  if (directFilePath)
550
618
  return { path: directFilePath };
619
+ if (args.path.startsWith("#")) {
620
+ const packagePrivatePath = resolvePackagePrivateImport(args.path, args.importer, configProjectRoot);
621
+ if (packagePrivatePath)
622
+ return { path: packagePrivatePath };
623
+ try {
624
+ const parent2 = args.importer && isAbsolute(args.importer) ? dirname(args.importer) : configProjectRoot;
625
+ const resolved = bun.resolveSync?.(args.path, parent2);
626
+ const filePath = resolvedFilePath(resolved, configProjectRoot);
627
+ if (filePath)
628
+ return { path: filePath };
629
+ } catch {}
630
+ return;
631
+ }
551
632
  const packageImport = packageNameAndSubpath(args.path);
552
- if (packageImport && (packageImport.packageName.startsWith("@rig/") || RUNTIME_ONLY_EXTERNAL_PACKAGES.has(packageImport.packageName))) {
553
- return { path: args.path, external: true };
633
+ if (packageImport?.packageName === "uhyphen" && packageImport.subpath === ".") {
634
+ const uhyphenPath = resolveProjectPackageImport("uhyphen", configProjectRoot);
635
+ if (uhyphenPath)
636
+ return { path: uhyphenPath };
637
+ }
638
+ if (packageImport && packageImport.packageName !== "uhyphen") {
639
+ try {
640
+ const parent2 = args.importer && isAbsolute(args.importer) ? dirname(args.importer) : configProjectRoot;
641
+ const resolved = bun.resolveSync?.(args.path, parent2);
642
+ const filePath = resolvedFilePath(resolved, configProjectRoot);
643
+ if (filePath)
644
+ return { path: filePath };
645
+ } catch {}
554
646
  }
555
- const projectPackagePath = resolveProjectPackageImport(args.path, configDir);
647
+ const projectPackagePath = packageImport ? resolveProjectPackageImport(args.path, configProjectRoot) : null;
556
648
  if (projectPackagePath)
557
649
  return { path: projectPackagePath };
650
+ if (packageImport && (packageImport.packageName.startsWith("@rig/") || RUNTIME_ONLY_EXTERNAL_PACKAGES.has(packageImport.packageName))) {
651
+ return { path: args.path, external: true };
652
+ }
558
653
  if (/^(?:node|bun):/.test(args.path) || isBuiltin(args.path))
559
654
  return;
560
655
  if (packageImport)
561
656
  return { path: args.path, external: true };
562
- const parentCandidates = args.path.startsWith(".") ? [args.importer && isAbsolute(args.importer) ? dirname(args.importer) : null, configDir].filter((value) => Boolean(value)) : [args.importer && isAbsolute(args.importer) ? dirname(args.importer) : configDir];
657
+ const importerDir = args.importer && isAbsolute(args.importer) ? dirname(args.importer) : null;
658
+ if (args.path.startsWith(".") && importerDir) {
659
+ const fromImporter = resolveModulePath(resolve(importerDir, args.path));
660
+ if (fromImporter)
661
+ return { path: fromImporter };
662
+ if (/[\\/]node_modules[\\/]/.test(args.importer ?? ""))
663
+ return;
664
+ }
665
+ const parentCandidates = args.path.startsWith(".") ? [importerDir, configDir].filter((value) => Boolean(value)) : [importerDir ?? configDir];
563
666
  for (const parent2 of parentCandidates) {
564
- const filePath = resolvedFilePath(resolve(parent2, args.path), configDir);
667
+ const filePath = resolvedFilePath(resolve(parent2, args.path), configProjectRoot);
565
668
  if (filePath)
566
669
  return { path: filePath };
567
670
  }
568
671
  const parent = parentCandidates[0] ?? configDir;
569
672
  try {
570
673
  const resolved = bun.resolveSync?.(args.path, parent) ?? resolve(parent, args.path);
571
- const filePath = resolvedFilePath(resolved, configDir);
674
+ const filePath = resolvedFilePath(resolved, configProjectRoot);
572
675
  if (filePath)
573
676
  return { path: filePath };
574
677
  } catch {}
575
678
  return { path: args.path, namespace: UNRESOLVED_NAMESPACE };
576
679
  });
680
+ build.onLoad({ filter: /.*/, namespace: ABSOLUTE_EXTERNAL_NAMESPACE }, (args) => {
681
+ const href = pathToFileURL(args.path).href;
682
+ return {
683
+ loader: "js",
684
+ contents: `export * from ${JSON.stringify(href)};
685
+ const mod = await import(${JSON.stringify(href)});
686
+ export default (mod && "default" in mod) ? mod.default : mod;
687
+ `
688
+ };
689
+ });
577
690
  build.onLoad({ filter: /.*/, namespace: UNRESOLVED_NAMESPACE }, (args) => ({
578
691
  loader: "js",
579
692
  contents: `module.exports = {};
@@ -585,7 +698,6 @@ throw new Error(${JSON.stringify(`Failed to bundle ${configPath}: Could not reso
585
698
  const result = await bun.build({
586
699
  entrypoints: [configPath],
587
700
  target: "bun",
588
- external: ["effect", "mupdf", "fastembed", "onnxruntime-node", "markit-ai"],
589
701
  format: "esm",
590
702
  throw: false,
591
703
  packages: "bundle",
@@ -596,7 +708,7 @@ throw new Error(${JSON.stringify(`Failed to bundle ${configPath}: Could not reso
596
708
  `);
597
709
  throw new Error(`Failed to bundle ${configPath}: ${detail || "unknown bundler error"}`);
598
710
  }
599
- const bundleParentDir = join2(configDir, ".rig", "tmp");
711
+ const bundleParentDir = join2(configProjectRoot, ".rig", "tmp");
600
712
  mkdirSync(bundleParentDir, { recursive: true });
601
713
  const dir = mkdtempSync(join2(bundleParentDir, "rig-config-bundle-"));
602
714
  try {
@@ -790,8 +902,17 @@ function createTaskFieldRegistry(extensions) {
790
902
  // packages/core/src/plugin-host-context.ts
791
903
  import {
792
904
  MANAGED_REPO_SERVICE_CAPABILITY,
905
+ REPO_CHANGE_SET,
906
+ REPO_NATIVE_GIT,
907
+ RUNTIME_SECRETS,
793
908
  SESSION_ASSET_MATERIALIZER,
794
- TASK_DATA_SERVICE_CAPABILITY,
909
+ SESSION_HOOK_MATERIALIZER,
910
+ TASK_ARTIFACTS,
911
+ TASK_STATE_STORE,
912
+ TASK_TRACKER_READINESS_POLICY,
913
+ TASK_TRACKER_RECONCILIATION_POLICY,
914
+ TASK_TRACKER_REPO_LOCATOR,
915
+ TASK_TRACKER_STATE,
795
916
  TOOL_MATERIALIZER
796
917
  } from "@rig/contracts";
797
918
 
@@ -945,277 +1066,19 @@ function setScopeRules(rules) {
945
1066
  scopeRulesState().rules = rules ?? null;
946
1067
  }
947
1068
 
948
- // packages/core/src/hook-materializer.ts
949
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
950
- import { dirname as dirname2, resolve as resolve4 } from "path";
951
-
952
- // packages/core/src/hook-runtime.ts
953
- import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync, writeSync } from "fs";
954
- import { resolve as resolve3 } from "path";
955
- import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME } from "@rig/contracts";
956
- function bunRuntime() {
957
- const runtime = globalThis;
958
- return runtime.Bun;
959
- }
960
- function normalizeBuildConfig(value) {
961
- if (!value || typeof value !== "object" || Array.isArray(value)) {
962
- return {};
963
- }
964
- return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
965
- }
966
- function readBuildConfigForCoreHooks() {
967
- if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
968
- return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
969
- }
970
- const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
971
- if (!raw) {
972
- return {};
973
- }
974
- try {
975
- return normalizeBuildConfig(JSON.parse(raw));
976
- } catch {
977
- return {};
978
- }
979
- }
980
- var BUILD_CONFIG = readBuildConfigForCoreHooks();
981
- var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
982
- var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
983
- var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
984
- var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
985
- var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
986
- var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
987
- var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
988
- function normalizeExecutablePath(candidate) {
989
- if (!candidate) {
990
- return "";
991
- }
992
- const normalized = resolve3(candidate);
993
- if (!existsSync5(normalized)) {
994
- return "";
995
- }
996
- try {
997
- return realpathSync(normalized);
998
- } catch {
999
- return normalized;
1000
- }
1001
- }
1002
- function looksLikeRuntimeGateway(candidate) {
1003
- const normalized = resolve3(candidate).replace(/\\/g, "/");
1004
- return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
1005
- }
1006
- function resolveBunBinaryPath() {
1007
- const explicit = normalizeExecutablePath(process.env.RIG_BUN_PATH?.trim());
1008
- if (explicit) {
1009
- return explicit;
1010
- }
1011
- const bunWhich = bunRuntime()?.which?.("bun");
1012
- const pathBun = normalizeExecutablePath(bunWhich?.trim());
1013
- if (pathBun && !looksLikeRuntimeGateway(pathBun)) {
1014
- return pathBun;
1015
- }
1016
- const home = process.env.HOME?.trim();
1017
- const fallbackCandidates = [
1018
- home ? resolve3(home, ".bun/bin/bun") : "",
1019
- "/opt/homebrew/bin/bun",
1020
- "/usr/local/bin/bun",
1021
- "/usr/bin/bun"
1022
- ];
1023
- for (const candidate of fallbackCandidates) {
1024
- const normalized = normalizeExecutablePath(candidate);
1025
- if (normalized) {
1026
- return normalized;
1027
- }
1028
- }
1029
- const execPath = normalizeExecutablePath(process.execPath?.trim());
1030
- if (execPath && !looksLikeRuntimeGateway(execPath)) {
1031
- return execPath;
1032
- }
1033
- throw new Error("bun not found in PATH");
1034
- }
1035
- function resolveBunCliInvocation() {
1036
- if (BAKED_BUN_PATH) {
1037
- return {
1038
- command: BAKED_BUN_PATH,
1039
- env: {}
1040
- };
1041
- }
1042
- if (process.env.RIG_BUN_PATH?.trim()) {
1043
- return {
1044
- command: process.env.RIG_BUN_PATH.trim(),
1045
- env: {}
1046
- };
1047
- }
1048
- try {
1049
- const systemBun = resolveBunBinaryPath();
1050
- return {
1051
- command: systemBun,
1052
- env: {}
1053
- };
1054
- } catch {}
1055
- if (process.execPath?.trim()) {
1056
- return {
1057
- command: process.execPath,
1058
- env: { BUN_BE_BUN: "1" }
1059
- };
1060
- }
1061
- return { command: "bun", env: {} };
1062
- }
1063
-
1064
- // packages/core/src/hook-materializer.ts
1065
- var MARKER_PLUGIN = "_rigPlugin";
1066
- var MARKER_HOOK_ID = "_rigHookId";
1067
- function matcherToString(matcher) {
1068
- if (matcher.kind === "all")
1069
- return;
1070
- if (matcher.kind === "tool")
1071
- return matcher.name;
1072
- return matcher.pattern;
1073
- }
1074
- function isPluginOwned(cmd) {
1075
- return typeof cmd[MARKER_PLUGIN] === "string";
1076
- }
1077
- function shellQuote(value) {
1078
- return `'${value.replaceAll("'", `'\\''`)}'`;
1079
- }
1080
- function resolveHookRunnerPath() {
1081
- const sibling = resolve4(import.meta.dirname, "hook-runner.ts");
1082
- if (existsSync6(sibling)) {
1083
- return sibling;
1084
- }
1085
- return "$CLAUDE_PROJECT_DIR/node_modules/@rig/core/src/hook-runner.ts";
1086
- }
1087
- function buildTypedHookShimCommand(pluginName, hook, projectRoot) {
1088
- const runnerPath = resolveHookRunnerPath();
1089
- const runnerArg = runnerPath.startsWith("$CLAUDE_PROJECT_DIR") ? `"${runnerPath}"` : shellQuote(runnerPath);
1090
- const bun = resolveBunCliInvocation();
1091
- const envPrefix = Object.entries(bun.env).map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ");
1092
- const parts = [
1093
- envPrefix,
1094
- shellQuote(bun.command),
1095
- runnerArg,
1096
- "--plugin",
1097
- shellQuote(pluginName),
1098
- "--hook",
1099
- shellQuote(hook.id),
1100
- "--event",
1101
- shellQuote(hook.event),
1102
- "--project-root",
1103
- shellQuote(projectRoot)
1104
- ].filter(Boolean);
1105
- return parts.join(" ");
1106
- }
1107
- function createPiNoopSessionHookAdapter() {
1108
- return {
1109
- id: "pi",
1110
- materialize() {
1111
- return {
1112
- adapterId: "pi",
1113
- status: "skipped",
1114
- reason: "Pi sessions do not consume Claude Code settings hooks."
1115
- };
1116
- }
1117
- };
1118
- }
1119
- function createClaudeCodeSessionHookAdapter() {
1120
- return {
1121
- id: "claude-code",
1122
- materialize(projectRoot, entries) {
1123
- return writeClaudeCodeHookSettings(projectRoot, entries);
1124
- }
1125
- };
1126
- }
1127
- function defaultAgentSessionHookAdapters(env = process.env) {
1128
- if (env.RIG_AGENT_SESSION_HOOK_ADAPTER === "claude-code") {
1129
- return [createClaudeCodeSessionHookAdapter()];
1130
- }
1131
- if (env.RIG_AGENT_SESSION_HOOK_ADAPTER === "pi" || typeof env.PI_CODING_AGENT_SESSION_DIR === "string" && env.PI_CODING_AGENT_SESSION_DIR.trim().length > 0 || env.RIG_RUN_PROCESS === "1") {
1132
- return [createPiNoopSessionHookAdapter()];
1133
- }
1134
- return [createClaudeCodeSessionHookAdapter()];
1135
- }
1136
- function materializeSessionHookAdapters(projectRoot, entries, adapters = [createClaudeCodeSessionHookAdapter()]) {
1137
- return adapters.map((adapter) => adapter.materialize(projectRoot, entries));
1138
- }
1139
- function applyClaudeCodeSessionHooksToSettings(existing, entries, projectRoot, options = {}) {
1140
- const hooks = typeof existing.hooks === "object" && existing.hooks !== null && !Array.isArray(existing.hooks) ? existing.hooks : {};
1141
- const replacePluginOwned = options.replacePluginOwned ?? true;
1142
- if (replacePluginOwned) {
1143
- for (const event of Object.keys(hooks)) {
1144
- const groups = hooks[event] ?? [];
1145
- const cleaned = [];
1146
- for (const group of groups) {
1147
- const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
1148
- if (operatorHooks.length > 0) {
1149
- cleaned.push({ ...group, hooks: operatorHooks });
1150
- }
1151
- }
1152
- if (cleaned.length > 0) {
1153
- hooks[event] = cleaned;
1154
- } else {
1155
- delete hooks[event];
1156
- }
1157
- }
1158
- }
1159
- const materializedEvents = new Set;
1160
- for (const { pluginName, hook, typed } of entries) {
1161
- const command = hook.command ?? (typed ? buildTypedHookShimCommand(pluginName, hook, projectRoot) : undefined);
1162
- if (!command) {
1163
- continue;
1164
- }
1165
- const event = hook.event;
1166
- materializedEvents.add(event);
1167
- const matcherString = matcherToString(hook.matcher);
1168
- const groups = hooks[event] ??= [];
1169
- let group = groups.find((g) => g.matcher === matcherString);
1170
- if (!group) {
1171
- group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
1172
- groups.push(group);
1173
- }
1174
- const alreadyPresent = group.hooks.some((candidate) => candidate[MARKER_PLUGIN] === pluginName && candidate[MARKER_HOOK_ID] === hook.id);
1175
- if (alreadyPresent) {
1176
- continue;
1177
- }
1178
- group.hooks.push({
1179
- type: "command",
1180
- command,
1181
- [MARKER_PLUGIN]: pluginName,
1182
- [MARKER_HOOK_ID]: hook.id
1183
- });
1184
- }
1185
- const next = { ...existing };
1186
- if (Object.keys(hooks).length > 0) {
1187
- next.hooks = hooks;
1188
- } else {
1189
- delete next.hooks;
1190
- }
1191
- return { settings: next, events: [...materializedEvents].sort() };
1192
- }
1193
- function writeClaudeCodeHookSettings(projectRoot, entries) {
1194
- const settingsPath = resolve4(projectRoot, ".claude", "settings.json");
1195
- const existing = existsSync6(settingsPath) ? safeReadJson(settingsPath) : {};
1196
- const { settings, events } = applyClaudeCodeSessionHooksToSettings(existing, entries, projectRoot);
1197
- mkdirSync3(dirname2(settingsPath), { recursive: true });
1198
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
1199
- `, "utf-8");
1200
- return {
1201
- adapterId: "claude-code",
1202
- status: "materialized",
1203
- path: settingsPath,
1204
- events
1205
- };
1206
- }
1207
- function safeReadJson(path) {
1208
- try {
1209
- return JSON.parse(readFileSync4(path, "utf-8"));
1210
- } catch {
1211
- return {};
1212
- }
1213
- }
1214
-
1215
1069
  // packages/core/src/plugin-host-context.ts
1216
1070
  var ManagedRepoCap = defineCapability(MANAGED_REPO_SERVICE_CAPABILITY);
1071
+ var RepoChangeSetCap = defineCapability(REPO_CHANGE_SET);
1072
+ var RepoNativeGitCap = defineCapability(REPO_NATIVE_GIT);
1073
+ var RuntimeSecretsCap = defineCapability(RUNTIME_SECRETS);
1217
1074
  var SessionAssetMaterializerCap = defineCapability(SESSION_ASSET_MATERIALIZER);
1218
- var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
1075
+ var SessionHookMaterializerCap = defineCapability(SESSION_HOOK_MATERIALIZER);
1076
+ var TaskArtifactsCap = defineCapability(TASK_ARTIFACTS);
1077
+ var TaskStateStoreCap = defineCapability(TASK_STATE_STORE);
1078
+ var TaskTrackerReadinessPolicyCap = defineCapability(TASK_TRACKER_READINESS_POLICY);
1079
+ var TaskTrackerReconciliationPolicyCap = defineCapability(TASK_TRACKER_RECONCILIATION_POLICY);
1080
+ var TaskTrackerRepoLocatorCap = defineCapability(TASK_TRACKER_REPO_LOCATOR);
1081
+ var TaskTrackerStateCap = defineCapability(TASK_TRACKER_STATE);
1219
1082
  var ToolMaterializerCap = defineCapability(TOOL_MATERIALIZER);
1220
1083
  async function buildPluginHostContext(projectRoot) {
1221
1084
  let config;
@@ -1238,10 +1101,20 @@ async function buildPluginHostContext(projectRoot) {
1238
1101
  validatorRegistry.register(impl);
1239
1102
  }
1240
1103
  const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost, { projectRoot });
1241
- const taskDataService = await TaskDataCap.resolve(pluginHost);
1242
- if (taskDataService) {
1243
- installCapability(TaskDataCap, taskDataService);
1244
- }
1104
+ async function installIfResolved(capability) {
1105
+ const impl = await capability.resolve(pluginHost);
1106
+ if (impl)
1107
+ installCapability(capability, impl);
1108
+ }
1109
+ await installIfResolved(RepoChangeSetCap);
1110
+ await installIfResolved(RepoNativeGitCap);
1111
+ await installIfResolved(RuntimeSecretsCap);
1112
+ await installIfResolved(TaskArtifactsCap);
1113
+ await installIfResolved(TaskStateStoreCap);
1114
+ await installIfResolved(TaskTrackerReadinessPolicyCap);
1115
+ await installIfResolved(TaskTrackerReconciliationPolicyCap);
1116
+ await installIfResolved(TaskTrackerRepoLocatorCap);
1117
+ await installIfResolved(TaskTrackerStateCap);
1245
1118
  const toolMaterializer = await ToolMaterializerCap.resolve(pluginHost);
1246
1119
  if (toolMaterializer) {
1247
1120
  installCapability(ToolMaterializerCap, toolMaterializer);
@@ -1268,7 +1141,10 @@ async function buildPluginHostContext(projectRoot) {
1268
1141
  typed: Boolean(hook.handler)
1269
1142
  })));
1270
1143
  if (hookEntries.length > 0) {
1271
- materializeSessionHookAdapters(projectRoot, hookEntries, defaultAgentSessionHookAdapters());
1144
+ const sessionHookMaterializer = await SessionHookMaterializerCap.resolve(pluginHost);
1145
+ if (sessionHookMaterializer) {
1146
+ await sessionHookMaterializer.materializeSessionHooks(projectRoot, hookEntries);
1147
+ }
1272
1148
  }
1273
1149
  } catch (err) {
1274
1150
  console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);