@akagilnc/pi-workflow-roles 0.1.5037 → 0.1.5066

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 (83) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/THIRD_PARTY_NOTICES.md +11 -23
  4. package/dist/acp-host/production-host.js +410 -878
  5. package/dist/headless-host/description.js +0 -2
  6. package/dist/headless-host/production-host.js +413 -914
  7. package/dist/host-descriptions.js +2 -3
  8. package/dist/migrate-book-topology.js +4 -10
  9. package/dist/packaged-role-registry.js +17 -16
  10. package/dist/pi/role-turn-host.js +3 -27
  11. package/dist/public-cli/cli.js +6 -0
  12. package/dist/public-cli/instruction-seat-run.js +12 -40
  13. package/dist/public-cli/machine-method-skills.js +150 -0
  14. package/dist/public-cli/main.js +378 -579
  15. package/dist/public-cli/post-admission.js +2 -0
  16. package/dist/public-cli/registry.js +2 -1
  17. package/dist/public-cli/settlement.js +12 -99
  18. package/dist/public-cli/turn-request.js +16 -8
  19. package/package.json +3 -1
  20. package/resources/836-deleted-machine-instruction-inventory.md +1 -1
  21. package/scripts/build-package.mjs +1 -6
  22. package/src/headless-host/description.ts +0 -3
  23. package/src/headless-host/role-turn-host.ts +2 -23
  24. package/src/host-contracts.ts +2 -23
  25. package/src/host-descriptions.ts +2 -3
  26. package/src/packaged-role-registry.ts +14 -14
  27. package/src/pi/adapter.ts +0 -27
  28. package/src/pi/role-turn-host.ts +3 -31
  29. package/src/public-cli/cli.ts +6 -0
  30. package/src/public-cli/instruction-seat-run.ts +9 -42
  31. package/src/public-cli/machine-method-skills.ts +153 -0
  32. package/src/public-cli/post-admission.ts +2 -0
  33. package/src/public-cli/registry.ts +1 -0
  34. package/src/public-cli/settlement.ts +6 -138
  35. package/src/public-cli/turn-request.ts +24 -16
  36. package/src/reviewer-role.ts +1 -12
  37. package/src/role-envelope.ts +0 -13
  38. package/src/role-runtime-dependencies.ts +0 -13
  39. package/src/role-runtime.ts +4 -56
  40. package/src/worker-role.ts +1 -81
  41. package/dist/method-host-plugin/.claude-plugin/plugin.json +0 -5
  42. package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +0 -48
  43. package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +0 -21
  44. package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +0 -170
  45. package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
  46. package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
  47. package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +0 -41
  48. package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +0 -134
  49. package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  50. package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +0 -31
  51. package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  52. package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +0 -14
  53. package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  54. package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +0 -26
  55. package/dist/method-host-plugin/skills/tdd/SKILL.md +0 -38
  56. package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +0 -3
  57. package/dist/method-host-plugin/skills/tdd/mocking.md +0 -59
  58. package/dist/method-host-plugin/skills/tdd/provenance.json +0 -36
  59. package/dist/method-host-plugin/skills/tdd/tests.md +0 -77
  60. package/dist/package-resources/method-skill.js +0 -259
  61. package/resources/method-host-plugin/.claude-plugin/plugin.json +0 -5
  62. package/resources/methods/ak-cross-m-review/CONTEXT.md +0 -48
  63. package/resources/methods/ak-cross-m-review/LICENSE +0 -21
  64. package/resources/methods/ak-cross-m-review/SKILL.md +0 -170
  65. package/resources/methods/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
  66. package/resources/methods/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
  67. package/resources/methods/ak-cross-m-review/provenance.json +0 -41
  68. package/resources/methods/diagnosing-bugs/SKILL.md +0 -134
  69. package/resources/methods/diagnosing-bugs/agents/openai.yaml +0 -3
  70. package/resources/methods/diagnosing-bugs/provenance.json +0 -31
  71. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  72. package/resources/methods/resolving-merge-conflicts/SKILL.md +0 -14
  73. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +0 -3
  74. package/resources/methods/resolving-merge-conflicts/provenance.json +0 -26
  75. package/resources/methods/tdd/SKILL.md +0 -38
  76. package/resources/methods/tdd/agents/openai.yaml +0 -3
  77. package/resources/methods/tdd/mocking.md +0 -59
  78. package/resources/methods/tdd/provenance.json +0 -36
  79. package/resources/methods/tdd/tests.md +0 -77
  80. package/src/canonical-skill-binding.ts +0 -118
  81. package/src/host-native-method.ts +0 -185
  82. package/src/package-resources/method-skill-binding.ts +0 -60
  83. package/src/package-resources/method-skill.ts +0 -363
