@cardor/agent-harness-kit 1.10.1 → 1.10.3

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
@@ -142,7 +142,7 @@ Then run the interactive setup inside your project:
142
142
  npx ahk init
143
143
  ```
144
144
 
145
- > **Local install required.** `ahk` needs to resolve your project's `agent-harness-kit.config.ts` and its dependencies relative to your project's own `node_modules`. A **global-only** install (`npm install -g @cardor/agent-harness-kit`) cannot do this reliably, so every command except `--version`/`--help` will detect a global-only install and exit with an error telling you to run `npm install --save-dev @cardor/agent-harness-kit` (or the `pnpm`/`yarn`/`bun` equivalent) in your project root.
145
+ > **Local install recommended.** The generated `agent-harness-kit.config.ts` uses `import type`, which is erased at compile time, so `ahk` no longer needs to resolve the package from your project's `node_modules` at runtime. If it detects a **global-only** install (`npm install -g @cardor/agent-harness-kit`), it prints a non-blocking warning recommending `npm install --save-dev @cardor/agent-harness-kit` (or the `pnpm`/`yarn`/`bun` equivalent) the command still runs and exits normally either way. This is only about keeping the CLI version pinned and reproducible across your team and CI, not a functional requirement.
146
146
  >
147
147
  > This check also works with **Yarn Berry (PnP)** projects, which never create a `node_modules` folder — `ahk` detects `.pnp.cjs`/`.pnp.loader.mjs` and falls back to checking that the package is declared in `package.json` instead of requiring a `node_modules` entry.
148
148
 
@@ -184,15 +184,7 @@ For Claude Code and Codex CLI (not OpenCode), you'll also be asked whether to pe
184
184
 
185
185
  Regardless of scope, `.harness/storage-state.json` is always written to the project — it records the *actual* current storage state (`scope`, `projectId`, `dbType`, `migratedAt`), separate from the *desired* state declared in the config file.
186
186
 
187
- When `--storage-scope global` is chosen, `ahk init` also synchronizes your provider's agent and skill files into your home directory (in addition to the project-local files it always writes), so subsequent `ahk init --storage-scope global` runs in *other* projects on the same machine can reuse them instead of re-scaffolding:
188
-
189
- | Provider | Global agents dir | Global skills dir |
190
- | ------------- | ---------------------------- | ----------------------------- |
191
- | `claude-code` | `~/.claude/agents/` | `~/.claude/skills/` |
192
- | `codex-cli` | `~/.codex/agents/` | `~/.agents/skills/` (separate namespace from `~/.codex/agents`) |
193
- | `opencode` | `~/.config/opencode/agents/` | `~/.config/opencode/skills/` |
194
-
195
- This sync is idempotent and non-destructive: it only creates files that are missing. If everything is already present, `ahk init` prints a message and skips; if only some files are missing, it creates just those and reports what was added. Files it detects as already customized/outdated are left untouched (same "preserve, don't overwrite" rule used for project-local agent files).
187
+ Agent and skill files always live in the project tree, regardless of storage scope `--storage-scope` only affects where the harness DB lives.
196
188
 
197
189
  ```bash
198
190
  ahk init
@@ -522,9 +514,9 @@ The `tasks` table includes an `updated_at` timestamp column, set on creation and
522
514
  Everything in the config file is yours to change:
523
515
 
524
516
  ```ts
525
- import { defineHarness } from '@cardor/agent-harness-kit'
517
+ import type { HarnessConfig } from '@cardor/agent-harness-kit'
526
518
 
527
- export default defineHarness({
519
+ const config: HarnessConfig = {
528
520
  project: {
529
521
  name: 'My App',
530
522
  description: 'What this project does',
@@ -578,9 +570,13 @@ export default defineHarness({
578
570
  mcp: { enabled: true, port: 3742 },
579
571
  scripts: { enabled: true, outputDir: './.harness/scripts' },
580
572
  },
581
- })
573
+ }
574
+
575
+ export default config
582
576
  ```
583
577
 
578
+ > `defineHarness()` is still exported for anyone who prefers the value-import form (`import { defineHarness } from '@cardor/agent-harness-kit'` + `export default defineHarness({ ... })`) — it's an identity function kept for backward compatibility, and `loadConfig()` supports both shapes.
579
+
584
580
  ### `health.sh`
585
581
 
586
582
  This is the most important file to implement. Agents will not start or close tasks until this script exits 0. Examples:
