cc-codeconductor 0.3.0 → 0.3.2
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 +1414 -1008
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +35 -2
- package/presets/claude/CLAUDE.md +32 -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/src/presets/manifests/claude.yml +2 -2
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.2",
|
|
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,1011 @@ 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
|
+
const hasBegin = existing.includes(MANAGED_BEGIN_MARKER);
|
|
11871
|
+
const hasEnd = existing.includes(MANAGED_END_MARKER);
|
|
11872
|
+
if (!hasBegin && !hasEnd) {
|
|
11873
|
+
return { content: incoming, action: "written" };
|
|
11874
|
+
}
|
|
11875
|
+
validateMarkers(existing, "existing content");
|
|
11876
|
+
const existingBlock = getManagedBlockRange(existing);
|
|
11877
|
+
const incomingBlock = getManagedBlockRange(incoming);
|
|
11878
|
+
return {
|
|
11879
|
+
content: existing.slice(0, existingBlock.start) + incoming.slice(incomingBlock.start, incomingBlock.end) + existing.slice(existingBlock.end),
|
|
11880
|
+
action: "merged"
|
|
11881
|
+
};
|
|
11882
|
+
}
|
|
11883
|
+
function validateMarkers(content, label) {
|
|
11884
|
+
const beginCount = countOccurrences(content, MANAGED_BEGIN_MARKER);
|
|
11885
|
+
const endCount = countOccurrences(content, MANAGED_END_MARKER);
|
|
11886
|
+
if (beginCount !== 1 || endCount !== 1) {
|
|
11887
|
+
throw new Error(`${label} must contain exactly one managed begin marker and one managed end marker`);
|
|
11888
|
+
}
|
|
11889
|
+
if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
|
|
11890
|
+
throw new Error(`${label} has managed markers in the wrong order`);
|
|
11891
|
+
}
|
|
11892
|
+
}
|
|
11893
|
+
function getManagedBlockRange(content) {
|
|
11894
|
+
const start = content.indexOf(MANAGED_BEGIN_MARKER);
|
|
11895
|
+
const end = content.indexOf(MANAGED_END_MARKER) + MANAGED_END_MARKER.length;
|
|
11896
|
+
return { start, end };
|
|
11897
|
+
}
|
|
11898
|
+
function countOccurrences(content, needle) {
|
|
11899
|
+
return content.split(needle).length - 1;
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
// src/core/presets/file-copier.ts
|
|
11903
|
+
async function listFilesRecursive(dir, base = dir) {
|
|
11904
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
11905
|
+
const files = [];
|
|
11906
|
+
for (const entry of entries) {
|
|
11907
|
+
const full = join2(dir, entry.name);
|
|
11908
|
+
if (entry.isDirectory()) {
|
|
11909
|
+
files.push(...await listFilesRecursive(full, base));
|
|
11910
|
+
} else {
|
|
11911
|
+
files.push(relative(base, full));
|
|
11912
|
+
}
|
|
11913
|
+
}
|
|
11914
|
+
return files;
|
|
11915
|
+
}
|
|
11916
|
+
async function resolveEntryFiles(entry, presetsDir, baseDir) {
|
|
11917
|
+
const srcAbsolute = resolve3(presetsDir, entry.src);
|
|
11918
|
+
try {
|
|
11919
|
+
const s = await stat(srcAbsolute);
|
|
11920
|
+
if (s.isDirectory()) {
|
|
11921
|
+
const files = await listFilesRecursive(srcAbsolute);
|
|
11922
|
+
return files.map((f) => ({
|
|
11923
|
+
src: join2(srcAbsolute, f),
|
|
11924
|
+
dest: resolve3(baseDir, entry.dest, f)
|
|
11925
|
+
}));
|
|
11926
|
+
}
|
|
11927
|
+
return [{ src: srcAbsolute, dest: resolve3(baseDir, entry.dest) }];
|
|
11928
|
+
} catch {
|
|
11929
|
+
return [];
|
|
11930
|
+
}
|
|
11931
|
+
}
|
|
11932
|
+
function mergeDeep(target, source) {
|
|
11933
|
+
const result = { ...target };
|
|
11934
|
+
for (const key of Object.keys(source)) {
|
|
11935
|
+
const srcVal = source[key];
|
|
11936
|
+
const tgtVal = target[key];
|
|
11937
|
+
if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
|
|
11938
|
+
result[key] = [...new Set([...tgtVal, ...srcVal])];
|
|
11939
|
+
} else if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
11940
|
+
result[key] = mergeDeep(tgtVal, srcVal);
|
|
11941
|
+
} else {
|
|
11942
|
+
result[key] = srcVal;
|
|
11943
|
+
}
|
|
11944
|
+
}
|
|
11945
|
+
return result;
|
|
11946
|
+
}
|
|
11947
|
+
function extractAgentRole(filePath) {
|
|
11948
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
11949
|
+
const basename = parts[parts.length - 1];
|
|
11950
|
+
const name = basename.replace(/\.md$/, "");
|
|
11951
|
+
const knownRoles = [
|
|
11952
|
+
"architect",
|
|
11953
|
+
"implementer",
|
|
11954
|
+
"tester",
|
|
11955
|
+
"orchestrator",
|
|
11956
|
+
"reviewer",
|
|
11957
|
+
"docs",
|
|
11958
|
+
"task-coach",
|
|
11959
|
+
"repo-explorer"
|
|
11960
|
+
];
|
|
11961
|
+
return knownRoles.includes(name) ? name : null;
|
|
11962
|
+
}
|
|
11963
|
+
function renderTemplate(content, modelConfig, filePath, locale = "en") {
|
|
11964
|
+
const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
|
|
11965
|
+
const templatedContent = content.replace(/\{\{COMMIT_STYLE\}\}/g, COMMIT_STYLE[cleanLocale]).replace(/\{\{COMMIT_WORKFLOW\}\}/g, COMMIT_WORKFLOW[cleanLocale]);
|
|
11966
|
+
const agentRole = extractAgentRole(filePath);
|
|
11967
|
+
if (agentRole && modelConfig.agents[agentRole]) {
|
|
11968
|
+
const agentModels = modelConfig.agents[agentRole];
|
|
11969
|
+
const targetModel = agentModels[modelConfig.target];
|
|
11970
|
+
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));
|
|
11971
|
+
if (modelConfig.tools || modelConfig.permissions) {
|
|
11972
|
+
result2 = substituteToolNames(result2, modelConfig);
|
|
11973
|
+
}
|
|
11974
|
+
return result2;
|
|
11975
|
+
}
|
|
11976
|
+
const knownRoles = [
|
|
11977
|
+
"orchestrator",
|
|
11978
|
+
"task-coach",
|
|
11979
|
+
"architect",
|
|
11980
|
+
"implementer",
|
|
11981
|
+
"tester",
|
|
11982
|
+
"reviewer",
|
|
11983
|
+
"docs",
|
|
11984
|
+
"repo-explorer"
|
|
11985
|
+
];
|
|
11986
|
+
let result = templatedContent;
|
|
11987
|
+
for (const role of knownRoles) {
|
|
11988
|
+
if (!modelConfig.agents[role])
|
|
11989
|
+
continue;
|
|
11990
|
+
const agentModels = modelConfig.agents[role];
|
|
11991
|
+
const sectionRegex = new RegExp(`(### ${role}[\\s\\S]*?)(?=### |## |$)`, "gi");
|
|
11992
|
+
const sectionMatch = result.match(sectionRegex);
|
|
11993
|
+
if (sectionMatch) {
|
|
11994
|
+
for (const section of sectionMatch) {
|
|
11995
|
+
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 ?? "");
|
|
11996
|
+
result = result.replace(section, renderedSection);
|
|
11997
|
+
}
|
|
11998
|
+
}
|
|
11999
|
+
}
|
|
12000
|
+
result = result.replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
|
|
12001
|
+
if (modelConfig.tools || modelConfig.permissions) {
|
|
12002
|
+
result = substituteToolNames(result, modelConfig);
|
|
12003
|
+
}
|
|
12004
|
+
return result;
|
|
12005
|
+
}
|
|
12006
|
+
function substituteToolNames(content, modelConfig) {
|
|
12007
|
+
if (!modelConfig.tools && !modelConfig.permissions)
|
|
12008
|
+
return content;
|
|
12009
|
+
const target = modelConfig.target;
|
|
12010
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
12011
|
+
if (!fmMatch)
|
|
12012
|
+
return content;
|
|
12013
|
+
const frontmatter = fmMatch[1];
|
|
12014
|
+
if (target === "opencode" && modelConfig.permissions) {
|
|
12015
|
+
const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
|
|
12016
|
+
return content.replace(fmMatch[0], `---
|
|
12017
|
+
${updatedFrontmatter2}
|
|
12018
|
+
---`);
|
|
12019
|
+
}
|
|
12020
|
+
if (!modelConfig.tools)
|
|
12021
|
+
return content;
|
|
12022
|
+
const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
|
|
12023
|
+
const baseNames = toolsLine.split(",").map((t) => t.trim());
|
|
12024
|
+
const mappedNames = baseNames.map((baseName) => {
|
|
12025
|
+
const toolMapping = modelConfig.tools?.[baseName];
|
|
12026
|
+
if (toolMapping && toolMapping[target]) {
|
|
12027
|
+
return toolMapping[target];
|
|
12028
|
+
}
|
|
12029
|
+
return baseName;
|
|
12030
|
+
});
|
|
12031
|
+
return `tools: ${mappedNames.join(", ")}`;
|
|
12032
|
+
});
|
|
12033
|
+
return content.replace(fmMatch[0], `---
|
|
12034
|
+
${updatedFrontmatter}
|
|
12035
|
+
---`);
|
|
12036
|
+
}
|
|
12037
|
+
async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale) {
|
|
12038
|
+
if (strategy === "skip") {
|
|
12039
|
+
return { src: srcPath, dest: destPath, action: "skipped", dryRun };
|
|
12040
|
+
}
|
|
12041
|
+
let content;
|
|
12042
|
+
try {
|
|
12043
|
+
content = await readFile4(srcPath, "utf-8");
|
|
12044
|
+
} catch (e) {
|
|
12045
|
+
return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
|
|
12046
|
+
}
|
|
12047
|
+
const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
|
|
12048
|
+
let finalContent = incomingContent;
|
|
12049
|
+
let action = "written";
|
|
12050
|
+
if (strategy === "append") {
|
|
12051
|
+
let existing = "";
|
|
12052
|
+
try {
|
|
12053
|
+
existing = await readFile4(destPath, "utf-8");
|
|
12054
|
+
} catch {}
|
|
12055
|
+
if (existing) {
|
|
12056
|
+
finalContent = existing + `
|
|
12057
|
+
|
|
12058
|
+
---
|
|
12059
|
+
|
|
12060
|
+
` + incomingContent;
|
|
12061
|
+
}
|
|
12062
|
+
action = "appended";
|
|
12063
|
+
} else if (strategy === "merge-json") {
|
|
12064
|
+
let existing = {};
|
|
12065
|
+
try {
|
|
12066
|
+
existing = JSON.parse(await readFile4(destPath, "utf-8"));
|
|
12067
|
+
} catch {}
|
|
12068
|
+
try {
|
|
12069
|
+
const incoming = JSON.parse(incomingContent);
|
|
12070
|
+
finalContent = JSON.stringify(mergeDeep(existing, incoming), null, 2);
|
|
12071
|
+
} catch (e) {
|
|
12072
|
+
return { src: srcPath, dest: destPath, action: "error", error: `JSON merge failed: ${e}` };
|
|
12073
|
+
}
|
|
12074
|
+
action = "merged";
|
|
12075
|
+
} else if (strategy === "merge-managed") {
|
|
12076
|
+
let existing = null;
|
|
12077
|
+
try {
|
|
12078
|
+
existing = await readFile4(destPath, "utf-8");
|
|
12079
|
+
} catch {}
|
|
12080
|
+
try {
|
|
12081
|
+
const merged = mergeManagedBlock(existing, incomingContent);
|
|
12082
|
+
finalContent = merged.content;
|
|
12083
|
+
action = merged.action;
|
|
12084
|
+
} catch (e) {
|
|
12085
|
+
return { src: srcPath, dest: destPath, action: "error", error: `Managed merge failed: ${e}` };
|
|
12086
|
+
}
|
|
12087
|
+
}
|
|
12088
|
+
if (dryRun) {
|
|
12089
|
+
return { src: srcPath, dest: destPath, action, dryRun: true };
|
|
12090
|
+
}
|
|
12091
|
+
try {
|
|
12092
|
+
await mkdir(dirname2(destPath), { recursive: true });
|
|
12093
|
+
await writeFile(destPath, finalContent, "utf-8");
|
|
12094
|
+
return { src: srcPath, dest: destPath, action };
|
|
12095
|
+
} catch (e) {
|
|
12096
|
+
return { src: srcPath, dest: destPath, action: "error", error: String(e) };
|
|
12097
|
+
}
|
|
12098
|
+
}
|
|
12099
|
+
async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
|
|
12100
|
+
const results = [];
|
|
12101
|
+
for (const entry of manifest.entries) {
|
|
12102
|
+
const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
|
|
12103
|
+
let resolvedBaseDir = baseDir;
|
|
12104
|
+
let resolvedEntry = entry;
|
|
12105
|
+
if (manifest.target === "agy" && isGlobal) {
|
|
12106
|
+
resolvedBaseDir = join2(homedir(), ".gemini", "config");
|
|
12107
|
+
resolvedEntry = {
|
|
12108
|
+
...entry,
|
|
12109
|
+
dest: entry.dest.replace(/^\.agents\/?/, "")
|
|
12110
|
+
};
|
|
12111
|
+
}
|
|
12112
|
+
const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
|
|
12113
|
+
const isTemplate = entry.template === true;
|
|
12114
|
+
for (const { src, dest } of files) {
|
|
12115
|
+
results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale));
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12118
|
+
return results;
|
|
12119
|
+
}
|
|
12120
|
+
|
|
12121
|
+
// src/core/presets/update-checker.ts
|
|
12122
|
+
function getTargetInstallationPath(target, basePath, isGlobal) {
|
|
12123
|
+
if (target === "agy") {
|
|
12124
|
+
return isGlobal ? resolve4(basePath, ".gemini", "config") : resolve4(basePath, ".agents");
|
|
12125
|
+
}
|
|
12126
|
+
return resolve4(basePath, `.${target}`);
|
|
12127
|
+
}
|
|
12128
|
+
async function isTargetInstalled(target, basePath, isGlobal) {
|
|
12129
|
+
const path = getTargetInstallationPath(target, basePath, isGlobal);
|
|
12130
|
+
try {
|
|
12131
|
+
const s = await stat2(path);
|
|
12132
|
+
return s.isDirectory();
|
|
12133
|
+
} catch {
|
|
12134
|
+
return false;
|
|
12135
|
+
}
|
|
12136
|
+
}
|
|
12137
|
+
async function validateAgentFileSizes(basePath, isGlobal) {
|
|
12138
|
+
const filesToCheck = [];
|
|
12139
|
+
filesToCheck.push(resolve4(basePath, ".claude", "CLAUDE.md"));
|
|
12140
|
+
filesToCheck.push(resolve4(basePath, ".codex", "AGENTS.md"));
|
|
12141
|
+
if (isGlobal) {
|
|
12142
|
+
filesToCheck.push(resolve4(basePath, ".gemini", "config", "AGENTS.md"));
|
|
12143
|
+
} else {
|
|
12144
|
+
filesToCheck.push(resolve4(basePath, ".agents", "AGENTS.md"));
|
|
12145
|
+
}
|
|
12146
|
+
const largeFiles = [];
|
|
12147
|
+
for (const filePath of filesToCheck) {
|
|
12148
|
+
try {
|
|
12149
|
+
const s = await stat2(filePath);
|
|
12150
|
+
if (s.isFile() && s.size > 40 * 1024) {
|
|
12151
|
+
largeFiles.push({ path: filePath, size: s.size });
|
|
12152
|
+
}
|
|
12153
|
+
} catch {}
|
|
12154
|
+
}
|
|
12155
|
+
return largeFiles;
|
|
12156
|
+
}
|
|
12157
|
+
async function validateAgentMarkers(basePath, isGlobal) {
|
|
12158
|
+
const targetsToCheck = [
|
|
12159
|
+
"opencode",
|
|
12160
|
+
"claude",
|
|
12161
|
+
"codex",
|
|
12162
|
+
"gemini",
|
|
12163
|
+
"cursor",
|
|
12164
|
+
"agy"
|
|
12165
|
+
];
|
|
12166
|
+
const results = [];
|
|
12167
|
+
for (const target of targetsToCheck) {
|
|
12168
|
+
const isInstalled = await isTargetInstalled(target, basePath, isGlobal);
|
|
12169
|
+
if (!isInstalled) {
|
|
12170
|
+
continue;
|
|
12171
|
+
}
|
|
12172
|
+
let manifest;
|
|
12173
|
+
try {
|
|
12174
|
+
manifest = await loadManifest(target);
|
|
12175
|
+
} catch {
|
|
12176
|
+
continue;
|
|
12177
|
+
}
|
|
12178
|
+
for (const entry of manifest.entries) {
|
|
12179
|
+
const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
|
|
12180
|
+
if (strategy !== "merge-managed") {
|
|
12181
|
+
continue;
|
|
12182
|
+
}
|
|
12183
|
+
let resolvedEntry = entry;
|
|
12184
|
+
let targetBaseDir = basePath;
|
|
12185
|
+
if (target === "agy" && isGlobal) {
|
|
12186
|
+
targetBaseDir = join3(homedir2(), ".gemini", "config");
|
|
12187
|
+
resolvedEntry = {
|
|
12188
|
+
...entry,
|
|
12189
|
+
dest: entry.dest.replace(/^\.agents\/?/, "")
|
|
12190
|
+
};
|
|
12191
|
+
}
|
|
12192
|
+
const files = await resolveEntryFiles(resolvedEntry, PRESETS_DIR, targetBaseDir);
|
|
12193
|
+
for (const { dest } of files) {
|
|
12194
|
+
try {
|
|
12195
|
+
const content = await readFile5(dest, "utf-8");
|
|
12196
|
+
const beginCount = content.split(MANAGED_BEGIN_MARKER).length - 1;
|
|
12197
|
+
const endCount = content.split(MANAGED_END_MARKER).length - 1;
|
|
12198
|
+
if (beginCount === 0 && endCount === 0) {
|
|
12199
|
+
results.push({ path: dest, error: "Missing managed markers" });
|
|
12200
|
+
} else if (beginCount !== 1 || endCount !== 1) {
|
|
12201
|
+
results.push({
|
|
12202
|
+
path: dest,
|
|
12203
|
+
error: "Must contain exactly one managed begin marker and one managed end marker"
|
|
12204
|
+
});
|
|
12205
|
+
} else if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
|
|
12206
|
+
results.push({
|
|
12207
|
+
path: dest,
|
|
12208
|
+
error: "Managed markers are in the wrong order"
|
|
12209
|
+
});
|
|
12210
|
+
}
|
|
12211
|
+
} catch {}
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
return results;
|
|
12216
|
+
}
|
|
12217
|
+
async function fileContentDiffers(pathA, pathB) {
|
|
12218
|
+
try {
|
|
12219
|
+
const contentA = await readFile5(pathA, "utf-8");
|
|
12220
|
+
const contentB = await readFile5(pathB, "utf-8");
|
|
12221
|
+
return contentA.trim() !== contentB.trim();
|
|
12222
|
+
} catch {
|
|
12223
|
+
try {
|
|
12224
|
+
await stat2(pathB);
|
|
12225
|
+
return true;
|
|
12226
|
+
} catch {
|
|
12227
|
+
return false;
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
}
|
|
12231
|
+
async function loadSkillsLock(basePath) {
|
|
12232
|
+
const paths = [
|
|
12233
|
+
resolve4(basePath, ".codeconductor", "skills-lock.json"),
|
|
12234
|
+
resolve4(basePath, ".agents", "skills-lock.json")
|
|
12235
|
+
];
|
|
12236
|
+
for (const p of paths) {
|
|
12237
|
+
try {
|
|
12238
|
+
const content = await readFile5(p, "utf-8");
|
|
12239
|
+
return JSON.parse(content);
|
|
12240
|
+
} catch {}
|
|
12241
|
+
}
|
|
12242
|
+
return null;
|
|
12243
|
+
}
|
|
12244
|
+
async function getLatestSkillVersion(skillId) {
|
|
12245
|
+
const targets = [
|
|
12246
|
+
"opencode",
|
|
12247
|
+
"agy",
|
|
12248
|
+
"claude",
|
|
12249
|
+
"codex",
|
|
12250
|
+
"gemini",
|
|
12251
|
+
"cursor"
|
|
12252
|
+
];
|
|
12253
|
+
for (const target of targets) {
|
|
12254
|
+
const skillPath = resolve4(ROOT_PRESETS_DIR, target, "skills", skillId, "SKILL.md");
|
|
12255
|
+
try {
|
|
12256
|
+
const content = await readFile5(skillPath, "utf-8");
|
|
12257
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
12258
|
+
if (fmMatch) {
|
|
12259
|
+
const parsed = $parse(fmMatch[1]);
|
|
12260
|
+
if (parsed && parsed.id === skillId && parsed.version) {
|
|
12261
|
+
return String(parsed.version);
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
} catch {}
|
|
12265
|
+
}
|
|
12266
|
+
return null;
|
|
12267
|
+
}
|
|
12268
|
+
async function checkUpdates(basePath, isGlobal) {
|
|
12269
|
+
const localCouncil = resolve4(basePath, ".codeconductor", "presets", "council.yml");
|
|
12270
|
+
const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
|
|
12271
|
+
const councilHasUpdate = await fileContentDiffers(localCouncil, bundledCouncil);
|
|
12272
|
+
const localPolicy = resolve4(basePath, ".codeconductor", "presets", "policy.yml");
|
|
12273
|
+
const policyHasUpdate = await fileContentDiffers(localPolicy, POLICY_PATH);
|
|
12274
|
+
const targetsToCheck = [
|
|
12275
|
+
"opencode",
|
|
12276
|
+
"claude",
|
|
12277
|
+
"codex",
|
|
12278
|
+
"gemini",
|
|
12279
|
+
"cursor",
|
|
12280
|
+
"agy"
|
|
12281
|
+
];
|
|
12282
|
+
const targetResults = [];
|
|
12283
|
+
let locale = "en";
|
|
12284
|
+
try {
|
|
12285
|
+
const configResult = await loadConfig(basePath);
|
|
12286
|
+
if (configResult.success) {
|
|
12287
|
+
locale = configResult.data.defaults.locale ?? "en";
|
|
12288
|
+
}
|
|
12289
|
+
} catch {}
|
|
12290
|
+
for (const target of targetsToCheck) {
|
|
12291
|
+
const isInstalled = await isTargetInstalled(target, basePath, isGlobal);
|
|
12292
|
+
if (!isInstalled) {
|
|
12293
|
+
continue;
|
|
12294
|
+
}
|
|
12295
|
+
let manifest;
|
|
12296
|
+
let modelConfig;
|
|
12297
|
+
try {
|
|
12298
|
+
manifest = await loadManifest(target);
|
|
12299
|
+
modelConfig = await loadModelConfig(target);
|
|
12300
|
+
} catch {
|
|
12301
|
+
continue;
|
|
12302
|
+
}
|
|
12303
|
+
const changedFiles = [];
|
|
12304
|
+
for (const entry of manifest.entries) {
|
|
12305
|
+
const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
|
|
12306
|
+
if (strategy === "skip")
|
|
12307
|
+
continue;
|
|
12308
|
+
let resolvedEntry = entry;
|
|
12309
|
+
let targetBaseDir = basePath;
|
|
12310
|
+
if (target === "agy" && isGlobal) {
|
|
12311
|
+
targetBaseDir = join3(homedir2(), ".gemini", "config");
|
|
12312
|
+
resolvedEntry = {
|
|
12313
|
+
...entry,
|
|
12314
|
+
dest: entry.dest.replace(/^\.agents\/?/, "")
|
|
12315
|
+
};
|
|
12316
|
+
}
|
|
12317
|
+
const files = await resolveEntryFiles(resolvedEntry, PRESETS_DIR, targetBaseDir);
|
|
12318
|
+
const isTemplate = entry.template === true;
|
|
12319
|
+
for (const { src, dest } of files) {
|
|
12320
|
+
let expectedContent;
|
|
12321
|
+
try {
|
|
12322
|
+
const srcContent = await readFile5(src, "utf-8");
|
|
12323
|
+
expectedContent = isTemplate && modelConfig ? renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
|
|
12324
|
+
} catch {
|
|
12325
|
+
continue;
|
|
12326
|
+
}
|
|
12327
|
+
let destContent;
|
|
12328
|
+
try {
|
|
12329
|
+
destContent = await readFile5(dest, "utf-8");
|
|
12330
|
+
} catch {
|
|
12331
|
+
changedFiles.push(dest);
|
|
12332
|
+
continue;
|
|
12333
|
+
}
|
|
12334
|
+
let fileHasUpdate = false;
|
|
12335
|
+
if (strategy === "overwrite") {
|
|
12336
|
+
if (destContent.trim() !== expectedContent.trim()) {
|
|
12337
|
+
fileHasUpdate = true;
|
|
12338
|
+
}
|
|
12339
|
+
} else if (strategy === "append") {
|
|
12340
|
+
if (!destContent.includes(expectedContent)) {
|
|
12341
|
+
fileHasUpdate = true;
|
|
12342
|
+
}
|
|
12343
|
+
} else if (strategy === "merge-json") {
|
|
12344
|
+
try {
|
|
12345
|
+
const destJson = JSON.parse(destContent);
|
|
12346
|
+
const expectedJson = JSON.parse(expectedContent);
|
|
12347
|
+
const merged = mergeDeep(destJson, expectedJson);
|
|
12348
|
+
if (JSON.stringify(merged) !== JSON.stringify(destJson)) {
|
|
12349
|
+
fileHasUpdate = true;
|
|
12350
|
+
}
|
|
12351
|
+
} catch {
|
|
12352
|
+
fileHasUpdate = true;
|
|
12353
|
+
}
|
|
12354
|
+
} else if (strategy === "merge-managed") {
|
|
12355
|
+
try {
|
|
12356
|
+
const merged = mergeManagedBlock(destContent, expectedContent);
|
|
12357
|
+
if (merged.content.trim() !== destContent.trim()) {
|
|
12358
|
+
fileHasUpdate = true;
|
|
12359
|
+
}
|
|
12360
|
+
} catch {
|
|
12361
|
+
fileHasUpdate = true;
|
|
12362
|
+
}
|
|
12363
|
+
}
|
|
12364
|
+
if (fileHasUpdate) {
|
|
12365
|
+
changedFiles.push(dest);
|
|
12366
|
+
}
|
|
12367
|
+
}
|
|
12368
|
+
}
|
|
12369
|
+
targetResults.push({
|
|
12370
|
+
target,
|
|
12371
|
+
hasUpdate: changedFiles.length > 0,
|
|
12372
|
+
files: changedFiles
|
|
12373
|
+
});
|
|
12374
|
+
}
|
|
12375
|
+
const skillResults = [];
|
|
12376
|
+
const skillsLock = await loadSkillsLock(basePath);
|
|
12377
|
+
if (skillsLock) {
|
|
12378
|
+
for (const [skillId, currentVersion] of Object.entries(skillsLock)) {
|
|
12379
|
+
const latestVersion = await getLatestSkillVersion(skillId);
|
|
12380
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
12381
|
+
skillResults.push({
|
|
12382
|
+
id: skillId,
|
|
12383
|
+
currentVersion,
|
|
12384
|
+
latestVersion,
|
|
12385
|
+
hasUpdate: true
|
|
12386
|
+
});
|
|
12387
|
+
} else {
|
|
12388
|
+
skillResults.push({
|
|
12389
|
+
id: skillId,
|
|
12390
|
+
currentVersion,
|
|
12391
|
+
latestVersion: latestVersion || currentVersion,
|
|
12392
|
+
hasUpdate: false
|
|
12393
|
+
});
|
|
12394
|
+
}
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
const hasPresetUpdates = councilHasUpdate || policyHasUpdate;
|
|
12398
|
+
const hasTargetUpdates = targetResults.some((t) => t.hasUpdate);
|
|
12399
|
+
const hasSkillUpdates = skillResults.some((s) => s.hasUpdate);
|
|
12400
|
+
return {
|
|
12401
|
+
hasUpdates: hasPresetUpdates || hasTargetUpdates || hasSkillUpdates,
|
|
12402
|
+
council: councilHasUpdate,
|
|
12403
|
+
policy: policyHasUpdate,
|
|
12404
|
+
targets: targetResults,
|
|
12405
|
+
skills: skillResults
|
|
12406
|
+
};
|
|
12407
|
+
}
|
|
12408
|
+
|
|
12409
|
+
// src/commands/doctor.command.ts
|
|
12410
|
+
async function doctorCommand(options) {
|
|
12411
|
+
const { projectRoot, output } = options;
|
|
12412
|
+
const checks = [];
|
|
12413
|
+
try {
|
|
12414
|
+
const hasConfig = await configExists(projectRoot);
|
|
12415
|
+
if (hasConfig) {
|
|
12416
|
+
checks.push({
|
|
12417
|
+
name: "config-exists",
|
|
12418
|
+
status: "pass",
|
|
12419
|
+
message: ".codeconductor/config.yml exists"
|
|
12420
|
+
});
|
|
12421
|
+
} else {
|
|
12422
|
+
checks.push({
|
|
12423
|
+
name: "config-exists",
|
|
12424
|
+
status: "fail",
|
|
12425
|
+
message: ".codeconductor/config.yml not found. Run `codeconductor init` first."
|
|
12426
|
+
});
|
|
12427
|
+
return {
|
|
12428
|
+
code: 4,
|
|
12429
|
+
data: {
|
|
12430
|
+
success: false,
|
|
12431
|
+
command: "doctor",
|
|
12432
|
+
checks
|
|
12433
|
+
}
|
|
12434
|
+
};
|
|
12435
|
+
}
|
|
12436
|
+
const configResult = await loadConfig(projectRoot);
|
|
12437
|
+
if (configResult.success) {
|
|
12438
|
+
checks.push({
|
|
12439
|
+
name: "config-valid",
|
|
12440
|
+
status: "pass",
|
|
12441
|
+
message: "Config is valid"
|
|
12442
|
+
});
|
|
12443
|
+
} else {
|
|
12444
|
+
checks.push({
|
|
12445
|
+
name: "config-valid",
|
|
12446
|
+
status: "fail",
|
|
12447
|
+
message: `Config validation failed: ${configResult.error.message}`
|
|
12448
|
+
});
|
|
12449
|
+
return {
|
|
12450
|
+
code: 1,
|
|
12451
|
+
data: {
|
|
12452
|
+
success: false,
|
|
12453
|
+
command: "doctor",
|
|
12454
|
+
checks
|
|
12455
|
+
}
|
|
12456
|
+
};
|
|
12457
|
+
}
|
|
12458
|
+
const runnerDirs = [".opencode", ".claude", ".codex"];
|
|
12459
|
+
for (const dir of runnerDirs) {
|
|
12460
|
+
try {
|
|
12461
|
+
const { access: access2 } = await import("node:fs/promises");
|
|
12462
|
+
await access2(join4(projectRoot, dir));
|
|
12463
|
+
checks.push({
|
|
12464
|
+
name: `dir-${dir}`,
|
|
12465
|
+
status: "pass",
|
|
12466
|
+
message: `${dir}/ exists`
|
|
12467
|
+
});
|
|
12468
|
+
} catch {
|
|
12469
|
+
checks.push({
|
|
12470
|
+
name: `dir-${dir}`,
|
|
12471
|
+
status: "warn",
|
|
12472
|
+
message: `${dir}/ not found (optional)`
|
|
12473
|
+
});
|
|
12474
|
+
}
|
|
12475
|
+
}
|
|
12476
|
+
const config = configResult.data;
|
|
12477
|
+
if (config.presets.council.enabled) {
|
|
12478
|
+
checks.push({
|
|
12479
|
+
name: "council-enabled",
|
|
12480
|
+
status: "pass",
|
|
12481
|
+
message: `Council preset enabled (v${config.presets.council.version})`
|
|
12482
|
+
});
|
|
12483
|
+
}
|
|
12484
|
+
const localUpdates = await checkUpdates(projectRoot, false);
|
|
12485
|
+
const globalUpdates = await checkUpdates(homedir3(), true);
|
|
12486
|
+
const updateDetails = [];
|
|
12487
|
+
if (localUpdates.hasUpdates) {
|
|
12488
|
+
if (localUpdates.council)
|
|
12489
|
+
updateDetails.push("local council preset");
|
|
12490
|
+
if (localUpdates.policy)
|
|
12491
|
+
updateDetails.push("local policy");
|
|
12492
|
+
const updatedLocalTargets = localUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
|
|
12493
|
+
if (updatedLocalTargets.length > 0) {
|
|
12494
|
+
updateDetails.push(`local targets (${updatedLocalTargets.join(", ")})`);
|
|
12495
|
+
}
|
|
12496
|
+
const updatedLocalSkills = localUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
|
|
12497
|
+
if (updatedLocalSkills.length > 0) {
|
|
12498
|
+
updateDetails.push(`local skills (${updatedLocalSkills.join(", ")})`);
|
|
12499
|
+
}
|
|
12500
|
+
}
|
|
12501
|
+
if (globalUpdates.hasUpdates) {
|
|
12502
|
+
if (globalUpdates.council)
|
|
12503
|
+
updateDetails.push("global council preset");
|
|
12504
|
+
if (globalUpdates.policy)
|
|
12505
|
+
updateDetails.push("global policy");
|
|
12506
|
+
const updatedGlobalTargets = globalUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
|
|
12507
|
+
if (updatedGlobalTargets.length > 0) {
|
|
12508
|
+
updateDetails.push(`global targets (${updatedGlobalTargets.join(", ")})`);
|
|
12509
|
+
}
|
|
12510
|
+
const updatedGlobalSkills = globalUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
|
|
12511
|
+
if (updatedGlobalSkills.length > 0) {
|
|
12512
|
+
updateDetails.push(`global skills (${updatedGlobalSkills.join(", ")})`);
|
|
12513
|
+
}
|
|
12514
|
+
}
|
|
12515
|
+
if (updateDetails.length > 0) {
|
|
12516
|
+
checks.push({
|
|
12517
|
+
name: "updates-available",
|
|
12518
|
+
status: "warn",
|
|
12519
|
+
message: `Updates available for: ${updateDetails.join(", ")}`
|
|
12520
|
+
});
|
|
12521
|
+
} else {
|
|
12522
|
+
checks.push({
|
|
12523
|
+
name: "updates-available",
|
|
12524
|
+
status: "pass",
|
|
12525
|
+
message: "All presets, targets, and skills are up to date"
|
|
12526
|
+
});
|
|
12527
|
+
}
|
|
12528
|
+
const largeFilesLocal = await validateAgentFileSizes(projectRoot, false);
|
|
12529
|
+
const largeFilesGlobal = await validateAgentFileSizes(homedir3(), true);
|
|
12530
|
+
const allLargeFiles = [...largeFilesLocal, ...largeFilesGlobal];
|
|
12531
|
+
if (allLargeFiles.length > 0) {
|
|
12532
|
+
checks.push({
|
|
12533
|
+
name: "agent-file-sizes",
|
|
12534
|
+
status: "warn",
|
|
12535
|
+
message: `The following files exceed 40KB: ${allLargeFiles.map((f) => f.path).join(", ")}`
|
|
12536
|
+
});
|
|
12537
|
+
} else {
|
|
12538
|
+
checks.push({
|
|
12539
|
+
name: "agent-file-sizes",
|
|
12540
|
+
status: "pass",
|
|
12541
|
+
message: "All agent files (AGENTS.md/CLAUDE.md) are under 40KB"
|
|
12542
|
+
});
|
|
12543
|
+
}
|
|
12544
|
+
const missingMarkersLocal = await validateAgentMarkers(projectRoot, false);
|
|
12545
|
+
const missingMarkersGlobal = await validateAgentMarkers(homedir3(), true);
|
|
12546
|
+
const allMissingMarkers = [...missingMarkersLocal, ...missingMarkersGlobal];
|
|
12547
|
+
if (allMissingMarkers.length > 0) {
|
|
12548
|
+
checks.push({
|
|
12549
|
+
name: "agent-file-markers",
|
|
12550
|
+
status: "warn",
|
|
12551
|
+
message: `The following files have missing or invalid managed markers: ${allMissingMarkers.map((f) => `${f.path} (${f.error})`).join(", ")}`
|
|
12552
|
+
});
|
|
12553
|
+
} else {
|
|
12554
|
+
checks.push({
|
|
12555
|
+
name: "agent-file-markers",
|
|
12556
|
+
status: "pass",
|
|
12557
|
+
message: "All agent files have valid managed markers"
|
|
12558
|
+
});
|
|
12559
|
+
}
|
|
12560
|
+
const securityCompatibility = await loadTargetSecurityCompatibility();
|
|
12561
|
+
for (const compatibility of securityCompatibility) {
|
|
12562
|
+
checks.push({
|
|
12563
|
+
name: `security-${compatibility.target}`,
|
|
12564
|
+
status: compatibility.status,
|
|
12565
|
+
message: compatibility.status === "pass" ? `${compatibility.target} can represent the canonical policy model` : `${compatibility.target} cannot enforce: ${compatibility.unsupportedRules.join(", ") || "see warnings"}`
|
|
12566
|
+
});
|
|
12567
|
+
}
|
|
12568
|
+
const failedCount = checks.filter((c) => c.status === "fail").length;
|
|
12569
|
+
if (failedCount > 0) {
|
|
12570
|
+
return {
|
|
12571
|
+
code: 4,
|
|
12572
|
+
data: {
|
|
12573
|
+
success: false,
|
|
12574
|
+
command: "doctor",
|
|
12575
|
+
checks
|
|
12576
|
+
}
|
|
12577
|
+
};
|
|
12578
|
+
}
|
|
12579
|
+
return {
|
|
12580
|
+
code: 0,
|
|
12581
|
+
data: {
|
|
12582
|
+
success: true,
|
|
12583
|
+
command: "doctor",
|
|
12584
|
+
checks,
|
|
12585
|
+
securityCompatibility
|
|
12586
|
+
}
|
|
12587
|
+
};
|
|
12588
|
+
} catch (error) {
|
|
12589
|
+
return {
|
|
12590
|
+
code: 1,
|
|
12591
|
+
data: {
|
|
12592
|
+
success: false,
|
|
12593
|
+
command: "doctor",
|
|
12594
|
+
errors: [String(error)]
|
|
12595
|
+
}
|
|
12596
|
+
};
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12599
|
+
|
|
12600
|
+
// src/commands/init.command.ts
|
|
12601
|
+
import { access as access3, mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
12602
|
+
import { homedir as homedir4 } from "node:os";
|
|
12603
|
+
import { basename, resolve as resolve6 } from "node:path";
|
|
12604
|
+
|
|
12605
|
+
// src/core/config/config-writer.ts
|
|
12606
|
+
import { access as access2, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
12607
|
+
import { resolve as resolve5 } from "node:path";
|
|
12608
|
+
|
|
12609
|
+
// src/core/config/codeconductor-config.ts
|
|
12610
|
+
var DEFAULT_CONFIG = {
|
|
12611
|
+
version: "0.2.0",
|
|
12612
|
+
project: {
|
|
12613
|
+
name: "unnamed-project"
|
|
12614
|
+
},
|
|
12615
|
+
defaults: {
|
|
12616
|
+
target: "opencode",
|
|
12617
|
+
overwrite: false,
|
|
12618
|
+
locale: "en"
|
|
12619
|
+
},
|
|
12620
|
+
presets: {
|
|
12621
|
+
council: {
|
|
12622
|
+
enabled: true,
|
|
12623
|
+
version: "0.1.0"
|
|
12624
|
+
}
|
|
12625
|
+
},
|
|
12626
|
+
safety: {
|
|
12627
|
+
destructiveCommands: ["rm -rf", "drop table", "delete from"],
|
|
12628
|
+
secretPatterns: ["password", "secret", "api_key", "token"]
|
|
12629
|
+
}
|
|
12630
|
+
};
|
|
12631
|
+
|
|
12632
|
+
// src/core/config/config-writer.ts
|
|
12633
|
+
var CONFIG_DIR = ".codeconductor";
|
|
12634
|
+
var CONFIG_FILE2 = "config.yml";
|
|
12635
|
+
async function writeConfig(projectRoot, config = {}, force = false) {
|
|
12636
|
+
try {
|
|
12637
|
+
const configDir = resolve5(projectRoot, CONFIG_DIR);
|
|
12638
|
+
await mkdir2(configDir, { recursive: true });
|
|
12639
|
+
const configPath = resolve5(configDir, CONFIG_FILE2);
|
|
12640
|
+
if (!force) {
|
|
12641
|
+
try {
|
|
12642
|
+
await access2(configPath);
|
|
12643
|
+
return ok(undefined);
|
|
12644
|
+
} catch {}
|
|
12645
|
+
}
|
|
12646
|
+
const mergedConfig = {
|
|
12647
|
+
...DEFAULT_CONFIG,
|
|
12648
|
+
...config,
|
|
12649
|
+
project: {
|
|
12650
|
+
...DEFAULT_CONFIG.project,
|
|
12651
|
+
...config.project
|
|
12652
|
+
},
|
|
12653
|
+
defaults: {
|
|
12654
|
+
...DEFAULT_CONFIG.defaults,
|
|
12655
|
+
...config.defaults
|
|
12656
|
+
},
|
|
12657
|
+
presets: {
|
|
12658
|
+
...DEFAULT_CONFIG.presets,
|
|
12659
|
+
...config.presets
|
|
12660
|
+
},
|
|
12661
|
+
safety: {
|
|
12662
|
+
...DEFAULT_CONFIG.safety,
|
|
12663
|
+
...config.safety
|
|
12664
|
+
}
|
|
12665
|
+
};
|
|
12666
|
+
const yamlContent = $stringify(mergedConfig);
|
|
12667
|
+
await writeFile2(configPath, yamlContent, "utf-8");
|
|
12668
|
+
return ok(undefined);
|
|
12669
|
+
} catch (error) {
|
|
12670
|
+
return err(new ValidationError("Failed to write config", error));
|
|
12671
|
+
}
|
|
12672
|
+
}
|
|
12673
|
+
|
|
12674
|
+
// src/core/presets/preset-resolver.ts
|
|
12675
|
+
var CURRENT_PRESET_VERSION = "v0.3.0";
|
|
12676
|
+
function resolvePreset(target, profile) {
|
|
12677
|
+
const stack = resolveStack(profile);
|
|
12678
|
+
const isMonorepo = profile.signals.some((s) => [
|
|
12679
|
+
"pnpm-workspace.yaml",
|
|
12680
|
+
"lerna.json",
|
|
12681
|
+
"go.work",
|
|
12682
|
+
"package.json-workspaces",
|
|
12683
|
+
"Cargo.toml-workspace"
|
|
12684
|
+
].includes(s));
|
|
12685
|
+
const architecture = isMonorepo ? "monorepo" : profile.signals.length > 0 ? "single-project" : "unknown";
|
|
12686
|
+
const warnings = [];
|
|
12687
|
+
if (profile.confidence === "low") {
|
|
12688
|
+
warnings.push("Detection confidence is low; review the selected preset before applying it.");
|
|
12689
|
+
}
|
|
12690
|
+
if (stack === "unknown") {
|
|
12691
|
+
warnings.push("No supported stack was detected; using the generic CodeConductor workflow preset.");
|
|
12692
|
+
} else {
|
|
12693
|
+
warnings.push(`${stack} projects currently receive the generic ${target} workflow plus matching skills; stack-specific asset pruning is not implemented yet.`);
|
|
12694
|
+
}
|
|
12695
|
+
if (architecture === "unknown") {
|
|
12696
|
+
warnings.push("Project architecture could not be inferred from available signals.");
|
|
12697
|
+
}
|
|
12698
|
+
return {
|
|
12699
|
+
target,
|
|
12700
|
+
stack,
|
|
12701
|
+
architecture,
|
|
12702
|
+
confidence: profile.confidence,
|
|
12703
|
+
presetVersion: CURRENT_PRESET_VERSION,
|
|
12704
|
+
assets: resolveAssets(target),
|
|
12705
|
+
warnings
|
|
12706
|
+
};
|
|
12707
|
+
}
|
|
12708
|
+
function resolveStack(profile) {
|
|
12709
|
+
if (profile.frameworks.includes("spring"))
|
|
12710
|
+
return "spring";
|
|
12711
|
+
if (profile.frameworks.includes("django"))
|
|
12712
|
+
return "django";
|
|
12713
|
+
if (profile.frameworks.includes("astro"))
|
|
12714
|
+
return "astro";
|
|
12715
|
+
if (profile.frameworks.includes("nextjs"))
|
|
12716
|
+
return "nextjs";
|
|
12717
|
+
if (profile.frameworks.includes("fastapi"))
|
|
12718
|
+
return "fastapi";
|
|
12719
|
+
if (profile.frameworks.includes("backend"))
|
|
12720
|
+
return "backend";
|
|
12721
|
+
if (profile.frameworks.includes("frontend"))
|
|
12722
|
+
return "frontend";
|
|
12723
|
+
if (profile.frameworks.includes("android"))
|
|
12724
|
+
return "android";
|
|
12725
|
+
if (profile.frameworks.includes("laravel"))
|
|
12726
|
+
return "laravel";
|
|
12727
|
+
if (profile.runtimes.includes("php"))
|
|
12728
|
+
return "php";
|
|
12729
|
+
if (profile.runtimes.includes("bun"))
|
|
11969
12730
|
return "bun";
|
|
11970
12731
|
if (profile.runtimes.includes("node"))
|
|
11971
12732
|
return "node";
|
|
@@ -11991,7 +12752,7 @@ function resolveAssets(target) {
|
|
|
11991
12752
|
// src/commands/init.command.ts
|
|
11992
12753
|
async function initCommand(options) {
|
|
11993
12754
|
const { projectRoot, dryRun, force, global: isGlobal, output, locale } = options;
|
|
11994
|
-
const baseDir = isGlobal ?
|
|
12755
|
+
const baseDir = isGlobal ? homedir4() : projectRoot;
|
|
11995
12756
|
try {
|
|
11996
12757
|
let profile = null;
|
|
11997
12758
|
if (!isGlobal) {
|
|
@@ -12098,7 +12859,7 @@ async function initCommand(options) {
|
|
|
12098
12859
|
}
|
|
12099
12860
|
async function resolvePresetsToCopy() {
|
|
12100
12861
|
const sources = [];
|
|
12101
|
-
const bundledCouncil =
|
|
12862
|
+
const bundledCouncil = resolve6(SRC_PRESETS_DIR, "council", "council.yml");
|
|
12102
12863
|
if (await fileExists2(bundledCouncil)) {
|
|
12103
12864
|
sources.push({ name: "council.yml", sourcePath: bundledCouncil });
|
|
12104
12865
|
}
|
|
@@ -12109,17 +12870,17 @@ async function resolvePresetsToCopy() {
|
|
|
12109
12870
|
return sources;
|
|
12110
12871
|
}
|
|
12111
12872
|
async function copyPresets(baseDir, presets, force) {
|
|
12112
|
-
const presetsDir =
|
|
12113
|
-
await
|
|
12873
|
+
const presetsDir = resolve6(baseDir, ".codeconductor", "presets");
|
|
12874
|
+
await mkdir3(presetsDir, { recursive: true });
|
|
12114
12875
|
const copied = [];
|
|
12115
12876
|
for (const preset of presets) {
|
|
12116
|
-
const destPath =
|
|
12877
|
+
const destPath = resolve6(presetsDir, preset.name);
|
|
12117
12878
|
if (!force && await fileExists2(destPath)) {
|
|
12118
12879
|
continue;
|
|
12119
12880
|
}
|
|
12120
12881
|
try {
|
|
12121
|
-
const content = await
|
|
12122
|
-
await
|
|
12882
|
+
const content = await readFile6(preset.sourcePath, "utf-8");
|
|
12883
|
+
await writeFile3(destPath, content, "utf-8");
|
|
12123
12884
|
copied.push(`.codeconductor/presets/${preset.name}`);
|
|
12124
12885
|
} catch (err2) {
|
|
12125
12886
|
const code = err2.code;
|
|
@@ -12141,8 +12902,8 @@ async function fileExists2(path) {
|
|
|
12141
12902
|
}
|
|
12142
12903
|
|
|
12143
12904
|
// src/commands/install.command.ts
|
|
12144
|
-
import { homedir as
|
|
12145
|
-
import { resolve as
|
|
12905
|
+
import { homedir as homedir5 } from "node:os";
|
|
12906
|
+
import { resolve as resolve8 } from "node:path";
|
|
12146
12907
|
|
|
12147
12908
|
// src/domain/council/council-agent.ts
|
|
12148
12909
|
function getResponsibilities(agentId) {
|
|
@@ -12200,281 +12961,63 @@ function generateAgentContent(agent) {
|
|
|
12200
12961
|
## Role
|
|
12201
12962
|
${agent.role}
|
|
12202
12963
|
|
|
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
|
|
12964
|
+
## Context
|
|
12965
|
+
${agent.context === "repo-readonly" ? "Can read repository but cannot modify files" : "Only receives prompts, no direct repository access"}
|
|
12427
12966
|
|
|
12428
|
-
##
|
|
12429
|
-
${
|
|
12967
|
+
## Model Hint
|
|
12968
|
+
${agent.modelHint}
|
|
12430
12969
|
|
|
12431
|
-
##
|
|
12432
|
-
${
|
|
12970
|
+
## Focus Areas
|
|
12971
|
+
${agent.focus.map((f) => `- ${f}`).join(`
|
|
12433
12972
|
`)}
|
|
12434
12973
|
|
|
12435
12974
|
## Responsibilities
|
|
12436
|
-
|
|
12437
|
-
- Synthesize different perspectives
|
|
12438
|
-
- Identify consensus and disagreements
|
|
12439
|
-
- Provide final recommendation
|
|
12975
|
+
${getResponsibilities(agent.id)}
|
|
12440
12976
|
`;
|
|
12441
12977
|
}
|
|
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
12978
|
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12979
|
+
// src/adapters/claude/claude-council-generator.ts
|
|
12980
|
+
function generateClaudeFiles(spec) {
|
|
12981
|
+
const files = [];
|
|
12982
|
+
files.push({
|
|
12983
|
+
path: ".claude/skills/council/SKILL.md",
|
|
12984
|
+
content: generateCouncilSkill(spec),
|
|
12985
|
+
overwrite: false
|
|
12986
|
+
});
|
|
12987
|
+
for (const agent of spec.agents) {
|
|
12988
|
+
files.push({
|
|
12989
|
+
path: `.claude/agents/council-${agent.id}.md`,
|
|
12990
|
+
content: generateAgentContent(agent),
|
|
12991
|
+
overwrite: false
|
|
12992
|
+
});
|
|
12461
12993
|
}
|
|
12462
|
-
return
|
|
12463
|
-
edit: deny
|
|
12464
|
-
bash: deny
|
|
12465
|
-
glob: deny
|
|
12466
|
-
grep: deny
|
|
12467
|
-
webfetch: deny
|
|
12468
|
-
websearch: deny`;
|
|
12994
|
+
return files;
|
|
12469
12995
|
}
|
|
12470
|
-
function
|
|
12471
|
-
return
|
|
12996
|
+
function generateCouncilSkill(spec) {
|
|
12997
|
+
return `# Council Skill
|
|
12998
|
+
|
|
12999
|
+
## Description
|
|
13000
|
+
${spec.description}
|
|
13001
|
+
|
|
13002
|
+
## Version
|
|
13003
|
+
${spec.version}
|
|
13004
|
+
|
|
13005
|
+
## Agents
|
|
13006
|
+
${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
|
|
13007
|
+
`)}
|
|
13008
|
+
|
|
13009
|
+
## Usage
|
|
13010
|
+
Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
|
|
13011
|
+
|
|
13012
|
+
## Context
|
|
13013
|
+
${spec.outputContract}
|
|
13014
|
+
`;
|
|
12472
13015
|
}
|
|
12473
13016
|
|
|
12474
|
-
// src/adapters/
|
|
12475
|
-
class
|
|
12476
|
-
name = "
|
|
12477
|
-
target = "
|
|
13017
|
+
// src/adapters/claude/claude-installer.ts
|
|
13018
|
+
class ClaudeInstaller {
|
|
13019
|
+
name = "claude";
|
|
13020
|
+
target = "claude";
|
|
12478
13021
|
spec = null;
|
|
12479
13022
|
setSpec(spec) {
|
|
12480
13023
|
this.spec = spec;
|
|
@@ -12483,467 +13026,299 @@ class OpenCodeInstaller {
|
|
|
12483
13026
|
if (!this.spec) {
|
|
12484
13027
|
throw new Error("Council spec not set");
|
|
12485
13028
|
}
|
|
12486
|
-
return
|
|
13029
|
+
return generateClaudeFiles(this.spec);
|
|
12487
13030
|
}
|
|
12488
13031
|
async isAvailable() {
|
|
12489
13032
|
return true;
|
|
12490
13033
|
}
|
|
12491
13034
|
}
|
|
12492
|
-
function
|
|
12493
|
-
const installer = new
|
|
13035
|
+
function createClaudeInstaller(spec) {
|
|
13036
|
+
const installer = new ClaudeInstaller;
|
|
12494
13037
|
installer.setSpec(spec);
|
|
12495
13038
|
return installer;
|
|
12496
13039
|
}
|
|
12497
13040
|
|
|
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
|
-
}
|
|
13041
|
+
// src/adapters/codex/codex-council-generator.ts
|
|
13042
|
+
function generateCodexFiles(spec) {
|
|
13043
|
+
const files = [];
|
|
13044
|
+
files.push({
|
|
13045
|
+
path: ".codex/config.toml",
|
|
13046
|
+
content: generateCodexConfig(spec),
|
|
13047
|
+
overwrite: false
|
|
13048
|
+
});
|
|
13049
|
+
for (const agent of spec.agents) {
|
|
13050
|
+
files.push({
|
|
13051
|
+
path: `.codex/agents/council_${agent.id}.toml`,
|
|
13052
|
+
content: generateCodexAgent(agent),
|
|
13053
|
+
overwrite: true
|
|
13054
|
+
});
|
|
12546
13055
|
}
|
|
12547
|
-
|
|
13056
|
+
files.push({
|
|
13057
|
+
path: ".codex/skills/council/SKILL.md",
|
|
13058
|
+
content: generateCodexSkill(spec),
|
|
13059
|
+
overwrite: true
|
|
13060
|
+
});
|
|
13061
|
+
return files;
|
|
12548
13062
|
}
|
|
13063
|
+
function generateCodexConfig(spec) {
|
|
13064
|
+
const agentTables = spec.agents.map((agent) => {
|
|
13065
|
+
const focusAreas = agent.focus.join(", ");
|
|
13066
|
+
return `
|
|
13067
|
+
[agents.${agent.id}]
|
|
13068
|
+
description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
|
|
13069
|
+
nickname_candidates = ["${agent.role}", "Council ${agent.role}"]`;
|
|
13070
|
+
}).join(`
|
|
13071
|
+
`);
|
|
13072
|
+
return `# Codex Council Configuration
|
|
12549
13073
|
|
|
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
|
|
13074
|
+
[project]
|
|
13075
|
+
name = "council"
|
|
13076
|
+
version = "${spec.version}"
|
|
13077
|
+
${agentTables}
|
|
13078
|
+
`;
|
|
13079
|
+
}
|
|
13080
|
+
function generateCodexAgent(agent) {
|
|
13081
|
+
const focusAreas = agent.focus.join(", ");
|
|
13082
|
+
return `name = "${agent.role}"
|
|
13083
|
+
description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
|
|
13084
|
+
nickname_candidates = ["${agent.role}", "Council ${agent.role}"]
|
|
13085
|
+
developer_instructions = "You are the ${agent.role} council agent. Your focus areas are: ${focusAreas}. Context: ${agent.context}. Apply ${agent.modelHint} reasoning to your analysis."
|
|
13086
|
+
`;
|
|
13087
|
+
}
|
|
13088
|
+
function generateCodexSkill(spec) {
|
|
13089
|
+
return `---
|
|
13090
|
+
name: council
|
|
13091
|
+
description: ${spec.description}
|
|
13092
|
+
version: ${spec.version}
|
|
13093
|
+
---
|
|
12645
13094
|
|
|
12646
|
-
##
|
|
13095
|
+
## Agents
|
|
13096
|
+
${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
|
|
13097
|
+
`)}
|
|
12647
13098
|
|
|
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]}`;
|
|
13099
|
+
## Usage
|
|
13100
|
+
Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
|
|
13101
|
+
`;
|
|
12660
13102
|
}
|
|
12661
|
-
var LOCALE_PLACEHOLDER = "{{LANGUAGE_INSTRUCTIONS}}";
|
|
12662
13103
|
|
|
12663
|
-
// src/
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
13104
|
+
// src/adapters/codex/codex-installer.ts
|
|
13105
|
+
class CodexInstaller {
|
|
13106
|
+
name = "codex";
|
|
13107
|
+
target = "codex";
|
|
13108
|
+
spec = null;
|
|
13109
|
+
setSpec(spec) {
|
|
13110
|
+
this.spec = spec;
|
|
12670
13111
|
}
|
|
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`);
|
|
13112
|
+
async generate() {
|
|
13113
|
+
if (!this.spec) {
|
|
13114
|
+
throw new Error("Council spec not set");
|
|
13115
|
+
}
|
|
13116
|
+
return generateCodexFiles(this.spec);
|
|
12684
13117
|
}
|
|
12685
|
-
|
|
12686
|
-
|
|
13118
|
+
async isAvailable() {
|
|
13119
|
+
return true;
|
|
12687
13120
|
}
|
|
12688
13121
|
}
|
|
12689
|
-
function
|
|
12690
|
-
const
|
|
12691
|
-
|
|
12692
|
-
return
|
|
12693
|
-
}
|
|
12694
|
-
function countOccurrences(content, needle) {
|
|
12695
|
-
return content.split(needle).length - 1;
|
|
13122
|
+
function createCodexInstaller(spec) {
|
|
13123
|
+
const installer = new CodexInstaller;
|
|
13124
|
+
installer.setSpec(spec);
|
|
13125
|
+
return installer;
|
|
12696
13126
|
}
|
|
12697
13127
|
|
|
12698
|
-
// src/
|
|
12699
|
-
|
|
12700
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
13128
|
+
// src/adapters/opencode/opencode-council-generator.ts
|
|
13129
|
+
function generateOpenCodeFiles(spec) {
|
|
12701
13130
|
const files = [];
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
13131
|
+
files.push({
|
|
13132
|
+
path: ".opencode/commands/cc-council.md",
|
|
13133
|
+
content: generateCouncilCommand(spec),
|
|
13134
|
+
overwrite: false
|
|
13135
|
+
});
|
|
13136
|
+
files.push({
|
|
13137
|
+
path: ".opencode/agents/council-lead.md",
|
|
13138
|
+
content: generateCouncilLead(spec),
|
|
13139
|
+
overwrite: false
|
|
13140
|
+
});
|
|
13141
|
+
for (const agent of spec.agents) {
|
|
13142
|
+
files.push({
|
|
13143
|
+
path: `.opencode/agents/council-${agent.id}.md`,
|
|
13144
|
+
content: generateOpenCodeAgentContent(agent),
|
|
13145
|
+
overwrite: false
|
|
13146
|
+
});
|
|
12709
13147
|
}
|
|
12710
13148
|
return files;
|
|
12711
13149
|
}
|
|
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;
|
|
13150
|
+
function generateCouncilCommand(spec) {
|
|
13151
|
+
return `---
|
|
13152
|
+
description: ${yamlString(spec.description)}
|
|
13153
|
+
agent: council-lead
|
|
13154
|
+
subtask: true
|
|
13155
|
+
---
|
|
13156
|
+
|
|
13157
|
+
Run the CodeConductor council for multi-perspective analysis.
|
|
13158
|
+
|
|
13159
|
+
## Version
|
|
13160
|
+
${spec.version}
|
|
13161
|
+
|
|
13162
|
+
## Agents
|
|
13163
|
+
${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
|
|
13164
|
+
`)}
|
|
13165
|
+
|
|
13166
|
+
## Instructions
|
|
13167
|
+
Coordinate with the council agents and synthesize their perspectives into the configured output contract.
|
|
13168
|
+
`;
|
|
12742
13169
|
}
|
|
12743
|
-
function
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
13170
|
+
function generateCouncilLead(spec) {
|
|
13171
|
+
return `---
|
|
13172
|
+
description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
|
|
13173
|
+
mode: subagent
|
|
13174
|
+
permission:
|
|
13175
|
+
read: allow
|
|
13176
|
+
edit: deny
|
|
13177
|
+
bash: deny
|
|
13178
|
+
glob: allow
|
|
13179
|
+
grep: allow
|
|
13180
|
+
webfetch: deny
|
|
13181
|
+
websearch: deny
|
|
13182
|
+
---
|
|
13183
|
+
|
|
13184
|
+
# Council Lead Agent
|
|
13185
|
+
|
|
13186
|
+
## Role
|
|
13187
|
+
Coordinates the council and synthesizes perspectives
|
|
13188
|
+
|
|
13189
|
+
## Version
|
|
13190
|
+
${spec.version}
|
|
13191
|
+
|
|
13192
|
+
## Council Members
|
|
13193
|
+
${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
|
|
13194
|
+
`)}
|
|
13195
|
+
|
|
13196
|
+
## Responsibilities
|
|
13197
|
+
- Coordinate agent responses
|
|
13198
|
+
- Synthesize different perspectives
|
|
13199
|
+
- Identify consensus and disagreements
|
|
13200
|
+
- Provide final recommendation
|
|
13201
|
+
`;
|
|
12758
13202
|
}
|
|
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;
|
|
13203
|
+
function generateOpenCodeAgentContent(agent) {
|
|
13204
|
+
return `---
|
|
13205
|
+
description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
|
|
13206
|
+
mode: subagent
|
|
13207
|
+
permission:
|
|
13208
|
+
${generatePermissionBlock(agent.context)}
|
|
13209
|
+
---
|
|
13210
|
+
|
|
13211
|
+
${generateAgentContent(agent)}`;
|
|
12801
13212
|
}
|
|
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
|
-
---`);
|
|
13213
|
+
function generatePermissionBlock(context) {
|
|
13214
|
+
if (context === "repo-readonly") {
|
|
13215
|
+
return ` read: allow
|
|
13216
|
+
edit: deny
|
|
13217
|
+
bash: deny
|
|
13218
|
+
glob: allow
|
|
13219
|
+
grep: allow
|
|
13220
|
+
webfetch: deny
|
|
13221
|
+
websearch: deny`;
|
|
12815
13222
|
}
|
|
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
|
-
---`);
|
|
13223
|
+
return ` read: deny
|
|
13224
|
+
edit: deny
|
|
13225
|
+
bash: deny
|
|
13226
|
+
glob: deny
|
|
13227
|
+
grep: deny
|
|
13228
|
+
webfetch: deny
|
|
13229
|
+
websearch: deny`;
|
|
13230
|
+
}
|
|
13231
|
+
function yamlString(value) {
|
|
13232
|
+
return JSON.stringify(value);
|
|
12832
13233
|
}
|
|
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
13234
|
|
|
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
|
-
}
|
|
13235
|
+
// src/adapters/opencode/opencode-installer.ts
|
|
13236
|
+
class OpenCodeInstaller {
|
|
13237
|
+
name = "opencode";
|
|
13238
|
+
target = "opencode";
|
|
13239
|
+
spec = null;
|
|
13240
|
+
setSpec(spec) {
|
|
13241
|
+
this.spec = spec;
|
|
12883
13242
|
}
|
|
12884
|
-
|
|
12885
|
-
|
|
13243
|
+
async generate() {
|
|
13244
|
+
if (!this.spec) {
|
|
13245
|
+
throw new Error("Council spec not set");
|
|
13246
|
+
}
|
|
13247
|
+
return generateOpenCodeFiles(this.spec);
|
|
12886
13248
|
}
|
|
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) };
|
|
13249
|
+
async isAvailable() {
|
|
13250
|
+
return true;
|
|
12893
13251
|
}
|
|
12894
13252
|
}
|
|
12895
|
-
|
|
13253
|
+
function createOpenCodeInstaller(spec) {
|
|
13254
|
+
const installer = new OpenCodeInstaller;
|
|
13255
|
+
installer.setSpec(spec);
|
|
13256
|
+
return installer;
|
|
13257
|
+
}
|
|
13258
|
+
|
|
13259
|
+
// src/core/filesystem/file-writer.ts
|
|
13260
|
+
import { access as access4, mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
13261
|
+
import { dirname as dirname3 } from "node:path";
|
|
13262
|
+
init_safety();
|
|
13263
|
+
async function writeGeneratedFiles(files, options) {
|
|
12896
13264
|
const results = [];
|
|
12897
|
-
for (const
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
dest: entry.dest.replace(/^\.agents\/?/, "")
|
|
12906
|
-
};
|
|
13265
|
+
for (const file of files) {
|
|
13266
|
+
if (!validateWritePath(file.path)) {
|
|
13267
|
+
results.push({
|
|
13268
|
+
path: file.path,
|
|
13269
|
+
success: false,
|
|
13270
|
+
error: "Protected path"
|
|
13271
|
+
});
|
|
13272
|
+
continue;
|
|
12907
13273
|
}
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
13274
|
+
if (options.dryRun) {
|
|
13275
|
+
results.push({
|
|
13276
|
+
path: file.path,
|
|
13277
|
+
success: true
|
|
13278
|
+
});
|
|
13279
|
+
continue;
|
|
13280
|
+
}
|
|
13281
|
+
if (!options.force) {
|
|
13282
|
+
try {
|
|
13283
|
+
await access4(file.path);
|
|
13284
|
+
results.push({
|
|
13285
|
+
path: file.path,
|
|
13286
|
+
success: false,
|
|
13287
|
+
error: "File exists, use --force to overwrite"
|
|
13288
|
+
});
|
|
13289
|
+
continue;
|
|
13290
|
+
} catch {}
|
|
13291
|
+
}
|
|
13292
|
+
try {
|
|
13293
|
+
const dir = dirname3(file.path);
|
|
13294
|
+
await mkdir4(dir, { recursive: true });
|
|
13295
|
+
await writeFile4(file.path, file.content, "utf-8");
|
|
13296
|
+
results.push({
|
|
13297
|
+
path: file.path,
|
|
13298
|
+
success: true
|
|
13299
|
+
});
|
|
13300
|
+
} catch (error) {
|
|
13301
|
+
results.push({
|
|
13302
|
+
path: file.path,
|
|
13303
|
+
success: false,
|
|
13304
|
+
error: String(error)
|
|
13305
|
+
});
|
|
12912
13306
|
}
|
|
12913
13307
|
}
|
|
12914
13308
|
return results;
|
|
12915
13309
|
}
|
|
12916
13310
|
|
|
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
13311
|
// src/core/presets/preset-loader.ts
|
|
12937
|
-
import { readFile as
|
|
12938
|
-
import { resolve as
|
|
13312
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
13313
|
+
import { resolve as resolve7 } from "node:path";
|
|
12939
13314
|
async function loadPreset(name, projectRoot = process.cwd()) {
|
|
12940
13315
|
const candidates = [
|
|
12941
|
-
|
|
12942
|
-
|
|
13316
|
+
resolve7(projectRoot, ".codeconductor", "presets", `${name}.yml`),
|
|
13317
|
+
resolve7(SRC_PRESETS_DIR, name, `${name}.yml`)
|
|
12943
13318
|
];
|
|
12944
13319
|
for (const presetPath of candidates) {
|
|
12945
13320
|
try {
|
|
12946
|
-
const content = await
|
|
13321
|
+
const content = await readFile7(presetPath, "utf-8");
|
|
12947
13322
|
const data = $parse(content);
|
|
12948
13323
|
if (!data)
|
|
12949
13324
|
continue;
|
|
@@ -12987,7 +13362,7 @@ function getIndividualTargets(target) {
|
|
|
12987
13362
|
// src/commands/install.command.ts
|
|
12988
13363
|
async function installCommand(options) {
|
|
12989
13364
|
const { target, dryRun, force, global: isGlobal, output, projectRoot } = options;
|
|
12990
|
-
const baseDir = isGlobal ?
|
|
13365
|
+
const baseDir = isGlobal ? homedir5() : projectRoot;
|
|
12991
13366
|
try {
|
|
12992
13367
|
const runnerTarget = parseRunnerTarget(target);
|
|
12993
13368
|
const targets = getIndividualTargets(runnerTarget);
|
|
@@ -13023,7 +13398,7 @@ async function installCommand(options) {
|
|
|
13023
13398
|
const generatedFiles = await installer.generate();
|
|
13024
13399
|
const resolvedFiles = generatedFiles.map((f) => ({
|
|
13025
13400
|
...f,
|
|
13026
|
-
path:
|
|
13401
|
+
path: resolve8(baseDir, f.path)
|
|
13027
13402
|
}));
|
|
13028
13403
|
const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
|
|
13029
13404
|
for (const result of results) {
|
|
@@ -13082,7 +13457,7 @@ async function installCommand(options) {
|
|
|
13082
13457
|
}
|
|
13083
13458
|
async function installPresetCommand(options) {
|
|
13084
13459
|
const { target, dryRun, force, global: isGlobal, projectRoot } = options;
|
|
13085
|
-
const baseDir = isGlobal ?
|
|
13460
|
+
const baseDir = isGlobal ? homedir5() : projectRoot;
|
|
13086
13461
|
try {
|
|
13087
13462
|
const runnerTarget = parseRunnerTarget(target);
|
|
13088
13463
|
const targets = getIndividualTargets(runnerTarget);
|
|
@@ -13132,8 +13507,8 @@ async function installPresetCommand(options) {
|
|
|
13132
13507
|
}
|
|
13133
13508
|
|
|
13134
13509
|
// src/commands/install-lsp.command.ts
|
|
13135
|
-
import { homedir as
|
|
13136
|
-
import { resolve as
|
|
13510
|
+
import { homedir as homedir7 } from "node:os";
|
|
13511
|
+
import { resolve as resolve9 } from "node:path";
|
|
13137
13512
|
|
|
13138
13513
|
// src/core/lsp/lsp-config-utils.ts
|
|
13139
13514
|
function getLanguageServerConfig(lspIds) {
|
|
@@ -13377,15 +13752,15 @@ function createOpenCodeLspGenerator() {
|
|
|
13377
13752
|
// src/core/lsp/lsp-installer.ts
|
|
13378
13753
|
import { execFile } from "node:child_process";
|
|
13379
13754
|
import { access as access5, mkdir as mkdir5 } from "node:fs/promises";
|
|
13380
|
-
import { homedir as
|
|
13381
|
-
import { join as
|
|
13755
|
+
import { homedir as homedir6 } from "node:os";
|
|
13756
|
+
import { join as join5 } from "node:path";
|
|
13382
13757
|
import { promisify } from "node:util";
|
|
13383
13758
|
var execFileAsync = promisify(execFile);
|
|
13384
13759
|
|
|
13385
13760
|
class LspInstaller {
|
|
13386
13761
|
lspBinDir;
|
|
13387
13762
|
constructor() {
|
|
13388
|
-
this.lspBinDir =
|
|
13763
|
+
this.lspBinDir = join5(homedir6(), ".codeconductor", "lsp", "bin");
|
|
13389
13764
|
}
|
|
13390
13765
|
async checkInstalled(def) {
|
|
13391
13766
|
try {
|
|
@@ -13512,7 +13887,7 @@ class LspInstaller {
|
|
|
13512
13887
|
throw new Error(`No binary available for platform: ${platformKey}`);
|
|
13513
13888
|
}
|
|
13514
13889
|
await mkdir5(this.lspBinDir, { recursive: true });
|
|
13515
|
-
const destPath =
|
|
13890
|
+
const destPath = join5(this.lspBinDir, def.binaryName);
|
|
13516
13891
|
try {
|
|
13517
13892
|
await access5(destPath);
|
|
13518
13893
|
return;
|
|
@@ -13619,7 +13994,7 @@ function resolveLsps(languages) {
|
|
|
13619
13994
|
// src/commands/install-lsp.command.ts
|
|
13620
13995
|
async function installLspCommand(options) {
|
|
13621
13996
|
const { target, lang, dryRun, force, global: isGlobal, output, projectRoot } = options;
|
|
13622
|
-
const baseDir = isGlobal ?
|
|
13997
|
+
const baseDir = isGlobal ? homedir7() : projectRoot;
|
|
13623
13998
|
try {
|
|
13624
13999
|
const runnerTarget = parseRunnerTarget(target);
|
|
13625
14000
|
const targets = getIndividualTargets(runnerTarget);
|
|
@@ -13665,7 +14040,7 @@ async function installLspCommand(options) {
|
|
|
13665
14040
|
const generatedFiles = generator.generate(installReport.results);
|
|
13666
14041
|
const resolvedFiles = generatedFiles.map((f) => ({
|
|
13667
14042
|
...f,
|
|
13668
|
-
path:
|
|
14043
|
+
path: resolve9(baseDir, f.path)
|
|
13669
14044
|
}));
|
|
13670
14045
|
const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
|
|
13671
14046
|
for (const result of results) {
|
|
@@ -13759,7 +14134,7 @@ function getLspConfigGenerator(target) {
|
|
|
13759
14134
|
|
|
13760
14135
|
// src/commands/seo-audit.command.ts
|
|
13761
14136
|
import { writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
|
|
13762
|
-
import { dirname as dirname4, resolve as
|
|
14137
|
+
import { dirname as dirname4, resolve as resolve10 } from "node:path";
|
|
13763
14138
|
|
|
13764
14139
|
// src/infrastructure/http/safe-fetch.ts
|
|
13765
14140
|
import { lookup } from "node:dns/promises";
|
|
@@ -13846,7 +14221,7 @@ async function safeFetch(urlString, options = {}) {
|
|
|
13846
14221
|
}
|
|
13847
14222
|
}
|
|
13848
14223
|
async function delay(ms) {
|
|
13849
|
-
return new Promise((
|
|
14224
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
13850
14225
|
}
|
|
13851
14226
|
|
|
13852
14227
|
// src/infrastructure/parsers/sitemap-parser.ts
|
|
@@ -15050,12 +15425,12 @@ async function seoAuditCommand(options) {
|
|
|
15050
15425
|
formattedOutput = formatCli(report);
|
|
15051
15426
|
}
|
|
15052
15427
|
if (output) {
|
|
15053
|
-
const outputPath =
|
|
15428
|
+
const outputPath = resolve10(options.projectRoot, output);
|
|
15054
15429
|
await mkdir6(dirname4(outputPath), { recursive: true });
|
|
15055
15430
|
await writeFile5(outputPath, formattedOutput, "utf-8");
|
|
15056
15431
|
} else if (format === "markdown") {
|
|
15057
15432
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
15058
|
-
const defaultPath =
|
|
15433
|
+
const defaultPath = resolve10(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
|
|
15059
15434
|
await mkdir6(dirname4(defaultPath), { recursive: true });
|
|
15060
15435
|
await writeFile5(defaultPath, formattedOutput, "utf-8");
|
|
15061
15436
|
process.stderr.write(`Report saved to: ${defaultPath}
|
|
@@ -15069,7 +15444,7 @@ async function seoAuditCommand(options) {
|
|
|
15069
15444
|
command: "seo audit",
|
|
15070
15445
|
report,
|
|
15071
15446
|
output: formattedOutput,
|
|
15072
|
-
outputFile: output ?
|
|
15447
|
+
outputFile: output ? resolve10(options.projectRoot, output) : format === "markdown" ? resolve10(options.projectRoot, "seo-reports") : undefined
|
|
15073
15448
|
}
|
|
15074
15449
|
};
|
|
15075
15450
|
} catch (error) {
|
|
@@ -15086,7 +15461,7 @@ async function seoAuditCommand(options) {
|
|
|
15086
15461
|
|
|
15087
15462
|
// src/commands/seo-llms.command.ts
|
|
15088
15463
|
import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
|
|
15089
|
-
import { dirname as dirname5, resolve as
|
|
15464
|
+
import { dirname as dirname5, resolve as resolve11 } from "node:path";
|
|
15090
15465
|
|
|
15091
15466
|
// src/domain/seo/llms-generator.ts
|
|
15092
15467
|
function extractTitle2(html) {
|
|
@@ -15229,7 +15604,7 @@ async function seoLlmsCommand(options) {
|
|
|
15229
15604
|
}
|
|
15230
15605
|
}) : await generateLlmsTxtFromUrl(url);
|
|
15231
15606
|
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
15232
|
-
const outputPath = output ?
|
|
15607
|
+
const outputPath = output ? resolve11(options.projectRoot, output) : resolve11(options.projectRoot, "llms.txt");
|
|
15233
15608
|
await mkdir7(dirname5(outputPath), { recursive: true });
|
|
15234
15609
|
await writeFile6(outputPath, result.content, "utf-8");
|
|
15235
15610
|
process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
|
|
@@ -15257,10 +15632,14 @@ async function seoLlmsCommand(options) {
|
|
|
15257
15632
|
}
|
|
15258
15633
|
|
|
15259
15634
|
// src/commands/update.command.ts
|
|
15635
|
+
import { homedir as homedir8 } from "node:os";
|
|
15636
|
+
import { resolve as resolve12, dirname as dirname6 } from "node:path";
|
|
15637
|
+
import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7, stat as stat3 } from "node:fs/promises";
|
|
15260
15638
|
async function updateCommand(options) {
|
|
15261
|
-
const { dryRun, force, output, projectRoot } = options;
|
|
15639
|
+
const { dryRun, force, global: isGlobal, output, projectRoot } = options;
|
|
15640
|
+
const basePath = isGlobal ? homedir8() : projectRoot;
|
|
15262
15641
|
try {
|
|
15263
|
-
const configResult = await loadConfig(
|
|
15642
|
+
const configResult = await loadConfig(basePath);
|
|
15264
15643
|
if (!configResult.success) {
|
|
15265
15644
|
return {
|
|
15266
15645
|
code: 1,
|
|
@@ -15272,42 +15651,40 @@ async function updateCommand(options) {
|
|
|
15272
15651
|
};
|
|
15273
15652
|
}
|
|
15274
15653
|
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
|
-
};
|
|
15654
|
+
const updateResults = await checkUpdates(basePath, isGlobal);
|
|
15655
|
+
const largeFiles = await validateAgentFileSizes(basePath, isGlobal);
|
|
15656
|
+
if (largeFiles.length > 0) {
|
|
15657
|
+
largeFiles.forEach((file) => {
|
|
15658
|
+
console.warn(`[WARNING] File exceeds 40KB: ${file.path} (${(file.size / 1024).toFixed(1)} KB)`);
|
|
15659
|
+
});
|
|
15295
15660
|
}
|
|
15296
|
-
|
|
15297
|
-
const currentVersion = config.presets.council.version;
|
|
15298
|
-
const newVersion = spec.version;
|
|
15299
|
-
if (currentVersion === newVersion) {
|
|
15661
|
+
if (!updateResults.hasUpdates) {
|
|
15300
15662
|
return {
|
|
15301
15663
|
code: 0,
|
|
15302
15664
|
data: {
|
|
15303
15665
|
success: true,
|
|
15304
15666
|
command: "update",
|
|
15305
15667
|
message: "Already up to date",
|
|
15306
|
-
|
|
15307
|
-
|
|
15668
|
+
wouldUpdate: [],
|
|
15669
|
+
updated: []
|
|
15308
15670
|
}
|
|
15309
15671
|
};
|
|
15310
15672
|
}
|
|
15673
|
+
const wouldUpdate = [];
|
|
15674
|
+
if (updateResults.council)
|
|
15675
|
+
wouldUpdate.push("council preset");
|
|
15676
|
+
if (updateResults.policy)
|
|
15677
|
+
wouldUpdate.push("policy file");
|
|
15678
|
+
for (const t of updateResults.targets) {
|
|
15679
|
+
if (t.hasUpdate) {
|
|
15680
|
+
wouldUpdate.push(`${t.target} target files`);
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15683
|
+
for (const s of updateResults.skills) {
|
|
15684
|
+
if (s.hasUpdate) {
|
|
15685
|
+
wouldUpdate.push(`skill ${s.id} to v${s.latestVersion}`);
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
15311
15688
|
if (dryRun) {
|
|
15312
15689
|
return {
|
|
15313
15690
|
code: 0,
|
|
@@ -15315,48 +15692,77 @@ async function updateCommand(options) {
|
|
|
15315
15692
|
success: true,
|
|
15316
15693
|
command: "update",
|
|
15317
15694
|
message: "Dry run - would update",
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
wouldUpdate: ["council preset files"]
|
|
15695
|
+
wouldUpdate,
|
|
15696
|
+
updated: []
|
|
15321
15697
|
}
|
|
15322
15698
|
};
|
|
15323
15699
|
}
|
|
15324
|
-
const
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15700
|
+
const updated = [];
|
|
15701
|
+
if (updateResults.council) {
|
|
15702
|
+
const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
|
|
15703
|
+
const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
|
|
15704
|
+
try {
|
|
15705
|
+
const content = await readFile8(bundledCouncil, "utf-8");
|
|
15706
|
+
await mkdir8(dirname6(localCouncil), { recursive: true });
|
|
15707
|
+
await writeFile7(localCouncil, content, "utf-8");
|
|
15708
|
+
updated.push(localCouncil);
|
|
15709
|
+
} catch (e) {
|
|
15710
|
+
throw new Error(`Failed to update council.yml: ${e}`);
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
if (updateResults.policy) {
|
|
15714
|
+
const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
|
|
15715
|
+
try {
|
|
15716
|
+
const content = await readFile8(POLICY_PATH, "utf-8");
|
|
15717
|
+
await mkdir8(dirname6(localPolicy), { recursive: true });
|
|
15718
|
+
await writeFile7(localPolicy, content, "utf-8");
|
|
15719
|
+
updated.push(localPolicy);
|
|
15720
|
+
} catch (e) {
|
|
15721
|
+
throw new Error(`Failed to update policy.yml: ${e}`);
|
|
15722
|
+
}
|
|
15723
|
+
}
|
|
15724
|
+
const locale = config?.defaults?.locale ?? "en";
|
|
15725
|
+
for (const t of updateResults.targets) {
|
|
15726
|
+
if (t.hasUpdate) {
|
|
15727
|
+
const targetName = t.target;
|
|
15728
|
+
const manifest = await loadManifest(targetName);
|
|
15729
|
+
const modelConfig = await loadModelConfig(targetName);
|
|
15730
|
+
const results = await copyFromManifest(manifest, PRESETS_DIR, basePath, isGlobal, false, force, modelConfig, locale);
|
|
15731
|
+
for (const r of results) {
|
|
15732
|
+
if (r.action !== "skipped" && r.action !== "error") {
|
|
15733
|
+
updated.push(r.dest);
|
|
15734
|
+
} else if (r.action === "error") {
|
|
15735
|
+
throw new Error(`Failed to copy target file: ${r.error}`);
|
|
15344
15736
|
}
|
|
15345
|
-
}
|
|
15737
|
+
}
|
|
15738
|
+
}
|
|
15346
15739
|
}
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15740
|
+
if (updateResults.skills.some((s) => s.hasUpdate)) {
|
|
15741
|
+
const newSkillsLock = {};
|
|
15742
|
+
const currentLock = await loadSkillsLock(basePath) || {};
|
|
15743
|
+
for (const [id, ver] of Object.entries(currentLock)) {
|
|
15744
|
+
newSkillsLock[id] = ver;
|
|
15745
|
+
}
|
|
15746
|
+
for (const s of updateResults.skills) {
|
|
15747
|
+
newSkillsLock[s.id] = s.latestVersion;
|
|
15748
|
+
}
|
|
15749
|
+
let lockDest = resolve12(basePath, ".codeconductor", "skills-lock.json");
|
|
15750
|
+
try {
|
|
15751
|
+
const statAgents = await stat3(resolve12(basePath, ".agents"));
|
|
15752
|
+
if (statAgents.isDirectory()) {
|
|
15753
|
+
const statCodeConductor = await stat3(resolve12(basePath, ".codeconductor")).catch(() => null);
|
|
15754
|
+
if (!statCodeConductor) {
|
|
15755
|
+
lockDest = resolve12(basePath, ".agents", "skills-lock.json");
|
|
15756
|
+
}
|
|
15358
15757
|
}
|
|
15359
|
-
}
|
|
15758
|
+
} catch {}
|
|
15759
|
+
try {
|
|
15760
|
+
await mkdir8(dirname6(lockDest), { recursive: true });
|
|
15761
|
+
await writeFile7(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
|
|
15762
|
+
updated.push(lockDest);
|
|
15763
|
+
} catch (e) {
|
|
15764
|
+
throw new Error(`Failed to write skills-lock.json: ${e}`);
|
|
15765
|
+
}
|
|
15360
15766
|
}
|
|
15361
15767
|
return {
|
|
15362
15768
|
code: 0,
|
|
@@ -15364,8 +15770,7 @@ async function updateCommand(options) {
|
|
|
15364
15770
|
success: true,
|
|
15365
15771
|
command: "update",
|
|
15366
15772
|
message: "Updated successfully",
|
|
15367
|
-
|
|
15368
|
-
newVersion,
|
|
15773
|
+
wouldUpdate: [],
|
|
15369
15774
|
updated
|
|
15370
15775
|
}
|
|
15371
15776
|
};
|
|
@@ -15570,6 +15975,7 @@ async function routeCommand(args, projectRoot) {
|
|
|
15570
15975
|
projectRoot,
|
|
15571
15976
|
dryRun: flags.dryRun,
|
|
15572
15977
|
force: flags.force,
|
|
15978
|
+
global: options.global === true || options.global === "true",
|
|
15573
15979
|
output: flags.output
|
|
15574
15980
|
});
|
|
15575
15981
|
case "seo": {
|