cc-codeconductor 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,6 +36,10 @@ contracts, task cards, and risk-based routing.
36
36
  > a sitemap.xml with rate limiting and SSRF prevention
37
37
  > - `npx cc-codeconductor seo llms --sitemap <url>` — generates a `llms.txt` file
38
38
  > for AI-search readiness from sitemap content
39
+ > - `npx cc-codeconductor help` (alias: `cc-help`) — shows preset inventory
40
+ > (skills, subagents, commands) for the active or specified target
41
+ > - `npx cc-codeconductor debt-harvest` (alias: `harvest`) — scans source files
42
+ > for `// defer` comments and writes `.codeconductor/debt-ledger.md`
39
43
  > - `/cc-pagespeed --url <url>` — audits web performance using the PageSpeed
40
44
  > Insights API; applies the 80/20 principle to produce a prioritized report of
41
45
  > Core Web Vitals (LCP, TBT, CLS, FCP, TTFB) with framework-specific fixes;
@@ -123,12 +127,14 @@ Task Card → Risk Classification → Routing Policy → Conductor Agent → Del
123
127
  - Codex preset
124
128
  - Spring Boot / Kotlin workflow
125
129
  - Python / Django workflow guidance
126
- - 8 core Conductor Agents
127
- - Routing Policy v0.1.0
130
+ - 10 core Conductor Agents
131
+ - Routing Policy v0.2.0
128
132
  - Task Card template
129
133
  - Scorecard template
130
134
  - End-to-end example
131
135
  - YAML-driven model configuration
136
+ - Provider-agnostic `AgentContract` abstraction with target renderers for Claude, OpenCode, Codex, and Agy
137
+ - Council consensus engine (`councilConsensus()`) for multi-agent governance with majority/unanimous algorithms and security veto
132
138
 
133
139
  ---
134
140
 
@@ -266,6 +272,34 @@ npx cc-codeconductor update --global
266
272
 
267
273
  Smart updates all currently installed target presets, council configurations, and skills (from `skills-lock.json`), preserving user edits outside managed blocks. Also validates that `AGENTS.md` and `CLAUDE.md` do not exceed the 40KB size limit.
268
274
 
275
+ #### `help` — show preset inventory
276
+
277
+ ```bash
278
+ npx cc-codeconductor help # show inventory for active target
279
+ npx cc-codeconductor help --target claude # show inventory for specific target
280
+ npx cc-codeconductor cc-help # alias
281
+ npx cc-codeconductor help --output json # machine-readable output
282
+ ```
283
+
284
+ Lists the skills, subagents, commands, and workflows available in the active
285
+ preset. Reads from `presets/<target>/` in the project root.
286
+
287
+ #### `debt-harvest` — collect deferred debt items
288
+
289
+ ```bash
290
+ npx cc-codeconductor debt-harvest # scan src/ for // defer comments
291
+ npx cc-codeconductor debt-harvest --dir lib # scan a different directory
292
+ npx cc-codeconductor harvest # alias
293
+ npx cc-codeconductor debt-harvest --output json
294
+ ```
295
+
296
+ Scans source files for `// defer - [reason]` comments and consolidates them
297
+ into `.codeconductor/debt-ledger.md`, grouped by optional tag
298
+ (`// defer - reason --tag`). Read-only on source files; only writes the ledger.
299
+
300
+ Supported extensions: `.ts`, `.tsx`, `.js`, `.jsx`, `.go`, `.rs`, `.java`,
301
+ `.kt`, `.swift`, `.cs`, `.php`, `.scala`, `.dart`, `.c`, `.cpp`, `.h`, `.hpp`.
302
+
269
303
 
270
304
  ### Global options
271
305
 
package/dist/index.js CHANGED
@@ -36,14 +36,15 @@ function getExitCode(error) {
36
36
  }
37
37
  return ExitCode.VALIDATION_ERROR;
38
38
  }
39
- var ExitCode, CliError, ValidationError, UnsafeOperationError;
39
+ var ExitCode, CliError, ValidationError, UnsafeOperationError, CredentialGuardError;
40
40
  var init_errors = __esm(() => {
41
41
  ExitCode = {
42
42
  SUCCESS: 0,
43
43
  VALIDATION_ERROR: 1,
44
44
  UNSAFE_OPERATION: 2,
45
45
  UNSUPPORTED_PROJECT: 3,
46
- CONFIG_CONFLICT: 4
46
+ CONFIG_CONFLICT: 4,
47
+ CREDENTIAL_LEAK: 5
47
48
  };
48
49
  CliError = class CliError extends Error {
49
50
  code;
@@ -67,14 +68,24 @@ var init_errors = __esm(() => {
67
68
  this.name = "UnsafeOperationError";
68
69
  }
69
70
  };
71
+ CredentialGuardError = class CredentialGuardError extends CliError {
72
+ matches;
73
+ constructor(message, matches, details) {
74
+ super(message, ExitCode.CREDENTIAL_LEAK, details);
75
+ this.matches = matches;
76
+ this.name = "CredentialGuardError";
77
+ }
78
+ };
70
79
  });
71
80
 
72
81
  // src/core/filesystem/safety.ts
73
82
  var exports_safety = {};
74
83
  __export(exports_safety, {
75
84
  validateWritePath: () => validateWritePath,
85
+ scanForCredentials: () => scanForCredentials,
76
86
  isWritable: () => isWritable,
77
87
  isProtectedPath: () => isProtectedPath,
88
+ isCredentialContent: () => isCredentialContent,
78
89
  fileExists: () => fileExists
79
90
  });
80
91
  import { constants } from "node:fs";
@@ -103,6 +114,42 @@ async function isWritable(dir) {
103
114
  return false;
104
115
  }
105
116
  }