package/dist/cli.js CHANGED
@@ -530,12 +530,9 @@ If orchestrating: Agent definition files in .claude/agents/
530
530
  function modelField(model) {
531
531
  return model ? `, model: ${JSON.stringify(model)}` : "";
532
532
  }
533
- function configTs(params) {
533
+ function configObjectBody(params) {
534
534
  const models = params.models ?? {};
535
- return `import { defineHarness } from '@cardor/agent-harness-kit'
536
-
537
- export default defineHarness({
538
- project: {
535
+ return ` project: {
539
536
  name: ${JSON.stringify(params.name)},
540
537
  description: ${JSON.stringify(params.description)},
541
538
  docsPath: '${params.docsPath}',
@@ -583,64 +580,29 @@ export default defineHarness({
583
580
  mcp: { enabled: true, port: ${params.port} },
584
581
  scripts: { enabled: true, outputDir: './.harness/scripts' },
585
582
  },
586
- })
587
583
  `;
588
584
  }
589
- var configMjs = configTs;
590
- function configCjs(params) {
591
- const models = params.models ?? {};
592
- return `const { defineHarness } = require('@cardor/agent-harness-kit')
593
-
594
- module.exports = defineHarness({
595
- project: {
596
- name: ${JSON.stringify(params.name)},
597
- description: ${JSON.stringify(params.description)},
598
- docsPath: '${params.docsPath}',
599
- },
600
-
601
- provider: '${params.provider}',
602
-
603
- agents: {
604
- lead: { instructionsPath: null${modelField(models.lead)} },
605
- explorer: { instructionsPath: null, allowedPaths: ['${params.docsPath}', './src']${modelField(models.explorer)} },
606
- builder: { instructionsPath: null, writablePaths: ['./src', './tests']${modelField(models.builder)} },
607
- reviewer: { instructionsPath: null${modelField(models.reviewer)} },
608
- ${models.consultant ? `consultant: { instructionsPath: null${modelField(models.consultant)} },
609
- ` : ""}custom: [],
610
- },
585
+ function configTs(params) {
586
+ return `import type { HarnessConfig } from '@cardor/agent-harness-kit'
611
587
 
612
- // SQLite (default). Switch to postgres/mysql by changing database.type.
613
- // database: { type: 'postgres', connectionString: process.env.DATABASE_URL },
614
- // database: { type: 'mysql', connectionString: process.env.DATABASE_URL },
615
- database: { type: 'sqlite', path: '.harness/harness.db' },
588
+ const config: HarnessConfig = {
589
+ ${configObjectBody(params)}}
616
590
 
617
- storage: {
618
- dir: '.harness',
619
- tasks: { adapter: '${params.tasksAdapter}' },
620
- sections: {
621
- toolsUsed: true,
622
- filesModified: true,
623
- result: true,
624
- blockers: true,
625
- nextSteps: false,
626
- },
627
- markdownFallback: { enabled: true, path: '.harness/current.md' },
628
- // 'local' \u2014 DB lives in .harness/ (project-relative). 'global' \u2014 DB lives
629
- // under ~/.harness/dbs/<projectId>/, outside the project tree.
630
- scope: '${params.scope}',
631
- projectId: '${params.projectId}',
632
- },
591
+ export default config
592
+ `;
593
+ }
594
+ function configMjs(params) {
595
+ return `const config = {
596
+ ${configObjectBody(params)}}
633
597
 
634
- health: {
635
- scriptPath: './health.sh',
636
- required: true,
637
- },
598
+ export default config
599
+ `;
600
+ }
601
+ function configCjs(params) {
602
+ return `const config = {
603
+ ${configObjectBody(params)}}
638
604
 
639
- tools: {
640
- mcp: { enabled: true, port: ${params.port} },
641
- scripts: { enabled: true, outputDir: './.harness/scripts' },
642
- },
643
- })
605
+ module.exports = config
644
606
  `;
645
607
  }
646
608
  function agentLead(vars) {
@@ -789,14 +751,6 @@ function writeSkills(cwd2, skillsDir) {
789
751
  writeFileSync3(dest, readFileSync4(src, "utf8"), "utf8");
790
752
  }
791
753
  }
792
- function writeSkill(skillsRoot, skillName) {
793
- const src = join4(__dirname2, "skills", skillName, "SKILL.md");
794
- const destDir = join4(skillsRoot, skillName);
795
- const dest = join4(destDir, "SKILL.md");
796
- if (existsSync3(dest)) return;
797
- mkdirSync3(destDir, { recursive: true });
798
- writeFileSync3(dest, readFileSync4(src, "utf8"), "utf8");
799
- }
800
754
 
801
755
  // src/core/materializer/claude-code.ts
802
756
  var ClaudeCodeMaterializer = class {
@@ -1340,7 +1294,6 @@ import pc3 from "picocolors";
1340
1294
 
1341
1295
  // src/core/doctor.ts
1342
1296
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1343
- import { homedir } from "os";
1344
1297
  import { dirname as dirname6, join as join11 } from "path";
1345
1298
  import { fileURLToPath as fileURLToPath5 } from "url";
1346
1299
 
@@ -1503,55 +1456,6 @@ function checkSkills(cwd2, provider) {
1503
1456
  const skillsDir = getProviderSkillsDir(provider);
1504
1457
  return checkSkillsAtRoot(join11(cwd2, skillsDir));
1505
1458
  }
1506
- function getGlobalProviderAgentDir(provider, homeDir) {
1507
- switch (provider) {
1508
- case "claude-code":
1509
- return { agentsDir: join11(homeDir, ".claude", "agents"), ext: ".md" };
1510
- case "opencode":
1511
- return { agentsDir: join11(homeDir, ".config", "opencode", "agents"), ext: ".md" };
1512
- case "codex-cli":
1513
- return { agentsDir: join11(homeDir, ".codex", "agents"), ext: ".toml" };
1514
- default:
1515
- return { agentsDir: join11(homeDir, ".claude", "agents"), ext: ".md" };
1516
- }
1517
- }
1518
- function getGlobalProviderSkillsDir(provider, homeDir) {
1519
- switch (provider) {
1520
- case "claude-code":
1521
- return join11(homeDir, ".claude", "skills");
1522
- case "opencode":
1523
- return join11(homeDir, ".config", "opencode", "skills");
1524
- case "codex-cli":
1525
- return join11(homeDir, ".agents", "skills");
1526
- default:
1527
- return join11(homeDir, ".claude", "skills");
1528
- }
1529
- }
1530
- async function getGlobalDoctorStatus(provider, config, homeDir = homedir()) {
1531
- const projectName = config.project.name;
1532
- const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1533
- const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1534
- const models = {
1535
- lead: config.agents.lead.model,
1536
- explorer: config.agents.explorer.model,
1537
- consultant: config.agents.consultant?.model,
1538
- builder: config.agents.builder.model,
1539
- reviewer: config.agents.reviewer.model
1540
- };
1541
- const { agentsDir, ext } = getGlobalProviderAgentDir(provider, homeDir);
1542
- const agents = checkAgentFilesAtRoot(
1543
- agentsDir,
1544
- ext,
1545
- provider,
1546
- projectName,
1547
- allowedPaths,
1548
- writablePaths,
1549
- models
1550
- );
1551
- const skillsDir = getGlobalProviderSkillsDir(provider, homeDir);
1552
- const skills = checkSkillsAtRoot(skillsDir);
1553
- return { agents, skills };
1554
- }
1555
1459
  async function getDoctorStatus(cwd2) {
1556
1460
  const lib = await checkLibVersion();
1557
1461
  let config;
@@ -1793,51 +1697,6 @@ import { join as join14 } from "path";
1793
1697
  import * as p3 from "@clack/prompts";
1794
1698
  import pc7 from "picocolors";
1795
1699
 
1796
- // src/core/materializer/global-sync.ts
1797
- import { homedir as homedir2 } from "os";
1798
- async function syncGlobalAgentsAndSkills(config, provider, homeDir = homedir2()) {
1799
- const status = await getGlobalDoctorStatus(provider, config, homeDir);
1800
- const missingAgents = status.agents.filter((a) => a.status === "missing");
1801
- const missingSkills = status.skills.filter((s) => s.status === "missing");
1802
- if (missingAgents.length === 0 && missingSkills.length === 0) {
1803
- return { alreadySynced: true, createdAgents: [], createdSkills: [] };
1804
- }
1805
- const projectName = config.project.name;
1806
- const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1807
- const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1808
- const models = {
1809
- lead: config.agents.lead.model,
1810
- explorer: config.agents.explorer.model,
1811
- consultant: config.agents.consultant?.model,
1812
- builder: config.agents.builder.model,
1813
- reviewer: config.agents.reviewer.model
1814
- };
1815
- const createdAgents = [];
1816
- if (missingAgents.length > 0) {
1817
- const { agentsDir, ext } = getGlobalProviderAgentDir(provider, homeDir);
1818
- for (const agent of missingAgents) {
1819
- const name = agent.name;
1820
- const content = generateExpectedAgentContent(name, provider, {
1821
- projectName,
1822
- allowedPaths,
1823
- writablePaths,
1824
- model: models[name]
1825
- });
1826
- writeAgentFile(agentsDir, `${name}${ext}`, content);
1827
- createdAgents.push(name);
1828
- }
1829
- }
1830
- const createdSkills = [];
1831
- if (missingSkills.length > 0) {
1832
- const skillsDir = getGlobalProviderSkillsDir(provider, homeDir);
1833
- for (const skill of missingSkills) {
1834
- writeSkill(skillsDir, skill.name);
1835
- createdSkills.push(skill.name);
1836
- }
1837
- }
1838
- return { alreadySynced: false, createdAgents, createdSkills };
1839
- }
1840
-
1841
1700
  // src/schema/init.ts
1842
1701
  import * as v from "valibot";
1843
1702
  var initNameSchema = v.pipe(
@@ -2079,7 +1938,7 @@ async function runInit(cwd2, flags) {
2079
1938
  const modelOverrides = {};
2080
1939
  if (provider === "claude-code" || provider === "codex-cli") {
2081
1940
  const wantsModelCustomization = await p3.confirm({
2082
- message: "\xBFPersonalizar el modelo por agente?",
1941
+ message: "Customize the model per agent?",
2083
1942
  initialValue: false
2084
1943
  });
2085
1944
  if (p3.isCancel(wantsModelCustomization)) {
@@ -2090,7 +1949,7 @@ async function runInit(cwd2, flags) {
2090
1949
  if (provider === "claude-code") {
2091
1950
  for (const agent of AGENT_LABELS) {
2092
1951
  const val = await p3.select({
2093
- message: `Modelo para ${agent.label}`,
1952
+ message: `Model for ${agent.label}`,
2094
1953
  options: [
2095
1954
  { value: "inherit", label: "inherit (default)" },
2096
1955
  { value: "haiku", label: "haiku" },
@@ -2109,8 +1968,8 @@ async function runInit(cwd2, flags) {
2109
1968
  } else {
2110
1969
  for (const agent of AGENT_LABELS) {
2111
1970
  const val = await p3.text({
2112
- message: `Modelo para ${agent.label} (Codex no valida este valor)`,
2113
- placeholder: "ej. gpt-5 (vac\xEDo o <3 caracteres = sin override)"
1971
+ message: `Model for ${agent.label} (Codex does not validate this value)`,
1972
+ placeholder: "e.g. gpt-5 (empty or <3 chars = no override)"
2114
1973
  });
2115
1974
  if (p3.isCancel(val)) {
2116
1975
  p3.cancel("Cancelled.");
@@ -2148,7 +2007,10 @@ async function runInit(cwd2, flags) {
2148
2007
  message: "Storage scope",
2149
2008
  options: [
2150
2009
  { value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
2151
- { value: "global", label: "Global \u2014 DB lives under ~/.harness/dbs/<projectId>/, outside the project" }
2010
+ {
2011
+ value: "global",
2012
+ label: "Global \u2014 DB lives under ~/.harness/dbs/<projectId>/, outside the project"
2013
+ }
2152
2014
  ],
2153
2015
  initialValue: "local"
2154
2016
  });
@@ -2212,7 +2074,6 @@ async function runInit(cwd2, flags) {
2212
2074
  firstTask = { title: taskTitle, description: taskDesc, acceptance };
2213
2075
  }
2214
2076
  let configExt = "ts";
2215
- let globalSyncResult = null;
2216
2077
  const spinner6 = p3.spinner();
2217
2078
  spinner6.start("Scaffolding...");
2218
2079
  try {
@@ -2246,9 +2107,6 @@ async function runInit(cwd2, flags) {
2246
2107
  const db = await openDB(config, installDir);
2247
2108
  await db.writeStorageState(installDir);
2248
2109
  await materializer.scaffold(config, { cwd: installDir, firstTask });
2249
- if (config.storage.scope === "global") {
2250
- globalSyncResult = await syncGlobalAgentsAndSkills(config, provider);
2251
- }
2252
2110
  if (firstTask) {
2253
2111
  const slug = slugify(firstTask.title);
2254
2112
  await db.addTask({
@@ -2272,8 +2130,16 @@ async function runInit(cwd2, flags) {
2272
2130
  console.log(pc7.green(`\u2713 agent-harness-kit.config.${configExt}`));
2273
2131
  console.log(pc7.green("\u2713 AGENTS.md"));
2274
2132
  console.log(pc7.green("\u2713 health.sh"));
2275
- console.log(pc7.green(storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/harness.db" : "\u2713 .harness/harness.db"));
2276
- console.log(pc7.green(storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/current.md" : "\u2713 .harness/current.md"));
2133
+ console.log(
2134
+ pc7.green(
2135
+ storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/harness.db" : "\u2713 .harness/harness.db"
2136
+ )
2137
+ );
2138
+ console.log(
2139
+ pc7.green(
2140
+ storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/current.md" : "\u2713 .harness/current.md"
2141
+ )
2142
+ );
2277
2143
  console.log(pc7.green("\u2713 .harness/storage-state.json"));
2278
2144
  console.log(pc7.green(`\u2713 ${agentsDir}lead.md`));
2279
2145
  console.log(pc7.green(`\u2713 ${agentsDir}explorer.md`));
@@ -2281,19 +2147,6 @@ async function runInit(cwd2, flags) {
2281
2147
  console.log(pc7.green(`\u2713 ${agentsDir}reviewer.md`));
2282
2148
  console.log(pc7.green(`\u2713 ${mcpFile}`));
2283
2149
  console.log(pc7.green("\u2713 .gitignore entries added"));
2284
- if (globalSyncResult) {
2285
- console.log("");
2286
- if (globalSyncResult.alreadySynced) {
2287
- console.log(pc7.dim("\u2713 Global agents/skills already synced \u2014 skipped"));
2288
- } else {
2289
- for (const name2 of globalSyncResult.createdAgents) {
2290
- console.log(pc7.green(`\u2713 ~global agent added: ${name2}`));
2291
- }
2292
- for (const name2 of globalSyncResult.createdSkills) {
2293
- console.log(pc7.green(`\u2713 ~global skill added: ${name2}`));
2294
- }
2295
- }
2296
- }
2297
2150
  console.log("");
2298
2151
  console.log(pc7.cyan("\u2192") + ` Edit ${pc7.cyan("health.sh")} with your project checks`);
2299
2152
  console.log(pc7.cyan("\u2192") + ` ${pc7.cyan("ahk task add")} to queue work for agents`);
@@ -2352,7 +2205,7 @@ async function runMigrate(cwd2, opts) {
2352
2205
 
2353
2206
  // src/commands/migrate-storage.ts
2354
2207
  import { copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
2355
- import { homedir as homedir3 } from "os";
2208
+ import { homedir } from "os";
2356
2209
  import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
2357
2210
  import pc9 from "picocolors";
2358
2211
  function log5(msg) {
@@ -2389,7 +2242,7 @@ function copySqliteFile(srcPath, destPath) {
2389
2242
  throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
2390
2243
  }
2391
2244
  }
2392
- async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
2245
+ async function runMigrateStorage(cwd2, opts, homeDir = homedir()) {
2393
2246
  const config = await loadConfig(cwd2);
2394
2247
  const storageDir = config.storage.dir;
2395
2248
  const state = readStorageStateFile(cwd2, storageDir);
@@ -3750,7 +3603,10 @@ function isLocalInstallSatisfied(cwd2) {
3750
3603
  return false;
3751
3604
  }
3752
3605
  function printLocalInstallWarning() {
3753
- console.error(pc17.red(`\u2717 ${pkg.name} must be installed locally in this project.`));
3606
+ console.error(pc17.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
3607
+ console.error(pc17.dim(" This is only a recommendation for reproducibility: pinning a local"));
3608
+ console.error(pc17.dim(" version keeps behavior consistent across your team and CI, instead of"));
3609
+ console.error(pc17.dim(" drifting with whatever version is installed globally on each machine."));
3754
3610
  console.error(pc17.dim(` Run: npm install --save-dev ${pkg.name}`));
3755
3611
  console.error(pc17.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
3756
3612
  }
@@ -3853,7 +3709,6 @@ program.command("doctor").description("Check lib version, agent files, and harne
3853
3709
  program.hook("preAction", () => {
3854
3710
  if (!isLocalInstallSatisfied(cwd)) {
3855
3711
  printLocalInstallWarning();
3856
- process.exit(1);
3857
3712
  }
3858
3713
  });
3859
3714
  program.hook("postAction", async () => {