cc-codeconductor 0.4.0 → 0.4.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 CHANGED
@@ -40,6 +40,9 @@ contracts, task cards, and risk-based routing.
40
40
  > (skills, subagents, commands) for the active or specified target
41
41
  > - `npx cc-codeconductor debt-harvest` (alias: `harvest`) — scans source files
42
42
  > for `// defer` comments and writes `.codeconductor/debt-ledger.md`
43
+ > - `npx cc-codeconductor goal "<objective>"` (alias: `cc-goal`) — decomposes an
44
+ > objective into a dependency-ordered task graph, writes
45
+ > `.codeconductor/current-goal.yml`
43
46
  > - `/cc-pagespeed --url <url>` — audits web performance using the PageSpeed
44
47
  > Insights API; applies the 80/20 principle to produce a prioritized report of
45
48
  > Core Web Vitals (LCP, TBT, CLS, FCP, TTFB) with framework-specific fixes;
@@ -120,21 +123,22 @@ Task Card → Risk Classification → Routing Policy → Conductor Agent → Del
120
123
 
121
124
  ---
122
125
 
123
- ## Current Support (v0.2.0)
126
+ ## Current Support
124
127
 
125
128
  - OpenCode preset
126
129
  - Claude Code-compatible preset (see [Claude Environment Options & Best Practices](file:///c:/Users/R2D2/Documents/GitHub/codeconductor/docs/claude-env-options.md))
127
130
  - Codex preset
128
131
  - Spring Boot / Kotlin workflow
129
132
  - Python / Django workflow guidance
130
- - 10 core Conductor Agents
131
- - Routing Policy v0.2.0
133
+ - 12 core Conductor Agents
134
+ - Routing Policy v0.3.0
132
135
  - Task Card template
133
136
  - Scorecard template
134
137
  - End-to-end example
135
138
  - YAML-driven model configuration
136
139
  - Provider-agnostic `AgentContract` abstraction with target renderers for Claude, OpenCode, Codex, and Agy
137
140
  - Council consensus engine (`councilConsensus()`) for multi-agent governance with majority/unanimous algorithms and security veto
141
+ - Phase 5 runtime modules — scoped context injection, TDD history compaction, concise inter-agent messaging, and token budget enforcement in the compile-fix loop
138
142
 
139
143
  ---
140
144
 
@@ -300,6 +304,20 @@ into `.codeconductor/debt-ledger.md`, grouped by optional tag
300
304
  Supported extensions: `.ts`, `.tsx`, `.js`, `.jsx`, `.go`, `.rs`, `.java`,
301
305
  `.kt`, `.swift`, `.cs`, `.php`, `.scala`, `.dart`, `.c`, `.cpp`, `.h`, `.hpp`.
302
306
 
307
+ #### `goal` — decompose objective into task graph
308
+
309
+ ```bash
310
+ npx cc-codeconductor goal "Add user authentication"
311
+ npx cc-codeconductor goal "Implement CRUD for invoices"
312
+ npx cc-codeconductor cc-goal "Add search with filters" # alias
313
+ npx cc-codeconductor goal "Add user authentication" --output json
314
+ ```
315
+
316
+ Matches the objective against built-in templates (auth, crud, search,
317
+ notification, migration) or falls back to a generic 4-task chain. Writes the
318
+ resulting task graph to `.codeconductor/current-goal.yml` with dependency
319
+ ordering. The orchestrator uses this file to delegate tasks in dependency order.
320
+
303
321
 
304
322
  ### Global options
305
323
 
@@ -404,9 +422,14 @@ codeconductor/
404
422
  ├── src/ ← CLI source (TypeScript + Bun)
405
423
  │ ├── cli/ ← entry point, router, error codes
406
424
  │ ├── commands/ ← init, detect, install, doctor, update
407
- │ ├── core/ ← config, detection, filesystem, presets
425
+ │ ├── core/ ← config, detection, filesystem, presets, goal
426
+ │ │ ├── context/ ← scoped context injection (Phase 5)
427
+ │ │ ├── compaction/ ← TDD history compaction hook (Phase 5)
428
+ │ │ ├── messages/ ← concise inter-agent formatter (Phase 5)
429
+ │ │ └── loop/ ← compile-fix loop controller (Phase 5)
408
430
  │ ├── adapters/ ← opencode, claude, codex generators
409
431
  │ ├── domain/council/ ← council spec, agent, contract
432
+ │ ├── domain/loop/ ← loop state machine
410
433
  │ ├── validation/ ← Zod schemas
411
434
  │ ├── utils/ ← Result type, logger, invariant
412
435
  │ └── presets/council/ ← bundled council.yml preset
package/dist/index.js CHANGED
@@ -11126,7 +11126,7 @@ function validateCouncilSpec(data) {
11126
11126
  function validateConfig(data) {
11127
11127
  return CodeConductorConfigSchema.parse(data);
11128
11128
  }
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;
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, GoalTaskSchema, GoalGraphSchema;
11130
11130
  var init_schemas = __esm(() => {
11131
11131
  init_zod();
11132
11132
  CouncilAgentSpecSchema = exports_external.object({
@@ -11167,7 +11167,11 @@ var init_schemas = __esm(() => {
11167
11167
  LoopConfigSchema = exports_external.object({
11168
11168
  enabled: exports_external.boolean().optional().default(true),
11169
11169
  maxIterations: exports_external.number().int().positive().optional().default(3),
11170
- maxTokenBudget: exports_external.number().nonnegative().optional().default(0)
11170
+ maxTokenBudget: exports_external.number().nonnegative().optional().default(0),
11171
+ compaction: exports_external.object({
11172
+ enabled: exports_external.boolean().optional().default(true),
11173
+ historyFile: exports_external.string().optional()
11174
+ }).optional()
11171
11175
  });
11172
11176
  CodeConductorConfigSchema = exports_external.object({
11173
11177
  version: exports_external.string(),
@@ -11309,6 +11313,22 @@ var init_schemas = __esm(() => {
11309
11313
  environment: exports_external.string().optional(),
11310
11314
  release: exports_external.string().optional()
11311
11315
  });
11316
+ GoalTaskSchema = exports_external.object({
11317
+ id: exports_external.string(),
11318
+ title: exports_external.string(),
11319
+ type: exports_external.enum(["feature", "fix", "refactor", "test", "docs"]),
11320
+ risk: exports_external.enum(["low", "medium", "high"]),
11321
+ status: exports_external.enum(["pending", "in-progress", "done", "blocked"]),
11322
+ depends_on: exports_external.array(exports_external.string()).optional().default([]),
11323
+ acceptance_criteria: exports_external.array(exports_external.string()),
11324
+ blocked_reason: exports_external.string().optional(),
11325
+ context_scope: exports_external.enum(["isolated", "continuation", "full"]).optional().default("isolated")
11326
+ });
11327
+ GoalGraphSchema = exports_external.object({
11328
+ objective: exports_external.string(),
11329
+ tasks: exports_external.array(GoalTaskSchema),
11330
+ created_at: exports_external.string()
11331
+ });
11312
11332
  });
11313
11333
 
11314
11334
  // src/core/presets/package-paths.ts
@@ -12000,10 +12020,9 @@ var init_agy_installer = __esm(() => {
12000
12020
 
12001
12021
  // src/core/filesystem/credential-guard.ts
12002
12022
  import { readFile as readFile7 } from "node:fs/promises";
12003
- import { resolve as resolve6 } from "node:path";
12004
12023
  async function loadPolicyPatterns() {
12005
12024
  try {
12006
- const content = await readFile7(POLICY_PATH2, "utf-8");
12025
+ const content = await readFile7(POLICY_PATH, "utf-8");
12007
12026
  const parsed = $parse(content);
12008
12027
  if (Array.isArray(parsed.secretPatterns) && parsed.secretPatterns.length > 0) {
12009
12028
  return parsed.secretPatterns;
@@ -12035,9 +12054,10 @@ async function loadCredentialPatterns(config) {
12035
12054
  }
12036
12055
  return DEFAULT_SECRET_PATTERNS;
12037
12056
  }
12038
- var DEFAULT_SECRET_PATTERNS, POLICY_PATH2;
12057
+ var DEFAULT_SECRET_PATTERNS;
12039
12058
  var init_credential_guard = __esm(() => {
12040
12059
  init_dist();
12060
+ init_package_paths();
12041
12061
  DEFAULT_SECRET_PATTERNS = [
12042
12062
  "password",
12043
12063
  "secret",
@@ -12048,7 +12068,6 @@ var init_credential_guard = __esm(() => {
12048
12068
  "auth[_-]?token",
12049
12069
  "private[_-]?key"
12050
12070
  ];
12051
- POLICY_PATH2 = resolve6(import.meta.dir, "..", "..", "..", "policy.yml");
12052
12071
  });
12053
12072
 
12054
12073
  // src/core/filesystem/file-writer.ts
@@ -12135,7 +12154,7 @@ init_errors();
12135
12154
  // package.json
12136
12155
  var package_default = {
12137
12156
  name: "cc-codeconductor",
12138
- version: "0.4.0",
12157
+ version: "0.4.2",
12139
12158
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
12140
12159
  keywords: [
12141
12160
  "ai",
@@ -13057,7 +13076,8 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13057
13076
  "tester",
13058
13077
  "reviewer",
13059
13078
  "docs",
13060
- "repo-explorer"
13079
+ "repo-explorer",
13080
+ "goal-planner"
13061
13081
  ];
13062
13082
  let result = templatedContent;
13063
13083
  for (const role of knownRoles) {
@@ -13145,8 +13165,20 @@ function injectMcpServers(jsonString, filePath) {
13145
13165
  if (isClaudeSettings || isAgyMcpConfig) {
13146
13166
  obj.mcpServers = mergeDeep(obj.mcpServers || {}, mcpServers);
13147
13167
  } else if (isOpencodeJsonc) {
13148
- obj.mcp = obj.mcp || {};
13149
- obj.mcp.servers = mergeDeep(obj.mcp.servers || {}, mcpServers);
13168
+ const opencodeMcp = {};
13169
+ for (const [key, server] of Object.entries(mcpServers)) {
13170
+ const commandArray = typeof server.command === "string" ? [server.command, ...server.args || []] : server.command;
13171
+ const formattedServer = {
13172
+ type: "local",
13173
+ command: commandArray,
13174
+ enabled: true
13175
+ };
13176
+ if (server.env) {
13177
+ formattedServer.environment = server.env;
13178
+ }
13179
+ opencodeMcp[key] = formattedServer;
13180
+ }
13181
+ obj.mcp = mergeDeep(obj.mcp || {}, opencodeMcp);
13150
13182
  }
13151
13183
  return JSON.stringify(obj, null, 2);
13152
13184
  } catch {
@@ -13800,13 +13832,13 @@ async function doctorCommand(options) {
13800
13832
  // src/commands/init.command.ts
13801
13833
  import { access as access3, mkdir as mkdir3, readFile as readFile8, writeFile as writeFile3 } from "node:fs/promises";
13802
13834
  import { homedir as homedir5 } from "node:os";
13803
- import { basename, resolve as resolve8 } from "node:path";
13835
+ import { basename, resolve as resolve7 } from "node:path";
13804
13836
 
13805
13837
  // src/core/config/config-writer.ts
13806
13838
  init_dist();
13807
13839
  init_errors();
13808
13840
  import { access as access2, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
13809
- import { resolve as resolve7 } from "node:path";
13841
+ import { resolve as resolve6 } from "node:path";
13810
13842
 
13811
13843
  // src/core/config/codeconductor-config.ts
13812
13844
  init_credential_guard();
@@ -13847,9 +13879,9 @@ var CONFIG_DIR = ".codeconductor";
13847
13879
  var CONFIG_FILE2 = "config.yml";
13848
13880
  async function writeConfig(projectRoot, config = {}, force = false) {
13849
13881
  try {
13850
- const configDir = resolve7(projectRoot, CONFIG_DIR);
13882
+ const configDir = resolve6(projectRoot, CONFIG_DIR);
13851
13883
  await mkdir2(configDir, { recursive: true });
13852
- const configPath = resolve7(configDir, CONFIG_FILE2);
13884
+ const configPath = resolve6(configDir, CONFIG_FILE2);
13853
13885
  if (!force) {
13854
13886
  try {
13855
13887
  await access2(configPath);
@@ -14075,7 +14107,7 @@ async function initCommand(options) {
14075
14107
  }
14076
14108
  async function resolvePresetsToCopy() {
14077
14109
  const sources = [];
14078
- const bundledCouncil = resolve8(SRC_PRESETS_DIR, "council", "council.yml");
14110
+ const bundledCouncil = resolve7(SRC_PRESETS_DIR, "council", "council.yml");
14079
14111
  if (await fileExists2(bundledCouncil)) {
14080
14112
  sources.push({ name: "council.yml", sourcePath: bundledCouncil });
14081
14113
  }
@@ -14086,11 +14118,11 @@ async function resolvePresetsToCopy() {
14086
14118
  return sources;
14087
14119
  }
14088
14120
  async function copyPresets(baseDir, presets, force) {
14089
- const presetsDir = resolve8(baseDir, ".codeconductor", "presets");
14121
+ const presetsDir = resolve7(baseDir, ".codeconductor", "presets");
14090
14122
  await mkdir3(presetsDir, { recursive: true });
14091
14123
  const copied = [];
14092
14124
  for (const preset of presets) {
14093
- const destPath = resolve8(presetsDir, preset.name);
14125
+ const destPath = resolve7(presetsDir, preset.name);
14094
14126
  if (!force && await fileExists2(destPath)) {
14095
14127
  continue;
14096
14128
  }
@@ -14123,7 +14155,7 @@ init_claude_installer();
14123
14155
  init_codex_installer();
14124
14156
  init_opencode_installer();
14125
14157
  import { homedir as homedir6 } from "node:os";
14126
- import { resolve as resolve9 } from "node:path";
14158
+ import { resolve as resolve8 } from "node:path";
14127
14159
  init_file_writer();
14128
14160
  init_preset_loader();
14129
14161
 
@@ -14193,12 +14225,12 @@ async function installCommand(options) {
14193
14225
  let targetPath = f.path;
14194
14226
  let targetBase = baseDir;
14195
14227
  if ((t === "agy" || t === "gemini") && isGlobal) {
14196
- targetBase = resolve9(homedir6(), ".gemini", "config");
14228
+ targetBase = resolve8(homedir6(), ".gemini", "config");
14197
14229
  targetPath = targetPath.replace(/^\.agents\/?/, "");
14198
14230
  }
14199
14231
  return {
14200
14232
  ...f,
14201
- path: resolve9(targetBase, targetPath)
14233
+ path: resolve8(targetBase, targetPath)
14202
14234
  };
14203
14235
  });
14204
14236
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
@@ -14309,7 +14341,7 @@ async function installPresetCommand(options) {
14309
14341
 
14310
14342
  // src/commands/install-lsp.command.ts
14311
14343
  import { homedir as homedir8 } from "node:os";
14312
- import { resolve as resolve10 } from "node:path";
14344
+ import { resolve as resolve9 } from "node:path";
14313
14345
 
14314
14346
  // src/core/lsp/lsp-config-utils.ts
14315
14347
  function getLanguageServerConfig(lspIds) {
@@ -14846,7 +14878,7 @@ async function installLspCommand(options) {
14846
14878
  const generatedFiles = generator.generate(installReport.results);
14847
14879
  const resolvedFiles = generatedFiles.map((f) => ({
14848
14880
  ...f,
14849
- path: resolve10(baseDir, f.path)
14881
+ path: resolve9(baseDir, f.path)
14850
14882
  }));
14851
14883
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
14852
14884
  for (const result of results) {
@@ -15081,6 +15113,522 @@ async function debtHarvestCommand(options) {
15081
15113
  }
15082
15114
  }
15083
15115
 
15116
+ // src/core/goal/goal-planner.ts
15117
+ var TEMPLATES = [
15118
+ {
15119
+ keywords: ["login", "auth", "authentication", "signin", "sign-in"],
15120
+ tasks: [
15121
+ {
15122
+ id: "auth-schema",
15123
+ title: "Define auth data model and DB schema",
15124
+ type: "feature",
15125
+ risk: "high",
15126
+ status: "pending",
15127
+ context_scope: "isolated",
15128
+ depends_on: [],
15129
+ acceptance_criteria: [
15130
+ "User entity with email, password_hash, created_at fields",
15131
+ "Migration script creates users table",
15132
+ "Schema supports password reset token"
15133
+ ]
15134
+ },
15135
+ {
15136
+ id: "auth-api",
15137
+ title: "Define auth API contract",
15138
+ type: "feature",
15139
+ risk: "medium",
15140
+ status: "pending",
15141
+ context_scope: "isolated",
15142
+ depends_on: ["auth-schema"],
15143
+ acceptance_criteria: [
15144
+ "POST /auth/register endpoint defined",
15145
+ "POST /auth/login endpoint defined",
15146
+ "POST /auth/logout endpoint defined",
15147
+ "OpenAPI spec updated"
15148
+ ]
15149
+ },
15150
+ {
15151
+ id: "auth-impl",
15152
+ title: "Implement auth endpoints",
15153
+ type: "feature",
15154
+ risk: "high",
15155
+ status: "pending",
15156
+ context_scope: "isolated",
15157
+ depends_on: ["auth-api"],
15158
+ acceptance_criteria: [
15159
+ "Register endpoint hashes password and stores user",
15160
+ "Login endpoint validates credentials and returns session",
15161
+ "Logout endpoint invalidates session",
15162
+ "Input validation on all endpoints"
15163
+ ]
15164
+ },
15165
+ {
15166
+ id: "auth-tests",
15167
+ title: "Write auth tests",
15168
+ type: "test",
15169
+ risk: "medium",
15170
+ status: "pending",
15171
+ context_scope: "isolated",
15172
+ depends_on: ["auth-impl"],
15173
+ acceptance_criteria: [
15174
+ "Unit tests for password hashing",
15175
+ "Integration tests for register/login/logout flow",
15176
+ "Edge cases: duplicate email, invalid password, expired token"
15177
+ ]
15178
+ }
15179
+ ]
15180
+ },
15181
+ {
15182
+ keywords: ["crud", "resource", "api", "endpoint", "rest"],
15183
+ tasks: [
15184
+ {
15185
+ id: "crud-model",
15186
+ title: "Define data model and schema",
15187
+ type: "feature",
15188
+ risk: "medium",
15189
+ status: "pending",
15190
+ context_scope: "isolated",
15191
+ depends_on: [],
15192
+ acceptance_criteria: [
15193
+ "Entity definition with required fields",
15194
+ "Database migration script",
15195
+ "Schema follows project conventions"
15196
+ ]
15197
+ },
15198
+ {
15199
+ id: "crud-service",
15200
+ title: "Implement repository/service layer",
15201
+ type: "feature",
15202
+ risk: "medium",
15203
+ status: "pending",
15204
+ context_scope: "isolated",
15205
+ depends_on: ["crud-model"],
15206
+ acceptance_criteria: [
15207
+ "Create, Read (list + by id), Update, Delete operations",
15208
+ "Input validation",
15209
+ "Error handling for not-found and conflict"
15210
+ ]
15211
+ },
15212
+ {
15213
+ id: "crud-api",
15214
+ title: "Implement API endpoints",
15215
+ type: "feature",
15216
+ risk: "medium",
15217
+ status: "pending",
15218
+ context_scope: "isolated",
15219
+ depends_on: ["crud-service"],
15220
+ acceptance_criteria: [
15221
+ "REST endpoints for all CRUD operations",
15222
+ "Proper HTTP status codes",
15223
+ "Request/response schemas"
15224
+ ]
15225
+ },
15226
+ {
15227
+ id: "crud-tests",
15228
+ title: "Write tests",
15229
+ type: "test",
15230
+ risk: "low",
15231
+ status: "pending",
15232
+ context_scope: "isolated",
15233
+ depends_on: ["crud-api"],
15234
+ acceptance_criteria: [
15235
+ "Unit tests for service layer",
15236
+ "Integration tests for API endpoints",
15237
+ "Edge cases covered"
15238
+ ]
15239
+ }
15240
+ ]
15241
+ },
15242
+ {
15243
+ keywords: ["search", "filter", "query", "full-text", "fts"],
15244
+ tasks: [
15245
+ {
15246
+ id: "search-schema",
15247
+ title: "Design search schema and indexing strategy",
15248
+ type: "feature",
15249
+ risk: "medium",
15250
+ status: "pending",
15251
+ context_scope: "isolated",
15252
+ depends_on: [],
15253
+ acceptance_criteria: [
15254
+ "Search index definition",
15255
+ "Indexing strategy documented",
15256
+ "Performance requirements defined"
15257
+ ]
15258
+ },
15259
+ {
15260
+ id: "search-service",
15261
+ title: "Implement search service",
15262
+ type: "feature",
15263
+ risk: "medium",
15264
+ status: "pending",
15265
+ context_scope: "isolated",
15266
+ depends_on: ["search-schema"],
15267
+ acceptance_criteria: [
15268
+ "Full-text search with ranking",
15269
+ "Filter support",
15270
+ "Pagination"
15271
+ ]
15272
+ },
15273
+ {
15274
+ id: "search-api",
15275
+ title: "Implement search API",
15276
+ type: "feature",
15277
+ risk: "low",
15278
+ status: "pending",
15279
+ context_scope: "isolated",
15280
+ depends_on: ["search-service"],
15281
+ acceptance_criteria: [
15282
+ "GET /search endpoint with query params",
15283
+ "Response includes results + metadata",
15284
+ "Rate limiting applied"
15285
+ ]
15286
+ },
15287
+ {
15288
+ id: "search-tests",
15289
+ title: "Write search tests",
15290
+ type: "test",
15291
+ risk: "low",
15292
+ status: "pending",
15293
+ context_scope: "isolated",
15294
+ depends_on: ["search-api"],
15295
+ acceptance_criteria: [
15296
+ "Unit tests for search logic",
15297
+ "Integration tests with sample data",
15298
+ "Performance test for large datasets"
15299
+ ]
15300
+ }
15301
+ ]
15302
+ },
15303
+ {
15304
+ keywords: ["notification", "email", "alert", "push", "notify"],
15305
+ tasks: [
15306
+ {
15307
+ id: "notif-model",
15308
+ title: "Design notification data model",
15309
+ type: "feature",
15310
+ risk: "medium",
15311
+ status: "pending",
15312
+ context_scope: "isolated",
15313
+ depends_on: [],
15314
+ acceptance_criteria: [
15315
+ "Notification entity with type, payload, status, user_id",
15316
+ "Database migration",
15317
+ "Enum for notification types"
15318
+ ]
15319
+ },
15320
+ {
15321
+ id: "notif-service",
15322
+ title: "Implement notification service",
15323
+ type: "feature",
15324
+ risk: "medium",
15325
+ status: "pending",
15326
+ context_scope: "isolated",
15327
+ depends_on: ["notif-model"],
15328
+ acceptance_criteria: [
15329
+ "Send notification by type",
15330
+ "Queue-based delivery",
15331
+ "Retry logic for failed deliveries"
15332
+ ]
15333
+ },
15334
+ {
15335
+ id: "notif-api",
15336
+ title: "Implement notification API",
15337
+ type: "feature",
15338
+ risk: "low",
15339
+ status: "pending",
15340
+ context_scope: "isolated",
15341
+ depends_on: ["notif-service"],
15342
+ acceptance_criteria: [
15343
+ "POST /notifications endpoint",
15344
+ "GET /notifications endpoint with read/unread filter",
15345
+ "PATCH /notifications/:id/read endpoint"
15346
+ ]
15347
+ },
15348
+ {
15349
+ id: "notif-tests",
15350
+ title: "Write notification tests",
15351
+ type: "test",
15352
+ risk: "low",
15353
+ status: "pending",
15354
+ context_scope: "isolated",
15355
+ depends_on: ["notif-api"],
15356
+ acceptance_criteria: [
15357
+ "Unit tests for notification logic",
15358
+ "Integration tests for API",
15359
+ "Edge cases: invalid type, missing user"
15360
+ ]
15361
+ }
15362
+ ]
15363
+ },
15364
+ {
15365
+ keywords: ["migration", "migrate", "schema change", "database change", "column"],
15366
+ tasks: [
15367
+ {
15368
+ id: "migrate-script",
15369
+ title: "Write migration script",
15370
+ type: "feature",
15371
+ risk: "high",
15372
+ status: "pending",
15373
+ context_scope: "isolated",
15374
+ depends_on: [],
15375
+ acceptance_criteria: [
15376
+ "Migration is reversible",
15377
+ "Handles existing data gracefully",
15378
+ "Rollback script included"
15379
+ ]
15380
+ },
15381
+ {
15382
+ id: "migrate-model",
15383
+ title: "Update domain model",
15384
+ type: "refactor",
15385
+ risk: "medium",
15386
+ status: "pending",
15387
+ context_scope: "isolated",
15388
+ depends_on: ["migrate-script"],
15389
+ acceptance_criteria: [
15390
+ "Domain model reflects new schema",
15391
+ "Existing code paths updated",
15392
+ "Backward compatibility maintained"
15393
+ ]
15394
+ },
15395
+ {
15396
+ id: "migrate-dal",
15397
+ title: "Update data access layer",
15398
+ type: "refactor",
15399
+ risk: "medium",
15400
+ status: "pending",
15401
+ context_scope: "isolated",
15402
+ depends_on: ["migrate-model"],
15403
+ acceptance_criteria: [
15404
+ "Repository/service reads/writes new schema",
15405
+ "No broken queries",
15406
+ "Performance not degraded"
15407
+ ]
15408
+ },
15409
+ {
15410
+ id: "migrate-tests",
15411
+ title: "Write migration tests",
15412
+ type: "test",
15413
+ risk: "high",
15414
+ status: "pending",
15415
+ context_scope: "isolated",
15416
+ depends_on: ["migrate-dal"],
15417
+ acceptance_criteria: [
15418
+ "Migration up/down test",
15419
+ "Data integrity test",
15420
+ "Integration tests with new schema"
15421
+ ]
15422
+ }
15423
+ ]
15424
+ }
15425
+ ];
15426
+ var GENERIC_TEMPLATE = [
15427
+ {
15428
+ id: "plan-scope",
15429
+ title: "Clarify requirements and scope",
15430
+ type: "feature",
15431
+ risk: "low",
15432
+ status: "pending",
15433
+ context_scope: "isolated",
15434
+ depends_on: [],
15435
+ acceptance_criteria: ["Objective is well-defined", "Scope boundaries identified"]
15436
+ },
15437
+ {
15438
+ id: "plan-design",
15439
+ title: "Design technical approach",
15440
+ type: "feature",
15441
+ risk: "medium",
15442
+ status: "pending",
15443
+ context_scope: "isolated",
15444
+ depends_on: ["plan-scope"],
15445
+ acceptance_criteria: ["Architecture decisions documented", "API contracts defined"]
15446
+ },
15447
+ {
15448
+ id: "plan-impl",
15449
+ title: "Implement solution",
15450
+ type: "feature",
15451
+ risk: "medium",
15452
+ status: "pending",
15453
+ context_scope: "isolated",
15454
+ depends_on: ["plan-design"],
15455
+ acceptance_criteria: ["Core functionality implemented", "Follows project conventions"]
15456
+ },
15457
+ {
15458
+ id: "plan-tests",
15459
+ title: "Write tests and verify",
15460
+ type: "test",
15461
+ risk: "low",
15462
+ status: "pending",
15463
+ context_scope: "isolated",
15464
+ depends_on: ["plan-impl"],
15465
+ acceptance_criteria: ["Unit tests pass", "Integration tests pass", "No regressions"]
15466
+ }
15467
+ ];
15468
+ function matchTemplate(objective) {
15469
+ const lower = objective.toLowerCase();
15470
+ for (const template of TEMPLATES) {
15471
+ const matched = template.keywords.some((kw) => lower.includes(kw));
15472
+ if (matched)
15473
+ return template.tasks;
15474
+ }
15475
+ return GENERIC_TEMPLATE;
15476
+ }
15477
+ function planGoal(objective) {
15478
+ const tasks = matchTemplate(objective);
15479
+ return {
15480
+ objective,
15481
+ tasks: tasks.map((t) => ({ ...t, status: "pending" })),
15482
+ created_at: new Date().toISOString()
15483
+ };
15484
+ }
15485
+
15486
+ // src/core/goal/goal-state.ts
15487
+ init_dist();
15488
+ init_schemas();
15489
+ import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
15490
+ import { resolve as resolve10 } from "node:path";
15491
+ function validateGoalGraph(graph) {
15492
+ const ids = new Set(graph.tasks.map((t) => t.id));
15493
+ if (ids.size !== graph.tasks.length) {
15494
+ return err(new Error("Duplicate task IDs in goal graph"));
15495
+ }
15496
+ for (const task of graph.tasks) {
15497
+ for (const dep of task.depends_on ?? []) {
15498
+ if (!ids.has(dep)) {
15499
+ return err(new Error(`Task "${task.id}" depends on unknown task "${dep}"`));
15500
+ }
15501
+ }
15502
+ }
15503
+ const visited = new Set;
15504
+ const inStack = new Set;
15505
+ function dfs(taskId) {
15506
+ if (inStack.has(taskId))
15507
+ return true;
15508
+ if (visited.has(taskId))
15509
+ return false;
15510
+ visited.add(taskId);
15511
+ inStack.add(taskId);
15512
+ const task = graph.tasks.find((t) => t.id === taskId);
15513
+ if (task) {
15514
+ for (const dep of task.depends_on ?? []) {
15515
+ if (dfs(dep))
15516
+ return true;
15517
+ }
15518
+ }
15519
+ inStack.delete(taskId);
15520
+ return false;
15521
+ }
15522
+ for (const task of graph.tasks) {
15523
+ if (dfs(task.id)) {
15524
+ return err(new Error(`Cycle detected involving task "${task.id}"`));
15525
+ }
15526
+ }
15527
+ return ok(graph);
15528
+ }
15529
+ async function writeGoal(projectRoot, graph) {
15530
+ const validation = validateGoalGraph(graph);
15531
+ if (!validation.success)
15532
+ return validation;
15533
+ const dir = resolve10(projectRoot, ".codeconductor");
15534
+ await mkdir7(dir, { recursive: true });
15535
+ const yaml = $stringify(graph);
15536
+ await writeFile6(resolve10(dir, "current-goal.yml"), yaml, "utf-8");
15537
+ return ok(undefined);
15538
+ }
15539
+
15540
+ // src/commands/goal.command.ts
15541
+ function renderDependencyTree(tasks) {
15542
+ const lines = [];
15543
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
15544
+ const roots = tasks.filter((t) => !t.depends_on || t.depends_on.length === 0);
15545
+ function render(taskId, prefix, isLast) {
15546
+ const task = taskMap.get(taskId);
15547
+ if (!task)
15548
+ return;
15549
+ const connector = isLast ? "└── " : "├── ";
15550
+ const label = `${task.id}: ${task.title}`;
15551
+ lines.push(`${prefix}${connector}${label}`);
15552
+ const children = tasks.filter((t) => t.depends_on && t.depends_on.includes(taskId));
15553
+ const childPrefix = prefix + (isLast ? " " : "│ ");
15554
+ children.forEach((child, i) => {
15555
+ render(child.id, childPrefix, i === children.length - 1);
15556
+ });
15557
+ }
15558
+ roots.forEach((root, i) => {
15559
+ render(root.id, "", i === roots.length - 1);
15560
+ });
15561
+ return lines.join(`
15562
+ `);
15563
+ }
15564
+ async function goalCommand(options) {
15565
+ const { objective, projectRoot, output } = options;
15566
+ if (!objective || objective.trim().length === 0) {
15567
+ return {
15568
+ code: 1,
15569
+ data: {
15570
+ success: false,
15571
+ command: "goal",
15572
+ errors: ['Objective is required. Usage: codeconductor goal "<objective>"']
15573
+ }
15574
+ };
15575
+ }
15576
+ try {
15577
+ const graph = planGoal(objective);
15578
+ const writeResult = await writeGoal(projectRoot, graph);
15579
+ if (!writeResult.success) {
15580
+ return {
15581
+ code: 1,
15582
+ data: {
15583
+ success: false,
15584
+ command: "goal",
15585
+ errors: [`Failed to write goal: ${writeResult.error.message}`]
15586
+ }
15587
+ };
15588
+ }
15589
+ if (output === "json") {
15590
+ return {
15591
+ code: 0,
15592
+ data: {
15593
+ success: true,
15594
+ command: "goal",
15595
+ objective: graph.objective,
15596
+ tasks: graph.tasks,
15597
+ file: ".codeconductor/current-goal.yml"
15598
+ }
15599
+ };
15600
+ }
15601
+ const tree = renderDependencyTree(graph.tasks);
15602
+ const lines = [
15603
+ `Objective: ${graph.objective}`,
15604
+ "",
15605
+ "Task dependency graph:",
15606
+ tree,
15607
+ "",
15608
+ `Tasks: ${graph.tasks.length} total`,
15609
+ `File: .codeconductor/current-goal.yml`
15610
+ ];
15611
+ return {
15612
+ code: 0,
15613
+ data: {
15614
+ success: true,
15615
+ command: "goal",
15616
+ output: lines.join(`
15617
+ `)
15618
+ }
15619
+ };
15620
+ } catch (e) {
15621
+ return {
15622
+ code: 1,
15623
+ data: {
15624
+ success: false,
15625
+ command: "goal",
15626
+ errors: [e instanceof Error ? e.message : String(e)]
15627
+ }
15628
+ };
15629
+ }
15630
+ }
15631
+
15084
15632
  // src/commands/help.command.ts
15085
15633
  import { readdir as readdir3 } from "node:fs/promises";
15086
15634
  import { join as join8 } from "node:path";
@@ -15208,7 +15756,7 @@ async function helpCommand(options) {
15208
15756
  }
15209
15757
 
15210
15758
  // src/commands/seo-audit.command.ts
15211
- import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
15759
+ import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
15212
15760
  import { dirname as dirname4, resolve as resolve11 } from "node:path";
15213
15761
 
15214
15762
  // src/infrastructure/http/safe-fetch.ts
@@ -16501,13 +17049,13 @@ async function seoAuditCommand(options) {
16501
17049
  }
16502
17050
  if (output) {
16503
17051
  const outputPath = resolve11(options.projectRoot, output);
16504
- await mkdir7(dirname4(outputPath), { recursive: true });
16505
- await writeFile6(outputPath, formattedOutput, "utf-8");
17052
+ await mkdir8(dirname4(outputPath), { recursive: true });
17053
+ await writeFile7(outputPath, formattedOutput, "utf-8");
16506
17054
  } else if (format === "markdown") {
16507
17055
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
16508
17056
  const defaultPath = resolve11(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
16509
- await mkdir7(dirname4(defaultPath), { recursive: true });
16510
- await writeFile6(defaultPath, formattedOutput, "utf-8");
17057
+ await mkdir8(dirname4(defaultPath), { recursive: true });
17058
+ await writeFile7(defaultPath, formattedOutput, "utf-8");
16511
17059
  process.stderr.write(`Report saved to: ${defaultPath}
16512
17060
  `);
16513
17061
  }
@@ -16535,7 +17083,7 @@ async function seoAuditCommand(options) {
16535
17083
  }
16536
17084
 
16537
17085
  // src/commands/seo-llms.command.ts
16538
- import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
17086
+ import { writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
16539
17087
  import { dirname as dirname5, resolve as resolve12 } from "node:path";
16540
17088
 
16541
17089
  // src/domain/seo/llms-generator.ts
@@ -16680,8 +17228,8 @@ async function seoLlmsCommand(options) {
16680
17228
  }) : await generateLlmsTxtFromUrl(url);
16681
17229
  process.stderr.write("\r" + " ".repeat(80) + "\r");
16682
17230
  const outputPath = output ? resolve12(options.projectRoot, output) : resolve12(options.projectRoot, "llms.txt");
16683
- await mkdir8(dirname5(outputPath), { recursive: true });
16684
- await writeFile7(outputPath, result.content, "utf-8");
17231
+ await mkdir9(dirname5(outputPath), { recursive: true });
17232
+ await writeFile8(outputPath, result.content, "utf-8");
16685
17233
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
16686
17234
  `);
16687
17235
  return {
@@ -16709,7 +17257,7 @@ async function seoLlmsCommand(options) {
16709
17257
  // src/commands/update.command.ts
16710
17258
  import { homedir as homedir9 } from "node:os";
16711
17259
  import { resolve as resolve13, dirname as dirname6 } from "node:path";
16712
- import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8, stat as stat3 } from "node:fs/promises";
17260
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, stat as stat3 } from "node:fs/promises";
16713
17261
  init_package_paths();
16714
17262
  async function updateCommand(options) {
16715
17263
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
@@ -16778,9 +17326,9 @@ async function updateCommand(options) {
16778
17326
  const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
16779
17327
  const bundledCouncil = resolve13(SRC_PRESETS_DIR, "council", "council.yml");
16780
17328
  try {
16781
- const content = await readFile10(bundledCouncil, "utf-8");
16782
- await mkdir9(dirname6(localCouncil), { recursive: true });
16783
- await writeFile8(localCouncil, content, "utf-8");
17329
+ const content = await readFile11(bundledCouncil, "utf-8");
17330
+ await mkdir10(dirname6(localCouncil), { recursive: true });
17331
+ await writeFile9(localCouncil, content, "utf-8");
16784
17332
  updated.push(localCouncil);
16785
17333
  } catch (e) {
16786
17334
  throw new Error(`Failed to update council.yml: ${e}`);
@@ -16789,9 +17337,9 @@ async function updateCommand(options) {
16789
17337
  if (updateResults.policy) {
16790
17338
  const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
16791
17339
  try {
16792
- const content = await readFile10(POLICY_PATH, "utf-8");
16793
- await mkdir9(dirname6(localPolicy), { recursive: true });
16794
- await writeFile8(localPolicy, content, "utf-8");
17340
+ const content = await readFile11(POLICY_PATH, "utf-8");
17341
+ await mkdir10(dirname6(localPolicy), { recursive: true });
17342
+ await writeFile9(localPolicy, content, "utf-8");
16795
17343
  updated.push(localPolicy);
16796
17344
  } catch (e) {
16797
17345
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -16887,8 +17435,8 @@ async function updateCommand(options) {
16887
17435
  }
16888
17436
  } catch {}
16889
17437
  try {
16890
- await mkdir9(dirname6(lockDest), { recursive: true });
16891
- await writeFile8(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
17438
+ await mkdir10(dirname6(lockDest), { recursive: true });
17439
+ await writeFile9(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
16892
17440
  updated.push(lockDest);
16893
17441
  } catch (e) {
16894
17442
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -16999,6 +17547,7 @@ Commands:
16999
17547
  update Update installed presets
17000
17548
  help / cc-help Show preset inventory (skills, subagents, commands)
17001
17549
  debt-harvest / harvest Scan source files for deferred debt items
17550
+ goal / cc-goal Plan goal into task graph with dependencies
17002
17551
 
17003
17552
  Options:
17004
17553
  --help, -h Show this help message
@@ -17124,6 +17673,13 @@ async function routeCommand(args, projectRoot) {
17124
17673
  dir: options.dir,
17125
17674
  output: flags.output
17126
17675
  });
17676
+ case "goal":
17677
+ case "cc-goal":
17678
+ return goalCommand({
17679
+ objective: subcommand || options.objective || "",
17680
+ projectRoot,
17681
+ output: flags.output
17682
+ });
17127
17683
  case "seo": {
17128
17684
  if (subcommand === "audit") {
17129
17685
  return seoAuditCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-codeconductor",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -248,6 +248,30 @@ High-risk checkpoint: [yes | no — if yes, describe what triggers a stop]
248
248
 
249
249
  ---
250
250
 
251
+ ### goal-planner
252
+
253
+ **Role:** Transforms an objective string into a YAML task graph with dependencies. Pure function: objective → GoalGraph. No side effects.
254
+
255
+ **Use when:** User runs `codeconductor goal "<objective>"` or the orchestrator needs a multi-step plan before delegation.
256
+
257
+ **Permissions:**
258
+ - read: `allow`
259
+ - edit: `deny`
260
+ - bash: `deny`
261
+ - network: `deny`
262
+
263
+ **Model:** `{{MODEL_GEMINI}}`
264
+
265
+ **Does not:** Write files. Execute commands. Make routing decisions.
266
+
267
+ **Template matching:**
268
+ The planner matches objective keywords against built-in templates (auth, crud, search, notification, migration) and falls back to a generic 4-task chain: task-coach → architect → implementer → tester.
269
+
270
+ **Dependency order delegation (orchestrator):**
271
+ When the orchestrator receives a GoalGraph, it delegates tasks in dependency order. A task is only routed after all its `depends_on` targets complete with status `done`. If a dependency is `blocked`, the dependent task remains `pending`. The orchestrator tracks the graph state in `.codeconductor/current-goal.yml`.
272
+
273
+ ---
274
+
251
275
  ### architect
252
276
 
253
277
  **Role:** Designs the technical approach. Produces ADRs, module boundaries, and API contracts.
@@ -790,6 +790,40 @@ module, or identifying the impact radius of a change.
790
790
 
791
791
  ---
792
792
 
793
+ ### goal-planner
794
+
795
+ **Role:** Transforms an objective string into a YAML task graph with
796
+ dependencies. Pure function: objective → GoalGraph. No side effects.
797
+
798
+ **Use when:** User runs `codeconductor goal "<objective>"` or the orchestrator
799
+ needs a multi-step plan before delegation.
800
+
801
+ **Permissions:**
802
+
803
+ - read: `allow`
804
+ - edit: `deny`
805
+ - bash: `deny`
806
+ - network: `deny`
807
+
808
+ **Does not:** Write files. Execute commands. Make routing decisions.
809
+
810
+ **Template matching:**
811
+
812
+ The planner matches objective keywords against built-in templates (auth, crud,
813
+ search, notification, migration) and falls back to a generic 4-task chain:
814
+ `task-coach → architect → implementer → tester`.
815
+
816
+ **Dependency order delegation (orchestrator):**
817
+
818
+ When the orchestrator receives a GoalGraph, it delegates tasks in dependency
819
+ order. A task is only routed after all its `depends_on` targets complete with
820
+ status `done`. If a dependency is `blocked`, the dependent task remains `pending`.
821
+ The orchestrator tracks the graph state in `.codeconductor/current-goal.yml`.
822
+
823
+ **Model:** `{{MODEL_CODEX}}`
824
+
825
+ ---
826
+
793
827
  ## Hard Rules (all agents)
794
828
 
795
829
  These apply regardless of agent or task.
@@ -57,6 +57,13 @@ agents:
57
57
  gemini: gemini-2.5-flash
58
58
  cursor: gpt-5.4-mini
59
59
  agy: gemini-2.5-flash
60
+ goal-planner:
61
+ claude: claude-haiku-4-5-20251001
62
+ opencode: opencode-go/qwen3.6-plus
63
+ codex: gpt-5.4-mini
64
+ gemini: gemini-2.5-flash
65
+ cursor: gpt-5.4-mini
66
+ agy: gemini-2.5-flash
60
67
  tools:
61
68
  Read:
62
69
  agy: view_file
@@ -51,6 +51,12 @@ agents:
51
51
  codex: gpt-5.4-mini
52
52
  gemini: gemini-2.5-flash
53
53
  cursor: gpt-5.4-mini
54
+ goal-planner:
55
+ claude: claude-haiku-4-5-20251001
56
+ opencode: opencode-go/qwen3.6-plus
57
+ codex: gpt-5.4-mini
58
+ gemini: gemini-2.5-flash
59
+ cursor: gpt-5.4-mini
54
60
  tools:
55
61
  Read:
56
62
  claude: ViewCodeItem / ReadFile
@@ -52,6 +52,12 @@ agents:
52
52
  codex: gpt-5.4-mini
53
53
  gemini: gemini-2.5-flash
54
54
  cursor: gpt-5.4-mini
55
+ goal-planner:
56
+ claude: claude-haiku-4-5-20251001
57
+ opencode: opencode-go/qwen3.6-plus
58
+ codex: gpt-5.4-mini
59
+ gemini: gemini-2.5-flash
60
+ cursor: gpt-5.4-mini
55
61
  tools:
56
62
  Read:
57
63
  claude: ViewCodeItem / ReadFile
@@ -51,6 +51,12 @@ agents:
51
51
  codex: gpt-5.4-mini
52
52
  gemini: gemini-2.5-flash
53
53
  cursor: gpt-5.4-mini
54
+ goal-planner:
55
+ claude: claude-haiku-4-5-20251001
56
+ opencode: opencode-go/qwen3.6-plus
57
+ codex: gpt-5.4-mini
58
+ gemini: gemini-2.5-flash
59
+ cursor: gpt-5.4-mini
54
60
  tools:
55
61
  Read:
56
62
  claude: ViewCodeItem / ReadFile
@@ -51,6 +51,12 @@ agents:
51
51
  codex: gpt-5.4-mini
52
52
  gemini: gemini-2.5-flash
53
53
  cursor: gpt-5.4-mini
54
+ goal-planner:
55
+ claude: claude-haiku-4-5-20251001
56
+ opencode: opencode-go/qwen3.6-plus
57
+ codex: gpt-5.4-mini
58
+ gemini: gemini-2.5-flash
59
+ cursor: gpt-5.4-mini
54
60
  tools:
55
61
  Read:
56
62
  claude: ViewCodeItem / ReadFile
@@ -51,6 +51,12 @@ agents:
51
51
  codex: gpt-5.4-mini
52
52
  gemini: gemini-2.5-flash
53
53
  cursor: gpt-5.4-mini
54
+ goal-planner:
55
+ claude: claude-haiku-4-5-20251001
56
+ opencode: opencode-go/deepseek-v4-flash
57
+ codex: gpt-5.4-mini
58
+ gemini: gemini-2.5-flash
59
+ cursor: gpt-5.4-mini
54
60
  permissions:
55
61
  Read: read
56
62
  Write: edit