cc-codeconductor 0.4.1 → 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
@@ -12134,7 +12154,7 @@ init_errors();
12134
12154
  // package.json
12135
12155
  var package_default = {
12136
12156
  name: "cc-codeconductor",
12137
- version: "0.4.1",
12157
+ version: "0.4.2",
12138
12158
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
12139
12159
  keywords: [
12140
12160
  "ai",
@@ -13056,7 +13076,8 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13056
13076
  "tester",
13057
13077
  "reviewer",
13058
13078
  "docs",
13059
- "repo-explorer"
13079
+ "repo-explorer",
13080
+ "goal-planner"
13060
13081
  ];
13061
13082
  let result = templatedContent;
13062
13083
  for (const role of knownRoles) {
@@ -13144,8 +13165,20 @@ function injectMcpServers(jsonString, filePath) {
13144
13165
  if (isClaudeSettings || isAgyMcpConfig) {
13145
13166
  obj.mcpServers = mergeDeep(obj.mcpServers || {}, mcpServers);
13146
13167
  } else if (isOpencodeJsonc) {
13147
- obj.mcp = obj.mcp || {};
13148
- 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);
13149
13182
  }
13150
13183
  return JSON.stringify(obj, null, 2);
13151
13184
  } catch {
@@ -15080,6 +15113,522 @@ async function debtHarvestCommand(options) {
15080
15113
  }
15081
15114
  }
15082
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
+
15083
15632
  // src/commands/help.command.ts
15084
15633
  import { readdir as readdir3 } from "node:fs/promises";
15085
15634
  import { join as join8 } from "node:path";
@@ -15207,8 +15756,8 @@ async function helpCommand(options) {
15207
15756
  }
15208
15757
 
15209
15758
  // src/commands/seo-audit.command.ts
15210
- import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
15211
- import { dirname as dirname4, resolve as resolve10 } from "node:path";
15759
+ import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
15760
+ import { dirname as dirname4, resolve as resolve11 } from "node:path";
15212
15761
 
15213
15762
  // src/infrastructure/http/safe-fetch.ts
15214
15763
  import { lookup } from "node:dns/promises";
@@ -15295,7 +15844,7 @@ async function safeFetch(urlString, options = {}) {
15295
15844
  }
15296
15845
  }
15297
15846
  async function delay(ms) {
15298
- return new Promise((resolve10) => setTimeout(resolve10, ms));
15847
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
15299
15848
  }
15300
15849
 
15301
15850
  // src/infrastructure/parsers/sitemap-parser.ts
@@ -16499,14 +17048,14 @@ async function seoAuditCommand(options) {
16499
17048
  formattedOutput = formatCli(report);
16500
17049
  }