117
+ function buildCredentialRegexes(patterns) {
118
+ return patterns.map((keyword) => new RegExp(`(?:${keyword})\\s*[:=]\\s*[^\\s]{8,}`, "i"));
119
+ }
120
+ function scanForCredentials(filePath, content, secretPatterns) {
121
+ const regexes = buildCredentialRegexes(secretPatterns);
122
+ const lines = content.split(`
123
+ `);
124
+ const matches = [];
125
+ for (let i = 0;i < lines.length; i++) {
126
+ const line = lines[i];
127
+ for (const regex of regexes) {
128
+ const m = regex.exec(line);
129
+ if (m) {
130
+ matches.push({
131
+ filePath,
132
+ line: i + 1,
133
+ pattern: regex.source,
134
+ matched: m[0]
135
+ });
136
+ }
137
+ }
138
+ }
139
+ return matches;
140
+ }
141
+ function isCredentialContent(content, secretPatterns) {
142
+ const regexes = buildCredentialRegexes(secretPatterns);
143
+ const lines = content.split(`
144
+ `);
145
+ for (const line of lines) {
146
+ for (const regex of regexes) {
147
+ if (regex.test(line))
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ }
106
153
  var PROTECTED_PATHS;
107
154
  var init_safety = __esm(() => {
108
155
  PROTECTED_PATHS = [".git", ".env", ".env.local", ".env.production", "secrets", "credentials"];
@@ -11079,7 +11126,7 @@ function validateCouncilSpec(data) {
11079
11126
  function validateConfig(data) {
11080
11127
  return CodeConductorConfigSchema.parse(data);
11081
11128
  }
11082
- var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema;
11129
+ var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CompileCheckConfigSchema, LoopConfigSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema, ContractTargetSchema, ContractFormatSchema, AgentContractSchema, CouncilFindingSchema, CouncilVerdictInputSchema, ConsensusConfigSchema, CouncilVerdictSchema, ClaudeAgentFileSchema, OpenCodeAgentFileSchema, SentryStackFrameSchema, SentryWebhookSchema;
11083
11130
  var init_schemas = __esm(() => {
11084
11131
  init_zod();
11085
11132
  CouncilAgentSpecSchema = exports_external.object({
@@ -11112,6 +11159,16 @@ var init_schemas = __esm(() => {
11112
11159
  signals: exports_external.array(exports_external.string()),
11113
11160
  confidence: exports_external.enum(["low", "medium", "high"])
11114
11161
  });
11162
+ CompileCheckConfigSchema = exports_external.object({
11163
+ enabled: exports_external.boolean(),
11164
+ command: exports_external.string().optional(),
11165
+ timeoutMs: exports_external.number().positive().optional()
11166
+ });
11167
+ LoopConfigSchema = exports_external.object({
11168
+ enabled: exports_external.boolean().optional().default(true),
11169
+ maxIterations: exports_external.number().int().positive().optional().default(3),
11170
+ maxTokenBudget: exports_external.number().nonnegative().optional().default(0)
11171
+ });
11115
11172
  CodeConductorConfigSchema = exports_external.object({
11116
11173
  version: exports_external.string(),
11117
11174
  project: exports_external.object({
@@ -11131,8 +11188,10 @@ var init_schemas = __esm(() => {
11131
11188
  }),
11132
11189
  safety: exports_external.object({
11133
11190
  destructiveCommands: exports_external.array(exports_external.string()),
11134
- secretPatterns: exports_external.array(exports_external.string())
11135
- })
11191
+ secretPatterns: exports_external.array(exports_external.string()),
11192
+ compileCheck: CompileCheckConfigSchema.optional()
11193
+ }),
11194
+ loop: LoopConfigSchema.optional()
11136
11195
  });
11137
11196
  RunnerTargetSchema = exports_external.enum([
11138
11197
  "opencode",
@@ -11176,6 +11235,80 @@ var init_schemas = __esm(() => {
11176
11235
  tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional(),
11177
11236
  permissions: PermissionProviderNamesSchema.optional()
11178
11237
  });
11238
+ ContractTargetSchema = exports_external.enum([
11239
+ "claude",
11240
+ "opencode",
11241
+ "codex",
11242
+ "gemini",
11243
+ "cursor",
11244
+ "agy"
11245
+ ]);
11246
+ ContractFormatSchema = exports_external.object({
11247
+ target: ContractTargetSchema,
11248
+ options: exports_external.record(exports_external.unknown()).optional()
11249
+ });
11250
+ AgentContractSchema = exports_external.object({
11251
+ council: CouncilSpecSchema,
11252
+ targets: exports_external.array(ContractFormatSchema),
11253
+ contractVersion: exports_external.string(),
11254
+ renderHints: exports_external.record(ContractTargetSchema, exports_external.record(exports_external.unknown())).optional()
11255
+ });
11256
+ CouncilFindingSchema = exports_external.object({
11257
+ category: exports_external.string(),
11258
+ severity: exports_external.enum(["info", "warning", "critical"]),
11259
+ message: exports_external.string(),
11260
+ agentId: exports_external.string()
11261
+ });
11262
+ CouncilVerdictInputSchema = exports_external.object({
11263
+ agentId: exports_external.string(),
11264
+ agentRole: exports_external.string(),
11265
+ status: exports_external.enum(["APPROVED", "REJECTED", "ABSTAIN"]),
11266
+ securityVeto: exports_external.boolean(),
11267
+ findings: exports_external.array(CouncilFindingSchema),
11268
+ summary: exports_external.string()
11269
+ });
11270
+ ConsensusConfigSchema = exports_external.object({
11271
+ algorithm: exports_external.enum(["majority", "unanimous"]),
11272
+ allowSecurityVeto: exports_external.boolean()
11273
+ });
11274
+ CouncilVerdictSchema = exports_external.object({
11275
+ status: exports_external.enum(["APPROVED", "REJECTED", "ESCALATED"]),
11276
+ totalAgents: exports_external.number(),
11277
+ approvedCount: exports_external.number(),
11278
+ rejectedCount: exports_external.number(),
11279
+ abstainedCount: exports_external.number(),
11280
+ vetoApplied: exports_external.boolean(),
11281
+ vetoByAgentId: exports_external.string().optional(),
11282
+ findings: exports_external.array(CouncilFindingSchema),
11283
+ summary: exports_external.string(),
11284
+ individualVerdicts: exports_external.array(CouncilVerdictInputSchema)
11285
+ });
11286
+ ClaudeAgentFileSchema = exports_external.object({
11287
+ path: exports_external.string().startsWith(".claude/"),
11288
+ content: exports_external.string(),
11289
+ overwrite: exports_external.boolean()
11290
+ });
11291
+ OpenCodeAgentFileSchema = exports_external.object({
11292
+ path: exports_external.string().startsWith(".opencode/"),
11293
+ content: exports_external.string(),
11294
+ overwrite: exports_external.boolean()
11295
+ });
11296
+ SentryStackFrameSchema = exports_external.object({
11297
+ filename: exports_external.string(),
11298
+ function: exports_external.string(),
11299
+ lineNo: exports_external.number(),
11300
+ colNo: exports_external.number().optional(),
11301
+ context: exports_external.array(exports_external.string())
11302
+ });
11303
+ SentryWebhookSchema = exports_external.object({
11304
+ issueId: exports_external.string(),
11305
+ title: exports_external.string(),
11306
+ culprit: exports_external.string(),
11307
+ filename: exports_external.string().optional(),
11308
+ stackTrace: exports_external.array(SentryStackFrameSchema),
11309
+ environment: exports_external.string().optional(),
11310
+ release: exports_external.string().optional()
11311
+ });
11179
11312
  });
11180
11313
 
11181
11314
  // src/core/presets/package-paths.ts
@@ -11865,6 +11998,58 @@ var init_agy_installer = __esm(() => {
11865
11998
  init_agy_council_generator();
11866
11999
  });
11867
12000
 
12001
+ // src/core/filesystem/credential-guard.ts
12002
+ import { readFile as readFile7 } from "node:fs/promises";
12003
+ async function loadPolicyPatterns() {
12004
+ try {
12005
+ const content = await readFile7(POLICY_PATH, "utf-8");
12006
+ const parsed = $parse(content);
12007
+ if (Array.isArray(parsed.secretPatterns) && parsed.secretPatterns.length > 0) {
12008
+ return parsed.secretPatterns;
12009
+ }
12010
+ } catch {}
12011
+ return [];
12012
+ }
12013
+ function mergePatterns(...arrays) {
12014
+ const seen = new Set;
12015
+ const result = [];
12016
+ for (const arr of arrays) {
12017
+ for (const p of arr) {
12018
+ if (!seen.has(p)) {
12019
+ seen.add(p);
12020
+ result.push(p);
12021
+ }
12022
+ }
12023
+ }
12024
+ return result;
12025
+ }
12026
+ async function loadCredentialPatterns(config) {
12027
+ const policyPatterns = await loadPolicyPatterns();
12028
+ const configPatterns = config?.safety?.secretPatterns;
12029
+ if (configPatterns && configPatterns.length > 0) {
12030
+ return mergePatterns(configPatterns, policyPatterns, DEFAULT_SECRET_PATTERNS);
12031
+ }
12032
+ if (policyPatterns.length > 0) {
12033
+ return mergePatterns(policyPatterns, DEFAULT_SECRET_PATTERNS);
12034
+ }
12035
+ return DEFAULT_SECRET_PATTERNS;
12036
+ }
12037
+ var DEFAULT_SECRET_PATTERNS;
12038
+ var init_credential_guard = __esm(() => {
12039
+ init_dist();
12040
+ init_package_paths();
12041
+ DEFAULT_SECRET_PATTERNS = [
12042
+ "password",
12043
+ "secret",
12044
+ "api_key",
12045
+ "token",
12046
+ "api[_-]?key",
12047
+ "access[_-]?token",
12048
+ "auth[_-]?token",
12049
+ "private[_-]?key"
12050
+ ];
12051
+ });
12052
+
11868
12053
  // src/core/filesystem/file-writer.ts
11869
12054
  var exports_file_writer = {};
11870
12055
  __export(exports_file_writer, {
@@ -11875,6 +12060,13 @@ import { access as access4, mkdir as mkdir4, writeFile as writeFile4 } from "nod
11875
12060
  import { dirname as dirname3 } from "node:path";
11876
12061
  async function writeGeneratedFiles(files, options) {
11877
12062
  const results = [];
12063
+ const secretPatterns = await loadCredentialPatterns(options.config);
12064
+ const allMatches = files.flatMap((file) => {
12065
+ return scanForCredentials(file.path, file.content, secretPatterns);
12066
+ });
12067
+ if (allMatches.length > 0) {
12068
+ throw new CredentialGuardError(`Credential leak detected in ${allMatches.length} file(s). No files written.`, allMatches);
12069
+ }
11878
12070
  for (const file of files) {
11879
12071
  if (!validateWritePath(file.path)) {
11880
12072
  results.push({
@@ -11933,6 +12125,7 @@ async function writeSingleFile(path, content, options) {
11933
12125
  }
11934
12126
  var init_file_writer = __esm(() => {
11935
12127
  init_errors();
12128
+ init_credential_guard();
11936
12129
  init_safety();
11937
12130
  });
11938
12131
 
@@ -11941,7 +12134,7 @@ init_errors();
11941
12134
  // package.json
11942
12135
  var package_default = {
11943
12136
  name: "cc-codeconductor",
11944
- version: "0.3.3",
12137
+ version: "0.4.1",
11945
12138
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
11946
12139
  keywords: [
11947
12140
  "ai",
@@ -13604,7 +13797,7 @@ async function doctorCommand(options) {
13604
13797
  }
13605
13798
 
13606
13799
  // src/commands/init.command.ts
13607
- import { access as access3, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
13800
+ import { access as access3, mkdir as mkdir3, readFile as readFile8, writeFile as writeFile3 } from "node:fs/promises";
13608
13801
  import { homedir as homedir5 } from "node:os";
13609
13802
  import { basename, resolve as resolve7 } from "node:path";
13610
13803
 
@@ -13615,6 +13808,7 @@ import { access as access2, mkdir as mkdir2, writeFile as writeFile2 } from "nod
13615
13808
  import { resolve as resolve6 } from "node:path";
13616
13809
 
13617
13810
  // src/core/config/codeconductor-config.ts
13811
+ init_credential_guard();
13618
13812
  var DEFAULT_CONFIG = {
13619
13813
  version: "0.2.0",
13620
13814
  project: {
@@ -13633,7 +13827,17 @@ var DEFAULT_CONFIG = {
13633
13827
  },
13634
13828
  safety: {
13635
13829
  destructiveCommands: ["rm -rf", "drop table", "delete from"],
13636
- secretPatterns: ["password", "secret", "api_key", "token"]
13830
+ secretPatterns: DEFAULT_SECRET_PATTERNS,
13831
+ compileCheck: {
13832
+ enabled: true,
13833
+ command: "tsc --noEmit",
13834
+ timeoutMs: 120000
13835
+ }
13836
+ },
13837
+ loop: {
13838
+ enabled: true,
13839
+ maxIterations: 3,
13840
+ maxTokenBudget: 0
13637
13841
  }
13638
13842
  };
13639
13843
 
@@ -13890,7 +14094,7 @@ async function copyPresets(baseDir, presets, force) {
13890
14094
  continue;
13891
14095
  }
13892
14096
  try {
13893
- const content = await readFile7(preset.sourcePath, "utf-8");
14097
+ const content = await readFile8(preset.sourcePath, "utf-8");
13894
14098
  await writeFile3(destPath, content, "utf-8");
13895
14099
  copied.push(`.codeconductor/presets/${preset.name}`);
13896
14100
  } catch (err2) {
@@ -13959,8 +14163,10 @@ async function installCommand(options) {
13959
14163
  }
13960
14164
  };
13961
14165
  }
14166
+ const configResult = await loadConfig(projectRoot);
14167
+ const config = configResult.success ? configResult.data : undefined;
13962
14168
  const spec = presetResult.data;
13963
- const writeOptions = { dryRun, force };
14169
+ const writeOptions = { dryRun, force, config };
13964
14170
  const allFiles = [];
13965
14171
  for (const t of targets) {
13966
14172
  let installer;
@@ -14627,7 +14833,9 @@ async function installLspCommand(options) {
14627
14833
  }
14628
14834
  const installer = createLspInstaller();
14629
14835
  const installReport = await installer.installAll(lsps, { dryRun });
14630
- const writeOptions = { dryRun, force };
14836
+ const configResult = await loadConfig(projectRoot);
14837
+ const config = configResult.success ? configResult.data : undefined;
14838
+ const writeOptions = { dryRun, force, config };
14631
14839
  const allConfigResults = [];
14632
14840
  for (const t of targets) {
14633
14841
  const generator = getLspConfigGenerator(t);
@@ -14729,8 +14937,277 @@ function getLspConfigGenerator(target) {
14729
14937
  }
14730
14938
  }
14731
14939
 
14940
+ // src/commands/debt-harvest.command.ts
14941
+ import { readdir as readdir2, readFile as readFile9, writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
14942
+ import { join as join7, relative as relative2, extname } from "node:path";
14943
+ var DEFER_REGEX = /\/\/\s*defer\s*[-:]\s*(.+?)(?:\s*--(\w+))?\s*$/gm;
14944
+ var SOURCE_EXTENSIONS = new Set([
14945
+ ".ts",
14946
+ ".tsx",
14947
+ ".js",
14948
+ ".jsx",
14949
+ ".mjs",
14950
+ ".cjs",
14951
+ ".go",
14952
+ ".rs",
14953
+ ".java",
14954
+ ".kt",
14955
+ ".swift",
14956
+ ".cs",
14957
+ ".php",
14958
+ ".scala",
14959
+ ".dart",
14960
+ ".c",
14961
+ ".cpp",
14962
+ ".h",
14963
+ ".hpp"
14964
+ ]);
14965
+ async function walkDir(dir, extensions) {
14966
+ const results = [];
14967
+ try {
14968
+ const entries = await readdir2(dir, { withFileTypes: true });
14969
+ for (const entry of entries) {
14970
+ const fullPath = join7(dir, entry.name);
14971
+ if (entry.isDirectory()) {
14972
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
14973
+ continue;
14974
+ results.push(...await walkDir(fullPath, extensions));
14975
+ } else if (extensions.has(extname(entry.name))) {
14976
+ results.push(fullPath);
14977
+ }
14978
+ }
14979
+ } catch {}
14980
+ return results;
14981
+ }
14982
+ function extractDefers(content, filePath) {
14983
+ const entries = [];
14984
+ const lines = content.split(`
14985
+ `);
14986
+ for (let i = 0;i < lines.length; i++) {
14987
+ const line = lines[i];
14988
+ DEFER_REGEX.lastIndex = 0;
14989
+ const match = DEFER_REGEX.exec(line);
14990
+ if (match) {
14991
+ entries.push({
14992
+ file: filePath,
14993
+ line: i + 1,
14994
+ reason: match[1].trim(),
14995
+ tag: match[2]
14996
+ });
14997
+ }
14998
+ }
14999
+ return entries;
15000
+ }
15001
+ function renderLedger(entries) {
15002
+ const grouped = new Map;
15003
+ for (const entry of entries) {
15004
+ const tag = entry.tag ?? "unclassified";
15005
+ const group = grouped.get(tag) ?? [];
15006
+ group.push(entry);
15007
+ grouped.set(tag, group);
15008
+ }
15009
+ const lines = [
15010
+ "<!-- CODECONDUCTOR:BEGIN managed -->",
15011
+ "",
15012
+ "# Debt Ledger",
15013
+ "",
15014
+ "> Auto-generated by `codeconductor debt-harvest`. Do not edit manually.",
15015
+ ""
15016
+ ];
15017
+ const sortedTags = [...grouped.keys()].sort();
15018
+ for (const tag of sortedTags) {
15019
+ const tagEntries = grouped.get(tag);
15020
+ lines.push(`## ${tag}`);
15021
+ lines.push("");
15022
+ lines.push("| File | Line | Reason |");
15023
+ lines.push("| ---- | ---- | ------ |");
15024
+ for (const entry of tagEntries) {
15025
+ lines.push(`| ${entry.file} | ${entry.line} | ${entry.reason} |`);
15026
+ }
15027
+ lines.push("");
15028
+ }
15029
+ lines.push("<!-- CODECONDUCTOR:END managed -->");
15030
+ lines.push("");
15031
+ return lines.join(`
15032
+ `);
15033
+ }
15034
+ async function debtHarvestCommand(options) {
15035
+ const { projectRoot, dir = "src", output } = options;
15036
+ try {
15037
+ const targetDir = join7(projectRoot, dir);
15038
+ const files = await walkDir(targetDir, SOURCE_EXTENSIONS);
15039
+ const allEntries = [];
15040
+ for (const file of files) {
15041
+ const content = await readFile9(file, "utf-8");
15042
+ const entries = extractDefers(content, relative2(projectRoot, file));
15043
+ allEntries.push(...entries);
15044
+ }
15045
+ const ledgerDir = join7(projectRoot, ".codeconductor");
15046
+ await mkdir6(ledgerDir, { recursive: true });
15047
+ const ledgerPath = join7(ledgerDir, "debt-ledger.md");
15048
+ const ledgerContent = renderLedger(allEntries);
15049
+ await writeFile5(ledgerPath, ledgerContent, "utf-8");
15050
+ if (output === "json") {
15051
+ return {
15052
+ code: 0,
15053
+ data: {
15054
+ success: true,
15055
+ command: "debt-harvest",
15056
+ entries: allEntries,
15057
+ entryCount: allEntries.length,
15058
+ ledger: ledgerPath
15059
+ }
15060
+ };
15061
+ }
15062
+ return {
15063
+ code: 0,
15064
+ data: {
15065
+ success: true,
15066
+ command: "debt-harvest",
15067
+ message: `Found ${allEntries.length} deferred item(s) across ${files.length} file(s). Ledger written to ${ledgerPath}`,
15068
+ entries: allEntries
15069
+ }
15070
+ };
15071
+ } catch (error) {
15072
+ return {
15073
+ code: 1,
15074
+ data: {
15075
+ success: false,
15076
+ command: "debt-harvest",
15077
+ errors: [String(error)]
15078
+ }
15079
+ };
15080
+ }
15081
+ }
15082
+
15083
+ // src/commands/help.command.ts
15084
+ import { readdir as readdir3 } from "node:fs/promises";
15085
+ import { join as join8 } from "node:path";
15086
+ async function scanPresetDir(projectRoot, target) {
15087
+ const inventory = {
15088
+ target,
15089
+ skills: [],
15090
+ commands: [],
15091
+ agents: [],
15092
+ workflows: []
15093
+ };
15094
+ const presetDir = join8(projectRoot, "presets", target);
15095
+ try {
15096
+ const entries = await readdir3(presetDir, { withFileTypes: true });
15097
+ for (const entry of entries) {
15098
+ if (entry.isDirectory()) {
15099
+ const subDir = join8(presetDir, entry.name);
15100
+ const subEntries = await readdir3(subDir, { withFileTypes: true });
15101
+ for (const sub of subEntries) {
15102
+ if (sub.isFile()) {
15103
+ const itemName = sub.name.replace(/\.[^.]+$/, "");
15104
+ if (entry.name === "skills")
15105
+ inventory.skills.push(itemName);
15106
+ else if (entry.name === "commands")
15107
+ inventory.commands.push(itemName);
15108
+ else if (entry.name === "agents")
15109
+ inventory.agents.push(itemName);
15110
+ else if (entry.name === "workflows")
15111
+ inventory.workflows.push(itemName);
15112
+ } else if (sub.isDirectory()) {
15113
+ if (entry.name === "skills")
15114
+ inventory.skills.push(sub.name);
15115
+ else if (entry.name === "agents")
15116
+ inventory.agents.push(sub.name);
15117
+ }
15118
+ }
15119
+ }
15120
+ }
15121
+ } catch {}
15122
+ return inventory;
15123
+ }
15124
+ function renderHuman(inventory, defaultTarget) {
15125
+ const lines = [
15126
+ `CodeConductor Help — ${inventory.target}${inventory.target === defaultTarget ? " (active)" : ""}`,
15127
+ ""
15128
+ ];
15129
+ lines.push(`Skills (${inventory.skills.length}):`);
15130
+ if (inventory.skills.length === 0) {
15131
+ lines.push(" (none)");
15132
+ } else {
15133
+ for (const skill of inventory.skills.sort()) {
15134
+ lines.push(` - ${skill}`);
15135
+ }
15136
+ }
15137
+ lines.push("");
15138
+ lines.push(`Subagents (${inventory.agents.length}):`);
15139
+ if (inventory.agents.length === 0) {
15140
+ lines.push(" (none)");
15141
+ } else {
15142
+ for (const agent of inventory.agents.sort()) {
15143
+ lines.push(` - ${agent}`);
15144
+ }
15145
+ }
15146
+ lines.push("");
15147
+ lines.push(`Commands (${inventory.commands.length}):`);
15148
+ if (inventory.commands.length === 0) {
15149
+ lines.push(" (none)");
15150
+ } else {
15151
+ for (const cmd of inventory.commands.sort()) {
15152
+ lines.push(` - ${cmd}`);
15153
+ }
15154
+ }
15155
+ if (inventory.workflows.length > 0) {
15156
+ lines.push("");
15157
+ lines.push(`Workflows (${inventory.workflows.length}):`);
15158
+ for (const wf of inventory.workflows.sort()) {
15159
+ lines.push(` - ${wf}`);
15160
+ }
15161
+ }
15162
+ return lines.join(`
15163
+ `);
15164
+ }
15165
+ async function helpCommand(options) {
15166
+ const { projectRoot, target: overrideTarget, output } = options;
15167
+ try {
15168
+ let defaultTarget = "opencode";
15169
+ try {
15170
+ const configResult = await loadConfig(projectRoot);
15171
+ if (configResult.success) {
15172
+ defaultTarget = configResult.data.defaults.target;
15173
+ }
15174
+ } catch {}
15175
+ const target = overrideTarget ?? defaultTarget;
15176
+ const inventory = await scanPresetDir(projectRoot, target);
15177
+ if (output === "json") {
15178
+ return {
15179
+ code: 0,
15180
+ data: {
15181
+ success: true,
15182
+ command: "help",
15183
+ inventory,
15184
+ defaultTarget
15185
+ }
15186
+ };
15187
+ }
15188
+ return {
15189
+ code: 0,
15190
+ data: {
15191
+ success: true,
15192
+ command: "help",
15193
+ message: renderHuman(inventory, defaultTarget),
15194
+ inventory
15195
+ }
15196
+ };
15197
+ } catch (error) {
15198
+ return {
15199
+ code: 1,
15200
+ data: {
15201
+ success: false,
15202
+ command: "help",
15203
+ errors: [String(error)]
15204
+ }
15205
+ };
15206
+ }
15207
+ }
15208
+
14732
15209
  // src/commands/seo-audit.command.ts
14733
- import { writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
15210
+ import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
14734
15211
  import { dirname as dirname4, resolve as resolve10 } from "node:path";
14735
15212
 
14736
15213
  // src/infrastructure/http/safe-fetch.ts
@@ -16023,13 +16500,13 @@ async function seoAuditCommand(options) {
16023
16500
  }
16024
16501
  if (output) {
16025
16502
  const outputPath = resolve10(options.projectRoot, output);
16026
- await mkdir6(dirname4(outputPath), { recursive: true });
16027
- await writeFile5(outputPath, formattedOutput, "utf-8");
16503
+ await mkdir7(dirname4(outputPath), { recursive: true });
16504
+ await writeFile6(outputPath, formattedOutput, "utf-8");
16028
16505
  } else if (format === "markdown") {
16029
16506
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
16030
16507
  const defaultPath = resolve10(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
16031
- await mkdir6(dirname4(defaultPath), { recursive: true });
16032
- await writeFile5(defaultPath, formattedOutput, "utf-8");
16508
+ await mkdir7(dirname4(defaultPath), { recursive: true });
16509
+ await writeFile6(defaultPath, formattedOutput, "utf-8");
16033
16510
  process.stderr.write(`Report saved to: ${defaultPath}
16034
16511
  `);
16035
16512
  }
@@ -16057,7 +16534,7 @@ async function seoAuditCommand(options) {
16057
16534
  }
16058
16535
 
16059
16536
  // src/commands/seo-llms.command.ts
16060
- import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
16537
+ import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
16061
16538
  import { dirname as dirname5, resolve as resolve11 } from "node:path";
16062
16539
 
16063
16540
  // src/domain/seo/llms-generator.ts
@@ -16202,8 +16679,8 @@ async function seoLlmsCommand(options) {
16202
16679
  }) : await generateLlmsTxtFromUrl(url);
16203
16680
  process.stderr.write("\r" + " ".repeat(80) + "\r");
16204
16681
  const outputPath = output ? resolve11(options.projectRoot, output) : resolve11(options.projectRoot, "llms.txt");
16205
- await mkdir7(dirname5(outputPath), { recursive: true });
16206
- await writeFile6(outputPath, result.content, "utf-8");
16682
+ await mkdir8(dirname5(outputPath), { recursive: true });
16683
+ await writeFile7(outputPath, result.content, "utf-8");
16207
16684
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
16208
16685
  `);
16209
16686
  return {
@@ -16231,7 +16708,7 @@ async function seoLlmsCommand(options) {
16231
16708
  // src/commands/update.command.ts
16232
16709
  import { homedir as homedir9 } from "node:os";
16233
16710
  import { resolve as resolve12, dirname as dirname6 } from "node:path";
16234
- import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7, stat as stat3 } from "node:fs/promises";
16711
+ import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8, stat as stat3 } from "node:fs/promises";
16235
16712
  init_package_paths();
16236
16713
  async function updateCommand(options) {
16237
16714
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
@@ -16300,9 +16777,9 @@ async function updateCommand(options) {
16300
16777
  const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
16301
16778
  const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
16302
16779
  try {
16303
- const content = await readFile8(bundledCouncil, "utf-8");
16304
- await mkdir8(dirname6(localCouncil), { recursive: true });
16305
- await writeFile7(localCouncil, content, "utf-8");
16780
+ const content = await readFile10(bundledCouncil, "utf-8");
16781
+ await mkdir9(dirname6(localCouncil), { recursive: true });
16782
+ await writeFile8(localCouncil, content, "utf-8");
16306
16783
  updated.push(localCouncil);
16307
16784
  } catch (e) {
16308
16785
  throw new Error(`Failed to update council.yml: ${e}`);
@@ -16311,9 +16788,9 @@ async function updateCommand(options) {
16311
16788
  if (updateResults.policy) {
16312
16789
  const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
16313
16790
  try {
16314
- const content = await readFile8(POLICY_PATH, "utf-8");
16315
- await mkdir8(dirname6(localPolicy), { recursive: true });
16316
- await writeFile7(localPolicy, content, "utf-8");
16791
+ const content = await readFile10(POLICY_PATH, "utf-8");
16792
+ await mkdir9(dirname6(localPolicy), { recursive: true });
16793
+ await writeFile8(localPolicy, content, "utf-8");
16317
16794
  updated.push(localPolicy);
16318
16795
  } catch (e) {
16319
16796
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -16375,7 +16852,7 @@ async function updateCommand(options) {
16375
16852
  });
16376
16853
  const filesToWrite = resolvedFiles.filter((f) => t.files.includes(f.path));
16377
16854
  if (filesToWrite.length > 0) {
16378
- const writeResults = await writeGeneratedFiles2(filesToWrite, { dryRun: false, force: true });
16855
+ const writeResults = await writeGeneratedFiles2(filesToWrite, { dryRun: false, force: true, config });
16379
16856
  for (const wr of writeResults) {
16380
16857
  if (wr.success) {
16381
16858
  updated.push(wr.path);
@@ -16409,8 +16886,8 @@ async function updateCommand(options) {
16409
16886
  }
16410
16887
  } catch {}
16411
16888
  try {
16412
- await mkdir8(dirname6(lockDest), { recursive: true });
16413
- await writeFile7(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
16889
+ await mkdir9(dirname6(lockDest), { recursive: true });
16890
+ await writeFile8(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
16414
16891
  updated.push(lockDest);
16415
16892
  } catch (e) {
16416
16893
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -16519,6 +16996,8 @@ Commands:
16519
16996
  seo llms Generate llms.txt from a URL or sitemap
16520
16997
  doctor Validate configuration and generated files
16521
16998
  update Update installed presets
16999
+ help / cc-help Show preset inventory (skills, subagents, commands)
17000
+ debt-harvest / harvest Scan source files for deferred debt items
16522
17001
 
16523
17002
  Options:
16524
17003
  --help, -h Show this help message
@@ -16562,8 +17041,6 @@ Examples:
16562
17041
  async function routeCommand(args, projectRoot) {
16563
17042
  const { command, subcommand, options, flags } = args;
16564
17043
  switch (command) {
16565
- case "help":
16566
- return { code: 0, data: { help: getHelp() } };
16567
17044
  case "init":
16568
17045
  return initCommand({
16569
17046
  projectRoot,
@@ -16632,6 +17109,20 @@ async function routeCommand(args, projectRoot) {
16632
17109
  global: options.global === true || options.global === "true",
16633
17110
  output: flags.output
16634
17111
  });
17112
+ case "help":
17113
+ case "cc-help":
17114
+ return helpCommand({
17115
+ projectRoot,
17116
+ target: options.target,
17117
+ output: flags.output
17118
+ });
17119
+ case "debt-harvest":
17120
+ case "harvest":
17121
+ return debtHarvestCommand({
17122
+ projectRoot,
17123
+ dir: options.dir,
17124
+ output: flags.output
17125
+ });
16635
17126
  case "seo": {
16636
17127
  if (subcommand === "audit") {
16637
17128
  return seoAuditCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-codeconductor",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
5
5
  "keywords": [
6
6
  "ai",
package/policy.yml CHANGED
@@ -48,6 +48,18 @@ denyWrite:
48
48
  - "/var/**"
49
49
  - "~/**"
50
50
 
51
+ # Credential patterns for secret detection.
52
+ # These are merged with CodeConductorConfig.safety.secretPatterns at runtime.
53
+ secretPatterns:
54
+ - password
55
+ - secret
56
+ - api_key
57
+ - token
58
+ - api[_-]?key
59
+ - access[_-]?token
60
+ - auth[_-]?token
61
+ - private[_-]?key
62
+
51
63
  targets:
52
64
  opencode:
53
65
  unsupportedRules: []
@@ -24,6 +24,21 @@ LLM coding mistakes and bias toward caution over speed.
24
24
  success criteria. For multi-step tasks, state a plan with verification
25
25
  checks. Loop until verified.
26
26
 
27
+ ### YAGNI (You Aren't Gonna Need It)
28
+
29
+ Do not build features, abstractions, or "flexibility" that is not explicitly
30
+ requested. If the user asks for a function, write a function — not a class
31
+ hierarchy. If they ask for a string, return a string — not a Result type
32
+ with 15 error codes. Every line you write must solve a problem that exists
33
+ **now**.
34
+
35
+ ### Stdlib-First
36
+
37
+ Prefer the language's standard library over third-party packages. Before
38
+ adding a dependency, ask: "Does `node:fs`, `node:path`, `node:crypto`, or
39
+ a built-in module solve this?" If yes, use it. Every external dependency
40
+ introduces maintenance burden, supply-chain risk, and version conflicts.
41
+
27
42
  ---
28
43
 
29
44
  ## Workflow Contract
@@ -95,6 +110,7 @@ When multiple signals apply, take the highest risk level. Do not average.
95
110
  | Documentation update | any | `docs` |
96
111
  | Codebase exploration | any | `repo-explorer` |
97
112
  | Code review | any | `reviewer` |
113
+ | Security review | high | `security-reviewer` → `reviewer` |
98
114
 
99
115
  ---
100
116
 
@@ -371,6 +387,32 @@ High-risk checkpoint: [yes | no — if yes, describe what triggers a stop]
371
387
 
372
388
  ---
373
389
 
390
+ ### security-reviewer
391
+
392
+ **Role:** Dedicated security review. Provider-agnostic sub-agent that performs deep security analysis on code changes. Can apply a security veto that overrides majority consensus.
393
+
394
+ **Use when:** High-risk tasks touching auth, payment, credentials, injection vectors, or supply-chain dependencies. Mandatory for security-sensitive changes.
395
+
396
+ **Permissions:**
397
+ - read: `allow`
398
+ - edit: `deny`
399
+ - bash: `allow` (git diff, git status)
400
+ - network: `deny`
401
+
402
+ **Does not:** Write code. Edit files. Bypass security veto mechanism.
403
+
404
+ **Provider-agnostic constraints:**
405
+ - No vendor-specific prompts, APIs, or model identifiers in role definition
406
+ - All security analysis must be expressed through the council consensus interface (`securityVeto` flag on `REJECTED` verdict)
407
+ - Focus areas: vulnerabilities, credentials, injection, auth, supply-chain, OWASP Top 10
408
+
409
+ **Veto behavior:**
410
+ - When `securityVeto: true` and `status: 'REJECTED'`, the veto overrides majority consensus → final status becomes `REJECTED`
411
+ - The veto agent is recorded in `vetoByAgentId` for traceability
412
+ - Composable: can be added alongside existing council agents without replacing the general `security` agent
413
+
414
+ ---
415
+
374
416
  ### docs
375
417
 
376
418
  **Role:** Updates README, OpenAPI specs, ADRs, and changelogs.
@@ -43,13 +43,37 @@ reviewer.
43
43
 
44
44
  ---
45
45
 
46
- ## Step 2 — Code review (reviewer)
46
+ ## Step 2 — Security and code review
47
+
48
+ ### Security review (security-reviewer)
49
+
50
+ Invoke `security-reviewer` **before** `reviewer` when the change touches:
51
+
52
+ - Authentication or authorization logic
53
+ - Payment or financial processing
54
+ - Credentials, tokens, secrets, or API keys
55
+ - SQL injection, XSS, or other injection vectors
56
+ - Supply-chain dependencies (new packages, lockfile changes)
57
+ - OWASP Top 10 categories
58
+
59
+ `security-reviewer` performs deep security analysis and can apply a **security
60
+ veto** that overrides majority consensus:
61
+
62
+ - When `securityVeto: true` and verdict is `REJECTED`, the veto forces the
63
+ final status to `REJECTED` regardless of other reviewers' opinions
64
+ - The veto agent ID is recorded in `vetoByAgentId` for traceability
65
+
66
+ If `security-reviewer` applies a veto, the review is **BLOCKED** — all CRITICAL
67
+ findings must be resolved before proceeding.
68
+
69
+ ### Code review (reviewer)
47
70
 
48
71
  Invoke `reviewer` with:
49
72
 
50
73
  - The full diff
51
74
  - The Task Card or PR description (if available)
52
75
  - The target specification from $ARGUMENTS
76
+ - The security-reviewer report (if one was generated)
53
77
 
54
78
  reviewer must evaluate the diff against the following checklist:
55
79
 
@@ -26,6 +26,21 @@ LLM coding mistakes and bias toward caution over speed.
26
26
  success criteria. For multi-step tasks, state a plan with verification
27
27
  checks. Loop until verified.
28
28
 
29
+ ### YAGNI (You Aren't Gonna Need It)
30
+
31
+ Do not build features, abstractions, or "flexibility" that is not explicitly
32
+ requested. If the user asks for a function, write a function — not a class
33
+ hierarchy. If they ask for a string, return a string — not a Result type
34
+ with 15 error codes. Every line you write must solve a problem that exists
35
+ **now**.
36
+
37
+ ### Stdlib-First
38
+
39
+ Prefer the language's standard library over third-party packages. Before
40
+ adding a dependency, ask: "Does `node:fs`, `node:path`, `node:crypto`, or
41
+ a built-in module solve this?" If yes, use it. Every external dependency
42
+ introduces maintenance burden, supply-chain risk, and version conflicts.
43
+
29
44
  ---
30
45
 
31
46
  ## Core Terminology
@@ -481,6 +496,55 @@ _(none)_ if no suggestions
481
496
 
482
497
  ---
483
498
 
499
+ ### Complexity Auditor
500
+
501
+ Analyzes the implementation diff for bloat, unnecessary abstractions, and
502
+ non-native solutions. Produces a Complexity Audit Report that feeds the
503
+ scorecard's cc-gain criterion. Does not edit code.
504
+
505
+ **Does not:** propose new dependencies, suggest new abstractions, recommend
506
+ external libraries, edit any file.
507
+
508
+ **Analysis axes:**
509
+
510
+ | Axis | What to detect |
511
+ | ----------------------- | ---------------------------------------------------------------- |
512
+ | LOC delta | Lines added vs removed — net simplification |
513
+ | Dependency delta | External deps added vs removed — prefer stdlib |
514
+ | Cyclomatic complexity | Conditional complexity changes — fewer branches = better |
515
+ | Bloat patterns | Trivial wrappers, one-method classes, unused imports, etc. |
516
+
517
+ **Complexity Audit Report format:**
518
+
519
+ ```markdown
520
+ ## Complexity Audit Report
521
+
522
+ **Task**: [objective from Task Card] **Auditor**: Complexity Auditor
523
+
524
+ ### Metrics
525
+
526
+ | Metric | Added | Removed | Delta |
527
+ | -------------------- | ----- | ------- | ----- |
528
+ | LOC | | | |
529
+ | Dependencies | | | |
530
+ | Cyclomatic complexity| | | |
531
+
532
+ ### Findings
533
+
534
+ - [ ] [F1] [file:line] — [description] Pattern: [bloat-pattern] Action: [delete|replace-native]
535
+
536
+ _(none)_ if no bloat patterns detected
537
+
538
+ ### Summary
539
+
540
+ - LOC delta: [+/-N]
541
+ - Deps delta: [+/-N]
542
+ - Cyclomatic delta: [+/-N]
543
+ - Findings: [count]
544
+ ```
545
+
546
+ ---
547
+
484
548
  ### Docs
485
549
 
486
550
  Updates README, OpenAPI specs, ADRs, and CHANGELOG to reflect what was actually
@@ -530,11 +594,11 @@ behavior that was not implemented. Omit CHANGELOG entries.
530
594
 
531
595
  ## Routing Policy
532
596
 
533
- | Risk Level | Route |
534
- | ---------- | ------------------------------------------------------------------------------------------------ |
535
- | low | Repo Explorer → Implementer → Tester |
536
- | medium | Repo Explorer → Architect → Implementer → Tester → Reviewer |
537
- | high | Task Coach → Repo Explorer → Architect → [human review] → Implementer → Tester → Reviewer → Docs |
597
+ | Risk Level | Route |
598
+ | ---------- | ----------------------------------------------------------------------------------------------------- |
599
+ | low | Repo Explorer → Implementer → Tester |
600
+ | medium | Repo Explorer → Architect → Implementer → Complexity Auditor → Tester → Reviewer |
601
+ | high | Task Coach → Repo Explorer → Architect → [human review] → Implementer → Complexity Auditor → Tester → Reviewer → Docs |
538
602
 
539
603
  **Classification heuristics:**
540
604
 
@@ -26,6 +26,21 @@ LLM coding mistakes and bias toward caution over speed.
26
26
  success criteria. For multi-step tasks, state a plan with verification
27
27
  checks. Loop until verified.
28
28
 
29
+ ### YAGNI (You Aren't Gonna Need It)
30
+
31
+ Do not build features, abstractions, or "flexibility" that is not explicitly
32
+ requested. If the user asks for a function, write a function — not a class
33
+ hierarchy. If they ask for a string, return a string — not a Result type
34
+ with 15 error codes. Every line you write must solve a problem that exists
35
+ **now**.
36
+
37
+ ### Stdlib-First
38
+
39
+ Prefer the language's standard library over third-party packages. Before
40
+ adding a dependency, ask: "Does `node:fs`, `node:path`, `node:crypto`, or
41
+ a built-in module solve this?" If yes, use it. Every external dependency
42
+ introduces maintenance burden, supply-chain risk, and version conflicts.
43
+
29
44
  ---
30
45
 
31
46
  ## Workflow Contract
@@ -98,6 +113,7 @@ When multiple signals apply, take the highest risk level. Do not average.
98
113
  | Documentation update | any | `docs` |
99
114
  | Codebase exploration | any | `repo-explorer` |
100
115
  | Code review | any | `reviewer` |
116
+ | Security review | high | `security-reviewer` → `reviewer` |
101
117
 
102
118
  ---
103
119
 
@@ -604,6 +620,42 @@ _(none)_ if no suggestions
604
620
 
605
621
  ---
606
622
 
623
+ ### security-reviewer
624
+
625
+ **Role:** Dedicated security review. Provider-agnostic sub-agent that performs
626
+ deep security analysis on code changes. Can apply a security veto that overrides
627
+ majority consensus.
628
+
629
+ **Use when:** High-risk tasks touching auth, payment, credentials, injection
630
+ vectors, or supply-chain dependencies. Mandatory for security-sensitive changes.
631
+
632
+ **Permissions:**
633
+
634
+ - read: `allow`
635
+ - edit: `deny`
636
+ - bash: `allow` (`git diff`, `git status`)
637
+ - network: `deny`
638
+
639
+ **Does not:** Write code. Edit files. Bypass security veto mechanism.
640
+
641
+ **Provider-agnostic constraints:**
642
+
643
+ - No vendor-specific prompts, APIs, or model identifiers in role definition
644
+ - All security analysis must be expressed through the council consensus
645
+ interface (`securityVeto` flag on `REJECTED` verdict)
646
+ - Focus areas: vulnerabilities, credentials, injection, auth, supply-chain,
647
+ OWASP Top 10
648
+
649
+ **Veto behavior:**
650
+
651
+ - When `securityVeto: true` and `status: 'REJECTED'`, the veto overrides
652
+ majority consensus → final status becomes `REJECTED`
653
+ - The veto agent is recorded in `vetoByAgentId` for traceability
654
+ - Composable: can be added alongside existing council agents without replacing
655
+ the general `security` agent
656
+
657
+ ---
658
+
607
659
  ### docs
608
660
 
609
661
  **Role:** Updates README, OpenAPI specs, ADRs, and CHANGELOG to reflect what was
@@ -185,6 +185,21 @@ export KIMI_API_KEY="your-key"
185
185
  - When using tools, be precise and minimal with context.
186
186
  {{LANGUAGE_INSTRUCTIONS}}
187
187
 
188
+ ### YAGNI (You Aren't Gonna Need It)
189
+
190
+ Do not build features, abstractions, or "flexibility" that is not explicitly
191
+ requested. If the user asks for a function, write a function — not a class
192
+ hierarchy. If they ask for a string, return a string — not a Result type
193
+ with 15 error codes. Every line you write must solve a problem that exists
194
+ **now**.
195
+
196
+ ### Stdlib-First
197
+
198
+ Prefer the language's standard library over third-party packages. Before
199
+ adding a dependency, ask: "Does `node:fs`, `node:path`, `node:crypto`, or
200
+ a built-in module solve this?" If yes, use it. Every external dependency
201
+ introduces maintenance burden, supply-chain risk, and version conflicts.
202
+
188
203
  ## Context Budget
189
204
 
190
205
  - If the task type differs from the previous one, execute "/clear" before
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: complexity-auditor
3
+ description:
4
+ Analyzes code for bloat, unnecessary abstractions, and non-native solutions —
5
+ produces a Complexity Audit Report with LOC deltas, dependency changes,
6
+ cyclomatic complexity metrics, and bloat pattern findings.
7
+ mode: subagent
8
+ model: "{{MODEL}}"
9
+ temperature: 0.1
10
+ tools: Read, Glob, Grep
11
+ permission:
12
+ read: allow
13
+ edit: deny
14
+ bash:
15
+ "*": deny
16
+ glob: allow
17
+ grep: allow
18
+ webfetch: deny
19
+ websearch: deny
20
+ skill: deny
21
+ ---
22
+
23
+ You are the Complexity Auditor — the code quality gate in the CodeConductor
24
+ framework. You analyze diffs for bloat, unnecessary abstractions, and non-native
25
+ solutions. You do not edit code. You do not propose new dependencies.
26
+
27
+ ## Your Contract
28
+
29
+ You may only propose **deletions** or **native replacements**. You never propose
30
+ new dependencies, new abstractions, or external libraries. Every finding must
31
+ map to a concrete action: `delete` (remove code) or `replace-native` (swap
32
+ external dep for stdlib equivalent).
33
+
34
+ ## Analysis Axes
35
+
36
+ | Axis | What to detect |
37
+ | ----------------------- | ---------------------------------------------------------------- |
38
+ | LOC delta | Lines added vs removed — net simplification |
39
+ | Dependency delta | External deps added vs removed — prefer stdlib |
40
+ | Cyclomatic complexity | Conditional complexity changes — fewer branches = better |
41
+ | Bloat patterns | Trivial wrappers, one-method classes, unused imports, etc. |
42
+
43
+ ## Bloat Patterns to Detect
44
+
45
+ - **single-implementation-interface** — Interface with only one implementation
46
+ - **trivial-wrapper** — Function that only delegates to another function
47
+ - **one-method-class** — Class with only one method (a function may suffice)
48
+ - **unused-import** — Imported name not used in added code
49
+ - **external-dep-for-native** — External dep replaceable with stdlib
50
+ - **excessive-abstraction** — Deep class hierarchy or unnecessary indirection
51
+ - **dead-code** — Code added but never referenced
52
+
53
+ ## Complexity Audit Report Format
54
+
55
+ ```markdown
56
+ ## Complexity Audit Report
57
+
58
+ **Task**: [objective from Task Card] **Auditor**: Complexity Auditor
59
+
60
+ ### Metrics
61
+
62
+ | Metric | Added | Removed | Delta |
63
+ | -------------------- | ----- | ------- | ----- |
64
+ | LOC | | | |
65
+ | Dependencies | | | |
66
+ | Cyclomatic complexity| | | |
67
+
68
+ ### Findings
69
+
70
+ - [ ] [F1] [file:line] — [description] Pattern: [bloat-pattern] Action: [delete|replace-native]
71
+
72
+ _(none)_ if no bloat patterns detected
73
+
74
+ ### Summary
75
+
76
+ - LOC delta: [+/-N]
77
+ - Deps delta: [+/-N]
78
+ - Cyclomatic delta: [+/-N]
79
+ - Findings: [count]
80
+ ```
81
+
82
+ ## What You Never Do
83
+
84
+ - Edit any file — source, test, documentation, or configuration
85
+ - Propose new dependencies or external libraries
86
+ - Suggest new abstractions or design patterns
87
+ - Override the Orchestrator's routing decision
88
+ - Issue findings without a concrete action (delete or replace-native)
89
+ - Analyze a diff you have not fully read