@cardor/agent-harness-kit 1.7.2 → 1.7.5

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/README.md CHANGED
@@ -174,7 +174,7 @@ Regenerates `AGENTS.md` and provider-specific files from your `agent-harness-kit
174
174
  ```bash
175
175
  ahk build
176
176
  ahk build --watch # watch mode: rebuilds automatically on config changes
177
- ahk build --sync # sync tools: frontmatter in .claude/agents/*.md to match current permission constants
177
+ ahk build --sync # sync tools: frontmatter in agent files to match current permission constants (claude-code only; no-op for opencode/codex-cli)
178
178
  ```
179
179
 
180
180
  ---
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import * as p from "@clack/prompts";
12
12
  import pc from "picocolors";
13
13
 
14
14
  // src/core/materializer/claude-code.ts
15
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
15
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
16
16
  import { join as join4, resolve as resolve3 } from "path";
17
17
 
18
18
  // src/utils/file.ts
@@ -672,38 +672,6 @@ function appendGitignore(cwd2) {
672
672
  function slugify(title) {
673
673
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
674
674
  }
675
- var AGENT_TOOLS = {
676
- lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
677
- explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
678
- consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
679
- builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
680
- reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
681
- };
682
- async function syncAgentPermissions(cwd2) {
683
- for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
684
- const filePath = join3(cwd2, ".claude", "agents", `${agent}.md`);
685
- if (!existsSync2(filePath)) {
686
- console.log(` ${agent}.md not found \u2014 skipping`);
687
- continue;
688
- }
689
- const content = readFileSync3(filePath, "utf-8");
690
- const updated = content.replace(
691
- /(tools:\n)((?: - [^\n]+\n)*)/m,
692
- (_match, header, toolsSection) => {
693
- const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
694
- const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
695
- const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
696
- return header + nativeSection + mcpSection;
697
- }
698
- );
699
- if (updated === content) {
700
- console.log(` ${agent}.md already in sync`);
701
- } else {
702
- writeFileSync3(filePath, updated, "utf-8");
703
- console.log(` ${agent}.md updated`);
704
- }
705
- }
706
- }
707
675
 
708
676
  // src/core/materializer/claude-code.ts
709
677
  var ClaudeCodeMaterializer = class {
@@ -765,6 +733,38 @@ No tasks in progress.
765
733
  async migrate(config, _to, _cwd) {
766
734
  void config;
767
735
  }
736
+ async syncPermissions(cwd2) {
737
+ const AGENT_TOOLS = {
738
+ lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
739
+ explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
740
+ consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
741
+ builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
742
+ reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
743
+ };
744
+ for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
745
+ const filePath = join4(cwd2, ".claude", "agents", `${agent}.md`);
746
+ if (!existsSync3(filePath)) {
747
+ console.log(` ${agent}.md not found \u2014 skipping`);
748
+ continue;
749
+ }
750
+ const content = readFileSync4(filePath, "utf-8");
751
+ const updated = content.replace(
752
+ /(tools:\n)((?: - [^\n]+\n)*)/m,
753
+ (_match, header, toolsSection) => {
754
+ const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
755
+ const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
756
+ const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
757
+ return header + nativeSection + mcpSection;
758
+ }
759
+ );
760
+ if (updated === content) {
761
+ console.log(` ${agent}.md already in sync`);
762
+ } else {
763
+ writeFileSync4(filePath, updated, "utf-8");
764
+ console.log(` ${agent}.md updated`);
765
+ }
766
+ }
767
+ }
768
768
  };
769
769
 
770
770
  // src/core/materializer/codex-cli.ts
@@ -829,6 +829,9 @@ No tasks in progress.
829
829
  async migrate(config, _to, _cwd) {
830
830
  void config;
831
831
  }
832
+ async syncPermissions(_cwd) {
833
+ console.log(" Permissions sync not needed for codex-cli \u2014 skipping");
834
+ }
832
835
  };
833
836
 
834
837
  // src/core/materializer/opencode.ts
@@ -891,6 +894,9 @@ No tasks in progress.
891
894
  async migrate(config, _to, _cwd) {
892
895
  void config;
893
896
  }
897
+ async syncPermissions(_cwd) {
898
+ console.log(" Permissions sync not needed for opencode \u2014 skipping");
899
+ }
894
900
  };
895
901
 
896
902
  // src/core/materializer/index.ts
@@ -911,7 +917,10 @@ function getMaterializer(provider) {
911
917
  async function runBuild(cwd2, opts) {
912
918
  await buildOnce(cwd2);
913
919
  if (opts.sync) {
914
- await syncAgentPermissions(cwd2);
920
+ p.log.step("Syncing agent permissions...");
921
+ const config = await loadConfig(cwd2);
922
+ const materializer = getMaterializer(config.provider);
923
+ await materializer.syncPermissions(cwd2);
915
924
  }
916
925
  if (opts.watch) {
917
926
  p.log.info(`Watching agent-harness-kit.config.ts for changes...`);
@@ -951,7 +960,7 @@ import pc2 from "picocolors";
951
960
 
952
961
  // src/core/dashboard-server.ts
953
962
  import { watch as watch2 } from "fs";
954
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
963
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
955
964
  import { extname, join as join7 } from "path";
956
965
  import { serve } from "@hono/node-server";
957
966
  import { Hono } from "hono";
@@ -994,7 +1003,7 @@ var MIME = {
994
1003
  ".ttf": "font/ttf"
995
1004
  };
996
1005
  function fileResponse(filePath) {
997
- const content = readFileSync4(filePath);
1006
+ const content = readFileSync5(filePath);
998
1007
  const mime = MIME[extname(filePath)] ?? "application/octet-stream";
999
1008
  return new Response(content, {
1000
1009
  headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
@@ -1978,14 +1987,14 @@ var cliFormWithRetry = async (formFn, schema) => {
1978
1987
  };
1979
1988
 
1980
1989
  // src/commands/init-helpers.ts
1981
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
1990
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
1982
1991
  import { join as join11 } from "path";
1983
1992
  import pc5 from "picocolors";
1984
1993
  function readProjectNameFromPackageJson(cwd2) {
1985
1994
  try {
1986
1995
  const pkgPath2 = join11(cwd2, "package.json");
1987
1996
  if (!existsSync8(pkgPath2)) return null;
1988
- const content = readFileSync5(pkgPath2, "utf8");
1997
+ const content = readFileSync6(pkgPath2, "utf8");
1989
1998
  const pkg2 = JSON.parse(content);
1990
1999
  const name = pkg2?.name;
1991
2000
  if (typeof name === "string" && name.trim()) return name.trim();
@@ -1999,7 +2008,7 @@ function detectConfigExtension(cwd2) {
1999
2008
  if (existsSync8(join11(cwd2, "tsconfig.json"))) return "ts";
2000
2009
  const pkgPath2 = join11(cwd2, "package.json");
2001
2010
  if (!existsSync8(pkgPath2)) return "mjs";
2002
- const pkg2 = JSON.parse(readFileSync5(pkgPath2, "utf8"));
2011
+ const pkg2 = JSON.parse(readFileSync6(pkgPath2, "utf8"));
2003
2012
  if (pkg2?.type === "module") return "mjs";
2004
2013
  } catch {
2005
2014
  }
@@ -2459,7 +2468,7 @@ async function runReset(cwd2, opts) {
2459
2468
  }
2460
2469
 
2461
2470
  // src/core/mcp-server.ts
2462
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync10 } from "fs";
2471
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync, writeFileSync as writeFileSync10 } from "fs";
2463
2472
  import { join as join15, resolve as resolve10 } from "path";
2464
2473
  import { Server } from "@modelcontextprotocol/sdk/server";
2465
2474
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -2469,7 +2478,7 @@ import {
2469
2478
  } from "@modelcontextprotocol/sdk/types.js";
2470
2479
 
2471
2480
  // src/core/permissions-check.ts
2472
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
2481
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
2473
2482
  import { join as join14 } from "path";
2474
2483
  var CANONICAL = {
2475
2484
  lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
@@ -2486,7 +2495,10 @@ function parseToolsFromFrontmatter(content) {
2486
2495
  if (!toolsMatch) return [];
2487
2496
  return toolsMatch[1].split("\n").map((l) => l.trim().replace(/^- /, "")).filter((l) => l.startsWith("mcp__"));
2488
2497
  }
2489
- function checkPermissionsSync(cwd2) {
2498
+ function checkPermissionsSync(cwd2, config) {
2499
+ if (config.provider !== "claude-code") {
2500
+ return { in_sync: true };
2501
+ }
2490
2502
  const agents = {};
2491
2503
  let in_sync = true;
2492
2504
  for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
@@ -2497,7 +2509,7 @@ function checkPermissionsSync(cwd2) {
2497
2509
  in_sync = false;
2498
2510
  continue;
2499
2511
  }
2500
- const content = readFileSync6(filePath, "utf-8");
2512
+ const content = readFileSync7(filePath, "utf-8");
2501
2513
  const installed = parseToolsFromFrontmatter(content);
2502
2514
  const canonical = CANONICAL[agent];
2503
2515
  const missing = canonical.filter((t) => !installed.includes(t));
@@ -2646,7 +2658,10 @@ var TOOLS = [
2646
2658
  inputSchema: {
2647
2659
  type: "object",
2648
2660
  properties: {
2649
- criterionId: { type: "number", description: "The id of the acceptance criterion to mark as met" }
2661
+ criterionId: {
2662
+ type: "number",
2663
+ description: "The id of the acceptance criterion to mark as met"
2664
+ }
2650
2665
  },
2651
2666
  required: ["criterionId"]
2652
2667
  }
@@ -2669,7 +2684,10 @@ var TOOLS = [
2669
2684
  type: "object",
2670
2685
  properties: {
2671
2686
  title: { type: "string", description: "Short human-readable title for the task" },
2672
- slug: { type: "string", description: "URL-safe identifier (lowercase, hyphens). Auto-derived from title if omitted." },
2687
+ slug: {
2688
+ type: "string",
2689
+ description: "URL-safe identifier (lowercase, hyphens). Auto-derived from title if omitted."
2690
+ },
2673
2691
  description: { type: "string", description: "Longer description of the task goal" },
2674
2692
  acceptance: {
2675
2693
  type: "array",
@@ -2687,8 +2705,14 @@ var TOOLS = [
2687
2705
  type: "object",
2688
2706
  properties: {
2689
2707
  actionId: { type: "string", description: "UUID returned by actions.start" },
2690
- toolName: { type: "string", description: "Name of the tool that was called (e.g. Read, Bash, Edit)" },
2691
- argsJson: { type: "string", description: "Optional JSON string of the arguments passed to the tool" },
2708
+ toolName: {
2709
+ type: "string",
2710
+ description: "Name of the tool that was called (e.g. Read, Bash, Edit)"
2711
+ },
2712
+ argsJson: {
2713
+ type: "string",
2714
+ description: "Optional JSON string of the arguments passed to the tool"
2715
+ },
2692
2716
  resultSummary: { type: "string", description: "Optional short summary of the tool result" }
2693
2717
  },
2694
2718
  required: ["actionId", "toolName"]
@@ -2762,7 +2786,7 @@ async function startMcpServer(config, cwd2) {
2762
2786
  const { name, arguments: args } = request.params;
2763
2787
  const a = args ?? {};
2764
2788
  try {
2765
- const result = await dispatch(name, a, db, docsPath, cwd2);
2789
+ const result = await dispatch(name, a, db, docsPath, cwd2, config);
2766
2790
  return result;
2767
2791
  } catch (err) {
2768
2792
  return ok(`Error: ${err instanceof Error ? err.message : String(err)}`, true);
@@ -2771,7 +2795,7 @@ async function startMcpServer(config, cwd2) {
2771
2795
  const transport = new StdioServerTransport();
2772
2796
  await server.connect(transport);
2773
2797
  }
2774
- async function dispatch(name, args, db, docsPath, cwd2) {
2798
+ async function dispatch(name, args, db, docsPath, cwd2, config) {
2775
2799
  switch (name) {
2776
2800
  case "actions.start": {
2777
2801
  const taskId = num(args, "taskId");
@@ -2790,7 +2814,9 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2790
2814
  const actionId = str(args, "actionId");
2791
2815
  const summary = str(args, "summary");
2792
2816
  const action = await db.completeAction(actionId, summary);
2793
- return ok(JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at }));
2817
+ return ok(
2818
+ JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at })
2819
+ );
2794
2820
  }
2795
2821
  case "actions.get": {
2796
2822
  const taskId = num(args, "taskId");
@@ -2873,7 +2899,10 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2873
2899
  const acceptance = args["acceptance"];
2874
2900
  const task2 = await db.getTaskById(id);
2875
2901
  if (!task2) return ok(JSON.stringify({ error: "Task not found", taskId: id }), true);
2876
- await db.updateTask(id, { title, description: description !== void 0 ? description : void 0 });
2902
+ await db.updateTask(id, {
2903
+ title,
2904
+ description: description !== void 0 ? description : void 0
2905
+ });
2877
2906
  if (acceptance !== void 0 && acceptance !== null) {
2878
2907
  await db.updateTaskAcceptance(id, acceptance);
2879
2908
  }
@@ -2891,7 +2920,7 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2891
2920
  return ok(JSON.stringify(task2));
2892
2921
  }
2893
2922
  case "permissions.check": {
2894
- const result = checkPermissionsSync(cwd2);
2923
+ const result = checkPermissionsSync(cwd2, config);
2895
2924
  return ok(JSON.stringify(result, null, 2));
2896
2925
  }
2897
2926
  case "deps.snapshot": {
@@ -2899,7 +2928,7 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2899
2928
  if (!existsSync11(pkgPath2)) {
2900
2929
  return ok("package.json not found in project root", true);
2901
2930
  }
2902
- const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2931
+ const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
2903
2932
  const snapshot = {
2904
2933
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2905
2934
  dependencies: pkg2.dependencies ?? {},
@@ -2908,7 +2937,12 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2908
2937
  const harnessDir = join15(cwd2, ".harness");
2909
2938
  mkdirSync9(harnessDir, { recursive: true });
2910
2939
  writeFileSync10(join15(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
2911
- return ok(JSON.stringify({ message: "Snapshot saved to .harness/deps-lock.json", capturedAt: snapshot.capturedAt }));
2940
+ return ok(
2941
+ JSON.stringify({
2942
+ message: "Snapshot saved to .harness/deps-lock.json",
2943
+ capturedAt: snapshot.capturedAt
2944
+ })
2945
+ );
2912
2946
  }
2913
2947
  case "deps.check": {
2914
2948
  const pkgPath2 = join15(cwd2, "package.json");
@@ -2917,10 +2951,15 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2917
2951
  return ok("package.json not found in project root", true);
2918
2952
  }
2919
2953
  if (!existsSync11(lockPath)) {
2920
- return ok(JSON.stringify({ status: "no-snapshot", message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline." }));
2954
+ return ok(
2955
+ JSON.stringify({
2956
+ status: "no-snapshot",
2957
+ message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline."
2958
+ })
2959
+ );
2921
2960
  }
2922
- const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2923
- const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
2961
+ const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
2962
+ const lock = JSON.parse(readFileSync8(lockPath, "utf8"));
2924
2963
  const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
2925
2964
  const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
2926
2965
  const added = [];
@@ -2930,7 +2969,10 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2930
2969
  if (!(name2 in previous)) {
2931
2970
  added.push(`${name2}@${version}`);
2932
2971
  } else {
2933
- const prevMajor = parseInt(previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
2972
+ const prevMajor = parseInt(
2973
+ previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0",
2974
+ 10
2975
+ );
2934
2976
  const curMajor = parseInt(version.replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
2935
2977
  if (!isNaN(prevMajor) && !isNaN(curMajor) && curMajor > prevMajor) {
2936
2978
  majorBumps.push({ name: name2, from: previous[name2], to: version });
@@ -2944,7 +2986,16 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2944
2986
  }
2945
2987
  const significant = added.length > 0 || removed.length > 0 || majorBumps.length > 0;
2946
2988
  const advisory = significant ? "Significant dependency changes detected. Consider running `pnpx autoskills` (or `npx autoskills` if pnpm is unavailable) to refresh agent skills. Clearing stale skills before re-running is recommended." : "No significant dependency changes detected.";
2947
- return ok(JSON.stringify({ significant, added, removed, majorBumps, advisory, snapshotDate: lock.capturedAt }));
2989
+ return ok(
2990
+ JSON.stringify({
2991
+ significant,
2992
+ added,
2993
+ removed,
2994
+ majorBumps,
2995
+ advisory,
2996
+ snapshotDate: lock.capturedAt
2997
+ })
2998
+ );
2948
2999
  }
2949
3000
  default:
2950
3001
  return ok(`Unknown tool: ${name}`, true);
@@ -2958,12 +3009,16 @@ function searchDocs(docsPath, query, maxResults = 10) {
2958
3009
  for (const file of files) {
2959
3010
  if (results.length >= maxResults) break;
2960
3011
  try {
2961
- const content = readFileSync7(file, "utf8");
3012
+ const content = readFileSync8(file, "utf8");
2962
3013
  const lines = content.split("\n");
2963
3014
  for (let i = 0; i < lines.length; i++) {
2964
3015
  const lower = lines[i].toLowerCase();
2965
3016
  if (terms.every((t) => lower.includes(t))) {
2966
- results.push({ file: file.replace(docsPath + "/", ""), line: i + 1, text: lines[i].trim() });
3017
+ results.push({
3018
+ file: file.replace(docsPath + "/", ""),
3019
+ line: i + 1,
3020
+ text: lines[i].trim()
3021
+ });
2967
3022
  if (results.length >= maxResults) break;
2968
3023
  }
2969
3024
  }
@@ -3013,8 +3068,8 @@ async function runServe(cwd2, opts) {
3013
3068
  }
3014
3069
  process.stderr.write(`[agent-harness-kit] MCP server starting (stdio)
