cc-codeconductor 0.3.0 → 0.3.1

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/dist/index.js CHANGED
@@ -7044,7 +7044,7 @@ function getExitCode(error) {
7044
7044
  // package.json
7045
7045
  var package_default = {
7046
7046
  name: "cc-codeconductor",
7047
- version: "0.3.0",
7047
+ version: "0.3.1",
7048
7048
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
7049
7049
  keywords: [
7050
7050
  "ai",
@@ -7511,7 +7511,8 @@ function getRecommendedPresets(profile) {
7511
7511
  }
7512
7512
 
7513
7513
  // src/commands/doctor.command.ts
7514
- import { join } from "node:path";
7514
+ import { join as join4 } from "node:path";
7515
+ import { homedir as homedir3 } from "node:os";
7515
7516
 
7516
7517
  // src/core/config/config-loader.ts
7517
7518
  import { readFile } from "node:fs/promises";
@@ -11721,251 +11722,930 @@ async function loadTargetSecurityCompatibility() {
11721
11722
  });
11722
11723
  }
11723
11724
 
11724
- // src/commands/doctor.command.ts
11725
- async function doctorCommand(options) {
11726
- const { projectRoot, output } = options;
11727
- const checks = [];
11728
- try {
11729
- const hasConfig = await configExists(projectRoot);
11730
- if (hasConfig) {
11731
- checks.push({
11732
- name: "config-exists",
11733
- status: "pass",
11734
- message: ".codeconductor/config.yml exists"
11735
- });
11736
- } else {
11737
- checks.push({
11738
- name: "config-exists",
11739
- status: "fail",
11740
- message: ".codeconductor/config.yml not found. Run `codeconductor init` first."
11741
- });
11742
- return {
11743
- code: 4,
11744
- data: {
11745
- success: false,
11746
- command: "doctor",
11747
- checks
11748
- }
11749
- };
11750
- }
11751
- const configResult = await loadConfig(projectRoot);
11752
- if (configResult.success) {
11753
- checks.push({
11754
- name: "config-valid",
11755
- status: "pass",
11756
- message: "Config is valid"
11757
- });
11758
- } else {
11759
- checks.push({
11760
- name: "config-valid",
11761
- status: "fail",
11762
- message: `Config validation failed: ${configResult.error.message}`
11763
- });
11764
- return {
11765
- code: 1,
11766
- data: {
11767
- success: false,
11768
- command: "doctor",
11769
- checks
11770
- }
11771
- };
11772
- }
11773
- const runnerDirs = [".opencode", ".claude", ".codex"];
11774
- for (const dir of runnerDirs) {
11775
- try {
11776
- const { access: access2 } = await import("node:fs/promises");
11777
- await access2(join(projectRoot, dir));
11778
- checks.push({
11779
- name: `dir-${dir}`,
11780
- status: "pass",
11781
- message: `${dir}/ exists`
11782
- });
11783
- } catch {
11784
- checks.push({
11785
- name: `dir-${dir}`,
11786
- status: "warn",
11787
- message: `${dir}/ not found (optional)`
11788
- });
11789
- }
11790
- }
11791
- const config = configResult.data;
11792
- if (config.presets.council.enabled) {
11793
- checks.push({
11794
- name: "council-enabled",
11795
- status: "pass",
11796
- message: `Council preset enabled (v${config.presets.council.version})`
11797
- });
11798
- }
11799
- const securityCompatibility = await loadTargetSecurityCompatibility();
11800
- for (const compatibility of securityCompatibility) {
11801
- checks.push({
11802
- name: `security-${compatibility.target}`,
11803
- status: compatibility.status,
11804
- message: compatibility.status === "pass" ? `${compatibility.target} can represent the canonical policy model` : `${compatibility.target} cannot enforce: ${compatibility.unsupportedRules.join(", ") || "see warnings"}`
11805
- });
11806
- }
11807
- const failedCount = checks.filter((c) => c.status === "fail").length;
11808
- if (failedCount > 0) {
11809
- return {
11810
- code: 4,
11811
- data: {
11812
- success: false,
11813
- command: "doctor",
11814
- checks
11815
- }
11816
- };
11817
- }
11818
- return {
11819
- code: 0,
11820
- data: {
11821
- success: true,
11822
- command: "doctor",
11823
- checks,
11824
- securityCompatibility
11825
- }
11826
- };
11827
- } catch (error) {
11828
- return {
11829
- code: 1,
11830
- data: {
11831
- success: false,
11832
- command: "doctor",
11833
- errors: [String(error)]
11834
- }
11835
- };
11836
- }
11725
+ // src/core/presets/update-checker.ts
11726
+ import { stat as stat2, readFile as readFile5 } from "node:fs/promises";
11727
+ import { resolve as resolve4, join as join3 } from "node:path";
11728
+ import { homedir as homedir2 } from "node:os";
11729
+
11730
+ // src/core/presets/manifest-loader.ts
11731
+ import { readFile as readFile3 } from "node:fs/promises";
11732
+ import { join } from "node:path";
11733
+ var MANIFESTS_DIR = join(SRC_PRESETS_DIR, "manifests");
11734
+ var MODELS_DIR = join(SRC_PRESETS_DIR, "models");
11735
+ var PRESETS_DIR = ROOT_PRESETS_DIR;
11736
+ async function loadManifest(target) {
11737
+ const manifestPath = join(MANIFESTS_DIR, `${target}.yml`);
11738
+ const content = await readFile3(manifestPath, "utf-8");
11739
+ const data = $parse(content);
11740
+ return InstallManifestSchema.parse(data);
11741
+ }
11742
+ async function loadModelConfig(target) {
11743
+ const modelPath = join(MODELS_DIR, `${target}.yml`);
11744
+ const content = await readFile3(modelPath, "utf-8");
11745
+ const data = $parse(content);
11746
+ return ModelConfigSchema.parse(data);
11837
11747
  }
11838
11748
 
11839
- // src/commands/init.command.ts
11840
- import { access as access3, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
11749
+ // src/core/presets/file-copier.ts
11750
+ import { mkdir, readdir, readFile as readFile4, stat, writeFile } from "node:fs/promises";
11841
11751
  import { homedir } from "node:os";
11842
- import { basename, resolve as resolve4 } from "node:path";
11843
-
11844
- // src/core/config/config-writer.ts
11845
- import { access as access2, mkdir, writeFile } from "node:fs/promises";
11846
- import { resolve as resolve3 } from "node:path";
11752
+ import { dirname as dirname2, join as join2, relative, resolve as resolve3 } from "node:path";
11847
11753
 
11848
- // src/core/config/codeconductor-config.ts
11849
- var DEFAULT_CONFIG = {
11850
- version: "0.2.0",
11851
- project: {
11852
- name: "unnamed-project"
11853
- },
11854
- defaults: {
11855
- target: "opencode",
11856
- overwrite: false,
11857
- locale: "en"
11858
- },
11859
- presets: {
11860
- council: {
11861
- enabled: true,
11862
- version: "0.1.0"
11863
- }
11864
- },
11865
- safety: {
11866
- destructiveCommands: ["rm -rf", "drop table", "delete from"],
11867
- secretPatterns: ["password", "secret", "api_key", "token"]
11868
- }
11754
+ // src/core/i18n/language-instructions.ts
11755
+ var SUPPORTED_LOCALES = ["en", "es"];
11756
+ var LANGUAGE_INSTRUCTIONS = {
11757
+ en: 'Prose/docs/code comments: be terse and direct. Prefer concrete nouns over abstract ones. Omit filler phrases ("note that", "please", "as mentioned"). One idea per sentence.',
11758
+ es: "Spanish prose/docs/reports/Markdown: preserve natural Spanish orthography, including accents, `ñ`, `¿`, `¡`, and normal Unicode. The ASCII-only editing preference does not apply to these artifacts."
11869
11759
  };
11760
+ var COMMIT_STYLE = {
11761
+ en: `---
11762
+ trigger: always_on
11763
+ description: Rules for generating commit messages using Conventional Commits format
11764
+ ---
11870
11765
 
11871
- // src/core/config/config-writer.ts
11872
- var CONFIG_DIR = ".codeconductor";
11873
- var CONFIG_FILE2 = "config.yml";
11874
- async function writeConfig(projectRoot, config = {}, force = false) {
11875
- try {
11876
- const configDir = resolve3(projectRoot, CONFIG_DIR);
11877
- await mkdir(configDir, { recursive: true });
11878
- const configPath = resolve3(configDir, CONFIG_FILE2);
11879
- if (!force) {
11880
- try {
11881
- await access2(configPath);
11882
- return ok(undefined);
11883
- } catch {}
11884
- }
11885
- const mergedConfig = {
11886
- ...DEFAULT_CONFIG,
11887
- ...config,
11888
- project: {
11889
- ...DEFAULT_CONFIG.project,
11890
- ...config.project
11891
- },
11892
- defaults: {
11893
- ...DEFAULT_CONFIG.defaults,
11894
- ...config.defaults
11895
- },
11896
- presets: {
11897
- ...DEFAULT_CONFIG.presets,
11898
- ...config.presets
11899
- },
11900
- safety: {
11901
- ...DEFAULT_CONFIG.safety,
11902
- ...config.safety
11903
- }
11904
- };
11905
- const yamlContent = $stringify(mergedConfig);
11906
- await writeFile(configPath, yamlContent, "utf-8");
11907
- return ok(undefined);
11908
- } catch (error) {
11909
- return err(new ValidationError("Failed to write config", error));
11910
- }
11911
- }
11766
+ ## Git Commit Messages
11912
11767
 
11913
- // src/core/presets/preset-resolver.ts
11914
- var CURRENT_PRESET_VERSION = "v0.3.0";
11915
- function resolvePreset(target, profile) {
11916
- const stack = resolveStack(profile);
11917
- const isMonorepo = profile.signals.some((s) => [
11918
- "pnpm-workspace.yaml",
11919
- "lerna.json",
11920
- "go.work",
11921
- "package.json-workspaces",
11922
- "Cargo.toml-workspace"
11923
- ].includes(s));
11924
- const architecture = isMonorepo ? "monorepo" : profile.signals.length > 0 ? "single-project" : "unknown";
11925
- const warnings = [];
11926
- if (profile.confidence === "low") {
11927
- warnings.push("Detection confidence is low; review the selected preset before applying it.");
11928
- }
11929
- if (stack === "unknown") {
11930
- warnings.push("No supported stack was detected; using the generic CodeConductor workflow preset.");
11931
- } else {
11932
- warnings.push(`${stack} projects currently receive the generic ${target} workflow plus matching skills; stack-specific asset pruning is not implemented yet.`);
11933
- }
11934
- if (architecture === "unknown") {
11935
- warnings.push("Project architecture could not be inferred from available signals.");
11936
- }
11937
- return {
11938
- target,
11939
- stack,
11940
- architecture,
11941
- confidence: profile.confidence,
11942
- presetVersion: CURRENT_PRESET_VERSION,
11943
- assets: resolveAssets(target),
11944
- warnings
11945
- };
11946
- }
11947
- function resolveStack(profile) {
11948
- if (profile.frameworks.includes("spring"))
11949
- return "spring";
11950
- if (profile.frameworks.includes("django"))
11951
- return "django";
11952
- if (profile.frameworks.includes("astro"))
11953
- return "astro";
11954
- if (profile.frameworks.includes("nextjs"))
11955
- return "nextjs";
11956
- if (profile.frameworks.includes("fastapi"))
11957
- return "fastapi";
11958
- if (profile.frameworks.includes("backend"))
11959
- return "backend";
11960
- if (profile.frameworks.includes("frontend"))
11961
- return "frontend";
11962
- if (profile.frameworks.includes("android"))
11963
- return "android";
11964
- if (profile.frameworks.includes("laravel"))
11965
- return "laravel";
11966
- if (profile.runtimes.includes("php"))
11967
- return "php";
11968
- if (profile.runtimes.includes("bun"))
11768
+ ### Format
11769
+
11770
+ \`\`\`
11771
+ <type>(<scope>): <short description>
11772
+
11773
+ - <detail 1>
11774
+ - <detail 2>
11775
+ \`\`\`
11776
+
11777
+ ### Valid Types
11778
+
11779
+ \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
11780
+ \`revert\`
11781
+
11782
+ ### Rules
11783
+
11784
+ - Language: **neutral English** always
11785
+ - Header: maximum 69 characters, no trailing period
11786
+ - Body: concise bullet points, one idea per line
11787
+ - Footer: only for breaking changes or issues
11788
+ - No gerunds ("adding", "fixing")
11789
+ - Use infinitive or imperative ("add", "fix", "update")`,
11790
+ es: `---
11791
+ trigger: always_on
11792
+ description: Reglas para generar mensajes de commit con formato Conventional Commits
11793
+ ---
11794
+
11795
+ ## Git Commit Messages
11796
+
11797
+ ### Formato
11798
+
11799
+ \`\`\`
11800
+ <tipo>(<scope>): <descripcion corta>
11801
+
11802
+ - <detalle 1>
11803
+ - <detalle 2>
11804
+ \`\`\`
11805
+
11806
+ ### Tipos validos
11807
+
11808
+ \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
11809
+ \`revert\`
11810
+
11811
+ ### Reglas
11812
+
11813
+ - Idioma: **inglés neutro** siempre
11814
+ - Encabezado: maximo 69 caracteres, sin punto final
11815
+ - Cuerpo: viñetas concisas, una idea por linea
11816
+ - Footer: solo para breaking changes o issues
11817
+ - Sin gerundios ("agregando", "corrigiendo")
11818
+ - Usar infinitivo o imperativo ("agregar", "corregir", "actualizar")`
11819
+ };
11820
+ var COMMIT_WORKFLOW = {
11821
+ en: `---
11822
+ name: commit
11823
+ description: Generates a commit in English following Conventional Commits based on staged changes
11824
+ ---
11825
+
11826
+ // turbo-all
11827
+
11828
+ ## Steps
11829
+
11830
+ 1. View staged changes: \`git diff --cached --stat\`
11831
+ 2. If nothing is staged, suggest \`git add .\` or specific files
11832
+ 3. Get full diff: \`git diff --cached\`
11833
+ 4. Check recent style: \`git log -n 3 --oneline\`
11834
+ 5. Generate message following \`.agents/rules/commit-style.md\`
11835
+ 6. Show proposal and confirm with the user
11836
+ 7. Execute: \`git commit -m "<message>"\`
11837
+ 8. Confirm success: \`git status\``,
11838
+ es: `---
11839
+ name: commit
11840
+ description: Genera un commit en inglés siguiendo Conventional Commits basado en los cambios staged
11841
+ ---
11842
+
11843
+ // turbo-all
11844
+
11845
+ ## Pasos
11846
+
11847
+ 1. Ver cambios staged: \`git diff --cached --stat\`
11848
+ 2. Si no hay nada staged, sugiere \`git add .\` o archivos específicos
11849
+ 3. Obtener diff completo: \`git diff --cached\`
11850
+ 4. Ver estilo reciente: \`git log -n 3 --oneline\`
11851
+ 5. Generar mensaje siguiendo \`.agents/rules/commit-style.md\`
11852
+ 6. Mostrar propuesta y confirmar con el usuario
11853
+ 7. Ejecutar: \`git commit -m "<mensaje>"\`
11854
+ 8. Confirmar éxito: \`git status\``
11855
+ };
11856
+ function getLanguageInstruction(locale) {
11857
+ const key = SUPPORTED_LOCALES.includes(locale) ? locale : "en";
11858
+ return `- ${LANGUAGE_INSTRUCTIONS[key]}`;
11859
+ }
11860
+ var LOCALE_PLACEHOLDER = "{{LANGUAGE_INSTRUCTIONS}}";
11861
+
11862
+ // src/core/filesystem/safe-merger.ts
11863
+ var MANAGED_BEGIN_MARKER = "<!-- CODECONDUCTOR:BEGIN managed -->";
11864
+ var MANAGED_END_MARKER = "<!-- CODECONDUCTOR:END managed -->";
11865
+ function mergeManagedBlock(existing, incoming) {
11866
+ validateMarkers(incoming, "incoming content");
11867
+ if (!existing) {
11868
+ return { content: incoming, action: "written" };
11869
+ }
11870
+ validateMarkers(existing, "existing content");
11871
+ const existingBlock = getManagedBlockRange(existing);
11872
+ const incomingBlock = getManagedBlockRange(incoming);
11873
+ return {
11874
+ content: existing.slice(0, existingBlock.start) + incoming.slice(incomingBlock.start, incomingBlock.end) + existing.slice(existingBlock.end),
11875
+ action: "merged"
11876
+ };
11877
+ }
11878
+ function validateMarkers(content, label) {
11879
+ const beginCount = countOccurrences(content, MANAGED_BEGIN_MARKER);
11880
+ const endCount = countOccurrences(content, MANAGED_END_MARKER);
11881
+ if (beginCount !== 1 || endCount !== 1) {
11882
+ throw new Error(`${label} must contain exactly one managed begin marker and one managed end marker`);
11883
+ }
11884
+ if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
11885
+ throw new Error(`${label} has managed markers in the wrong order`);
11886
+ }
11887
+ }
11888
+ function getManagedBlockRange(content) {
11889
+ const start = content.indexOf(MANAGED_BEGIN_MARKER);
11890
+ const end = content.indexOf(MANAGED_END_MARKER) + MANAGED_END_MARKER.length;
11891
+ return { start, end };
11892
+ }
11893
+ function countOccurrences(content, needle) {
11894
+ return content.split(needle).length - 1;
11895
+ }
11896
+
11897
+ // src/core/presets/file-copier.ts
11898
+ async function listFilesRecursive(dir, base = dir) {
11899
+ const entries = await readdir(dir, { withFileTypes: true });
11900
+ const files = [];
11901
+ for (const entry of entries) {
11902
+ const full = join2(dir, entry.name);
11903
+ if (entry.isDirectory()) {
11904
+ files.push(...await listFilesRecursive(full, base));
11905
+ } else {
11906
+ files.push(relative(base, full));
11907
+ }
11908
+ }
11909
+ return files;
11910
+ }
11911
+ async function resolveEntryFiles(entry, presetsDir, baseDir) {
11912
+ const srcAbsolute = resolve3(presetsDir, entry.src);
11913
+ try {
11914
+ const s = await stat(srcAbsolute);
11915
+ if (s.isDirectory()) {
11916
+ const files = await listFilesRecursive(srcAbsolute);
11917
+ return files.map((f) => ({
11918
+ src: join2(srcAbsolute, f),
11919
+ dest: resolve3(baseDir, entry.dest, f)
11920
+ }));
11921
+ }
11922
+ return [{ src: srcAbsolute, dest: resolve3(baseDir, entry.dest) }];
11923
+ } catch {
11924
+ return [];
11925
+ }
11926
+ }
11927
+ function mergeDeep(target, source) {
11928
+ const result = { ...target };
11929
+ for (const key of Object.keys(source)) {
11930
+ const srcVal = source[key];
11931
+ const tgtVal = target[key];
11932
+ if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
11933
+ result[key] = [...new Set([...tgtVal, ...srcVal])];
11934
+ } else if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
11935
+ result[key] = mergeDeep(tgtVal, srcVal);
11936
+ } else {
11937
+ result[key] = srcVal;
11938
+ }
11939
+ }
11940
+ return result;
11941
+ }
11942
+ function extractAgentRole(filePath) {
11943
+ const parts = filePath.replace(/\\/g, "/").split("/");
11944
+ const basename = parts[parts.length - 1];
11945
+ const name = basename.replace(/\.md$/, "");
11946
+ const knownRoles = [
11947
+ "architect",
11948
+ "implementer",
11949
+ "tester",
11950
+ "orchestrator",
11951
+ "reviewer",
11952
+ "docs",
11953
+ "task-coach",
11954
+ "repo-explorer"
11955
+ ];
11956
+ return knownRoles.includes(name) ? name : null;
11957
+ }
11958
+ function renderTemplate(content, modelConfig, filePath, locale = "en") {
11959
+ const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
11960
+ const templatedContent = content.replace(/\{\{COMMIT_STYLE\}\}/g, COMMIT_STYLE[cleanLocale]).replace(/\{\{COMMIT_WORKFLOW\}\}/g, COMMIT_WORKFLOW[cleanLocale]);
11961
+ const agentRole = extractAgentRole(filePath);
11962
+ if (agentRole && modelConfig.agents[agentRole]) {
11963
+ const agentModels = modelConfig.agents[agentRole];
11964
+ const targetModel = agentModels[modelConfig.target];
11965
+ let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
11966
+ if (modelConfig.tools || modelConfig.permissions) {
11967
+ result2 = substituteToolNames(result2, modelConfig);
11968
+ }
11969
+ return result2;
11970
+ }
11971
+ const knownRoles = [
11972
+ "orchestrator",
11973
+ "task-coach",
11974
+ "architect",
11975
+ "implementer",
11976
+ "tester",
11977
+ "reviewer",
11978
+ "docs",
11979
+ "repo-explorer"
11980
+ ];
11981
+ let result = templatedContent;
11982
+ for (const role of knownRoles) {
11983
+ if (!modelConfig.agents[role])
11984
+ continue;
11985
+ const agentModels = modelConfig.agents[role];
11986
+ const sectionRegex = new RegExp(`(### ${role}[\\s\\S]*?)(?=### |## |$)`, "gi");
11987
+ const sectionMatch = result.match(sectionRegex);
11988
+ if (sectionMatch) {
11989
+ for (const section of sectionMatch) {
11990
+ const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
11991
+ result = result.replace(section, renderedSection);
11992
+ }
11993
+ }
11994
+ }
11995
+ result = result.replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
11996
+ if (modelConfig.tools || modelConfig.permissions) {
11997
+ result = substituteToolNames(result, modelConfig);
11998
+ }
11999
+ return result;
12000
+ }
12001
+ function substituteToolNames(content, modelConfig) {
12002
+ if (!modelConfig.tools && !modelConfig.permissions)
12003
+ return content;
12004
+ const target = modelConfig.target;
12005
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12006
+ if (!fmMatch)
12007
+ return content;
12008
+ const frontmatter = fmMatch[1];
12009
+ if (target === "opencode" && modelConfig.permissions) {
12010
+ const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
12011
+ return content.replace(fmMatch[0], `---
12012
+ ${updatedFrontmatter2}
12013
+ ---`);
12014
+ }
12015
+ if (!modelConfig.tools)
12016
+ return content;
12017
+ const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12018
+ const baseNames = toolsLine.split(",").map((t) => t.trim());
12019
+ const mappedNames = baseNames.map((baseName) => {
12020
+ const toolMapping = modelConfig.tools?.[baseName];
12021
+ if (toolMapping && toolMapping[target]) {
12022
+ return toolMapping[target];
12023
+ }
12024
+ return baseName;
12025
+ });
12026
+ return `tools: ${mappedNames.join(", ")}`;
12027
+ });
12028
+ return content.replace(fmMatch[0], `---
12029
+ ${updatedFrontmatter}
12030
+ ---`);
12031
+ }
12032
+ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale) {
12033
+ if (strategy === "skip") {
12034
+ return { src: srcPath, dest: destPath, action: "skipped", dryRun };
12035
+ }
12036
+ let content;
12037
+ try {
12038
+ content = await readFile4(srcPath, "utf-8");
12039
+ } catch (e) {
12040
+ return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
12041
+ }
12042
+ const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
12043
+ let finalContent = incomingContent;
12044
+ let action = "written";
12045
+ if (strategy === "append") {
12046
+ let existing = "";
12047
+ try {
12048
+ existing = await readFile4(destPath, "utf-8");
12049
+ } catch {}
12050
+ if (existing) {
12051
+ finalContent = existing + `
12052
+
12053
+ ---
12054
+
12055
+ ` + incomingContent;
12056
+ }
12057
+ action = "appended";
12058
+ } else if (strategy === "merge-json") {
12059
+ let existing = {};
12060
+ try {
12061
+ existing = JSON.parse(await readFile4(destPath, "utf-8"));
12062
+ } catch {}
12063
+ try {
12064
+ const incoming = JSON.parse(incomingContent);
12065
+ finalContent = JSON.stringify(mergeDeep(existing, incoming), null, 2);
12066
+ } catch (e) {
12067
+ return { src: srcPath, dest: destPath, action: "error", error: `JSON merge failed: ${e}` };
12068
+ }
12069
+ action = "merged";
12070
+ } else if (strategy === "merge-managed") {
12071
+ let existing = null;
12072
+ try {
12073
+ existing = await readFile4(destPath, "utf-8");
12074
+ } catch {}
12075
+ try {
12076
+ const merged = mergeManagedBlock(existing, incomingContent);
12077
+ finalContent = merged.content;
12078
+ action = merged.action;
12079
+ } catch (e) {
12080
+ return { src: srcPath, dest: destPath, action: "error", error: `Managed merge failed: ${e}` };
12081
+ }
12082
+ }
12083
+ if (dryRun) {
12084
+ return { src: srcPath, dest: destPath, action, dryRun: true };
12085
+ }
12086
+ try {
12087
+ await mkdir(dirname2(destPath), { recursive: true });
12088
+ await writeFile(destPath, finalContent, "utf-8");
12089
+ return { src: srcPath, dest: destPath, action };
12090
+ } catch (e) {
12091
+ return { src: srcPath, dest: destPath, action: "error", error: String(e) };
12092
+ }
12093
+ }
12094
+ async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
12095
+ const results = [];
12096
+ for (const entry of manifest.entries) {
12097
+ const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12098
+ let resolvedBaseDir = baseDir;
12099
+ let resolvedEntry = entry;
12100
+ if (manifest.target === "agy" && isGlobal) {
12101
+ resolvedBaseDir = join2(homedir(), ".gemini", "config");
12102
+ resolvedEntry = {
12103
+ ...entry,
12104
+ dest: entry.dest.replace(/^\.agents\/?/, "")
12105
+ };
12106
+ }
12107
+ const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
12108
+ const isTemplate = entry.template === true;
12109
+ for (const { src, dest } of files) {
12110
+ results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale));
12111
+ }
12112
+ }
12113
+ return results;
12114
+ }
12115
+
12116
+ // src/core/presets/update-checker.ts
12117
+ function getTargetInstallationPath(target, basePath, isGlobal) {
12118
+ if (target === "agy") {
12119
+ return isGlobal ? resolve4(basePath, ".gemini", "config") : resolve4(basePath, ".agents");
12120
+ }
12121
+ return resolve4(basePath, `.${target}`);
12122
+ }
12123
+ async function isTargetInstalled(target, basePath, isGlobal) {
12124
+ const path = getTargetInstallationPath(target, basePath, isGlobal);
12125
+ try {
12126
+ const s = await stat2(path);
12127
+ return s.isDirectory();
12128
+ } catch {
12129
+ return false;
12130
+ }
12131
+ }
12132
+ async function validateAgentFileSizes(basePath, isGlobal) {
12133
+ const filesToCheck = [];
12134
+ filesToCheck.push(resolve4(basePath, ".claude", "CLAUDE.md"));
12135
+ filesToCheck.push(resolve4(basePath, ".codex", "AGENTS.md"));
12136
+ if (isGlobal) {
12137
+ filesToCheck.push(resolve4(basePath, ".gemini", "config", "AGENTS.md"));
12138
+ } else {
12139
+ filesToCheck.push(resolve4(basePath, ".agents", "AGENTS.md"));
12140
+ }
12141
+ const largeFiles = [];
12142
+ for (const filePath of filesToCheck) {
12143
+ try {
12144
+ const s = await stat2(filePath);
12145
+ if (s.isFile() && s.size > 40 * 1024) {
12146
+ largeFiles.push({ path: filePath, size: s.size });
12147
+ }
12148
+ } catch {}
12149
+ }
12150
+ return largeFiles;
12151
+ }
12152
+ async function fileContentDiffers(pathA, pathB) {
12153
+ try {
12154
+ const contentA = await readFile5(pathA, "utf-8");
12155
+ const contentB = await readFile5(pathB, "utf-8");
12156
+ return contentA.trim() !== contentB.trim();
12157
+ } catch {
12158
+ try {
12159
+ await stat2(pathB);
12160
+ return true;
12161
+ } catch {
12162
+ return false;
12163
+ }
12164
+ }
12165
+ }
12166
+ async function loadSkillsLock(basePath) {
12167
+ const paths = [
12168
+ resolve4(basePath, ".codeconductor", "skills-lock.json"),
12169
+ resolve4(basePath, ".agents", "skills-lock.json")
12170
+ ];
12171
+ for (const p of paths) {
12172
+ try {
12173
+ const content = await readFile5(p, "utf-8");
12174
+ return JSON.parse(content);
12175
+ } catch {}
12176
+ }
12177
+ return null;
12178
+ }
12179
+ async function getLatestSkillVersion(skillId) {
12180
+ const targets = [
12181
+ "opencode",
12182
+ "agy",
12183
+ "claude",
12184
+ "codex",
12185
+ "gemini",
12186
+ "cursor"
12187
+ ];
12188
+ for (const target of targets) {
12189
+ const skillPath = resolve4(ROOT_PRESETS_DIR, target, "skills", skillId, "SKILL.md");
12190
+ try {
12191
+ const content = await readFile5(skillPath, "utf-8");
12192
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12193
+ if (fmMatch) {
12194
+ const parsed = $parse(fmMatch[1]);
12195
+ if (parsed && parsed.id === skillId && parsed.version) {
12196
+ return String(parsed.version);
12197
+ }
12198
+ }
12199
+ } catch {}
12200
+ }
12201
+ return null;
12202
+ }
12203
+ async function checkUpdates(basePath, isGlobal) {
12204
+ const localCouncil = resolve4(basePath, ".codeconductor", "presets", "council.yml");
12205
+ const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
12206
+ const councilHasUpdate = await fileContentDiffers(localCouncil, bundledCouncil);
12207
+ const localPolicy = resolve4(basePath, ".codeconductor", "presets", "policy.yml");
12208
+ const policyHasUpdate = await fileContentDiffers(localPolicy, POLICY_PATH);
12209
+ const targetsToCheck = [
12210
+ "opencode",
12211
+ "claude",
12212
+ "codex",
12213
+ "gemini",
12214
+ "cursor",
12215
+ "agy"
12216
+ ];
12217
+ const targetResults = [];
12218
+ let locale = "en";
12219
+ try {
12220
+ const configResult = await loadConfig(basePath);
12221
+ if (configResult.success) {
12222
+ locale = configResult.data.defaults.locale ?? "en";
12223
+ }
12224
+ } catch {}
12225
+ for (const target of targetsToCheck) {
12226
+ const isInstalled = await isTargetInstalled(target, basePath, isGlobal);
12227
+ if (!isInstalled) {
12228
+ continue;
12229
+ }
12230
+ let manifest;
12231
+ let modelConfig;
12232
+ try {
12233
+ manifest = await loadManifest(target);
12234
+ modelConfig = await loadModelConfig(target);
12235
+ } catch {
12236
+ continue;
12237
+ }
12238
+ const changedFiles = [];
12239
+ for (const entry of manifest.entries) {
12240
+ const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12241
+ if (strategy === "skip")
12242
+ continue;
12243
+ let resolvedEntry = entry;
12244
+ let targetBaseDir = basePath;
12245
+ if (target === "agy" && isGlobal) {
12246
+ targetBaseDir = join3(homedir2(), ".gemini", "config");
12247
+ resolvedEntry = {
12248
+ ...entry,
12249
+ dest: entry.dest.replace(/^\.agents\/?/, "")
12250
+ };
12251
+ }
12252
+ const files = await resolveEntryFiles(resolvedEntry, PRESETS_DIR, targetBaseDir);
12253
+ const isTemplate = entry.template === true;
12254
+ for (const { src, dest } of files) {
12255
+ let expectedContent;
12256
+ try {
12257
+ const srcContent = await readFile5(src, "utf-8");
12258
+ expectedContent = isTemplate && modelConfig ? renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
12259
+ } catch {
12260
+ continue;
12261
+ }
12262
+ let destContent;
12263
+ try {
12264
+ destContent = await readFile5(dest, "utf-8");
12265
+ } catch {
12266
+ changedFiles.push(dest);
12267
+ continue;
12268
+ }
12269
+ let fileHasUpdate = false;
12270
+ if (strategy === "overwrite") {
12271
+ if (destContent.trim() !== expectedContent.trim()) {
12272
+ fileHasUpdate = true;
12273
+ }
12274
+ } else if (strategy === "append") {
12275
+ if (!destContent.includes(expectedContent)) {
12276
+ fileHasUpdate = true;
12277
+ }
12278
+ } else if (strategy === "merge-json") {
12279
+ try {
12280
+ const destJson = JSON.parse(destContent);
12281
+ const expectedJson = JSON.parse(expectedContent);
12282
+ const merged = mergeDeep(destJson, expectedJson);
12283
+ if (JSON.stringify(merged) !== JSON.stringify(destJson)) {
12284
+ fileHasUpdate = true;
12285
+ }
12286
+ } catch {
12287
+ fileHasUpdate = true;
12288
+ }
12289
+ } else if (strategy === "merge-managed") {
12290
+ try {
12291
+ const merged = mergeManagedBlock(destContent, expectedContent);
12292
+ if (merged.content.trim() !== destContent.trim()) {
12293
+ fileHasUpdate = true;
12294
+ }
12295
+ } catch {
12296
+ fileHasUpdate = true;
12297
+ }
12298
+ }
12299
+ if (fileHasUpdate) {
12300
+ changedFiles.push(dest);
12301
+ }
12302
+ }
12303
+ }
12304
+ targetResults.push({
12305
+ target,
12306
+ hasUpdate: changedFiles.length > 0,
12307
+ files: changedFiles
12308
+ });
12309
+ }
12310
+ const skillResults = [];
12311
+ const skillsLock = await loadSkillsLock(basePath);
12312
+ if (skillsLock) {
12313
+ for (const [skillId, currentVersion] of Object.entries(skillsLock)) {
12314
+ const latestVersion = await getLatestSkillVersion(skillId);
12315
+ if (latestVersion && latestVersion !== currentVersion) {
12316
+ skillResults.push({
12317
+ id: skillId,
12318
+ currentVersion,
12319
+ latestVersion,
12320
+ hasUpdate: true
12321
+ });
12322
+ } else {
12323
+ skillResults.push({
12324
+ id: skillId,
12325
+ currentVersion,
12326
+ latestVersion: latestVersion || currentVersion,
12327
+ hasUpdate: false
12328
+ });
12329
+ }
12330
+ }
12331
+ }
12332
+ const hasPresetUpdates = councilHasUpdate || policyHasUpdate;
12333
+ const hasTargetUpdates = targetResults.some((t) => t.hasUpdate);
12334
+ const hasSkillUpdates = skillResults.some((s) => s.hasUpdate);
12335
+ return {
12336
+ hasUpdates: hasPresetUpdates || hasTargetUpdates || hasSkillUpdates,
12337
+ council: councilHasUpdate,
12338
+ policy: policyHasUpdate,
12339
+ targets: targetResults,
12340
+ skills: skillResults
12341
+ };
12342
+ }
12343
+
12344
+ // src/commands/doctor.command.ts
12345
+ async function doctorCommand(options) {
12346
+ const { projectRoot, output } = options;
12347
+ const checks = [];
12348
+ try {
12349
+ const hasConfig = await configExists(projectRoot);
12350
+ if (hasConfig) {
12351
+ checks.push({
12352
+ name: "config-exists",
12353
+ status: "pass",
12354
+ message: ".codeconductor/config.yml exists"
12355
+ });
12356
+ } else {
12357
+ checks.push({
12358
+ name: "config-exists",
12359
+ status: "fail",
12360
+ message: ".codeconductor/config.yml not found. Run `codeconductor init` first."
12361
+ });
12362
+ return {
12363
+ code: 4,
12364
+ data: {
12365
+ success: false,
12366
+ command: "doctor",
12367
+ checks
12368
+ }
12369
+ };
12370
+ }
12371
+ const configResult = await loadConfig(projectRoot);
12372
+ if (configResult.success) {
12373
+ checks.push({
12374
+ name: "config-valid",
12375
+ status: "pass",
12376
+ message: "Config is valid"
12377
+ });
12378
+ } else {
12379
+ checks.push({
12380
+ name: "config-valid",
12381
+ status: "fail",
12382
+ message: `Config validation failed: ${configResult.error.message}`
12383
+ });
12384
+ return {
12385
+ code: 1,
12386
+ data: {
12387
+ success: false,
12388
+ command: "doctor",
12389
+ checks
12390
+ }
12391
+ };
12392
+ }
12393
+ const runnerDirs = [".opencode", ".claude", ".codex"];
12394
+ for (const dir of runnerDirs) {
12395
+ try {
12396
+ const { access: access2 } = await import("node:fs/promises");
12397
+ await access2(join4(projectRoot, dir));
12398
+ checks.push({
12399
+ name: `dir-${dir}`,
12400
+ status: "pass",
12401
+ message: `${dir}/ exists`
12402
+ });
12403
+ } catch {
12404
+ checks.push({
12405
+ name: `dir-${dir}`,
12406
+ status: "warn",
12407
+ message: `${dir}/ not found (optional)`
12408
+ });
12409
+ }
12410
+ }
12411
+ const config = configResult.data;
12412
+ if (config.presets.council.enabled) {
12413
+ checks.push({
12414
+ name: "council-enabled",
12415
+ status: "pass",
12416
+ message: `Council preset enabled (v${config.presets.council.version})`
12417
+ });
12418
+ }
12419
+ const localUpdates = await checkUpdates(projectRoot, false);
12420
+ const globalUpdates = await checkUpdates(homedir3(), true);
12421
+ const updateDetails = [];
12422
+ if (localUpdates.hasUpdates) {
12423
+ if (localUpdates.council)
12424
+ updateDetails.push("local council preset");
12425
+ if (localUpdates.policy)
12426
+ updateDetails.push("local policy");
12427
+ const updatedLocalTargets = localUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
12428
+ if (updatedLocalTargets.length > 0) {
12429
+ updateDetails.push(`local targets (${updatedLocalTargets.join(", ")})`);
12430
+ }
12431
+ const updatedLocalSkills = localUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
12432
+ if (updatedLocalSkills.length > 0) {
12433
+ updateDetails.push(`local skills (${updatedLocalSkills.join(", ")})`);
12434
+ }
12435
+ }
12436
+ if (globalUpdates.hasUpdates) {
12437
+ if (globalUpdates.council)
12438
+ updateDetails.push("global council preset");
12439
+ if (globalUpdates.policy)
12440
+ updateDetails.push("global policy");
12441
+ const updatedGlobalTargets = globalUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
12442
+ if (updatedGlobalTargets.length > 0) {
12443
+ updateDetails.push(`global targets (${updatedGlobalTargets.join(", ")})`);
12444
+ }
12445
+ const updatedGlobalSkills = globalUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
12446
+ if (updatedGlobalSkills.length > 0) {
12447
+ updateDetails.push(`global skills (${updatedGlobalSkills.join(", ")})`);
12448
+ }
12449
+ }
12450
+ if (updateDetails.length > 0) {
12451
+ checks.push({
12452
+ name: "updates-available",
12453
+ status: "warn",
12454
+ message: `Updates available for: ${updateDetails.join(", ")}`
12455
+ });
12456
+ } else {
12457
+ checks.push({
12458
+ name: "updates-available",
12459
+ status: "pass",
12460
+ message: "All presets, targets, and skills are up to date"
12461
+ });
12462
+ }
12463
+ const largeFilesLocal = await validateAgentFileSizes(projectRoot, false);
12464
+ const largeFilesGlobal = await validateAgentFileSizes(homedir3(), true);
12465
+ const allLargeFiles = [...largeFilesLocal, ...largeFilesGlobal];
12466
+ if (allLargeFiles.length > 0) {
12467
+ checks.push({
12468
+ name: "agent-file-sizes",
12469
+ status: "warn",
12470
+ message: `The following files exceed 40KB: ${allLargeFiles.map((f) => f.path).join(", ")}`
12471
+ });
12472
+ } else {
12473
+ checks.push({
12474
+ name: "agent-file-sizes",
12475
+ status: "pass",
12476
+ message: "All agent files (AGENTS.md/CLAUDE.md) are under 40KB"
12477
+ });
12478
+ }
12479
+ const securityCompatibility = await loadTargetSecurityCompatibility();
12480
+ for (const compatibility of securityCompatibility) {
12481
+ checks.push({
12482
+ name: `security-${compatibility.target}`,
12483
+ status: compatibility.status,
12484
+ message: compatibility.status === "pass" ? `${compatibility.target} can represent the canonical policy model` : `${compatibility.target} cannot enforce: ${compatibility.unsupportedRules.join(", ") || "see warnings"}`
12485
+ });
12486
+ }
12487
+ const failedCount = checks.filter((c) => c.status === "fail").length;
12488
+ if (failedCount > 0) {
12489
+ return {
12490
+ code: 4,
12491
+ data: {
12492
+ success: false,
12493
+ command: "doctor",
12494
+ checks
12495
+ }
12496
+ };
12497
+ }
12498
+ return {
12499
+ code: 0,
12500
+ data: {
12501
+ success: true,
12502
+ command: "doctor",
12503
+ checks,
12504
+ securityCompatibility
12505
+ }
12506
+ };
12507
+ } catch (error) {
12508
+ return {
12509
+ code: 1,
12510
+ data: {
12511
+ success: false,
12512
+ command: "doctor",
12513
+ errors: [String(error)]
12514
+ }
12515
+ };
12516
+ }
12517
+ }
12518
+
12519
+ // src/commands/init.command.ts
12520
+ import { access as access3, mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
12521
+ import { homedir as homedir4 } from "node:os";
12522
+ import { basename, resolve as resolve6 } from "node:path";
12523
+
12524
+ // src/core/config/config-writer.ts
12525
+ import { access as access2, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
12526
+ import { resolve as resolve5 } from "node:path";
12527
+
12528
+ // src/core/config/codeconductor-config.ts
12529
+ var DEFAULT_CONFIG = {
12530
+ version: "0.2.0",
12531
+ project: {
12532
+ name: "unnamed-project"
12533
+ },
12534
+ defaults: {
12535
+ target: "opencode",
12536
+ overwrite: false,
12537
+ locale: "en"
12538
+ },
12539
+ presets: {
12540
+ council: {
12541
+ enabled: true,
12542
+ version: "0.1.0"
12543
+ }
12544
+ },
12545
+ safety: {
12546
+ destructiveCommands: ["rm -rf", "drop table", "delete from"],
12547
+ secretPatterns: ["password", "secret", "api_key", "token"]
12548
+ }
12549
+ };
12550
+
12551
+ // src/core/config/config-writer.ts
12552
+ var CONFIG_DIR = ".codeconductor";
12553
+ var CONFIG_FILE2 = "config.yml";
12554
+ async function writeConfig(projectRoot, config = {}, force = false) {
12555
+ try {
12556
+ const configDir = resolve5(projectRoot, CONFIG_DIR);
12557
+ await mkdir2(configDir, { recursive: true });
12558
+ const configPath = resolve5(configDir, CONFIG_FILE2);
12559
+ if (!force) {
12560
+ try {
12561
+ await access2(configPath);
12562
+ return ok(undefined);
12563
+ } catch {}
12564
+ }
12565
+ const mergedConfig = {
12566
+ ...DEFAULT_CONFIG,
12567
+ ...config,
12568
+ project: {
12569
+ ...DEFAULT_CONFIG.project,
12570
+ ...config.project
12571
+ },
12572
+ defaults: {
12573
+ ...DEFAULT_CONFIG.defaults,
12574
+ ...config.defaults
12575
+ },
12576
+ presets: {
12577
+ ...DEFAULT_CONFIG.presets,
12578
+ ...config.presets
12579
+ },
12580
+ safety: {
12581
+ ...DEFAULT_CONFIG.safety,
12582
+ ...config.safety
12583
+ }
12584
+ };
12585
+ const yamlContent = $stringify(mergedConfig);
12586
+ await writeFile2(configPath, yamlContent, "utf-8");
12587
+ return ok(undefined);
12588
+ } catch (error) {
12589
+ return err(new ValidationError("Failed to write config", error));
12590
+ }
12591
+ }
12592
+
12593
+ // src/core/presets/preset-resolver.ts
12594
+ var CURRENT_PRESET_VERSION = "v0.3.0";
12595
+ function resolvePreset(target, profile) {
12596
+ const stack = resolveStack(profile);
12597
+ const isMonorepo = profile.signals.some((s) => [
12598
+ "pnpm-workspace.yaml",
12599
+ "lerna.json",
12600
+ "go.work",
12601
+ "package.json-workspaces",
12602
+ "Cargo.toml-workspace"
12603
+ ].includes(s));
12604
+ const architecture = isMonorepo ? "monorepo" : profile.signals.length > 0 ? "single-project" : "unknown";
12605
+ const warnings = [];
12606
+ if (profile.confidence === "low") {
12607
+ warnings.push("Detection confidence is low; review the selected preset before applying it.");
12608
+ }
12609
+ if (stack === "unknown") {
12610
+ warnings.push("No supported stack was detected; using the generic CodeConductor workflow preset.");
12611
+ } else {
12612
+ warnings.push(`${stack} projects currently receive the generic ${target} workflow plus matching skills; stack-specific asset pruning is not implemented yet.`);
12613
+ }
12614
+ if (architecture === "unknown") {
12615
+ warnings.push("Project architecture could not be inferred from available signals.");
12616
+ }
12617
+ return {
12618
+ target,
12619
+ stack,
12620
+ architecture,
12621
+ confidence: profile.confidence,
12622
+ presetVersion: CURRENT_PRESET_VERSION,
12623
+ assets: resolveAssets(target),
12624
+ warnings
12625
+ };
12626
+ }
12627
+ function resolveStack(profile) {
12628
+ if (profile.frameworks.includes("spring"))
12629
+ return "spring";
12630
+ if (profile.frameworks.includes("django"))
12631
+ return "django";
12632
+ if (profile.frameworks.includes("astro"))
12633
+ return "astro";
12634
+ if (profile.frameworks.includes("nextjs"))
12635
+ return "nextjs";
12636
+ if (profile.frameworks.includes("fastapi"))
12637
+ return "fastapi";
12638
+ if (profile.frameworks.includes("backend"))
12639
+ return "backend";
12640
+ if (profile.frameworks.includes("frontend"))
12641
+ return "frontend";
12642
+ if (profile.frameworks.includes("android"))
12643
+ return "android";
12644
+ if (profile.frameworks.includes("laravel"))
12645
+ return "laravel";
12646
+ if (profile.runtimes.includes("php"))
12647
+ return "php";
12648
+ if (profile.runtimes.includes("bun"))
11969
12649
  return "bun";
11970
12650
  if (profile.runtimes.includes("node"))
11971
12651
  return "node";
@@ -11991,7 +12671,7 @@ function resolveAssets(target) {
11991
12671
  // src/commands/init.command.ts
11992
12672
  async function initCommand(options) {
11993
12673
  const { projectRoot, dryRun, force, global: isGlobal, output, locale } = options;
11994
- const baseDir = isGlobal ? homedir() : projectRoot;
12674
+ const baseDir = isGlobal ? homedir4() : projectRoot;
11995
12675
  try {
11996
12676
  let profile = null;
11997
12677
  if (!isGlobal) {
@@ -12098,7 +12778,7 @@ async function initCommand(options) {
12098
12778
  }
12099
12779
  async function resolvePresetsToCopy() {
12100
12780
  const sources = [];
12101
- const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
12781
+ const bundledCouncil = resolve6(SRC_PRESETS_DIR, "council", "council.yml");
12102
12782
  if (await fileExists2(bundledCouncil)) {
12103
12783
  sources.push({ name: "council.yml", sourcePath: bundledCouncil });
12104
12784
  }
@@ -12109,17 +12789,17 @@ async function resolvePresetsToCopy() {
12109
12789
  return sources;
12110
12790
  }
12111
12791
  async function copyPresets(baseDir, presets, force) {
12112
- const presetsDir = resolve4(baseDir, ".codeconductor", "presets");
12113
- await mkdir2(presetsDir, { recursive: true });
12792
+ const presetsDir = resolve6(baseDir, ".codeconductor", "presets");
12793
+ await mkdir3(presetsDir, { recursive: true });
12114
12794
  const copied = [];
12115
12795
  for (const preset of presets) {
12116
- const destPath = resolve4(presetsDir, preset.name);
12796
+ const destPath = resolve6(presetsDir, preset.name);
12117
12797
  if (!force && await fileExists2(destPath)) {
12118
12798
  continue;
12119
12799
  }
12120
12800
  try {
12121
- const content = await readFile3(preset.sourcePath, "utf-8");
12122
- await writeFile2(destPath, content, "utf-8");
12801
+ const content = await readFile6(preset.sourcePath, "utf-8");
12802
+ await writeFile3(destPath, content, "utf-8");
12123
12803
  copied.push(`.codeconductor/presets/${preset.name}`);
12124
12804
  } catch (err2) {
12125
12805
  const code = err2.code;
@@ -12141,8 +12821,8 @@ async function fileExists2(path) {
12141
12821
  }
12142
12822
 
12143
12823
  // src/commands/install.command.ts
12144
- import { homedir as homedir3 } from "node:os";
12145
- import { resolve as resolve7 } from "node:path";
12824
+ import { homedir as homedir5 } from "node:os";
12825
+ import { resolve as resolve8 } from "node:path";
12146
12826
 
12147
12827
  // src/domain/council/council-agent.ts
12148
12828
  function getResponsibilities(agentId) {
@@ -12200,281 +12880,63 @@ function generateAgentContent(agent) {
12200
12880
  ## Role
12201
12881
  ${agent.role}
12202
12882
 
12203
- ## Context
12204
- ${agent.context === "repo-readonly" ? "Can read repository but cannot modify files" : "Only receives prompts, no direct repository access"}
12205
-
12206
- ## Model Hint
12207
- ${agent.modelHint}
12208
-
12209
- ## Focus Areas
12210
- ${agent.focus.map((f) => `- ${f}`).join(`
12211
- `)}
12212
-
12213
- ## Responsibilities
12214
- ${getResponsibilities(agent.id)}
12215
- `;
12216
- }
12217
-
12218
- // src/adapters/claude/claude-council-generator.ts
12219
- function generateClaudeFiles(spec) {
12220
- const files = [];
12221
- files.push({
12222
- path: ".claude/skills/council/SKILL.md",
12223
- content: generateCouncilSkill(spec),
12224
- overwrite: false
12225
- });
12226
- for (const agent of spec.agents) {
12227
- files.push({
12228
- path: `.claude/agents/council-${agent.id}.md`,
12229
- content: generateAgentContent(agent),
12230
- overwrite: false
12231
- });
12232
- }
12233
- return files;
12234
- }
12235
- function generateCouncilSkill(spec) {
12236
- return `# Council Skill
12237
-
12238
- ## Description
12239
- ${spec.description}
12240
-
12241
- ## Version
12242
- ${spec.version}
12243
-
12244
- ## Agents
12245
- ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12246
- `)}
12247
-
12248
- ## Usage
12249
- Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12250
-
12251
- ## Context
12252
- ${spec.outputContract}
12253
- `;
12254
- }
12255
-
12256
- // src/adapters/claude/claude-installer.ts
12257
- class ClaudeInstaller {
12258
- name = "claude";
12259
- target = "claude";
12260
- spec = null;
12261
- setSpec(spec) {
12262
- this.spec = spec;
12263
- }
12264
- async generate() {
12265
- if (!this.spec) {
12266
- throw new Error("Council spec not set");
12267
- }
12268
- return generateClaudeFiles(this.spec);
12269
- }
12270
- async isAvailable() {
12271
- return true;
12272
- }
12273
- }
12274
- function createClaudeInstaller(spec) {
12275
- const installer = new ClaudeInstaller;
12276
- installer.setSpec(spec);
12277
- return installer;
12278
- }
12279
-
12280
- // src/adapters/codex/codex-council-generator.ts
12281
- function generateCodexFiles(spec) {
12282
- const files = [];
12283
- files.push({
12284
- path: ".codex/config.toml",
12285
- content: generateCodexConfig(spec),
12286
- overwrite: false
12287
- });
12288
- for (const agent of spec.agents) {
12289
- files.push({
12290
- path: `.codex/agents/council_${agent.id}.toml`,
12291
- content: generateCodexAgent(agent),
12292
- overwrite: true
12293
- });
12294
- }
12295
- files.push({
12296
- path: ".codex/skills/council/SKILL.md",
12297
- content: generateCodexSkill(spec),
12298
- overwrite: true
12299
- });
12300
- return files;
12301
- }
12302
- function generateCodexConfig(spec) {
12303
- const agentTables = spec.agents.map((agent) => {
12304
- const focusAreas = agent.focus.join(", ");
12305
- return `
12306
- [agents.${agent.id}]
12307
- description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12308
- nickname_candidates = ["${agent.role}", "Council ${agent.role}"]`;
12309
- }).join(`
12310
- `);
12311
- return `# Codex Council Configuration
12312
-
12313
- [project]
12314
- name = "council"
12315
- version = "${spec.version}"
12316
- ${agentTables}
12317
- `;
12318
- }
12319
- function generateCodexAgent(agent) {
12320
- const focusAreas = agent.focus.join(", ");
12321
- return `name = "${agent.role}"
12322
- description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12323
- nickname_candidates = ["${agent.role}", "Council ${agent.role}"]
12324
- developer_instructions = "You are the ${agent.role} council agent. Your focus areas are: ${focusAreas}. Context: ${agent.context}. Apply ${agent.modelHint} reasoning to your analysis."
12325
- `;
12326
- }
12327
- function generateCodexSkill(spec) {
12328
- return `---
12329
- name: council
12330
- description: ${spec.description}
12331
- version: ${spec.version}
12332
- ---
12333
-
12334
- ## Agents
12335
- ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12336
- `)}
12337
-
12338
- ## Usage
12339
- Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12340
- `;
12341
- }
12342
-
12343
- // src/adapters/codex/codex-installer.ts
12344
- class CodexInstaller {
12345
- name = "codex";
12346
- target = "codex";
12347
- spec = null;
12348
- setSpec(spec) {
12349
- this.spec = spec;
12350
- }
12351
- async generate() {
12352
- if (!this.spec) {
12353
- throw new Error("Council spec not set");
12354
- }
12355
- return generateCodexFiles(this.spec);
12356
- }
12357
- async isAvailable() {
12358
- return true;
12359
- }
12360
- }
12361
- function createCodexInstaller(spec) {
12362
- const installer = new CodexInstaller;
12363
- installer.setSpec(spec);
12364
- return installer;
12365
- }
12366
-
12367
- // src/adapters/opencode/opencode-council-generator.ts
12368
- function generateOpenCodeFiles(spec) {
12369
- const files = [];
12370
- files.push({
12371
- path: ".opencode/commands/cc-council.md",
12372
- content: generateCouncilCommand(spec),
12373
- overwrite: false
12374
- });
12375
- files.push({
12376
- path: ".opencode/agents/council-lead.md",
12377
- content: generateCouncilLead(spec),
12378
- overwrite: false
12379
- });
12380
- for (const agent of spec.agents) {
12381
- files.push({
12382
- path: `.opencode/agents/council-${agent.id}.md`,
12383
- content: generateOpenCodeAgentContent(agent),
12384
- overwrite: false
12385
- });
12386
- }
12387
- return files;
12388
- }
12389
- function generateCouncilCommand(spec) {
12390
- return `---
12391
- description: ${yamlString(spec.description)}
12392
- agent: council-lead
12393
- subtask: true
12394
- ---
12395
-
12396
- Run the CodeConductor council for multi-perspective analysis.
12397
-
12398
- ## Version
12399
- ${spec.version}
12400
-
12401
- ## Agents
12402
- ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
12403
- `)}
12404
-
12405
- ## Instructions
12406
- Coordinate with the council agents and synthesize their perspectives into the configured output contract.
12407
- `;
12408
- }
12409
- function generateCouncilLead(spec) {
12410
- return `---
12411
- description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
12412
- mode: subagent
12413
- permission:
12414
- read: allow
12415
- edit: deny
12416
- bash: deny
12417
- glob: allow
12418
- grep: allow
12419
- webfetch: deny
12420
- websearch: deny
12421
- ---
12422
-
12423
- # Council Lead Agent
12424
-
12425
- ## Role
12426
- Coordinates the council and synthesizes perspectives
12883
+ ## Context
12884
+ ${agent.context === "repo-readonly" ? "Can read repository but cannot modify files" : "Only receives prompts, no direct repository access"}
12427
12885
 
12428
- ## Version
12429
- ${spec.version}
12886
+ ## Model Hint
12887
+ ${agent.modelHint}
12430
12888
 
12431
- ## Council Members
12432
- ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
12889
+ ## Focus Areas
12890
+ ${agent.focus.map((f) => `- ${f}`).join(`
12433
12891
  `)}
12434
12892
 
12435
12893
  ## Responsibilities
12436
- - Coordinate agent responses
12437
- - Synthesize different perspectives
12438
- - Identify consensus and disagreements
12439
- - Provide final recommendation
12894
+ ${getResponsibilities(agent.id)}
12440
12895
  `;
12441
12896
  }
12442
- function generateOpenCodeAgentContent(agent) {
12443
- return `---
12444
- description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
12445
- mode: subagent
12446
- permission:
12447
- ${generatePermissionBlock(agent.context)}
12448
- ---
12449
12897
 
12450
- ${generateAgentContent(agent)}`;
12451
- }
12452
- function generatePermissionBlock(context) {
12453
- if (context === "repo-readonly") {
12454
- return ` read: allow
12455
- edit: deny
12456
- bash: deny
12457
- glob: allow
12458
- grep: allow
12459
- webfetch: deny
12460
- websearch: deny`;
12898
+ // src/adapters/claude/claude-council-generator.ts
12899
+ function generateClaudeFiles(spec) {
12900
+ const files = [];
12901
+ files.push({
12902
+ path: ".claude/skills/council/SKILL.md",
12903
+ content: generateCouncilSkill(spec),
12904
+ overwrite: false
12905
+ });
12906
+ for (const agent of spec.agents) {
12907
+ files.push({
12908
+ path: `.claude/agents/council-${agent.id}.md`,
12909
+ content: generateAgentContent(agent),
12910
+ overwrite: false
12911
+ });
12461
12912
  }
12462
- return ` read: deny
12463
- edit: deny
12464
- bash: deny
12465
- glob: deny
12466
- grep: deny
12467
- webfetch: deny
12468
- websearch: deny`;
12913
+ return files;
12469
12914
  }
12470
- function yamlString(value) {
12471
- return JSON.stringify(value);
12915
+ function generateCouncilSkill(spec) {
12916
+ return `# Council Skill
12917
+
12918
+ ## Description
12919
+ ${spec.description}
12920
+
12921
+ ## Version
12922
+ ${spec.version}
12923
+
12924
+ ## Agents
12925
+ ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12926
+ `)}
12927
+
12928
+ ## Usage
12929
+ Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12930
+
12931
+ ## Context
12932
+ ${spec.outputContract}
12933
+ `;
12472
12934
  }
12473
12935
 
12474
- // src/adapters/opencode/opencode-installer.ts
12475
- class OpenCodeInstaller {
12476
- name = "opencode";
12477
- target = "opencode";
12936
+ // src/adapters/claude/claude-installer.ts
12937
+ class ClaudeInstaller {
12938
+ name = "claude";
12939
+ target = "claude";
12478
12940
  spec = null;
12479
12941
  setSpec(spec) {
12480
12942
  this.spec = spec;
@@ -12483,467 +12945,299 @@ class OpenCodeInstaller {
12483
12945
  if (!this.spec) {
12484
12946
  throw new Error("Council spec not set");
12485
12947
  }
12486
- return generateOpenCodeFiles(this.spec);
12948
+ return generateClaudeFiles(this.spec);
12487
12949
  }
12488
12950
  async isAvailable() {
12489
12951
  return true;
12490
12952
  }
12491
12953
  }
12492
- function createOpenCodeInstaller(spec) {
12493
- const installer = new OpenCodeInstaller;
12954
+ function createClaudeInstaller(spec) {
12955
+ const installer = new ClaudeInstaller;
12494
12956
  installer.setSpec(spec);
12495
12957
  return installer;
12496
12958
  }
12497
12959
 
12498
- // src/core/filesystem/file-writer.ts
12499
- import { access as access4, mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
12500
- import { dirname as dirname2 } from "node:path";
12501
- init_safety();
12502
- async function writeGeneratedFiles(files, options) {
12503
- const results = [];
12504
- for (const file of files) {
12505
- if (!validateWritePath(file.path)) {
12506
- results.push({
12507
- path: file.path,
12508
- success: false,
12509
- error: "Protected path"
12510
- });
12511
- continue;
12512
- }
12513
- if (options.dryRun) {
12514
- results.push({
12515
- path: file.path,
12516
- success: true
12517
- });
12518
- continue;
12519
- }
12520
- if (!options.force) {
12521
- try {
12522
- await access4(file.path);
12523
- results.push({
12524
- path: file.path,
12525
- success: false,
12526
- error: "File exists, use --force to overwrite"
12527
- });
12528
- continue;
12529
- } catch {}
12530
- }
12531
- try {
12532
- const dir = dirname2(file.path);
12533
- await mkdir3(dir, { recursive: true });
12534
- await writeFile3(file.path, file.content, "utf-8");
12535
- results.push({
12536
- path: file.path,
12537
- success: true
12538
- });
12539
- } catch (error) {
12540
- results.push({
12541
- path: file.path,
12542
- success: false,
12543
- error: String(error)
12544
- });
12545
- }
12960
+ // src/adapters/codex/codex-council-generator.ts
12961
+ function generateCodexFiles(spec) {
12962
+ const files = [];
12963
+ files.push({
12964
+ path: ".codex/config.toml",
12965
+ content: generateCodexConfig(spec),
12966
+ overwrite: false
12967
+ });
12968
+ for (const agent of spec.agents) {
12969
+ files.push({
12970
+ path: `.codex/agents/council_${agent.id}.toml`,
12971
+ content: generateCodexAgent(agent),
12972
+ overwrite: true
12973
+ });
12546
12974
  }
12547
- return results;
12975
+ files.push({
12976
+ path: ".codex/skills/council/SKILL.md",
12977
+ content: generateCodexSkill(spec),
12978
+ overwrite: true
12979
+ });
12980
+ return files;
12548
12981
  }
12982
+ function generateCodexConfig(spec) {
12983
+ const agentTables = spec.agents.map((agent) => {
12984
+ const focusAreas = agent.focus.join(", ");
12985
+ return `
12986
+ [agents.${agent.id}]
12987
+ description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12988
+ nickname_candidates = ["${agent.role}", "Council ${agent.role}"]`;
12989
+ }).join(`
12990
+ `);
12991
+ return `# Codex Council Configuration
12549
12992
 
12550
- // src/core/presets/file-copier.ts
12551
- import { mkdir as mkdir4, readdir, readFile as readFile4, stat, writeFile as writeFile4 } from "node:fs/promises";
12552
- import { homedir as homedir2 } from "node:os";
12553
- import { dirname as dirname3, join as join2, relative, resolve as resolve5 } from "node:path";
12554
-
12555
- // src/core/i18n/language-instructions.ts
12556
- var SUPPORTED_LOCALES = ["en", "es"];
12557
- var LANGUAGE_INSTRUCTIONS = {
12558
- en: 'Prose/docs/code comments: be terse and direct. Prefer concrete nouns over abstract ones. Omit filler phrases ("note that", "please", "as mentioned"). One idea per sentence.',
12559
- es: "Spanish prose/docs/reports/Markdown: preserve natural Spanish orthography, including accents, `ñ`, `¿`, `¡`, and normal Unicode. The ASCII-only editing preference does not apply to these artifacts."
12560
- };
12561
- var COMMIT_STYLE = {
12562
- en: `---
12563
- trigger: always_on
12564
- description: Rules for generating commit messages using Conventional Commits format
12565
- ---
12566
-
12567
- ## Git Commit Messages
12568
-
12569
- ### Format
12570
-
12571
- \`\`\`
12572
- <type>(<scope>): <short description>
12573
-
12574
- - <detail 1>
12575
- - <detail 2>
12576
- \`\`\`
12577
-
12578
- ### Valid Types
12579
-
12580
- \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
12581
- \`revert\`
12582
-
12583
- ### Rules
12584
-
12585
- - Language: **neutral English** always
12586
- - Header: maximum 69 characters, no trailing period
12587
- - Body: concise bullet points, one idea per line
12588
- - Footer: only for breaking changes or issues
12589
- - No gerunds ("adding", "fixing")
12590
- - Use infinitive or imperative ("add", "fix", "update")`,
12591
- es: `---
12592
- trigger: always_on
12593
- description: Reglas para generar mensajes de commit con formato Conventional Commits
12594
- ---
12595
-
12596
- ## Git Commit Messages
12597
-
12598
- ### Formato
12599
-
12600
- \`\`\`
12601
- <tipo>(<scope>): <descripcion corta>
12602
-
12603
- - <detalle 1>
12604
- - <detalle 2>
12605
- \`\`\`
12606
-
12607
- ### Tipos validos
12608
-
12609
- \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
12610
- \`revert\`
12611
-
12612
- ### Reglas
12613
-
12614
- - Idioma: **inglés neutro** siempre
12615
- - Encabezado: maximo 69 caracteres, sin punto final
12616
- - Cuerpo: viñetas concisas, una idea por linea
12617
- - Footer: solo para breaking changes o issues
12618
- - Sin gerundios ("agregando", "corrigiendo")
12619
- - Usar infinitivo o imperativo ("agregar", "corregir", "actualizar")`
12620
- };
12621
- var COMMIT_WORKFLOW = {
12622
- en: `---
12623
- name: commit
12624
- description: Generates a commit in English following Conventional Commits based on staged changes
12625
- ---
12626
-
12627
- // turbo-all
12628
-
12629
- ## Steps
12630
-
12631
- 1. View staged changes: \`git diff --cached --stat\`
12632
- 2. If nothing is staged, suggest \`git add .\` or specific files
12633
- 3. Get full diff: \`git diff --cached\`
12634
- 4. Check recent style: \`git log -n 3 --oneline\`
12635
- 5. Generate message following \`.agents/rules/commit-style.md\`
12636
- 6. Show proposal and confirm with the user
12637
- 7. Execute: \`git commit -m "<message>"\`
12638
- 8. Confirm success: \`git status\``,
12639
- es: `---
12640
- name: commit
12641
- description: Genera un commit en inglés siguiendo Conventional Commits basado en los cambios staged
12642
- ---
12643
-
12644
- // turbo-all
12993
+ [project]
12994
+ name = "council"
12995
+ version = "${spec.version}"
12996
+ ${agentTables}
12997
+ `;
12998
+ }
12999
+ function generateCodexAgent(agent) {
13000
+ const focusAreas = agent.focus.join(", ");
13001
+ return `name = "${agent.role}"
13002
+ description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
13003
+ nickname_candidates = ["${agent.role}", "Council ${agent.role}"]
13004
+ developer_instructions = "You are the ${agent.role} council agent. Your focus areas are: ${focusAreas}. Context: ${agent.context}. Apply ${agent.modelHint} reasoning to your analysis."
13005
+ `;
13006
+ }
13007
+ function generateCodexSkill(spec) {
13008
+ return `---
13009
+ name: council
13010
+ description: ${spec.description}
13011
+ version: ${spec.version}
13012
+ ---
12645
13013
 
12646
- ## Pasos
13014
+ ## Agents
13015
+ ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
13016
+ `)}
12647
13017
 
12648
- 1. Ver cambios staged: \`git diff --cached --stat\`
12649
- 2. Si no hay nada staged, sugiere \`git add .\` o archivos específicos
12650
- 3. Obtener diff completo: \`git diff --cached\`
12651
- 4. Ver estilo reciente: \`git log -n 3 --oneline\`
12652
- 5. Generar mensaje siguiendo \`.agents/rules/commit-style.md\`
12653
- 6. Mostrar propuesta y confirmar con el usuario
12654
- 7. Ejecutar: \`git commit -m "<mensaje>"\`
12655
- 8. Confirmar éxito: \`git status\``
12656
- };
12657
- function getLanguageInstruction(locale) {
12658
- const key = SUPPORTED_LOCALES.includes(locale) ? locale : "en";
12659
- return `- ${LANGUAGE_INSTRUCTIONS[key]}`;
13018
+ ## Usage
13019
+ Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
13020
+ `;
12660
13021
  }
12661
- var LOCALE_PLACEHOLDER = "{{LANGUAGE_INSTRUCTIONS}}";
12662
13022
 
12663
- // src/core/filesystem/safe-merger.ts
12664
- var MANAGED_BEGIN_MARKER = "<!-- CODECONDUCTOR:BEGIN managed -->";
12665
- var MANAGED_END_MARKER = "<!-- CODECONDUCTOR:END managed -->";
12666
- function mergeManagedBlock(existing, incoming) {
12667
- validateMarkers(incoming, "incoming content");
12668
- if (!existing) {
12669
- return { content: incoming, action: "written" };
13023
+ // src/adapters/codex/codex-installer.ts
13024
+ class CodexInstaller {
13025
+ name = "codex";
13026
+ target = "codex";
13027
+ spec = null;
13028
+ setSpec(spec) {
13029
+ this.spec = spec;
12670
13030
  }
12671
- validateMarkers(existing, "existing content");
12672
- const existingBlock = getManagedBlockRange(existing);
12673
- const incomingBlock = getManagedBlockRange(incoming);
12674
- return {
12675
- content: existing.slice(0, existingBlock.start) + incoming.slice(incomingBlock.start, incomingBlock.end) + existing.slice(existingBlock.end),
12676
- action: "merged"
12677
- };
12678
- }
12679
- function validateMarkers(content, label) {
12680
- const beginCount = countOccurrences(content, MANAGED_BEGIN_MARKER);
12681
- const endCount = countOccurrences(content, MANAGED_END_MARKER);
12682
- if (beginCount !== 1 || endCount !== 1) {
12683
- throw new Error(`${label} must contain exactly one managed begin marker and one managed end marker`);
13031
+ async generate() {
13032
+ if (!this.spec) {
13033
+ throw new Error("Council spec not set");
13034
+ }
13035
+ return generateCodexFiles(this.spec);
12684
13036
  }
12685
- if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
12686
- throw new Error(`${label} has managed markers in the wrong order`);
13037
+ async isAvailable() {
13038
+ return true;
12687
13039
  }
12688
13040
  }
12689
- function getManagedBlockRange(content) {
12690
- const start = content.indexOf(MANAGED_BEGIN_MARKER);
12691
- const end = content.indexOf(MANAGED_END_MARKER) + MANAGED_END_MARKER.length;
12692
- return { start, end };
12693
- }
12694
- function countOccurrences(content, needle) {
12695
- return content.split(needle).length - 1;
13041
+ function createCodexInstaller(spec) {
13042
+ const installer = new CodexInstaller;
13043
+ installer.setSpec(spec);
13044
+ return installer;
12696
13045
  }
12697
13046
 
12698
- // src/core/presets/file-copier.ts
12699
- async function listFilesRecursive(dir, base = dir) {
12700
- const entries = await readdir(dir, { withFileTypes: true });
13047
+ // src/adapters/opencode/opencode-council-generator.ts
13048
+ function generateOpenCodeFiles(spec) {
12701
13049
  const files = [];
12702
- for (const entry of entries) {
12703
- const full = join2(dir, entry.name);
12704
- if (entry.isDirectory()) {
12705
- files.push(...await listFilesRecursive(full, base));
12706
- } else {
12707
- files.push(relative(base, full));
12708
- }
13050
+ files.push({
13051
+ path: ".opencode/commands/cc-council.md",
13052
+ content: generateCouncilCommand(spec),
13053
+ overwrite: false
13054
+ });
13055
+ files.push({
13056
+ path: ".opencode/agents/council-lead.md",
13057
+ content: generateCouncilLead(spec),
13058
+ overwrite: false
13059
+ });
13060
+ for (const agent of spec.agents) {
13061
+ files.push({
13062
+ path: `.opencode/agents/council-${agent.id}.md`,
13063
+ content: generateOpenCodeAgentContent(agent),
13064
+ overwrite: false
13065
+ });
12709
13066
  }
12710
13067
  return files;
12711
13068
  }
12712
- async function resolveEntryFiles(entry, presetsDir, baseDir) {
12713
- const srcAbsolute = resolve5(presetsDir, entry.src);
12714
- try {
12715
- const s = await stat(srcAbsolute);
12716
- if (s.isDirectory()) {
12717
- const files = await listFilesRecursive(srcAbsolute);
12718
- return files.map((f) => ({
12719
- src: join2(srcAbsolute, f),
12720
- dest: resolve5(baseDir, entry.dest, f)
12721
- }));
12722
- }
12723
- return [{ src: srcAbsolute, dest: resolve5(baseDir, entry.dest) }];
12724
- } catch {
12725
- return [];
12726
- }
12727
- }
12728
- function mergeDeep(target, source) {
12729
- const result = { ...target };
12730
- for (const key of Object.keys(source)) {
12731
- const srcVal = source[key];
12732
- const tgtVal = target[key];
12733
- if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
12734
- result[key] = [...new Set([...tgtVal, ...srcVal])];
12735
- } else if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
12736
- result[key] = mergeDeep(tgtVal, srcVal);
12737
- } else {
12738
- result[key] = srcVal;
12739
- }
12740
- }
12741
- return result;
13069
+ function generateCouncilCommand(spec) {
13070
+ return `---
13071
+ description: ${yamlString(spec.description)}
13072
+ agent: council-lead
13073
+ subtask: true
13074
+ ---
13075
+
13076
+ Run the CodeConductor council for multi-perspective analysis.
13077
+
13078
+ ## Version
13079
+ ${spec.version}
13080
+
13081
+ ## Agents
13082
+ ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
13083
+ `)}
13084
+
13085
+ ## Instructions
13086
+ Coordinate with the council agents and synthesize their perspectives into the configured output contract.
13087
+ `;
12742
13088
  }
12743
- function extractAgentRole(filePath) {
12744
- const parts = filePath.replace(/\\/g, "/").split("/");
12745
- const basename2 = parts[parts.length - 1];
12746
- const name = basename2.replace(/\.md$/, "");
12747
- const knownRoles = [
12748
- "architect",
12749
- "implementer",
12750
- "tester",
12751
- "orchestrator",
12752
- "reviewer",
12753
- "docs",
12754
- "task-coach",
12755
- "repo-explorer"
12756
- ];
12757
- return knownRoles.includes(name) ? name : null;
13089
+ function generateCouncilLead(spec) {
13090
+ return `---
13091
+ description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
13092
+ mode: subagent
13093
+ permission:
13094
+ read: allow
13095
+ edit: deny
13096
+ bash: deny
13097
+ glob: allow
13098
+ grep: allow
13099
+ webfetch: deny
13100
+ websearch: deny
13101
+ ---
13102
+
13103
+ # Council Lead Agent
13104
+
13105
+ ## Role
13106
+ Coordinates the council and synthesizes perspectives
13107
+
13108
+ ## Version
13109
+ ${spec.version}
13110
+
13111
+ ## Council Members
13112
+ ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
13113
+ `)}
13114
+
13115
+ ## Responsibilities
13116
+ - Coordinate agent responses
13117
+ - Synthesize different perspectives
13118
+ - Identify consensus and disagreements
13119
+ - Provide final recommendation
13120
+ `;
12758
13121
  }
12759
- function renderTemplate(content, modelConfig, filePath, locale = "en") {
12760
- const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
12761
- const templatedContent = content.replace(/\{\{COMMIT_STYLE\}\}/g, COMMIT_STYLE[cleanLocale]).replace(/\{\{COMMIT_WORKFLOW\}\}/g, COMMIT_WORKFLOW[cleanLocale]);
12762
- const agentRole = extractAgentRole(filePath);
12763
- if (agentRole && modelConfig.agents[agentRole]) {
12764
- const agentModels = modelConfig.agents[agentRole];
12765
- const targetModel = agentModels[modelConfig.target];
12766
- let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
12767
- if (modelConfig.tools || modelConfig.permissions) {
12768
- result2 = substituteToolNames(result2, modelConfig);
12769
- }
12770
- return result2;
12771
- }
12772
- const knownRoles = [
12773
- "orchestrator",
12774
- "task-coach",
12775
- "architect",
12776
- "implementer",
12777
- "tester",
12778
- "reviewer",
12779
- "docs",
12780
- "repo-explorer"
12781
- ];
12782
- let result = templatedContent;
12783
- for (const role of knownRoles) {
12784
- if (!modelConfig.agents[role])
12785
- continue;
12786
- const agentModels = modelConfig.agents[role];
12787
- const sectionRegex = new RegExp(`(### ${role}[\\s\\S]*?)(?=### |## |$)`, "gi");
12788
- const sectionMatch = result.match(sectionRegex);
12789
- if (sectionMatch) {
12790
- for (const section of sectionMatch) {
12791
- const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
12792
- result = result.replace(section, renderedSection);
12793
- }
12794
- }
12795
- }
12796
- result = result.replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
12797
- if (modelConfig.tools || modelConfig.permissions) {
12798
- result = substituteToolNames(result, modelConfig);
12799
- }
12800
- return result;
13122
+ function generateOpenCodeAgentContent(agent) {
13123
+ return `---
13124
+ description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
13125
+ mode: subagent
13126
+ permission:
13127
+ ${generatePermissionBlock(agent.context)}
13128
+ ---
13129
+
13130
+ ${generateAgentContent(agent)}`;
12801
13131
  }
12802
- function substituteToolNames(content, modelConfig) {
12803
- if (!modelConfig.tools && !modelConfig.permissions)
12804
- return content;
12805
- const target = modelConfig.target;
12806
- const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12807
- if (!fmMatch)
12808
- return content;
12809
- const frontmatter = fmMatch[1];
12810
- if (target === "opencode" && modelConfig.permissions) {
12811
- const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
12812
- return content.replace(fmMatch[0], `---
12813
- ${updatedFrontmatter2}
12814
- ---`);
13132
+ function generatePermissionBlock(context) {
13133
+ if (context === "repo-readonly") {
13134
+ return ` read: allow
13135
+ edit: deny
13136
+ bash: deny
13137
+ glob: allow
13138
+ grep: allow
13139
+ webfetch: deny
13140
+ websearch: deny`;
12815
13141
  }
12816
- if (!modelConfig.tools)
12817
- return content;
12818
- const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12819
- const baseNames = toolsLine.split(",").map((t) => t.trim());
12820
- const mappedNames = baseNames.map((baseName) => {
12821
- const toolMapping = modelConfig.tools?.[baseName];
12822
- if (toolMapping && toolMapping[target]) {
12823
- return toolMapping[target];
12824
- }
12825
- return baseName;
12826
- });
12827
- return `tools: ${mappedNames.join(", ")}`;
12828
- });
12829
- return content.replace(fmMatch[0], `---
12830
- ${updatedFrontmatter}
12831
- ---`);
13142
+ return ` read: deny
13143
+ edit: deny
13144
+ bash: deny
13145
+ glob: deny
13146
+ grep: deny
13147
+ webfetch: deny
13148
+ websearch: deny`;
13149
+ }
13150
+ function yamlString(value) {
13151
+ return JSON.stringify(value);
12832
13152
  }
12833
- async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale) {
12834
- if (strategy === "skip") {
12835
- return { src: srcPath, dest: destPath, action: "skipped", dryRun };
12836
- }
12837
- let content;
12838
- try {
12839
- content = await readFile4(srcPath, "utf-8");
12840
- } catch (e) {
12841
- return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
12842
- }
12843
- const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
12844
- let finalContent = incomingContent;
12845
- let action = "written";
12846
- if (strategy === "append") {
12847
- let existing = "";
12848
- try {
12849
- existing = await readFile4(destPath, "utf-8");
12850
- } catch {}
12851
- if (existing) {
12852
- finalContent = existing + `
12853
-
12854
- ---
12855
13153
 
12856
- ` + incomingContent;
12857
- }
12858
- action = "appended";
12859
- } else if (strategy === "merge-json") {
12860
- let existing = {};
12861
- try {
12862
- existing = JSON.parse(await readFile4(destPath, "utf-8"));
12863
- } catch {}
12864
- try {
12865
- const incoming = JSON.parse(incomingContent);
12866
- finalContent = JSON.stringify(mergeDeep(existing, incoming), null, 2);
12867
- } catch (e) {
12868
- return { src: srcPath, dest: destPath, action: "error", error: `JSON merge failed: ${e}` };
12869
- }
12870
- action = "merged";
12871
- } else if (strategy === "merge-managed") {
12872
- let existing = null;
12873
- try {
12874
- existing = await readFile4(destPath, "utf-8");
12875
- } catch {}
12876
- try {
12877
- const merged = mergeManagedBlock(existing, incomingContent);
12878
- finalContent = merged.content;
12879
- action = merged.action;
12880
- } catch (e) {
12881
- return { src: srcPath, dest: destPath, action: "error", error: `Managed merge failed: ${e}` };
12882
- }
13154
+ // src/adapters/opencode/opencode-installer.ts
13155
+ class OpenCodeInstaller {
13156
+ name = "opencode";
13157
+ target = "opencode";
13158
+ spec = null;
13159
+ setSpec(spec) {
13160
+ this.spec = spec;
12883
13161
  }
12884
- if (dryRun) {
12885
- return { src: srcPath, dest: destPath, action, dryRun: true };
13162
+ async generate() {
13163
+ if (!this.spec) {
13164
+ throw new Error("Council spec not set");
13165
+ }
13166
+ return generateOpenCodeFiles(this.spec);
12886
13167
  }
12887
- try {
12888
- await mkdir4(dirname3(destPath), { recursive: true });
12889
- await writeFile4(destPath, finalContent, "utf-8");
12890
- return { src: srcPath, dest: destPath, action };
12891
- } catch (e) {
12892
- return { src: srcPath, dest: destPath, action: "error", error: String(e) };
13168
+ async isAvailable() {
13169
+ return true;
12893
13170
  }
12894
13171
  }
12895
- async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
13172
+ function createOpenCodeInstaller(spec) {
13173
+ const installer = new OpenCodeInstaller;
13174
+ installer.setSpec(spec);
13175
+ return installer;
13176
+ }
13177
+
13178
+ // src/core/filesystem/file-writer.ts
13179
+ import { access as access4, mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
13180
+ import { dirname as dirname3 } from "node:path";
13181
+ init_safety();
13182
+ async function writeGeneratedFiles(files, options) {
12896
13183
  const results = [];
12897
- for (const entry of manifest.entries) {
12898
- const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12899
- let resolvedBaseDir = baseDir;
12900
- let resolvedEntry = entry;
12901
- if (manifest.target === "agy" && isGlobal) {
12902
- resolvedBaseDir = join2(homedir2(), ".gemini", "config");
12903
- resolvedEntry = {
12904
- ...entry,
12905
- dest: entry.dest.replace(/^\.agents\/?/, "")
12906
- };
13184
+ for (const file of files) {
13185
+ if (!validateWritePath(file.path)) {
13186
+ results.push({
13187
+ path: file.path,
13188
+ success: false,
13189
+ error: "Protected path"
13190
+ });
13191
+ continue;
12907
13192
  }
12908
- const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
12909
- const isTemplate = entry.template === true;
12910
- for (const { src, dest } of files) {
12911
- results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale));
13193
+ if (options.dryRun) {
13194
+ results.push({
13195
+ path: file.path,
13196
+ success: true
13197
+ });
13198
+ continue;
13199
+ }
13200
+ if (!options.force) {
13201
+ try {
13202
+ await access4(file.path);
13203
+ results.push({
13204
+ path: file.path,
13205
+ success: false,
13206
+ error: "File exists, use --force to overwrite"
13207
+ });
13208
+ continue;
13209
+ } catch {}
13210
+ }
13211
+ try {
13212
+ const dir = dirname3(file.path);
13213
+ await mkdir4(dir, { recursive: true });
13214
+ await writeFile4(file.path, file.content, "utf-8");
13215
+ results.push({
13216
+ path: file.path,
13217
+ success: true
13218
+ });
13219
+ } catch (error) {
13220
+ results.push({
13221
+ path: file.path,
13222
+ success: false,
13223
+ error: String(error)
13224
+ });
12912
13225
  }
12913
13226
  }
12914
13227
  return results;
12915
13228
  }
12916
13229
 
12917
- // src/core/presets/manifest-loader.ts
12918
- import { readFile as readFile5 } from "node:fs/promises";
12919
- import { join as join3 } from "node:path";
12920
- var MANIFESTS_DIR = join3(SRC_PRESETS_DIR, "manifests");
12921
- var MODELS_DIR = join3(SRC_PRESETS_DIR, "models");
12922
- var PRESETS_DIR = ROOT_PRESETS_DIR;
12923
- async function loadManifest(target) {
12924
- const manifestPath = join3(MANIFESTS_DIR, `${target}.yml`);
12925
- const content = await readFile5(manifestPath, "utf-8");
12926
- const data = $parse(content);
12927
- return InstallManifestSchema.parse(data);
12928
- }
12929
- async function loadModelConfig(target) {
12930
- const modelPath = join3(MODELS_DIR, `${target}.yml`);
12931
- const content = await readFile5(modelPath, "utf-8");
12932
- const data = $parse(content);
12933
- return ModelConfigSchema.parse(data);
12934
- }
12935
-
12936
13230
  // src/core/presets/preset-loader.ts
12937
- import { readFile as readFile6 } from "node:fs/promises";
12938
- import { resolve as resolve6 } from "node:path";
13231
+ import { readFile as readFile7 } from "node:fs/promises";
13232
+ import { resolve as resolve7 } from "node:path";
12939
13233
  async function loadPreset(name, projectRoot = process.cwd()) {
12940
13234
  const candidates = [
12941
- resolve6(projectRoot, ".codeconductor", "presets", `${name}.yml`),
12942
- resolve6(SRC_PRESETS_DIR, name, `${name}.yml`)
13235
+ resolve7(projectRoot, ".codeconductor", "presets", `${name}.yml`),
13236
+ resolve7(SRC_PRESETS_DIR, name, `${name}.yml`)
12943
13237
  ];
12944
13238
  for (const presetPath of candidates) {
12945
13239
  try {
12946
- const content = await readFile6(presetPath, "utf-8");
13240
+ const content = await readFile7(presetPath, "utf-8");
12947
13241
  const data = $parse(content);
12948
13242
  if (!data)
12949
13243
  continue;
@@ -12987,7 +13281,7 @@ function getIndividualTargets(target) {
12987
13281
  // src/commands/install.command.ts
12988
13282
  async function installCommand(options) {
12989
13283
  const { target, dryRun, force, global: isGlobal, output, projectRoot } = options;
12990
- const baseDir = isGlobal ? homedir3() : projectRoot;
13284
+ const baseDir = isGlobal ? homedir5() : projectRoot;
12991
13285
  try {
12992
13286
  const runnerTarget = parseRunnerTarget(target);
12993
13287
  const targets = getIndividualTargets(runnerTarget);
@@ -13023,7 +13317,7 @@ async function installCommand(options) {
13023
13317
  const generatedFiles = await installer.generate();
13024
13318
  const resolvedFiles = generatedFiles.map((f) => ({
13025
13319
  ...f,
13026
- path: resolve7(baseDir, f.path)
13320
+ path: resolve8(baseDir, f.path)
13027
13321
  }));
13028
13322
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
13029
13323
  for (const result of results) {
@@ -13082,7 +13376,7 @@ async function installCommand(options) {
13082
13376
  }
13083
13377
  async function installPresetCommand(options) {
13084
13378
  const { target, dryRun, force, global: isGlobal, projectRoot } = options;
13085
- const baseDir = isGlobal ? homedir3() : projectRoot;
13379
+ const baseDir = isGlobal ? homedir5() : projectRoot;
13086
13380
  try {
13087
13381
  const runnerTarget = parseRunnerTarget(target);
13088
13382
  const targets = getIndividualTargets(runnerTarget);
@@ -13132,8 +13426,8 @@ async function installPresetCommand(options) {
13132
13426
  }
13133
13427
 
13134
13428
  // src/commands/install-lsp.command.ts
13135
- import { homedir as homedir5 } from "node:os";
13136
- import { resolve as resolve8 } from "node:path";
13429
+ import { homedir as homedir7 } from "node:os";
13430
+ import { resolve as resolve9 } from "node:path";
13137
13431
 
13138
13432
  // src/core/lsp/lsp-config-utils.ts
13139
13433
  function getLanguageServerConfig(lspIds) {
@@ -13377,15 +13671,15 @@ function createOpenCodeLspGenerator() {
13377
13671
  // src/core/lsp/lsp-installer.ts
13378
13672
  import { execFile } from "node:child_process";
13379
13673
  import { access as access5, mkdir as mkdir5 } from "node:fs/promises";
13380
- import { homedir as homedir4 } from "node:os";
13381
- import { join as join4 } from "node:path";
13674
+ import { homedir as homedir6 } from "node:os";
13675
+ import { join as join5 } from "node:path";
13382
13676
  import { promisify } from "node:util";
13383
13677
  var execFileAsync = promisify(execFile);
13384
13678
 
13385
13679
  class LspInstaller {
13386
13680
  lspBinDir;
13387
13681
  constructor() {
13388
- this.lspBinDir = join4(homedir4(), ".codeconductor", "lsp", "bin");
13682
+ this.lspBinDir = join5(homedir6(), ".codeconductor", "lsp", "bin");
13389
13683
  }
13390
13684
  async checkInstalled(def) {
13391
13685
  try {
@@ -13512,7 +13806,7 @@ class LspInstaller {
13512
13806
  throw new Error(`No binary available for platform: ${platformKey}`);
13513
13807
  }
13514
13808
  await mkdir5(this.lspBinDir, { recursive: true });
13515
- const destPath = join4(this.lspBinDir, def.binaryName);
13809
+ const destPath = join5(this.lspBinDir, def.binaryName);
13516
13810
  try {
13517
13811
  await access5(destPath);
13518
13812
  return;
@@ -13619,7 +13913,7 @@ function resolveLsps(languages) {
13619
13913
  // src/commands/install-lsp.command.ts
13620
13914
  async function installLspCommand(options) {
13621
13915
  const { target, lang, dryRun, force, global: isGlobal, output, projectRoot } = options;
13622
- const baseDir = isGlobal ? homedir5() : projectRoot;
13916
+ const baseDir = isGlobal ? homedir7() : projectRoot;
13623
13917
  try {
13624
13918
  const runnerTarget = parseRunnerTarget(target);
13625
13919
  const targets = getIndividualTargets(runnerTarget);
@@ -13665,7 +13959,7 @@ async function installLspCommand(options) {
13665
13959
  const generatedFiles = generator.generate(installReport.results);
13666
13960
  const resolvedFiles = generatedFiles.map((f) => ({
13667
13961
  ...f,
13668
- path: resolve8(baseDir, f.path)
13962
+ path: resolve9(baseDir, f.path)
13669
13963
  }));
13670
13964
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
13671
13965
  for (const result of results) {
@@ -13759,7 +14053,7 @@ function getLspConfigGenerator(target) {
13759
14053
 
13760
14054
  // src/commands/seo-audit.command.ts
13761
14055
  import { writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
13762
- import { dirname as dirname4, resolve as resolve9 } from "node:path";
14056
+ import { dirname as dirname4, resolve as resolve10 } from "node:path";
13763
14057
 
13764
14058
  // src/infrastructure/http/safe-fetch.ts
13765
14059
  import { lookup } from "node:dns/promises";
@@ -13846,7 +14140,7 @@ async function safeFetch(urlString, options = {}) {
13846
14140
  }
13847
14141
  }
13848
14142
  async function delay(ms) {
13849
- return new Promise((resolve9) => setTimeout(resolve9, ms));
14143
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
13850
14144
  }
13851
14145
 
13852
14146
  // src/infrastructure/parsers/sitemap-parser.ts
@@ -15050,12 +15344,12 @@ async function seoAuditCommand(options) {
15050
15344
  formattedOutput = formatCli(report);
15051
15345
  }
15052
15346
  if (output) {
15053
- const outputPath = resolve9(options.projectRoot, output);
15347
+ const outputPath = resolve10(options.projectRoot, output);
15054
15348
  await mkdir6(dirname4(outputPath), { recursive: true });
15055
15349
  await writeFile5(outputPath, formattedOutput, "utf-8");
15056
15350
  } else if (format === "markdown") {
15057
15351
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
15058
- const defaultPath = resolve9(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
15352
+ const defaultPath = resolve10(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
15059
15353
  await mkdir6(dirname4(defaultPath), { recursive: true });
15060
15354
  await writeFile5(defaultPath, formattedOutput, "utf-8");
15061
15355
  process.stderr.write(`Report saved to: ${defaultPath}
@@ -15069,7 +15363,7 @@ async function seoAuditCommand(options) {
15069
15363
  command: "seo audit",
15070
15364
  report,
15071
15365
  output: formattedOutput,
15072
- outputFile: output ? resolve9(options.projectRoot, output) : format === "markdown" ? resolve9(options.projectRoot, "seo-reports") : undefined
15366
+ outputFile: output ? resolve10(options.projectRoot, output) : format === "markdown" ? resolve10(options.projectRoot, "seo-reports") : undefined
15073
15367
  }
15074
15368
  };
15075
15369
  } catch (error) {
@@ -15086,7 +15380,7 @@ async function seoAuditCommand(options) {
15086
15380
 
15087
15381
  // src/commands/seo-llms.command.ts
15088
15382
  import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
15089
- import { dirname as dirname5, resolve as resolve10 } from "node:path";
15383
+ import { dirname as dirname5, resolve as resolve11 } from "node:path";
15090
15384
 
15091
15385
  // src/domain/seo/llms-generator.ts
15092
15386
  function extractTitle2(html) {
@@ -15229,7 +15523,7 @@ async function seoLlmsCommand(options) {
15229
15523
  }
15230
15524
  }) : await generateLlmsTxtFromUrl(url);
15231
15525
  process.stderr.write("\r" + " ".repeat(80) + "\r");
15232
- const outputPath = output ? resolve10(options.projectRoot, output) : resolve10(options.projectRoot, "llms.txt");
15526
+ const outputPath = output ? resolve11(options.projectRoot, output) : resolve11(options.projectRoot, "llms.txt");
15233
15527
  await mkdir7(dirname5(outputPath), { recursive: true });
15234
15528
  await writeFile6(outputPath, result.content, "utf-8");
15235
15529
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
@@ -15257,10 +15551,14 @@ async function seoLlmsCommand(options) {
15257
15551
  }
15258
15552
 
15259
15553
  // src/commands/update.command.ts
15554
+ import { homedir as homedir8 } from "node:os";
15555
+ import { resolve as resolve12, dirname as dirname6 } from "node:path";
15556
+ import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7, stat as stat3 } from "node:fs/promises";
15260
15557
  async function updateCommand(options) {
15261
- const { dryRun, force, output, projectRoot } = options;
15558
+ const { dryRun, force, global: isGlobal, output, projectRoot } = options;
15559
+ const basePath = isGlobal ? homedir8() : projectRoot;
15262
15560
  try {
15263
- const configResult = await loadConfig(projectRoot);
15561
+ const configResult = await loadConfig(basePath);
15264
15562
  if (!configResult.success) {
15265
15563
  return {
15266
15564
  code: 1,
@@ -15272,42 +15570,40 @@ async function updateCommand(options) {
15272
15570
  };
15273
15571
  }
15274
15572
  const config = configResult.data;
15275
- if (!config.presets.council.enabled) {
15276
- return {
15277
- code: 4,
15278
- data: {
15279
- success: false,
15280
- command: "update",
15281
- errors: ["Council preset is not enabled"]
15282
- }
15283
- };
15284
- }
15285
- const presetResult = await loadCouncilPreset(projectRoot);
15286
- if (!presetResult.success) {
15287
- return {
15288
- code: 1,
15289
- data: {
15290
- success: false,
15291
- command: "update",
15292
- errors: ["Failed to load preset"]
15293
- }
15294
- };
15573
+ const updateResults = await checkUpdates(basePath, isGlobal);
15574
+ const largeFiles = await validateAgentFileSizes(basePath, isGlobal);
15575
+ if (largeFiles.length > 0) {
15576
+ largeFiles.forEach((file) => {
15577
+ console.warn(`[WARNING] File exceeds 40KB: ${file.path} (${(file.size / 1024).toFixed(1)} KB)`);
15578
+ });
15295
15579
  }
15296
- const spec = presetResult.data;
15297
- const currentVersion = config.presets.council.version;
15298
- const newVersion = spec.version;
15299
- if (currentVersion === newVersion) {
15580
+ if (!updateResults.hasUpdates) {
15300
15581
  return {
15301
15582
  code: 0,
15302
15583
  data: {
15303
15584
  success: true,
15304
15585
  command: "update",
15305
15586
  message: "Already up to date",
15306
- currentVersion,
15307
- newVersion
15587
+ wouldUpdate: [],
15588
+ updated: []
15308
15589
  }
15309
15590
  };
15310
15591
  }
15592
+ const wouldUpdate = [];
15593
+ if (updateResults.council)
15594
+ wouldUpdate.push("council preset");
15595
+ if (updateResults.policy)
15596
+ wouldUpdate.push("policy file");
15597
+ for (const t of updateResults.targets) {
15598
+ if (t.hasUpdate) {
15599
+ wouldUpdate.push(`${t.target} target files`);
15600
+ }
15601
+ }
15602
+ for (const s of updateResults.skills) {
15603
+ if (s.hasUpdate) {
15604
+ wouldUpdate.push(`skill ${s.id} to v${s.latestVersion}`);
15605
+ }
15606
+ }
15311
15607
  if (dryRun) {
15312
15608
  return {
15313
15609
  code: 0,
@@ -15315,48 +15611,77 @@ async function updateCommand(options) {
15315
15611
  success: true,
15316
15612
  command: "update",
15317
15613
  message: "Dry run - would update",
15318
- currentVersion,
15319
- newVersion,
15320
- wouldUpdate: ["council preset files"]
15614
+ wouldUpdate,
15615
+ updated: []
15321
15616
  }
15322
15617
  };
15323
15618
  }
15324
- const writeOptions = { dryRun: false, force };
15325
- const target = config.defaults.target;
15326
- let installer;
15327
- switch (target) {
15328
- case "opencode":
15329
- installer = createOpenCodeInstaller(spec);
15330
- break;
15331
- case "claude":
15332
- installer = createClaudeInstaller(spec);
15333
- break;
15334
- case "codex":
15335
- installer = createCodexInstaller(spec);
15336
- break;
15337
- default:
15338
- return {
15339
- code: 1,
15340
- data: {
15341
- success: false,
15342
- command: "update",
15343
- errors: [`Unknown target: ${target}`]
15619
+ const updated = [];
15620
+ if (updateResults.council) {
15621
+ const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
15622
+ const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
15623
+ try {
15624
+ const content = await readFile8(bundledCouncil, "utf-8");
15625
+ await mkdir8(dirname6(localCouncil), { recursive: true });
15626
+ await writeFile7(localCouncil, content, "utf-8");
15627
+ updated.push(localCouncil);
15628
+ } catch (e) {
15629
+ throw new Error(`Failed to update council.yml: ${e}`);
15630
+ }
15631
+ }
15632
+ if (updateResults.policy) {
15633
+ const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
15634
+ try {
15635
+ const content = await readFile8(POLICY_PATH, "utf-8");
15636
+ await mkdir8(dirname6(localPolicy), { recursive: true });
15637
+ await writeFile7(localPolicy, content, "utf-8");
15638
+ updated.push(localPolicy);
15639
+ } catch (e) {
15640
+ throw new Error(`Failed to update policy.yml: ${e}`);
15641
+ }
15642
+ }
15643
+ const locale = config?.defaults?.locale ?? "en";
15644
+ for (const t of updateResults.targets) {
15645
+ if (t.hasUpdate) {
15646
+ const targetName = t.target;
15647
+ const manifest = await loadManifest(targetName);
15648
+ const modelConfig = await loadModelConfig(targetName);
15649
+ const results = await copyFromManifest(manifest, PRESETS_DIR, basePath, isGlobal, false, force, modelConfig, locale);
15650
+ for (const r of results) {
15651
+ if (r.action !== "skipped" && r.action !== "error") {
15652
+ updated.push(r.dest);
15653
+ } else if (r.action === "error") {
15654
+ throw new Error(`Failed to copy target file: ${r.error}`);
15344
15655
  }
15345
- };
15656
+ }
15657
+ }
15346
15658
  }
15347
- const files = await installer.generate();
15348
- const results = await writeGeneratedFiles(files, writeOptions);
15349
- const updated = results.filter((r) => r.success).map((r) => r.path);
15350
- const errors3 = results.filter((r) => !r.success).map((r) => `${r.path}: ${r.error}`);
15351
- if (errors3.length > 0) {
15352
- return {
15353
- code: 2,
15354
- data: {
15355
- success: false,
15356
- command: "update",
15357
- errors: errors3
15659
+ if (updateResults.skills.some((s) => s.hasUpdate)) {
15660
+ const newSkillsLock = {};
15661
+ const currentLock = await loadSkillsLock(basePath) || {};
15662
+ for (const [id, ver] of Object.entries(currentLock)) {
15663
+ newSkillsLock[id] = ver;
15664
+ }
15665
+ for (const s of updateResults.skills) {
15666
+ newSkillsLock[s.id] = s.latestVersion;
15667
+ }
15668
+ let lockDest = resolve12(basePath, ".codeconductor", "skills-lock.json");
15669
+ try {
15670
+ const statAgents = await stat3(resolve12(basePath, ".agents"));
15671
+ if (statAgents.isDirectory()) {
15672
+ const statCodeConductor = await stat3(resolve12(basePath, ".codeconductor")).catch(() => null);
15673
+ if (!statCodeConductor) {
15674
+ lockDest = resolve12(basePath, ".agents", "skills-lock.json");
15675
+ }
15358
15676
  }
15359
- };
15677
+ } catch {}
15678
+ try {
15679
+ await mkdir8(dirname6(lockDest), { recursive: true });
15680
+ await writeFile7(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
15681
+ updated.push(lockDest);
15682
+ } catch (e) {
15683
+ throw new Error(`Failed to write skills-lock.json: ${e}`);
15684
+ }
15360
15685
  }
15361
15686
  return {
15362
15687
  code: 0,
@@ -15364,8 +15689,7 @@ async function updateCommand(options) {
15364
15689
  success: true,
15365
15690
  command: "update",
15366
15691
  message: "Updated successfully",
15367
- currentVersion,
15368
- newVersion,
15692
+ wouldUpdate: [],
15369
15693
  updated
15370
15694
  }
15371
15695
  };
@@ -15570,6 +15894,7 @@ async function routeCommand(args, projectRoot) {
15570
15894
  projectRoot,
15571
15895
  dryRun: flags.dryRun,
15572
15896
  force: flags.force,
15897
+ global: options.global === true || options.global === "true",
15573
15898
  output: flags.output
15574
15899
  });
15575
15900
  case "seo": {