16501
17050
  if (output) {
16502
- const outputPath = resolve10(options.projectRoot, output);
16503
- await mkdir7(dirname4(outputPath), { recursive: true });
16504
- await writeFile6(outputPath, formattedOutput, "utf-8");
17051
+ const outputPath = resolve11(options.projectRoot, output);
17052
+ await mkdir8(dirname4(outputPath), { recursive: true });
17053
+ await writeFile7(outputPath, formattedOutput, "utf-8");
16505
17054
  } else if (format === "markdown") {
16506
17055
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
16507
- const defaultPath = resolve10(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
16508
- await mkdir7(dirname4(defaultPath), { recursive: true });
16509
- await writeFile6(defaultPath, formattedOutput, "utf-8");
17056
+ const defaultPath = resolve11(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
17057
+ await mkdir8(dirname4(defaultPath), { recursive: true });
17058
+ await writeFile7(defaultPath, formattedOutput, "utf-8");
16510
17059
  process.stderr.write(`Report saved to: ${defaultPath}
16511
17060
  `);
16512
17061
  }
@@ -16518,7 +17067,7 @@ async function seoAuditCommand(options) {
16518
17067
  command: "seo audit",
16519
17068
  report,
16520
17069
  output: formattedOutput,
16521
- outputFile: output ? resolve10(options.projectRoot, output) : format === "markdown" ? resolve10(options.projectRoot, "seo-reports") : undefined
17070
+ outputFile: output ? resolve11(options.projectRoot, output) : format === "markdown" ? resolve11(options.projectRoot, "seo-reports") : undefined
16522
17071
  }
16523
17072
  };
16524
17073
  } catch (error) {
@@ -16534,8 +17083,8 @@ async function seoAuditCommand(options) {
16534
17083
  }
16535
17084
 
16536
17085
  // src/commands/seo-llms.command.ts
16537
- import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
16538
- import { dirname as dirname5, resolve as resolve11 } from "node:path";
17086
+ import { writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
17087
+ import { dirname as dirname5, resolve as resolve12 } from "node:path";
16539
17088
 
16540
17089
  // src/domain/seo/llms-generator.ts
16541
17090
  function extractTitle2(html) {
@@ -16678,9 +17227,9 @@ async function seoLlmsCommand(options) {
16678
17227
  }
16679
17228
  }) : await generateLlmsTxtFromUrl(url);
16680
17229
  process.stderr.write("\r" + " ".repeat(80) + "\r");
16681
- const outputPath = output ? resolve11(options.projectRoot, output) : resolve11(options.projectRoot, "llms.txt");
16682
- await mkdir8(dirname5(outputPath), { recursive: true });
16683
- await writeFile7(outputPath, result.content, "utf-8");
17230
+ const outputPath = output ? resolve12(options.projectRoot, output) : resolve12(options.projectRoot, "llms.txt");
17231
+ await mkdir9(dirname5(outputPath), { recursive: true });
17232
+ await writeFile8(outputPath, result.content, "utf-8");
16684
17233
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
16685
17234
  `);
16686
17235
  return {
@@ -16707,8 +17256,8 @@ async function seoLlmsCommand(options) {
16707
17256
 
16708
17257
  // src/commands/update.command.ts
16709
17258
  import { homedir as homedir9 } from "node:os";
16710
- import { resolve as resolve12, dirname as dirname6 } from "node:path";
16711
- import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8, stat as stat3 } from "node:fs/promises";
17259
+ import { resolve as resolve13, dirname as dirname6 } from "node:path";
17260
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, stat as stat3 } from "node:fs/promises";
16712
17261
  init_package_paths();
16713
17262
  async function updateCommand(options) {
16714
17263
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
@@ -16774,23 +17323,23 @@ async function updateCommand(options) {
16774
17323
  }
16775
17324
  const updated = [];
16776
17325
  if (updateResults.council) {
16777
- const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
16778
- const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
17326
+ const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
17327
+ const bundledCouncil = resolve13(SRC_PRESETS_DIR, "council", "council.yml");
16779
17328
  try {
16780
- const content = await readFile10(bundledCouncil, "utf-8");
16781
- await mkdir9(dirname6(localCouncil), { recursive: true });
16782
- 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");
16783
17332
  updated.push(localCouncil);
16784
17333
  } catch (e) {
16785
17334
  throw new Error(`Failed to update council.yml: ${e}`);
16786
17335
  }
16787
17336
  }
16788
17337
  if (updateResults.policy) {
16789
- const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
17338
+ const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
16790
17339
  try {
16791
- const content = await readFile10(POLICY_PATH, "utf-8");
16792
- await mkdir9(dirname6(localPolicy), { recursive: true });
16793
- 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");
16794
17343
  updated.push(localPolicy);
16795
17344
  } catch (e) {
16796
17345
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -16842,12 +17391,12 @@ async function updateCommand(options) {
16842
17391
  let targetPath = f.path;
16843
17392
  let targetBase = basePath;
16844
17393
  if ((targetName === "agy" || targetName === "gemini") && isGlobal) {
16845
- targetBase = resolve12(homedir9(), ".gemini", "config");
17394
+ targetBase = resolve13(homedir9(), ".gemini", "config");
16846
17395
  targetPath = targetPath.replace(/^\.agents\/?/, "");
16847
17396
  }
16848
17397
  return {
16849
17398
  ...f,
16850
- path: resolve12(targetBase, targetPath)
17399
+ path: resolve13(targetBase, targetPath)
16851
17400
  };
16852
17401
  });
16853
17402
  const filesToWrite = resolvedFiles.filter((f) => t.files.includes(f.path));
@@ -16875,19 +17424,19 @@ async function updateCommand(options) {
16875
17424
  for (const s of updateResults.skills) {
16876
17425
  newSkillsLock[s.id] = s.latestVersion;
16877
17426
  }
16878
- let lockDest = resolve12(basePath, ".codeconductor", "skills-lock.json");
17427
+ let lockDest = resolve13(basePath, ".codeconductor", "skills-lock.json");
16879
17428
  try {
16880
- const statAgents = await stat3(resolve12(basePath, ".agents"));
17429
+ const statAgents = await stat3(resolve13(basePath, ".agents"));
16881
17430
  if (statAgents.isDirectory()) {
16882
- const statCodeConductor = await stat3(resolve12(basePath, ".codeconductor")).catch(() => null);
17431
+ const statCodeConductor = await stat3(resolve13(basePath, ".codeconductor")).catch(() => null);
16883
17432
  if (!statCodeConductor) {
16884
- lockDest = resolve12(basePath, ".agents", "skills-lock.json");
17433
+ lockDest = resolve13(basePath, ".agents", "skills-lock.json");
16885
17434
  }
16886
17435
  }
16887
17436
  } catch {}
16888
17437
  try {
16889
- await mkdir9(dirname6(lockDest), { recursive: true });
16890
- 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");
16891
17440
  updated.push(lockDest);
16892
17441
  } catch (e) {
16893
17442
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -16998,6 +17547,7 @@ Commands:
16998
17547
  update Update installed presets
16999
17548
  help / cc-help Show preset inventory (skills, subagents, commands)
17000
17549
  debt-harvest / harvest Scan source files for deferred debt items
17550
+ goal / cc-goal Plan goal into task graph with dependencies
17001
17551
 
17002
17552
  Options:
17003
17553
  --help, -h Show this help message
@@ -17123,6 +17673,13 @@ async function routeCommand(args, projectRoot) {
17123
17673
  dir: options.dir,
17124
17674
  output: flags.output
17125
17675
  });
17676
+ case "goal":
17677
+ case "cc-goal":
17678
+ return goalCommand({
17679
+ objective: subcommand || options.objective || "",
17680
+ projectRoot,
17681
+ output: flags.output
17682
+ });
17126
17683
  case "seo": {
17127
17684
  if (subcommand === "audit") {
17128
17685
  return seoAuditCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-codeconductor",
3
- "version": "0.4.1",
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