@cardor/agent-harness-kit 1.10.4 → 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 +510 -576
- 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";
|
|
@@ -1736,8 +1759,7 @@ var taskTitleSchema = v2.pipe(
|
|
|
1736
1759
|
);
|
|
1737
1760
|
var taskDescriptionSchema = v2.pipe(
|
|
1738
1761
|
v2.string(),
|
|
1739
|
-
v2.nonEmpty("Description is required")
|
|
1740
|
-
v2.maxLength(1e3, "Description must be at most 1000 characters")
|
|
1762
|
+
v2.nonEmpty("Description is required")
|
|
1741
1763
|
);
|
|
1742
1764
|
|
|
1743
1765
|
// src/utils/form.ts
|
|
@@ -1756,13 +1778,52 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1756
1778
|
|
|
1757
1779
|
// src/commands/init-helpers.ts
|
|
1758
1780
|
import { randomUUID } from "crypto";
|
|
1759
|
-
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";
|
|
1760
1787
|
import { join as join13 } from "path";
|
|
1761
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
|
|
1762
1823
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1763
1824
|
try {
|
|
1764
|
-
const pkgPath2 =
|
|
1765
|
-
if (!
|
|
1825
|
+
const pkgPath2 = join14(cwd2, "package.json");
|
|
1826
|
+
if (!existsSync12(pkgPath2)) return null;
|
|
1766
1827
|
const content = readFileSync8(pkgPath2, "utf8");
|
|
1767
1828
|
const pkg2 = JSON.parse(content);
|
|
1768
1829
|
const name = pkg2?.name;
|
|
@@ -1773,10 +1834,11 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
1773
1834
|
}
|
|
1774
1835
|
}
|
|
1775
1836
|
function detectConfigExtension(cwd2) {
|
|
1837
|
+
if (!isLocalInstallSatisfied(cwd2)) return "json";
|
|
1776
1838
|
try {
|
|
1777
|
-
if (
|
|
1778
|
-
const pkgPath2 =
|
|
1779
|
-
if (!
|
|
1839
|
+
if (existsSync12(join14(cwd2, "tsconfig.json"))) return "ts";
|
|
1840
|
+
const pkgPath2 = join14(cwd2, "package.json");
|
|
1841
|
+
if (!existsSync12(pkgPath2)) return "mjs";
|
|
1780
1842
|
const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
|
|
1781
1843
|
if (pkg2?.type === "module") return "mjs";
|
|
1782
1844
|
} catch {
|
|
@@ -1784,7 +1846,6 @@ function detectConfigExtension(cwd2) {
|
|
|
1784
1846
|
return "mjs";
|
|
1785
1847
|
}
|
|
1786
1848
|
function applyConfigDefaults(params) {
|
|
1787
|
-
const models = params.models ?? {};
|
|
1788
1849
|
const scope = params.scope ?? "local";
|
|
1789
1850
|
const projectId = params.projectId ?? randomUUID();
|
|
1790
1851
|
const baseStorage = {
|
|
@@ -1807,22 +1868,10 @@ function applyConfigDefaults(params) {
|
|
|
1807
1868
|
docsPath: params.docsPath,
|
|
1808
1869
|
agentsMd: "./AGENTS.md"
|
|
1809
1870
|
},
|
|
1810
|
-
agents:
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
allowedPaths: [params.docsPath, "./src"],
|
|
1815
|
-
...models.explorer && { model: models.explorer }
|
|
1816
|
-
},
|
|
1817
|
-
builder: {
|
|
1818
|
-
instructionsPath: null,
|
|
1819
|
-
writablePaths: ["./src", "./tests"],
|
|
1820
|
-
...models.builder && { model: models.builder }
|
|
1821
|
-
},
|
|
1822
|
-
reviewer: { instructionsPath: null, ...models.reviewer && { model: models.reviewer } },
|
|
1823
|
-
...models.consultant && { consultant: { instructionsPath: null, model: models.consultant } },
|
|
1824
|
-
custom: []
|
|
1825
|
-
},
|
|
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.
|
|
1826
1875
|
database: { type: "sqlite" },
|
|
1827
1876
|
storage,
|
|
1828
1877
|
health: {
|
|
@@ -1841,27 +1890,27 @@ function stripAnsi(str2) {
|
|
|
1841
1890
|
function drawBox(lines) {
|
|
1842
1891
|
const width = Math.max(...lines.map((l) => stripAnsi(l).length));
|
|
1843
1892
|
const border = "\u2500".repeat(width);
|
|
1844
|
-
console.log(
|
|
1893
|
+
console.log(pc7.yellow(`\u250C${border}\u2510`));
|
|
1845
1894
|
for (const line of lines) {
|
|
1846
1895
|
const pad = width - stripAnsi(line).length;
|
|
1847
1896
|
const padStr = pad > 0 ? " ".repeat(pad) : "";
|
|
1848
|
-
console.log(
|
|
1897
|
+
console.log(pc7.yellow("\u2502") + line + padStr + pc7.yellow("\u2502"));
|
|
1849
1898
|
}
|
|
1850
|
-
console.log(
|
|
1899
|
+
console.log(pc7.yellow(`\u2514${border}\u2518`));
|
|
1851
1900
|
}
|
|
1852
1901
|
function printWelcomeMessage(projectName) {
|
|
1853
1902
|
const sep = "\u2500".repeat(38);
|
|
1854
1903
|
const lines = [
|
|
1855
|
-
` ${
|
|
1856
|
-
` ${
|
|
1857
|
-
` ${
|
|
1858
|
-
` ${
|
|
1859
|
-
` ${
|
|
1860
|
-
` ${
|
|
1861
|
-
` ${
|
|
1862
|
-
` ${
|
|
1863
|
-
` ${
|
|
1864
|
-
` ${
|
|
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")} `
|
|
1865
1914
|
];
|
|
1866
1915
|
console.log();
|
|
1867
1916
|
drawBox(lines);
|
|
@@ -1873,18 +1922,18 @@ async function runInit(cwd2, flags) {
|
|
|
1873
1922
|
const existingConfig = findConfigFile(cwd2);
|
|
1874
1923
|
if (existingConfig) {
|
|
1875
1924
|
console.log(
|
|
1876
|
-
|
|
1925
|
+
pc8.yellow("\u26A0") + " " + pc8.bold("Project already initialized.") + pc8.dim(` (${existingConfig})`)
|
|
1877
1926
|
);
|
|
1878
1927
|
console.log();
|
|
1879
|
-
console.log(
|
|
1928
|
+
console.log(pc8.dim("Suggested next steps:"));
|
|
1880
1929
|
console.log(
|
|
1881
|
-
" " +
|
|
1930
|
+
" " + pc8.cyan("ahk build") + pc8.dim(" \u2014 re-sync agent files after updating the library")
|
|
1882
1931
|
);
|
|
1883
|
-
console.log(" " +
|
|
1932
|
+
console.log(" " + pc8.cyan("ahk build --sync") + pc8.dim(" \u2014 also sync agent permissions"));
|
|
1884
1933
|
console.log(
|
|
1885
|
-
" " +
|
|
1934
|
+
" " + pc8.cyan("ahk reset") + pc8.dim(" \u2014 wipe and re-initialize from scratch")
|
|
1886
1935
|
);
|
|
1887
|
-
console.log(" " +
|
|
1936
|
+
console.log(" " + pc8.cyan("ahk dashboard") + pc8.dim(" \u2014 open the harness dashboard"));
|
|
1888
1937
|
process.exit(0);
|
|
1889
1938
|
}
|
|
1890
1939
|
const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
|
|
@@ -1936,61 +1985,6 @@ async function runInit(cwd2, flags) {
|
|
|
1936
1985
|
}
|
|
1937
1986
|
provider = val;
|
|
1938
1987
|
}
|
|
1939
|
-
const AGENT_LABELS = [
|
|
1940
|
-
{ key: "lead", label: "Lead" },
|
|
1941
|
-
{ key: "explorer", label: "Explorer" },
|
|
1942
|
-
{ key: "consultant", label: "Consultant" },
|
|
1943
|
-
{ key: "builder", label: "Builder" },
|
|
1944
|
-
{ key: "reviewer", label: "Reviewer" }
|
|
1945
|
-
];
|
|
1946
|
-
const modelOverrides = {};
|
|
1947
|
-
if (provider === "claude-code" || provider === "codex-cli") {
|
|
1948
|
-
const wantsModelCustomization = await p3.confirm({
|
|
1949
|
-
message: "Customize the model per agent?",
|
|
1950
|
-
initialValue: false
|
|
1951
|
-
});
|
|
1952
|
-
if (p3.isCancel(wantsModelCustomization)) {
|
|
1953
|
-
p3.cancel("Cancelled.");
|
|
1954
|
-
process.exit(0);
|
|
1955
|
-
}
|
|
1956
|
-
if (wantsModelCustomization) {
|
|
1957
|
-
if (provider === "claude-code") {
|
|
1958
|
-
for (const agent of AGENT_LABELS) {
|
|
1959
|
-
const val = await p3.select({
|
|
1960
|
-
message: `Model for ${agent.label}`,
|
|
1961
|
-
options: [
|
|
1962
|
-
{ value: "inherit", label: "inherit (default)" },
|
|
1963
|
-
{ value: "haiku", label: "haiku" },
|
|
1964
|
-
{ value: "sonnet", label: "sonnet" },
|
|
1965
|
-
{ value: "opus", label: "opus" },
|
|
1966
|
-
{ value: "fable", label: "fable" }
|
|
1967
|
-
],
|
|
1968
|
-
initialValue: "inherit"
|
|
1969
|
-
});
|
|
1970
|
-
if (p3.isCancel(val)) {
|
|
1971
|
-
p3.cancel("Cancelled.");
|
|
1972
|
-
process.exit(0);
|
|
1973
|
-
}
|
|
1974
|
-
modelOverrides[agent.key] = val;
|
|
1975
|
-
}
|
|
1976
|
-
} else {
|
|
1977
|
-
for (const agent of AGENT_LABELS) {
|
|
1978
|
-
const val = await p3.text({
|
|
1979
|
-
message: `Model for ${agent.label} (Codex does not validate this value)`,
|
|
1980
|
-
placeholder: "e.g. gpt-5 (empty or <3 chars = no override)"
|
|
1981
|
-
});
|
|
1982
|
-
if (p3.isCancel(val)) {
|
|
1983
|
-
p3.cancel("Cancelled.");
|
|
1984
|
-
process.exit(0);
|
|
1985
|
-
}
|
|
1986
|
-
const trimmed = val.trim();
|
|
1987
|
-
if (trimmed.length >= 3) {
|
|
1988
|
-
modelOverrides[agent.key] = trimmed;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
1988
|
let docsPath;
|
|
1995
1989
|
if (flags.docs) {
|
|
1996
1990
|
docsPath = flags.docs;
|
|
@@ -2091,14 +2085,13 @@ async function runInit(cwd2, flags) {
|
|
|
2091
2085
|
provider,
|
|
2092
2086
|
docsPath,
|
|
2093
2087
|
tasksAdapter,
|
|
2094
|
-
models: modelOverrides,
|
|
2095
2088
|
scope: storageScope
|
|
2096
2089
|
});
|
|
2097
2090
|
const materializer = getMaterializer(provider);
|
|
2098
2091
|
const installDir = cwd2;
|
|
2099
2092
|
configExt = detectConfigExtension(cwd2);
|
|
2100
2093
|
const configFileName = `agent-harness-kit.config.${configExt}`;
|
|
2101
|
-
const templateFn = configExt === "ts" ? configTs : configExt === "mjs" ? configMjs : configCjs;
|
|
2094
|
+
const templateFn = configExt === "json" ? configJson : configExt === "ts" ? configTs : configExt === "mjs" ? configMjs : configCjs;
|
|
2102
2095
|
const configContent = templateFn({
|
|
2103
2096
|
name,
|
|
2104
2097
|
description,
|
|
@@ -2106,12 +2099,11 @@ async function runInit(cwd2, flags) {
|
|
|
2106
2099
|
docsPath,
|
|
2107
2100
|
tasksAdapter,
|
|
2108
2101
|
port: config.tools.mcp.port,
|
|
2109
|
-
models: modelOverrides,
|
|
2110
2102
|
scope: config.storage.scope,
|
|
2111
2103
|
projectId: config.storage.projectId
|
|
2112
2104
|
});
|
|
2113
|
-
writeFileSync8(
|
|
2114
|
-
mkdirSync7(
|
|
2105
|
+
writeFileSync8(join15(installDir, configFileName), configContent, "utf8");
|
|
2106
|
+
mkdirSync7(join15(installDir, config.storage.dir), { recursive: true });
|
|
2115
2107
|
const db = await openDB(config, installDir);
|
|
2116
2108
|
await db.writeStorageState(installDir);
|
|
2117
2109
|
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
@@ -2131,39 +2123,39 @@ async function runInit(cwd2, flags) {
|
|
|
2131
2123
|
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
2132
2124
|
throw err;
|
|
2133
2125
|
}
|
|
2134
|
-
console.log(
|
|
2126
|
+
console.log(pc8.green("\u2713 Scaffolded harness in current directory"));
|
|
2135
2127
|
const agentsDir = provider === "claude-code" ? ".claude/agents/" : ".opencode/agents/";
|
|
2136
2128
|
const mcpFile = provider === "claude-code" ? ".claude/mcp.json" : "./opencode.json";
|
|
2137
2129
|
console.log("");
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2140
|
-
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"));
|
|
2141
2133
|
console.log(
|
|
2142
|
-
|
|
2134
|
+
pc8.green(
|
|
2143
2135
|
storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/harness.db" : "\u2713 .harness/harness.db"
|
|
2144
2136
|
)
|
|
2145
2137
|
);
|
|
2146
2138
|
console.log(
|
|
2147
|
-
|
|
2139
|
+
pc8.green(
|
|
2148
2140
|
storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/current.md" : "\u2713 .harness/current.md"
|
|
2149
2141
|
)
|
|
2150
2142
|
);
|
|
2151
|
-
console.log(
|
|
2152
|
-
console.log(
|
|
2153
|
-
console.log(
|
|
2154
|
-
console.log(
|
|
2155
|
-
console.log(
|
|
2156
|
-
console.log(
|
|
2157
|
-
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"));
|
|
2158
2150
|
console.log("");
|
|
2159
|
-
console.log(
|
|
2160
|
-
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`);
|
|
2161
2153
|
console.log(
|
|
2162
|
-
|
|
2154
|
+
pc8.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc8.cyan("https://github.com/safishamsi/graphify")}`
|
|
2163
2155
|
);
|
|
2164
2156
|
const recommendations = [
|
|
2165
2157
|
` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
|
|
2166
|
-
` Learn more: ${
|
|
2158
|
+
` Learn more: ${pc8.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
|
|
2167
2159
|
];
|
|
2168
2160
|
console.log("");
|
|
2169
2161
|
drawBox(recommendations);
|
|
@@ -2171,7 +2163,7 @@ async function runInit(cwd2, flags) {
|
|
|
2171
2163
|
|
|
2172
2164
|
// src/commands/migrate.ts
|
|
2173
2165
|
import * as p4 from "@clack/prompts";
|
|
2174
|
-
import
|
|
2166
|
+
import pc9 from "picocolors";
|
|
2175
2167
|
async function runMigrate(cwd2, opts) {
|
|
2176
2168
|
const config = await loadConfig(cwd2);
|
|
2177
2169
|
let target;
|
|
@@ -2193,7 +2185,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2193
2185
|
target = val;
|
|
2194
2186
|
}
|
|
2195
2187
|
if (target === config.provider) {
|
|
2196
|
-
console.log(
|
|
2188
|
+
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2197
2189
|
return;
|
|
2198
2190
|
}
|
|
2199
2191
|
const spinner6 = p4.spinner();
|
|
@@ -2201,21 +2193,21 @@ async function runMigrate(cwd2, opts) {
|
|
|
2201
2193
|
try {
|
|
2202
2194
|
const targetMaterializer = getMaterializer(target);
|
|
2203
2195
|
await targetMaterializer.build(config, cwd2);
|
|
2204
|
-
spinner6.stop(
|
|
2196
|
+
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2205
2197
|
p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2206
2198
|
p4.log.warn(`Then run: ahk build`);
|
|
2207
2199
|
} catch (err) {
|
|
2208
|
-
spinner6.stop(
|
|
2200
|
+
spinner6.stop(pc9.red("Migration failed"));
|
|
2209
2201
|
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
2210
2202
|
process.exit(1);
|
|
2211
2203
|
}
|
|
2212
2204
|
}
|
|
2213
2205
|
|
|
2214
2206
|
// src/commands/migrate-storage.ts
|
|
2215
|
-
import { copyFileSync, existsSync as
|
|
2207
|
+
import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
|
|
2216
2208
|
import { homedir as homedir3 } from "os";
|
|
2217
|
-
import { dirname as dirname7, join as
|
|
2218
|
-
import
|
|
2209
|
+
import { dirname as dirname7, join as join16, resolve as resolve7 } from "path";
|
|
2210
|
+
import pc10 from "picocolors";
|
|
2219
2211
|
function log5(msg) {
|
|
2220
2212
|
console.log(msg);
|
|
2221
2213
|
}
|
|
@@ -2226,14 +2218,14 @@ function defaultMarkdownPathForConfig(config) {
|
|
|
2226
2218
|
return config.storage.scope === "local" ? config.storage.markdownFallback.path : DEFAULT_MARKDOWN_PATH;
|
|
2227
2219
|
}
|
|
2228
2220
|
function currentMdPathForScope(scope, config, cwd2, homeDir) {
|
|
2229
|
-
return scope === "global" ?
|
|
2221
|
+
return scope === "global" ? join16(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve7(cwd2, defaultMarkdownPathForConfig(config));
|
|
2230
2222
|
}
|
|
2231
2223
|
function defaultSqlitePathForConfig(config) {
|
|
2232
2224
|
return config.storage.scope === "local" && config.database.type === "sqlite" ? config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH : DEFAULT_SQLITE_PATH;
|
|
2233
2225
|
}
|
|
2234
2226
|
async function backupDestination(cwd2, storageDir, data) {
|
|
2235
2227
|
const backupsDir = resolve7(cwd2, storageDir, "backups");
|
|
2236
|
-
const path =
|
|
2228
|
+
const path = join16(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
2237
2229
|
try {
|
|
2238
2230
|
mkdirSync8(backupsDir, { recursive: true });
|
|
2239
2231
|
writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
@@ -2248,11 +2240,11 @@ function copySqliteFile(srcPath, destPath) {
|
|
|
2248
2240
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
2249
2241
|
copyFileSync(srcPath, destPath);
|
|
2250
2242
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2251
|
-
if (
|
|
2243
|
+
if (existsSync13(`${srcPath}${suffix}`)) {
|
|
2252
2244
|
copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
|
|
2253
2245
|
}
|
|
2254
2246
|
}
|
|
2255
|
-
if (!
|
|
2247
|
+
if (!existsSync13(destPath)) {
|
|
2256
2248
|
throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
|
|
2257
2249
|
}
|
|
2258
2250
|
}
|
|
@@ -2280,13 +2272,13 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2280
2272
|
} finally {
|
|
2281
2273
|
await db.close();
|
|
2282
2274
|
}
|
|
2283
|
-
log5(
|
|
2275
|
+
log5(pc10.dim("storage-state.json was missing; no data found at either candidate location. Nothing to migrate \u2014 state recorded."));
|
|
2284
2276
|
return;
|
|
2285
2277
|
}
|
|
2286
2278
|
realScope = localCount > 0 ? "local" : "global";
|
|
2287
2279
|
realDbType = "sqlite";
|
|
2288
2280
|
log5(
|
|
2289
|
-
|
|
2281
|
+
pc10.yellow(
|
|
2290
2282
|
`storage-state.json was missing. Detected real data at ${realScope} sqlite location (${realScope === "local" ? localPath : globalPath}) \u2014 using it as the migration source.`
|
|
2291
2283
|
)
|
|
2292
2284
|
);
|
|
@@ -2297,7 +2289,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2297
2289
|
const desiredScope = config.storage.scope;
|
|
2298
2290
|
const desiredDbType = config.database.type;
|
|
2299
2291
|
if (realScope === desiredScope && realDbType === desiredDbType) {
|
|
2300
|
-
log5(
|
|
2292
|
+
log5(pc10.green(`\u2713 Storage already matches config (scope=${desiredScope}, database=${desiredDbType}) \u2014 nothing to migrate.`));
|
|
2301
2293
|
return;
|
|
2302
2294
|
}
|
|
2303
2295
|
if (realDbType !== "sqlite") {
|
|
@@ -2311,7 +2303,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2311
2303
|
return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
|
|
2312
2304
|
}
|
|
2313
2305
|
async function probeTaskCount(dbPath) {
|
|
2314
|
-
if (!
|
|
2306
|
+
if (!existsSync13(dbPath)) return 0;
|
|
2315
2307
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
2316
2308
|
const driver = new SQLiteDriver(dbPath);
|
|
2317
2309
|
try {
|
|
@@ -2328,10 +2320,10 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2328
2320
|
const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
|
|
2329
2321
|
const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
|
|
2330
2322
|
const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
|
|
2331
|
-
if (!
|
|
2323
|
+
if (!existsSync13(srcDb)) {
|
|
2332
2324
|
fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
|
|
2333
2325
|
}
|
|
2334
|
-
const destExists =
|
|
2326
|
+
const destExists = existsSync13(destDb);
|
|
2335
2327
|
if (destExists) {
|
|
2336
2328
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
2337
2329
|
const destDriver = new SQLiteDriver(destDb);
|
|
@@ -2360,36 +2352,36 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2360
2352
|
await backupDriver.close();
|
|
2361
2353
|
}
|
|
2362
2354
|
const backupPath = await backupDestination(cwd2, config.storage.dir, data);
|
|
2363
|
-
log5(
|
|
2355
|
+
log5(pc10.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
|
|
2364
2356
|
}
|
|
2365
2357
|
}
|
|
2366
2358
|
if (opts.dryRun) {
|
|
2367
|
-
log5(
|
|
2359
|
+
log5(pc10.dim(`[dry-run] Would copy ${srcDb} \u2192 ${destDb} (scope ${fromScope} \u2192 ${toScope}), and move current.md.`));
|
|
2368
2360
|
return;
|
|
2369
2361
|
}
|
|
2370
2362
|
copySqliteFile(srcDb, destDb);
|
|
2371
|
-
log5(
|
|
2372
|
-
if (
|
|
2363
|
+
log5(pc10.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
|
|
2364
|
+
if (existsSync13(srcMd)) {
|
|
2373
2365
|
mkdirSync8(dirname7(destMd), { recursive: true });
|
|
2374
2366
|
copyFileSync(srcMd, destMd);
|
|
2375
|
-
log5(
|
|
2367
|
+
log5(pc10.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
|
|
2376
2368
|
}
|
|
2377
2369
|
rmSync(srcDb, { force: true });
|
|
2378
2370
|
rmSync(`${srcDb}-wal`, { force: true });
|
|
2379
2371
|
rmSync(`${srcDb}-shm`, { force: true });
|
|
2380
|
-
if (
|
|
2372
|
+
if (existsSync13(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
|
|
2381
2373
|
const db = await openDB(config, cwd2, homeDir);
|
|
2382
2374
|
try {
|
|
2383
2375
|
await db.writeStorageState(cwd2);
|
|
2384
2376
|
} finally {
|
|
2385
2377
|
await db.close();
|
|
2386
2378
|
}
|
|
2387
|
-
log5(
|
|
2379
|
+
log5(pc10.green(`\u2713 Storage migrated: scope ${fromScope} \u2192 ${toScope}`));
|
|
2388
2380
|
}
|
|
2389
2381
|
async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
2390
2382
|
const sqlitePath = defaultSqlitePathForConfig(config);
|
|
2391
2383
|
const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
|
|
2392
|
-
if (!
|
|
2384
|
+
if (!existsSync13(srcPath)) {
|
|
2393
2385
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2394
2386
|
}
|
|
2395
2387
|
const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
|
|
@@ -2425,7 +2417,7 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2425
2417
|
}
|
|
2426
2418
|
if (opts.dryRun) {
|
|
2427
2419
|
log5(
|
|
2428
|
-
|
|
2420
|
+
pc10.dim(
|
|
2429
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)"}.`
|
|
2430
2422
|
)
|
|
2431
2423
|
);
|
|
@@ -2435,34 +2427,34 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2435
2427
|
if (!destEmpty && opts.force) {
|
|
2436
2428
|
const currentDestData = await destDb.exportJson();
|
|
2437
2429
|
backupPath = await backupDestination(cwd2, config.storage.dir, currentDestData);
|
|
2438
|
-
log5(
|
|
2430
|
+
log5(pc10.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
|
|
2439
2431
|
}
|
|
2440
2432
|
await destDb.importFullExport(sourceData, config.database.type, { truncateFirst: !destEmpty });
|
|
2441
2433
|
await destDb.writeStorageState(cwd2);
|
|
2442
2434
|
log5(
|
|
2443
|
-
|
|
2435
|
+
pc10.green(
|
|
2444
2436
|
`\u2713 Migrated ${sourceData.tasks.length} task(s), ${sourceData.actions.length} action(s) from sqlite (${sourceScope}) \u2192 ${config.database.type}.`
|
|
2445
2437
|
)
|
|
2446
2438
|
);
|
|
2447
|
-
if (backupPath) log5(
|
|
2448
|
-
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.`));
|
|
2449
2441
|
} finally {
|
|
2450
2442
|
await destDb.close();
|
|
2451
2443
|
}
|
|
2452
2444
|
}
|
|
2453
2445
|
|
|
2454
2446
|
// src/commands/reset.ts
|
|
2455
|
-
import { existsSync as
|
|
2447
|
+
import { existsSync as existsSync14, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2456
2448
|
import { homedir as homedir4 } from "os";
|
|
2457
|
-
import { join as
|
|
2449
|
+
import { join as join17, resolve as resolve8 } from "path";
|
|
2458
2450
|
import * as p5 from "@clack/prompts";
|
|
2459
|
-
import
|
|
2451
|
+
import pc11 from "picocolors";
|
|
2460
2452
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2461
2453
|
async function resetAgentMds(cwd2, provider) {
|
|
2462
2454
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
2463
2455
|
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2464
|
-
if (!
|
|
2465
|
-
console.log(
|
|
2456
|
+
if (!existsSync14(agentDirPath)) {
|
|
2457
|
+
console.log(pc11.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2466
2458
|
return;
|
|
2467
2459
|
}
|
|
2468
2460
|
const existingFiles = [];
|
|
@@ -2474,11 +2466,11 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2474
2466
|
}
|
|
2475
2467
|
}
|
|
2476
2468
|
} catch {
|
|
2477
|
-
console.log(
|
|
2469
|
+
console.log(pc11.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2478
2470
|
return;
|
|
2479
2471
|
}
|
|
2480
2472
|
if (existingFiles.length === 0) {
|
|
2481
|
-
console.log(
|
|
2473
|
+
console.log(pc11.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2482
2474
|
return;
|
|
2483
2475
|
}
|
|
2484
2476
|
for (const file of existingFiles) {
|
|
@@ -2487,19 +2479,19 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2487
2479
|
initialValue: true
|
|
2488
2480
|
});
|
|
2489
2481
|
if (p5.isCancel(confirm3)) {
|
|
2490
|
-
console.log(
|
|
2482
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2491
2483
|
return;
|
|
2492
2484
|
}
|
|
2493
2485
|
if (confirm3) {
|
|
2494
2486
|
try {
|
|
2495
|
-
const filePath =
|
|
2487
|
+
const filePath = join17(agentDirPath, file);
|
|
2496
2488
|
rmSync2(filePath, { force: true });
|
|
2497
|
-
console.log(
|
|
2489
|
+
console.log(pc11.green(` Removed ${file}`));
|
|
2498
2490
|
} catch {
|
|
2499
|
-
console.error(
|
|
2491
|
+
console.error(pc11.red(` Failed to remove ${file}`));
|
|
2500
2492
|
}
|
|
2501
2493
|
} else {
|
|
2502
|
-
console.log(
|
|
2494
|
+
console.log(pc11.cyan(` Skipped ${file}`));
|
|
2503
2495
|
}
|
|
2504
2496
|
}
|
|
2505
2497
|
}
|
|
@@ -2508,7 +2500,7 @@ async function runReset(cwd2, opts) {
|
|
|
2508
2500
|
try {
|
|
2509
2501
|
config = await loadConfig(cwd2);
|
|
2510
2502
|
} catch {
|
|
2511
|
-
console.error(
|
|
2503
|
+
console.error(pc11.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2512
2504
|
process.exit(1);
|
|
2513
2505
|
}
|
|
2514
2506
|
const storageDir = config.storage.dir || ".harness";
|
|
@@ -2517,12 +2509,12 @@ async function runReset(cwd2, opts) {
|
|
|
2517
2509
|
let resetDb = false;
|
|
2518
2510
|
let resetFeatureList = false;
|
|
2519
2511
|
let resetAgentMdsFlag = false;
|
|
2520
|
-
if (dbPath &&
|
|
2512
|
+
if (dbPath && existsSync14(dbPath)) {
|
|
2521
2513
|
if (opts.force) {
|
|
2522
2514
|
resetDb = true;
|
|
2523
2515
|
} else {
|
|
2524
2516
|
if (config.database.type !== "sqlite") {
|
|
2525
|
-
console.log(
|
|
2517
|
+
console.log(pc11.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2526
2518
|
resetDb = false;
|
|
2527
2519
|
} else {
|
|
2528
2520
|
const confirm3 = await p5.confirm({
|
|
@@ -2530,16 +2522,16 @@ async function runReset(cwd2, opts) {
|
|
|
2530
2522
|
initialValue: true
|
|
2531
2523
|
});
|
|
2532
2524
|
if (p5.isCancel(confirm3)) {
|
|
2533
|
-
console.log(
|
|
2525
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2534
2526
|
return;
|
|
2535
2527
|
}
|
|
2536
2528
|
resetDb = confirm3;
|
|
2537
2529
|
}
|
|
2538
2530
|
}
|
|
2539
2531
|
} else if (!dbPath) {
|
|
2540
|
-
console.log(
|
|
2532
|
+
console.log(pc11.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2541
2533
|
}
|
|
2542
|
-
if (
|
|
2534
|
+
if (existsSync14(featureListPath)) {
|
|
2543
2535
|
if (opts.force) {
|
|
2544
2536
|
resetFeatureList = true;
|
|
2545
2537
|
} else {
|
|
@@ -2548,7 +2540,7 @@ async function runReset(cwd2, opts) {
|
|
|
2548
2540
|
initialValue: true
|
|
2549
2541
|
});
|
|
2550
2542
|
if (p5.isCancel(confirm3)) {
|
|
2551
|
-
console.log(
|
|
2543
|
+
console.log(pc11.red(" Cancelled by user."));
|
|
2552
2544
|
return;
|
|
2553
2545
|
}
|
|
2554
2546
|
resetFeatureList = confirm3;
|
|
@@ -2562,17 +2554,17 @@ async function runReset(cwd2, opts) {
|
|
|
2562
2554
|
rmSync2(dbPath, { force: true });
|
|
2563
2555
|
rmSync2(`${dbPath}-wal`, { force: true });
|
|
2564
2556
|
rmSync2(`${dbPath}-shm`, { force: true });
|
|
2565
|
-
console.log(
|
|
2557
|
+
console.log(pc11.green(` \u2713 Removed ${dbPath}`));
|
|
2566
2558
|
} catch {
|
|
2567
|
-
console.error(
|
|
2559
|
+
console.error(pc11.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2568
2560
|
}
|
|
2569
2561
|
}
|
|
2570
2562
|
if (resetFeatureList) {
|
|
2571
2563
|
try {
|
|
2572
2564
|
rmSync2(featureListPath, { force: true });
|
|
2573
|
-
console.log(
|
|
2565
|
+
console.log(pc11.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2574
2566
|
} catch {
|
|
2575
|
-
console.error(
|
|
2567
|
+
console.error(pc11.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2576
2568
|
}
|
|
2577
2569
|
}
|
|
2578
2570
|
if (resetAgentMdsFlag) {
|
|
@@ -2580,16 +2572,16 @@ async function runReset(cwd2, opts) {
|
|
|
2580
2572
|
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2581
2573
|
}
|
|
2582
2574
|
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2583
|
-
console.log(
|
|
2575
|
+
console.log(pc11.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2584
2576
|
return;
|
|
2585
2577
|
}
|
|
2586
2578
|
console.log("");
|
|
2587
|
-
console.log(
|
|
2579
|
+
console.log(pc11.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2588
2580
|
}
|
|
2589
2581
|
|
|
2590
2582
|
// src/core/mcp-server.ts
|
|
2591
|
-
import { existsSync as
|
|
2592
|
-
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";
|
|
2593
2585
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2594
2586
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2595
2587
|
import {
|
|
@@ -2598,45 +2590,20 @@ import {
|
|
|
2598
2590
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2599
2591
|
|
|
2600
2592
|
// src/core/permissions-check.ts
|
|
2601
|
-
import { existsSync as
|
|
2602
|
-
import { join as
|
|
2603
|
-
var
|
|
2604
|
-
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
2605
|
-
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
2606
|
-
consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
|
|
2607
|
-
builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
|
|
2608
|
-
reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
|
|
2609
|
-
};
|
|
2610
|
-
function parseToolsFromFrontmatter(content) {
|
|
2611
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/m);
|
|
2612
|
-
if (!match) return [];
|
|
2613
|
-
const fm = match[1];
|
|
2614
|
-
const toolsMatch = fm.match(/^tools:\n((?: - [^\n]+\n?)*)/m);
|
|
2615
|
-
if (!toolsMatch) return [];
|
|
2616
|
-
return toolsMatch[1].split("\n").map((l) => l.trim().replace(/^- /, "")).filter((l) => l.startsWith("mcp__"));
|
|
2617
|
-
}
|
|
2593
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2594
|
+
import { join as join18 } from "path";
|
|
2595
|
+
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2618
2596
|
function checkPermissionsSync(cwd2, config) {
|
|
2619
2597
|
if (config.provider !== "claude-code") {
|
|
2620
2598
|
return { in_sync: true };
|
|
2621
2599
|
}
|
|
2622
2600
|
const agents = {};
|
|
2623
2601
|
let in_sync = true;
|
|
2624
|
-
for (const agent of
|
|
2625
|
-
const filePath =
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
in_sync = false;
|
|
2630
|
-
continue;
|
|
2631
|
-
}
|
|
2632
|
-
const content = readFileSync9(filePath, "utf-8");
|
|
2633
|
-
const installed = parseToolsFromFrontmatter(content);
|
|
2634
|
-
const canonical = CANONICAL[agent];
|
|
2635
|
-
const missing = canonical.filter((t) => !installed.includes(t));
|
|
2636
|
-
const extra = installed.filter((t) => !canonical.includes(t));
|
|
2637
|
-
const ok3 = missing.length === 0 && extra.length === 0;
|
|
2638
|
-
if (!ok3) in_sync = false;
|
|
2639
|
-
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" };
|
|
2640
2607
|
}
|
|
2641
2608
|
return { in_sync, agents };
|
|
2642
2609
|
}
|
|
@@ -2670,7 +2637,10 @@ var TOOLS = [
|
|
|
2670
2637
|
type: "string",
|
|
2671
2638
|
description: "Section name: result | tools_used | blockers | next_steps | <custom>. Do NOT use files_modified to track files \u2014 it is stored as plain text only. Use actions.record_file instead."
|
|
2672
2639
|
},
|
|
2673
|
-
content: {
|
|
2640
|
+
content: {
|
|
2641
|
+
type: "string",
|
|
2642
|
+
description: "Content for this section. No length limit \u2014 include all information that's relevant and necessary, but avoid unnecessary padding to prevent context bottlenecks between agents."
|
|
2643
|
+
}
|
|
2674
2644
|
},
|
|
2675
2645
|
required: ["actionId", "sectionType", "content"]
|
|
2676
2646
|
}
|
|
@@ -2808,11 +2778,14 @@ var TOOLS = [
|
|
|
2808
2778
|
type: "string",
|
|
2809
2779
|
description: "URL-safe identifier (lowercase, hyphens). Auto-derived from title if omitted."
|
|
2810
2780
|
},
|
|
2811
|
-
description: {
|
|
2781
|
+
description: {
|
|
2782
|
+
type: "string",
|
|
2783
|
+
description: "Longer description of the task goal. No length limit \u2014 include all information that's relevant and necessary, but avoid unnecessary padding to prevent context bottlenecks between agents."
|
|
2784
|
+
},
|
|
2812
2785
|
acceptance: {
|
|
2813
2786
|
type: "array",
|
|
2814
2787
|
items: { type: "string" },
|
|
2815
|
-
description: "List of acceptance criteria (plain sentences)"
|
|
2788
|
+
description: "List of acceptance criteria (plain sentences). No length limit \u2014 include all information that's relevant and necessary, but avoid unnecessary padding to prevent context bottlenecks between agents."
|
|
2816
2789
|
}
|
|
2817
2790
|
},
|
|
2818
2791
|
required: ["title"]
|
|
@@ -2880,7 +2853,7 @@ var TOOLS = [
|
|
|
2880
2853
|
},
|
|
2881
2854
|
{
|
|
2882
2855
|
name: "permissions.check",
|
|
2883
|
-
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.',
|
|
2884
2857
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2885
2858
|
},
|
|
2886
2859
|
{
|
|
@@ -3049,19 +3022,19 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3049
3022
|
return ok2(JSON.stringify(result, null, 2));
|
|
3050
3023
|
}
|
|
3051
3024
|
case "deps.snapshot": {
|
|
3052
|
-
const pkgPath2 =
|
|
3053
|
-
if (!
|
|
3025
|
+
const pkgPath2 = join19(cwd2, "package.json");
|
|
3026
|
+
if (!existsSync16(pkgPath2)) {
|
|
3054
3027
|
return ok2("package.json not found in project root", true);
|
|
3055
3028
|
}
|
|
3056
|
-
const pkg2 = JSON.parse(
|
|
3029
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
3057
3030
|
const snapshot = {
|
|
3058
3031
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3059
3032
|
dependencies: pkg2.dependencies ?? {},
|
|
3060
3033
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3061
3034
|
};
|
|
3062
|
-
const harnessDir =
|
|
3035
|
+
const harnessDir = join19(cwd2, ".harness");
|
|
3063
3036
|
mkdirSync9(harnessDir, { recursive: true });
|
|
3064
|
-
writeFileSync10(
|
|
3037
|
+
writeFileSync10(join19(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3065
3038
|
return ok2(
|
|
3066
3039
|
JSON.stringify({
|
|
3067
3040
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3070,12 +3043,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3070
3043
|
);
|
|
3071
3044
|
}
|
|
3072
3045
|
case "deps.check": {
|
|
3073
|
-
const pkgPath2 =
|
|
3074
|
-
const lockPath =
|
|
3075
|
-
if (!
|
|
3046
|
+
const pkgPath2 = join19(cwd2, "package.json");
|
|
3047
|
+
const lockPath = join19(cwd2, ".harness", "deps-lock.json");
|
|
3048
|
+
if (!existsSync16(pkgPath2)) {
|
|
3076
3049
|
return ok2("package.json not found in project root", true);
|
|
3077
3050
|
}
|
|
3078
|
-
if (!
|
|
3051
|
+
if (!existsSync16(lockPath)) {
|
|
3079
3052
|
return ok2(
|
|
3080
3053
|
JSON.stringify({
|
|
3081
3054
|
status: "no-snapshot",
|
|
@@ -3083,8 +3056,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3083
3056
|
})
|
|
3084
3057
|
);
|
|
3085
3058
|
}
|
|
3086
|
-
const pkg2 = JSON.parse(
|
|
3087
|
-
const lock = JSON.parse(
|
|
3059
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
3060
|
+
const lock = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
3088
3061
|
const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
|
|
3089
3062
|
const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
|
|
3090
3063
|
const added = [];
|
|
@@ -3126,9 +3099,10 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3126
3099
|
const status = await getDoctorStatus(cwd2);
|
|
3127
3100
|
const result = {
|
|
3128
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.
|
|
3129
3104
|
agents: {
|
|
3130
3105
|
missing: status.agents.filter((a) => a.status === "missing").map((a) => a.name),
|
|
3131
|
-
outdated: status.agents.filter((a) => a.status === "outdated").map((a) => a.name),
|
|
3132
3106
|
ok: status.agents.filter((a) => a.status === "ok").map((a) => a.name)
|
|
3133
3107
|
},
|
|
3134
3108
|
skills: {
|
|
@@ -3151,7 +3125,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
3151
3125
|
for (const file of files) {
|
|
3152
3126
|
if (results.length >= maxResults) break;
|
|
3153
3127
|
try {
|
|
3154
|
-
const content =
|
|
3128
|
+
const content = readFileSync9(file, "utf8");
|
|
3155
3129
|
const lines = content.split("\n");
|
|
3156
3130
|
for (let i = 0; i < lines.length; i++) {
|
|
3157
3131
|
const lower = lines[i].toLowerCase();
|
|
@@ -3176,7 +3150,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3176
3150
|
const files = [];
|
|
3177
3151
|
try {
|
|
3178
3152
|
for (const entry of readdirSync2(dir)) {
|
|
3179
|
-
const full =
|
|
3153
|
+
const full = join19(dir, entry);
|
|
3180
3154
|
const stat = statSync(full);
|
|
3181
3155
|
if (stat.isDirectory()) {
|
|
3182
3156
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3212,14 +3186,8 @@ async function runServe(cwd2, opts) {
|
|
|
3212
3186
|
`);
|
|
3213
3187
|
const syncResult = checkPermissionsSync(cwd2, config);
|
|
3214
3188
|
if (!syncResult.in_sync && syncResult.agents) {
|
|
3215
|
-
const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name
|
|
3216
|
-
|
|
3217
|
-
if (r.missing.length) parts.push(`missing: ${r.missing.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
3218
|
-
if (r.extra.length) parts.push(`extra: ${r.extra.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
3219
|
-
return `${name} (${parts.join("; ")})`;
|
|
3220
|
-
}).join("\n ");
|
|
3221
|
-
process.stderr.write(`[agent-harness-kit] Agent permissions out of sync. Run: ahk build --sync
|
|
3222
|
-
${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
|
|
3223
3191
|
`);
|
|
3224
3192
|
}
|
|
3225
3193
|
await startMcpServer(config, cwd2);
|
|
@@ -3227,12 +3195,12 @@ async function runServe(cwd2, opts) {
|
|
|
3227
3195
|
|
|
3228
3196
|
// src/commands/status.ts
|
|
3229
3197
|
import Table from "cli-table3";
|
|
3230
|
-
import
|
|
3198
|
+
import pc12 from "picocolors";
|
|
3231
3199
|
var STATUS_COLOR = {
|
|
3232
|
-
pending: (s) =>
|
|
3233
|
-
in_progress: (s) =>
|
|
3234
|
-
done: (s) =>
|
|
3235
|
-
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)
|
|
3236
3204
|
};
|
|
3237
3205
|
async function runStatus(cwd2, opts) {
|
|
3238
3206
|
const config = await loadConfig(cwd2);
|
|
@@ -3253,11 +3221,11 @@ async function runStatus(cwd2, opts) {
|
|
|
3253
3221
|
return;
|
|
3254
3222
|
}
|
|
3255
3223
|
if (tasks.length === 0) {
|
|
3256
|
-
console.log(
|
|
3224
|
+
console.log(pc12.dim("No tasks yet. Run: ahk task add"));
|
|
3257
3225
|
return;
|
|
3258
3226
|
}
|
|
3259
3227
|
const table = new Table({
|
|
3260
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
3228
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc12.bold(h)),
|
|
3261
3229
|
style: { head: [], border: [] }
|
|
3262
3230
|
});
|
|
3263
3231
|
for (const t of tasks) {
|
|
@@ -3275,12 +3243,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3275
3243
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3276
3244
|
if (inProgress.length > 0) {
|
|
3277
3245
|
console.log("");
|
|
3278
|
-
console.log(
|
|
3246
|
+
console.log(pc12.bold("Active actions:"));
|
|
3279
3247
|
for (const t of inProgress) {
|
|
3280
3248
|
const actions = await db.getActionsForTask(t.id);
|
|
3281
3249
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
3282
3250
|
for (const a of active) {
|
|
3283
|
-
console.log(` ${
|
|
3251
|
+
console.log(` ${pc12.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
3284
3252
|
}
|
|
3285
3253
|
}
|
|
3286
3254
|
}
|
|
@@ -3289,10 +3257,10 @@ async function runStatus(cwd2, opts) {
|
|
|
3289
3257
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
3290
3258
|
return `${fn(s.status)}: ${s.total}`;
|
|
3291
3259
|
});
|
|
3292
|
-
console.log(
|
|
3260
|
+
console.log(pc12.dim("Tasks \u2014 ") + parts.join(pc12.dim(" | ")));
|
|
3293
3261
|
const archivedTasks = await db.getArchivedTasks();
|
|
3294
3262
|
if (archivedTasks.length > 0) {
|
|
3295
|
-
console.log(
|
|
3263
|
+
console.log(pc12.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
|
|
3296
3264
|
}
|
|
3297
3265
|
} finally {
|
|
3298
3266
|
await db.close();
|
|
@@ -3300,13 +3268,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3300
3268
|
}
|
|
3301
3269
|
|
|
3302
3270
|
// src/commands/sync.ts
|
|
3303
|
-
import { existsSync as
|
|
3304
|
-
import { join as
|
|
3305
|
-
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";
|
|
3306
3274
|
async function runSync(cwd2, opts) {
|
|
3307
3275
|
const config = await loadConfig(cwd2);
|
|
3308
3276
|
const direction = opts.direction ?? "both";
|
|
3309
|
-
const featureListPath = resolve10(
|
|
3277
|
+
const featureListPath = resolve10(join20(cwd2, config.storage.dir, "feature_list.json"));
|
|
3310
3278
|
const db = await openDB(config, cwd2);
|
|
3311
3279
|
try {
|
|
3312
3280
|
if (direction === "in" || direction === "both") {
|
|
@@ -3320,44 +3288,44 @@ async function runSync(cwd2, opts) {
|
|
|
3320
3288
|
}
|
|
3321
3289
|
}
|
|
3322
3290
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3323
|
-
if (!
|
|
3324
|
-
console.log(
|
|
3291
|
+
if (!existsSync17(featureListPath)) {
|
|
3292
|
+
console.log(pc13.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3325
3293
|
return;
|
|
3326
3294
|
}
|
|
3327
3295
|
let seeds;
|
|
3328
3296
|
try {
|
|
3329
|
-
seeds = JSON.parse(
|
|
3297
|
+
seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
|
|
3330
3298
|
} catch (err) {
|
|
3331
|
-
console.error(
|
|
3299
|
+
console.error(pc13.red(`Failed to parse feature_list.json: ${err}`));
|
|
3332
3300
|
process.exit(1);
|
|
3333
3301
|
}
|
|
3334
3302
|
if (dryRun) {
|
|
3335
|
-
console.log(
|
|
3303
|
+
console.log(pc13.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
3336
3304
|
for (const t of seeds) {
|
|
3337
3305
|
const existing = await db.getTaskBySlug(t.slug);
|
|
3338
|
-
console.log(` ${existing ?
|
|
3306
|
+
console.log(` ${existing ? pc13.dim("skip") : pc13.green("add ")} ${t.slug}`);
|
|
3339
3307
|
}
|
|
3340
3308
|
return;
|
|
3341
3309
|
}
|
|
3342
3310
|
const result = await db.syncFromFeatureList(seeds);
|
|
3343
|
-
console.log(
|
|
3311
|
+
console.log(pc13.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
3344
3312
|
}
|
|
3345
3313
|
async function syncOut(db, cwd2, dryRun) {
|
|
3346
3314
|
if (dryRun) {
|
|
3347
3315
|
const tasks = await db.getTasks();
|
|
3348
|
-
console.log(
|
|
3316
|
+
console.log(pc13.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
3349
3317
|
console.log(` ${tasks.length} tasks would be written`);
|
|
3350
3318
|
return;
|
|
3351
3319
|
}
|
|
3352
3320
|
await db.writeFeatureList(cwd2);
|
|
3353
|
-
console.log(
|
|
3321
|
+
console.log(pc13.green("\u2713 Out-sync: feature_list.json updated"));
|
|
3354
3322
|
}
|
|
3355
3323
|
|
|
3356
3324
|
// src/commands/task/add.ts
|
|
3357
3325
|
import * as p6 from "@clack/prompts";
|
|
3358
|
-
import
|
|
3326
|
+
import pc14 from "picocolors";
|
|
3359
3327
|
async function runTaskAdd(cwd2) {
|
|
3360
|
-
p6.intro(
|
|
3328
|
+
p6.intro(pc14.bold("agent-harness-kit \u2014 add task"));
|
|
3361
3329
|
const title = await cliFormWithRetry(
|
|
3362
3330
|
async () => {
|
|
3363
3331
|
const val = await p6.text({ message: "Task title" });
|
|
@@ -3400,10 +3368,10 @@ async function runTaskAdd(cwd2) {
|
|
|
3400
3368
|
await db.writeFeatureList(cwd2);
|
|
3401
3369
|
await db.close();
|
|
3402
3370
|
spinner6.stop("");
|
|
3403
|
-
console.log(
|
|
3404
|
-
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");
|
|
3405
3373
|
} catch (err) {
|
|
3406
|
-
spinner6.stop(
|
|
3374
|
+
spinner6.stop(pc14.red("Failed"));
|
|
3407
3375
|
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
3408
3376
|
process.exit(1);
|
|
3409
3377
|
}
|
|
@@ -3411,17 +3379,17 @@ async function runTaskAdd(cwd2) {
|
|
|
3411
3379
|
|
|
3412
3380
|
// src/commands/task/done.ts
|
|
3413
3381
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3414
|
-
import { existsSync as
|
|
3382
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3415
3383
|
import { resolve as resolve11 } from "path";
|
|
3416
|
-
import
|
|
3384
|
+
import pc15 from "picocolors";
|
|
3417
3385
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3418
3386
|
const config = await loadConfig(cwd2);
|
|
3419
3387
|
if (config.health.required) {
|
|
3420
3388
|
const scriptPath = resolve11(cwd2, config.health.scriptPath);
|
|
3421
|
-
if (
|
|
3389
|
+
if (existsSync18(scriptPath)) {
|
|
3422
3390
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3423
3391
|
if (result.status !== 0) {
|
|
3424
|
-
console.error(
|
|
3392
|
+
console.error(pc15.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
3425
3393
|
if (result.stdout) console.error(result.stdout);
|
|
3426
3394
|
if (result.stderr) console.error(result.stderr);
|
|
3427
3395
|
process.exit(1);
|
|
@@ -3434,16 +3402,16 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3434
3402
|
const isId = !isNaN(parsed);
|
|
3435
3403
|
const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
|
|
3436
3404
|
if (!task2) {
|
|
3437
|
-
console.error(
|
|
3405
|
+
console.error(pc15.red(`Task not found: ${idOrSlug}`));
|
|
3438
3406
|
process.exit(1);
|
|
3439
3407
|
}
|
|
3440
3408
|
if (task2.status === "done") {
|
|
3441
|
-
console.log(
|
|
3409
|
+
console.log(pc15.dim(`Task #${task2.id} is already done.`));
|
|
3442
3410
|
return;
|
|
3443
3411
|
}
|
|
3444
3412
|
await db.updateTaskStatus(task2.id, "done");
|
|
3445
3413
|
await db.writeFeatureList(cwd2);
|
|
3446
|
-
console.log(
|
|
3414
|
+
console.log(pc15.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
3447
3415
|
} finally {
|
|
3448
3416
|
await db.close();
|
|
3449
3417
|
}
|
|
@@ -3451,9 +3419,9 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3451
3419
|
|
|
3452
3420
|
// src/commands/task/edit.ts
|
|
3453
3421
|
import * as p7 from "@clack/prompts";
|
|
3454
|
-
import
|
|
3422
|
+
import pc16 from "picocolors";
|
|
3455
3423
|
async function runTaskEdit(cwd2) {
|
|
3456
|
-
p7.intro(
|
|
3424
|
+
p7.intro(pc16.bold("agent-harness-kit \u2014 edit task"));
|
|
3457
3425
|
const config = await loadConfig(cwd2);
|
|
3458
3426
|
const db = await openDB(config, cwd2);
|
|
3459
3427
|
try {
|
|
@@ -3531,9 +3499,9 @@ async function runTaskEdit(cwd2) {
|
|
|
3531
3499
|
await db.updateTaskAcceptance(task2.id, newAcceptance);
|
|
3532
3500
|
await db.writeFeatureList(cwd2);
|
|
3533
3501
|
spinner6.stop("");
|
|
3534
|
-
console.log(
|
|
3502
|
+
console.log(pc16.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3535
3503
|
} catch (err) {
|
|
3536
|
-
spinner6.stop(
|
|
3504
|
+
spinner6.stop(pc16.red("Failed"));
|
|
3537
3505
|
p7.log.error(err instanceof Error ? err.message : String(err));
|
|
3538
3506
|
process.exit(1);
|
|
3539
3507
|
}
|
|
@@ -3544,12 +3512,12 @@ async function runTaskEdit(cwd2) {
|
|
|
3544
3512
|
|
|
3545
3513
|
// src/commands/task/list.ts
|
|
3546
3514
|
import Table2 from "cli-table3";
|
|
3547
|
-
import
|
|
3515
|
+
import pc17 from "picocolors";
|
|
3548
3516
|
var STATUS_COLOR2 = {
|
|
3549
|
-
pending: (s) =>
|
|
3550
|
-
in_progress: (s) =>
|
|
3551
|
-
done: (s) =>
|
|
3552
|
-
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)
|
|
3553
3521
|
};
|
|
3554
3522
|
async function runTaskList(cwd2, opts) {
|
|
3555
3523
|
const config = await loadConfig(cwd2);
|
|
@@ -3566,11 +3534,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
3566
3534
|
let msg = "No tasks";
|
|
3567
3535
|
if (filterStatus) msg += ` with status: ${filterStatus}`;
|
|
3568
3536
|
if (opts.archived) msg += " (archived)";
|
|
3569
|
-
console.log(
|
|
3537
|
+
console.log(pc17.dim(msg + "."));
|
|
3570
3538
|
return;
|
|
3571
3539
|
}
|
|
3572
3540
|
const table = new Table2({
|
|
3573
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
3541
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc17.bold(h)),
|
|
3574
3542
|
style: { head: [], border: [] }
|
|
3575
3543
|
});
|
|
3576
3544
|
for (const t of tasks) {
|
|
@@ -3581,7 +3549,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3581
3549
|
if (!opts.archived && !opts.includeArchived) {
|
|
3582
3550
|
const archivedTasks = await db.getArchivedTasks();
|
|
3583
3551
|
if (archivedTasks.length > 0) {
|
|
3584
|
-
console.log(
|
|
3552
|
+
console.log(pc17.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
|
|
3585
3553
|
}
|
|
3586
3554
|
}
|
|
3587
3555
|
} finally {
|
|
@@ -3589,43 +3557,6 @@ async function runTaskList(cwd2, opts) {
|
|
|
3589
3557
|
}
|
|
3590
3558
|
}
|
|
3591
3559
|
|
|
3592
|
-
// src/core/local-install-guard.ts
|
|
3593
|
-
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
3594
|
-
import { join as join20 } from "path";
|
|
3595
|
-
import pc17 from "picocolors";
|
|
3596
|
-
function isLocalInstallSatisfied(cwd2) {
|
|
3597
|
-
const selfPkgPath = join20(cwd2, "package.json");
|
|
3598
|
-
let projectPkg = null;
|
|
3599
|
-
if (existsSync18(selfPkgPath)) {
|
|
3600
|
-
try {
|
|
3601
|
-
const selfPkg = JSON.parse(readFileSync12(selfPkgPath, "utf8"));
|
|
3602
|
-
if (selfPkg?.name === pkg.name) return true;
|
|
3603
|
-
projectPkg = selfPkg;
|
|
3604
|
-
} catch {
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
const [scope, name] = pkg.name.split("/");
|
|
3608
|
-
const localPath = pkg.name.startsWith("@") ? join20(cwd2, "node_modules", scope, name) : join20(cwd2, "node_modules", pkg.name);
|
|
3609
|
-
if (existsSync18(localPath)) return true;
|
|
3610
|
-
const isPnp = existsSync18(join20(cwd2, ".pnp.cjs")) || existsSync18(join20(cwd2, ".pnp.loader.mjs"));
|
|
3611
|
-
if (isPnp && projectPkg) {
|
|
3612
|
-
const deps = {
|
|
3613
|
-
...projectPkg.dependencies ?? {},
|
|
3614
|
-
...projectPkg.devDependencies ?? {}
|
|
3615
|
-
};
|
|
3616
|
-
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
3617
|
-
}
|
|
3618
|
-
return false;
|
|
3619
|
-
}
|
|
3620
|
-
function printLocalInstallWarning() {
|
|
3621
|
-
console.error(pc17.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
|
|
3622
|
-
console.error(pc17.dim(" This is only a recommendation for reproducibility: pinning a local"));
|
|
3623
|
-
console.error(pc17.dim(" version keeps behavior consistent across your team and CI, instead of"));
|
|
3624
|
-
console.error(pc17.dim(" drifting with whatever version is installed globally on each machine."));
|
|
3625
|
-
console.error(pc17.dim(` Run: npm install --save-dev ${pkg.name}`));
|
|
3626
|
-
console.error(pc17.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
|
|
3627
|
-
}
|
|
3628
|
-
|
|
3629
3560
|
// src/core/update-check.ts
|
|
3630
3561
|
import pc18 from "picocolors";
|
|
3631
3562
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
@@ -3667,7 +3598,10 @@ program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for mu
|
|
|
3667
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) => {
|
|
3668
3599
|
await runInit(cwd, opts);
|
|
3669
3600
|
});
|
|
3670
|
-
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) => {
|
|
3671
3605
|
await runBuild(cwd, opts);
|
|
3672
3606
|
});
|
|
3673
3607
|
program.command("health").description("Run health.sh and report result").action(async () => {
|