3015
3070
  `);
3016
- const syncResult = checkPermissionsSync(cwd2);
3017
- if (!syncResult.in_sync) {
3071
+ const syncResult = checkPermissionsSync(cwd2, config);
3072
+ if (!syncResult.in_sync && syncResult.agents) {
3018
3073
  const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name, r]) => {
3019
3074
  const parts = [];
3020
3075
  if (r.missing.length) parts.push(`missing: ${r.missing.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
@@ -3103,7 +3158,7 @@ async function runStatus(cwd2, opts) {
3103
3158
  }
3104
3159
 
3105
3160
  // src/commands/sync.ts
3106
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
3161
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
3107
3162
  import { join as join16, resolve as resolve11 } from "path";
3108
3163
  import pc10 from "picocolors";
3109
3164
  async function runSync(cwd2, opts) {
@@ -3129,7 +3184,7 @@ async function syncIn(featureListPath, db, dryRun) {
3129
3184
  }
3130
3185
  let seeds;
3131
3186
  try {
3132
- seeds = JSON.parse(readFileSync8(featureListPath, "utf8"));
3187
+ seeds = JSON.parse(readFileSync9(featureListPath, "utf8"));
3133
3188
  } catch (err) {
3134
3189
  console.error(pc10.red(`Failed to parse feature_list.json: ${err}`));
3135
3190
  process.exit(1);