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/README.md +5 -4
- package/dist/index.js +1333 -1008
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +35 -2
- package/presets/claude/CLAUDE.md +28 -2
- package/presets/codex/AGENTS.md +28 -2
- package/presets/opencode/agents/implementer.md +2 -2
- package/presets/opencode/agents/orchestrator.md +11 -4
- package/presets/opencode/agents/reviewer.md +2 -0
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.
|
|
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/
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
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/
|
|
11840
|
-
import {
|
|
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 {
|
|
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/
|
|
11849
|
-
var
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
12113
|
-
await
|
|
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 =
|
|
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
|
|
12122
|
-
await
|
|
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
|
|
12145
|
-
import { resolve as
|
|
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
|
-
##
|
|
12429
|
-
${
|
|
12886
|
+
## Model Hint
|
|
12887
|
+
${agent.modelHint}
|
|
12430
12888
|
|
|
12431
|
-
##
|
|
12432
|
-
${
|
|
12889
|
+
## Focus Areas
|
|
12890
|
+
${agent.focus.map((f) => `- ${f}`).join(`
|
|
12433
12891
|
`)}
|
|
12434
12892
|
|
|
12435
12893
|
## Responsibilities
|
|
12436
|
-
|
|
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
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
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
|
|
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
|
|
12471
|
-
return
|
|
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/
|
|
12475
|
-
class
|
|
12476
|
-
name = "
|
|
12477
|
-
target = "
|
|
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
|
|
12948
|
+
return generateClaudeFiles(this.spec);
|
|
12487
12949
|
}
|
|
12488
12950
|
async isAvailable() {
|
|
12489
12951
|
return true;
|
|
12490
12952
|
}
|
|
12491
12953
|
}
|
|
12492
|
-
function
|
|
12493
|
-
const installer = new
|
|
12954
|
+
function createClaudeInstaller(spec) {
|
|
12955
|
+
const installer = new ClaudeInstaller;
|
|
12494
12956
|
installer.setSpec(spec);
|
|
12495
12957
|
return installer;
|
|
12496
12958
|
}
|
|
12497
12959
|
|
|
12498
|
-
// src/
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
}
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
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
|
-
##
|
|
13014
|
+
## Agents
|
|
13015
|
+
${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
|
|
13016
|
+
`)}
|
|
12647
13017
|
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
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/
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
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
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
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
|
-
|
|
12686
|
-
|
|
13037
|
+
async isAvailable() {
|
|
13038
|
+
return true;
|
|
12687
13039
|
}
|
|
12688
13040
|
}
|
|
12689
|
-
function
|
|
12690
|
-
const
|
|
12691
|
-
|
|
12692
|
-
return
|
|
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/
|
|
12699
|
-
|
|
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
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
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
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
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
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
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
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
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
|
|
12803
|
-
if (
|
|
12804
|
-
return
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
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
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
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
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
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
|
-
|
|
12885
|
-
|
|
13162
|
+
async generate() {
|
|
13163
|
+
if (!this.spec) {
|
|
13164
|
+
throw new Error("Council spec not set");
|
|
13165
|
+
}
|
|
13166
|
+
return generateOpenCodeFiles(this.spec);
|
|
12886
13167
|
}
|
|
12887
|
-
|
|
12888
|
-
|
|
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
|
-
|
|
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
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
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
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
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
|
|
12938
|
-
import { resolve as
|
|
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
|
-
|
|
12942
|
-
|
|
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
|
|
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 ?
|
|
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:
|
|
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 ?
|
|
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
|
|
13136
|
-
import { resolve as
|
|
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
|
|
13381
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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:
|
|
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
|
|
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((
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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
|
|
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 ?
|
|
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(
|
|
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
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15307
|
-
|
|
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
|
-
|
|
15319
|
-
|
|
15320
|
-
wouldUpdate: ["council preset files"]
|
|
15614
|
+
wouldUpdate,
|
|
15615
|
+
updated: []
|
|
15321
15616
|
}
|
|
15322
15617
|
};
|
|
15323
15618
|
}
|
|
15324
|
-
const
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
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
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
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
|
-
|
|
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": {
|