@@ -41,9 +41,8 @@ export const HOST_DESCRIPTIONS = Object.freeze({
41
41
  * Headless CLI family (#645 / #646). Claude print-mode is the first row;
42
42
  * codex exec (#646) adds another. Protocol-specific argv/parse live in
43
43
  * headless-host helpers (#752 per-host impl).
44
- * Claude fixedArgs: print mode, full permissions. stream-json + verbose: live
45
- * host events for sitian records (#811); result is last line. Forced methods
46
- * ride `--plugin-dir` (#922); operator skill/setting surfaces stay open.
44
+ * Claude fixedArgs: print mode. stream-json + verbose: live
45
+ * host events for sitian records (#811); result is last line.
47
46
  */
48
47
  export const HEADLESS_HOST_DESCRIPTIONS = Object.freeze({
49
48
  "claude": Object.freeze({
@@ -14051,8 +14051,7 @@ var init_packaged_role_registry = __esm({
14051
14051
  { key: "packetPath" },
14052
14052
  { key: "prerequisitesPath", omitUndefined: true },
14053
14053
  { key: "prerequisites" }
14054
- ],
14055
- method: "observed"
14054
+ ]
14056
14055
  },
14057
14056
  boardPlacement: "marshal",
14058
14057
  activationFlags: [
@@ -14078,7 +14077,6 @@ var init_packaged_role_registry = __esm({
14078
14077
  worker: true,
14079
14078
  applyMethod: "tdd",
14080
14079
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
14081
- methodLoadFailureCause: "activation",
14082
14080
  boardPlacement: "coder",
14083
14081
  phases: ["plan", "apply"],
14084
14082
  outputTool: CODER_OUTPUT_TOOL_NAME,
@@ -14089,8 +14087,7 @@ var init_packaged_role_registry = __esm({
14089
14087
  leaves: [
14090
14088
  { key: "phase" },
14091
14089
  { key: "taskPath" }
14092
- ],
14093
- method: "optional"
14090
+ ]
14094
14091
  },
14095
14092
  activationFlags: [
14096
14093
  { field: "taskPath", flag: "ak-coder-task", binds: "input" },
@@ -14131,8 +14128,7 @@ var init_packaged_role_registry = __esm({
14131
14128
  { key: "lens" },
14132
14129
  { key: "authorityRefs", copyArray: true },
14133
14130
  { key: "callerProvenance", callerProvenance: true }
14134
- ],
14135
- method: "observed"
14131
+ ]
14136
14132
  },
14137
14133
  /** Frozen base/lens/authority become the initial prompt; instruction follows. */
14138
14134
  transportPrompt: "skill-args",
@@ -14231,7 +14227,6 @@ var init_packaged_role_registry = __esm({
14231
14227
  methodSkills: ["resolving-merge-conflicts"],
14232
14228
  settleMethod: "resolving-merge-conflicts",
14233
14229
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
14234
- methodLoadFailureCause: "activation",
14235
14230
  phases: [null],
14236
14231
  outputTool: MERGER_OUTPUT_TOOL_NAME,
14237
14232
  settlement: "residual",
@@ -14243,8 +14238,7 @@ var init_packaged_role_registry = __esm({
14243
14238
  leaves: [
14244
14239
  { key: "mergerInputPath" },
14245
14240
  { key: "derived" }
14246
- ],
14247
- method: "observed"
14241
+ ]
14248
14242
  },
14249
14243
  activationFlags: [
14250
14244
  { field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" }
@@ -93,8 +93,7 @@ const PUBLIC_ROLE_RECORDS = [
93
93
  { key: "packetPath" },
94
94
  { key: "prerequisitesPath", omitUndefined: true },
95
95
  { key: "prerequisites" }
96
- ],
97
- method: "observed"
96
+ ]
98
97
  },
99
98
  boardPlacement: "marshal",
100
99
  activationFlags: [
@@ -120,7 +119,6 @@ const PUBLIC_ROLE_RECORDS = [
120
119
  worker: true,
121
120
  applyMethod: "tdd",
122
121
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
123
- methodLoadFailureCause: "activation",
124
122
  boardPlacement: "coder",
125
123
  phases: ["plan", "apply"],
126
124
  outputTool: CODER_OUTPUT_TOOL_NAME,
@@ -131,8 +129,7 @@ const PUBLIC_ROLE_RECORDS = [
131
129
  leaves: [
132
130
  { key: "phase" },
133
131
  { key: "taskPath" }
134
- ],
135
- method: "optional"
132
+ ]
136
133
  },
137
134
  activationFlags: [
138
135
  { field: "taskPath", flag: "ak-coder-task", binds: "input" },
@@ -173,8 +170,7 @@ const PUBLIC_ROLE_RECORDS = [
173
170
  { key: "lens" },
174
171
  { key: "authorityRefs", copyArray: true },
175
172
  { key: "callerProvenance", callerProvenance: true }
176
- ],
177
- method: "observed"
173
+ ]
178
174
  },
179
175
  /** Frozen base/lens/authority become the initial prompt; instruction follows. */
180
176
  transportPrompt: "skill-args",
@@ -273,7 +269,6 @@ const PUBLIC_ROLE_RECORDS = [
273
269
  methodSkills: ["resolving-merge-conflicts"],
274
270
  settleMethod: "resolving-merge-conflicts",
275
271
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
276
- methodLoadFailureCause: "activation",
277
272
  phases: [null],
278
273
  outputTool: MERGER_OUTPUT_TOOL_NAME,
279
274
  settlement: "residual",
@@ -285,8 +280,7 @@ const PUBLIC_ROLE_RECORDS = [
285
280
  leaves: [
286
281
  { key: "mergerInputPath" },
287
282
  { key: "derived" }
288
- ],
289
- method: "observed"
283
+ ]
290
284
  },
291
285
  activationFlags: [
292
286
  { field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" }
@@ -526,6 +520,18 @@ const PACKAGED_ROLE_REGISTRY = PUBLIC_ROLE_RECORDS.map(({ sessionMaterials: _omi
526
520
  function packagedRoleMetadata(role) {
527
521
  return PACKAGED_ROLE_REGISTRY.find((entry) => entry.role === role);
528
522
  }
523
+ function packagedMethodSkillNames() {
524
+ const names = [];
525
+ for (const record of PACKAGED_ROLE_REGISTRY) {
526
+ if ("methodSkills" in record && record.methodSkills !== void 0) {
527
+ for (const name of record.methodSkills) if (!names.includes(name)) names.push(name);
528
+ }
529
+ if ("applyMethod" in record && record.applyMethod !== void 0 && !names.includes(record.applyMethod)) {
530
+ names.push(record.applyMethod);
531
+ }
532
+ }
533
+ return names;
534
+ }
529
535
  function isOfficerReviewSeat(role) {
530
536
  const record = packagedRoleMetadata(role);
531
537
  return record !== void 0 && "reviewOfficer" in record && record.reviewOfficer === true;
@@ -560,11 +566,6 @@ function packagedRolePhaseFlag(role) {
560
566
  function packagedRoleOutputTool(role) {
561
567
  return packagedRoleMetadata(role)?.outputTool;
562
568
  }
563
- function packagedMethodLoadFailureCause(role) {
564
- const record = packagedRoleMetadata(role);
565
- if (record === void 0 || !("methodLoadFailureCause" in record)) return void 0;
566
- return record.methodLoadFailureCause;
567
- }
568
569
  function packagedBindsBoardTicket(role) {
569
570
  const record = packagedRoleMetadata(role);
570
571
  return record?.sameParent === "board-ticket";
@@ -690,7 +691,7 @@ export {
690
691
  packagedEmitsAuthorityRefs,
691
692
  packagedGateStageLabel,
692
693
  packagedGateSummon,
693
- packagedMethodLoadFailureCause,
694
+ packagedMethodSkillNames,
694
695
  packagedModelParent,
695
696
  packagedNavigatorSubject,
696
697
  packagedProjectsTargetBindRejection,
@@ -6,7 +6,7 @@
6
6
  import { execFile, spawn } from "node:child_process";
7
7
  import { constants } from "node:fs";
8
8
  import { access, appendFile, readFile, realpath } from "node:fs/promises";
9
- import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
9
+ import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
12
12
  import { randomUUID } from "node:crypto";
@@ -67,33 +67,11 @@ function buildMethodArgs(methods) {
67
67
  const skillArgs = [];
68
68
  for (const method of methods) {
69
69
  if (method.kind === "skill") {
70
- skillArgs.push("--skill", method.path);
70
+ skillArgs.push("--skill", dirname(method.path));
71
71
  }
72
72
  }
73
73
  return skillArgs;
74
74
  }
75
- /**
76
- * Pi-native `/skill:<name>` for a single forced method (ADR 0082: pi is one adapter
77
- * with no privilege; Pi-only seams stay inside the pi adapter).
78
- * Driven by typed RoleTurnRequest.methods — middle-layer decision, Pi-only syntax.
79
- * Zero/many methods leave the prompt alone (Fixer optional pair stays `--skill` only).
80
- */
81
- export function applyPiNativeSkillInvocation(methods, prompt) {
82
- const skills = methods.filter((method) => method.kind === "skill");
83
- if (skills.length !== 1)
84
- return prompt;
85
- const name = basename(dirname(skills[0].path));
86
- if (name.length === 0)
87
- return prompt;
88
- const token = `/skill:${name}`;
89
- const trimmed = prompt.trimStart();
90
- if (trimmed === token
91
- || trimmed.startsWith(`${token} `)
92
- || trimmed.startsWith(`${token}\n`)) {
93
- return prompt;
94
- }
95
- return prompt.length === 0 ? token : `${token} ${prompt}`;
96
- }
97
75
  /**
98
76
  * Pi last-hop argv after `--no-extensions -e entry` (#819).
99
77
  * Activation flag membership comes from middle-layer projectActivationFlags;
@@ -135,9 +113,7 @@ function piUserDialogueBody(request) {
135
113
  const _exhaustive = request.continuation;
136
114
  return _exhaustive;
137
115
  })();
138
- return request.continuation.kind === "resume"
139
- ? rawPrompt
140
- : applyPiNativeSkillInvocation(request.methods, rawPrompt);
116
+ return rawPrompt;
141
117
  }
142
118
  const execFileAsync = promisify(execFile);
143
119
  async function resolveSelectedPi(command, cwd, env) {
@@ -12,6 +12,7 @@ import { loadHostProvidersTable, projectHostFacingProvider, renderHostProvidersT
12
12
  import { packagedModelParent } from "../packaged-role-registry.js";
13
13
  import { seatModelOnly } from "./registry.js";
14
14
  import { CliUsageError } from "./cli-errors.js";
15
+ import { runMachineSkillSetup } from "./machine-method-skills.js";
15
16
  import { appendPiSessionCustomEntry } from "../pi/role-turn-host.js";
16
17
  import { composeRoleTurnHostAdapters, formatHostSelectionFailure, HostSelectionError, selectRoleTurnHost, } from "./role-turn-host-resolution.js";
17
18
  import { parseAnalystArgv, parsePublicSeatArgv, } from "./invocation.js";
@@ -793,6 +794,11 @@ export async function runAkRole(argv, env) {
793
794
  exitCode: await runConfigCommand(parsed.args, home, env.packageRoot, io),
794
795
  };
795
796
  }
797
+ if (parsed.command === "setup") {
798
+ if (parsed.args.length !== 0)
799
+ throw new CliUsageError("setup takes no arguments");
800
+ return { exitCode: await runMachineSkillSetup(home, io.stdout) };
801
+ }
796
802
  // #724 explicit fresh summons: `ak-role new <role> …` — same role argv, always mint.
797
803
  // Rewrites onto the role command with freshSummons; auto-resume and resume stay intact.
798
804
  if (parsed.command === "new") {
@@ -8,17 +8,17 @@ import { resolve, sep } from "node:path";
8
8
  import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.js";
9
9
  import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.js";
10
10
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
11
- import { loadPackagedMethodSkillMaterial, } from "../package-resources/method-skill.js";
12
- import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedMethodLoadFailureCause, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
11
+ import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
13
12
  import { isAuditorSoulRole } from "../auditor-soul.js";
14
13
  import { CliUsageError } from "./cli-errors.js";
15
14
  import { admitPublicRole, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
16
- import { presentControlledFailure, prepareSummonsResumeMaterials, resolveResumeMethodMaterialAdapters, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
15
+ import { presentControlledFailure, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
17
16
  import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
18
17
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
19
18
  import { presentStructuralRejection, seatKnownFailureResolver, trySettlePublicSeat, } from "./settlement.js";
19
+ import { warnMissingMethodSkills } from "./machine-method-skills.js";
20
20
  import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
21
- import { admittedSeatTurnDetails, packagedSettleSkill, projectRoleTurnRequest, } from "./turn-request.js";
21
+ import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
22
22
  import { invokeCourtDiarist, runCountersignCourtDiaristStation, } from "./countersign-run.js";
23
23
  function roleRecord(role) {
24
24
  const record = packagedRoleMetadata(role);
@@ -29,7 +29,7 @@ function roleRecord(role) {
29
29
  }
30
30
  /** Project any admitted public role onto the host-neutral turn request. */
31
31
  export function buildInstructionSeatTurnRequest(admitted, options) {
32
- return projectRoleTurnRequest(admitted, admittedSeatTurnDetails(admitted, options.packageRoot), options);
32
+ return projectRoleTurnRequest(admitted, admittedSeatTurnDetails(admitted, options.home, options.host), options);
33
33
  }
34
34
  function initialPrompt(admitted, env) {
35
35
  const record = roleRecord(admitted.role);
@@ -58,11 +58,11 @@ async function bindAndRelocateDiarist(admitted, authority, lease) {
58
58
  }
59
59
  await relocateAdmittedRunToTicket(admitted, authority, lease);
60
60
  }
61
- function seatAdapters(admitted, env, material) {
61
+ function seatAdapters(admitted, env) {
62
62
  const record = roleRecord(admitted.role);
63
63
  const present = "presentSettled" in record ? record.presentSettled : "default";
64
64
  return {
65
- trySettle: (seat, authority, scope) => trySettlePublicSeat(seat, authority, scope, material, env.packageRoot),
65
+ trySettle: (seat, authority, scope) => trySettlePublicSeat(seat, authority, scope),
66
66
  ...(present === "always" ? { shouldPresentSettled: () => true } : {}),
67
67
  ...(present === "typed"
68
68
  ? { shouldPresentSettled: (terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome) }
@@ -126,24 +126,8 @@ async function withAuditorSoulEnv(options) {
126
126
  }
127
127
  async function dispatchAdmitted(admitted, env, io) {
128
128
  const record = roleRecord(admitted.role);
129
- let material;
130
- const skill = packagedSettleSkill(admitted);
131
- if (skill !== undefined) {
132
- try {
133
- material = await loadPackagedMethodSkillMaterial(env.packageRoot, skill);
134
- }
135
- catch (error) {
136
- const knownCause = packagedMethodLoadFailureCause(admitted.role);
137
- return await presentControlledFailure(admitted, {
138
- timedOut: false,
139
- code: null,
140
- stderr: "",
141
- thrown: error,
142
- ...(knownCause === undefined ? {} : { knownCause }),
143
- }, seatAdapters(admitted, env), env.principalAuthority, io);
144
- }
145
- }
146
- const adapters = seatAdapters(admitted, env, material);
129
+ await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
130
+ const adapters = seatAdapters(admitted, env);
147
131
  const execute = async (activeEnv) => {
148
132
  const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
149
133
  if (auto) {
@@ -650,21 +634,9 @@ export async function runPublicInstructionSeatResume(request, env, io) {
650
634
  adapters: {
651
635
  trySettle: async () => undefined,
652
636
  },
653
- afterAdmittedLoad: (admitted) => {
654
- const skill = packagedSettleSkill(admitted);
655
- if (skill === undefined) {
656
- return Promise.resolve({ kind: "continue", adapters: seatAdapters(admitted, env) });
657
- }
658
- const knownCause = packagedMethodLoadFailureCause(admitted.role);
659
- return resolveResumeMethodMaterialAdapters({
660
- admitted,
661
- authority: env.principalAuthority,
662
- io,
663
- loadMaterial: () => loadPackagedMethodSkillMaterial(env.packageRoot, skill),
664
- adaptersWith: (material) => seatAdapters(admitted, env, material),
665
- emptyAdapters: seatAdapters(admitted, env),
666
- ...(knownCause === undefined ? {} : { knownCause }),
667
- });
637
+ afterAdmittedLoad: async (admitted) => {
638
+ await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
639
+ return { kind: "continue", adapters: seatAdapters(admitted, env) };
668
640
  },
669
641
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
670
642
  });
@@ -0,0 +1,150 @@
1
+ import { accessSync, constants, statSync } from "node:fs";
2
+ import { lstat, mkdir, symlink } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { HEADLESS_HOST_DESCRIPTIONS, HOST_DESCRIPTIONS } from "../host-descriptions.js";
6
+ import { packagedMethodSkillNames } from "../packaged-role-registry.js";
7
+ /** Install source only. Which names are required comes from the role registry. */
8
+ const SKILL_SOURCE = {
9
+ tdd: "mattpocock/skills",
10
+ "diagnosing-bugs": "mattpocock/skills",
11
+ "resolving-merge-conflicts": "mattpocock/skills",
12
+ "ak-cross-m-review": "Akagilnc/ak-cross-m-review",
13
+ };
14
+ function skillRoot(home) {
15
+ return join(home, ".agents/skills");
16
+ }
17
+ export function installedMethodSkillPath(home, name) {
18
+ return installedSkillPath(skillRoot(home), name);
19
+ }
20
+ function installedSkillPath(root, name) {
21
+ const path = join(root, name, "SKILL.md");
22
+ try {
23
+ if (!statSync(path).isFile())
24
+ return undefined;
25
+ accessSync(path, constants.R_OK);
26
+ return path;
27
+ }
28
+ catch (error) {
29
+ const code = error.code;
30
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP")
31
+ return undefined;
32
+ throw error;
33
+ }
34
+ }
35
+ function linkedSkillRoot(home, host) {
36
+ if (host === "claude" || host === "claude-code") {
37
+ return join(process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"), "skills");
38
+ }
39
+ if (host === "hermes")
40
+ return join(home, ".hermes/skills");
41
+ return undefined;
42
+ }
43
+ async function occupied(path) {
44
+ try {
45
+ await lstat(path);
46
+ return true;
47
+ }
48
+ catch (error) {
49
+ if (error.code !== "ENOENT")
50
+ throw error;
51
+ return false;
52
+ }
53
+ }
54
+ function warnOccupied(path, stdout) {
55
+ stdout(`Warning: ${path} is already occupied; left untouched.\n`);
56
+ }
57
+ export async function warnMissingMethodSkills(home, host, role, skills, stdout) {
58
+ const missing = [];
59
+ const linkedRoot = linkedSkillRoot(home, host);
60
+ for (const skill of skills) {
61
+ if (installedMethodSkillPath(home, skill) === undefined ||
62
+ (linkedRoot !== undefined && installedSkillPath(linkedRoot, skill) === undefined))
63
+ missing.push(skill);
64
+ }
65
+ for (const skill of missing) {
66
+ stdout(`Warning: required machine Skill "${skill}" is missing for ${role}; its method guidance may be unavailable and reduce work quality. Run \`ak-role setup\` to install required Skills.\n`);
67
+ }
68
+ }
69
+ function runSkillsCli(args, home) {
70
+ const result = spawnSync("npx", ["--yes", "skills", ...args], {
71
+ stdio: "inherit",
72
+ env: { ...process.env, HOME: home },
73
+ });
74
+ if (result.error !== undefined)
75
+ throw result.error;
76
+ return result.status === 0;
77
+ }
78
+ function addSkills(source, skills, home) {
79
+ if (skills.length === 0)
80
+ return true;
81
+ return runSkillsCli([
82
+ "add", source,
83
+ ...skills.flatMap((skill) => ["--skill", skill]),
84
+ "--agent", "codex", "--global", "--yes",
85
+ ], home);
86
+ }
87
+ function updateRequiredSkills(home) {
88
+ const names = packagedMethodSkillNames();
89
+ if (names.length === 0)
90
+ return true;
91
+ return runSkillsCli(["update", "-g", "-y", ...names], home);
92
+ }
93
+ /** Explicit user-run setup; delegates acquisition/install to the ecosystem Skills CLI. */
94
+ export async function runMachineSkillSetup(home, stdout) {
95
+ const canonical = skillRoot(home);
96
+ const missingBySource = new Map();
97
+ let unsourced = false;
98
+ for (const name of packagedMethodSkillNames()) {
99
+ const path = join(canonical, name);
100
+ if (await occupied(path)) {
101
+ warnOccupied(path, stdout);
102
+ continue;
103
+ }
104
+ const source = SKILL_SOURCE[name];
105
+ if (source === undefined) {
106
+ unsourced = true;
107
+ stdout(`Warning: required machine Skill "${name}" has no install source; setup did not install it.\n`);
108
+ continue;
109
+ }
110
+ const missing = missingBySource.get(source) ?? [];
111
+ missing.push(name);
112
+ missingBySource.set(source, missing);
113
+ }
114
+ for (const [source, missing] of missingBySource) {
115
+ if (!addSkills(source, missing, home))
116
+ return 1;
117
+ }
118
+ if (unsourced)
119
+ return 1;
120
+ for (const host of ["claude-code", "hermes"]) {
121
+ const root = linkedSkillRoot(home, host);
122
+ const binaryFromHome = host === "claude-code"
123
+ ? HEADLESS_HOST_DESCRIPTIONS.claude.binaryFromHome
124
+ : HOST_DESCRIPTIONS.hermes.binaryFromHome;
125
+ try {
126
+ accessSync(join(home, ...binaryFromHome), constants.X_OK);
127
+ }
128
+ catch (error) {
129
+ if (["ENOENT", "ENOTDIR", "EACCES"].includes(error.code ?? ""))
130
+ continue;
131
+ throw error;
132
+ }
133
+ for (const name of packagedMethodSkillNames()) {
134
+ const target = join(canonical, name);
135
+ if (installedMethodSkillPath(home, name) === undefined)
136
+ continue;
137
+ const link = join(root, name);
138
+ if (await occupied(link)) {
139
+ warnOccupied(link, stdout);
140
+ continue;
141
+ }
142
+ await mkdir(root, { recursive: true });
143
+ await symlink(target, link, "dir");
144
+ }
145
+ }
146
+ if (!updateRequiredSkills(home))
147
+ return 1;
148
+ stdout("Machine method Skills setup finished. Existing same-name Skills were left untouched.\n");
149
+ return 0;
150
+ }