@cardor/agent-harness-kit 1.10.5 → 1.11.0
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 +142 -45
- package/dist/agent-templates/builder.md +7 -9
- package/dist/agent-templates/consultant.md +0 -3
- package/dist/agent-templates/explorer.md +5 -6
- package/dist/agent-templates/lead.md +4 -4
- package/dist/agent-templates/reviewer.md +0 -3
- package/dist/{chunk-D64KK6UU.js → chunk-U3O77CGE.js} +38 -16
- package/dist/chunk-U3O77CGE.js.map +1 -0
- package/dist/cli.js +500 -571
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -23
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-D64KK6UU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findConfigFile,
|
|
3
3
|
loadConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U3O77CGE.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MARKDOWN_PATH,
|
|
7
7
|
DEFAULT_SQLITE_PATH,
|
|
@@ -24,7 +24,7 @@ import * as p from "@clack/prompts";
|
|
|
24
24
|
import pc from "picocolors";
|
|
25
25
|
|
|
26
26
|
// src/core/materializer/claude-code.ts
|
|
27
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4,
|
|
27
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
28
28
|
import { join as join5, resolve as resolve3 } from "path";
|
|
29
29
|
|
|
30
30
|
// src/utils/file.ts
|
|
@@ -335,6 +335,37 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
335
335
|
import { readFileSync as readFileSync3 } from "fs";
|
|
336
336
|
import { dirname as dirname2, join as join3 } from "path";
|
|
337
337
|
import { fileURLToPath } from "url";
|
|
338
|
+
|
|
339
|
+
// src/core/materializer/agent-restrictions.ts
|
|
340
|
+
var AGENT_RESTRICTIONS = {
|
|
341
|
+
lead: "no-write",
|
|
342
|
+
explorer: "no-write",
|
|
343
|
+
consultant: "no-write",
|
|
344
|
+
builder: "none",
|
|
345
|
+
reviewer: "no-write"
|
|
346
|
+
};
|
|
347
|
+
function restrictionFor(agentName) {
|
|
348
|
+
return AGENT_RESTRICTIONS[agentName] ?? "no-write";
|
|
349
|
+
}
|
|
350
|
+
function claudeDisallowedTools(agentName) {
|
|
351
|
+
return restrictionFor(agentName) === "no-write" ? ["Write", "Edit"] : [];
|
|
352
|
+
}
|
|
353
|
+
function opencodePermissions(agentName) {
|
|
354
|
+
return restrictionFor(agentName) === "no-write" ? { edit: "deny" } : {};
|
|
355
|
+
}
|
|
356
|
+
function codexSandboxMode(agentName) {
|
|
357
|
+
return restrictionFor(agentName) === "no-write" ? "read-only" : "workspace-write";
|
|
358
|
+
}
|
|
359
|
+
var CODEX_READ_ONLY_NOTICE = `## Tool restrictions (enforced by the sandbox)
|
|
360
|
+
|
|
361
|
+
This agent runs with \`sandbox_mode = "read-only"\`. You MUST NOT create, modify, or delete any file: no \`Write\`, no \`Edit\`, no \`apply_patch\`, and no shell command that writes to disk (\`>\`, \`tee\`, \`sed -i\`, \`mv\`, \`rm\`, ...).
|
|
362
|
+
|
|
363
|
+
These tools may still appear available to you. The sandbox will reject the call. Do not retry a rejected write \u2014 report it as a blocker instead.`;
|
|
364
|
+
function codexRestrictionNotice(agentName) {
|
|
365
|
+
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/core/materializer/templates.ts
|
|
338
369
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
339
370
|
var TEMPLATES_DIR = join3(__dirname, "agent-templates");
|
|
340
371
|
function loadAgentTemplate(name, vars = {}) {
|
|
@@ -530,11 +561,7 @@ If orchestrating: Agent definition files in .claude/agents/
|
|
|
530
561
|
\`\`\`
|
|
531
562
|
`;
|
|
532
563
|
}
|
|
533
|
-
function modelField(model) {
|
|
534
|
-
return model ? `, model: ${JSON.stringify(model)}` : "";
|
|
535
|
-
}
|
|
536
564
|
function configObjectBody(params) {
|
|
537
|
-
const models = params.models ?? {};
|
|
538
565
|
const isGlobal = params.scope === "global";
|
|
539
566
|
const markdownFallbackLine = isGlobal ? `markdownFallback: { enabled: true },` : `markdownFallback: { enabled: true, path: '.harness/current.md' },`;
|
|
540
567
|
return ` project: {
|
|
@@ -545,14 +572,13 @@ function configObjectBody(params) {
|
|
|
545
572
|
|
|
546
573
|
provider: '${params.provider}',
|
|
547
574
|
|
|
548
|
-
agents:
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
},
|
|
575
|
+
// There is no 'agents' key. Agent files are yours: edit the role prompt and
|
|
576
|
+
// the 'model:' frontmatter line directly in the generated file. 'ahk build'
|
|
577
|
+
// creates them when missing and never overwrites them \u2014 use
|
|
578
|
+
// 'ahk build --force' to regenerate them from the packaged templates.
|
|
579
|
+
// What each role may NOT do is enforced per-tool inside those files
|
|
580
|
+
// (disallowedTools / permission.edit / sandbox_mode); see
|
|
581
|
+
// src/core/materializer/agent-restrictions.ts.
|
|
556
582
|
|
|
557
583
|
// SQLite (default). Switch to postgres/mysql by changing database.type.
|
|
558
584
|
// database: { type: 'postgres', connectionString: process.env.DATABASE_URL },
|
|
@@ -587,6 +613,47 @@ function configObjectBody(params) {
|
|
|
587
613
|
},
|
|
588
614
|
`;
|
|
589
615
|
}
|
|
616
|
+
function configObject(params) {
|
|
617
|
+
const isGlobal = params.scope === "global";
|
|
618
|
+
return {
|
|
619
|
+
project: {
|
|
620
|
+
name: params.name,
|
|
621
|
+
description: params.description,
|
|
622
|
+
docsPath: params.docsPath
|
|
623
|
+
},
|
|
624
|
+
provider: params.provider,
|
|
625
|
+
// There is no 'agents' key here either — it was removed from the config
|
|
626
|
+
// entirely, so the JSON variant must not reintroduce it.
|
|
627
|
+
database: { type: "sqlite" },
|
|
628
|
+
storage: {
|
|
629
|
+
dir: ".harness",
|
|
630
|
+
tasks: { adapter: params.tasksAdapter },
|
|
631
|
+
sections: {
|
|
632
|
+
toolsUsed: true,
|
|
633
|
+
filesModified: true,
|
|
634
|
+
result: true,
|
|
635
|
+
blockers: true,
|
|
636
|
+
nextSteps: false
|
|
637
|
+
},
|
|
638
|
+
// Same scope rule as configObjectBody(): 'global' has no local path to
|
|
639
|
+
// declare, so markdownFallback.path is omitted for it.
|
|
640
|
+
markdownFallback: isGlobal ? { enabled: true } : { enabled: true, path: ".harness/current.md" },
|
|
641
|
+
scope: params.scope,
|
|
642
|
+
projectId: params.projectId
|
|
643
|
+
},
|
|
644
|
+
health: {
|
|
645
|
+
scriptPath: "./health.sh",
|
|
646
|
+
required: true
|
|
647
|
+
},
|
|
648
|
+
tools: {
|
|
649
|
+
mcp: { enabled: true, port: params.port },
|
|
650
|
+
scripts: { enabled: true, outputDir: "./.harness/scripts" }
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function configJson(params) {
|
|
655
|
+
return JSON.stringify(configObject(params), null, 2) + "\n";
|
|
656
|
+
}
|
|
590
657
|
function configTs(params) {
|
|
591
658
|
return `import type { HarnessConfig } from '@cardor/agent-harness-kit'
|
|
592
659
|
|
|
@@ -643,81 +710,83 @@ function stripFrontmatter(md) {
|
|
|
643
710
|
}
|
|
644
711
|
return { description, body };
|
|
645
712
|
}
|
|
646
|
-
function toCodexToml(
|
|
713
|
+
function toCodexToml(tomlName, agentName, description, body) {
|
|
647
714
|
const safe = (s) => s.replace(/"""/g, '""\\u0022');
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
715
|
+
const sandboxMode = codexSandboxMode(agentName);
|
|
716
|
+
const notice = codexRestrictionNotice(agentName);
|
|
717
|
+
const instructions = notice ? `${body.trimEnd()}
|
|
718
|
+
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
${notice}` : body.trimEnd();
|
|
722
|
+
return `name = "${tomlName}"
|
|
652
723
|
sandbox_mode = "${sandboxMode}"
|
|
653
|
-
|
|
724
|
+
|
|
654
725
|
description = """
|
|
655
726
|
${safe(description)}
|
|
656
727
|
"""
|
|
657
728
|
|
|
658
729
|
developer_instructions = """
|
|
659
|
-
${safe(
|
|
730
|
+
${safe(instructions)}
|
|
660
731
|
"""
|
|
661
732
|
`;
|
|
662
733
|
}
|
|
663
734
|
function agentLeadToml(vars) {
|
|
664
735
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
665
|
-
return toCodexToml("lead", description, body
|
|
736
|
+
return toCodexToml("lead", "lead", description, body);
|
|
666
737
|
}
|
|
667
738
|
function agentLeadAsDefaultToml(vars) {
|
|
668
739
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
669
|
-
return toCodexToml("default", description, body
|
|
740
|
+
return toCodexToml("default", "lead", description, body);
|
|
670
741
|
}
|
|
671
742
|
function agentExplorerToml(vars) {
|
|
672
743
|
const { description, body } = stripFrontmatter(loadAgentTemplate("explorer", vars));
|
|
673
|
-
return toCodexToml("explorer", description, body
|
|
744
|
+
return toCodexToml("explorer", "explorer", description, body);
|
|
674
745
|
}
|
|
675
746
|
function agentBuilderToml(vars) {
|
|
676
747
|
const { description, body } = stripFrontmatter(loadAgentTemplate("builder", vars));
|
|
677
|
-
return toCodexToml("builder", description, body
|
|
748
|
+
return toCodexToml("builder", "builder", description, body);
|
|
678
749
|
}
|
|
679
750
|
function agentReviewerToml(vars) {
|
|
680
751
|
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
681
|
-
return toCodexToml("reviewer", description, body
|
|
752
|
+
return toCodexToml("reviewer", "reviewer", description, body);
|
|
682
753
|
}
|
|
683
754
|
function agentConsultantToml(vars) {
|
|
684
755
|
const { description, body } = stripFrontmatter(loadAgentTemplate("consultant", vars));
|
|
685
|
-
return toCodexToml("consultant", description, body
|
|
686
|
-
}
|
|
687
|
-
function
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
const permissions = permissionsMap[agentName] ?? MCP_CLAUDE_PERMISSIONS;
|
|
696
|
-
const mcpLines = permissions.map((t) => ` - ${t}`).join("\n");
|
|
697
|
-
let result = md.replace(/(tools:\n(?: - (?!mcp__)[^\n]+\n)+)/, (match) => {
|
|
698
|
-
const trimmed = match.trimEnd();
|
|
699
|
-
return `${trimmed}
|
|
700
|
-
- Task
|
|
701
|
-
${mcpLines}
|
|
756
|
+
return toCodexToml("consultant", "consultant", description, body);
|
|
757
|
+
}
|
|
758
|
+
function stripFrontmatterBlockSequence(md, key) {
|
|
759
|
+
const re = new RegExp(`^${key}:\\n(?: - [^\\n]+\\n)+`, "m");
|
|
760
|
+
return md.replace(re, "");
|
|
761
|
+
}
|
|
762
|
+
function appendFrontmatterBlockSequence(md, key, values) {
|
|
763
|
+
if (values.length === 0) return md;
|
|
764
|
+
const block = `${key}:
|
|
765
|
+
${values.map((v4) => ` - ${v4}`).join("\n")}
|
|
702
766
|
`;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
}
|
|
707
|
-
return result;
|
|
767
|
+
return md.replace(/^---\n([\s\S]*?)^---\n/m, (_m, body) => `---
|
|
768
|
+
${body}${block}---
|
|
769
|
+
`);
|
|
708
770
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
|
|
771
|
+
function appendFrontmatterMapping(md, key, entries) {
|
|
772
|
+
const keys = Object.keys(entries);
|
|
773
|
+
if (keys.length === 0) return md;
|
|
774
|
+
const block = `${key}:
|
|
775
|
+
${keys.map((k) => ` ${k}: ${entries[k]}`).join("\n")}
|
|
776
|
+
`;
|
|
777
|
+
return md.replace(/^---\n([\s\S]*?)^---\n/m, (_m, body) => `---
|
|
778
|
+
${body}${block}---
|
|
779
|
+
`);
|
|
715
780
|
}
|
|
716
|
-
function
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
781
|
+
function translateFrontmatterForClaudeCode(md, agentName) {
|
|
782
|
+
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
783
|
+
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
784
|
+
return appendFrontmatterBlockSequence(result, "disallowedTools", claudeDisallowedTools(agentName));
|
|
785
|
+
}
|
|
786
|
+
function translateFrontmatterForOpenCode(md, agentName) {
|
|
787
|
+
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
788
|
+
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
789
|
+
return appendFrontmatterMapping(result, "permission", opencodePermissions(agentName));
|
|
721
790
|
}
|
|
722
791
|
var GITIGNORE_ENTRIES = `
|
|
723
792
|
# agent-harness-kit
|
|
@@ -729,11 +798,43 @@ var GITIGNORE_ENTRIES = `
|
|
|
729
798
|
|
|
730
799
|
// src/core/materializer/scaffold-utils.ts
|
|
731
800
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
732
|
-
function
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
801
|
+
function writeAgentFiles(cwd2, entries, opts = {}) {
|
|
802
|
+
const result = { created: [], overwritten: [], preserved: [] };
|
|
803
|
+
const existing = entries.filter((e) => existsSync3(join4(cwd2, e.relPath)));
|
|
804
|
+
if (opts.force && existing.length > 0) {
|
|
805
|
+
if (!opts.backupRoot) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
"writeAgentFiles: force is set and existing agent files would be overwritten, but no backupRoot was provided. Refusing to overwrite without a backup."
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
811
|
+
const backupDir = join4(opts.backupRoot, `agents-${stamp}`);
|
|
812
|
+
try {
|
|
813
|
+
for (const entry of existing) {
|
|
814
|
+
const dest = join4(backupDir, entry.relPath);
|
|
815
|
+
mkdirSync3(resolve2(dest, ".."), { recursive: true });
|
|
816
|
+
writeFileSync3(dest, readFileSync4(join4(cwd2, entry.relPath), "utf8"), "utf8");
|
|
817
|
+
}
|
|
818
|
+
} catch (err) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`Could not back up existing agent files to ${backupDir} (${err instanceof Error ? err.message : String(err)}). Aborting WITHOUT overwriting anything \u2014 no agent file was modified.`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
result.backupDir = backupDir;
|
|
824
|
+
}
|
|
825
|
+
for (const entry of entries) {
|
|
826
|
+
const abs = join4(cwd2, entry.relPath);
|
|
827
|
+
const exists = existsSync3(abs);
|
|
828
|
+
if (exists && !opts.force) {
|
|
829
|
+
result.preserved.push(entry.relPath);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
mkdirSync3(resolve2(abs, ".."), { recursive: true });
|
|
833
|
+
writeFileSync3(abs, entry.content, "utf8");
|
|
834
|
+
if (exists) result.overwritten.push(entry.relPath);
|
|
835
|
+
else result.created.push(entry.relPath);
|
|
836
|
+
}
|
|
837
|
+
return result;
|
|
737
838
|
}
|
|
738
839
|
function appendGitignore(cwd2) {
|
|
739
840
|
const giPath = join4(cwd2, ".gitignore");
|
|
@@ -758,6 +859,16 @@ function writeSkills(cwd2, skillsDir) {
|
|
|
758
859
|
}
|
|
759
860
|
|
|
760
861
|
// src/core/materializer/claude-code.ts
|
|
862
|
+
function claudeAgentFiles(config) {
|
|
863
|
+
const projectName = config.project.name;
|
|
864
|
+
return [
|
|
865
|
+
{ relPath: ".claude/agents/lead.md", content: translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead") },
|
|
866
|
+
{ relPath: ".claude/agents/explorer.md", content: translateFrontmatterForClaudeCode(agentExplorer({ projectName }), "explorer") },
|
|
867
|
+
{ relPath: ".claude/agents/consultant.md", content: translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant") },
|
|
868
|
+
{ relPath: ".claude/agents/builder.md", content: translateFrontmatterForClaudeCode(agentBuilder({ projectName }), "builder") },
|
|
869
|
+
{ relPath: ".claude/agents/reviewer.md", content: translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer") }
|
|
870
|
+
];
|
|
871
|
+
}
|
|
761
872
|
var ClaudeCodeMaterializer = class {
|
|
762
873
|
async scaffold(config, opts) {
|
|
763
874
|
const { cwd: cwd2 } = opts;
|
|
@@ -781,26 +892,14 @@ No tasks in progress.
|
|
|
781
892
|
`
|
|
782
893
|
);
|
|
783
894
|
}
|
|
784
|
-
|
|
785
|
-
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
786
|
-
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
787
|
-
const leadModel = config.agents.lead.model;
|
|
788
|
-
const explorerModel = config.agents.explorer.model;
|
|
789
|
-
const consultantModel = config.agents.consultant?.model;
|
|
790
|
-
const builderModel = config.agents.builder.model;
|
|
791
|
-
const reviewerModel = config.agents.reviewer.model;
|
|
792
|
-
writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead", leadModel));
|
|
793
|
-
writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer", explorerModel));
|
|
794
|
-
writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant", consultantModel));
|
|
795
|
-
writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder", builderModel));
|
|
796
|
-
writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer", reviewerModel));
|
|
895
|
+
writeAgentFiles(cwd2, claudeAgentFiles(config));
|
|
797
896
|
mergeClaudeMcpJson(join5(cwd2, ".mcp.json"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
798
897
|
mergeClaudeSettingsJson(join5(cwd2, ".claude/settings.json"));
|
|
799
898
|
mergeClaudeSettingsLocalJson(join5(cwd2, ".claude/settings.local.json"));
|
|
800
899
|
appendGitignore(cwd2);
|
|
801
900
|
writeSkills(cwd2, ".claude/skills");
|
|
802
901
|
}
|
|
803
|
-
async build(config, cwd2) {
|
|
902
|
+
async build(config, cwd2, opts = {}) {
|
|
804
903
|
const write2 = (relPath, content) => {
|
|
805
904
|
const abs = join5(cwd2, relPath);
|
|
806
905
|
mkdirSync4(resolve3(abs, ".."), { recursive: true });
|
|
@@ -808,64 +907,54 @@ No tasks in progress.
|
|
|
808
907
|
};
|
|
809
908
|
write2("AGENTS.md", agentsMd(config));
|
|
810
909
|
write2("CLAUDE.md", claudeMd(config));
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const explorerModel = config.agents.explorer.model;
|
|
816
|
-
const consultantModel = config.agents.consultant?.model;
|
|
817
|
-
const builderModel = config.agents.builder.model;
|
|
818
|
-
const reviewerModel = config.agents.reviewer.model;
|
|
819
|
-
write2(".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead", leadModel));
|
|
820
|
-
write2(".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer", explorerModel));
|
|
821
|
-
write2(".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant", consultantModel));
|
|
822
|
-
write2(".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder", builderModel));
|
|
823
|
-
write2(".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer", reviewerModel));
|
|
910
|
+
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config), {
|
|
911
|
+
force: opts.force,
|
|
912
|
+
backupRoot: join5(cwd2, config.storage.dir, "backups")
|
|
913
|
+
});
|
|
824
914
|
mergeClaudeMcpJson(join5(cwd2, ".mcp.json"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
825
915
|
mergeClaudeSettingsJson(join5(cwd2, ".claude/settings.json"));
|
|
826
916
|
mergeClaudeSettingsLocalJson(join5(cwd2, ".claude/settings.local.json"));
|
|
827
917
|
writeSkills(cwd2, ".claude/skills");
|
|
918
|
+
return { agents };
|
|
828
919
|
}
|
|
829
920
|
async migrate(config, _to, _cwd) {
|
|
830
921
|
void config;
|
|
831
922
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
|
|
851
|
-
const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
|
|
852
|
-
const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
|
|
853
|
-
return header + nativeSection + mcpSection;
|
|
854
|
-
}
|
|
855
|
-
);
|
|
856
|
-
if (updated === content) {
|
|
857
|
-
console.log(` ${agent}.md already in sync`);
|
|
858
|
-
} else {
|
|
859
|
-
writeFileSync4(filePath, updated, "utf-8");
|
|
860
|
-
console.log(` ${agent}.md updated`);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
923
|
+
/**
|
|
924
|
+
* No-op by design.
|
|
925
|
+
*
|
|
926
|
+
* This used to rewrite the `tools:` frontmatter block of every agent file,
|
|
927
|
+
* re-injecting the canonical `mcp__agent-harness-kit__*` allowlist. Agent
|
|
928
|
+
* files no longer declare `tools` at all: they inherit the full tool set
|
|
929
|
+
* (Task and every MCP tool included) and express restrictions as a
|
|
930
|
+
* `disallowedTools` denylist instead.
|
|
931
|
+
*
|
|
932
|
+
* Keeping the old rewrite alive would be actively harmful — on a project
|
|
933
|
+
* upgraded from an older version, whose agent files still carry a legacy
|
|
934
|
+
* `tools:` block, it would re-inject the allowlist and silently undo the
|
|
935
|
+
* migration on exactly the files that most need it. Run `ahk build` to
|
|
936
|
+
* regenerate agent files in the current shape.
|
|
937
|
+
*/
|
|
938
|
+
async syncPermissions(_cwd) {
|
|
939
|
+
console.log(" Agent files inherit tools and declare a disallowedTools denylist \u2014");
|
|
940
|
+
console.log(" there is no permission allowlist to sync. Run `ahk build` to regenerate them.");
|
|
863
941
|
}
|
|
864
942
|
};
|
|
865
943
|
|
|
866
944
|
// src/core/materializer/codex-cli.ts
|
|
867
945
|
import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
868
946
|
import { join as join6, resolve as resolve4 } from "path";
|
|
947
|
+
function codexAgentFiles(config) {
|
|
948
|
+
const projectName = config.project.name;
|
|
949
|
+
return [
|
|
950
|
+
{ relPath: ".codex/agents/lead.toml", content: agentLeadToml({ projectName }) },
|
|
951
|
+
{ relPath: ".codex/agents/explorer.toml", content: agentExplorerToml({ projectName }) },
|
|
952
|
+
{ relPath: ".codex/agents/consultant.toml", content: agentConsultantToml({ projectName }) },
|
|
953
|
+
{ relPath: ".codex/agents/builder.toml", content: agentBuilderToml({ projectName }) },
|
|
954
|
+
{ relPath: ".codex/agents/reviewer.toml", content: agentReviewerToml({ projectName }) },
|
|
955
|
+
{ relPath: ".codex/agents/default.toml", content: agentLeadAsDefaultToml({ projectName }) }
|
|
956
|
+
];
|
|
957
|
+
}
|
|
869
958
|
var CodexCliMaterializer = class {
|
|
870
959
|
async scaffold(config, opts) {
|
|
871
960
|
const { cwd: cwd2 } = opts;
|
|
@@ -892,47 +981,25 @@ No tasks in progress.
|
|
|
892
981
|
`
|
|
893
982
|
);
|
|
894
983
|
}
|
|
895
|
-
|
|
896
|
-
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
897
|
-
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
898
|
-
const leadModel = config.agents.lead.model;
|
|
899
|
-
const explorerModel = config.agents.explorer.model;
|
|
900
|
-
const consultantModel = config.agents.consultant?.model;
|
|
901
|
-
const builderModel = config.agents.builder.model;
|
|
902
|
-
const reviewerModel = config.agents.reviewer.model;
|
|
903
|
-
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName, model: leadModel }));
|
|
904
|
-
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths, model: explorerModel }));
|
|
905
|
-
writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName, model: consultantModel }));
|
|
906
|
-
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths, model: builderModel }));
|
|
907
|
-
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName, model: reviewerModel }));
|
|
908
|
-
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName, model: leadModel }));
|
|
984
|
+
writeAgentFiles(cwd2, codexAgentFiles(config));
|
|
909
985
|
mergeCodexConfigToml(join6(cwd2, ".codex/config.toml"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
910
986
|
appendGitignore(cwd2);
|
|
911
987
|
writeSkills(cwd2, ".agents/skills");
|
|
912
988
|
}
|
|
913
|
-
async build(config, cwd2) {
|
|
989
|
+
async build(config, cwd2, opts = {}) {
|
|
914
990
|
const write2 = (relPath, content) => {
|
|
915
991
|
const abs = join6(cwd2, relPath);
|
|
916
992
|
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
917
993
|
writeFileSync5(abs, content, "utf8");
|
|
918
994
|
};
|
|
919
995
|
write2("AGENTS.md", agentsMd(config));
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const explorerModel = config.agents.explorer.model;
|
|
925
|
-
const consultantModel = config.agents.consultant?.model;
|
|
926
|
-
const builderModel = config.agents.builder.model;
|
|
927
|
-
const reviewerModel = config.agents.reviewer.model;
|
|
928
|
-
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName, model: leadModel }));
|
|
929
|
-
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths, model: explorerModel }));
|
|
930
|
-
writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName, model: consultantModel }));
|
|
931
|
-
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths, model: builderModel }));
|
|
932
|
-
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName, model: reviewerModel }));
|
|
933
|
-
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName, model: leadModel }));
|
|
996
|
+
const agents = writeAgentFiles(cwd2, codexAgentFiles(config), {
|
|
997
|
+
force: opts.force,
|
|
998
|
+
backupRoot: join6(cwd2, config.storage.dir, "backups")
|
|
999
|
+
});
|
|
934
1000
|
mergeCodexConfigToml(join6(cwd2, ".codex/config.toml"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
935
1001
|
writeSkills(cwd2, ".agents/skills");
|
|
1002
|
+
return { agents };
|
|
936
1003
|
}
|
|
937
1004
|
async migrate(config, _to, _cwd) {
|
|
938
1005
|
void config;
|
|
@@ -945,6 +1012,16 @@ No tasks in progress.
|
|
|
945
1012
|
// src/core/materializer/opencode.ts
|
|
946
1013
|
import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
947
1014
|
import { join as join7, resolve as resolve5 } from "path";
|
|
1015
|
+
function opencodeAgentFiles(config) {
|
|
1016
|
+
const projectName = config.project.name;
|
|
1017
|
+
return [
|
|
1018
|
+
{ relPath: ".opencode/agents/lead.md", content: translateFrontmatterForOpenCode(agentLead({ projectName }), "lead") },
|
|
1019
|
+
{ relPath: ".opencode/agents/explorer.md", content: translateFrontmatterForOpenCode(agentExplorer({ projectName }), "explorer") },
|
|
1020
|
+
{ relPath: ".opencode/agents/consultant.md", content: translateFrontmatterForOpenCode(agentConsultant({ projectName }), "consultant") },
|
|
1021
|
+
{ relPath: ".opencode/agents/builder.md", content: translateFrontmatterForOpenCode(agentBuilder({ projectName }), "builder") },
|
|
1022
|
+
{ relPath: ".opencode/agents/reviewer.md", content: translateFrontmatterForOpenCode(agentReviewer({ projectName }), "reviewer") }
|
|
1023
|
+
];
|
|
1024
|
+
}
|
|
948
1025
|
var OpenCodeMaterializer = class {
|
|
949
1026
|
async scaffold(config, opts) {
|
|
950
1027
|
const { cwd: cwd2 } = opts;
|
|
@@ -971,35 +1048,25 @@ No tasks in progress.
|
|
|
971
1048
|
`
|
|
972
1049
|
);
|
|
973
1050
|
}
|
|
974
|
-
|
|
975
|
-
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
976
|
-
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
977
|
-
writeAgentFile(cwd2, ".opencode/agents/lead.md", translateFrontmatterForOpenCode(agentLead({ projectName })));
|
|
978
|
-
writeAgentFile(cwd2, ".opencode/agents/explorer.md", translateFrontmatterForOpenCode(agentExplorer({ projectName, allowedPaths })));
|
|
979
|
-
writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
|
|
980
|
-
writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
|
|
981
|
-
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
1051
|
+
writeAgentFiles(cwd2, opencodeAgentFiles(config));
|
|
982
1052
|
mergeOpencodeJson(join7(cwd2, "opencode.json"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
983
1053
|
appendGitignore(cwd2);
|
|
984
1054
|
writeSkills(cwd2, ".opencode/skills");
|
|
985
1055
|
}
|
|
986
|
-
async build(config, cwd2) {
|
|
1056
|
+
async build(config, cwd2, opts = {}) {
|
|
987
1057
|
const write2 = (relPath, content) => {
|
|
988
1058
|
const abs = join7(cwd2, relPath);
|
|
989
1059
|
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
990
1060
|
writeFileSync6(abs, content, "utf8");
|
|
991
1061
|
};
|
|
992
1062
|
write2("AGENTS.md", agentsMd(config));
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
writeAgentFile(cwd2, ".opencode/agents/explorer.md", translateFrontmatterForOpenCode(agentExplorer({ projectName, allowedPaths })));
|
|
998
|
-
writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
|
|
999
|
-
writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
|
|
1000
|
-
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
1063
|
+
const agents = writeAgentFiles(cwd2, opencodeAgentFiles(config), {
|
|
1064
|
+
force: opts.force,
|
|
1065
|
+
backupRoot: join7(cwd2, config.storage.dir, "backups")
|
|
1066
|
+
});
|
|
1001
1067
|
mergeOpencodeJson(join7(cwd2, "opencode.json"), config.tools.mcp.port, detectPackageManager(cwd2));
|
|
1002
1068
|
writeSkills(cwd2, ".opencode/skills");
|
|
1069
|
+
return { agents };
|
|
1003
1070
|
}
|
|
1004
1071
|
async migrate(config, _to, _cwd) {
|
|
1005
1072
|
void config;
|
|
@@ -1025,7 +1092,7 @@ function getMaterializer(provider) {
|
|
|
1025
1092
|
|
|
1026
1093
|
// src/commands/build.ts
|
|
1027
1094
|
async function runBuild(cwd2, opts) {
|
|
1028
|
-
await buildOnce(cwd2);
|
|
1095
|
+
await buildOnce(cwd2, opts.force);
|
|
1029
1096
|
if (opts.sync) {
|
|
1030
1097
|
p.log.step("Syncing agent permissions...");
|
|
1031
1098
|
const config = await loadConfig(cwd2);
|
|
@@ -1037,25 +1104,48 @@ async function runBuild(cwd2, opts) {
|
|
|
1037
1104
|
watch(cwd2, { recursive: false }, async (_, filename) => {
|
|
1038
1105
|
if (filename?.startsWith("agent-harness-kit.config")) {
|
|
1039
1106
|
p.log.step("Config changed \u2014 rebuilding...");
|
|
1040
|
-
await buildOnce(cwd2);
|
|
1107
|
+
await buildOnce(cwd2, false);
|
|
1041
1108
|
}
|
|
1042
1109
|
});
|
|
1043
1110
|
await new Promise(() => {
|
|
1044
1111
|
});
|
|
1045
1112
|
}
|
|
1046
1113
|
}
|
|
1047
|
-
async function buildOnce(cwd2) {
|
|
1114
|
+
async function buildOnce(cwd2, force) {
|
|
1048
1115
|
const spinner6 = p.spinner();
|
|
1049
1116
|
spinner6.start("Loading config...");
|
|
1050
1117
|
try {
|
|
1051
1118
|
const config = await loadConfig(cwd2);
|
|
1052
1119
|
spinner6.message("Rebuilding files...");
|
|
1053
1120
|
const materializer = getMaterializer(config.provider);
|
|
1054
|
-
await materializer.build(config, cwd2);
|
|
1121
|
+
const report = await materializer.build(config, cwd2, { force });
|
|
1055
1122
|
spinner6.stop(pc.green("Build complete"));
|
|
1056
1123
|
p.log.success("AGENTS.md");
|
|
1057
1124
|
p.log.success(`Agent definitions (${config.provider})`);
|
|
1058
1125
|
p.log.success("MCP config");
|
|
1126
|
+
const { created, overwritten, preserved, backupDir } = report.agents;
|
|
1127
|
+
if (created.length > 0) {
|
|
1128
|
+
p.log.info(`Created ${created.length} missing agent file(s):
|
|
1129
|
+
${created.join("\n ")}`);
|
|
1130
|
+
}
|
|
1131
|
+
if (overwritten.length > 0) {
|
|
1132
|
+
p.log.warn(
|
|
1133
|
+
pc.yellow(
|
|
1134
|
+
`--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
|
|
1135
|
+
` + overwritten.join("\n ")
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
if (backupDir) {
|
|
1139
|
+
p.log.info(pc.yellow(` Previous content backed up \u2192 ${backupDir}`));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (preserved.length > 0) {
|
|
1143
|
+
p.log.info(
|
|
1144
|
+
`Left ${preserved.length} existing agent file(s) untouched \u2014 agent files are yours to edit.
|
|
1145
|
+
Re-run with --force to regenerate them from the packaged templates (this DESTROYS your edits;
|
|
1146
|
+
a backup is written first).`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1059
1149
|
} catch (err) {
|
|
1060
1150
|
spinner6.stop(pc.red("Build failed"));
|
|
1061
1151
|
p.log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -1071,7 +1161,7 @@ import pc2 from "picocolors";
|
|
|
1071
1161
|
|
|
1072
1162
|
// src/core/dashboard-server.ts
|
|
1073
1163
|
import { watch as watch2 } from "fs";
|
|
1074
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1164
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
1075
1165
|
import { extname, join as join8 } from "path";
|
|
1076
1166
|
import { serve } from "@hono/node-server";
|
|
1077
1167
|
import { Hono } from "hono";
|
|
@@ -1114,7 +1204,7 @@ var MIME = {
|
|
|
1114
1204
|
".ttf": "font/ttf"
|
|
1115
1205
|
};
|
|
1116
1206
|
function fileResponse(filePath) {
|
|
1117
|
-
const content =
|
|
1207
|
+
const content = readFileSync5(filePath);
|
|
1118
1208
|
const mime = MIME[extname(filePath)] ?? "application/octet-stream";
|
|
1119
1209
|
return new Response(content, {
|
|
1120
1210
|
headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
|
|
@@ -1299,7 +1389,7 @@ async function runDashboard(cwd2, opts) {
|
|
|
1299
1389
|
import pc3 from "picocolors";
|
|
1300
1390
|
|
|
1301
1391
|
// src/core/doctor.ts
|
|
1302
|
-
import { existsSync as existsSync9, readFileSync as
|
|
1392
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
1303
1393
|
import { dirname as dirname6, join as join11 } from "path";
|
|
1304
1394
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1305
1395
|
|
|
@@ -1366,68 +1456,15 @@ function getProviderAgentInfo(provider) {
|
|
|
1366
1456
|
return { agentsDir: ".claude/agents", ext: ".md" };
|
|
1367
1457
|
}
|
|
1368
1458
|
}
|
|
1369
|
-
function
|
|
1370
|
-
const { projectName, allowedPaths, writablePaths, model } = vars;
|
|
1371
|
-
if (provider === "claude-code") {
|
|
1372
|
-
const templateFns = {
|
|
1373
|
-
lead: () => agentLead({ projectName }),
|
|
1374
|
-
explorer: () => agentExplorer({ projectName, allowedPaths }),
|
|
1375
|
-
consultant: () => agentConsultant({ projectName }),
|
|
1376
|
-
builder: () => agentBuilder({ projectName, writablePaths }),
|
|
1377
|
-
reviewer: () => agentReviewer({ projectName })
|
|
1378
|
-
};
|
|
1379
|
-
return translateFrontmatterForClaudeCode(templateFns[agentName](), agentName, model);
|
|
1380
|
-
}
|
|
1381
|
-
if (provider === "opencode") {
|
|
1382
|
-
const templateFns = {
|
|
1383
|
-
lead: () => agentLead({ projectName }),
|
|
1384
|
-
explorer: () => agentExplorer({ projectName, allowedPaths }),
|
|
1385
|
-
consultant: () => agentConsultant({ projectName }),
|
|
1386
|
-
builder: () => agentBuilder({ projectName, writablePaths }),
|
|
1387
|
-
reviewer: () => agentReviewer({ projectName })
|
|
1388
|
-
};
|
|
1389
|
-
return translateFrontmatterForOpenCode(templateFns[agentName]());
|
|
1390
|
-
}
|
|
1391
|
-
const tomlFns = {
|
|
1392
|
-
lead: () => agentLeadToml({ projectName, model }),
|
|
1393
|
-
explorer: () => agentExplorerToml({ projectName, allowedPaths, model }),
|
|
1394
|
-
consultant: () => agentConsultantToml({ projectName, model }),
|
|
1395
|
-
builder: () => agentBuilderToml({ projectName, writablePaths, model }),
|
|
1396
|
-
reviewer: () => agentReviewerToml({ projectName, model })
|
|
1397
|
-
};
|
|
1398
|
-
return tomlFns[agentName]();
|
|
1399
|
-
}
|
|
1400
|
-
function checkAgentFilesAtRoot(agentsRoot, ext, provider, projectName, allowedPaths, writablePaths, models) {
|
|
1459
|
+
function checkAgentFilesAtRoot(agentsRoot, ext) {
|
|
1401
1460
|
return AGENT_NAMES.map((name) => {
|
|
1402
1461
|
const filePath = join11(agentsRoot, `${name}${ext}`);
|
|
1403
|
-
|
|
1404
|
-
return { name, status: "missing" };
|
|
1405
|
-
}
|
|
1406
|
-
try {
|
|
1407
|
-
const live = readFileSync7(filePath, "utf8");
|
|
1408
|
-
const expected = generateExpectedAgentContent(name, provider, {
|
|
1409
|
-
projectName,
|
|
1410
|
-
allowedPaths,
|
|
1411
|
-
writablePaths,
|
|
1412
|
-
model: models[name]
|
|
1413
|
-
});
|
|
1414
|
-
return { name, status: live === expected ? "ok" : "outdated" };
|
|
1415
|
-
} catch {
|
|
1416
|
-
return { name, status: "outdated" };
|
|
1417
|
-
}
|
|
1462
|
+
return { name, status: existsSync9(filePath) ? "ok" : "missing" };
|
|
1418
1463
|
});
|
|
1419
1464
|
}
|
|
1420
|
-
function checkAgentFiles(cwd2, provider
|
|
1465
|
+
function checkAgentFiles(cwd2, provider) {
|
|
1421
1466
|
const { agentsDir, ext } = getProviderAgentInfo(provider);
|
|
1422
|
-
return checkAgentFilesAtRoot(
|
|
1423
|
-
join11(cwd2, agentsDir),
|
|
1424
|
-
ext,
|
|
1425
|
-
provider,
|
|
1426
|
-
projectName,
|
|
1427
|
-
allowedPaths,
|
|
1428
|
-
writablePaths,
|
|
1429
|
-
models
|
|
1430
|
-
);
|
|
1467
|
+
return checkAgentFilesAtRoot(join11(cwd2, agentsDir), ext);
|
|
1431
1468
|
}
|
|
1432
1469
|
function getProviderSkillsDir(provider) {
|
|
1433
1470
|
switch (provider) {
|
|
@@ -1450,8 +1487,8 @@ function checkSkillsAtRoot(skillsRoot) {
|
|
|
1450
1487
|
return { name, status: "missing" };
|
|
1451
1488
|
}
|
|
1452
1489
|
try {
|
|
1453
|
-
const live =
|
|
1454
|
-
const source =
|
|
1490
|
+
const live = readFileSync6(livePath, "utf8");
|
|
1491
|
+
const source = readFileSync6(sourcePath, "utf8");
|
|
1455
1492
|
return { name, status: live === source ? "ok" : "outdated" };
|
|
1456
1493
|
} catch {
|
|
1457
1494
|
return { name, status: "outdated" };
|
|
@@ -1475,17 +1512,7 @@ async function getDoctorStatus(cwd2) {
|
|
|
1475
1512
|
};
|
|
1476
1513
|
}
|
|
1477
1514
|
const provider = config.provider;
|
|
1478
|
-
const
|
|
1479
|
-
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
1480
|
-
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
1481
|
-
const models = {
|
|
1482
|
-
lead: config.agents.lead.model,
|
|
1483
|
-
explorer: config.agents.explorer.model,
|
|
1484
|
-
consultant: config.agents.consultant?.model,
|
|
1485
|
-
builder: config.agents.builder.model,
|
|
1486
|
-
reviewer: config.agents.reviewer.model
|
|
1487
|
-
};
|
|
1488
|
-
const agents = checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths, models);
|
|
1515
|
+
const agents = checkAgentFiles(cwd2, provider);
|
|
1489
1516
|
const skills = checkSkills(cwd2, provider);
|
|
1490
1517
|
return { lib, agents, skills };
|
|
1491
1518
|
}
|
|
@@ -1522,17 +1549,13 @@ function printAgentsSection(agents) {
|
|
|
1522
1549
|
return;
|
|
1523
1550
|
}
|
|
1524
1551
|
const missing = agents.filter((a) => a.status === "missing");
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
ok("agent files", "all up to date");
|
|
1552
|
+
if (missing.length === 0) {
|
|
1553
|
+
ok("agent files", "all present");
|
|
1528
1554
|
return;
|
|
1529
1555
|
}
|
|
1530
1556
|
for (const agent of missing) {
|
|
1531
1557
|
warn("agent files", `${agent.name} missing`, "run: ahk build");
|
|
1532
1558
|
}
|
|
1533
|
-
for (const agent of outdated) {
|
|
1534
|
-
warn("agent files", `${agent.name} outdated`, "run: ahk build");
|
|
1535
|
-
}
|
|
1536
1559
|
}
|
|
1537
1560
|
function printSkillsSection(skills) {
|
|
1538
1561
|
if (skills.length === 0) {
|
|
@@ -1700,9 +1723,9 @@ function getProviderHealthFiles(provider) {
|
|
|
1700
1723
|
|
|
1701
1724
|
// src/commands/init.ts
|
|
1702
1725
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
1703
|
-
import { join as
|
|
1726
|
+
import { join as join15 } from "path";
|
|
1704
1727
|
import * as p3 from "@clack/prompts";
|
|
1705
|
-
import
|
|
1728
|
+
import pc8 from "picocolors";
|
|
1706
1729
|
|
|
1707
1730
|
// src/schema/init.ts
|
|
1708
1731
|
import * as v from "valibot";
|
|
@@ -1755,13 +1778,52 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1755
1778
|
|
|
1756
1779
|
// src/commands/init-helpers.ts
|
|
1757
1780
|
import { randomUUID } from "crypto";
|
|
1758
|
-
import { existsSync as
|
|
1781
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
1782
|
+
import { join as join14 } from "path";
|
|
1783
|
+
import pc7 from "picocolors";
|
|
1784
|
+
|
|
1785
|
+
// src/core/local-install-guard.ts
|
|
1786
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
1759
1787
|
import { join as join13 } from "path";
|
|
1760
1788
|
import pc6 from "picocolors";
|
|
1789
|
+
function isLocalInstallSatisfied(cwd2) {
|
|
1790
|
+
const selfPkgPath = join13(cwd2, "package.json");
|
|
1791
|
+
let projectPkg = null;
|
|
1792
|
+
if (existsSync11(selfPkgPath)) {
|
|
1793
|
+
try {
|
|
1794
|
+
const selfPkg = JSON.parse(readFileSync7(selfPkgPath, "utf8"));
|
|
1795
|
+
if (selfPkg?.name === pkg.name) return true;
|
|
1796
|
+
projectPkg = selfPkg;
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
const [scope, name] = pkg.name.split("/");
|
|
1801
|
+
const localPath = pkg.name.startsWith("@") ? join13(cwd2, "node_modules", scope, name) : join13(cwd2, "node_modules", pkg.name);
|
|
1802
|
+
if (existsSync11(localPath)) return true;
|
|
1803
|
+
const isPnp = existsSync11(join13(cwd2, ".pnp.cjs")) || existsSync11(join13(cwd2, ".pnp.loader.mjs"));
|
|
1804
|
+
if (isPnp && projectPkg) {
|
|
1805
|
+
const deps = {
|
|
1806
|
+
...projectPkg.dependencies ?? {},
|
|
1807
|
+
...projectPkg.devDependencies ?? {}
|
|
1808
|
+
};
|
|
1809
|
+
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
1810
|
+
}
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
function printLocalInstallWarning() {
|
|
1814
|
+
console.error(pc6.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
|
|
1815
|
+
console.error(pc6.dim(" This is only a recommendation for reproducibility: pinning a local"));
|
|
1816
|
+
console.error(pc6.dim(" version keeps behavior consistent across your team and CI, instead of"));
|
|
1817
|
+
console.error(pc6.dim(" drifting with whatever version is installed globally on each machine."));
|
|
1818
|
+
console.error(pc6.dim(` Run: npm install --save-dev ${pkg.name}`));
|
|
1819
|
+
console.error(pc6.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/commands/init-helpers.ts
|
|
1761
1823
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1762
1824
|
try {
|
|
1763
|
-
const pkgPath2 =
|
|
1764
|
-
if (!
|
|
1825
|
+
const pkgPath2 = join14(cwd2, "package.json");
|
|
1826
|
+
if (!existsSync12(pkgPath2)) return null;
|
|
1765
1827
|
const content = readFileSync8(pkgPath2, "utf8");
|
|
1766
1828
|
const pkg2 = JSON.parse(content);
|
|
1767
1829
|
const name = pkg2?.name;
|
|
@@ -1772,10 +1834,11 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
1772
1834
|
}
|
|
1773
1835
|
}
|
|
1774
1836
|
function detectConfigExtension(cwd2) {
|
|
1837
|
+
if (!isLocalInstallSatisfied(cwd2)) return "json";
|
|
1775
1838
|
try {
|
|
1776
|
-
if (
|
|
1777
|
-
const pkgPath2 =
|
|
1778
|
-
if (!
|
|
1839
|
+
if (existsSync12(join14(cwd2, "tsconfig.json"))) return "ts";
|
|
1840
|
+
const pkgPath2 = join14(cwd2, "package.json");
|
|
1841
|
+
if (!existsSync12(pkgPath2)) return "mjs";
|
|
1779
1842
|
const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
|
|
1780
1843
|
if (pkg2?.type === "module") return "mjs";
|
|
1781
1844
|
} catch {
|
|
@@ -1783,7 +1846,6 @@ function detectConfigExtension(cwd2) {
|
|
|
1783
1846
|
return "mjs";
|
|
1784
1847
|
}
|
|
1785
1848
|
function applyConfigDefaults(params) {
|
|
1786
|
-
const models = params.models ?? {};
|
|
1787
1849
|
const scope = params.scope ?? "local";
|
|
1788
1850
|
const projectId = params.projectId ?? randomUUID();
|
|
1789
1851
|
const baseStorage = {
|
|
@@ -1806,22 +1868,10 @@ function applyConfigDefaults(params) {
|
|
|
1806
1868
|
docsPath: params.docsPath,
|
|
1807
1869
|
agentsMd: "./AGENTS.md"
|
|
1808
1870
|
},
|
|
1809
|
-
agents:
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
allowedPaths: [params.docsPath, "./src"],
|
|
1814
|
-
...models.explorer && { model: models.explorer }
|
|
1815
|
-
},
|
|
1816
|
-
builder: {
|
|
1817
|
-
instructionsPath: null,
|
|
1818
|
-
writablePaths: ["./src", "./tests"],
|
|
1819
|
-
...models.builder && { model: models.builder }
|
|
1820
|
-
},
|
|
1821
|
-
reviewer: { instructionsPath: null, ...models.reviewer && { model: models.reviewer } },
|
|
1822
|
-
...models.consultant && { consultant: { instructionsPath: null, model: models.consultant } },
|
|
1823
|
-
custom: []
|
|
1824
|
-
},
|
|
1871
|
+
// No `agents` key: per-agent settings (model, role instructions) live in
|
|
1872
|
+
// the generated agent file, which is user-owned. This object is the runtime
|
|
1873
|
+
// twin of the config body emitted by configObjectBody() in templates.ts —
|
|
1874
|
+
// drift between the two is the bug this pairing has to keep out.
|
|
1825
1875
|
database: { type: "sqlite" },
|
|
1826
1876
|
storage,
|
|
1827
1877
|
health: {
|
|
@@ -1840,27 +1890,27 @@ function stripAnsi(str2) {
|
|
|
1840
1890
|
function drawBox(lines) {
|
|
1841
1891
|
const width = Math.max(...lines.map((l) => stripAnsi(l).length));
|
|
1842
1892
|
const border = "\u2500".repeat(width);
|
|
1843
|
-
console.log(
|
|
1893
|
+
console.log(pc7.yellow(`\u250C${border}\u2510`));
|
|
1844
1894
|
for (const line of lines) {
|
|
1845
1895
|
const pad = width - stripAnsi(line).length;
|
|
1846
1896
|
const padStr = pad > 0 ? " ".repeat(pad) : "";
|
|
1847
|
-
console.log(
|
|
1897
|
+
console.log(pc7.yellow("\u2502") + line + padStr + pc7.yellow("\u2502"));
|
|
1848
1898
|
}
|
|
1849
|
-
console.log(
|
|
1899
|
+
console.log(pc7.yellow(`\u2514${border}\u2518`));
|
|
1850
1900
|
}
|
|
1851
1901
|
function printWelcomeMessage(projectName) {
|
|
1852
1902
|
const sep = "\u2500".repeat(38);
|
|
1853
1903
|
const lines = [
|
|
1854
|
-
` ${
|
|
1855
|
-
` ${
|
|
1856
|
-
` ${
|
|
1857
|
-
` ${
|
|
1858
|
-
` ${
|
|
1859
|
-
` ${
|
|
1860
|
-
` ${
|
|
1861
|
-
` ${
|
|
1862
|
-
` ${
|
|
1863
|
-
` ${
|
|
1904
|
+
` ${pc7.bold(pc7.white("agent-harness-kit"))} `,
|
|
1905
|
+
` ${pc7.gray("\u2014")} harness scaffolding ${pc7.gray("\u2014")} `,
|
|
1906
|
+
` ${pc7.gray(sep)} `,
|
|
1907
|
+
` ${pc7.bold("Project:")} ${projectName || "\u2014"} `,
|
|
1908
|
+
` ${pc7.bold("Status:")} ${pc7.green("ready to configure")} `,
|
|
1909
|
+
` ${pc7.gray(sep)} `,
|
|
1910
|
+
` ${pc7.gray("Next steps:")} `,
|
|
1911
|
+
` ${pc7.gray("\u2192")} ${pc7.gray("Set up your AI provider config")} `,
|
|
1912
|
+
` ${pc7.gray("\u2192")} ${pc7.gray("Run your health check to verify")} `,
|
|
1913
|
+
` ${pc7.gray("\u2192")} ${pc7.gray("Start adding tasks for your agents")} `
|
|
1864
1914
|
];
|
|
1865
1915
|
console.log();
|
|
1866
1916
|
drawBox(lines);
|
|
@@ -1872,18 +1922,18 @@ async function runInit(cwd2, flags) {
|
|
|
1872
1922
|
const existingConfig = findConfigFile(cwd2);
|
|
1873
1923
|
if (existingConfig) {
|
|
1874
1924
|
console.log(
|
|
1875
|
-
|
|
1925
|
+
pc8.yellow("\u26A0") + " " + pc8.bold("Project already initialized.") + pc8.dim(` (${existingConfig})`)
|
|
1876
1926
|
);
|
|
1877
1927
|
console.log();
|
|
1878
|
-
console.log(
|
|
1928
|
+
console.log(pc8.dim("Suggested next steps:"));
|
|
1879
1929
|
console.log(
|
|
1880
|
-
" " +
|
|
1930
|
+
" " + pc8.cyan("ahk build") + pc8.dim(" \u2014 re-sync agent files after updating the library")
|
|
1881
1931
|
);
|
|
1882
|
-
console.log(" " +
|
|
1932
|
+
console.log(" " + pc8.cyan("ahk build --sync") + pc8.dim(" \u2014 also sync agent permissions"));
|
|
1883
1933
|
console.log(
|
|
1884
|
-
" " +
|
|
1934
|
+
" " + pc8.cyan("ahk reset") + pc8.dim(" \u2014 wipe and re-initialize from scratch")
|
|
1885
1935
|
);
|
|
1886
|
-
console.log(" " +
|
|
1936
|
+
console.log(" " + pc8.cyan("ahk dashboard") + pc8.dim(" \u2014 open the harness dashboard"));
|
|
1887
1937
|
process.exit(0);
|
|
1888
1938
|
}
|
|
1889
1939
|
const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
|
|
@@ -1935,61 +1985,6 @@ async function runInit(cwd2, flags) {
|
|
|
1935
1985
|
}
|
|
1936
1986
|
provider = val;
|
|
1937
1987
|
}
|
|
1938
|
-
const AGENT_LABELS = [
|
|
1939
|
-
{ key: "lead", label: "Lead" },
|
|
1940
|
-
{ key: "explorer", label: "Explorer" },
|
|
1941
|
-
{ key: "consultant", label: "Consultant" },
|
|
1942
|
-
{ key: "builder", label: "Builder" },
|
|
1943
|
-
{ key: "reviewer", label: "Reviewer" }
|
|
1944
|
-
];
|
|
1945
|
-
const modelOverrides = {};
|
|
1946
|
-
if (provider === "claude-code" || provider === "codex-cli") {
|
|
1947
|
-
const wantsModelCustomization = await p3.confirm({
|
|
1948
|
-
message: "Customize the model per agent?",
|
|
1949
|
-
initialValue: false
|
|
1950
|
-
});
|
|
1951
|
-
if (p3.isCancel(wantsModelCustomization)) {
|
|
1952
|
-
p3.cancel("Cancelled.");
|
|
1953
|
-
process.exit(0);
|
|
1954
|
-
}
|
|
1955
|
-
if (wantsModelCustomization) {
|
|
1956
|
-
if (provider === "claude-code") {
|
|
1957
|
-
for (const agent of AGENT_LABELS) {
|
|
1958
|
-
const val = await p3.select({
|
|
1959
|
-
message: `Model for ${agent.label}`,
|
|
1960
|
-
options: [
|
|
1961
|
-
{ value: "inherit", label: "inherit (default)" },
|
|
1962
|
-
{ value: "haiku", label: "haiku" },
|
|
1963
|
-
{ value: "sonnet", label: "sonnet" },
|
|
1964
|
-
{ value: "opus", label: "opus" },
|
|
1965
|
-
{ value: "fable", label: "fable" }
|
|
1966
|
-
],
|
|
1967
|
-
initialValue: "inherit"
|
|
1968
|
-
});
|
|
1969
|
-
if (p3.isCancel(val)) {
|
|
1970
|
-
p3.cancel("Cancelled.");
|
|
1971
|
-
process.exit(0);
|
|
1972
|
-
}
|
|
1973
|
-
modelOverrides[agent.key] = val;
|
|
1974
|
-
}
|
|
1975
|
-
} else {
|
|
1976
|
-
for (const agent of AGENT_LABELS) {
|
|
1977
|
-
const val = await p3.text({
|
|
1978
|
-
message: `Model for ${agent.label} (Codex does not validate this value)`,
|
|
1979
|
-
placeholder: "e.g. gpt-5 (empty or <3 chars = no override)"
|
|
1980
|
-
});
|
|
1981
|
-
if (p3.isCancel(val)) {
|
|
1982
|
-
p3.cancel("Cancelled.");
|
|
1983
|
-
process.exit(0);
|
|
1984
|
-
}
|
|
1985
|
-
const trimmed = val.trim();
|
|
1986
|
-
if (trimmed.length >= 3) {
|
|
1987
|
-
modelOverrides[agent.key] = trimmed;
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
1988
|
let docsPath;
|
|
1994
1989
|
if (flags.docs) {
|
|
1995
1990
|
docsPath = flags.docs;
|
|
@@ -2090,14 +2085,13 @@ async function runInit(cwd2, flags) {
|
|
|
2090
2085
|
provider,
|
|
2091
2086
|
docsPath,
|
|
2092
2087
|
tasksAdapter,
|
|
2093
|
-
models: modelOverrides,
|
|
2094
2088
|
scope: storageScope
|
|
2095
2089
|
});
|
|
2096
2090
|
const materializer = getMaterializer(provider);
|
|
2097
2091
|
const installDir = cwd2;
|
|
2098
2092
|
configExt = detectConfigExtension(cwd2);
|
|
2099
2093
|
const configFileName = `agent-harness-kit.config.${configExt}`;
|
|
2100
|
-
const templateFn = configExt === "ts" ? configTs : configExt === "mjs" ? configMjs : configCjs;
|
|
2094
|
+
const templateFn = configExt === "json" ? configJson : configExt === "ts" ? configTs : configExt === "mjs" ? configMjs : configCjs;
|
|
2101
2095
|
const configContent = templateFn({
|
|
2102
2096
|
name,
|
|
2103
2097
|
description,
|
|
@@ -2105,12 +2099,11 @@ async function runInit(cwd2, flags) {
|
|
|
2105
2099
|
docsPath,
|
|
2106
2100
|
tasksAdapter,
|
|
2107
2101
|
port: config.tools.mcp.port,
|
|
2108
|
-
models: modelOverrides,
|
|
2109
2102
|
scope: config.storage.scope,
|
|
2110
2103
|
projectId: config.storage.projectId
|
|
2111
2104
|
});
|
|
2112
|
-
writeFileSync8(
|
|
2113
|
-
mkdirSync7(
|
|
2105
|
+
writeFileSync8(join15(installDir, configFileName), configContent, "utf8");
|
|
2106
|
+
mkdirSync7(join15(installDir, config.storage.dir), { recursive: true });
|
|
2114
2107
|
const db = await openDB(config, installDir);
|
|
2115
2108
|
await db.writeStorageState(installDir);
|
|
2116
2109
|
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
@@ -2130,39 +2123,39 @@ async function runInit(cwd2, flags) {
|
|
|
2130
2123
|
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
2131
2124
|
throw err;
|
|
2132
2125
|
}
|
|
2133
|
-
console.log(
|
|
2126
|
+
console.log(pc8.green("\u2713 Scaffolded harness in current directory"));
|
|
2134
2127
|
const agentsDir = provider === "claude-code" ? ".claude/agents/" : ".opencode/agents/";
|
|
2135
2128
|
const mcpFile = provider === "claude-code" ? ".claude/mcp.json" : "./opencode.json";
|
|
2136
2129
|
console.log("");
|
|
2137
|
-
console.log(
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2130
|
+
console.log(pc8.green(`\u2713 agent-harness-kit.config.${configExt}`));
|
|
2131
|
+
console.log(pc8.green("\u2713 AGENTS.md"));
|
|
2132
|
+
console.log(pc8.green("\u2713 health.sh"));
|
|
2140
2133
|
console.log(
|
|
2141
|
-
|
|
2134
|
+
pc8.green(
|
|
2142
2135
|
storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/harness.db" : "\u2713 .harness/harness.db"
|
|
2143
2136
|
)
|
|
2144
2137
|
);
|
|
2145
2138
|
console.log(
|
|
2146
|
-
|
|
2139
|
+
pc8.green(
|
|
2147
2140
|
storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/current.md" : "\u2713 .harness/current.md"
|
|
2148
2141
|
)
|
|
2149
2142
|
);
|
|
2150
|
-
console.log(
|
|
2151
|
-
console.log(
|
|
2152
|
-
console.log(
|
|
2153
|
-
console.log(
|
|
2154
|
-
console.log(
|
|
2155
|
-
console.log(
|
|
2156
|
-
console.log(
|
|
2143
|
+
console.log(pc8.green("\u2713 .harness/storage-state.json"));
|
|
2144
|
+
console.log(pc8.green(`\u2713 ${agentsDir}lead.md`));
|
|
2145
|
+
console.log(pc8.green(`\u2713 ${agentsDir}explorer.md`));
|
|
2146
|
+
console.log(pc8.green(`\u2713 ${agentsDir}builder.md`));
|
|
2147
|
+
console.log(pc8.green(`\u2713 ${agentsDir}reviewer.md`));
|
|
2148
|
+
console.log(pc8.green(`\u2713 ${mcpFile}`));
|
|
2149
|
+
console.log(pc8.green("\u2713 .gitignore entries added"));
|
|
2157
2150
|
console.log("");
|
|
2158
|
-
console.log(
|
|
2159
|
-
console.log(
|
|
2151
|
+
console.log(pc8.cyan("\u2192") + ` Edit ${pc8.cyan("health.sh")} with your project checks`);
|
|
2152
|
+
console.log(pc8.cyan("\u2192") + ` ${pc8.cyan("ahk task add")} to queue work for agents`);
|
|
2160
2153
|
console.log(
|
|
2161
|
-
|
|
2154
|
+
pc8.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc8.cyan("https://github.com/safishamsi/graphify")}`
|
|
2162
2155
|
);
|
|
2163
2156
|
const recommendations = [
|
|
2164
2157
|
` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
|
|
2165
|
-
` Learn more: ${
|
|
2158
|
+
` Learn more: ${pc8.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
|
|
2166
2159
|
];
|
|
2167
2160
|
console.log("");
|
|
2168
2161
|
drawBox(recommendations);
|
|
@@ -2170,7 +2163,7 @@ async function runInit(cwd2, flags) {
|
|
|
2170
2163
|
|
|
2171
2164
|
// src/commands/migrate.ts
|
|
2172
2165
|
import * as p4 from "@clack/prompts";
|
|
2173
|
-
import
|
|
2166
|
+
import pc9 from "picocolors";
|
|
2174
2167
|
async function runMigrate(cwd2, opts) {
|
|
2175
2168
|
const config = await loadConfig(cwd2);
|
|
2176
2169
|
let target;
|
|
@@ -2192,7 +2185,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2192
2185
|
target = val;
|
|
2193
2186
|
}
|
|
2194
2187
|
if (target === config.provider) {
|
|
2195
|
-
console.log(
|
|
2188
|
+
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2196
2189
|
return;
|
|
2197
2190
|
}
|
|
2198
2191
|
const spinner6 = p4.spinner();
|
|
@@ -2200,21 +2193,21 @@ async function runMigrate(cwd2, opts) {
|
|
|
2200
2193
|
try {
|
|
2201
2194
|
const targetMaterializer = getMaterializer(target);
|
|
2202
2195
|
await targetMaterializer.build(config, cwd2);
|
|
2203
|
-
spinner6.stop(
|
|
2196
|
+
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2204
2197
|
p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2205
2198
|
p4.log.warn(`Then run: ahk build`);
|
|
2206
2199
|
} catch (err) {
|
|
2207
|
-
spinner6.stop(
|
|
2200
|
+
spinner6.stop(pc9.red("Migration failed"));
|
|
2208
2201
|
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
2209
2202
|
process.exit(1);
|
|
2210
2203
|
}
|
|
2211
2204
|
}
|
|
2212
2205
|
|
|
2213
2206
|
// src/commands/migrate-storage.ts
|
|
2214
|
-
import { copyFileSync, existsSync as
|
|
2207
|
+
import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
|
|
2215
2208
|
import { homedir as homedir3 } from "os";
|
|
2216
|
-
import { dirname as dirname7, join as
|
|
2217
|
-
import
|
|
2209
|
+
import { dirname as dirname7, join as join16, resolve as resolve7 } from "path";
|
|
2210
|
+
import pc10 from "picocolors";
|
|
2218
2211
|
function log5(msg) {
|
|
2219
2212
|
console.log(msg);
|
|
2220
2213
|
}
|
|
@@ -2225,14 +2218,14 @@ function defaultMarkdownPathForConfig(config) {
|
|
|
2225
2218
|
return config.storage.scope === "local" ? config.storage.markdownFallback.path : DEFAULT_MARKDOWN_PATH;
|
|
2226
2219
|
}
|
|
2227
2220
|
function currentMdPathForScope(scope, config, cwd2, homeDir) {
|
|
2228
|
-
return scope === "global" ?
|
|
2221
|
+
return scope === "global" ? join16(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve7(cwd2, defaultMarkdownPathForConfig(config));
|
|
2229
2222
|
}
|
|
2230
2223
|
function defaultSqlitePathForConfig(config) {
|
|
2231
2224
|
return config.storage.scope === "local" && config.database.type === "sqlite" ? config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH : DEFAULT_SQLITE_PATH;
|
|
2232
2225
|
}
|
|
2233
2226
|
async function backupDestination(cwd2, storageDir, data) {
|
|
2234
2227
|
const backupsDir = resolve7(cwd2, storageDir, "backups");
|
|
2235
|
-
const path =
|
|
2228
|
+
const path = join16(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
2236
2229
|
try {
|
|
2237
2230
|
mkdirSync8(backupsDir, { recursive: true });
|
|
2238
2231
|
writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
@@ -2247,11 +2240,11 @@ function copySqliteFile(srcPath, destPath) {
|
|
|
2247
2240
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
2248
2241
|
copyFileSync(srcPath, destPath);
|
|
2249
2242
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2250
|
-
if (
|
|
2243
|
+
if (existsSync13(`${srcPath}${suffix}`)) {
|
|
2251
2244
|
copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
|
|
2252
2245
|
}
|
|
2253
2246
|
}
|
|
2254
|
-
if (!
|
|
2247
|
+
if (!existsSync13(destPath)) {
|
|
2255
2248
|
throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
|
|
2256
2249
|
}
|
|
2257
2250
|
}
|
|
@@ -2279,13 +2272,13 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2279
2272
|
} finally {
|
|
2280
2273
|
await db.close();
|
|
2281
2274
|
}
|
|
2282
|
-
log5(
|
|
2275
|
+
log5(pc10.dim("storage-state.json was missing; no data found at either candidate location. Nothing to migrate \u2014 state recorded."));
|
|
2283
2276
|
return;
|
|
2284
2277
|
}
|
|
2285
2278
|
realScope = localCount > 0 ? "local" : "global";
|
|
2286
2279
|
realDbType = "sqlite";
|
|
2287
2280
|
log5(
|
|
2288
|
-
|
|
2281
|
+
pc10.yellow(
|
|
2289
2282
|
`storage-state.json was missing. Detected real data at ${realScope} sqlite location (${realScope === "local" ? localPath : globalPath}) \u2014 using it as the migration source.`
|
|
2290
2283
|
)
|
|
2291
2284
|
);
|
|
@@ -2296,7 +2289,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2296
2289
|
const desiredScope = config.storage.scope;
|
|
2297
2290
|
const desiredDbType = config.database.type;
|
|
2298
2291
|
if (realScope === desiredScope && realDbType === desiredDbType) {
|
|
2299
|
-
log5(
|
|
2292
|
+
log5(pc10.green(`\u2713 Storage already matches config (scope=${desiredScope}, database=${desiredDbType}) \u2014 nothing to migrate.`));
|
|
2300
2293
|
return;
|
|
2301
2294
|
}
|
|
2302
2295
|
if (realDbType !== "sqlite") {
|
|
@@ -2310,7 +2303,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2310
2303
|
return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
|
|
2311
2304
|
}
|
|
2312
2305
|
async function probeTaskCount(dbPath) {
|
|
2313
|
-
if (!
|
|
2306
|
+
if (!existsSync13(dbPath)) return 0;
|
|
2314
2307
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
2315
2308
|
const driver = new SQLiteDriver(dbPath);
|
|
2316
2309
|
try {
|
|
@@ -2327,10 +2320,10 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2327
2320
|
const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
|
|
2328
2321
|
const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
|
|
2329
2322
|
const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
|
|
2330
|
-
if (!
|
|
2323
|
+
if (!existsSync13(srcDb)) {
|
|
2331
2324
|
fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
|
|
2332
2325
|
}
|
|
2333
|
-
const destExists =
|
|
2326
|
+
const destExists = existsSync13(destDb);
|
|
2334
2327
|
if (destExists) {
|
|
2335
2328
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
2336
2329
|
const destDriver = new SQLiteDriver(destDb);
|
|
@@ -2359,36 +2352,36 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2359
2352
|
await backupDriver.close();
|
|
2360
2353
|
}
|
|
2361
2354
|
const backupPath = await backupDestination(cwd2, config.storage.dir, data);
|
|
2362
|
-
log5(
|
|
2355
|
+
log5(pc10.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
|
|
2363
2356
|
}
|
|
2364
2357
|
}
|
|
2365
2358
|
if (opts.dryRun) {
|
|
2366
|
-
log5(
|
|
2359
|
+
log5(pc10.dim(`[dry-run] Would copy ${srcDb} \u2192 ${destDb} (scope ${fromScope} \u2192 ${toScope}), and move current.md.`));
|
|
2367
2360
|
return;
|
|
2368
2361
|
}
|
|
2369
2362
|
copySqliteFile(srcDb, destDb);
|
|
2370
|
-
log5(
|
|
2371
|
-
if (
|
|
2363
|
+
log5(pc10.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
|
|
2364
|
+
if (existsSync13(srcMd)) {
|
|
2372
2365
|
mkdirSync8(dirname7(destMd), { recursive: true });
|
|
2373
2366
|
copyFileSync(srcMd, destMd);
|
|
2374
|
-
log5(
|
|
2367
|
+
log5(pc10.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
|
|
2375
2368
|
}
|
|
2376
2369
|
rmSync(srcDb, { force: true });
|
|
2377
2370
|
rmSync(`${srcDb}-wal`, { force: true });
|
|
2378
2371
|
rmSync(`${srcDb}-shm`, { force: true });
|
|
2379
|
-
if (
|
|
2372
|
+
if (existsSync13(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
|
|
2380
2373
|
const db = await openDB(config, cwd2, homeDir);
|
|
2381
2374
|
try {
|
|
2382
2375
|
await db.writeStorageState(cwd2);
|
|
2383
2376
|
} finally {
|
|
2384
2377
|
await db.close();
|
|
2385
2378
|
}
|
|
2386
|
-
log5(
|
|
2379
|
+
log5(pc10.green(`\u2713 Storage migrated: scope ${fromScope} \u2192 ${toScope}`));
|
|
2387
2380
|
}
|
|
2388
2381
|
async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
2389
2382
|
const sqlitePath = defaultSqlitePathForConfig(config);
|
|
2390
2383
|
const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
|
|
2391
|
-
if (!
|
|
2384
|
+
if (!existsSync13(srcPath)) {
|
|
2392
2385
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2393
2386
|
}
|
|
2394
2387
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
@@ -2424,7 +2417,7 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2424
2417
|
}
|
|
2425
2418
|
if (opts.dryRun) {
|
|
2426
2419
|
log5(
|
|
2427
|
-
|
|
2420
|
+
pc10.dim(
|
|
2428
2421
|
`[dry-run] Would migrate ${sourceCounts.tasks} task(s) from sqlite (${sourceScope}, ${srcPath}) to ${config.database.type}${destEmpty ? "" : " (destination has data \u2014 would back up first, then overwrite)"}.`
|
|
2429
2422
|
)
|
|
2430
2423
|
);
|
|
@@ -2434,34 +2427,34 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2434
2427
|
if (!destEmpty && opts.force) {
|
|
2435
2428
|
const currentDestData = await destDb.exportJson();
|
|
2436
2429
|
backupPath = await backupDestination(cwd2, config.storage.dir, currentDestData);
|
|
2437
|
-
log5(
|
|
2430
|
+
log5(pc10.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
|
|
2438
2431
|
}
|
|
2439
2432
|
await destDb.importFullExport(sourceData, config.database.type, { truncateFirst: !destEmpty });
|
|
2440
2433
|
await destDb.writeStorageState(cwd2);
|
|
2441
2434
|
log5(
|
|
2442
|
-
|
|
2435
|
+
pc10.green(
|
|
2443
2436
|
`\u2713 Migrated ${sourceData.tasks.length} task(s), ${sourceData.actions.length} action(s) from sqlite (${sourceScope}) \u2192 ${config.database.type}.`
|
|
2444
2437
|
)
|
|
2445
2438
|
);
|
|
2446
|
-
if (backupPath) log5(
|
|
2447
|
-
log5(
|
|
2439
|
+
if (backupPath) log5(pc10.dim(` Destination backup: ${backupPath}`));
|
|
2440
|
+
log5(pc10.yellow(` Note: the original sqlite file at ${srcPath} was NOT deleted \u2014 remove it manually once you've verified the migration.`));
|
|
2448
2441
|
} finally {
|
|
2449
2442
|
await destDb.close();
|
|
2450
2443
|
}
|
|
2451
2444
|
}
|
|
2452
2445
|
|
|
2453
2446
|
// src/commands/reset.ts
|
|
2454
|
-
import { existsSync as
|
|
2447
|
+
import { existsSync as existsSync14, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2455
2448
|
import { homedir as homedir4 } from "os";
|
|
2456
|
-
import { join as
|
|
2449
|
+
import { join as join17, resolve as resolve8 } from "path";
|
|
2457
2450
|
import * as p5 from "@clack/prompts";
|
|
2458
|
-
import
|
|
2451
|
+
import pc11 from "picocolors";
|
|
2459
2452
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2460
2453
|
async function resetAgentMds(cwd2, provider) {
|
|
2461
2454
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
2462
2455
|
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2463
|
-
if (!
|
|
2464
|
-
console.log(
|
|
2456
|
+
if (!existsSync14(agentDirPath)) {
|
|
2457
|
+
console.log(pc11.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2465
2458
|
return;
|
|
2466
2459
|
}
|
|
2467
2460
|
const existingFiles = [];
|
|
@@ -2473,11 +2466,11 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2473
2466
|
}
|
|
2474
2467
|
}
|
|
2475
2468
|
} catch {
|
|
2476
|
-
console.log(
|
|
2469
|
+
console.log(pc11.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2477
2470
|
return;
|
|
2478
2471
|
}
|
|
2479
2472
|
if (existingFiles.length === 0) {
|
|
2480
|
-
console.log(
|
|
2473
|
+
console.log(pc11.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2481
2474
|
return;
|
|
2482
2475
|
}
|
|
2483
2476
|
for (const file of existingFiles) {
|
|
@@ -2486,19 +2479,19 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2486
2479
|
initialValue: true
|
|
2487
2480
|
});
|
|
2488
2481
|
if (p5.isCancel(confirm3)) {
|
|
2489
|
-
console.log(
|
|
2482
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2490
2483
|
return;
|
|
2491
2484
|
}
|
|
2492
2485
|
if (confirm3) {
|
|
2493
2486
|
try {
|
|
2494
|
-
const filePath =
|
|
2487
|
+
const filePath = join17(agentDirPath, file);
|
|
2495
2488
|
rmSync2(filePath, { force: true });
|
|
2496
|
-
console.log(
|
|
2489
|
+
console.log(pc11.green(` Removed ${file}`));
|
|
2497
2490
|
} catch {
|
|
2498
|
-
console.error(
|
|
2491
|
+
console.error(pc11.red(` Failed to remove ${file}`));
|
|
2499
2492
|
}
|
|
2500
2493
|
} else {
|
|
2501
|
-
console.log(
|
|
2494
|
+
console.log(pc11.cyan(` Skipped ${file}`));
|
|
2502
2495
|
}
|
|
2503
2496
|
}
|
|
2504
2497
|
}
|
|
@@ -2507,7 +2500,7 @@ async function runReset(cwd2, opts) {
|
|
|
2507
2500
|
try {
|
|
2508
2501
|
config = await loadConfig(cwd2);
|
|
2509
2502
|
} catch {
|
|
2510
|
-
console.error(
|
|
2503
|
+
console.error(pc11.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2511
2504
|
process.exit(1);
|
|
2512
2505
|
}
|
|
2513
2506
|
const storageDir = config.storage.dir || ".harness";
|
|
@@ -2516,12 +2509,12 @@ async function runReset(cwd2, opts) {
|
|
|
2516
2509
|
let resetDb = false;
|
|
2517
2510
|
let resetFeatureList = false;
|
|
2518
2511
|
let resetAgentMdsFlag = false;
|
|
2519
|
-
if (dbPath &&
|
|
2512
|
+
if (dbPath && existsSync14(dbPath)) {
|
|
2520
2513
|
if (opts.force) {
|
|
2521
2514
|
resetDb = true;
|
|
2522
2515
|
} else {
|
|
2523
2516
|
if (config.database.type !== "sqlite") {
|
|
2524
|
-
console.log(
|
|
2517
|
+
console.log(pc11.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2525
2518
|
resetDb = false;
|
|
2526
2519
|
} else {
|
|
2527
2520
|
const confirm3 = await p5.confirm({
|
|
@@ -2529,16 +2522,16 @@ async function runReset(cwd2, opts) {
|
|
|
2529
2522
|
initialValue: true
|
|
2530
2523
|
});
|
|
2531
2524
|
if (p5.isCancel(confirm3)) {
|
|
2532
|
-
console.log(
|
|
2525
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2533
2526
|
return;
|
|
2534
2527
|
}
|
|
2535
2528
|
resetDb = confirm3;
|
|
2536
2529
|
}
|
|
2537
2530
|
}
|
|
2538
2531
|
} else if (!dbPath) {
|
|
2539
|
-
console.log(
|
|
2532
|
+
console.log(pc11.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2540
2533
|
}
|
|
2541
|
-
if (
|
|
2534
|
+
if (existsSync14(featureListPath)) {
|
|
2542
2535
|
if (opts.force) {
|
|
2543
2536
|
resetFeatureList = true;
|
|
2544
2537
|
} else {
|
|
@@ -2547,7 +2540,7 @@ async function runReset(cwd2, opts) {
|
|
|
2547
2540
|
initialValue: true
|
|
2548
2541
|
});
|
|
2549
2542
|
if (p5.isCancel(confirm3)) {
|
|
2550
|
-
console.log(
|
|
2543
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2551
2544
|
return;
|
|
2552
2545
|
}
|
|
2553
2546
|
resetFeatureList = confirm3;
|
|
@@ -2561,17 +2554,17 @@ async function runReset(cwd2, opts) {
|
|
|
2561
2554
|
rmSync2(dbPath, { force: true });
|
|
2562
2555
|
rmSync2(`${dbPath}-wal`, { force: true });
|
|
2563
2556
|
rmSync2(`${dbPath}-shm`, { force: true });
|
|
2564
|
-
console.log(
|
|
2557
|
+
console.log(pc11.green(` \u2713 Removed ${dbPath}`));
|
|
2565
2558
|
} catch {
|
|
2566
|
-
console.error(
|
|
2559
|
+
console.error(pc11.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2567
2560
|
}
|
|
2568
2561
|
}
|
|
2569
2562
|
if (resetFeatureList) {
|
|
2570
2563
|
try {
|
|
2571
2564
|
rmSync2(featureListPath, { force: true });
|
|
2572
|
-
console.log(
|
|
2565
|
+
console.log(pc11.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2573
2566
|
} catch {
|
|
2574
|
-
console.error(
|
|
2567
|
+
console.error(pc11.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2575
2568
|
}
|
|
2576
2569
|
}
|
|
2577
2570
|
if (resetAgentMdsFlag) {
|
|
@@ -2579,16 +2572,16 @@ async function runReset(cwd2, opts) {
|
|
|
2579
2572
|
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2580
2573
|
}
|
|
2581
2574
|
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2582
|
-
console.log(
|
|
2575
|
+
console.log(pc11.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2583
2576
|
return;
|
|
2584
2577
|
}
|
|
2585
2578
|
console.log("");
|
|
2586
|
-
console.log(
|
|
2579
|
+
console.log(pc11.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2587
2580
|
}
|
|
2588
2581
|
|
|
2589
2582
|
// src/core/mcp-server.ts
|
|
2590
|
-
import { existsSync as
|
|
2591
|
-
import { join as
|
|
2583
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
2584
|
+
import { join as join19, resolve as resolve9 } from "path";
|
|
2592
2585
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2593
2586
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2594
2587
|
import {
|
|
@@ -2597,45 +2590,20 @@ import {
|
|
|
2597
2590
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2598
2591
|
|
|
2599
2592
|
// src/core/permissions-check.ts
|
|
2600
|
-
import { existsSync as
|
|
2601
|
-
import { join as
|
|
2602
|
-
var
|
|
2603
|
-
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
2604
|
-
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
2605
|
-
consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
|
|
2606
|
-
builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
|
|
2607
|
-
reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
|
|
2608
|
-
};
|
|
2609
|
-
function parseToolsFromFrontmatter(content) {
|
|
2610
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/m);
|
|
2611
|
-
if (!match) return [];
|
|
2612
|
-
const fm = match[1];
|
|
2613
|
-
const toolsMatch = fm.match(/^tools:\n((?: - [^\n]+\n?)*)/m);
|
|
2614
|
-
if (!toolsMatch) return [];
|
|
2615
|
-
return toolsMatch[1].split("\n").map((l) => l.trim().replace(/^- /, "")).filter((l) => l.startsWith("mcp__"));
|
|
2616
|
-
}
|
|
2593
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2594
|
+
import { join as join18 } from "path";
|
|
2595
|
+
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2617
2596
|
function checkPermissionsSync(cwd2, config) {
|
|
2618
2597
|
if (config.provider !== "claude-code") {
|
|
2619
2598
|
return { in_sync: true };
|
|
2620
2599
|
}
|
|
2621
2600
|
const agents = {};
|
|
2622
2601
|
let in_sync = true;
|
|
2623
|
-
for (const agent of
|
|
2624
|
-
const filePath =
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
in_sync = false;
|
|
2629
|
-
continue;
|
|
2630
|
-
}
|
|
2631
|
-
const content = readFileSync9(filePath, "utf-8");
|
|
2632
|
-
const installed = parseToolsFromFrontmatter(content);
|
|
2633
|
-
const canonical = CANONICAL[agent];
|
|
2634
|
-
const missing = canonical.filter((t) => !installed.includes(t));
|
|
2635
|
-
const extra = installed.filter((t) => !canonical.includes(t));
|
|
2636
|
-
const ok3 = missing.length === 0 && extra.length === 0;
|
|
2637
|
-
if (!ok3) in_sync = false;
|
|
2638
|
-
agents[agent] = { ok: ok3, missing, extra };
|
|
2602
|
+
for (const agent of AGENTS) {
|
|
2603
|
+
const filePath = join18(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2604
|
+
const exists = existsSync15(filePath);
|
|
2605
|
+
if (!exists) in_sync = false;
|
|
2606
|
+
agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
|
|
2639
2607
|
}
|
|
2640
2608
|
return { in_sync, agents };
|
|
2641
2609
|
}
|
|
@@ -2885,7 +2853,7 @@ var TOOLS = [
|
|
|
2885
2853
|
},
|
|
2886
2854
|
{
|
|
2887
2855
|
name: "permissions.check",
|
|
2888
|
-
description:
|
|
2856
|
+
description: 'Check that a .claude/agents/*.md definition file exists for every role. Returns { in_sync, agents } where each agent is { ok } or { ok: false, reason: "missing_file" }. Agent file CONTENTS are never inspected \u2014 they are meant to be customised freely \u2014 so this never reports drift, only absence. Run `ahk build` to restore a missing file.',
|
|
2889
2857
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2890
2858
|
},
|
|
2891
2859
|
{
|
|
@@ -3054,19 +3022,19 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3054
3022
|
return ok2(JSON.stringify(result, null, 2));
|
|
3055
3023
|
}
|
|
3056
3024
|
case "deps.snapshot": {
|
|
3057
|
-
const pkgPath2 =
|
|
3058
|
-
if (!
|
|
3025
|
+
const pkgPath2 = join19(cwd2, "package.json");
|
|
3026
|
+
if (!existsSync16(pkgPath2)) {
|
|
3059
3027
|
return ok2("package.json not found in project root", true);
|
|
3060
3028
|
}
|
|
3061
|
-
const pkg2 = JSON.parse(
|
|
3029
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
3062
3030
|
const snapshot = {
|
|
3063
3031
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3064
3032
|
dependencies: pkg2.dependencies ?? {},
|
|
3065
3033
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3066
3034
|
};
|
|
3067
|
-
const harnessDir =
|
|
3035
|
+
const harnessDir = join19(cwd2, ".harness");
|
|
3068
3036
|
mkdirSync9(harnessDir, { recursive: true });
|
|
3069
|
-
writeFileSync10(
|
|
3037
|
+
writeFileSync10(join19(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3070
3038
|
return ok2(
|
|
3071
3039
|
JSON.stringify({
|
|
3072
3040
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3075,12 +3043,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3075
3043
|
);
|
|
3076
3044
|
}
|
|
3077
3045
|
case "deps.check": {
|
|
3078
|
-
const pkgPath2 =
|
|
3079
|
-
const lockPath =
|
|
3080
|
-
if (!
|
|
3046
|
+
const pkgPath2 = join19(cwd2, "package.json");
|
|
3047
|
+
const lockPath = join19(cwd2, ".harness", "deps-lock.json");
|
|
3048
|
+
if (!existsSync16(pkgPath2)) {
|
|
3081
3049
|
return ok2("package.json not found in project root", true);
|
|
3082
3050
|
}
|
|
3083
|
-
if (!
|
|
3051
|
+
if (!existsSync16(lockPath)) {
|
|
3084
3052
|
return ok2(
|
|
3085
3053
|
JSON.stringify({
|
|
3086
3054
|
status: "no-snapshot",
|
|
@@ -3088,8 +3056,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3088
3056
|
})
|
|
3089
3057
|
);
|
|
3090
3058
|
}
|
|
3091
|
-
const pkg2 = JSON.parse(
|
|
3092
|
-
const lock = JSON.parse(
|
|
3059
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
3060
|
+
const lock = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
3093
3061
|
const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
|
|
3094
3062
|
const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
|
|
3095
3063
|
const added = [];
|
|
@@ -3131,9 +3099,10 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3131
3099
|
const status = await getDoctorStatus(cwd2);
|
|
3132
3100
|
const result = {
|
|
3133
3101
|
lib: { current: status.lib.current, latest: status.lib.latest, outdated: status.lib.outdated },
|
|
3102
|
+
// Agents are existence-checked only — there is no `outdated` bucket.
|
|
3103
|
+
// Hand-edited agent definitions are supported and must not be flagged.
|
|
3134
3104
|
agents: {
|
|
3135
3105
|
missing: status.agents.filter((a) => a.status === "missing").map((a) => a.name),
|
|
3136
|
-
outdated: status.agents.filter((a) => a.status === "outdated").map((a) => a.name),
|
|
3137
3106
|
ok: status.agents.filter((a) => a.status === "ok").map((a) => a.name)
|
|
3138
3107
|
},
|
|
3139
3108
|
skills: {
|
|
@@ -3156,7 +3125,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
3156
3125
|
for (const file of files) {
|
|
3157
3126
|
if (results.length >= maxResults) break;
|
|
3158
3127
|
try {
|
|
3159
|
-
const content =
|
|
3128
|
+
const content = readFileSync9(file, "utf8");
|
|
3160
3129
|
const lines = content.split("\n");
|
|
3161
3130
|
for (let i = 0; i < lines.length; i++) {
|
|
3162
3131
|
const lower = lines[i].toLowerCase();
|
|
@@ -3181,7 +3150,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3181
3150
|
const files = [];
|
|
3182
3151
|
try {
|
|
3183
3152
|
for (const entry of readdirSync2(dir)) {
|
|
3184
|
-
const full =
|
|
3153
|
+
const full = join19(dir, entry);
|
|
3185
3154
|
const stat = statSync(full);
|
|
3186
3155
|
if (stat.isDirectory()) {
|
|
3187
3156
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3217,14 +3186,8 @@ async function runServe(cwd2, opts) {
|
|
|
3217
3186
|
`);
|
|
3218
3187
|
const syncResult = checkPermissionsSync(cwd2, config);
|
|
3219
3188
|
if (!syncResult.in_sync && syncResult.agents) {
|
|
3220
|
-
const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name
|
|
3221
|
-
|
|
3222
|
-
if (r.missing.length) parts.push(`missing: ${r.missing.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
3223
|
-
if (r.extra.length) parts.push(`extra: ${r.extra.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
3224
|
-
return `${name} (${parts.join("; ")})`;
|
|
3225
|
-
}).join("\n ");
|
|
3226
|
-
process.stderr.write(`[agent-harness-kit] Agent permissions out of sync. Run: ahk build --sync
|
|
3227
|
-
${affected}
|
|
3189
|
+
const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name]) => name).join(", ");
|
|
3190
|
+
process.stderr.write(`[agent-harness-kit] Agent definition files missing: ${affected}. Run: ahk build
|
|
3228
3191
|
`);
|
|
3229
3192
|
}
|
|
3230
3193
|
await startMcpServer(config, cwd2);
|
|
@@ -3232,12 +3195,12 @@ async function runServe(cwd2, opts) {
|
|
|
3232
3195
|
|
|
3233
3196
|
// src/commands/status.ts
|
|
3234
3197
|
import Table from "cli-table3";
|
|
3235
|
-
import
|
|
3198
|
+
import pc12 from "picocolors";
|
|
3236
3199
|
var STATUS_COLOR = {
|
|
3237
|
-
pending: (s) =>
|
|
3238
|
-
in_progress: (s) =>
|
|
3239
|
-
done: (s) =>
|
|
3240
|
-
blocked: (s) =>
|
|
3200
|
+
pending: (s) => pc12.dim(s),
|
|
3201
|
+
in_progress: (s) => pc12.cyan(s),
|
|
3202
|
+
done: (s) => pc12.green(s),
|
|
3203
|
+
blocked: (s) => pc12.red(s)
|
|
3241
3204
|
};
|
|
3242
3205
|
async function runStatus(cwd2, opts) {
|
|
3243
3206
|
const config = await loadConfig(cwd2);
|
|
@@ -3258,11 +3221,11 @@ async function runStatus(cwd2, opts) {
|
|
|
3258
3221
|
return;
|
|
3259
3222
|
}
|
|
3260
3223
|
if (tasks.length === 0) {
|
|
3261
|
-
console.log(
|
|
3224
|
+
console.log(pc12.dim("No tasks yet. Run: ahk task add"));
|
|
3262
3225
|
return;
|
|
3263
3226
|
}
|
|
3264
3227
|
const table = new Table({
|
|
3265
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
3228
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc12.bold(h)),
|
|
3266
3229
|
style: { head: [], border: [] }
|
|
3267
3230
|
});
|
|
3268
3231
|
for (const t of tasks) {
|
|
@@ -3280,12 +3243,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3280
3243
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3281
3244
|
if (inProgress.length > 0) {
|
|
3282
3245
|
console.log("");
|
|
3283
|
-
console.log(
|
|
3246
|
+
console.log(pc12.bold("Active actions:"));
|
|
3284
3247
|
for (const t of inProgress) {
|
|
3285
3248
|
const actions = await db.getActionsForTask(t.id);
|
|
3286
3249
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
3287
3250
|
for (const a of active) {
|
|
3288
|
-
console.log(` ${
|
|
3251
|
+
console.log(` ${pc12.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
3289
3252
|
}
|
|
3290
3253
|
}
|
|
3291
3254
|
}
|
|
@@ -3294,10 +3257,10 @@ async function runStatus(cwd2, opts) {
|
|
|
3294
3257
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
3295
3258
|
return `${fn(s.status)}: ${s.total}`;
|
|
3296
3259
|
});
|
|
3297
|
-
console.log(
|
|
3260
|
+
console.log(pc12.dim("Tasks \u2014 ") + parts.join(pc12.dim(" | ")));
|
|
3298
3261
|
const archivedTasks = await db.getArchivedTasks();
|
|
3299
3262
|
if (archivedTasks.length > 0) {
|
|
3300
|
-
console.log(
|
|
3263
|
+
console.log(pc12.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
|
|
3301
3264
|
}
|
|
3302
3265
|
} finally {
|
|
3303
3266
|
await db.close();
|
|
@@ -3305,13 +3268,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3305
3268
|
}
|
|
3306
3269
|
|
|
3307
3270
|
// src/commands/sync.ts
|
|
3308
|
-
import { existsSync as
|
|
3309
|
-
import { join as
|
|
3310
|
-
import
|
|
3271
|
+
import { existsSync as existsSync17, readFileSync as readFileSync10 } from "fs";
|
|
3272
|
+
import { join as join20, resolve as resolve10 } from "path";
|
|
3273
|
+
import pc13 from "picocolors";
|
|
3311
3274
|
async function runSync(cwd2, opts) {
|
|
3312
3275
|
const config = await loadConfig(cwd2);
|
|
3313
3276
|
const direction = opts.direction ?? "both";
|
|
3314
|
-
const featureListPath = resolve10(
|
|
3277
|
+
const featureListPath = resolve10(join20(cwd2, config.storage.dir, "feature_list.json"));
|
|
3315
3278
|
const db = await openDB(config, cwd2);
|
|
3316
3279
|
try {
|
|
3317
3280
|
if (direction === "in" || direction === "both") {
|
|
@@ -3325,44 +3288,44 @@ async function runSync(cwd2, opts) {
|
|
|
3325
3288
|
}
|
|
3326
3289
|
}
|
|
3327
3290
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3328
|
-
if (!
|
|
3329
|
-
console.log(
|
|
3291
|
+
if (!existsSync17(featureListPath)) {
|
|
3292
|
+
console.log(pc13.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3330
3293
|
return;
|
|
3331
3294
|
}
|
|
3332
3295
|
let seeds;
|
|
3333
3296
|
try {
|
|
3334
|
-
seeds = JSON.parse(
|
|
3297
|
+
seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
|
|
3335
3298
|
} catch (err) {
|
|
3336
|
-
console.error(
|
|
3299
|
+
console.error(pc13.red(`Failed to parse feature_list.json: ${err}`));
|
|
3337
3300
|
process.exit(1);
|
|
3338
3301
|
}
|
|
3339
3302
|
if (dryRun) {
|
|
3340
|
-
console.log(
|
|
3303
|
+
console.log(pc13.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
3341
3304
|
for (const t of seeds) {
|
|
3342
3305
|
const existing = await db.getTaskBySlug(t.slug);
|
|
3343
|
-
console.log(` ${existing ?
|
|
3306
|
+
console.log(` ${existing ? pc13.dim("skip") : pc13.green("add ")} ${t.slug}`);
|
|
3344
3307
|
}
|
|
3345
3308
|
return;
|
|
3346
3309
|
}
|
|
3347
3310
|
const result = await db.syncFromFeatureList(seeds);
|
|
3348
|
-
console.log(
|
|
3311
|
+
console.log(pc13.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
3349
3312
|
}
|
|
3350
3313
|
async function syncOut(db, cwd2, dryRun) {
|
|
3351
3314
|
if (dryRun) {
|
|
3352
3315
|
const tasks = await db.getTasks();
|
|
3353
|
-
console.log(
|
|
3316
|
+
console.log(pc13.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
3354
3317
|
console.log(` ${tasks.length} tasks would be written`);
|
|
3355
3318
|
return;
|
|
3356
3319
|
}
|
|
3357
3320
|
await db.writeFeatureList(cwd2);
|
|
3358
|
-
console.log(
|
|
3321
|
+
console.log(pc13.green("\u2713 Out-sync: feature_list.json updated"));
|
|
3359
3322
|
}
|
|
3360
3323
|
|
|
3361
3324
|
// src/commands/task/add.ts
|
|
3362
3325
|
import * as p6 from "@clack/prompts";
|
|
3363
|
-
import
|
|
3326
|
+
import pc14 from "picocolors";
|
|
3364
3327
|
async function runTaskAdd(cwd2) {
|
|
3365
|
-
p6.intro(
|
|
3328
|
+
p6.intro(pc14.bold("agent-harness-kit \u2014 add task"));
|
|
3366
3329
|
const title = await cliFormWithRetry(
|
|
3367
3330
|
async () => {
|
|
3368
3331
|
const val = await p6.text({ message: "Task title" });
|
|
@@ -3405,10 +3368,10 @@ async function runTaskAdd(cwd2) {
|
|
|
3405
3368
|
await db.writeFeatureList(cwd2);
|
|
3406
3369
|
await db.close();
|
|
3407
3370
|
spinner6.stop("");
|
|
3408
|
-
console.log(
|
|
3409
|
-
console.log(
|
|
3371
|
+
console.log(pc14.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
|
|
3372
|
+
console.log(pc14.cyan("\u2192") + " " + pc14.cyan("ahk status") + " to see all tasks");
|
|
3410
3373
|
} catch (err) {
|
|
3411
|
-
spinner6.stop(
|
|
3374
|
+
spinner6.stop(pc14.red("Failed"));
|
|
3412
3375
|
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
3413
3376
|
process.exit(1);
|
|
3414
3377
|
}
|
|
@@ -3416,17 +3379,17 @@ async function runTaskAdd(cwd2) {
|
|
|
3416
3379
|
|
|
3417
3380
|
// src/commands/task/done.ts
|
|
3418
3381
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3419
|
-
import { existsSync as
|
|
3382
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3420
3383
|
import { resolve as resolve11 } from "path";
|
|
3421
|
-
import
|
|
3384
|
+
import pc15 from "picocolors";
|
|
3422
3385
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3423
3386
|
const config = await loadConfig(cwd2);
|
|
3424
3387
|
if (config.health.required) {
|
|
3425
3388
|
const scriptPath = resolve11(cwd2, config.health.scriptPath);
|
|
3426
|
-
if (
|
|
3389
|
+
if (existsSync18(scriptPath)) {
|
|
3427
3390
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3428
3391
|
if (result.status !== 0) {
|
|
3429
|
-
console.error(
|
|
3392
|
+
console.error(pc15.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
3430
3393
|
if (result.stdout) console.error(result.stdout);
|
|
3431
3394
|
if (result.stderr) console.error(result.stderr);
|
|
3432
3395
|
process.exit(1);
|
|
@@ -3439,16 +3402,16 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3439
3402
|
const isId = !isNaN(parsed);
|
|
3440
3403
|
const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
|
|
3441
3404
|
if (!task2) {
|
|
3442
|
-
console.error(
|
|
3405
|
+
console.error(pc15.red(`Task not found: ${idOrSlug}`));
|
|
3443
3406
|
process.exit(1);
|
|
3444
3407
|
}
|
|
3445
3408
|
if (task2.status === "done") {
|
|
3446
|
-
console.log(
|
|
3409
|
+
console.log(pc15.dim(`Task #${task2.id} is already done.`));
|
|
3447
3410
|
return;
|
|
3448
3411
|
}
|
|
3449
3412
|
await db.updateTaskStatus(task2.id, "done");
|
|
3450
3413
|
await db.writeFeatureList(cwd2);
|
|
3451
|
-
console.log(
|
|
3414
|
+
console.log(pc15.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
3452
3415
|
} finally {
|
|
3453
3416
|
await db.close();
|
|
3454
3417
|
}
|
|
@@ -3456,9 +3419,9 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3456
3419
|
|
|
3457
3420
|
// src/commands/task/edit.ts
|
|
3458
3421
|
import * as p7 from "@clack/prompts";
|
|
3459
|
-
import
|
|
3422
|
+
import pc16 from "picocolors";
|
|
3460
3423
|
async function runTaskEdit(cwd2) {
|
|
3461
|
-
p7.intro(
|
|
3424
|
+
p7.intro(pc16.bold("agent-harness-kit \u2014 edit task"));
|
|
3462
3425
|
const config = await loadConfig(cwd2);
|
|
3463
3426
|
const db = await openDB(config, cwd2);
|
|
3464
3427
|
try {
|
|
@@ -3536,9 +3499,9 @@ async function runTaskEdit(cwd2) {
|
|
|
3536
3499
|
await db.updateTaskAcceptance(task2.id, newAcceptance);
|
|
3537
3500
|
await db.writeFeatureList(cwd2);
|
|
3538
3501
|
spinner6.stop("");
|
|
3539
|
-
console.log(
|
|
3502
|
+
console.log(pc16.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3540
3503
|
} catch (err) {
|
|
3541
|
-
spinner6.stop(
|
|
3504
|
+
spinner6.stop(pc16.red("Failed"));
|
|
3542
3505
|
p7.log.error(err instanceof Error ? err.message : String(err));
|
|
3543
3506
|
process.exit(1);
|
|
3544
3507
|
}
|
|
@@ -3549,12 +3512,12 @@ async function runTaskEdit(cwd2) {
|
|
|
3549
3512
|
|
|
3550
3513
|
// src/commands/task/list.ts
|
|
3551
3514
|
import Table2 from "cli-table3";
|
|
3552
|
-
import
|
|
3515
|
+
import pc17 from "picocolors";
|
|
3553
3516
|
var STATUS_COLOR2 = {
|
|
3554
|
-
pending: (s) =>
|
|
3555
|
-
in_progress: (s) =>
|
|
3556
|
-
done: (s) =>
|
|
3557
|
-
blocked: (s) =>
|
|
3517
|
+
pending: (s) => pc17.dim(s),
|
|
3518
|
+
in_progress: (s) => pc17.cyan(s),
|
|
3519
|
+
done: (s) => pc17.green(s),
|
|
3520
|
+
blocked: (s) => pc17.red(s)
|
|
3558
3521
|
};
|
|
3559
3522
|
async function runTaskList(cwd2, opts) {
|
|
3560
3523
|
const config = await loadConfig(cwd2);
|
|
@@ -3571,11 +3534,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
3571
3534
|
let msg = "No tasks";
|
|
3572
3535
|
if (filterStatus) msg += ` with status: ${filterStatus}`;
|
|
3573
3536
|
if (opts.archived) msg += " (archived)";
|
|
3574
|
-
console.log(
|
|
3537
|
+
console.log(pc17.dim(msg + "."));
|
|
3575
3538
|
return;
|
|
3576
3539
|
}
|
|
3577
3540
|
const table = new Table2({
|
|
3578
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
3541
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc17.bold(h)),
|
|
3579
3542
|
style: { head: [], border: [] }
|
|
3580
3543
|
});
|
|
3581
3544
|
for (const t of tasks) {
|
|
@@ -3586,7 +3549,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3586
3549
|
if (!opts.archived && !opts.includeArchived) {
|
|
3587
3550
|
const archivedTasks = await db.getArchivedTasks();
|
|
3588
3551
|
if (archivedTasks.length > 0) {
|
|
3589
|
-
console.log(
|
|
3552
|
+
console.log(pc17.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
|
|
3590
3553
|
}
|
|
3591
3554
|
}
|
|
3592
3555
|
} finally {
|
|
@@ -3594,43 +3557,6 @@ async function runTaskList(cwd2, opts) {
|
|
|
3594
3557
|
}
|
|
3595
3558
|
}
|
|
3596
3559
|
|
|
3597
|
-
// src/core/local-install-guard.ts
|
|
3598
|
-
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
3599
|
-
import { join as join20 } from "path";
|
|
3600
|
-
import pc17 from "picocolors";
|
|
3601
|
-
function isLocalInstallSatisfied(cwd2) {
|
|
3602
|
-
const selfPkgPath = join20(cwd2, "package.json");
|
|
3603
|
-
let projectPkg = null;
|
|
3604
|
-
if (existsSync18(selfPkgPath)) {
|
|
3605
|
-
try {
|
|
3606
|
-
const selfPkg = JSON.parse(readFileSync12(selfPkgPath, "utf8"));
|
|
3607
|
-
if (selfPkg?.name === pkg.name) return true;
|
|
3608
|
-
projectPkg = selfPkg;
|
|
3609
|
-
} catch {
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
const [scope, name] = pkg.name.split("/");
|
|
3613
|
-
const localPath = pkg.name.startsWith("@") ? join20(cwd2, "node_modules", scope, name) : join20(cwd2, "node_modules", pkg.name);
|
|
3614
|
-
if (existsSync18(localPath)) return true;
|
|
3615
|
-
const isPnp = existsSync18(join20(cwd2, ".pnp.cjs")) || existsSync18(join20(cwd2, ".pnp.loader.mjs"));
|
|
3616
|
-
if (isPnp && projectPkg) {
|
|
3617
|
-
const deps = {
|
|
3618
|
-
...projectPkg.dependencies ?? {},
|
|
3619
|
-
...projectPkg.devDependencies ?? {}
|
|
3620
|
-
};
|
|
3621
|
-
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
3622
|
-
}
|
|
3623
|
-
return false;
|
|
3624
|
-
}
|
|
3625
|
-
function printLocalInstallWarning() {
|
|
3626
|
-
console.error(pc17.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
|
|
3627
|
-
console.error(pc17.dim(" This is only a recommendation for reproducibility: pinning a local"));
|
|
3628
|
-
console.error(pc17.dim(" version keeps behavior consistent across your team and CI, instead of"));
|
|
3629
|
-
console.error(pc17.dim(" drifting with whatever version is installed globally on each machine."));
|
|
3630
|
-
console.error(pc17.dim(` Run: npm install --save-dev ${pkg.name}`));
|
|
3631
|
-
console.error(pc17.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
3560
|
// src/core/update-check.ts
|
|
3635
3561
|
import pc18 from "picocolors";
|
|
3636
3562
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
@@ -3672,7 +3598,10 @@ program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for mu
|
|
|
3672
3598
|
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
3673
3599
|
await runInit(cwd, opts);
|
|
3674
3600
|
});
|
|
3675
|
-
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").
|
|
3601
|
+
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").option(
|
|
3602
|
+
"--force",
|
|
3603
|
+
"Regenerate agent files that already exist, DESTROYING your customizations (a backup is written first). Without this flag, build only creates agent files that are missing and never modifies existing ones."
|
|
3604
|
+
).action(async (opts) => {
|
|
3676
3605
|
await runBuild(cwd, opts);
|
|
3677
3606
|
});
|
|
3678
3607
|
program.command("health").description("Run health.sh and report result").action(async () => {
|