cc-codeconductor 1.5.1 → 1.6.0

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/dist/index.js CHANGED
@@ -72,7 +72,7 @@ var package_default;
72
72
  var init_package = __esm(() => {
73
73
  package_default = {
74
74
  name: "cc-codeconductor",
75
- version: "1.5.1",
75
+ version: "1.6.0",
76
76
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
77
77
  keywords: [
78
78
  "ai",
@@ -4140,9 +4140,10 @@ var init_runner_target = __esm(() => {
4140
4140
  "cursor",
4141
4141
  "agy",
4142
4142
  "pi",
4143
+ "muse",
4143
4144
  "all"
4144
4145
  ];
4145
- INDIVIDUAL_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy", "pi"];
4146
+ INDIVIDUAL_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy", "pi", "muse"];
4146
4147
  });
4147
4148
 
4148
4149
  // src/utils/cli-bin.ts
@@ -11324,6 +11325,12 @@ __export(exports_schemas, {
11324
11325
  validateTaskOutcome: () => validateTaskOutcome,
11325
11326
  validateWorkflowProfile: () => validateWorkflowProfile
11326
11327
  });
11328
+ import { isAbsolute } from "node:path";
11329
+ function isContainedRelativePath(value) {
11330
+ if (value === "" || isAbsolute(value))
11331
+ return false;
11332
+ return !value.replace(/\\/g, "/").split("/").includes("..");
11333
+ }
11327
11334
  function validateCouncilSpec(data) {
11328
11335
  return CouncilSpecSchema.parse(data);
11329
11336
  }
@@ -11441,7 +11448,7 @@ function validateProductEvent(data) {
11441
11448
  function validateSkillsRegistry(data) {
11442
11449
  return SkillsRegistrySchema.parse(data);
11443
11450
  }
11444
- var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CompileCheckConfigSchema, LoopConfigSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema, SkillFrontmatterSchema, CommandFrontmatterSchema, TargetCapabilitiesSchema, ContractTargetSchema, ContractFormatSchema, AgentContractSchema, CouncilFindingSchema, CouncilVerdictInputSchema, ConsensusConfigSchema, CouncilVerdictSchema, ClaudeAgentFileSchema, OpenCodeAgentFileSchema, SentryStackFrameSchema, SentryWebhookSchema, MemoryPointerSchema, MemoryIndexSchema, GoalTaskSchema, GoalGraphSchema, ConfidenceLevelSchema, ProductNodeTypeSchema, GraphRelationSchema, KnowledgeEntitySchema, DecisionSchema, EvidenceSchema, CanonicalTaskCardStatusSchema, CanonicalTaskCardSchema, ProductGraphNodeSchema, ProductGraphEdgeSchema, ProductGraphSchema, ProductMetaSchema, ProductEventTypeSchema, ProductEventSchema, OperationalStateSchema, StrategicMemorySchema, ImpactReportSchema, BusinessReviewOutputSchema, VerificationReportSchema, BacklogStatusSchema, BacklogTypeSchema, BacklogPrioritySchema, BacklogGlobalSchema, BacklogItemSchema, BacklogDocumentSchema, OpenspecTaskCardPhaseSchema, OpenspecTaskCardStatusSchema, OpenspecTaskCardSchema, OpenspecStateSchema, SpecAnalyzeFindingSchema, SpecAnalyzeReportSchema, WorkflowCommandSchema, CcepOutputFormatSchema, CommandEnvelopeSchema, WorkflowPhaseSchema, WorkflowRiskRuleSchema, WorkflowProfileSchema, ExecutionContextSchema, PlannerOutputSchema, AgentArtifactSchema, AgentOutputSchema, ImplementerTestsSchema, ImplementerOutputSchema, ReviewAxisSchema, ReviewFindingSchema, ReviewerOutputSchema, TechnicalPlanOutputSchema, FixIntakeOutputSchema, ScorecardVerdictSchema, ScorecardCriterionIdSchema, ScorecardCriterionSchema, ScorecardRecordSchema, TaskOutcomeSourceSchema, TaskOutcomeStatusSchema, DeliveryRouteSchema, TokenUsageSchema, DeliveryTelemetrySchema, DeliveryLedgerTaskSchema, DeliveryLedgerSchema, HandoffEnvelopeSchema, RepoRelativePathSchema, DeliveryLedgerRequestSchema, TaskOutcomeSchema, HarnessComponentIdSchema, HarnessComponentLayerSchema, HarnessToggleKindSchema, HarnessCatalogEntrySchema, HarnessCatalogSchema, HarnessOverlaySchema, HarnessExperimentSchema, HarnessSuiteTaskSchema, HarnessSuiteSchema, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema, SkillsRegistrySchema;
11451
+ var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CompileCheckConfigSchema, LoopConfigSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ContainedRelativePathSchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema, SkillFrontmatterSchema, CommandFrontmatterSchema, TargetCapabilitiesSchema, ContractTargetSchema, ContractFormatSchema, AgentContractSchema, CouncilFindingSchema, CouncilVerdictInputSchema, ConsensusConfigSchema, CouncilVerdictSchema, ClaudeAgentFileSchema, OpenCodeAgentFileSchema, SentryStackFrameSchema, SentryWebhookSchema, MemoryPointerSchema, MemoryIndexSchema, GoalTaskSchema, GoalGraphSchema, ConfidenceLevelSchema, ProductNodeTypeSchema, GraphRelationSchema, KnowledgeEntitySchema, DecisionSchema, EvidenceSchema, CanonicalTaskCardStatusSchema, CanonicalTaskCardSchema, ProductGraphNodeSchema, ProductGraphEdgeSchema, ProductGraphSchema, ProductMetaSchema, ProductEventTypeSchema, ProductEventSchema, OperationalStateSchema, StrategicMemorySchema, ImpactReportSchema, BusinessReviewOutputSchema, VerificationReportSchema, BacklogStatusSchema, BacklogTypeSchema, BacklogPrioritySchema, BacklogGlobalSchema, BacklogItemSchema, BacklogDocumentSchema, OpenspecTaskCardPhaseSchema, OpenspecTaskCardStatusSchema, OpenspecTaskCardSchema, OpenspecStateSchema, SpecAnalyzeFindingSchema, SpecAnalyzeReportSchema, WorkflowCommandSchema, CcepOutputFormatSchema, CommandEnvelopeSchema, WorkflowPhaseSchema, WorkflowRiskRuleSchema, WorkflowProfileSchema, ExecutionContextSchema, PlannerOutputSchema, AgentArtifactSchema, AgentOutputSchema, ImplementerTestsSchema, ImplementerOutputSchema, ReviewAxisSchema, ReviewFindingSchema, ReviewerOutputSchema, TechnicalPlanOutputSchema, FixIntakeOutputSchema, ScorecardVerdictSchema, ScorecardCriterionIdSchema, ScorecardCriterionSchema, ScorecardRecordSchema, TaskOutcomeSourceSchema, TaskOutcomeStatusSchema, DeliveryRouteSchema, TokenUsageSchema, DeliveryTelemetrySchema, DeliveryLedgerTaskSchema, DeliveryLedgerSchema, HandoffEnvelopeSchema, RepoRelativePathSchema, DeliveryLedgerRequestSchema, TaskOutcomeSchema, HarnessComponentIdSchema, HarnessComponentLayerSchema, HarnessToggleKindSchema, HarnessCatalogEntrySchema, HarnessCatalogSchema, HarnessOverlaySchema, HarnessExperimentSchema, HarnessSuiteTaskSchema, HarnessSuiteSchema, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema, SkillsRegistrySchema;
11445
11452
  var init_schemas = __esm(() => {
11446
11453
  init_zod();
11447
11454
  init_runner_target();
@@ -11522,9 +11529,12 @@ var init_schemas = __esm(() => {
11522
11529
  "create-only",
11523
11530
  "skip"
11524
11531
  ]);
11532
+ ContainedRelativePathSchema = exports_external.string().refine(isContainedRelativePath, (value) => ({
11533
+ message: `Path must be relative without ".." segments: ${value}`
11534
+ }));
11525
11535
  ManifestEntrySchema = exports_external.object({
11526
- src: exports_external.string(),
11527
- dest: exports_external.string(),
11536
+ src: ContainedRelativePathSchema,
11537
+ dest: ContainedRelativePathSchema,
11528
11538
  strategy: InstallStrategySchema,
11529
11539
  globalStrategy: InstallStrategySchema.optional(),
11530
11540
  template: exports_external.boolean().optional()
@@ -11545,7 +11555,8 @@ var init_schemas = __esm(() => {
11545
11555
  cursor: exports_external.string().optional(),
11546
11556
  agy: exports_external.string().optional(),
11547
11557
  pi: exports_external.string().optional(),
11548
- grok: exports_external.string().optional()
11558
+ grok: exports_external.string().optional(),
11559
+ muse: exports_external.string().optional()
11549
11560
  })),
11550
11561
  tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional(),
11551
11562
  permissions: PermissionProviderNamesSchema.optional()
@@ -12514,30 +12525,45 @@ var init_target_compatibility = __esm(() => {
12514
12525
  import { readFile as readFile3 } from "node:fs/promises";
12515
12526
  import { join } from "node:path";
12516
12527
  async function loadManifest(target) {
12528
+ const cached = manifestCache.get(target);
12529
+ if (cached)
12530
+ return cached;
12517
12531
  const manifestPath = join(MANIFESTS_DIR, `${target}.yml`);
12518
12532
  const content = await readFile3(manifestPath, "utf-8");
12519
12533
  const data = $parse(content);
12520
- return InstallManifestSchema.parse(data);
12534
+ const manifest = InstallManifestSchema.parse(data);
12535
+ manifestCache.set(target, manifest);
12536
+ return manifest;
12521
12537
  }
12522
12538
  async function loadModelConfig(target) {
12539
+ const cached = modelConfigCache.get(target);
12540
+ if (cached)
12541
+ return cached;
12523
12542
  const [rolesRaw, targetRaw] = await Promise.all([
12524
12543
  readFile3(join(MODELS_DIR, "roles.yml"), "utf-8"),
12525
12544
  readFile3(join(MODELS_DIR, `${target}.yml`), "utf-8")
12526
12545
  ]);
12527
12546
  const roles = $parse(rolesRaw);
12528
12547
  const targetData = $parse(targetRaw);
12529
- return ModelConfigSchema.parse({
12548
+ const config = ModelConfigSchema.parse({
12530
12549
  target: targetData.target,
12531
12550
  agents: roles.agents,
12532
12551
  tools: targetData.permissions ? undefined : roles.tools,
12533
12552
  permissions: targetData.permissions
12534
12553
  });
12554
+ modelConfigCache.set(target, config);
12555
+ return config;
12535
12556
  }
12536
12557
  async function loadTargetCapabilities(target) {
12558
+ const cached = capabilitiesCache.get(target);
12559
+ if (cached)
12560
+ return cached;
12537
12561
  const content = await readFile3(join(TARGETS_DIR, `${target}.yml`), "utf-8");
12538
- return TargetCapabilitiesSchema.parse($parse(content));
12562
+ const capabilities = TargetCapabilitiesSchema.parse($parse(content));
12563
+ capabilitiesCache.set(target, capabilities);
12564
+ return capabilities;
12539
12565
  }
12540
- var MANIFESTS_DIR, MODELS_DIR, TARGETS_DIR, PRESETS_DIR;
12566
+ var MANIFESTS_DIR, MODELS_DIR, TARGETS_DIR, PRESETS_DIR, manifestCache, modelConfigCache, capabilitiesCache;
12541
12567
  var init_manifest_loader = __esm(() => {
12542
12568
  init_dist();
12543
12569
  init_schemas();
@@ -12546,6 +12572,9 @@ var init_manifest_loader = __esm(() => {
12546
12572
  MODELS_DIR = join(SRC_PRESETS_DIR, "models");
12547
12573
  TARGETS_DIR = join(SRC_PRESETS_DIR, "targets");
12548
12574
  PRESETS_DIR = ROOT_PRESETS_DIR;
12575
+ manifestCache = new Map;
12576
+ modelConfigCache = new Map;
12577
+ capabilitiesCache = new Map;
12549
12578
  });
12550
12579
 
12551
12580
  // src/core/i18n/language-instructions.ts
@@ -12796,23 +12825,23 @@ var init_safety = __esm(() => {
12796
12825
  // src/core/filesystem/path-containment.ts
12797
12826
  import { constants as constants2 } from "node:fs";
12798
12827
  import { lstat, mkdir, open, realpath } from "node:fs/promises";
12799
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve3, sep as sep2 } from "node:path";
12828
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve3, sep as sep2 } from "node:path";
12800
12829
  function isInside(root, target) {
12801
12830
  const rel = relative(root, target);
12802
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute(rel);
12831
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute2(rel);
12803
12832
  }
12804
12833
  function isProtectedUnderRoot(rootReal, absolutePath) {
12805
12834
  if (absolutePath === rootReal) {
12806
12835
  return false;
12807
12836
  }
12808
12837
  const relFromRoot = relative(rootReal, absolutePath);
12809
- if (relFromRoot === "" || relFromRoot === ".." || relFromRoot.startsWith(`..${sep2}`) || isAbsolute(relFromRoot)) {
12838
+ if (relFromRoot === "" || relFromRoot === ".." || relFromRoot.startsWith(`..${sep2}`) || isAbsolute2(relFromRoot)) {
12810
12839
  return true;
12811
12840
  }
12812
12841
  return !validateWritePath(relFromRoot);
12813
12842
  }
12814
12843
  async function resolveWithinRoot(projectRoot, relPath) {
12815
- if (isAbsolute(relPath)) {
12844
+ if (isAbsolute2(relPath)) {
12816
12845
  return;
12817
12846
  }
12818
12847
  const rootAbs = resolve3(projectRoot);
@@ -12877,7 +12906,7 @@ async function canonicalizeExistingPrefix(target) {
12877
12906
  }
12878
12907
  }
12879
12908
  async function resolveOutputWithinRoot(projectRoot, relPath) {
12880
- if (relPath === "" || isAbsolute(relPath)) {
12909
+ if (relPath === "" || isAbsolute2(relPath)) {
12881
12910
  return;
12882
12911
  }
12883
12912
  const rootAbs = resolve3(projectRoot);
@@ -12996,7 +13025,7 @@ async function writeContainedFile(projectRoot, relPath, content, options) {
12996
13025
  }
12997
13026
  }
12998
13027
  async function preflightContainedOutput(projectRoot, relPath, options) {
12999
- if (relPath === "" || isAbsolute(relPath)) {
13028
+ if (relPath === "" || isAbsolute2(relPath)) {
13000
13029
  return `Invalid --output path: ${relPath}. It must be relative to the project root.`;
13001
13030
  }
13002
13031
  if (!validateWritePath(relPath)) {
@@ -13385,7 +13414,7 @@ async function injectMcpServers(jsonString, filePath) {
13385
13414
  return jsonString;
13386
13415
  }
13387
13416
  }
13388
- async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale, baseDir) {
13417
+ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale, baseDir, preRendered) {
13389
13418
  if (strategy === "skip") {
13390
13419
  return { src: srcPath, dest: destPath, action: "skipped", dryRun };
13391
13420
  }
@@ -13409,13 +13438,18 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
13409
13438
  if ((strategy === "overwrite" && !force || strategy === "create-only") && await fileExists2(destPath)) {
13410
13439
  return { src: srcPath, dest: destPath, action: "skipped", dryRun };
13411
13440
  }
13412
- let content;
13413
- try {
13414
- content = await readFile4(srcPath, "utf-8");
13415
- } catch (e) {
13416
- return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
13441
+ let incomingContent;
13442
+ if (preRendered !== undefined) {
13443
+ incomingContent = preRendered;
13444
+ } else {
13445
+ let content;
13446
+ try {
13447
+ content = await readFile4(srcPath, "utf-8");
13448
+ } catch (e) {
13449
+ return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
13450
+ }
13451
+ incomingContent = isTemplate && modelConfig ? await renderTemplate(content, modelConfig, srcPath, locale) : content;
13417
13452
  }
13418
- let incomingContent = isTemplate && modelConfig ? await renderTemplate(content, modelConfig, srcPath, locale) : content;
13419
13453
  incomingContent = await injectMcpServers(incomingContent, destPath);
13420
13454
  let finalContent = incomingContent;
13421
13455
  let action = "written";
@@ -13462,7 +13496,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
13462
13496
  }
13463
13497
  }
13464
13498
  if (dryRun) {
13465
- return { src: srcPath, dest: destPath, action, dryRun: true };
13499
+ return { src: srcPath, dest: destPath, action, dryRun: true, renderedContent: finalContent };
13466
13500
  }
13467
13501
  if (baseDir === undefined) {
13468
13502
  return {
@@ -13476,12 +13510,12 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
13476
13510
  await writeContainedFile(baseDir, relative2(baseDir, destPath), finalContent, {
13477
13511
  force: true
13478
13512
  });
13479
- return { src: srcPath, dest: destPath, action };
13513
+ return { src: srcPath, dest: destPath, action, renderedContent: finalContent };
13480
13514
  } catch (e) {
13481
13515
  return { src: srcPath, dest: destPath, action: "error", error: String(e) };
13482
13516
  }
13483
13517
  }
13484
- async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
13518
+ async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en", preRendered) {
13485
13519
  const results = [];
13486
13520
  for (const entry of manifest.entries) {
13487
13521
  const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
@@ -13495,7 +13529,7 @@ async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun,
13495
13529
  const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
13496
13530
  const isTemplate = entry.template === true;
13497
13531
  for (const { src, dest } of files) {
13498
- results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale, resolvedBaseDir));
13532
+ results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale, resolvedBaseDir, preRendered?.[dest]));
13499
13533
  }
13500
13534
  }
13501
13535
  return results;
@@ -13632,6 +13666,9 @@ __export(exports_preset_loader, {
13632
13666
  import { readFile as readFile6 } from "node:fs/promises";
13633
13667
  import { resolve as resolve6 } from "node:path";
13634
13668
  async function loadPreset(name, projectRoot = process.cwd()) {
13669
+ if (!PRESET_NAME_RE.test(name)) {
13670
+ return err(new ValidationError(`Invalid preset name: ${name}`));
13671
+ }
13635
13672
  const candidates = [
13636
13673
  resolve6(projectRoot, ".codeconductor", "presets", `${name}.yml`),
13637
13674
  resolve6(SRC_PRESETS_DIR, name, `${name}.yml`)
@@ -13659,11 +13696,13 @@ async function loadPreset(name, projectRoot = process.cwd()) {
13659
13696
  async function loadCouncilPreset(projectRoot) {
13660
13697
  return loadPreset("council", projectRoot);
13661
13698
  }
13699
+ var PRESET_NAME_RE;
13662
13700
  var init_preset_loader = __esm(() => {
13663
13701
  init_dist();
13664
13702
  init_errors();
13665
13703
  init_schemas();
13666
13704
  init_package_paths();
13705
+ PRESET_NAME_RE = /^[a-z0-9-]+$/;
13667
13706
  });
13668
13707
 
13669
13708
  // src/domain/council/council-agent.ts
@@ -14450,6 +14489,9 @@ function getTargetInstallationPath(target, basePath, isGlobal) {
14450
14489
  if (target === "agy") {
14451
14490
  return isGlobal ? resolve7(basePath, ".gemini", "config") : resolve7(basePath, ".agents");
14452
14491
  }
14492
+ if (target === "muse" && isGlobal) {
14493
+ return resolve7(basePath, ".agents");
14494
+ }
14453
14495
  return resolve7(basePath, `.${target}`);
14454
14496
  }
14455
14497
  async function isTargetInstalled(target, basePath, isGlobal) {
@@ -14676,6 +14718,7 @@ async function checkUpdates(basePath, isGlobal, includeConflicts = false) {
14676
14718
  locale = configResult.data.defaults.locale ?? "en";
14677
14719
  }
14678
14720
  } catch {}
14721
+ const rendered = {};
14679
14722
  const checkTarget = async (target) => {
14680
14723
  const isInstalled = await isTargetInstalled(target, basePath, isGlobal);
14681
14724
  if (!isInstalled) {
@@ -14710,6 +14753,7 @@ async function checkUpdates(basePath, isGlobal, includeConflicts = false) {
14710
14753
  try {
14711
14754
  const srcContent = await readFile7(src, "utf-8");
14712
14755
  expectedContent = isTemplate && modelConfig ? await renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
14756
+ rendered[dest] = expectedContent;
14713
14757
  } catch {
14714
14758
  continue;
14715
14759
  }
@@ -14848,7 +14892,8 @@ async function checkUpdates(basePath, isGlobal, includeConflicts = false) {
14848
14892
  council: councilHasUpdate,
14849
14893
  policy: policyHasUpdate,
14850
14894
  targets: targetResults,
14851
- skills: skillResults
14895
+ skills: skillResults,
14896
+ rendered
14852
14897
  };
14853
14898
  }
14854
14899
  var cachedRegistry, registryLoadAttempted = false;
@@ -15617,6 +15662,7 @@ var init_profiles = __esm(() => {
15617
15662
  { id: "implement", agent: "implementer" },
15618
15663
  {
15619
15664
  id: "council-review",
15665
+ agents: ["architect", "product", "delivery", "data-ops", "security-reviewer", "devil"],
15620
15666
  skill: "council",
15621
15667
  outputSchema: "council-verdict"
15622
15668
  }
@@ -16272,6 +16318,8 @@ function resolveAssets(target) {
16272
16318
  return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v1.0.0"];
16273
16319
  case "pi":
16274
16320
  return ["AGENTS.md", "settings.json", "prompts", "skills", "agents", "prompts/v1.0.0"];
16321
+ case "muse":
16322
+ return ["AGENTS.md", "skills", "hooks.json"];
16275
16323
  }
16276
16324
  }
16277
16325
  var CURRENT_PRESET_VERSION = "v1.0.0";
@@ -17206,11 +17254,13 @@ async function verifyRenderedFiles(manifest, baseDir, results) {
17206
17254
  const isTemplateFile = templatePrefixes.some((p) => r.dest === p || r.dest.startsWith(p + sep3));
17207
17255
  if (!isTemplateFile)
17208
17256
  continue;
17209
- let content;
17210
- try {
17211
- content = await readFile12(r.dest, "utf-8");
17212
- } catch {
17213
- continue;
17257
+ let content = r.renderedContent;
17258
+ if (content === undefined) {
17259
+ try {
17260
+ content = await readFile12(r.dest, "utf-8");
17261
+ } catch {
17262
+ continue;
17263
+ }
17214
17264
  }
17215
17265
  const leaked = [...new Set(content.match(/\{\{[A-Z_]+\}\}/g) ?? [])];
17216
17266
  if (leaked.length > 0) {
@@ -17834,7 +17884,7 @@ var init_opencode_lsp_generator = __esm(() => {
17834
17884
 
17835
17885
  // src/core/lsp/binary-integrity.ts
17836
17886
  import { createHash as createHash2 } from "node:crypto";
17837
- import { isAbsolute as isAbsolute2, resolve as resolve14, sep as sep4 } from "node:path";
17887
+ import { isAbsolute as isAbsolute3, resolve as resolve14, sep as sep4 } from "node:path";
17838
17888
  function assertPinnedBinaryUrl(url) {
17839
17889
  let parsed;
17840
17890
  try {
@@ -17867,7 +17917,7 @@ function assertSha256Hex(content, expectedHex) {
17867
17917
  }
17868
17918
  function resolveSafeArchiveEntry(destRoot, entry) {
17869
17919
  const normalized = entry.replace(/\\/g, "/");
17870
- if (normalized === "" || isAbsolute2(normalized) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || normalized.split("/").includes("..")) {
17920
+ if (normalized === "" || isAbsolute3(normalized) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || normalized.split("/").includes("..")) {
17871
17921
  throw new Error(`Unsafe archive entry rejected: ${entry}`);
17872
17922
  }
17873
17923
  const root = resolve14(destRoot);
@@ -18914,17 +18964,31 @@ var init_install_lsp_command = __esm(() => {
18914
18964
 
18915
18965
  // src/core/install/update-transaction.ts
18916
18966
  import { mkdir as mkdir7, readFile as readFile13, rm as rm2, unlink, writeFile as writeFile6 } from "node:fs/promises";
18917
- import { dirname as dirname7, join as join10, relative as relative5, resolve as resolve16 } from "node:path";
18967
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join10, relative as relative5, resolve as resolve16, sep as sep5 } from "node:path";
18968
+ function assertInsideBase(basePath, destination) {
18969
+ const base = resolve16(basePath);
18970
+ const absolute = resolve16(base, destination);
18971
+ const rel = relative5(base, absolute);
18972
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute4(rel)) {
18973
+ throw new Error(`Update destination escapes the base path: ${destination}`);
18974
+ }
18975
+ return rel;
18976
+ }
18918
18977
 
18919
18978
  class UpdateTransaction {
18920
18979
  snapshots;
18921
18980
  backupDir;
18922
- constructor(snapshots, backupDir) {
18981
+ basePath;
18982
+ constructor(snapshots, backupDir, basePath) {
18923
18983
  this.snapshots = snapshots;
18924
18984
  this.backupDir = backupDir;
18985
+ this.basePath = basePath;
18925
18986
  }
18926
18987
  static async begin(basePath, destinations) {
18927
18988
  const unique = [...new Set(destinations)];
18989
+ for (const destination of unique) {
18990
+ assertInsideBase(basePath, destination);
18991
+ }
18928
18992
  const backupDir = resolve16(basePath, ".codeconductor", "backups", `update-${Date.now()}`);
18929
18993
  await mkdir7(backupDir, { recursive: true });
18930
18994
  const snapshots = [];
@@ -18932,7 +18996,7 @@ class UpdateTransaction {
18932
18996
  try {
18933
18997
  const content = await readFile13(destination);
18934
18998
  snapshots.push({ path: destination, existed: true, content });
18935
- const backupPath = join10(backupDir, relative5(basePath, destination));
18999
+ const backupPath = join10(backupDir, relative5(resolve16(basePath), resolve16(destination)));
18936
19000
  await mkdir7(dirname7(backupPath), { recursive: true });
18937
19001
  await writeFile6(backupPath, content);
18938
19002
  } catch (error) {
@@ -18943,15 +19007,20 @@ class UpdateTransaction {
18943
19007
  }
18944
19008
  }
18945
19009
  }
18946
- return new UpdateTransaction(snapshots, backupDir);
19010
+ return new UpdateTransaction(snapshots, backupDir, basePath);
18947
19011
  }
18948
19012
  async rollback() {
18949
19013
  for (const snapshot of this.snapshots) {
19014
+ const rel = assertInsideBase(this.basePath, snapshot.path);
19015
+ const gated = await resolveOutputWithinRoot(this.basePath, rel);
19016
+ if (gated === undefined) {
19017
+ throw new Error(`Rollback destination is no longer contained: ${snapshot.path}`);
19018
+ }
18950
19019
  if (snapshot.existed) {
18951
- await mkdir7(dirname7(snapshot.path), { recursive: true });
18952
- await writeFile6(snapshot.path, snapshot.content);
19020
+ await mkdir7(dirname7(gated), { recursive: true });
19021
+ await writeFile6(gated, snapshot.content);
18953
19022
  } else {
18954
- await unlink(snapshot.path).catch((error) => {
19023
+ await unlink(gated).catch((error) => {
18955
19024
  if (error.code !== "ENOENT")
18956
19025
  throw error;
18957
19026
  });
@@ -18963,7 +19032,9 @@ class UpdateTransaction {
18963
19032
  await rm2(this.backupDir, { recursive: true, force: true });
18964
19033
  }
18965
19034
  }
18966
- var init_update_transaction = () => {};
19035
+ var init_update_transaction = __esm(() => {
19036
+ init_path_containment();
19037
+ });
18967
19038
 
18968
19039
  // src/commands/update.command.ts
18969
19040
  var exports_update_command = {};
@@ -18971,8 +19042,8 @@ __export(exports_update_command, {
18971
19042
  updateCommand: () => updateCommand
18972
19043
  });
18973
19044
  import { homedir as homedir10 } from "node:os";
18974
- import { resolve as resolve17, dirname as dirname8 } from "node:path";
18975
- import { mkdir as mkdir8, readFile as readFile14, writeFile as writeFile7, stat as stat3 } from "node:fs/promises";
19045
+ import { join as join11, resolve as resolve17 } from "node:path";
19046
+ import { readFile as readFile14, stat as stat3 } from "node:fs/promises";
18976
19047
  async function updateCommand(options) {
18977
19048
  const { dryRun, force, global: isGlobal, output, projectRoot, check = false } = options;
18978
19049
  const basePath = isGlobal ? homedir10() : projectRoot;
@@ -19078,24 +19149,20 @@ async function updateCommand(options) {
19078
19149
  ];
19079
19150
  transaction = await UpdateTransaction.begin(basePath, plannedDestinations);
19080
19151
  if (updateResults.council) {
19081
- const localCouncil = resolve17(basePath, ".codeconductor", "presets", "council.yml");
19082
19152
  const bundledCouncil = resolve17(SRC_PRESETS_DIR, "council", "council.yml");
19083
19153
  try {
19084
19154
  const content = await readFile14(bundledCouncil, "utf-8");
19085
- await mkdir8(dirname8(localCouncil), { recursive: true });
19086
- await writeFile7(localCouncil, content, "utf-8");
19087
- updated.push(localCouncil);
19155
+ const written = await writeContainedFile(basePath, join11(".codeconductor", "presets", "council.yml"), content, { force: true });
19156
+ updated.push(written);
19088
19157
  } catch (e) {
19089
19158
  throw new Error(`Failed to update council.yml: ${e}`);
19090
19159
  }
19091
19160
  }
19092
19161
  if (updateResults.policy) {
19093
- const localPolicy = resolve17(basePath, ".codeconductor", "presets", "policy.yml");
19094
19162
  try {
19095
19163
  const content = await readFile14(POLICY_PATH, "utf-8");
19096
- await mkdir8(dirname8(localPolicy), { recursive: true });
19097
- await writeFile7(localPolicy, content, "utf-8");
19098
- updated.push(localPolicy);
19164
+ const written = await writeContainedFile(basePath, join11(".codeconductor", "presets", "policy.yml"), content, { force: true });
19165
+ updated.push(written);
19099
19166
  } catch (e) {
19100
19167
  throw new Error(`Failed to update policy.yml: ${e}`);
19101
19168
  }
@@ -19106,7 +19173,7 @@ async function updateCommand(options) {
19106
19173
  const targetName = t.target;
19107
19174
  const manifest = await loadManifest(targetName);
19108
19175
  const modelConfig = await loadModelConfig(targetName);
19109
- const results = await copyFromManifest(manifest, PRESETS_DIR, basePath, isGlobal, false, force, modelConfig, locale);
19176
+ const results = await copyFromManifest(manifest, PRESETS_DIR, basePath, isGlobal, false, force, modelConfig, locale, updateResults.rendered);
19110
19177
  for (const r of results) {
19111
19178
  if (r.action !== "skipped" && r.action !== "error") {
19112
19179
  updated.push(r.dest);
@@ -19173,20 +19240,19 @@ async function updateCommand(options) {
19173
19240
  for (const s of updateResults.skills) {
19174
19241
  newSkillsLock[s.id] = s.latestVersion;
19175
19242
  }
19176
- let lockDest = resolve17(basePath, ".codeconductor", "skills-lock.json");
19243
+ let lockRel = join11(".codeconductor", "skills-lock.json");
19177
19244
  try {
19178
19245
  const statAgents = await stat3(resolve17(basePath, ".agents"));
19179
19246
  if (statAgents.isDirectory()) {
19180
19247
  const statCodeConductor = await stat3(resolve17(basePath, ".codeconductor")).catch(() => null);
19181
19248
  if (!statCodeConductor) {
19182
- lockDest = resolve17(basePath, ".agents", "skills-lock.json");
19249
+ lockRel = join11(".agents", "skills-lock.json");
19183
19250
  }
19184
19251
  }
19185
19252
  } catch {}
19186
19253
  try {
19187
- await mkdir8(dirname8(lockDest), { recursive: true });
19188
- await writeFile7(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
19189
- updated.push(lockDest);
19254
+ const written = await writeContainedFile(basePath, lockRel, JSON.stringify(newSkillsLock, null, 2), { force: true });
19255
+ updated.push(written);
19190
19256
  } catch (e) {
19191
19257
  throw new Error(`Failed to write skills-lock.json: ${e}`);
19192
19258
  }
@@ -19233,6 +19299,7 @@ async function updateCommand(options) {
19233
19299
  }
19234
19300
  }
19235
19301
  var init_update_command = __esm(() => {
19302
+ init_path_containment();
19236
19303
  init_config_loader();
19237
19304
  init_update_checker();
19238
19305
  init_file_copier();
@@ -19250,9 +19317,9 @@ __export(exports_migrate_command, {
19250
19317
  migrateCommand: () => migrateCommand,
19251
19318
  migratePermissionRules: () => migratePermissionRules
19252
19319
  });
19253
- import { readFile as readFile15, readdir as readdir3, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
19320
+ import { readFile as readFile15, readdir as readdir3, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
19254
19321
  import { homedir as homedir11 } from "node:os";
19255
- import { join as join11, resolve as resolve18 } from "node:path";
19322
+ import { join as join12, resolve as resolve18 } from "node:path";
19256
19323
  function migratePermissionRules(settings) {
19257
19324
  const permissions = settings.permissions;
19258
19325
  if (typeof permissions !== "object" || permissions === null) {
@@ -19299,7 +19366,7 @@ function resolveSettingsPath(options) {
19299
19366
  if (options.file)
19300
19367
  return resolve18(options.projectRoot, options.file);
19301
19368
  const base = options.global ? homedir11() : options.projectRoot;
19302
- return join11(base, ".claude", "settings.json");
19369
+ return join12(base, ".claude", "settings.json");
19303
19370
  }
19304
19371
  async function findOrphanedPromptVersions(basePath, isGlobal) {
19305
19372
  const found = [];
@@ -19309,7 +19376,7 @@ async function findOrphanedPromptVersions(basePath, isGlobal) {
19309
19376
  if (!installed)
19310
19377
  continue;
19311
19378
  const installPath = getTargetInstallationPath(target, basePath, isGlobal);
19312
- const promptsDir = join11(installPath, "prompts");
19379
+ const promptsDir = join12(installPath, "prompts");
19313
19380
  if (seenDirs.has(promptsDir))
19314
19381
  continue;
19315
19382
  seenDirs.add(promptsDir);
@@ -19324,7 +19391,7 @@ async function findOrphanedPromptVersions(basePath, isGlobal) {
19324
19391
  continue;
19325
19392
  if (entry.name === CURRENT_PRESET_VERSION)
19326
19393
  continue;
19327
- found.push({ target, path: join11(promptsDir, entry.name), version: entry.name });
19394
+ found.push({ target, path: join12(promptsDir, entry.name), version: entry.name });
19328
19395
  }
19329
19396
  }
19330
19397
  return found;
@@ -19368,7 +19435,7 @@ async function migrateCommand(options) {
19368
19435
  duplicatesRemoved = migration.duplicatesRemoved;
19369
19436
  settingsChanged = migration.changes.length > 0 || migration.duplicatesRemoved.length > 0;
19370
19437
  if (settingsChanged && !options.dryRun) {
19371
- await writeFile8(settingsPath, `${JSON.stringify(migration.settings, null, 2)}
19438
+ await writeFile7(settingsPath, `${JSON.stringify(migration.settings, null, 2)}
19372
19439
  `, "utf-8");
19373
19440
  }
19374
19441
  }
@@ -19606,7 +19673,7 @@ __export(exports_help_command, {
19606
19673
  helpCommand: () => helpCommand
19607
19674
  });
19608
19675
  import { readdir as readdir4 } from "node:fs/promises";
19609
- import { join as join12 } from "node:path";
19676
+ import { join as join13 } from "node:path";
19610
19677
  async function scanPresetDir(projectRoot, target) {
19611
19678
  const inventory = {
19612
19679
  target,
@@ -19615,12 +19682,12 @@ async function scanPresetDir(projectRoot, target) {
19615
19682
  agents: [],
19616
19683
  workflows: []
19617
19684
  };
19618
- const presetDir = join12(projectRoot, "presets", target);
19685
+ const presetDir = join13(projectRoot, "presets", target);
19619
19686
  try {
19620
19687
  const entries = await readdir4(presetDir, { withFileTypes: true });
19621
19688
  for (const entry of entries) {
19622
19689
  if (entry.isDirectory()) {
19623
- const subDir = join12(presetDir, entry.name);
19690
+ const subDir = join13(presetDir, entry.name);
19624
19691
  const subEntries = await readdir4(subDir, { withFileTypes: true });
19625
19692
  for (const sub of subEntries) {
19626
19693
  if (sub.isFile()) {
@@ -19746,14 +19813,14 @@ var exports_debt_harvest_command = {};
19746
19813
  __export(exports_debt_harvest_command, {
19747
19814
  debtHarvestCommand: () => debtHarvestCommand
19748
19815
  });
19749
- import { readdir as readdir5, readFile as readFile16, writeFile as writeFile9, mkdir as mkdir9 } from "node:fs/promises";
19750
- import { join as join13, relative as relative6, extname } from "node:path";
19816
+ import { readdir as readdir5, readFile as readFile16, writeFile as writeFile8, mkdir as mkdir8 } from "node:fs/promises";
19817
+ import { join as join14, relative as relative6, extname } from "node:path";
19751
19818
  async function walkDir(dir, extensions) {
19752
19819
  const results = [];
19753
19820
  try {
19754
19821
  const entries = await readdir5(dir, { withFileTypes: true });
19755
19822
  for (const entry of entries) {
19756
- const fullPath = join13(dir, entry.name);
19823
+ const fullPath = join14(dir, entry.name);
19757
19824
  if (entry.isDirectory()) {
19758
19825
  if (entry.name.startsWith(".") || entry.name === "node_modules")
19759
19826
  continue;
@@ -19820,7 +19887,7 @@ function renderLedger(entries) {
19820
19887
  async function debtHarvestCommand(options) {
19821
19888
  const { projectRoot, dir = "src", output } = options;
19822
19889
  try {
19823
- const targetDir = join13(projectRoot, dir);
19890
+ const targetDir = join14(projectRoot, dir);
19824
19891
  const files = await walkDir(targetDir, SOURCE_EXTENSIONS);
19825
19892
  const allEntries = [];
19826
19893
  for (const file of files) {
@@ -19828,11 +19895,11 @@ async function debtHarvestCommand(options) {
19828
19895
  const entries = extractDefers(content, relative6(projectRoot, file));
19829
19896
  allEntries.push(...entries);
19830
19897
  }
19831
- const ledgerDir = join13(projectRoot, ".codeconductor");
19832
- await mkdir9(ledgerDir, { recursive: true });
19833
- const ledgerPath = join13(ledgerDir, "debt-ledger.md");
19898
+ const ledgerDir = join14(projectRoot, ".codeconductor");
19899
+ await mkdir8(ledgerDir, { recursive: true });
19900
+ const ledgerPath = join14(ledgerDir, "debt-ledger.md");
19834
19901
  const ledgerContent = renderLedger(allEntries);
19835
- await writeFile9(ledgerPath, ledgerContent, "utf-8");
19902
+ await writeFile8(ledgerPath, ledgerContent, "utf-8");
19836
19903
  if (output === "json") {
19837
19904
  return {
19838
19905
  code: 0,
@@ -20265,27 +20332,27 @@ var init_goal_planner = __esm(() => {
20265
20332
  });
20266
20333
 
20267
20334
  // src/core/product-graph/paths.ts
20268
- import { join as join14 } from "node:path";
20335
+ import { join as join15 } from "node:path";
20269
20336
  function productGraphPath(projectRoot) {
20270
- return join14(projectRoot, PRODUCT_DIR, PRODUCT_GRAPH_FILE);
20337
+ return join15(projectRoot, PRODUCT_DIR, PRODUCT_GRAPH_FILE);
20271
20338
  }
20272
20339
  function productMetaPath(projectRoot) {
20273
- return join14(projectRoot, PRODUCT_DIR, PRODUCT_META_FILE);
20340
+ return join15(projectRoot, PRODUCT_DIR, PRODUCT_META_FILE);
20274
20341
  }
20275
20342
  function eventsPath(projectRoot) {
20276
- return join14(projectRoot, PRODUCT_DIR, EVENTS_FILE);
20343
+ return join15(projectRoot, PRODUCT_DIR, EVENTS_FILE);
20277
20344
  }
20278
20345
  function operationalStatePath(projectRoot) {
20279
- return join14(projectRoot, PRODUCT_DIR, OPERATIONAL_STATE_FILE);
20346
+ return join15(projectRoot, PRODUCT_DIR, OPERATIONAL_STATE_FILE);
20280
20347
  }
20281
20348
  function strategicPath(projectRoot) {
20282
- return join14(projectRoot, PRODUCT_DIR, STRATEGIC_FILE);
20349
+ return join15(projectRoot, PRODUCT_DIR, STRATEGIC_FILE);
20283
20350
  }
20284
20351
  function productReportPath(projectRoot) {
20285
- return join14(projectRoot, PRODUCT_DIR, PRODUCT_REPORT_FILE);
20352
+ return join15(projectRoot, PRODUCT_DIR, PRODUCT_REPORT_FILE);
20286
20353
  }
20287
20354
  function evidenceDir(projectRoot) {
20288
- return join14(projectRoot, PRODUCT_DIR, EVIDENCE_DIR);
20355
+ return join15(projectRoot, PRODUCT_DIR, EVIDENCE_DIR);
20289
20356
  }
20290
20357
  var PRODUCT_DIR = ".codeconductor", PRODUCT_GRAPH_FILE = "product-graph.json", PRODUCT_META_FILE = "product-meta.json", EVENTS_FILE = "events.jsonl", OPERATIONAL_STATE_FILE = "operational-state.json", STRATEGIC_FILE = "strategic.json", PRODUCT_REPORT_FILE = "product-report.md", EVIDENCE_DIR = "evidence";
20291
20358
  var init_paths = () => {};
@@ -20302,8 +20369,8 @@ __export(exports_graph_store, {
20302
20369
  saveGraph: () => saveGraph,
20303
20370
  upsertNode: () => upsertNode
20304
20371
  });
20305
- import { mkdir as mkdir10, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
20306
- import { dirname as dirname9 } from "node:path";
20372
+ import { mkdir as mkdir9, readFile as readFile17, writeFile as writeFile9 } from "node:fs/promises";
20373
+ import { dirname as dirname8 } from "node:path";
20307
20374
  async function loadGraph(projectRoot) {
20308
20375
  try {
20309
20376
  const raw = await readFile17(productGraphPath(projectRoot), "utf-8");
@@ -20319,8 +20386,8 @@ async function saveGraph(projectRoot, graph) {
20319
20386
  try {
20320
20387
  const validated = ProductGraphSchema.parse(graph);
20321
20388
  const path = productGraphPath(projectRoot);
20322
- await mkdir10(dirname9(path), { recursive: true });
20323
- await writeFile10(path, JSON.stringify(validated, null, 2), "utf-8");
20389
+ await mkdir9(dirname8(path), { recursive: true });
20390
+ await writeFile9(path, JSON.stringify(validated, null, 2), "utf-8");
20324
20391
  return ok(undefined);
20325
20392
  } catch (e) {
20326
20393
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -20488,7 +20555,7 @@ __export(exports_goal_state, {
20488
20555
  markTaskBlocked: () => markTaskBlocked,
20489
20556
  writeGoal: () => writeGoal
20490
20557
  });
20491
- import { mkdir as mkdir11, readFile as readFile18, writeFile as writeFile11 } from "node:fs/promises";
20558
+ import { mkdir as mkdir10, readFile as readFile18, writeFile as writeFile10 } from "node:fs/promises";
20492
20559
  import { resolve as resolve19 } from "node:path";
20493
20560
  function validateGoalGraph2(graph) {
20494
20561
  const ids = new Set(graph.tasks.map((t) => t.id));
@@ -20533,9 +20600,9 @@ async function writeGoal(projectRoot, graph) {
20533
20600
  if (!validation.success)
20534
20601
  return validation;
20535
20602
  const dir = resolve19(projectRoot, ".codeconductor");
20536
- await mkdir11(dir, { recursive: true });
20603
+ await mkdir10(dir, { recursive: true });
20537
20604
  const yaml = $stringify(graph);
20538
- await writeFile11(resolve19(dir, "current-goal.yml"), yaml, "utf-8");
20605
+ await writeFile10(resolve19(dir, "current-goal.yml"), yaml, "utf-8");
20539
20606
  return ok(undefined);
20540
20607
  }
20541
20608
  async function loadGoal(projectRoot) {
@@ -20929,7 +20996,7 @@ function slugify(text) {
20929
20996
 
20930
20997
  // src/core/knowledge/source-parsers.ts
20931
20998
  import { readFile as readFile20 } from "node:fs/promises";
20932
- import { basename as basename6, join as join15, relative as relative7, sep as sep5 } from "node:path";
20999
+ import { basename as basename6, join as join16, relative as relative7, sep as sep6 } from "node:path";
20933
21000
  async function parseAdrFile(filePath) {
20934
21001
  try {
20935
21002
  const content = await readFile20(filePath, "utf-8");
@@ -20981,7 +21048,7 @@ function adrToEntities(adr, source) {
20981
21048
  }
20982
21049
  async function parseReadmeEntities(projectRoot, productName) {
20983
21050
  try {
20984
- const content = await readFile20(join15(projectRoot, "README.md"), "utf-8");
21051
+ const content = await readFile20(join16(projectRoot, "README.md"), "utf-8");
20985
21052
  const firstPara = content.split(`
20986
21053
 
20987
21054
  `).find((p) => p.trim() && !p.startsWith("#") && !p.startsWith(">")) ?? "";
@@ -21002,7 +21069,7 @@ async function parseReadmeEntities(projectRoot, productName) {
21002
21069
  }
21003
21070
  async function parseGraphifyEntities(projectRoot) {
21004
21071
  try {
21005
- const raw = await readFile20(join15(projectRoot, "graphify-out", "graph.json"), "utf-8");
21072
+ const raw = await readFile20(join16(projectRoot, "graphify-out", "graph.json"), "utf-8");
21006
21073
  const graph = JSON.parse(raw);
21007
21074
  const entities = [];
21008
21075
  for (const node2 of graph.nodes ?? []) {
@@ -21026,14 +21093,14 @@ async function parseGraphifyEntities(projectRoot) {
21026
21093
  async function scanSrcComponents(projectRoot) {
21027
21094
  const { readdir: readdir6 } = await import("node:fs/promises");
21028
21095
  const entities = [];
21029
- const srcDir = join15(projectRoot, "src");
21096
+ const srcDir = join16(projectRoot, "src");
21030
21097
  async function walk(dir, prefix) {
21031
21098
  try {
21032
21099
  const entries = await readdir6(dir, { withFileTypes: true });
21033
21100
  for (const entry of entries) {
21034
21101
  if (entry.name.startsWith("."))
21035
21102
  continue;
21036
- const full = join15(dir, entry.name);
21103
+ const full = join16(dir, entry.name);
21037
21104
  if (entry.isDirectory()) {
21038
21105
  if (entry.name === "node_modules")
21039
21106
  continue;
@@ -21045,7 +21112,7 @@ async function scanSrcComponents(projectRoot) {
21045
21112
  source: "src/",
21046
21113
  confidence: "medium",
21047
21114
  relations: [],
21048
- data: { path: relative7(projectRoot, full).split(sep5).join("/") }
21115
+ data: { path: relative7(projectRoot, full).split(sep6).join("/") }
21049
21116
  });
21050
21117
  await walk(full, prefix ? `${prefix}-${domainSlug}` : domainSlug);
21051
21118
  }
@@ -21058,7 +21125,7 @@ async function scanSrcComponents(projectRoot) {
21058
21125
  var init_source_parsers = () => {};
21059
21126
 
21060
21127
  // src/core/memory/episodic-store.ts
21061
- import { appendFile, mkdir as mkdir12, readFile as readFile21 } from "node:fs/promises";
21128
+ import { appendFile, mkdir as mkdir11, readFile as readFile21 } from "node:fs/promises";
21062
21129
  import { resolve as resolve21 } from "node:path";
21063
21130
  async function appendEvent(projectRoot, event) {
21064
21131
  try {
@@ -21067,7 +21134,7 @@ async function appendEvent(projectRoot, event) {
21067
21134
  id: event.id ?? `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
21068
21135
  });
21069
21136
  const path = eventsPath(projectRoot);
21070
- await mkdir12(resolve21(projectRoot, ".codeconductor"), { recursive: true });
21137
+ await mkdir11(resolve21(projectRoot, ".codeconductor"), { recursive: true });
21071
21138
  await appendFile(path, JSON.stringify(full) + `
21072
21139
  `, "utf-8");
21073
21140
  return ok(full);
@@ -21106,8 +21173,8 @@ var init_episodic_store = __esm(() => {
21106
21173
  // src/core/knowledge/ingest-pipeline.ts
21107
21174
  import { createHash as createHash3 } from "node:crypto";
21108
21175
  import { readFile as readFile22 } from "node:fs/promises";
21109
- import { join as join16, relative as relative8, sep as sep6 } from "node:path";
21110
- import { mkdir as mkdir13, writeFile as writeFile12 } from "node:fs/promises";
21176
+ import { join as join17, relative as relative8, sep as sep7 } from "node:path";
21177
+ import { mkdir as mkdir12, writeFile as writeFile11 } from "node:fs/promises";
21111
21178
  async function mapLimit(items, limit, fn) {
21112
21179
  if (items.length === 0)
21113
21180
  return [];
@@ -21142,7 +21209,7 @@ async function collectDeferRisks(projectRoot) {
21142
21209
  for (const entry of entries) {
21143
21210
  if (entry.name.startsWith(".") || entry.name === "node_modules")
21144
21211
  continue;
21145
- const full = join16(dir, entry.name);
21212
+ const full = join17(dir, entry.name);
21146
21213
  if (entry.isDirectory()) {
21147
21214
  await walk(full);
21148
21215
  } else if (entry.name.endsWith(".ts")) {
@@ -21151,11 +21218,11 @@ async function collectDeferRisks(projectRoot) {
21151
21218
  }
21152
21219
  } catch {}
21153
21220
  }
21154
- await walk(join16(projectRoot, "src"));
21221
+ await walk(join17(projectRoot, "src"));
21155
21222
  const perFile = await mapLimit(files, WALK_READ_CONCURRENCY, async (full) => {
21156
21223
  const entities = [];
21157
21224
  const content = await readFile22(full, "utf-8");
21158
- const rel = relative8(projectRoot, full).split(sep6).join("/");
21225
+ const rel = relative8(projectRoot, full).split(sep7).join("/");
21159
21226
  const deferRe = new RegExp(DEFER_REGEX_SOURCE, "gm");
21160
21227
  let match;
21161
21228
  while ((match = deferRe.exec(content)) !== null) {
@@ -21214,12 +21281,12 @@ async function parseBacklogEntities(projectRoot) {
21214
21281
  }
21215
21282
  async function parseAdrEntities(projectRoot) {
21216
21283
  const { readdir: readdir6 } = await import("node:fs/promises");
21217
- const adrDir = join16(projectRoot, "docs", "adr");
21284
+ const adrDir = join17(projectRoot, "docs", "adr");
21218
21285
  const entities = [];
21219
21286
  try {
21220
21287
  const files = (await readdir6(adrDir)).filter((file) => file.endsWith(".md"));
21221
21288
  const parsed = await Promise.all(files.map(async (file) => {
21222
- const adr = await parseAdrFile(join16(adrDir, file));
21289
+ const adr = await parseAdrFile(join17(adrDir, file));
21223
21290
  return adr ? adrToEntities(adr, `docs/adr/${file}`) : [];
21224
21291
  }));
21225
21292
  entities.push(...parsed.flat());
@@ -21311,7 +21378,7 @@ async function runIngest(projectRoot, productName) {
21311
21378
  await saveGraph2(projectRoot, graph);
21312
21379
  const sourceHashes = {};
21313
21380
  for (const src of ["README.md", "BACKLOG.md", "ROADMAP.md"]) {
21314
- const h = await hashFile(join16(projectRoot, src));
21381
+ const h = await hashFile(join17(projectRoot, src));
21315
21382
  if (h)
21316
21383
  sourceHashes[src] = h;
21317
21384
  }
@@ -21321,8 +21388,8 @@ async function runIngest(projectRoot, productName) {
21321
21388
  lastIngestAt: new Date().toISOString(),
21322
21389
  sourceHashes
21323
21390
  };
21324
- await mkdir13(join16(projectRoot, ".codeconductor"), { recursive: true });
21325
- await writeFile12(productMetaPath(projectRoot), JSON.stringify(ProductMetaSchema.parse(meta), null, 2), "utf-8");
21391
+ await mkdir12(join17(projectRoot, ".codeconductor"), { recursive: true });
21392
+ await writeFile11(productMetaPath(projectRoot), JSON.stringify(ProductMetaSchema.parse(meta), null, 2), "utf-8");
21326
21393
  await appendEvent(projectRoot, {
21327
21394
  type: "ingest.completed",
21328
21395
  timestamp: new Date().toISOString(),
@@ -21403,7 +21470,7 @@ var init_ingest_command = __esm(() => {
21403
21470
  });
21404
21471
 
21405
21472
  // src/core/memory/operational-state.ts
21406
- import { mkdir as mkdir14, readFile as readFile23, writeFile as writeFile13 } from "node:fs/promises";
21473
+ import { mkdir as mkdir13, readFile as readFile23, writeFile as writeFile12 } from "node:fs/promises";
21407
21474
  import { resolve as resolve22 } from "node:path";
21408
21475
  async function loadOperationalState(projectRoot) {
21409
21476
  try {
@@ -21429,8 +21496,8 @@ async function saveOperationalState(projectRoot, state) {
21429
21496
  updatedAt: new Date().toISOString()
21430
21497
  });
21431
21498
  const path = operationalStatePath(projectRoot);
21432
- await mkdir14(resolve22(projectRoot, ".codeconductor"), { recursive: true });
21433
- await writeFile13(path, JSON.stringify(validated, null, 2), "utf-8");
21499
+ await mkdir13(resolve22(projectRoot, ".codeconductor"), { recursive: true });
21500
+ await writeFile12(path, JSON.stringify(validated, null, 2), "utf-8");
21434
21501
  return ok(undefined);
21435
21502
  } catch (e) {
21436
21503
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -21464,7 +21531,7 @@ var init_operational_state = __esm(() => {
21464
21531
  });
21465
21532
 
21466
21533
  // src/core/memory/strategic-memory.ts
21467
- import { mkdir as mkdir15, readFile as readFile24, writeFile as writeFile14 } from "node:fs/promises";
21534
+ import { mkdir as mkdir14, readFile as readFile24, writeFile as writeFile13 } from "node:fs/promises";
21468
21535
  import { resolve as resolve23 } from "node:path";
21469
21536
  async function loadStrategicMemory(projectRoot) {
21470
21537
  try {
@@ -21489,8 +21556,8 @@ async function saveStrategicMemory(projectRoot, memory) {
21489
21556
  updatedAt: new Date().toISOString()
21490
21557
  });
21491
21558
  const path = strategicPath(projectRoot);
21492
- await mkdir15(resolve23(projectRoot, ".codeconductor"), { recursive: true });
21493
- await writeFile14(path, JSON.stringify(validated, null, 2), "utf-8");
21559
+ await mkdir14(resolve23(projectRoot, ".codeconductor"), { recursive: true });
21560
+ await writeFile13(path, JSON.stringify(validated, null, 2), "utf-8");
21494
21561
  return ok(undefined);
21495
21562
  } catch (e) {
21496
21563
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -21502,7 +21569,7 @@ var init_strategic_memory = __esm(() => {
21502
21569
  });
21503
21570
 
21504
21571
  // src/core/memory/memory-index.ts
21505
- import { mkdir as mkdir16, readFile as readFile25, writeFile as writeFile15 } from "node:fs/promises";
21572
+ import { mkdir as mkdir15, readFile as readFile25, writeFile as writeFile14 } from "node:fs/promises";
21506
21573
  import { resolve as resolve24 } from "node:path";
21507
21574
  function parseMarkdown(content) {
21508
21575
  const startIdx = content.indexOf(SENTINEL_START);
@@ -21540,12 +21607,12 @@ var init_memory_index = __esm(() => {
21540
21607
  });
21541
21608
 
21542
21609
  // src/core/evaluation/outcome-store.ts
21543
- import { appendFile as appendFile2, mkdir as mkdir17, readFile as readFile26, readdir as readdir6, writeFile as writeFile16 } from "node:fs/promises";
21610
+ import { appendFile as appendFile2, mkdir as mkdir16, readFile as readFile26, readdir as readdir6, writeFile as writeFile15 } from "node:fs/promises";
21544
21611
  import { resolve as resolve25 } from "node:path";
21545
21612
  async function ensureEvaluationDir(projectRoot) {
21546
21613
  const dir = resolve25(projectRoot, EVAL_DIR);
21547
- await mkdir17(dir, { recursive: true });
21548
- await mkdir17(resolve25(dir, SCORECARDS_DIR), { recursive: true });
21614
+ await mkdir16(dir, { recursive: true });
21615
+ await mkdir16(resolve25(dir, SCORECARDS_DIR), { recursive: true });
21549
21616
  return dir;
21550
21617
  }
21551
21618
  async function appendOutcome(projectRoot, outcome) {
@@ -21561,7 +21628,7 @@ async function appendOutcome(projectRoot, outcome) {
21561
21628
  index = EvaluationIndexSchema.parse(JSON.parse(raw));
21562
21629
  index.lastOutcomeId = validated.id;
21563
21630
  } catch {}
21564
- await writeFile16(indexPath, JSON.stringify(index, null, 2), "utf-8");
21631
+ await writeFile15(indexPath, JSON.stringify(index, null, 2), "utf-8");
21565
21632
  return ok(undefined);
21566
21633
  } catch (e) {
21567
21634
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -21609,7 +21676,7 @@ async function saveScorecard(projectRoot, record) {
21609
21676
  const dir = await ensureEvaluationDir(projectRoot);
21610
21677
  const validated = ScorecardRecordSchema.parse(record);
21611
21678
  const filePath = resolve25(dir, SCORECARDS_DIR, `${validated.id}.json`);
21612
- await writeFile16(filePath, JSON.stringify(validated, null, 2), "utf-8");
21679
+ await writeFile15(filePath, JSON.stringify(validated, null, 2), "utf-8");
21613
21680
  return ok(filePath);
21614
21681
  } catch (e) {
21615
21682
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -21746,10 +21813,10 @@ var init_outcome_store = __esm(() => {
21746
21813
 
21747
21814
  // src/core/feedback/feedback-ingestor.ts
21748
21815
  import { readFile as readFile27 } from "node:fs/promises";
21749
- import { join as join17 } from "node:path";
21816
+ import { join as join18 } from "node:path";
21750
21817
  async function parseChangelogLearnings(projectRoot) {
21751
21818
  try {
21752
- const content = await readFile27(join17(projectRoot, "CHANGELOG.md"), "utf-8");
21819
+ const content = await readFile27(join18(projectRoot, "CHANGELOG.md"), "utf-8");
21753
21820
  const unreleased = content.match(/## \[Unreleased\]([\s\S]*?)(?=## \[|\n## )/i);
21754
21821
  if (!unreleased)
21755
21822
  return [];
@@ -21869,7 +21936,7 @@ var exports_product_command = {};
21869
21936
  __export(exports_product_command, {
21870
21937
  productCommand: () => productCommand
21871
21938
  });
21872
- import { writeFile as writeFile17 } from "node:fs/promises";
21939
+ import { writeFile as writeFile16 } from "node:fs/promises";
21873
21940
  async function productCommand(options) {
21874
21941
  const { subcommand, projectRoot, output, query, from, to, since, format } = options;
21875
21942
  try {
@@ -22072,7 +22139,7 @@ async function handleExport(projectRoot, format, output) {
22072
22139
  ...queryNodes(graph.data, "risk").map((n) => `- ${n.name}`)
22073
22140
  ].join(`
22074
22141
  `);
22075
- await writeFile17(productReportPath(projectRoot), md, "utf-8");
22142
+ await writeFile16(productReportPath(projectRoot), md, "utf-8");
22076
22143
  return {
22077
22144
  code: 0,
22078
22145
  data: {
@@ -22354,21 +22421,21 @@ __export(exports_rdd_receipt, {
22354
22421
  });
22355
22422
  import { createHash as createHash4 } from "node:crypto";
22356
22423
  import { readdir as readdir7, readFile as readFile28 } from "node:fs/promises";
22357
- import { join as join18, relative as relative9, resolve as resolve26, sep as sep7 } from "node:path";
22424
+ import { join as join19, relative as relative9, resolve as resolve26, sep as sep8 } from "node:path";
22358
22425
  function sha2562(content) {
22359
22426
  return createHash4("sha256").update(content).digest("hex");
22360
22427
  }
22361
22428
  function canonicalPath(projectRoot, input) {
22362
22429
  const root = resolve26(projectRoot);
22363
22430
  const absolute = resolve26(root, input);
22364
- if (absolute !== root && !absolute.startsWith(`${root}${sep7}`)) {
22431
+ if (absolute !== root && !absolute.startsWith(`${root}${sep8}`)) {
22365
22432
  throw new Error(`RDD receipt path "${input}" is outside the project root`);
22366
22433
  }
22367
22434
  const normalized = relative9(root, absolute);
22368
22435
  if (!normalized || normalized === ".") {
22369
22436
  throw new Error("RDD receipts require file paths, not the project root");
22370
22437
  }
22371
- return { absolute, relative: normalized.split(sep7).join("/") };
22438
+ return { absolute, relative: normalized.split(sep8).join("/") };
22372
22439
  }
22373
22440
  async function fingerprint(projectRoot, paths) {
22374
22441
  const deduplicated = new Map;
@@ -22419,11 +22486,11 @@ async function collectReceiptPaths(projectRoot) {
22419
22486
  for (const entry of entries) {
22420
22487
  if (entry.isDirectory()) {
22421
22488
  if (!EXCLUDED_DIRECTORIES.has(entry.name))
22422
- await visit2(join18(current, entry.name));
22489
+ await visit2(join19(current, entry.name));
22423
22490
  continue;
22424
22491
  }
22425
22492
  if (entry.isFile())
22426
- paths.push(relative9(root, join18(current, entry.name)).split(sep7).join("/"));
22493
+ paths.push(relative9(root, join19(current, entry.name)).split(sep8).join("/"));
22427
22494
  }
22428
22495
  }
22429
22496
  await visit2(root);
@@ -22476,8 +22543,8 @@ var init_rdd_receipt = __esm(() => {
22476
22543
  // src/core/verification/verification-runner.ts
22477
22544
  import { existsSync as existsSync3 } from "node:fs";
22478
22545
  import { readFile as readFile29 } from "node:fs/promises";
22479
- import { dirname as dirname10, join as join19, resolve as resolve27 } from "node:path";
22480
- import { mkdir as mkdir18, readdir as readdir8, writeFile as writeFile18 } from "node:fs/promises";
22546
+ import { dirname as dirname9, join as join20, resolve as resolve27 } from "node:path";
22547
+ import { mkdir as mkdir17, readdir as readdir8, writeFile as writeFile17 } from "node:fs/promises";
22481
22548
  function isPassingEvidence(ev) {
22482
22549
  switch (ev.type) {
22483
22550
  case "verification":
@@ -22504,7 +22571,7 @@ async function isCurrentPassingEvidence(projectRoot, ev) {
22504
22571
  }
22505
22572
  function evidenceFilePath(evDir, id) {
22506
22573
  const path = resolve27(evDir, `${id.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
22507
- if (dirname10(path) !== resolve27(evDir)) {
22574
+ if (dirname9(path) !== resolve27(evDir)) {
22508
22575
  return err(new Error(`Refusing to write evidence for "${id}" outside ${evDir}`));
22509
22576
  }
22510
22577
  return ok(path);
@@ -22515,8 +22582,8 @@ async function persistEvidence(projectRoot, evidence) {
22515
22582
  if (!evidencePath.success)
22516
22583
  return evidencePath;
22517
22584
  try {
22518
- await mkdir18(evDir, { recursive: true });
22519
- await writeFile18(evidencePath.data, JSON.stringify(evidence, null, 2), "utf-8");
22585
+ await mkdir17(evDir, { recursive: true });
22586
+ await writeFile17(evidencePath.data, JSON.stringify(evidence, null, 2), "utf-8");
22520
22587
  } catch (e) {
22521
22588
  return err(e instanceof Error ? e : new Error(String(e)));
22522
22589
  }
@@ -22538,7 +22605,7 @@ async function collectTaskEvidence(projectRoot, taskId) {
22538
22605
  if (!file.endsWith(".json"))
22539
22606
  continue;
22540
22607
  try {
22541
- const raw = await readFile29(join19(evDir, file), "utf-8");
22608
+ const raw = await readFile29(join20(evDir, file), "utf-8");
22542
22609
  const ev = EvidenceSchema.parse(JSON.parse(raw));
22543
22610
  if (ev.relatedTask === taskId)
22544
22611
  records.push(ev);
@@ -23917,13 +23984,13 @@ __export(exports_rdd_command, {
23917
23984
  });
23918
23985
  import { execFile as execFile4 } from "node:child_process";
23919
23986
  import { existsSync as existsSync4 } from "node:fs";
23920
- import { mkdir as mkdir19, readFile as readFile30, readdir as readdir9, rename as rename3, writeFile as writeFile19, chmod } from "node:fs/promises";
23921
- import { dirname as dirname11, resolve as resolve28 } from "node:path";
23987
+ import { mkdir as mkdir18, readFile as readFile30, readdir as readdir9, rename as rename3, writeFile as writeFile18, chmod } from "node:fs/promises";
23988
+ import { dirname as dirname10, resolve as resolve28 } from "node:path";
23922
23989
  import { promisify as promisify4 } from "node:util";
23923
23990
  function receiptPath(projectRoot, id) {
23924
23991
  const directory = resolve28(evidenceDir(projectRoot));
23925
23992
  const path = resolve28(directory, `${id.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
23926
- if (dirname11(path) !== directory)
23993
+ if (dirname10(path) !== directory)
23927
23994
  throw new Error(`Invalid RDD receipt id: ${id}`);
23928
23995
  return path;
23929
23996
  }
@@ -23971,7 +24038,7 @@ async function installHooks(projectRoot) {
23971
24038
  encoding: "utf8"
23972
24039
  });
23973
24040
  const hooksDir = resolve28(projectRoot, stdout.trim());
23974
- await mkdir19(hooksDir, { recursive: true });
24041
+ await mkdir18(hooksDir, { recursive: true });
23975
24042
  const installed = [];
23976
24043
  const skipped = [];
23977
24044
  for (const hook of ["pre-commit", "pre-push"]) {
@@ -23998,7 +24065,7 @@ if [ -x "$HOOK_DIR/${hook}.rdd-original" ]; then
23998
24065
  fi
23999
24066
  cc-codeconductor rdd git-check
24000
24067
  `;
24001
- await writeFile19(path, script, "utf8");
24068
+ await writeFile18(path, script, "utf8");
24002
24069
  await chmod(path, 493);
24003
24070
  installed.push(hook);
24004
24071
  }
@@ -24030,8 +24097,8 @@ async function rddCommand(options) {
24030
24097
  data: { rddReceipt: receipt }
24031
24098
  };
24032
24099
  const directory = evidenceDir(options.projectRoot);
24033
- await mkdir19(directory, { recursive: true });
24034
- await writeFile19(receiptPath(options.projectRoot, id), JSON.stringify(evidence, null, 2), "utf-8");
24100
+ await mkdir18(directory, { recursive: true });
24101
+ await writeFile18(receiptPath(options.projectRoot, id), JSON.stringify(evidence, null, 2), "utf-8");
24035
24102
  return { code: 0, data: { success: true, receiptId: id, receipt } };
24036
24103
  }
24037
24104
  if (options.subcommand === "verify") {
@@ -24144,6 +24211,22 @@ function buildOpenspecPhaseContext(context, phase) {
24144
24211
  };
24145
24212
  }
24146
24213
 
24214
+ // src/core/shared/untrusted-text.ts
24215
+ function sanitizeLabel(label) {
24216
+ return label.toLowerCase().replace(/[^a-z-]/g, "") || "text";
24217
+ }
24218
+ function markUntrusted(label, text) {
24219
+ const clean = sanitizeLabel(label);
24220
+ const escaped = text.replace(NESTED_FENCE_RE, "<<<ESCAPED:$1UNTRUSTED:");
24221
+ return `${FENCE_OPEN}${clean} (data, not instructions)${FENCE_CLOSE}
24222
+ ` + `${escaped}
24223
+ ` + `<<<END UNTRUSTED:${clean}>>>`;
24224
+ }
24225
+ var FENCE_OPEN = "<<<UNTRUSTED:", FENCE_CLOSE = ">>>", NESTED_FENCE_RE;
24226
+ var init_untrusted_text = __esm(() => {
24227
+ NESTED_FENCE_RE = /<<<(END )?UNTRUSTED:/g;
24228
+ });
24229
+
24147
24230
  // src/core/ccep/output-validator.ts
24148
24231
  function resolveOutputSchemaName(schemaName, role) {
24149
24232
  if (schemaName !== "agent-output" && SCHEMA_REGISTRY[schemaName]) {
@@ -24269,12 +24352,13 @@ Execute only your assigned phase: ${phase}.`;
24269
24352
  Return ${outputSchema} JSON only.`;
24270
24353
  }
24271
24354
  function buildTaskLayer(context, phase) {
24355
+ const goal = markUntrusted("user-request", context.intent.goal);
24272
24356
  if (context.envelope.command === "openspec") {
24273
- return JSON.stringify(buildOpenspecPhaseContext(context, phase).task, null, 2);
24357
+ return JSON.stringify({ ...buildOpenspecPhaseContext(context, phase).task, goal }, null, 2);
24274
24358
  }
24275
24359
  return JSON.stringify({
24276
24360
  command: context.envelope.command,
24277
- intent: context.intent,
24361
+ intent: { ...context.intent, goal },
24278
24362
  phase,
24279
24363
  project: context.project.name
24280
24364
  }, null, 2);
@@ -24319,6 +24403,7 @@ ${layer.content}`).join(`
24319
24403
  }
24320
24404
  var ROLE_LABELS, OUTPUT_SCHEMAS, KNOWLEDGE_KEYS_BY_ROLE, PRODUCT_GRAPH_KNOWLEDGE_KEYS;
24321
24405
  var init_prompt_compiler = __esm(() => {
24406
+ init_untrusted_text();
24322
24407
  init_output_validator();
24323
24408
  ROLE_LABELS = {
24324
24409
  "task-coach": "Task Coach",
@@ -24330,7 +24415,12 @@ var init_prompt_compiler = __esm(() => {
24330
24415
  "contract-builder": "Contract Builder",
24331
24416
  "complexity-auditor": "Complexity Auditor",
24332
24417
  orchestrator: "Orchestrator",
24333
- "repo-explorer": "Repo Explorer"
24418
+ "repo-explorer": "Repo Explorer",
24419
+ product: "Product",
24420
+ delivery: "Delivery",
24421
+ "data-ops": "DataOps",
24422
+ "security-reviewer": "Security Reviewer",
24423
+ devil: "Devil"
24334
24424
  };
24335
24425
  OUTPUT_SCHEMAS = {
24336
24426
  "planner-output": `{
@@ -24403,7 +24493,12 @@ var init_prompt_compiler = __esm(() => {
24403
24493
  "contract-builder": ["productName", "domains", "decisions", "requirements"],
24404
24494
  "complexity-auditor": ["productName", "decisions", "requirements", "risks"],
24405
24495
  orchestrator: ["productName", "domains", "decisions", "requirements", "risks"],
24406
- "repo-explorer": ["productName", "domains", "requirements"]
24496
+ "repo-explorer": ["productName", "domains", "requirements"],
24497
+ product: ["productName", "requirements"],
24498
+ delivery: ["productName", "requirements", "risks"],
24499
+ "data-ops": ["productName", "domains", "requirements"],
24500
+ "security-reviewer": ["productName", "decisions", "requirements", "risks"],
24501
+ devil: ["decisions", "requirements", "risks"]
24407
24502
  };
24408
24503
  PRODUCT_GRAPH_KNOWLEDGE_KEYS = new Set([
24409
24504
  "productName",
@@ -24417,9 +24512,9 @@ var init_prompt_compiler = __esm(() => {
24417
24512
 
24418
24513
  // src/core/ccep/command-parser.ts
24419
24514
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
24420
- import { basename as basename7, join as join20 } from "node:path";
24515
+ import { basename as basename7, join as join21 } from "node:path";
24421
24516
  function readProjectId(projectRoot) {
24422
- const packageJsonPath = join20(projectRoot, "package.json");
24517
+ const packageJsonPath = join21(projectRoot, "package.json");
24423
24518
  if (existsSync5(packageJsonPath)) {
24424
24519
  try {
24425
24520
  const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -24449,7 +24544,7 @@ function parseCommand(command, userRequest, projectRoot) {
24449
24544
  maxVariance: "low"
24450
24545
  }
24451
24546
  };
24452
- const packageJsonPath = join20(projectRoot, "package.json");
24547
+ const packageJsonPath = join21(projectRoot, "package.json");
24453
24548
  if (existsSync5(packageJsonPath)) {
24454
24549
  try {
24455
24550
  const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -24505,9 +24600,9 @@ function resolveWorkflowPhase(profile, phaseId) {
24505
24600
  if (!phase) {
24506
24601
  return null;
24507
24602
  }
24508
- const role = phase.agent ?? phase.agents?.[0] ?? "orchestrator";
24603
+ const roles = phase.agent !== undefined ? [phase.agent] : phase.agents !== undefined && phase.agents.length > 0 ? [...phase.agents] : ["orchestrator"];
24509
24604
  const outputSchema = phase.outputSchema ?? profile.intakeSchema ?? "agent-output";
24510
- return { id: phase.id, role, outputSchema };
24605
+ return { id: phase.id, role: roles[0], roles, outputSchema };
24511
24606
  }
24512
24607
  function loadWorkflowProfile(command, projectRoot) {
24513
24608
  const fromYaml = loadWorkflowProfileFromYaml(command, projectRoot);
@@ -24524,8 +24619,8 @@ var init_workflow_profile_loader = __esm(() => {
24524
24619
  // src/core/evaluation/harness-catalog.ts
24525
24620
  import { createHash as createHash5 } from "node:crypto";
24526
24621
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
24527
- import { copyFile as copyFile2, mkdir as mkdir20, readFile as readFile31, readdir as readdir10, rm as rm4, writeFile as writeFile20 } from "node:fs/promises";
24528
- import { dirname as dirname12, join as join21, resolve as resolve29 } from "node:path";
24622
+ import { copyFile as copyFile2, mkdir as mkdir19, readFile as readFile31, readdir as readdir10, rm as rm4, writeFile as writeFile19 } from "node:fs/promises";
24623
+ import { dirname as dirname11, join as join22, resolve as resolve29 } from "node:path";
24529
24624
  function catalogIds(catalog = DEFAULT_HARNESS_CATALOG) {
24530
24625
  return catalog.components.map((c) => c.id);
24531
24626
  }
@@ -24648,22 +24743,22 @@ async function restoreOverlayBackup(projectRoot) {
24648
24743
  const backupRoot = resolve29(projectRoot, EVAL_DIR2, OVERLAY_BACKUP_DIR);
24649
24744
  if (!existsSync6(backupRoot))
24650
24745
  return;
24651
- const configBackup = join21(backupRoot, "config.yml");
24746
+ const configBackup = join22(backupRoot, "config.yml");
24652
24747
  const configDest = resolve29(projectRoot, ".codeconductor", "config.yml");
24653
24748
  if (existsSync6(configBackup)) {
24654
- await mkdir20(dirname12(configDest), { recursive: true });
24749
+ await mkdir19(dirname11(configDest), { recursive: true });
24655
24750
  await copyFile2(configBackup, configDest);
24656
24751
  }
24657
- const workflowsBackup = join21(backupRoot, "workflows");
24752
+ const workflowsBackup = join22(backupRoot, "workflows");
24658
24753
  const workflowsDest = resolve29(projectRoot, ".codeconductor", "workflows");
24659
24754
  if (existsSync6(workflowsBackup)) {
24660
- await mkdir20(workflowsDest, { recursive: true });
24755
+ await mkdir19(workflowsDest, { recursive: true });
24661
24756
  const files = await readdir10(workflowsBackup);
24662
24757
  for (const file of files) {
24663
- await copyFile2(join21(workflowsBackup, file), join21(workflowsDest, file));
24758
+ await copyFile2(join22(workflowsBackup, file), join22(workflowsDest, file));
24664
24759
  }
24665
24760
  }
24666
- const overlayBackup = join21(backupRoot, ACTIVE_OVERLAY_FILE);
24761
+ const overlayBackup = join22(backupRoot, ACTIVE_OVERLAY_FILE);
24667
24762
  const overlayDest = activeOverlayPath(projectRoot);
24668
24763
  if (existsSync6(overlayBackup)) {
24669
24764
  await copyFile2(overlayBackup, overlayDest);
@@ -24675,25 +24770,25 @@ async function snapshotForBackup(projectRoot) {
24675
24770
  const backupRoot = resolve29(projectRoot, EVAL_DIR2, OVERLAY_BACKUP_DIR);
24676
24771
  if (existsSync6(backupRoot))
24677
24772
  return;
24678
- await mkdir20(backupRoot, { recursive: true });
24773
+ await mkdir19(backupRoot, { recursive: true });
24679
24774
  const configPath = resolve29(projectRoot, ".codeconductor", "config.yml");
24680
24775
  if (existsSync6(configPath)) {
24681
- await copyFile2(configPath, join21(backupRoot, "config.yml"));
24776
+ await copyFile2(configPath, join22(backupRoot, "config.yml"));
24682
24777
  }
24683
24778
  const workflowsDir = resolve29(projectRoot, ".codeconductor", "workflows");
24684
24779
  if (existsSync6(workflowsDir)) {
24685
- const dest = join21(backupRoot, "workflows");
24686
- await mkdir20(dest, { recursive: true });
24780
+ const dest = join22(backupRoot, "workflows");
24781
+ await mkdir19(dest, { recursive: true });
24687
24782
  const files = await readdir10(workflowsDir);
24688
24783
  for (const file of files) {
24689
24784
  if (file.endsWith(".yml")) {
24690
- await copyFile2(join21(workflowsDir, file), join21(dest, file));
24785
+ await copyFile2(join22(workflowsDir, file), join22(dest, file));
24691
24786
  }
24692
24787
  }
24693
24788
  }
24694
24789
  const overlay = activeOverlayPath(projectRoot);
24695
24790
  if (existsSync6(overlay)) {
24696
- await copyFile2(overlay, join21(backupRoot, ACTIVE_OVERLAY_FILE));
24791
+ await copyFile2(overlay, join22(backupRoot, ACTIVE_OVERLAY_FILE));
24697
24792
  }
24698
24793
  }
24699
24794
  async function applyHarnessOverlay(projectRoot, disabled, options = {}) {
@@ -24703,12 +24798,12 @@ async function applyHarnessOverlay(projectRoot, disabled, options = {}) {
24703
24798
  await snapshotForBackup(projectRoot);
24704
24799
  }
24705
24800
  const workflowsDir = resolve29(projectRoot, ".codeconductor", "workflows");
24706
- await mkdir20(workflowsDir, { recursive: true });
24707
- await mkdir20(resolve29(projectRoot, EVAL_DIR2), { recursive: true });
24801
+ await mkdir19(workflowsDir, { recursive: true });
24802
+ await mkdir19(resolve29(projectRoot, EVAL_DIR2), { recursive: true });
24708
24803
  for (const command of WORKFLOW_OVERLAY_COMMANDS) {
24709
24804
  const base = loadWorkflowProfile(command);
24710
24805
  const next = applyCatalogToProfile(base, disabled);
24711
- await writeFile20(join21(workflowsDir, `${command}.yml`), $stringify(next), "utf-8");
24806
+ await writeFile19(join22(workflowsDir, `${command}.yml`), $stringify(next), "utf-8");
24712
24807
  }
24713
24808
  const needsConfig = disabled.includes("compile_loop") || disabled.includes("council");
24714
24809
  if (needsConfig) {
@@ -24736,7 +24831,7 @@ async function applyHarnessOverlay(projectRoot, disabled, options = {}) {
24736
24831
  contractVersion: options.contractVersion,
24737
24832
  ccepProfile: options.ccepProfile ?? "feature"
24738
24833
  });
24739
- await writeFile20(activeOverlayPath(projectRoot), $stringify(overlay), "utf-8");
24834
+ await writeFile19(activeOverlayPath(projectRoot), $stringify(overlay), "utf-8");
24740
24835
  return ok(overlay);
24741
24836
  } catch (e) {
24742
24837
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -24848,7 +24943,7 @@ var init_harness_catalog = __esm(() => {
24848
24943
 
24849
24944
  // src/core/openspec/openspec-state.ts
24850
24945
  import { createHash as createHash6 } from "node:crypto";
24851
- import { mkdir as mkdir21, readFile as readFile32, rename as rename4, writeFile as writeFile21 } from "node:fs/promises";
24946
+ import { mkdir as mkdir20, readFile as readFile32, rename as rename4, writeFile as writeFile20 } from "node:fs/promises";
24852
24947
  import { resolve as resolve30 } from "node:path";
24853
24948
  function canTransition(from, to) {
24854
24949
  return ALLOWED_TRANSITIONS[from]?.includes(to) ?? false;
@@ -24870,7 +24965,7 @@ function applyBacklogTransition(content, itemId, from, to, progress) {
24870
24965
  }
24871
24966
  async function writeFileAtomic(path, content) {
24872
24967
  const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
24873
- await writeFile21(tmp, content, "utf-8");
24968
+ await writeFile20(tmp, content, "utf-8");
24874
24969
  await rename4(tmp, path);
24875
24970
  }
24876
24971
  async function loadOpenspecState(projectRoot) {
@@ -24894,7 +24989,7 @@ async function loadOpenspecState(projectRoot) {
24894
24989
  async function writeOpenspecState(projectRoot, state) {
24895
24990
  try {
24896
24991
  const dir = resolve30(projectRoot, ".codeconductor");
24897
- await mkdir21(dir, { recursive: true });
24992
+ await mkdir20(dir, { recursive: true });
24898
24993
  const validated = OpenspecStateSchema.parse(state);
24899
24994
  await writeFileAtomic(resolve30(dir, "openspec-state.json"), JSON.stringify(validated, null, 2));
24900
24995
  return ok(undefined);
@@ -25002,7 +25097,7 @@ var init_openspec_state = __esm(() => {
25002
25097
  // src/core/ccep/context-resolver.ts
25003
25098
  import { existsSync as existsSync7 } from "node:fs";
25004
25099
  import { readFile as readFile33 } from "node:fs/promises";
25005
- import { join as join22 } from "node:path";
25100
+ import { join as join23 } from "node:path";
25006
25101
  async function loadProductKnowledge(projectRoot) {
25007
25102
  if (isProductGraphDisabled(projectRoot)) {
25008
25103
  return {};
@@ -25028,7 +25123,7 @@ async function loadProductKnowledge(projectRoot) {
25028
25123
  }
25029
25124
  async function resolveAstSource(projectRoot) {
25030
25125
  if (isProductGraphDisabled(projectRoot)) {
25031
- const graphifyPath2 = join22(projectRoot, "graphify-out", "graph.json");
25126
+ const graphifyPath2 = join23(projectRoot, "graphify-out", "graph.json");
25032
25127
  if (existsSync7(graphifyPath2)) {
25033
25128
  return { source: "graphify", confidence: "medium" };
25034
25129
  }
@@ -25038,7 +25133,7 @@ async function resolveAstSource(projectRoot) {
25038
25133
  if (existsSync7(graphPath)) {
25039
25134
  return { source: "product-graph", confidence: "high" };
25040
25135
  }
25041
- const graphifyPath = join22(projectRoot, "graphify-out", "graph.json");
25136
+ const graphifyPath = join23(projectRoot, "graphify-out", "graph.json");
25042
25137
  if (existsSync7(graphifyPath)) {
25043
25138
  return { source: "graphify", confidence: "medium" };
25044
25139
  }
@@ -25114,6 +25209,9 @@ function canonicalAgentId(agentId) {
25114
25209
  function isSecurityCategory(category) {
25115
25210
  return SECURITY_CATEGORIES.has(category.trim().toLowerCase());
25116
25211
  }
25212
+ function isSecurityEmitter(agentId) {
25213
+ return canonicalAgentId(agentId).includes("security");
25214
+ }
25117
25215
  function resolveQuorum(config, ballotCount) {
25118
25216
  if (typeof config.quorum === "number") {
25119
25217
  const required = Math.max(1, config.quorum);
@@ -25238,7 +25336,7 @@ function councilConsensus(verdicts, config = DEFAULT_CONFIG2) {
25238
25336
  if (confidence < 0.6) {
25239
25337
  hasLowConfidence = true;
25240
25338
  }
25241
- if (config.allowSecurityVeto && v.securityVeto && v.status === "REJECTED") {
25339
+ if (config.allowSecurityVeto && v.securityVeto && v.status === "REJECTED" && isSecurityEmitter(v.agentId)) {
25242
25340
  vetoApplied = true;
25243
25341
  vetoByAgentId = v.agentId;
25244
25342
  }
@@ -25248,7 +25346,7 @@ function councilConsensus(verdicts, config = DEFAULT_CONFIG2) {
25248
25346
  }
25249
25347
  for (const finding of v.findings) {
25250
25348
  allFindings.push(finding);
25251
- if (config.allowSecurityVeto && finding.severity === "critical" && isSecurityCategory(finding.category)) {
25349
+ if (config.allowSecurityVeto && finding.severity === "critical" && isSecurityCategory(finding.category) && isSecurityEmitter(v.agentId)) {
25252
25350
  vetoApplied = true;
25253
25351
  vetoByAgentId = v.agentId;
25254
25352
  }
@@ -25387,6 +25485,148 @@ var init_council_consensus = __esm(() => {
25387
25485
  ]);
25388
25486
  });
25389
25487
 
25488
+ // src/domain/council/council-spec.ts
25489
+ function defaultCouncilSpec() {
25490
+ return {
25491
+ name: "council",
25492
+ version: "0.1.0",
25493
+ description: "Multi-agent council",
25494
+ outputContract: "v1",
25495
+ agents: DEFAULT_COUNCIL_AGENTS
25496
+ };
25497
+ }
25498
+ function hasSecurityFocusedAgent(spec) {
25499
+ return spec.agents.some((agent) => {
25500
+ const haystack = [agent.id, agent.role, ...agent.focus].join(" ").toLowerCase();
25501
+ return SECURITY_HINTS.some((hint) => haystack.includes(hint));
25502
+ });
25503
+ }
25504
+ function deriveConsensusConfig(spec, overrides = {}) {
25505
+ const expectedAgentIds = overrides.expectedAgentIds ?? spec.agents.map((agent) => agent.id);
25506
+ const allowSecurityVeto = overrides.allowSecurityVeto ?? true;
25507
+ if (allowSecurityVeto && !hasSecurityFocusedAgent(spec)) {
25508
+ throw new Error("allowSecurityVeto requires at least one security-focused agent in the council roster");
25509
+ }
25510
+ return {
25511
+ algorithm: overrides.algorithm ?? "majority",
25512
+ allowSecurityVeto,
25513
+ allowComplianceVeto: overrides.allowComplianceVeto ?? true,
25514
+ expectedAgentIds,
25515
+ quorum: overrides.quorum ?? Math.ceil(expectedAgentIds.length / 2),
25516
+ criticalFindingsPolicy: overrides.criticalFindingsPolicy ?? "escalate",
25517
+ candidateHash: overrides.candidateHash
25518
+ };
25519
+ }
25520
+ function selectCouncilPanel(spec, request) {
25521
+ const type = request.type.trim().toLowerCase();
25522
+ const scope = request.scope.join(" ").toLowerCase();
25523
+ const selected = new Set(["delivery", "security-reviewer", "devil"]);
25524
+ const isFeature = type === "feature" || type === "api";
25525
+ const isStructural = ["feature", "api", "refactor", "migration", "database"].includes(type);
25526
+ if (isFeature || request.risk !== "low")
25527
+ selected.add("architect");
25528
+ if (isFeature || request.risk === "high")
25529
+ selected.add("product");
25530
+ if (DATA_HINTS.some((hint) => scope.includes(hint)) || ["migration", "database"].includes(type)) {
25531
+ selected.add("data-ops");
25532
+ }
25533
+ if (request.risk === "high" || SECURITY_HINTS.some((hint) => scope.includes(hint))) {
25534
+ selected.add("security-reviewer");
25535
+ }
25536
+ if (isStructural)
25537
+ selected.add("architect");
25538
+ const expectedAgentIds = spec.agents.map((agent) => agent.id).filter((id) => selected.has(id));
25539
+ return deriveConsensusConfig(spec, {
25540
+ expectedAgentIds,
25541
+ quorum: Math.ceil(expectedAgentIds.length / 2)
25542
+ });
25543
+ }
25544
+ var SECURITY_HINTS, DEFAULT_COUNCIL_AGENTS, DATA_HINTS;
25545
+ var init_council_spec = __esm(() => {
25546
+ SECURITY_HINTS = ["security", "auth", "injection", "credentials", "supply-chain"];
25547
+ DEFAULT_COUNCIL_AGENTS = [
25548
+ {
25549
+ id: "architect",
25550
+ role: "Architect",
25551
+ context: "repo-readonly",
25552
+ modelHint: "strong-reasoning",
25553
+ focus: ["architecture", "design-patterns", "code-structure"]
25554
+ },
25555
+ {
25556
+ id: "product",
25557
+ role: "Product",
25558
+ context: "prompt-only",
25559
+ modelHint: "balanced",
25560
+ focus: ["requirements", "ux", "business-value"]
25561
+ },
25562
+ {
25563
+ id: "delivery",
25564
+ role: "Delivery",
25565
+ context: "repo-readonly",
25566
+ modelHint: "practical-coding",
25567
+ focus: ["delivery", "testing", "deployment"]
25568
+ },
25569
+ {
25570
+ id: "data-ops",
25571
+ role: "DataOps",
25572
+ context: "repo-readonly",
25573
+ modelHint: "analytical",
25574
+ focus: ["data", "pipelines", "analytics"]
25575
+ },
25576
+ {
25577
+ id: "security-reviewer",
25578
+ role: "Security Reviewer",
25579
+ context: "repo-readonly",
25580
+ modelHint: "security-reasoning",
25581
+ focus: [
25582
+ "security",
25583
+ "vulnerabilities",
25584
+ "compliance",
25585
+ "credentials",
25586
+ "injection",
25587
+ "auth",
25588
+ "supply-chain"
25589
+ ]
25590
+ },
25591
+ {
25592
+ id: "devil",
25593
+ role: "Devil",
25594
+ context: "repo-readonly",
25595
+ modelHint: "adversarial",
25596
+ focus: ["review", "edge-cases", "failure-modes"]
25597
+ }
25598
+ ];
25599
+ DATA_HINTS = ["data", "database", "migration", "analytics", "pipeline", "sql"];
25600
+ });
25601
+
25602
+ // src/core/ccep/risk-classifier.ts
25603
+ function classifyRisk(input) {
25604
+ const type = (input.type ?? "").toLowerCase();
25605
+ const blob = [type, ...input.targetFiles ?? [], ...input.signals ?? []].join(" ");
25606
+ if (HIGH_RE.test(blob) || INFRA_RE.test(blob) || type === "db-migration" || type === "api-contract") {
25607
+ return "high";
25608
+ }
25609
+ if (type === "docs" || type === "review") {
25610
+ return "low";
25611
+ }
25612
+ if (type === "test") {
25613
+ return "low";
25614
+ }
25615
+ if (type === "refactor" && (input.signals ?? []).some((s) => /full[-_ ]?test[-_ ]?coverage/i.test(s))) {
25616
+ return "low";
25617
+ }
25618
+ if (type === "fix") {
25619
+ const isolated = (input.signals ?? []).some((s) => /isolated/i.test(s));
25620
+ return isolated ? "low" : "medium";
25621
+ }
25622
+ return "medium";
25623
+ }
25624
+ var HIGH_RE, INFRA_RE;
25625
+ var init_risk_classifier = __esm(() => {
25626
+ HIGH_RE = /(migrat|schema|auth(?:n|z|entication|orization)?|oauth|payment|billing|credential|password|secret|openapi|public[-_ ]?api|api[-_ ]?contract)/i;
25627
+ INFRA_RE = /(alembic|\.github[/\\]workflows|dockerfile|terraform|\.tf\b|package-lock\.json|go\.sum|cargo\.lock)/i;
25628
+ });
25629
+
25390
25630
  // src/core/shared/vague-criterion.ts
25391
25631
  function isVagueCriterion(text) {
25392
25632
  const t = text.trim();
@@ -25547,16 +25787,34 @@ function consensusExitCode(status) {
25547
25787
  return 1;
25548
25788
  return 2;
25549
25789
  }
25790
+ function parseConsensusBallots(verdicts) {
25791
+ if (!Array.isArray(verdicts)) {
25792
+ return { ok: false, error: "Consensus input must be a verdicts array or { verdicts, config? }" };
25793
+ }
25794
+ const parsed = [];
25795
+ for (let i = 0;i < verdicts.length; i++) {
25796
+ const ballot = CouncilVerdictInputSchema.safeParse(verdicts[i]);
25797
+ if (!ballot.success) {
25798
+ return {
25799
+ ok: false,
25800
+ error: ballot.error.issues.map((issue) => `verdicts[${i}].${issue.path.join(".")}: ${issue.message}`).join("; ")
25801
+ };
25802
+ }
25803
+ parsed.push(ballot.data);
25804
+ }
25805
+ return { ok: true, verdicts: parsed };
25806
+ }
25550
25807
  function parseConsensusPayload(data) {
25551
25808
  if (Array.isArray(data)) {
25552
- return { ok: true, verdicts: data };
25809
+ return parseConsensusBallots(data);
25553
25810
  }
25554
25811
  if (typeof data !== "object" || data === null) {
25555
25812
  return { ok: false, error: "Consensus input must be a verdicts array or { verdicts, config? }" };
25556
25813
  }
25557
25814
  const record = data;
25558
- if (!Array.isArray(record.verdicts)) {
25559
- return { ok: false, error: "Consensus input must be a verdicts array or { verdicts, config? }" };
25815
+ const ballots = parseConsensusBallots(record.verdicts);
25816
+ if (!ballots.ok) {
25817
+ return ballots;
25560
25818
  }
25561
25819
  let config;
25562
25820
  if (record.config !== undefined) {
@@ -25569,7 +25827,19 @@ function parseConsensusPayload(data) {
25569
25827
  }
25570
25828
  config = parsed.data;
25571
25829
  }
25572
- return { ok: true, verdicts: record.verdicts, config };
25830
+ return { ok: true, verdicts: ballots.verdicts, config };
25831
+ }
25832
+ function resolvePanelRisk(panelRisk, type, scope) {
25833
+ if (panelRisk === undefined || panelRisk === "") {
25834
+ return { ok: true, risk: classifyRisk({ type, signals: scope }) };
25835
+ }
25836
+ if (panelRisk !== "low" && panelRisk !== "medium" && panelRisk !== "high") {
25837
+ return {
25838
+ ok: false,
25839
+ error: `Invalid --risk: ${panelRisk}. Expected one of: low, medium, high`
25840
+ };
25841
+ }
25842
+ return { ok: true, risk: panelRisk };
25573
25843
  }
25574
25844
  async function ccepCommand(options) {
25575
25845
  const {
@@ -25588,7 +25858,11 @@ async function ccepCommand(options) {
25588
25858
  taskId,
25589
25859
  contextStrategy,
25590
25860
  config,
25591
- rest
25861
+ rest,
25862
+ panel,
25863
+ panelType,
25864
+ panelScope,
25865
+ panelRisk
25592
25866
  } = options;
25593
25867
  switch (subcommand) {
25594
25868
  case "parse": {
@@ -25779,6 +26053,7 @@ async function ccepCommand(options) {
25779
26053
  command: "ccep compile",
25780
26054
  phase: phaseId,
25781
26055
  role: agentRole,
26056
+ phaseRoles: resolvedPhase.roles,
25782
26057
  outputSchema: compiled.outputSchema,
25783
26058
  promptVersion,
25784
26059
  ...representation
@@ -25890,6 +26165,30 @@ async function ccepCommand(options) {
25890
26165
  };
25891
26166
  }
25892
26167
  case "consensus": {
26168
+ let panelConfig;
26169
+ if (panel) {
26170
+ const type = panelType ?? "";
26171
+ const scope = (panelScope ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
26172
+ const risk = resolvePanelRisk(panelRisk, type, scope);
26173
+ if (!risk.ok) {
26174
+ return {
26175
+ code: 1,
26176
+ data: { success: false, command: "ccep consensus", errors: [risk.error] }
26177
+ };
26178
+ }
26179
+ panelConfig = selectCouncilPanel(defaultCouncilSpec(), { type, risk: risk.risk, scope });
26180
+ const hasBallots = (input ?? rest?.join(" ").trim() ?? "") !== "";
26181
+ if (!hasBallots) {
26182
+ return {
26183
+ code: 0,
26184
+ data: {
26185
+ success: true,
26186
+ command: "ccep consensus",
26187
+ panel: panelConfig
26188
+ }
26189
+ };
26190
+ }
26191
+ }
25893
26192
  const payload = await readInputPayload(projectRoot, input, rest);
25894
26193
  if (!payload.ok) {
25895
26194
  return {
@@ -25904,7 +26203,7 @@ async function ccepCommand(options) {
25904
26203
  data: { success: false, command: "ccep consensus", errors: [parsedBallots.error] }
25905
26204
  };
25906
26205
  }
25907
- let mergedConfig = parsedBallots.config;
26206
+ let mergedConfig = panelConfig ?? parsedBallots.config;
25908
26207
  if (config) {
25909
26208
  const configPayload = await readInputPayload(projectRoot, config);
25910
26209
  if (!configPayload.ok) {
@@ -26045,6 +26344,8 @@ var init_ccep_command = __esm(() => {
26045
26344
  init_context_resolver();
26046
26345
  init_workflow_profile_loader();
26047
26346
  init_council_consensus();
26347
+ init_council_spec();
26348
+ init_risk_classifier();
26048
26349
  init_task_card_validator();
26049
26350
  init_schemas();
26050
26351
  CONTEXT_STRATEGIES = ["sticky", "isolated", "artifact", "compact", "fork", "full"];
@@ -26190,8 +26491,6 @@ var init_backlog_validator = __esm(() => {
26190
26491
 
26191
26492
  // src/core/openspec/backlog-scanner.ts
26192
26493
  import { execFileSync } from "node:child_process";
26193
- import { readFile as readFile34 } from "node:fs/promises";
26194
- import { resolve as resolve31 } from "node:path";
26195
26494
  function itemSnapshot(items) {
26196
26495
  const snap = {};
26197
26496
  for (const item of items) {
@@ -26199,67 +26498,106 @@ function itemSnapshot(items) {
26199
26498
  }
26200
26499
  return snap;
26201
26500
  }
26202
- async function scanBacklog(projectRoot, previousSnapshot = {}) {
26501
+ function checkBacklogFileChanged(projectRoot) {
26203
26502
  try {
26204
- const filePath = resolve31(projectRoot, BACKLOG_FILENAME);
26205
- const content = await readFile34(filePath, "utf-8");
26206
- const hash = hashContent(content);
26207
- let fileChanged = true;
26208
- try {
26209
- const diff = execFileSync("git", ["diff", "--name-only", "--", BACKLOG_FILENAME], {
26210
- cwd: projectRoot,
26211
- encoding: "utf-8",
26212
- stdio: ["ignore", "pipe", "ignore"]
26213
- });
26214
- fileChanged = diff.trim().length > 0;
26215
- } catch {
26216
- fileChanged = true;
26217
- }
26218
- const parseResult = parseBacklogMarkdown(content);
26219
- if (!parseResult.success)
26220
- return parseResult;
26221
- const doc = parseResult.data;
26222
- const current = itemSnapshot([...doc.items, ...doc.archive]);
26223
- const newItems = [];
26224
- const modifiedItems = [];
26225
- const closedItems = [];
26226
- for (const [id, snap] of Object.entries(current)) {
26227
- if (!previousSnapshot[id]) {
26228
- newItems.push(id);
26229
- } else if (previousSnapshot[id] !== snap) {
26230
- modifiedItems.push(id);
26231
- }
26232
- }
26233
- for (const id of Object.keys(previousSnapshot)) {
26234
- if (!current[id])
26235
- closedItems.push(id);
26503
+ const diff = execFileSync("git", ["diff", "--name-only", "--", BACKLOG_FILENAME], {
26504
+ cwd: projectRoot,
26505
+ encoding: "utf-8",
26506
+ stdio: ["ignore", "pipe", "ignore"]
26507
+ });
26508
+ return diff.trim().length > 0;
26509
+ } catch {
26510
+ return true;
26511
+ }
26512
+ }
26513
+ function computeScanDiff(content, doc, previousSnapshot = {}, fileChanged = true) {
26514
+ const hash = hashContent(content);
26515
+ const current = itemSnapshot([...doc.items, ...doc.archive]);
26516
+ const newItems = [];
26517
+ const modifiedItems = [];
26518
+ const closedItems = [];
26519
+ for (const [id, snap] of Object.entries(current)) {
26520
+ if (!previousSnapshot[id]) {
26521
+ newItems.push(id);
26522
+ } else if (previousSnapshot[id] !== snap) {
26523
+ modifiedItems.push(id);
26236
26524
  }
26237
- for (const item of doc.archive) {
26238
- if (!closedItems.includes(item.id))
26239
- closedItems.push(item.id);
26525
+ }
26526
+ for (const id of Object.keys(previousSnapshot)) {
26527
+ if (!current[id])
26528
+ closedItems.push(id);
26529
+ }
26530
+ for (const item of doc.archive) {
26531
+ if (!closedItems.includes(item.id))
26532
+ closedItems.push(item.id);
26533
+ }
26534
+ for (const item of doc.items) {
26535
+ if (item.status === "DONE" && !closedItems.includes(item.id)) {
26536
+ closedItems.push(item.id);
26240
26537
  }
26241
- for (const item of doc.items) {
26242
- if (item.status === "DONE" && !closedItems.includes(item.id)) {
26243
- closedItems.push(item.id);
26244
- }
26538
+ }
26539
+ return {
26540
+ fileChanged,
26541
+ contentHash: hash,
26542
+ newItems,
26543
+ modifiedItems,
26544
+ closedItems
26545
+ };
26546
+ }
26547
+ function buildItemSnapshot(items, archive) {
26548
+ return itemSnapshot([...items, ...archive]);
26549
+ }
26550
+ var init_backlog_scanner = __esm(() => {
26551
+ init_backlog_parser();
26552
+ init_openspec_state();
26553
+ });
26554
+
26555
+ // src/core/openspec/backlog-session.ts
26556
+ import { readFile as readFile34 } from "node:fs/promises";
26557
+ import { resolve as resolve31 } from "node:path";
26558
+ async function loadBacklogSession(projectRoot) {
26559
+ try {
26560
+ const raw = await readFile34(resolve31(projectRoot, BACKLOG_FILENAME), "utf-8");
26561
+ const parsed = parseBacklogMarkdown(raw);
26562
+ if (!parsed.success)
26563
+ return parsed;
26564
+ const stateResult = await loadOpenspecState(projectRoot);
26565
+ if (!stateResult.success)
26566
+ return stateResult;
26567
+ const state = stateResult.data;
26568
+ const report = validateBacklog(parsed.data);
26569
+ if (!report.valid) {
26570
+ return err(new BacklogSessionError(parsed.data, report, state));
26245
26571
  }
26246
26572
  return ok({
26247
- fileChanged,
26248
- contentHash: hash,
26249
- newItems,
26250
- modifiedItems,
26251
- closedItems
26573
+ raw,
26574
+ doc: parsed.data,
26575
+ state,
26576
+ scan: computeScanDiff(raw, parsed.data, state.itemSnapshots ?? {}, checkBacklogFileChanged(projectRoot)),
26577
+ report
26252
26578
  });
26253
26579
  } catch (e) {
26254
26580
  return err(e instanceof Error ? e : new Error(String(e)));
26255
26581
  }
26256
26582
  }
26257
- function buildItemSnapshot(items, archive) {
26258
- return itemSnapshot([...items, ...archive]);
26259
- }
26260
- var init_backlog_scanner = __esm(() => {
26583
+ var BacklogSessionError;
26584
+ var init_backlog_session = __esm(() => {
26261
26585
  init_backlog_parser();
26586
+ init_backlog_validator();
26587
+ init_backlog_scanner();
26262
26588
  init_openspec_state();
26589
+ BacklogSessionError = class BacklogSessionError extends Error {
26590
+ doc;
26591
+ report;
26592
+ state;
26593
+ constructor(doc, report, state) {
26594
+ super(report.errors.map((e) => e.message).join("; ") || "Invalid BACKLOG.md");
26595
+ this.name = "BacklogSessionError";
26596
+ this.doc = doc;
26597
+ this.report = report;
26598
+ this.state = state;
26599
+ }
26600
+ };
26263
26601
  });
26264
26602
 
26265
26603
  // src/core/openspec/agent-router.ts
@@ -26294,8 +26632,11 @@ function phaseTitle(phase, item) {
26294
26632
  function phasePrompt(phase, item) {
26295
26633
  const base = `Backlog item ${item.id} — ${item.title}
26296
26634
 
26297
- Description: ${item.description}
26298
- Scope: ${item.scope}`;
26635
+ ` + `Description:
26636
+ ${markUntrusted("description", item.description)}
26637
+
26638
+ ` + `Scope:
26639
+ ${markUntrusted("scope", item.scope)}`;
26299
26640
  switch (phase) {
26300
26641
  case "discover":
26301
26642
  return `${base}
@@ -26422,6 +26763,7 @@ var DEFAULT_PHASE_ORDER, TDD_PHASE_ORDER;
26422
26763
  var init_backlog_planner = __esm(() => {
26423
26764
  init_agent_router();
26424
26765
  init_openspec_state();
26766
+ init_untrusted_text();
26425
26767
  DEFAULT_PHASE_ORDER = [
26426
26768
  "discover",
26427
26769
  "design",
@@ -26439,7 +26781,7 @@ var init_backlog_planner = __esm(() => {
26439
26781
  });
26440
26782
 
26441
26783
  // src/core/openspec/openspec-generator.ts
26442
- import { mkdir as mkdir22, rename as rename5, writeFile as writeFile22 } from "node:fs/promises";
26784
+ import { access as access7, mkdir as mkdir21, rename as rename5, writeFile as writeFile21 } from "node:fs/promises";
26443
26785
  import { basename as basename8, resolve as resolve32 } from "node:path";
26444
26786
  function pad3(n) {
26445
26787
  return String(n).padStart(3, "0");
@@ -26452,13 +26794,14 @@ function proposalContent(item) {
26452
26794
 
26453
26795
  ## Why
26454
26796
 
26455
- ${item.description}
26797
+ ${markUntrusted("description", item.description)}
26456
26798
 
26457
- ${item.businessValue ? `**Business value:** ${item.businessValue}` : ""}
26799
+ ${item.businessValue ? `**Business value:**
26800
+ ${markUntrusted("business-value", item.businessValue)}` : ""}
26458
26801
 
26459
26802
  ## What Changes
26460
26803
 
26461
- - ${item.scope}
26804
+ - ${markUntrusted("scope", item.scope)}
26462
26805
 
26463
26806
  ## Capabilities
26464
26807
 
@@ -26467,9 +26810,11 @@ ${item.businessValue ? `**Business value:** ${item.businessValue}` : ""}
26467
26810
 
26468
26811
  ## Impact
26469
26812
 
26470
- ${item.risks ? `Risks: ${item.risks}` : "See design.md for technical impact."}
26813
+ ${item.risks ? `Risks:
26814
+ ${markUntrusted("risks", item.risks)}` : "See design.md for technical impact."}
26471
26815
 
26472
- **Out of scope:** ${item.outOfScope || "None specified."}
26816
+ **Out of scope:**
26817
+ ${markUntrusted("out-of-scope", item.outOfScope || "None specified.")}
26473
26818
  `;
26474
26819
  }
26475
26820
  function designContent(item) {
@@ -26477,15 +26822,20 @@ function designContent(item) {
26477
26822
 
26478
26823
  ## Approach
26479
26824
 
26480
- Deliver "${item.title}" inside ${item.scope}. The architect MUST refine files and risks before implementation.
26825
+ Deliver "${item.title}" inside:
26826
+
26827
+ ${markUntrusted("scope", item.scope)}
26828
+
26829
+ The architect MUST refine files and risks before implementation.
26481
26830
 
26482
26831
  ## Files Affected
26483
26832
 
26484
- ${item.scope}
26833
+ ${markUntrusted("scope", item.scope)}
26485
26834
 
26486
26835
  ## Acceptance Criteria
26487
26836
 
26488
- ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
26837
+ ${item.acceptanceCriteria.map((c) => markUntrusted("acceptance-criterion", c)).join(`
26838
+
26489
26839
  `)}
26490
26840
 
26491
26841
  ## Complexity Tracking
@@ -26519,8 +26869,8 @@ ${blocks.join(`
26519
26869
  `;
26520
26870
  }
26521
26871
  async function writeTasksMarkdown(projectRoot, changePath, cards, options = {}) {
26522
- await mkdir22(resolve32(projectRoot, changePath), { recursive: true });
26523
- await writeFile22(resolve32(projectRoot, changePath, "tasks.md"), tasksContent(cards, options), "utf-8");
26872
+ await mkdir21(resolve32(projectRoot, changePath), { recursive: true });
26873
+ await writeFile21(resolve32(projectRoot, changePath, "tasks.md"), tasksContent(cards, options), "utf-8");
26524
26874
  }
26525
26875
  function tasksContent(cards, options = {}) {
26526
26876
  const criteria = options.acceptanceCriteria ?? [];
@@ -26566,18 +26916,18 @@ function tasksContent(cards, options = {}) {
26566
26916
  async function generateOpenspecChange(projectRoot, item, taskCards, options = {}) {
26567
26917
  const slug = buildChangeSlug(item);
26568
26918
  const changeDir = resolve32(projectRoot, "openspec", "changes", slug);
26569
- await mkdir22(resolve32(changeDir, "specs"), { recursive: true });
26919
+ await mkdir21(resolve32(changeDir, "specs"), { recursive: true });
26570
26920
  const taskOpts = {
26571
26921
  tddRequired: options.tddRequired,
26572
26922
  acceptanceCriteria: options.acceptanceCriteria ?? item.acceptanceCriteria
26573
26923
  };
26574
- await writeFile22(resolve32(changeDir, "proposal.md"), proposalContent(item), "utf-8");
26575
- await writeFile22(resolve32(changeDir, "design.md"), designContent(item), "utf-8");
26576
- await writeFile22(resolve32(changeDir, "tasks.md"), tasksContent(taskCards, taskOpts), "utf-8");
26924
+ await writeFile21(resolve32(changeDir, "proposal.md"), proposalContent(item), "utf-8");
26925
+ await writeFile21(resolve32(changeDir, "design.md"), designContent(item), "utf-8");
26926
+ await writeFile21(resolve32(changeDir, "tasks.md"), tasksContent(taskCards, taskOpts), "utf-8");
26577
26927
  const capability = capabilitySlug(item.title);
26578
- await mkdir22(resolve32(changeDir, "specs", capability), { recursive: true });
26579
- await writeFile22(resolve32(changeDir, "specs", capability, "spec.md"), specDelta(item), "utf-8");
26580
- await writeFile22(resolve32(changeDir, "change.yaml"), `version: 1
26928
+ await mkdir21(resolve32(changeDir, "specs", capability), { recursive: true });
26929
+ await writeFile21(resolve32(changeDir, "specs", capability, "spec.md"), specDelta(item), "utf-8");
26930
+ await writeFile21(resolve32(changeDir, "change.yaml"), `version: 1
26581
26931
  backlogId: ${item.id}
26582
26932
  profile: standard
26583
26933
  capabilities:
@@ -26589,38 +26939,86 @@ async function archiveChangeFolder(projectRoot, changePath) {
26589
26939
  const src = resolve32(projectRoot, changePath);
26590
26940
  const slug = basename8(changePath);
26591
26941
  const destDir = resolve32(projectRoot, "openspec", "changes", "archive");
26592
- await mkdir22(destDir, { recursive: true });
26942
+ await mkdir21(destDir, { recursive: true });
26593
26943
  const dest = resolve32(destDir, slug);
26944
+ try {
26945
+ await access7(dest);
26946
+ throw new Error(`Archive destination already exists: openspec/changes/archive/${slug}`);
26947
+ } catch (error) {
26948
+ if (error instanceof Error && error.message.startsWith("Archive destination already exists")) {
26949
+ throw error;
26950
+ }
26951
+ }
26594
26952
  await rename5(src, dest);
26595
26953
  return `openspec/changes/archive/${slug}`;
26596
26954
  }
26597
26955
  async function ensureOpenspecConfig(projectRoot) {
26598
26956
  const configPath = resolve32(projectRoot, "openspec", "config.yaml");
26599
26957
  try {
26600
- const { access: access7 } = await import("node:fs/promises");
26601
- await access7(configPath);
26958
+ const { access: access8 } = await import("node:fs/promises");
26959
+ await access8(configPath);
26602
26960
  } catch {
26603
- await mkdir22(resolve32(projectRoot, "openspec"), { recursive: true });
26961
+ await mkdir21(resolve32(projectRoot, "openspec"), { recursive: true });
26604
26962
  const stub = `# OpenSpec project config (CodeConductor native compatible)
26605
26963
  schema: spec-driven
26606
26964
  `;
26607
- await writeFile22(configPath, stub, "utf-8");
26965
+ await writeFile21(configPath, stub, "utf-8");
26608
26966
  }
26609
26967
  }
26610
26968
  var init_openspec_generator = __esm(() => {
26611
26969
  init_backlog_planner();
26970
+ init_untrusted_text();
26971
+ });
26972
+
26973
+ // src/core/openspec/spec-files.ts
26974
+ import { readFile as readFile35, readdir as readdir11 } from "node:fs/promises";
26975
+ import { join as join24 } from "node:path";
26976
+ async function walkMarkdownFiles(dir) {
26977
+ let entries;
26978
+ try {
26979
+ entries = await readdir11(dir, { withFileTypes: true });
26980
+ } catch {
26981
+ return [];
26982
+ }
26983
+ const files = [];
26984
+ for (const entry of entries) {
26985
+ const path = join24(dir, entry.name);
26986
+ if (entry.isDirectory()) {
26987
+ files.push(...await walkMarkdownFiles(path));
26988
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
26989
+ files.push(path);
26990
+ }
26991
+ }
26992
+ return files;
26993
+ }
26994
+ async function readMarkdownFiles(dir) {
26995
+ const files = await walkMarkdownFiles(dir);
26996
+ const out = [];
26997
+ for (const path of files) {
26998
+ out.push({ path, content: await readFile35(path, "utf-8") });
26999
+ }
27000
+ return out;
27001
+ }
27002
+ function requirementBlocks(markdown) {
27003
+ const starts = [...markdown.matchAll(/^### Requirement:.*$/gm)];
27004
+ return starts.map((match, index) => {
27005
+ const start = match.index ?? 0;
27006
+ const end = starts[index + 1]?.index ?? markdown.length;
27007
+ const block = markdown.slice(start, end).trim();
27008
+ return { id: block.match(REQUIREMENT_HEADING)?.[1] ?? null, markdown: block };
27009
+ });
27010
+ }
27011
+ var REQUIREMENT_HEADING;
27012
+ var init_spec_files = __esm(() => {
27013
+ REQUIREMENT_HEADING = /^### Requirement:.*?\b(FR-\d{3})\b.*$/m;
26612
27014
  });
26613
27015
 
26614
27016
  // src/core/openspec/spec-quality.ts
26615
- import { readdir as readdir11, readFile as readFile35 } from "node:fs/promises";
26616
- import { join as join23, relative as relative10, resolve as resolve33 } from "node:path";
27017
+ import { readFile as readFile36 } from "node:fs/promises";
27018
+ import { join as join25, relative as relative10, resolve as resolve33 } from "node:path";
26617
27019
  function uniqueIds(matches) {
26618
27020
  return [...new Set(matches ?? [])];
26619
27021
  }
26620
- function requirementBlocks(content) {
26621
- const parts = content.split(/^### Requirement:/m);
26622
- return parts.slice(1).map((block) => `### Requirement:${block}`);
26623
- }
26624
27022
  function assessSpecMarkdown(content, path) {
26625
27023
  const issues = [];
26626
27024
  const clarifications = content.match(NEEDS_CLARIFICATION) ?? [];
@@ -26663,9 +27061,10 @@ function assessSpecMarkdown(content, path) {
26663
27061
  });
26664
27062
  }
26665
27063
  for (const block of blocks) {
26666
- const heading = block.split(`
27064
+ const text = block.markdown;
27065
+ const heading = text.split(`
26667
27066
  `, 1)[0] ?? "";
26668
- if (!RFC2119.test(block)) {
27067
+ if (!RFC2119.test(text)) {
26669
27068
  issues.push({
26670
27069
  code: "MISSING_RFC2119",
26671
27070
  message: `Requirement "${heading}" MUST use RFC 2119 keywords (MUST/SHALL/SHOULD/MAY)`,
@@ -26673,7 +27072,7 @@ function assessSpecMarkdown(content, path) {
26673
27072
  severity: "error"
26674
27073
  });
26675
27074
  }
26676
- if (!GWT.test(block)) {
27075
+ if (!GWT.test(text)) {
26677
27076
  issues.push({
26678
27077
  code: "MISSING_GWT",
26679
27078
  message: `Requirement "${heading}" MUST include a Given/When/Then scenario`,
@@ -26726,24 +27125,6 @@ function mergeSpecQualityReports(reports) {
26726
27125
  successCriteria
26727
27126
  };
26728
27127
  }
26729
- async function walkMarkdown(dir) {
26730
- const out = [];
26731
- let entries;
26732
- try {
26733
- entries = await readdir11(dir, { withFileTypes: true });
26734
- } catch {
26735
- return out;
26736
- }
26737
- for (const entry of entries) {
26738
- const full = join23(dir, entry.name);
26739
- if (entry.isDirectory()) {
26740
- out.push(...await walkMarkdown(full));
26741
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
26742
- out.push(full);
26743
- }
26744
- }
26745
- return out;
26746
- }
26747
27128
  async function assessChangeFolder(projectRoot, changePath) {
26748
27129
  const root = resolve33(projectRoot);
26749
27130
  const changeRoot = resolve33(root, changePath);
@@ -26764,18 +27145,18 @@ async function assessChangeFolder(projectRoot, changePath) {
26764
27145
  successCriteria: []
26765
27146
  };
26766
27147
  }
26767
- const specDir = join23(changeRoot, "specs");
26768
- const specFiles = await walkMarkdown(specDir);
26769
- const extra = ["proposal.md", "design.md"].map((name) => join23(changeRoot, name));
27148
+ const specDir = join25(changeRoot, "specs");
27149
+ const specFiles = await walkMarkdownFiles(specDir);
27150
+ const extra = ["proposal.md", "design.md"].map((name) => join25(changeRoot, name));
26770
27151
  const reports = [];
26771
27152
  for (const file of specFiles) {
26772
- const content = await readFile35(file, "utf-8");
27153
+ const content = await readFile36(file, "utf-8");
26773
27154
  reports.push(assessSpecMarkdown(content, relative10(root, file).replace(/\\/g, "/")));
26774
27155
  }
26775
27156
  for (const file of extra) {
26776
27157
  let content;
26777
27158
  try {
26778
- content = await readFile35(file, "utf-8");
27159
+ content = await readFile36(file, "utf-8");
26779
27160
  } catch {
26780
27161
  continue;
26781
27162
  }
@@ -26819,6 +27200,7 @@ async function assessChangeFolder(projectRoot, changePath) {
26819
27200
  }
26820
27201
  var RFC2119, FR_ID, SC_ID, US_AC, GWT, NEEDS_CLARIFICATION, PLACEHOLDER, HOW_LEAK, MAX_CLARIFICATIONS = 3;
26821
27202
  var init_spec_quality = __esm(() => {
27203
+ init_spec_files();
26822
27204
  RFC2119 = /\b(MUST(?: NOT)?|SHALL(?: NOT)?|SHOULD(?: NOT)?|MAY)\b/;
26823
27205
  FR_ID = /\bFR-\d{3}\b/g;
26824
27206
  SC_ID = /\bSC-\d{3}\b/g;
@@ -26830,8 +27212,8 @@ var init_spec_quality = __esm(() => {
26830
27212
  });
26831
27213
 
26832
27214
  // src/core/openspec/spec-analyzer.ts
26833
- import { readFile as readFile36, readdir as readdir12 } from "node:fs/promises";
26834
- import { join as join24, resolve as resolve34 } from "node:path";
27215
+ import { readFile as readFile37 } from "node:fs/promises";
27216
+ import { join as join26, resolve as resolve34 } from "node:path";
26835
27217
  function unique(ids) {
26836
27218
  return [...new Set(ids)];
26837
27219
  }
@@ -26856,22 +27238,8 @@ function testSection(tasksMarkdown) {
26856
27238
  `);
26857
27239
  }
26858
27240
  async function readDeltaSpecs(specDir) {
26859
- let entries;
26860
- try {
26861
- entries = await readdir12(specDir, { withFileTypes: true });
26862
- } catch {
26863
- return "";
26864
- }
26865
- const contents = [];
26866
- for (const entry of entries) {
26867
- const path = join24(specDir, entry.name);
26868
- if (entry.isDirectory()) {
26869
- contents.push(await readDeltaSpecs(path));
26870
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
26871
- contents.push(await readFile36(path, "utf-8"));
26872
- }
26873
- }
26874
- return contents.filter(Boolean).join(`
27241
+ const files = await readMarkdownFiles(specDir);
27242
+ return files.map((f) => f.content).filter(Boolean).join(`
26875
27243
 
26876
27244
  `);
26877
27245
  }
@@ -26947,16 +27315,16 @@ function analyzeSpecArtifacts(input) {
26947
27315
  }
26948
27316
  async function analyzeChangeFolder(projectRoot, changePath, options) {
26949
27317
  const changeRoot = resolve34(projectRoot, changePath);
26950
- const specMarkdown = await readDeltaSpecs(join24(changeRoot, "specs"));
27318
+ const specMarkdown = await readDeltaSpecs(join26(changeRoot, "specs"));
26951
27319
  let tasksMarkdown = "";
26952
27320
  try {
26953
- tasksMarkdown = await readFile36(join24(changeRoot, "tasks.md"), "utf-8");
27321
+ tasksMarkdown = await readFile37(join26(changeRoot, "tasks.md"), "utf-8");
26954
27322
  } catch {
26955
27323
  tasksMarkdown = "";
26956
27324
  }
26957
27325
  let designMarkdown;
26958
27326
  try {
26959
- designMarkdown = await readFile36(join24(changeRoot, "design.md"), "utf-8");
27327
+ designMarkdown = await readFile37(join26(changeRoot, "design.md"), "utf-8");
26960
27328
  } catch {
26961
27329
  designMarkdown = undefined;
26962
27330
  }
@@ -26971,53 +27339,28 @@ async function analyzeChangeFolder(projectRoot, changePath, options) {
26971
27339
  });
26972
27340
  }
26973
27341
  var init_spec_analyzer = __esm(() => {
27342
+ init_spec_files();
26974
27343
  init_spec_quality();
26975
27344
  });
26976
27345
 
26977
27346
  // src/core/openspec/spec-sync.ts
26978
- import { mkdir as mkdir23, readFile as readFile37, readdir as readdir13 } from "node:fs/promises";
26979
- import { join as join25, relative as relative11, resolve as resolve35 } from "node:path";
26980
- async function markdownFiles(dir) {
26981
- let entries;
26982
- try {
26983
- entries = await readdir13(dir, { withFileTypes: true });
26984
- } catch {
26985
- return [];
26986
- }
26987
- const files = [];
26988
- for (const entry of entries) {
26989
- const path = join25(dir, entry.name);
26990
- if (entry.isDirectory())
26991
- files.push(...await markdownFiles(path));
26992
- else if (entry.isFile() && entry.name.endsWith(".md"))
26993
- files.push(path);
26994
- }
26995
- return files;
26996
- }
26997
- function requirementBlocks2(markdown) {
26998
- const starts = [...markdown.matchAll(/^### Requirement:.*$/gm)];
26999
- return starts.flatMap((match, index) => {
27000
- const start = match.index ?? 0;
27001
- const end = starts[index + 1]?.index ?? markdown.length;
27002
- const block = markdown.slice(start, end).trim();
27003
- const id = block.match(REQUIREMENT_HEADING)?.[1];
27004
- return id ? [{ id, markdown: block }] : [];
27005
- });
27006
- }
27347
+ import { mkdir as mkdir22, readFile as readFile38 } from "node:fs/promises";
27348
+ import { join as join27, relative as relative11, resolve as resolve35 } from "node:path";
27007
27349
  function deltaRequirements(markdown) {
27008
27350
  const sections = [...markdown.matchAll(SECTION_HEADING)];
27009
27351
  return sections.flatMap((section, index) => {
27010
27352
  const start = (section.index ?? 0) + section[0].length;
27011
27353
  const end = sections[index + 1]?.index ?? markdown.length;
27012
27354
  const operation = section[1];
27013
- return requirementBlocks2(markdown.slice(start, end)).map((requirement) => ({
27014
- ...requirement,
27355
+ return requirementBlocks(markdown.slice(start, end)).filter((requirement) => requirement.id !== null).map((requirement) => ({
27356
+ id: requirement.id,
27357
+ markdown: requirement.markdown,
27015
27358
  operation
27016
27359
  }));
27017
27360
  });
27018
27361
  }
27019
27362
  function upsertRequirements(existing, deltas, source) {
27020
- const requirements = requirementBlocks2(existing);
27363
+ const requirements = requirementBlocks(existing).filter((requirement) => requirement.id !== null);
27021
27364
  const byId = new Map(requirements.map((requirement) => [requirement.id, requirement]));
27022
27365
  if (byId.size !== requirements.length) {
27023
27366
  return { ok: false, error: `${source}: durable spec has duplicate requirement IDs` };
@@ -27049,7 +27392,7 @@ ${delta.markdown}`;
27049
27392
  if (!current)
27050
27393
  continue;
27051
27394
  if (delta.operation === "MODIFIED") {
27052
- next = next.replace(current.markdown, delta.markdown);
27395
+ next = next.replace(current.markdown, () => delta.markdown);
27053
27396
  byId.set(delta.id, { id: delta.id, markdown: delta.markdown });
27054
27397
  } else {
27055
27398
  next = next.replace(current.markdown, "").replace(/\n{3,}/g, `
@@ -27064,17 +27407,17 @@ ${delta.markdown}`;
27064
27407
  async function syncChangeSpecs(projectRoot, changePath) {
27065
27408
  const root = resolve35(projectRoot);
27066
27409
  const changeRoot = resolve35(root, changePath);
27067
- const changeSpecs = join25(changeRoot, "specs");
27410
+ const changeSpecs = join27(changeRoot, "specs");
27068
27411
  if (changeRoot !== root && !changeRoot.startsWith(`${root}/`)) {
27069
27412
  return { success: false, syncedPaths: [], errors: ["Change path escapes project root"] };
27070
27413
  }
27071
- const files = await markdownFiles(changeSpecs);
27414
+ const files = await walkMarkdownFiles(changeSpecs);
27072
27415
  if (files.length === 0) {
27073
27416
  return { success: false, syncedPaths: [], errors: ["Change folder has no delta specs"] };
27074
27417
  }
27075
27418
  const writes = [];
27076
27419
  for (const file of files) {
27077
- const delta = await readFile37(file, "utf-8");
27420
+ const delta = await readFile38(file, "utf-8");
27078
27421
  const requirements = deltaRequirements(delta);
27079
27422
  if (requirements.length === 0) {
27080
27423
  return {
@@ -27084,11 +27427,11 @@ async function syncChangeSpecs(projectRoot, changePath) {
27084
27427
  };
27085
27428
  }
27086
27429
  const relativeSpec = relative11(changeSpecs, file);
27087
- const target = join25(root, "openspec", "specs", relativeSpec);
27430
+ const target = join27(root, "openspec", "specs", relativeSpec);
27088
27431
  let existing = `# ${relativeSpec.replace(/\\/g, "/")}
27089
27432
  `;
27090
27433
  try {
27091
- existing = await readFile37(target, "utf-8");
27434
+ existing = await readFile38(target, "utf-8");
27092
27435
  } catch {}
27093
27436
  const merged = upsertRequirements(existing, requirements, relative11(root, file));
27094
27437
  if (!merged.ok)
@@ -27096,7 +27439,7 @@ async function syncChangeSpecs(projectRoot, changePath) {
27096
27439
  writes.push({ path: target, content: merged.content });
27097
27440
  }
27098
27441
  for (const write of writes) {
27099
- await mkdir23(resolve35(write.path, ".."), { recursive: true });
27442
+ await mkdir22(resolve35(write.path, ".."), { recursive: true });
27100
27443
  await writeFileAtomic(write.path, write.content);
27101
27444
  }
27102
27445
  return {
@@ -27105,20 +27448,104 @@ async function syncChangeSpecs(projectRoot, changePath) {
27105
27448
  errors: []
27106
27449
  };
27107
27450
  }
27108
- var REQUIREMENT_HEADING, SECTION_HEADING;
27451
+ var SECTION_HEADING;
27109
27452
  var init_spec_sync = __esm(() => {
27110
27453
  init_openspec_state();
27111
- REQUIREMENT_HEADING = /^### Requirement:.*?\b(FR-\d{3})\b.*$/m;
27454
+ init_spec_files();
27112
27455
  SECTION_HEADING = /^## (ADDED|MODIFIED|REMOVED) Requirements\s*$/gm;
27113
27456
  });
27114
27457
 
27458
+ // src/core/openspec/artifact-progress.ts
27459
+ import { access as access8, readdir as readdir12, readFile as readFile39 } from "node:fs/promises";
27460
+ import { join as join28, resolve as resolve36 } from "node:path";
27461
+ function countCheckboxes(markdown) {
27462
+ let total = 0;
27463
+ let complete = 0;
27464
+ for (const line of markdown.split(`
27465
+ `)) {
27466
+ const marker = line.match(CHECKBOX)?.[1];
27467
+ if (marker === undefined)
27468
+ continue;
27469
+ total += 1;
27470
+ if (marker === "x" || marker === "X")
27471
+ complete += 1;
27472
+ }
27473
+ return { total, complete, remaining: total - complete };
27474
+ }
27475
+ async function exists(path) {
27476
+ try {
27477
+ await access8(path);
27478
+ return true;
27479
+ } catch {
27480
+ return false;
27481
+ }
27482
+ }
27483
+ async function hasMarkdown(dir) {
27484
+ let entries;
27485
+ try {
27486
+ entries = await readdir12(dir, { withFileTypes: true });
27487
+ } catch {
27488
+ return false;
27489
+ }
27490
+ for (const entry of entries) {
27491
+ const full = join28(dir, entry.name);
27492
+ if (entry.isFile() && entry.name.endsWith(".md"))
27493
+ return true;
27494
+ if (entry.isDirectory() && await hasMarkdown(full))
27495
+ return true;
27496
+ }
27497
+ return false;
27498
+ }
27499
+ async function readArtifactProgress(projectRoot, changePath) {
27500
+ const root = resolve36(projectRoot);
27501
+ const changeRoot = resolve36(root, changePath);
27502
+ if (changeRoot !== root && !changeRoot.startsWith(`${root}/`)) {
27503
+ return ABSENT;
27504
+ }
27505
+ const [proposal, design, tasks, specs] = await Promise.all([
27506
+ exists(join28(changeRoot, "proposal.md")),
27507
+ exists(join28(changeRoot, "design.md")),
27508
+ exists(join28(changeRoot, "tasks.md")),
27509
+ hasMarkdown(join28(changeRoot, "specs"))
27510
+ ]);
27511
+ let checkboxes = { total: 0, complete: 0, remaining: 0 };
27512
+ if (tasks) {
27513
+ try {
27514
+ checkboxes = countCheckboxes(await readFile39(join28(changeRoot, "tasks.md"), "utf-8"));
27515
+ } catch {
27516
+ checkboxes = { total: 0, complete: 0, remaining: 0 };
27517
+ }
27518
+ }
27519
+ return { artifacts: { proposal, design, tasks, specs }, checkboxes };
27520
+ }
27521
+ function missingArtifacts(presence) {
27522
+ const missing = [];
27523
+ if (!presence.proposal)
27524
+ missing.push("proposal.md");
27525
+ if (!presence.design)
27526
+ missing.push("design.md");
27527
+ if (!presence.tasks)
27528
+ missing.push("tasks.md");
27529
+ if (!presence.specs)
27530
+ missing.push("specs/*.md");
27531
+ return missing;
27532
+ }
27533
+ var CHECKBOX, ABSENT;
27534
+ var init_artifact_progress = __esm(() => {
27535
+ CHECKBOX = /^\s*-\s*\[([^\]])\]/;
27536
+ ABSENT = {
27537
+ artifacts: { proposal: false, design: false, tasks: false, specs: false },
27538
+ checkboxes: { total: 0, complete: 0, remaining: 0 }
27539
+ };
27540
+ });
27541
+
27115
27542
  // src/commands/openspec.command.ts
27116
27543
  var exports_openspec_command = {};
27117
27544
  __export(exports_openspec_command, {
27118
27545
  openspecCommand: () => openspecCommand
27119
27546
  });
27120
- import { readFile as readFile38 } from "node:fs/promises";
27121
- import { resolve as resolve36 } from "node:path";
27547
+ import { readFile as readFile40 } from "node:fs/promises";
27548
+ import { resolve as resolve37 } from "node:path";
27122
27549
  async function openspecCommand(options) {
27123
27550
  const { subcommand, itemId, projectRoot, output, reason } = options;
27124
27551
  switch (subcommand) {
@@ -27144,6 +27571,10 @@ async function openspecCommand(options) {
27144
27571
  return handleUnblock(projectRoot, itemId);
27145
27572
  case "archive":
27146
27573
  return handleArchive(projectRoot, itemId);
27574
+ case "sync":
27575
+ return handleSync(projectRoot, itemId);
27576
+ case "verify":
27577
+ return handleVerify(projectRoot, itemId);
27147
27578
  default:
27148
27579
  return {
27149
27580
  code: 1,
@@ -27164,7 +27595,7 @@ function fail(command, errors3, extra = {}) {
27164
27595
  };
27165
27596
  }
27166
27597
  async function persistBacklog(projectRoot, content) {
27167
- await writeFileAtomic(resolve36(projectRoot, BACKLOG_FILENAME), content);
27598
+ await writeFileAtomic(resolve37(projectRoot, BACKLOG_FILENAME), content);
27168
27599
  }
27169
27600
  async function persistState(projectRoot, state) {
27170
27601
  const written = await writeOpenspecState(projectRoot, state);
@@ -27173,17 +27604,13 @@ async function persistState(projectRoot, state) {
27173
27604
  }
27174
27605
  return { ok: true };
27175
27606
  }
27176
- async function readBacklogMarkdown(projectRoot) {
27177
- return readFile38(resolve36(projectRoot, BACKLOG_FILENAME), "utf-8");
27178
- }
27179
- async function transitionItem(projectRoot, item, to, progress) {
27180
- const content = await readBacklogMarkdown(projectRoot);
27607
+ async function transitionItem(projectRoot, item, to, progress, content) {
27181
27608
  const next = applyBacklogTransition(content, item.id, item.status, to, progress);
27182
27609
  if (!next.success) {
27183
27610
  return { ok: false, error: next.error.message };
27184
27611
  }
27185
27612
  await persistBacklog(projectRoot, next.data);
27186
- return { ok: true };
27613
+ return { ok: true, content: next.data };
27187
27614
  }
27188
27615
  function findCard(state, cardId) {
27189
27616
  return state.taskCards.find((c) => c.id === cardId);
@@ -27191,28 +27618,92 @@ function findCard(state, cardId) {
27191
27618
  function itemCards(state, backlogId) {
27192
27619
  return state.taskCards.filter((c) => c.backlogId === backlogId);
27193
27620
  }
27194
- async function handleValidate(projectRoot) {
27195
- const loadResult = await loadBacklog(projectRoot);
27196
- if (!loadResult.success) {
27621
+ function resolveActiveChange(state, command, itemId) {
27622
+ const targetId = itemId ?? state.activeItemId;
27623
+ if (!targetId) {
27197
27624
  return {
27198
- code: 1,
27199
- data: {
27200
- success: false,
27201
- command: "openspec validate",
27202
- errors: [loadResult.error.message],
27203
- recommendations: [
27204
- "Create BACKLOG.md at project root using the CodeConductor template."
27205
- ]
27206
- }
27625
+ ok: false,
27626
+ response: fail(command, ["No active change. Run openspec plan or pass an item id."])
27207
27627
  };
27208
27628
  }
27209
- const report = validateBacklog(loadResult.data);
27210
- const errors3 = report.errors.map((e) => e.message);
27211
- const recommendations = [...report.recommendations];
27629
+ const changePath = state.changePaths[targetId];
27630
+ if (!changePath || changePath.includes("/archive/")) {
27631
+ return { ok: false, response: fail(command, [`No active change folder for ${targetId}`]) };
27632
+ }
27633
+ return { ok: true, targetId, changePath };
27634
+ }
27635
+ async function collectAnalyzeReport(projectRoot, session, targetId, changePath) {
27636
+ const tddRequired = session.doc.global.tddRequired;
27637
+ const policyParts = [];
27638
+ try {
27639
+ policyParts.push(await readFile40(resolve37(projectRoot, "AGENTS.md"), "utf-8"));
27640
+ } catch {}
27641
+ policyParts.push(session.raw);
27642
+ const cards = itemCards(session.state, targetId);
27643
+ const tddCards = cards.filter((c) => c.phase === "test" || c.phase === "implement");
27644
+ let hasTddEvidence;
27645
+ if (tddCards.length > 0) {
27646
+ hasTddEvidence = true;
27647
+ for (const card of tddCards) {
27648
+ const expectedPhase = card.phase === "test" ? "red" : "green";
27649
+ if (!await hasTddRunnerEvidence(projectRoot, card.id, expectedPhase)) {
27650
+ hasTddEvidence = false;
27651
+ break;
27652
+ }
27653
+ }
27654
+ }
27655
+ const report = await analyzeChangeFolder(projectRoot, changePath, {
27656
+ tddRequired,
27657
+ policyText: policyParts.join(`
27658
+ `),
27659
+ hasTddEvidence
27660
+ });
27661
+ return { tddRequired, report };
27662
+ }
27663
+ function buildNextSteps(input) {
27664
+ const { state, nextItemId, progress } = input;
27665
+ if (!state || !state.activeItemId) {
27666
+ return [
27667
+ nextItemId ? `Run openspec plan ${nextItemId} to start the next READY item.` : "No active change and no READY item. Author one with /cc-backlog, then run openspec plan <BC-id>."
27668
+ ];
27669
+ }
27670
+ const itemId = state.activeItemId;
27671
+ const steps = [];
27672
+ if (progress) {
27673
+ const missing = missingArtifacts(progress.artifacts);
27674
+ if (missing.length > 0) {
27675
+ steps.push(`Change folder is missing ${missing.join(", ")} — re-run openspec plan ${itemId} or restore them.`);
27676
+ }
27677
+ }
27678
+ const cards = itemCards(state, itemId);
27679
+ const blocked = cards.find((c) => c.status === "blocked");
27680
+ if (blocked) {
27681
+ steps.push(`Run openspec unblock ${blocked.id} to resume the blocked card.`);
27682
+ return steps;
27683
+ }
27684
+ const next = getNextTaskCard(state);
27685
+ if (next && next.backlogId === itemId) {
27686
+ steps.push(`Run openspec start ${next.id} then delegate to ${next.agent}.`);
27687
+ return steps;
27688
+ }
27689
+ const doing = cards.find((c) => c.status === "doing");
27690
+ if (doing) {
27691
+ steps.push(`Run openspec done ${doing.id} when the ${doing.phase} phase completes.`);
27692
+ return steps;
27693
+ }
27694
+ if (cards.length > 0 && cards.every((c) => c.status === "done")) {
27695
+ steps.push(`Run openspec verify ${itemId}, record a scorecard verdict, then run openspec archive ${itemId}.`);
27696
+ return steps;
27697
+ }
27698
+ steps.push(`Run openspec next to get the next TaskCard for ${itemId}.`);
27699
+ return steps;
27700
+ }
27701
+ async function assessActiveChangeSpecs(projectRoot, state) {
27702
+ const errors3 = [];
27703
+ const recommendations = [];
27212
27704
  let specValid = true;
27213
- const stateResult = await loadOpenspecState(projectRoot);
27214
- const activeId = stateResult.success ? stateResult.data.activeItemId : undefined;
27215
- const changePath = activeId && stateResult.success ? stateResult.data.changePaths[activeId] : undefined;
27705
+ const activeId = state.activeItemId;
27706
+ const changePath = activeId ? state.changePaths[activeId] : undefined;
27216
27707
  if (changePath && !changePath.includes("/archive/")) {
27217
27708
  const specReport = await assessChangeFolder(projectRoot, changePath);
27218
27709
  specValid = specReport.valid;
@@ -27223,7 +27714,32 @@ async function handleValidate(projectRoot) {
27223
27714
  recommendations.push("Spec has [NEEDS CLARIFICATION] markers — run ccep evaluate and wait for human input.");
27224
27715
  }
27225
27716
  }
27226
- const valid = report.valid && specValid;
27717
+ return { specValid, errors: errors3, recommendations };
27718
+ }
27719
+ function sessionParts(session) {
27720
+ return { doc: session.doc, state: session.state, report: session.report };
27721
+ }
27722
+ async function buildValidationResponse(projectRoot, sessionResult) {
27723
+ if (!sessionResult.success && !(sessionResult.error instanceof BacklogSessionError)) {
27724
+ return {
27725
+ code: 1,
27726
+ data: {
27727
+ success: false,
27728
+ command: "openspec validate",
27729
+ errors: [sessionResult.error.message],
27730
+ recommendations: [
27731
+ "Create BACKLOG.md at project root using the CodeConductor template."
27732
+ ]
27733
+ }
27734
+ };
27735
+ }
27736
+ const { doc, state, report } = sessionParts(sessionResult.success ? sessionResult.data : sessionResult.error);
27737
+ const errors3 = report.errors.map((e) => e.message);
27738
+ const recommendations = [...report.recommendations];
27739
+ const spec = await assessActiveChangeSpecs(projectRoot, state);
27740
+ errors3.push(...spec.errors);
27741
+ recommendations.push(...spec.recommendations);
27742
+ const valid = report.valid && spec.specValid;
27227
27743
  return {
27228
27744
  code: valid ? 0 : 1,
27229
27745
  data: {
@@ -27232,57 +27748,56 @@ async function handleValidate(projectRoot) {
27232
27748
  valid,
27233
27749
  errors: errors3,
27234
27750
  recommendations,
27235
- itemCount: loadResult.data.items.length,
27236
- archiveCount: loadResult.data.archive.length
27751
+ itemCount: doc.items.length,
27752
+ archiveCount: doc.archive.length
27237
27753
  }
27238
27754
  };
27239
27755
  }
27756
+ async function handleValidate(projectRoot) {
27757
+ return buildValidationResponse(projectRoot, await loadBacklogSession(projectRoot));
27758
+ }
27240
27759
  async function handleScan(projectRoot) {
27241
- const stateResult = await loadOpenspecState(projectRoot);
27242
- const prevSnap = stateResult.success ? stateResult.data.itemSnapshots ?? {} : {};
27243
- const scanResult = await scanBacklog(projectRoot, prevSnap);
27244
- if (!scanResult.success) {
27760
+ const sessionResult = await loadBacklogSession(projectRoot);
27761
+ if (!sessionResult.success) {
27245
27762
  return {
27246
27763
  code: 1,
27247
27764
  data: {
27248
27765
  success: false,
27249
27766
  command: "openspec scan",
27250
- errors: [scanResult.error.message]
27767
+ errors: [sessionResult.error.message]
27251
27768
  }
27252
27769
  };
27253
27770
  }
27254
- const loadResult = await loadBacklog(projectRoot);
27255
- if (loadResult.success && stateResult.success) {
27256
- const snapshots = buildItemSnapshot(loadResult.data.items, loadResult.data.archive);
27257
- const state = {
27258
- ...stateResult.data,
27259
- lastScanHash: scanResult.data.contentHash,
27260
- lastScanAt: new Date().toISOString(),
27261
- itemSnapshots: snapshots
27262
- };
27263
- const written = await persistState(projectRoot, state);
27264
- if (!written.ok) {
27265
- return fail("openspec scan", [written.error]);
27266
- }
27771
+ const { doc, state: loadedState, scan } = sessionResult.data;
27772
+ const snapshots = buildItemSnapshot(doc.items, doc.archive);
27773
+ const state = {
27774
+ ...loadedState,
27775
+ lastScanHash: scan.contentHash,
27776
+ lastScanAt: new Date().toISOString(),
27777
+ itemSnapshots: snapshots
27778
+ };
27779
+ const written = await persistState(projectRoot, state);
27780
+ if (!written.ok) {
27781
+ return fail("openspec scan", [written.error]);
27267
27782
  }
27268
27783
  return {
27269
27784
  code: 0,
27270
27785
  data: {
27271
27786
  success: true,
27272
27787
  command: "openspec scan",
27273
- ...scanResult.data
27788
+ ...scan
27274
27789
  }
27275
27790
  };
27276
27791
  }
27277
27792
  async function handlePlan(projectRoot, itemId) {
27278
- const validateFirst = await handleValidate(projectRoot);
27793
+ const sessionResult = await loadBacklogSession(projectRoot);
27794
+ const validateFirst = await buildValidationResponse(projectRoot, sessionResult);
27279
27795
  if (validateFirst.code !== 0)
27280
27796
  return validateFirst;
27281
- const loadResult = await loadBacklog(projectRoot);
27282
- if (!loadResult.success) {
27283
- return { code: 1, data: { success: false, errors: [loadResult.error.message] } };
27284
- }
27285
- const doc = loadResult.data;
27797
+ if (!sessionResult.success)
27798
+ return validateFirst;
27799
+ const session = sessionResult.data;
27800
+ const doc = session.doc;
27286
27801
  const item = selectNextItem(doc, itemId);
27287
27802
  if (!item) {
27288
27803
  return {
@@ -27296,13 +27811,7 @@ async function handlePlan(projectRoot, itemId) {
27296
27811
  }
27297
27812
  };
27298
27813
  }
27299
- const stateResult = await loadOpenspecState(projectRoot);
27300
- const existingState = stateResult.success ? stateResult.data : {
27301
- version: 1,
27302
- taskCards: [],
27303
- changePaths: {},
27304
- itemSnapshots: {}
27305
- };
27814
+ const existingState = session.state;
27306
27815
  if (item.status !== "PLANNED" && !canTransition(item.status, "PLANNED")) {
27307
27816
  return fail("openspec plan", [
27308
27817
  `Illegal backlog transition for ${item.id}: ${item.status} → PLANNED.`
@@ -27333,8 +27842,7 @@ async function handlePlan(projectRoot, itemId) {
27333
27842
  if (!written.ok) {
27334
27843
  return fail("openspec plan", [written.error]);
27335
27844
  }
27336
- const content = await readBacklogMarkdown(projectRoot);
27337
- const transition = applyBacklogTransition(content, item.id, item.status, "PLANNED", item.progress);
27845
+ const transition = applyBacklogTransition(session.raw, item.id, item.status, "PLANNED", item.progress);
27338
27846
  if (!transition.success) {
27339
27847
  return fail("openspec plan", [transition.error.message]);
27340
27848
  }
@@ -27355,45 +27863,14 @@ async function handlePlan(projectRoot, itemId) {
27355
27863
  }
27356
27864
  async function handleAnalyze(projectRoot, itemId) {
27357
27865
  const command = "openspec analyze";
27358
- const stateResult = await loadOpenspecState(projectRoot);
27359
- if (!stateResult.success) {
27360
- return fail(command, [stateResult.error.message]);
27361
- }
27362
- const targetId = itemId ?? stateResult.data.activeItemId;
27363
- if (!targetId) {
27364
- return fail(command, ["No active change. Run openspec plan or pass an item id."]);
27365
- }
27366
- const changePath = stateResult.data.changePaths[targetId];
27367
- if (!changePath || changePath.includes("/archive/")) {
27368
- return fail(command, [`No active change folder for ${targetId}`]);
27369
- }
27370
- const backlog = await loadBacklog(projectRoot);
27371
- const tddRequired = backlog.success ? backlog.data.global.tddRequired : false;
27372
- const policyParts = [];
27373
- for (const name of ["AGENTS.md", "BACKLOG.md"]) {
27374
- try {
27375
- policyParts.push(await readFile38(resolve36(projectRoot, name), "utf-8"));
27376
- } catch {}
27377
- }
27378
- const cards = itemCards(stateResult.data, targetId);
27379
- const tddCards = cards.filter((c) => c.phase === "test" || c.phase === "implement");
27380
- let hasTddEvidence;
27381
- if (tddCards.length > 0) {
27382
- hasTddEvidence = true;
27383
- for (const card of tddCards) {
27384
- const expectedPhase = card.phase === "test" ? "red" : "green";
27385
- if (!await hasTddRunnerEvidence(projectRoot, card.id, expectedPhase)) {
27386
- hasTddEvidence = false;
27387
- break;
27388
- }
27389
- }
27390
- }
27391
- const report = await analyzeChangeFolder(projectRoot, changePath, {
27392
- tddRequired,
27393
- policyText: policyParts.join(`
27394
- `),
27395
- hasTddEvidence
27396
- });
27866
+ const sessionResult = await loadBacklogSession(projectRoot);
27867
+ if (!sessionResult.success)
27868
+ return fail(command, [sessionResult.error.message]);
27869
+ const session = sessionResult.data;
27870
+ const resolved = resolveActiveChange(session.state, command, itemId);
27871
+ if (!resolved.ok)
27872
+ return resolved.response;
27873
+ const { report } = await collectAnalyzeReport(projectRoot, session, resolved.targetId, resolved.changePath);
27397
27874
  const data = SpecAnalyzeReportSchema.parse({
27398
27875
  changePath: report.changePath,
27399
27876
  frIds: report.frIds,
@@ -27417,13 +27894,146 @@ async function handleAnalyze(projectRoot, itemId) {
27417
27894
  }
27418
27895
  };
27419
27896
  }
27897
+ async function handleSync(projectRoot, itemId) {
27898
+ const command = "openspec sync";
27899
+ const loaded = await loadStateOrFail(projectRoot, command);
27900
+ if (!loaded.ok)
27901
+ return loaded.response;
27902
+ const resolved = resolveActiveChange(loaded.state, command, itemId);
27903
+ if (!resolved.ok)
27904
+ return resolved.response;
27905
+ const synced = await syncChangeSpecs(projectRoot, resolved.changePath);
27906
+ if (!synced.success) {
27907
+ return fail(command, [
27908
+ `Spec sync failed for ${resolved.targetId}: ${synced.errors.join("; ")}`
27909
+ ]);
27910
+ }
27911
+ return {
27912
+ code: 0,
27913
+ data: {
27914
+ success: true,
27915
+ command,
27916
+ itemId: resolved.targetId,
27917
+ changePath: resolved.changePath,
27918
+ syncedPaths: synced.syncedPaths
27919
+ }
27920
+ };
27921
+ }
27922
+ function analyzeSeverityToVerify(severity) {
27923
+ if (severity === "CRITICAL")
27924
+ return "CRITICAL";
27925
+ if (severity === "HIGH")
27926
+ return "WARNING";
27927
+ return "SUGGESTION";
27928
+ }
27929
+ async function handleVerify(projectRoot, itemId) {
27930
+ const command = "openspec verify";
27931
+ const sessionResult = await loadBacklogSession(projectRoot);
27932
+ if (!sessionResult.success)
27933
+ return fail(command, [sessionResult.error.message]);
27934
+ const session = sessionResult.data;
27935
+ const resolved = resolveActiveChange(session.state, command, itemId);
27936
+ if (!resolved.ok)
27937
+ return resolved.response;
27938
+ const { targetId, changePath } = resolved;
27939
+ const itemLoaded = findItemOrFail(session, targetId, command);
27940
+ if (!itemLoaded.ok)
27941
+ return itemLoaded.response;
27942
+ const { report } = await collectAnalyzeReport(projectRoot, session, targetId, changePath);
27943
+ const progress = await readArtifactProgress(projectRoot, changePath);
27944
+ const cards = itemCards(session.state, targetId);
27945
+ const doneCount = cards.filter((c) => c.status === "done").length;
27946
+ const issues = [];
27947
+ for (const finding of report.findings) {
27948
+ const dimension = finding.code === "POLICY_CONFLICT" || finding.code === "NEEDS_CLARIFICATION" ? "Coherence" : "Correctness";
27949
+ issues.push({
27950
+ severity: analyzeSeverityToVerify(finding.severity),
27951
+ code: finding.code,
27952
+ message: finding.message,
27953
+ dimension
27954
+ });
27955
+ }
27956
+ if (cards.length === 0) {
27957
+ issues.push({
27958
+ severity: "WARNING",
27959
+ code: "NO_CARDS",
27960
+ message: `No task cards for ${targetId}. Run openspec plan ${targetId}.`,
27961
+ dimension: "Completeness"
27962
+ });
27963
+ } else if (doneCount < cards.length) {
27964
+ const pending = cards.filter((c) => c.status !== "done").map((c) => c.id);
27965
+ issues.push({
27966
+ severity: "WARNING",
27967
+ code: "CARDS_PENDING",
27968
+ message: `${cards.length - doneCount} card(s) not done (${pending.join(", ")}).`,
27969
+ dimension: "Completeness"
27970
+ });
27971
+ }
27972
+ for (const name of missingArtifacts(progress.artifacts)) {
27973
+ issues.push({
27974
+ severity: "WARNING",
27975
+ code: "ARTIFACT_MISSING",
27976
+ message: `Change folder is missing ${name}.`,
27977
+ dimension: "Completeness"
27978
+ });
27979
+ }
27980
+ if (progress.checkboxes.remaining > 0) {
27981
+ issues.push({
27982
+ severity: "WARNING",
27983
+ code: "CHECKBOXES_REMAINING",
27984
+ message: `${progress.checkboxes.remaining} of ${progress.checkboxes.total} tasks.md boxes are unchecked. ` + "Tick them as FRs complete (only [x]/[X] counts).",
27985
+ dimension: "Completeness"
27986
+ });
27987
+ }
27988
+ const scorecardOk = !itemLoaded.reviewRequired || await hasPassingScorecard(projectRoot, targetId);
27989
+ if (!scorecardOk) {
27990
+ issues.push({
27991
+ severity: "WARNING",
27992
+ code: "SCORECARD_PENDING",
27993
+ message: `Global review is required but no PASS scorecard exists for ${targetId}.`,
27994
+ dimension: "Coherence"
27995
+ });
27996
+ }
27997
+ const archiveReady = !issues.some((i) => i.severity === "CRITICAL") && cards.length > 0 && doneCount === cards.length && missingArtifacts(progress.artifacts).length === 0 && scorecardOk;
27998
+ return {
27999
+ code: 0,
28000
+ data: {
28001
+ success: true,
28002
+ command,
28003
+ advisory: true,
28004
+ itemId: targetId,
28005
+ changePath,
28006
+ archiveReady,
28007
+ artifacts: progress.artifacts,
28008
+ checkboxProgress: progress.checkboxes,
28009
+ cardsTotal: cards.length,
28010
+ cardsDone: doneCount,
28011
+ frCoveragePct: report.frCoveragePct,
28012
+ scCoveragePct: report.scCoveragePct,
28013
+ testCoveragePct: report.testCoveragePct,
28014
+ issues
28015
+ }
28016
+ };
28017
+ }
27420
28018
  async function handleStatus2(projectRoot) {
27421
- const loadResult = await loadBacklog(projectRoot);
27422
- const stateResult = await loadOpenspecState(projectRoot);
27423
- const nextItem = loadResult.success ? selectNextItem(loadResult.data) : null;
27424
- const state = stateResult.success ? stateResult.data : null;
28019
+ const sessionResult = await loadBacklogSession(projectRoot);
28020
+ let doc = null;
28021
+ let state = null;
28022
+ if (sessionResult.success) {
28023
+ doc = sessionResult.data.doc;
28024
+ state = sessionResult.data.state;
28025
+ } else if (sessionResult.error instanceof BacklogSessionError) {
28026
+ doc = sessionResult.error.doc;
28027
+ state = sessionResult.error.state;
28028
+ } else {
28029
+ const fallback = await loadOpenspecState(projectRoot);
28030
+ state = fallback.success ? fallback.data : null;
28031
+ }
28032
+ const nextItem = doc ? selectNextItem(doc) : null;
27425
28033
  const pendingCards = state?.taskCards.filter((c) => c.status === "pending").length ?? 0;
27426
28034
  const doneCards = state?.taskCards.filter((c) => c.status === "done").length ?? 0;
28035
+ const changePath = state?.activeItemId ? state.changePaths[state.activeItemId] : undefined;
28036
+ const progress = changePath && !changePath.includes("/archive/") ? await readArtifactProgress(projectRoot, changePath) : undefined;
27427
28037
  return {
27428
28038
  code: 0,
27429
28039
  data: {
@@ -27434,7 +28044,10 @@ async function handleStatus2(projectRoot) {
27434
28044
  nextItemTitle: nextItem?.title,
27435
28045
  taskCardsPending: pendingCards,
27436
28046
  taskCardsDone: doneCards,
27437
- changePaths: state?.changePaths ?? {}
28047
+ changePaths: state?.changePaths ?? {},
28048
+ artifacts: progress?.artifacts,
28049
+ checkboxProgress: progress?.checkboxes,
28050
+ nextSteps: buildNextSteps({ state, nextItemId: nextItem?.id, progress })
27438
28051
  }
27439
28052
  };
27440
28053
  }
@@ -27486,19 +28099,15 @@ async function loadStateOrFail(projectRoot, command) {
27486
28099
  }
27487
28100
  return { ok: true, state: stateResult.data };
27488
28101
  }
27489
- async function loadItemOrFail(projectRoot, itemId, command) {
27490
- const loadResult = await loadBacklog(projectRoot);
27491
- if (!loadResult.success) {
27492
- return { ok: false, response: fail(command, [loadResult.error.message]) };
27493
- }
27494
- const item = loadResult.data.items.find((i) => i.id === itemId) ?? loadResult.data.archive.find((i) => i.id === itemId);
28102
+ function findItemOrFail(session, itemId, command) {
28103
+ const item = session.doc.items.find((i) => i.id === itemId) ?? session.doc.archive.find((i) => i.id === itemId);
27495
28104
  if (!item) {
27496
28105
  return { ok: false, response: fail(command, [`Backlog item ${itemId} not found`]) };
27497
28106
  }
27498
28107
  return {
27499
28108
  ok: true,
27500
28109
  item,
27501
- reviewRequired: loadResult.data.global.reviewRequired
28110
+ reviewRequired: session.doc.global.reviewRequired
27502
28111
  };
27503
28112
  }
27504
28113
  async function handleStart(projectRoot, cardId) {
@@ -27506,27 +28115,28 @@ async function handleStart(projectRoot, cardId) {
27506
28115
  if (!cardId) {
27507
28116
  return fail(command, ["Missing card id. Usage: openspec start <cardId>"]);
27508
28117
  }
27509
- const loaded = await loadStateOrFail(projectRoot, command);
27510
- if (!loaded.ok)
27511
- return loaded.response;
27512
- const card = findCard(loaded.state, cardId);
28118
+ const sessionResult = await loadBacklogSession(projectRoot);
28119
+ if (!sessionResult.success)
28120
+ return fail(command, [sessionResult.error.message]);
28121
+ const session = sessionResult.data;
28122
+ const card = findCard(session.state, cardId);
27513
28123
  if (!card) {
27514
28124
  return fail(command, [`Task card ${cardId} not found`]);
27515
28125
  }
27516
28126
  if (card.status !== "pending" && card.status !== "doing") {
27517
28127
  return fail(command, [`Card ${cardId} cannot start from status ${card.status}`]);
27518
28128
  }
27519
- const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
28129
+ const itemLoaded = findItemOrFail(session, card.backlogId, command);
27520
28130
  if (!itemLoaded.ok)
27521
28131
  return itemLoaded.response;
27522
- let state = loaded.state;
28132
+ let state = session.state;
27523
28133
  if (card.status === "pending") {
27524
28134
  state = setTaskCardStatus(state, cardId, "doing");
27525
28135
  const written = await persistState(projectRoot, state);
27526
28136
  if (!written.ok)
27527
28137
  return fail(command, [written.error]);
27528
28138
  }
27529
- const transition = await transitionItem(projectRoot, itemLoaded.item, "IN_PROGRESS", itemLoaded.item.progress);
28139
+ const transition = await transitionItem(projectRoot, itemLoaded.item, "IN_PROGRESS", itemLoaded.item.progress, session.raw);
27530
28140
  if (!transition.ok)
27531
28141
  return fail(command, [transition.error]);
27532
28142
  return {
@@ -27546,10 +28156,11 @@ async function handleDone(projectRoot, cardId) {
27546
28156
  if (!cardId) {
27547
28157
  return fail(command, ["Missing card id. Usage: openspec done <cardId>"]);
27548
28158
  }
27549
- const loaded = await loadStateOrFail(projectRoot, command);
27550
- if (!loaded.ok)
27551
- return loaded.response;
27552
- const card = findCard(loaded.state, cardId);
28159
+ const sessionResult = await loadBacklogSession(projectRoot);
28160
+ if (!sessionResult.success)
28161
+ return fail(command, [sessionResult.error.message]);
28162
+ const session = sessionResult.data;
28163
+ const card = findCard(session.state, cardId);
27553
28164
  if (!card) {
27554
28165
  return fail(command, [`Task card ${cardId} not found`]);
27555
28166
  }
@@ -27562,11 +28173,10 @@ async function handleDone(projectRoot, cardId) {
27562
28173
  if (card.status !== "doing") {
27563
28174
  return fail(command, [`Card ${cardId} must be doing before done (got ${card.status})`]);
27564
28175
  }
27565
- const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
28176
+ const itemLoaded = findItemOrFail(session, card.backlogId, command);
27566
28177
  if (!itemLoaded.ok)
27567
28178
  return itemLoaded.response;
27568
- const backlog = await loadBacklog(projectRoot);
27569
- const tddRequired = backlog.success ? backlog.data.global.tddRequired : false;
28179
+ const tddRequired = session.doc.global.tddRequired;
27570
28180
  if (tddRequired && (card.phase === "test" || card.phase === "implement")) {
27571
28181
  const expectedPhase = card.phase === "test" ? "red" : "green";
27572
28182
  const evidenced = await hasTddRunnerEvidence(projectRoot, cardId, expectedPhase);
@@ -27576,7 +28186,7 @@ async function handleDone(projectRoot, cardId) {
27576
28186
  ]);
27577
28187
  }
27578
28188
  }
27579
- const state = setTaskCardStatus(loaded.state, cardId, "done");
28189
+ const state = setTaskCardStatus(session.state, cardId, "done");
27580
28190
  const cards = itemCards(state, card.backlogId);
27581
28191
  const doneCount = cards.filter((c) => c.status === "done").length;
27582
28192
  const progress = cards.length === 0 ? 0 : Math.round(doneCount / cards.length * 100);
@@ -27592,7 +28202,7 @@ async function handleDone(projectRoot, cardId) {
27592
28202
  });
27593
28203
  }
27594
28204
  const nextStatus = allDone ? "REVIEW" : "IN_PROGRESS";
27595
- const transition = await transitionItem(projectRoot, itemLoaded.item, nextStatus, progress);
28205
+ const transition = await transitionItem(projectRoot, itemLoaded.item, nextStatus, progress, session.raw);
27596
28206
  if (!transition.ok)
27597
28207
  return fail(command, [transition.error]);
27598
28208
  return {
@@ -27617,21 +28227,22 @@ async function handleBlock(projectRoot, cardId, reason) {
27617
28227
  if (!reason || !reason.trim()) {
27618
28228
  return fail(command, ["--reason is required"]);
27619
28229
  }
27620
- const loaded = await loadStateOrFail(projectRoot, command);
27621
- if (!loaded.ok)
27622
- return loaded.response;
27623
- const card = findCard(loaded.state, cardId);
28230
+ const sessionResult = await loadBacklogSession(projectRoot);
28231
+ if (!sessionResult.success)
28232
+ return fail(command, [sessionResult.error.message]);
28233
+ const session = sessionResult.data;
28234
+ const card = findCard(session.state, cardId);
27624
28235
  if (!card) {
27625
28236
  return fail(command, [`Task card ${cardId} not found`]);
27626
28237
  }
27627
- const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
28238
+ const itemLoaded = findItemOrFail(session, card.backlogId, command);
27628
28239
  if (!itemLoaded.ok)
27629
28240
  return itemLoaded.response;
27630
- const state = setTaskCardStatus(loaded.state, cardId, "blocked");
28241
+ const state = setTaskCardStatus(session.state, cardId, "blocked");
27631
28242
  const written = await persistState(projectRoot, state);
27632
28243
  if (!written.ok)
27633
28244
  return fail(command, [written.error]);
27634
- const transition = await transitionItem(projectRoot, itemLoaded.item, "BLOCKED");
28245
+ const transition = await transitionItem(projectRoot, itemLoaded.item, "BLOCKED", undefined, session.raw);
27635
28246
  if (!transition.ok)
27636
28247
  return fail(command, [transition.error]);
27637
28248
  return {
@@ -27652,17 +28263,18 @@ async function handleUnblock(projectRoot, cardId) {
27652
28263
  if (!cardId) {
27653
28264
  return fail(command, ["Missing card id. Usage: openspec unblock <cardId>"]);
27654
28265
  }
27655
- const loaded = await loadStateOrFail(projectRoot, command);
27656
- if (!loaded.ok)
27657
- return loaded.response;
27658
- const card = findCard(loaded.state, cardId);
28266
+ const sessionResult = await loadBacklogSession(projectRoot);
28267
+ if (!sessionResult.success)
28268
+ return fail(command, [sessionResult.error.message]);
28269
+ const session = sessionResult.data;
28270
+ const card = findCard(session.state, cardId);
27659
28271
  if (!card) {
27660
28272
  return fail(command, [`Task card ${cardId} not found`]);
27661
28273
  }
27662
28274
  if (card.status !== "blocked") {
27663
28275
  return fail(command, [`Card ${cardId} must be blocked before unblock (got ${card.status})`]);
27664
28276
  }
27665
- const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
28277
+ const itemLoaded = findItemOrFail(session, card.backlogId, command);
27666
28278
  if (!itemLoaded.ok)
27667
28279
  return itemLoaded.response;
27668
28280
  if (itemLoaded.item.status !== "BLOCKED") {
@@ -27670,19 +28282,18 @@ async function handleUnblock(projectRoot, cardId) {
27670
28282
  `Backlog item ${itemLoaded.item.id} must be BLOCKED before unblock (got ${itemLoaded.item.status})`
27671
28283
  ]);
27672
28284
  }
27673
- const state = setTaskCardStatus(loaded.state, cardId, "pending");
28285
+ const state = setTaskCardStatus(session.state, cardId, "pending");
27674
28286
  const written = await persistState(projectRoot, state);
27675
28287
  if (!written.ok)
27676
28288
  return fail(command, [written.error]);
27677
28289
  const changePath = state.changePaths[card.backlogId];
27678
28290
  if (changePath) {
27679
- const backlog = await loadBacklog(projectRoot);
27680
28291
  await writeTasksMarkdown(projectRoot, changePath, itemCards(state, card.backlogId), {
27681
- tddRequired: backlog.success ? backlog.data.global.tddRequired : false,
28292
+ tddRequired: session.doc.global.tddRequired,
27682
28293
  acceptanceCriteria: itemLoaded.item.acceptanceCriteria
27683
28294
  });
27684
28295
  }
27685
- const transition = await transitionItem(projectRoot, itemLoaded.item, "READY");
28296
+ const transition = await transitionItem(projectRoot, itemLoaded.item, "READY", undefined, session.raw);
27686
28297
  if (!transition.ok)
27687
28298
  return fail(command, [transition.error]);
27688
28299
  return {
@@ -27703,13 +28314,14 @@ async function handleArchive(projectRoot, itemId) {
27703
28314
  if (!itemId) {
27704
28315
  return fail(command, ["Missing item id. Usage: openspec archive <itemId>"]);
27705
28316
  }
27706
- const loaded = await loadStateOrFail(projectRoot, command);
27707
- if (!loaded.ok)
27708
- return loaded.response;
27709
- const itemLoaded = await loadItemOrFail(projectRoot, itemId, command);
28317
+ const sessionResult = await loadBacklogSession(projectRoot);
28318
+ if (!sessionResult.success)
28319
+ return fail(command, [sessionResult.error.message]);
28320
+ const session = sessionResult.data;
28321
+ const itemLoaded = findItemOrFail(session, itemId, command);
27710
28322
  if (!itemLoaded.ok)
27711
28323
  return itemLoaded.response;
27712
- const cards = itemCards(loaded.state, itemId);
28324
+ const cards = itemCards(session.state, itemId);
27713
28325
  if (cards.length === 0) {
27714
28326
  return fail(command, [`No task cards found for ${itemId}. Run openspec plan first.`]);
27715
28327
  }
@@ -27734,8 +28346,26 @@ async function handleArchive(projectRoot, itemId) {
27734
28346
  }
27735
28347
  }
27736
28348
  let archivedPath;
27737
- const changePath = loaded.state.changePaths[itemId];
28349
+ const changePath = session.state.changePaths[itemId];
28350
+ const warnings = [];
27738
28351
  if (changePath && !changePath.includes("/archive/")) {
28352
+ const progress = await readArtifactProgress(projectRoot, changePath);
28353
+ const missing = missingArtifacts(progress.artifacts);
28354
+ if (missing.length > 0) {
28355
+ return fail(command, [
28356
+ `Cannot archive ${itemId}: change folder is missing ${missing.join(", ")}. Re-run openspec plan ${itemId} or restore them.`
28357
+ ]);
28358
+ }
28359
+ const { report } = await collectAnalyzeReport(projectRoot, session, itemId, changePath);
28360
+ const critical = report.findings.filter((f) => f.severity === "CRITICAL");
28361
+ if (critical.length > 0) {
28362
+ return fail(command, [
28363
+ `Cannot archive ${itemId}: analyze reports ${critical.length} CRITICAL finding(s): ${critical.map((f) => f.code).join(", ")}. Resolve them first.`
28364
+ ]);
28365
+ }
28366
+ if (progress.checkboxes.remaining > 0) {
28367
+ warnings.push(`${progress.checkboxes.remaining} of ${progress.checkboxes.total} tasks.md checkboxes are unchecked; tick them as FRs complete (only [x]/[X] counts as done).`);
28368
+ }
27739
28369
  const synced = await syncChangeSpecs(projectRoot, changePath);
27740
28370
  if (!synced.success) {
27741
28371
  return fail(command, [`Cannot archive ${itemId}: spec sync failed: ${synced.errors.join("; ")}`]);
@@ -27748,18 +28378,19 @@ async function handleArchive(projectRoot, itemId) {
27748
28378
  ]);
27749
28379
  }
27750
28380
  }
28381
+ let content = session.raw;
27751
28382
  if (itemLoaded.item.status !== "DONE") {
27752
- const toDone = await transitionItem(projectRoot, itemLoaded.item, "DONE", 100);
28383
+ const toDone = await transitionItem(projectRoot, itemLoaded.item, "DONE", 100, content);
27753
28384
  if (!toDone.ok)
27754
28385
  return fail(command, [toDone.error]);
28386
+ content = toDone.content;
27755
28387
  }
27756
- const content = await readBacklogMarkdown(projectRoot);
27757
28388
  const archived = archiveItemInMarkdown(content, itemId);
27758
28389
  await persistBacklog(projectRoot, archived);
27759
28390
  const nextState = {
27760
- ...loaded.state,
27761
- activeItemId: loaded.state.activeItemId === itemId ? undefined : loaded.state.activeItemId,
27762
- changePaths: archivedPath ? { ...loaded.state.changePaths, [itemId]: archivedPath } : loaded.state.changePaths
28391
+ ...session.state,
28392
+ activeItemId: session.state.activeItemId === itemId ? undefined : session.state.activeItemId,
28393
+ changePaths: archivedPath ? { ...session.state.changePaths, [itemId]: archivedPath } : session.state.changePaths
27763
28394
  };
27764
28395
  const written = await persistState(projectRoot, nextState);
27765
28396
  if (!written.ok)
@@ -27770,20 +28401,21 @@ async function handleArchive(projectRoot, itemId) {
27770
28401
  success: true,
27771
28402
  command,
27772
28403
  itemId,
27773
- archivedPath: archivedPath ?? changePath
28404
+ archivedPath: archivedPath ?? changePath,
28405
+ warnings
27774
28406
  }
27775
28407
  };
27776
28408
  }
27777
- var KNOWN_SUBCOMMANDS = "validate, scan, plan, analyze, status, next, start, done, block, unblock, archive";
28409
+ var KNOWN_SUBCOMMANDS = "validate, scan, plan, analyze, status, next, start, done, block, unblock, archive, sync, verify";
27778
28410
  var init_openspec_command = __esm(() => {
27779
- init_backlog_parser();
27780
- init_backlog_validator();
28411
+ init_backlog_session();
27781
28412
  init_backlog_scanner();
27782
28413
  init_backlog_planner();
27783
28414
  init_openspec_generator();
27784
28415
  init_spec_quality();
27785
28416
  init_spec_analyzer();
27786
28417
  init_spec_sync();
28418
+ init_artifact_progress();
27787
28419
  init_schemas();
27788
28420
  init_verification_runner();
27789
28421
  init_outcome_store();
@@ -27841,24 +28473,24 @@ var init_handoff_envelope = __esm(() => {
27841
28473
 
27842
28474
  // src/core/delivery/delivery-ledger.ts
27843
28475
  import { createHash as createHash7 } from "node:crypto";
27844
- import { mkdir as mkdir24, readFile as readFile39, writeFile as writeFile23 } from "node:fs/promises";
27845
- import { resolve as resolve37, sep as sep8 } from "node:path";
28476
+ import { mkdir as mkdir23, readFile as readFile41, writeFile as writeFile22 } from "node:fs/promises";
28477
+ import { resolve as resolve38, sep as sep9 } from "node:path";
27846
28478
  function ledgerPath(projectRoot, id) {
27847
28479
  if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id))
27848
28480
  throw new Error("Invalid delivery ledger id");
27849
- return resolve37(projectRoot, DELIVERY_DIR, `${id}.md`);
28481
+ return resolve38(projectRoot, DELIVERY_DIR, `${id}.md`);
27850
28482
  }
27851
28483
  async function fingerprintWorkspace(projectRoot, paths) {
27852
- const root = resolve37(projectRoot);
28484
+ const root = resolve38(projectRoot);
27853
28485
  const fingerprints = {};
27854
28486
  for (const path of paths) {
27855
- const candidate = resolve37(root, path);
27856
- if (candidate !== root && !candidate.startsWith(`${root}${sep8}`)) {
28487
+ const candidate = resolve38(root, path);
28488
+ if (candidate !== root && !candidate.startsWith(`${root}${sep9}`)) {
27857
28489
  fingerprints[path] = "outside-root";
27858
28490
  continue;
27859
28491
  }
27860
28492
  try {
27861
- fingerprints[path] = createHash7("sha256").update(await readFile39(candidate)).digest("hex");
28493
+ fingerprints[path] = createHash7("sha256").update(await readFile41(candidate)).digest("hex");
27862
28494
  } catch {
27863
28495
  fingerprints[path] = "missing";
27864
28496
  }
@@ -27881,7 +28513,7 @@ function parseLedger(content) {
27881
28513
  return DeliveryLedgerSchema.parse($parse(match[1]));
27882
28514
  }
27883
28515
  async function loadDeliveryLedger(projectRoot, id) {
27884
- return parseLedger(await readFile39(ledgerPath(projectRoot, id), "utf-8"));
28516
+ return parseLedger(await readFile41(ledgerPath(projectRoot, id), "utf-8"));
27885
28517
  }
27886
28518
  async function createDeliveryLedger(projectRoot, input) {
27887
28519
  const request = DeliveryLedgerRequestSchema.parse(input);
@@ -27906,9 +28538,9 @@ async function createDeliveryLedger(projectRoot, input) {
27906
28538
  createdAt: now,
27907
28539
  updatedAt: now
27908
28540
  });
27909
- await mkdir24(resolve37(projectRoot, DELIVERY_DIR), { recursive: true });
28541
+ await mkdir23(resolve38(projectRoot, DELIVERY_DIR), { recursive: true });
27910
28542
  try {
27911
- await writeFile23(path, render(ledger), { encoding: "utf-8", flag: "wx" });
28543
+ await writeFile22(path, render(ledger), { encoding: "utf-8", flag: "wx" });
27912
28544
  return { created: true, ledger };
27913
28545
  } catch (error) {
27914
28546
  if (error instanceof Error && error.message.includes("EEXIST")) {
@@ -28151,6 +28783,9 @@ function formatHookOutput(verdict, format) {
28151
28783
  function claudeExitCode(verdict) {
28152
28784
  return verdict.action === "deny" ? 2 : 0;
28153
28785
  }
28786
+ function parseMusePayload(stdinText) {
28787
+ return parseClaudePayload(stdinText);
28788
+ }
28154
28789
  function parseAgyPayload(raw) {
28155
28790
  if (!raw.trim())
28156
28791
  return {};
@@ -28232,12 +28867,16 @@ var init_hook_runner = __esm(() => {
28232
28867
  var exports_hook_command = {};
28233
28868
  __export(exports_hook_command, {
28234
28869
  hookCommand: () => hookCommand,
28870
+ parseHookFormat: () => parseHookFormat,
28235
28871
  readStdinText: () => readStdinText
28236
28872
  });
28237
28873
  import { spawnSync } from "node:child_process";
28238
28874
  import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
28239
28875
  import { createRequire as createRequire2 } from "node:module";
28240
- import { dirname as dirname13, resolve as resolve38 } from "node:path";
28876
+ import { dirname as dirname12, resolve as resolve39 } from "node:path";
28877
+ function parseHookFormat(raw) {
28878
+ return raw === "agy" || raw === "claude" || raw === "muse" ? raw : undefined;
28879
+ }
28241
28880
  function detectFormat(explicit, stdinText) {
28242
28881
  if (explicit)
28243
28882
  return explicit;
@@ -28258,7 +28897,7 @@ async function readStdinText() {
28258
28897
  return Buffer.concat(chunks).toString("utf8");
28259
28898
  }
28260
28899
  function resolvePreToolInput(options, stdinText, format) {
28261
- const fromStdin = format === "agy" ? parseAgyPayload(stdinText) : parseClaudePayload(stdinText);
28900
+ const fromStdin = format === "agy" ? parseAgyPayload(stdinText) : format === "muse" ? parseMusePayload(stdinText) : parseClaudePayload(stdinText);
28262
28901
  return {
28263
28902
  command: options.command ?? (process.env.CLAUDE_TOOL_INPUT_COMMAND || undefined) ?? fromStdin.command,
28264
28903
  filePath: options.filePath ?? (process.env.CLAUDE_TOOL_INPUT_FILE_PATH || undefined) ?? fromStdin.filePath,
@@ -28266,8 +28905,8 @@ function resolvePreToolInput(options, stdinText, format) {
28266
28905
  };
28267
28906
  }
28268
28907
  async function sessionLines(projectRoot) {
28269
- const backlogPath = resolve38(projectRoot, "BACKLOG.md");
28270
- const profilePath = resolve38(projectRoot, ".codeconductor", "evaluation", "execution-profile.yml");
28908
+ const backlogPath = resolve39(projectRoot, "BACKLOG.md");
28909
+ const profilePath = resolve39(projectRoot, ".codeconductor", "evaluation", "execution-profile.yml");
28271
28910
  return [
28272
28911
  "[CodeConductor] Session start",
28273
28912
  "Skills: openspec, backlog, evaluation, testing-tdd — invoke via /cc-* then CLI.",
@@ -28322,21 +28961,21 @@ async function hookCommand(options) {
28322
28961
  function formatWrittenFile(filePath, projectRoot) {
28323
28962
  if (!filePath)
28324
28963
  return;
28325
- filePath = resolve38(projectRoot, filePath);
28964
+ filePath = resolve39(projectRoot, filePath);
28326
28965
  if (!existsSync8(filePath))
28327
28966
  return;
28328
28967
  const prettier = /\.(ts|tsx|js|jsx|mjs|cjs|json|jsonc|md|mdx|css|scss|html|astro|ya?ml)$/i;
28329
28968
  const eslint = /\.(ts|tsx|js|jsx|mjs|cjs)$/i;
28330
28969
  const python = /\.py$/i;
28331
28970
  const opts = { cwd: projectRoot, stdio: "ignore", windowsHide: true, timeout: 5000 };
28332
- const require2 = createRequire2(resolve38(projectRoot, "package.json"));
28971
+ const require2 = createRequire2(resolve39(projectRoot, "package.json"));
28333
28972
  function runNodeTool(name, args) {
28334
28973
  try {
28335
28974
  const manifestPath = require2.resolve(`${name}/package.json`);
28336
28975
  const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
28337
28976
  const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[name];
28338
28977
  if (typeof bin === "string")
28339
- spawnSync(process.execPath, [resolve38(dirname13(manifestPath), bin), ...args], opts);
28978
+ spawnSync(process.execPath, [resolve39(dirname12(manifestPath), bin), ...args], opts);
28340
28979
  } catch {}
28341
28980
  }
28342
28981
  if (prettier.test(filePath)) {
@@ -28875,24 +29514,24 @@ var init_scorecard_signals = __esm(() => {
28875
29514
 
28876
29515
  // src/core/evaluation/harness-experiment.ts
28877
29516
  import { existsSync as existsSync9 } from "node:fs";
28878
- import { cp, mkdir as mkdir25, readFile as readFile40, readdir as readdir14, writeFile as writeFile24 } from "node:fs/promises";
28879
- import { dirname as dirname14, join as join26, resolve as resolve39 } from "node:path";
29517
+ import { cp, mkdir as mkdir24, readFile as readFile42, readdir as readdir13, writeFile as writeFile23 } from "node:fs/promises";
29518
+ import { dirname as dirname13, join as join29, resolve as resolve40 } from "node:path";
28880
29519
  function experimentDir(projectRoot, experimentId) {
28881
- return resolve39(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR, experimentId);
29520
+ return resolve40(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR, experimentId);
28882
29521
  }
28883
29522
  function experimentYamlPath(projectRoot, experimentId) {
28884
- return join26(experimentDir(projectRoot, experimentId), "experiment.yml");
29523
+ return join29(experimentDir(projectRoot, experimentId), "experiment.yml");
28885
29524
  }
28886
29525
  async function loadHarnessSuite(projectRoot, suiteId, suitePath) {
28887
29526
  const candidates = suitePath ? [suitePath] : [
28888
- resolve39(projectRoot, "eval", "suites", suiteId, "suite.yml"),
28889
- resolve39(projectRoot, EVAL_DIR2, "suites", suiteId, "suite.yml")
29527
+ resolve40(projectRoot, "eval", "suites", suiteId, "suite.yml"),
29528
+ resolve40(projectRoot, EVAL_DIR2, "suites", suiteId, "suite.yml")
28890
29529
  ];
28891
29530
  for (const path of candidates) {
28892
29531
  if (!existsSync9(path))
28893
29532
  continue;
28894
29533
  try {
28895
- const suite = HarnessSuiteSchema.parse($parse(await readFile40(path, "utf-8")));
29534
+ const suite = HarnessSuiteSchema.parse($parse(await readFile42(path, "utf-8")));
28896
29535
  return ok({ ...suite, path });
28897
29536
  } catch (e) {
28898
29537
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -28902,7 +29541,7 @@ async function loadHarnessSuite(projectRoot, suiteId, suitePath) {
28902
29541
  }
28903
29542
  async function loadExperiment(projectRoot, experimentId) {
28904
29543
  try {
28905
- const raw = await readFile40(experimentYamlPath(projectRoot, experimentId), "utf-8");
29544
+ const raw = await readFile42(experimentYamlPath(projectRoot, experimentId), "utf-8");
28906
29545
  return ok(HarnessExperimentSchema.parse($parse(raw)));
28907
29546
  } catch (e) {
28908
29547
  if (e.code === "ENOENT") {
@@ -28912,10 +29551,10 @@ async function loadExperiment(projectRoot, experimentId) {
28912
29551
  }
28913
29552
  }
28914
29553
  async function listExperiments(projectRoot) {
28915
- const root = resolve39(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR);
29554
+ const root = resolve40(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR);
28916
29555
  if (!existsSync9(root))
28917
29556
  return [];
28918
- const ids = await readdir14(root);
29557
+ const ids = await readdir13(root);
28919
29558
  const experiments = [];
28920
29559
  for (const id of ids) {
28921
29560
  const loaded = await loadExperiment(projectRoot, id);
@@ -28991,8 +29630,8 @@ function generateExperimentId(suiteId, now = new Date) {
28991
29630
  return `abl-${date}-${suiteId}-${rand}`;
28992
29631
  }
28993
29632
  async function copyFixture(suitePath, fixtureDir, dest) {
28994
- const source = fixtureDir ? resolve39(dirname14(suitePath), fixtureDir) : join26(dirname14(suitePath), "fixture");
28995
- await mkdir25(dest, { recursive: true });
29633
+ const source = fixtureDir ? resolve40(dirname13(suitePath), fixtureDir) : join29(dirname13(suitePath), "fixture");
29634
+ await mkdir24(dest, { recursive: true });
28996
29635
  if (existsSync9(source)) {
28997
29636
  await cp(source, dest, { recursive: true });
28998
29637
  }
@@ -29014,12 +29653,12 @@ async function startHarnessExperiment(projectRoot, options) {
29014
29653
  suitePath: suite.data.path
29015
29654
  };
29016
29655
  const root = experimentDir(projectRoot, experimentId);
29017
- await mkdir25(root, { recursive: true });
29018
- await writeFile24(experimentYamlPath(projectRoot, experimentId), $stringify(experiment), "utf-8");
29019
- await writeFile24(join26(root, "EXPERIMENT.md"), formatExperimentMarkdown(experiment), "utf-8");
29656
+ await mkdir24(root, { recursive: true });
29657
+ await writeFile23(experimentYamlPath(projectRoot, experimentId), $stringify(experiment), "utf-8");
29658
+ await writeFile23(join29(root, "EXPERIMENT.md"), formatExperimentMarkdown(experiment), "utf-8");
29020
29659
  for (const task of suite.data.tasks) {
29021
29660
  for (const variantId of variants) {
29022
- const runRoot = join26(root, "runs", task.id, variantDirName(variantId));
29661
+ const runRoot = join29(root, "runs", task.id, variantDirName(variantId));
29023
29662
  await copyFixture(suite.data.path, suite.data.fixtureDir, runRoot);
29024
29663
  await writeConfig(runRoot, structuredClone(DEFAULT_CONFIG), true);
29025
29664
  const disabled = variantId === "baseline" ? [] : options.components.filter((id) => variantIdFor(id) === variantId);
@@ -29031,7 +29670,7 @@ async function startHarnessExperiment(projectRoot, options) {
29031
29670
  });
29032
29671
  if (!overlay.success)
29033
29672
  return overlay;
29034
- await writeFile24(join26(runRoot, "TASK.md"), formatTaskCard(task, experimentId, variantId), "utf-8");
29673
+ await writeFile23(join29(runRoot, "TASK.md"), formatTaskCard(task, experimentId, variantId), "utf-8");
29035
29674
  }
29036
29675
  }
29037
29676
  return ok(experiment);
@@ -29066,7 +29705,7 @@ var init_harness_experiment = __esm(() => {
29066
29705
  // src/core/evaluation/suite-run.ts
29067
29706
  import { spawnSync as spawnSync2 } from "node:child_process";
29068
29707
  import { existsSync as existsSync10 } from "node:fs";
29069
- import { resolve as resolve40 } from "node:path";
29708
+ import { resolve as resolve41 } from "node:path";
29070
29709
  async function runHarnessSuiteTasks(projectRoot, suiteId, suitePath) {
29071
29710
  const suite = await loadHarnessSuite(projectRoot, suiteId, suitePath);
29072
29711
  if (!suite.success) {
@@ -29126,7 +29765,7 @@ function runTaskCommand(projectRoot, task) {
29126
29765
  if (parts.length === 0) {
29127
29766
  return { ok: false, detail: "Empty testCommand" };
29128
29767
  }
29129
- const bin = parts[0] === "bun" && existsSync10(resolve40(projectRoot, "src/cli/main.ts")) ? "bun" : parts[0];
29768
+ const bin = parts[0] === "bun" && existsSync10(resolve41(projectRoot, "src/cli/main.ts")) ? "bun" : parts[0];
29130
29769
  const args = parts[0] === "bun" ? parts.slice(1) : parts.slice(1);
29131
29770
  const result = spawnSync2(bin, args, {
29132
29771
  cwd: projectRoot,
@@ -29177,11 +29816,11 @@ var init_suite_run = __esm(() => {
29177
29816
  });
29178
29817
 
29179
29818
  // src/core/evaluation/execution-profile.ts
29180
- import { readFile as readFile41 } from "node:fs/promises";
29181
- import { resolve as resolve41 } from "node:path";
29819
+ import { readFile as readFile43 } from "node:fs/promises";
29820
+ import { resolve as resolve42 } from "node:path";
29182
29821
  async function loadExecutionProfile(projectRoot, defaultTarget = "opencode") {
29183
29822
  try {
29184
- const content = await readFile41(resolve41(projectRoot, PROFILE_FILE), "utf-8");
29823
+ const content = await readFile43(resolve42(projectRoot, PROFILE_FILE), "utf-8");
29185
29824
  const data = $parse(content);
29186
29825
  return ExecutionProfileSchema.parse(data);
29187
29826
  } catch {
@@ -29261,14 +29900,14 @@ var init_execution_profile = __esm(() => {
29261
29900
  });
29262
29901
 
29263
29902
  // src/core/evaluation/prompt-diff.ts
29264
- import { readFile as readFile42, readdir as readdir15 } from "node:fs/promises";
29265
- import { join as join27, resolve as resolve42 } from "node:path";
29903
+ import { readFile as readFile44, readdir as readdir14 } from "node:fs/promises";
29904
+ import { join as join30, resolve as resolve43 } from "node:path";
29266
29905
  function normalizeVersion(v) {
29267
29906
  return v.startsWith("v") ? v : `v${v}`;
29268
29907
  }
29269
29908
  async function listPromptFiles(dir) {
29270
29909
  try {
29271
- const entries = await readdir15(dir, { withFileTypes: true });
29910
+ const entries = await readdir14(dir, { withFileTypes: true });
29272
29911
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
29273
29912
  } catch {
29274
29913
  return [];
@@ -29311,30 +29950,30 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
29311
29950
  const fromV = normalizeVersion(fromVersion);
29312
29951
  const toV = normalizeVersion(toVersion);
29313
29952
  const searchRoots = [
29314
- resolve42(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
29315
- join27(ROOT_PRESETS_DIR, target, "prompts"),
29316
- join27(ROOT_PRESETS_DIR, "opencode", "prompts")
29953
+ resolve43(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
29954
+ join30(ROOT_PRESETS_DIR, target, "prompts"),
29955
+ join30(ROOT_PRESETS_DIR, "opencode", "prompts")
29317
29956
  ];
29318
29957
  let fromDir = "";
29319
29958
  let toDir = "";
29320
29959
  for (const root of searchRoots) {
29321
- const f = join27(root, fromV);
29322
- const t = join27(root, toV);
29960
+ const f = join30(root, fromV);
29961
+ const t = join30(root, toV);
29323
29962
  try {
29324
- await readFile42(join27(f, "orchestrator.md"), "utf-8");
29963
+ await readFile44(join30(f, "orchestrator.md"), "utf-8");
29325
29964
  fromDir = f;
29326
29965
  toDir = t;
29327
29966
  break;
29328
29967
  } catch {}
29329
29968
  }
29330
29969
  if (!fromDir) {
29331
- fromDir = join27(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
29332
- toDir = join27(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
29970
+ fromDir = join30(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
29971
+ toDir = join30(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
29333
29972
  } else {
29334
29973
  try {
29335
- await readFile42(join27(toDir, "orchestrator.md"), "utf-8");
29974
+ await readFile44(join30(toDir, "orchestrator.md"), "utf-8");
29336
29975
  } catch {
29337
- toDir = join27(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
29976
+ toDir = join30(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
29338
29977
  }
29339
29978
  }
29340
29979
  const fromFiles = await listPromptFiles(fromDir);
@@ -29352,12 +29991,12 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
29352
29991
  let oldText = "";
29353
29992
  let newText = "";
29354
29993
  try {
29355
- oldText = await readFile42(join27(fromDir, name), "utf-8");
29994
+ oldText = await readFile44(join30(fromDir, name), "utf-8");
29356
29995
  } catch {
29357
29996
  oldText = "";
29358
29997
  }
29359
29998
  try {
29360
- newText = await readFile42(join27(toDir, name), "utf-8");
29999
+ newText = await readFile44(join30(toDir, name), "utf-8");
29361
30000
  } catch {
29362
30001
  newText = "";
29363
30002
  }
@@ -29397,12 +30036,12 @@ var init_prompt_diff = __esm(() => {
29397
30036
 
29398
30037
  // src/core/evaluation/regression-checklist.ts
29399
30038
  import { execFileSync as execFileSync3 } from "node:child_process";
29400
- import { mkdir as mkdir26, readFile as readFile43, writeFile as writeFile25 } from "node:fs/promises";
29401
- import { dirname as dirname15, resolve as resolve43 } from "node:path";
30039
+ import { mkdir as mkdir25, readFile as readFile45, writeFile as writeFile24 } from "node:fs/promises";
30040
+ import { dirname as dirname14, resolve as resolve44 } from "node:path";
29402
30041
  async function loadRegressionChecklist() {
29403
30042
  try {
29404
- const path = resolve43(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
29405
- const content = await readFile43(path, "utf-8");
30043
+ const path = resolve44(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
30044
+ const content = await readFile45(path, "utf-8");
29406
30045
  const data = $parse(content);
29407
30046
  return data.checks ?? [];
29408
30047
  } catch {
@@ -29457,9 +30096,9 @@ async function runRegressionChecklist(projectRoot, options = {}) {
29457
30096
  checks,
29458
30097
  timestamp: new Date().toISOString()
29459
30098
  };
29460
- const outPath = resolve43(projectRoot, ".codeconductor/evaluation/regression-last.json");
29461
- await mkdir26(dirname15(outPath), { recursive: true });
29462
- await writeFile25(outPath, JSON.stringify(report, null, 2), "utf-8");
30099
+ const outPath = resolve44(projectRoot, ".codeconductor/evaluation/regression-last.json");
30100
+ await mkdir25(dirname14(outPath), { recursive: true });
30101
+ await writeFile24(outPath, JSON.stringify(report, null, 2), "utf-8");
29463
30102
  return ok(report);
29464
30103
  } catch (e) {
29465
30104
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -29501,8 +30140,8 @@ var init_model_comparison = __esm(() => {
29501
30140
  });
29502
30141
 
29503
30142
  // src/core/evaluation/cost-quality-matrix.ts
29504
- import { writeFile as writeFile26 } from "node:fs/promises";
29505
- import { resolve as resolve44 } from "node:path";
30143
+ import { writeFile as writeFile25 } from "node:fs/promises";
30144
+ import { resolve as resolve45 } from "node:path";
29506
30145
  async function buildCostQualityMatrix(projectRoot, outcomes) {
29507
30146
  const agg = aggregateOutcomes(outcomes);
29508
30147
  const rows = Object.entries(agg.byModel).map(([model, entry]) => {
@@ -29522,7 +30161,7 @@ async function buildCostQualityMatrix(projectRoot, outcomes) {
29522
30161
  rows
29523
30162
  };
29524
30163
  const dir = await ensureEvaluationDir(projectRoot);
29525
- await writeFile26(resolve44(dir, "matrix.yml"), $stringify(matrix), "utf-8");
30164
+ await writeFile25(resolve45(dir, "matrix.yml"), $stringify(matrix), "utf-8");
29526
30165
  return matrix;
29527
30166
  }
29528
30167
  function formatMatrixMarkdown(matrix) {
@@ -29546,8 +30185,8 @@ var init_cost_quality_matrix = __esm(() => {
29546
30185
  });
29547
30186
 
29548
30187
  // src/core/evaluation/ablation-report.ts
29549
- import { mkdir as mkdir27, writeFile as writeFile27 } from "node:fs/promises";
29550
- import { resolve as resolve45 } from "node:path";
30188
+ import { mkdir as mkdir26, writeFile as writeFile26 } from "node:fs/promises";
30189
+ import { resolve as resolve46 } from "node:path";
29551
30190
  function isPass(o) {
29552
30191
  return o.verdict === "PASS" || o.status === "pass";
29553
30192
  }
@@ -29715,10 +30354,10 @@ function formatAblationMarkdown(report) {
29715
30354
  `);
29716
30355
  }
29717
30356
  async function persistAblationReport(projectRoot, markdown) {
29718
- const dir = resolve45(projectRoot, EVAL_DIR2);
29719
- await mkdir27(dir, { recursive: true });
29720
- const path = resolve45(dir, "ablation-last.md");
29721
- await writeFile27(path, markdown, "utf-8");
30357
+ const dir = resolve46(projectRoot, EVAL_DIR2);
30358
+ await mkdir26(dir, { recursive: true });
30359
+ const path = resolve46(dir, "ablation-last.md");
30360
+ await writeFile26(path, markdown, "utf-8");
29722
30361
  return path;
29723
30362
  }
29724
30363
  var SCORE_THRESHOLD = 0.1, PASS_RATE_THRESHOLD = 0.05;
@@ -29732,8 +30371,8 @@ var exports_scorecard_command = {};
29732
30371
  __export(exports_scorecard_command, {
29733
30372
  scorecardCommand: () => scorecardCommand
29734
30373
  });
29735
- import { readFile as readFile44 } from "node:fs/promises";
29736
- import { resolve as resolve46 } from "node:path";
30374
+ import { readFile as readFile46 } from "node:fs/promises";
30375
+ import { resolve as resolve47 } from "node:path";
29737
30376
  function contractVersion() {
29738
30377
  return package_default.version;
29739
30378
  }
@@ -30021,13 +30660,13 @@ async function handleCompareModels(projectRoot, options) {
30021
30660
  outcomes = outcomes.filter((o) => options.modelsFilter.includes(o.model));
30022
30661
  }
30023
30662
  const markdown = generateModelComparisonMarkdown(outcomes, options.modelsFilter);
30024
- const templatePath = resolve46(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
30663
+ const templatePath = resolve47(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
30025
30664
  try {
30026
- await readFile44(templatePath);
30665
+ await readFile46(templatePath);
30027
30666
  } catch {}
30028
- const { writeFile: writeFile28, mkdir: mkdir28 } = await import("node:fs/promises");
30029
- await mkdir28(resolve46(projectRoot, ".codeconductor/evaluation"), { recursive: true });
30030
- await writeFile28(templatePath, markdown, "utf-8");
30667
+ const { writeFile: writeFile27, mkdir: mkdir27 } = await import("node:fs/promises");
30668
+ await mkdir27(resolve47(projectRoot, ".codeconductor/evaluation"), { recursive: true });
30669
+ await writeFile27(templatePath, markdown, "utf-8");
30031
30670
  return {
30032
30671
  code: 0,
30033
30672
  data: {
@@ -31496,7 +32135,7 @@ var exports_seo_audit_command = {};
31496
32135
  __export(exports_seo_audit_command, {
31497
32136
  seoAuditCommand: () => seoAuditCommand
31498
32137
  });
31499
- import { join as join28 } from "node:path";
32138
+ import { join as join31 } from "node:path";
31500
32139
  async function seoAuditCommand(options) {
31501
32140
  const { url, sitemap, format, failOn, delay: delay2, output, followRedirects, force } = options;
31502
32141
  if (!url && !sitemap) {
@@ -31509,7 +32148,7 @@ async function seoAuditCommand(options) {
31509
32148
  }
31510
32149
  };
31511
32150
  }
31512
- const defaultMarkdownPath = output === undefined && format === "markdown" ? join28("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
32151
+ const defaultMarkdownPath = output === undefined && format === "markdown" ? join31("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
31513
32152
  const outputPathToWrite = output ?? defaultMarkdownPath;
31514
32153
  if (outputPathToWrite !== undefined) {
31515
32154
  const preflightError = await preflightContainedOutput(options.projectRoot, outputPathToWrite, { force });
@@ -31871,7 +32510,7 @@ var COMMANDS = [
31871
32510
  { name: "detect", group: "getting-started", summary: "Inspect the project stack and recommended presets.", usage: ["cc-codeconductor detect"] },
31872
32511
  { name: "init", group: "getting-started", summary: "Initialize low-level CodeConductor configuration.", usage: ["cc-codeconductor init [--locale en|es]"] },
31873
32512
  { name: "usage", group: "getting-started", summary: "Show installation examples for presets, council, and LSP.", usage: ["cc-codeconductor usage"] },
31874
- { name: "install", group: "getting-started", summary: "Install harness components.", usage: ["cc-codeconductor install preset --target <target>"], options: ["--target opencode|claude|codex|gemini|cursor|agy|pi|all"], subcommands: [
32513
+ { name: "install", group: "getting-started", summary: "Install harness components.", usage: ["cc-codeconductor install preset --target <target>"], options: ["--target opencode|claude|codex|gemini|cursor|agy|pi|muse|all"], subcommands: [
31875
32514
  { name: "preset", group: "getting-started", summary: "Install agents, prompts, skills, and commands.", usage: ["cc-codeconductor install preset --target <target> [--locale en|es]"] },
31876
32515
  { name: "council", group: "getting-started", summary: "Install generated council files.", usage: ["cc-codeconductor install council --target <target>"] },
31877
32516
  { name: "lsp", group: "getting-started", summary: "Install supported language servers.", usage: ["cc-codeconductor install lsp --target <target> [--lang typescript,python]"] }
@@ -31965,7 +32604,7 @@ function renderDocs(command, bin = "cc-codeconductor") {
31965
32604
  }
31966
32605
  function renderCompletion(shell) {
31967
32606
  const names = COMMANDS.flatMap((item) => [item.name, ...item.aliases ?? []]).join(" ");
31968
- const targets = "opencode claude codex gemini cursor agy pi all";
32607
+ const targets = "opencode claude codex gemini cursor agy pi muse all";
31969
32608
  if (shell === "bash")
31970
32609
  return `_cc(){ local cur="${"${COMP_WORDS[COMP_CWORD]}"}"; local prev="${"${COMP_WORDS[COMP_CWORD-1]}"}"; if [[ "$prev" == "--target" ]]; then COMPREPLY=( $(compgen -W "${targets}" -- "$cur") ); else COMPREPLY=( $(compgen -W "${names}" -- "$cur") ); fi; }
31971
32610
  complete -F _cc cc-codeconductor codeconductor cc`;
@@ -32517,7 +33156,11 @@ async function routeCommand(args, projectRoot) {
32517
33156
  taskId: options["task-id"],
32518
33157
  contextStrategy: options["context-strategy"],
32519
33158
  config: options.config,
32520
- rest: validateRest
33159
+ rest: validateRest,
33160
+ panel: options.panel === true || options.panel === "true",
33161
+ panelType: options.type,
33162
+ panelScope: options.scope,
33163
+ panelRisk: options.risk
32521
33164
  });
32522
33165
  }
32523
33166
  case "openspec":
@@ -32533,7 +33176,9 @@ async function routeCommand(args, projectRoot) {
32533
33176
  "done",
32534
33177
  "block",
32535
33178
  "unblock",
32536
- "archive"
33179
+ "archive",
33180
+ "sync",
33181
+ "verify"
32537
33182
  ];
32538
33183
  let openspecSub = "validate";
32539
33184
  let itemId = options.item || undefined;
@@ -32574,9 +33219,8 @@ async function routeCommand(args, projectRoot) {
32574
33219
  return unknownSubcommand(command, subcommand, [...validHookEvents]);
32575
33220
  }
32576
33221
  const event = validHookEvents.includes(subcommand) ? subcommand : "pre-tool";
32577
- const formatRaw = options.format;
32578
- const format = formatRaw === "agy" || formatRaw === "claude" ? formatRaw : undefined;
32579
- const { hookCommand: hookCommand2, readStdinText: readStdinText2 } = await Promise.resolve().then(() => (init_hook_command(), exports_hook_command));
33222
+ const { hookCommand: hookCommand2, parseHookFormat: parseHookFormat2, readStdinText: readStdinText2 } = await Promise.resolve().then(() => (init_hook_command(), exports_hook_command));
33223
+ const format = parseHookFormat2(options.format);
32580
33224
  const stdinText = await readStdinText2();
32581
33225
  return hookCommand2({
32582
33226
  event,