@voybio/ace-swarm 2.4.3 → 2.4.4

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/assets/agent-state/INTERFACE_REGISTRY.md +2 -2
  3. package/assets/agent-state/MODULES/gates/gate-autonomy.json +4 -2
  4. package/assets/agent-state/MODULES/gates/gate-completeness.json +4 -2
  5. package/assets/agent-state/MODULES/gates/gate-operability.json +4 -2
  6. package/assets/agent-state/MODULES/gates/gate-security.json +3 -1
  7. package/assets/agent-state/MODULES/registry.json +1 -4
  8. package/assets/agent-state/MODULES/roles/capability-build.json +1 -2
  9. package/assets/agent-state/MODULES/roles/capability-eval.json +1 -1
  10. package/assets/agent-state/MODULES/roles/capability-qa.json +1 -2
  11. package/assets/agent-state/QUALITY_GATES.md +25 -9
  12. package/assets/agent-state/TEAL_CONFIG.md +2 -11
  13. package/dist/astgrep-index.d.ts +7 -1
  14. package/dist/astgrep-index.js +66 -39
  15. package/dist/gate-contract.d.ts +63 -0
  16. package/dist/gate-contract.js +178 -0
  17. package/dist/runtime-executor.js +45 -2
  18. package/dist/runtime-tool-specs.d.ts +8 -1
  19. package/dist/runtime-tool-specs.js +19 -1
  20. package/dist/schemas.js +16 -15
  21. package/dist/store/bootstrap-store.js +30 -6
  22. package/dist/store/gate-contract-migration.d.ts +10 -0
  23. package/dist/store/gate-contract-migration.js +413 -0
  24. package/dist/tools-files.js +68 -5
  25. package/dist/tools-framework.js +115 -37
  26. package/package.json +3 -1
  27. package/assets/agent-state/MODULES/gates/gate-correctness.json +0 -7
  28. package/assets/agent-state/MODULES/gates/gate-evaluation.json +0 -7
  29. package/assets/agent-state/MODULES/gates/gate-typescript-public-surface.json +0 -7
@@ -20,7 +20,7 @@ import { getWorkspaceStorePath, storeExistsSync, } from "./store/store-snapshot.
20
20
  import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
21
21
  import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
22
22
  import { LocalModelRuntimeRepository, withLocalModelRuntimeRepository, } from "./store/repositories/local-model-runtime-repository.js";
23
- import { proposePlanDeterministic, validatePlan } from "./plan-proposal.js";
23
+ import { loadAcceptanceTraceContract, proposePlanDeterministic, validatePlan } from "./plan-proposal.js";
24
24
  export const RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH = "agent-state/runtime-executor-sessions.json";
25
25
  export const RUNTIME_EXECUTOR_SESSION_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json";
26
26
  export const RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME = "runtime-executor-session-registry@1.0.0";
@@ -583,6 +583,7 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
583
583
  }
584
584
  return;
585
585
  }
586
+ let mutationLanePolicy = { mutation_lane: "raw_write_allowed" };
586
587
  // ── Plan validation preflight (Task 6) ───────────────────────────────
587
588
  {
588
589
  const currentValidatedPlanId = registryStart.validated_plan_id;
@@ -592,6 +593,13 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
592
593
  const proposal = proposePlanDeterministic(task);
593
594
  const verdict = await validatePlan({ proposal, sessionId });
594
595
  if (verdict.ok) {
596
+ const structuralStep = proposal.steps.find((step) => step.structural_edit_plan_required === true);
597
+ if (structuralStep) {
598
+ mutationLanePolicy = {
599
+ mutation_lane: "structural_edit",
600
+ waiver: structuralStep.structural_edit_waiver ?? null,
601
+ };
602
+ }
595
603
  // Persist validated_plan_id opportunistically. This is a readiness hint,
596
604
  // not a hard gate on unattended execution.
597
605
  await mutateRegistry((registry) => {
@@ -606,6 +614,16 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
606
614
  // even when the planner/model path or trace lookup is unavailable.
607
615
  }
608
616
  }
617
+ else {
618
+ const contract = loadAcceptanceTraceContract(currentValidatedPlanId);
619
+ const structuralStep = contract?.steps.find((step) => step.structural_edit_plan_required === true);
620
+ if (structuralStep) {
621
+ mutationLanePolicy = {
622
+ mutation_lane: "structural_edit",
623
+ waiver: structuralStep.structural_edit_waiver ?? null,
624
+ };
625
+ }
626
+ }
609
627
  }
610
628
  // ── End plan validation preflight ────────────────────────────────────
611
629
  setSessionPhase(sessionId, "workspace_before_run");
@@ -706,7 +724,7 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
706
724
  const turnStartedAt = new Date().toISOString();
707
725
  const paths = buildTurnPaths(sessionId, sessionSnapshot.workspace_path, turnNumber);
708
726
  // Build capability-filtered tool catalog in-memory from the resolved model class.
709
- const filteredCatalog = buildFilteredToolCatalog(effectiveModelClass);
727
+ const filteredCatalog = buildFilteredToolCatalog(effectiveModelClass, undefined, mutationLanePolicy);
710
728
  const capabilitySnapshotId = randomUUID();
711
729
  const selectedBudget = resolveEffectiveSurgicalReadBudget(runtimeProfileSnapshot.profile, effectiveModelClass);
712
730
  const toolCatalog = filteredCatalog.entries.map((e) => ({
@@ -1078,6 +1096,31 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
1078
1096
  for (const toolCall of response.tool_calls) {
1079
1097
  const toolStartedAt = new Date().toISOString();
1080
1098
  noteProgress(`Runtime tool ${toolCall.name} started during turn ${turnNumber}.`);
1099
+ const fencedReason = filteredCatalog.unavailable_tools.find((entry) => entry.name === toolCall.name && entry.reason_code === "structural_lane_fenced");
1100
+ if (fencedReason) {
1101
+ const toolResult = {
1102
+ ok: false,
1103
+ name: toolCall.name,
1104
+ summary: fencedReason.reason,
1105
+ request_path: "policy://structural-lane-fenced/request",
1106
+ response_path: "policy://structural-lane-fenced/response",
1107
+ error: { reason_code: "structural_lane_fenced", summary: fencedReason.reason },
1108
+ };
1109
+ await appendStatusEventSafe({
1110
+ source_module: "capability-framework",
1111
+ event_type: "STRUCTURAL_LANE_FENCED",
1112
+ status: "blocked",
1113
+ summary: fencedReason.reason,
1114
+ payload: {
1115
+ session_id: sessionId,
1116
+ turn_number: turnNumber,
1117
+ tool_name: toolCall.name,
1118
+ reason_code: "structural_lane_fenced",
1119
+ },
1120
+ }).catch(() => undefined);
1121
+ toolCalls.push(mapToolCallResult(toolResult, toolStartedAt));
1122
+ continue;
1123
+ }
1081
1124
  const toolResult = await executeRuntimeTool(toolCall.name, toolCall.input, {
1082
1125
  session_id: sessionId,
1083
1126
  workspace_path: sessionSnapshot.workspace_path,
@@ -102,6 +102,13 @@ export interface FilteredToolCatalog {
102
102
  }[];
103
103
  tool_cost_class: Record<string, ToolCostClass>;
104
104
  }
105
- export declare function buildFilteredToolCatalog(modelClass: ModelClass | undefined, allowedTools?: string[]): FilteredToolCatalog;
105
+ export interface MutationLanePolicy {
106
+ mutation_lane: "structural_edit" | "raw_write_allowed";
107
+ waiver?: {
108
+ reason: string;
109
+ evidence_ref: string;
110
+ } | null;
111
+ }
112
+ export declare function buildFilteredToolCatalog(modelClass: ModelClass | undefined, allowedTools?: string[], lanePolicy?: MutationLanePolicy): FilteredToolCatalog;
106
113
  export {};
107
114
  //# sourceMappingURL=runtime-tool-specs.d.ts.map
@@ -594,7 +594,12 @@ function extractCostClass(tool) {
594
594
  return "moderate";
595
595
  return "cheap";
596
596
  }
597
- export function buildFilteredToolCatalog(modelClass, allowedTools) {
597
+ const STRUCTURAL_LANE_FENCED = new Set([
598
+ "write_workspace_file",
599
+ "safe_edit_file",
600
+ "apply_patch",
601
+ ]);
602
+ export function buildFilteredToolCatalog(modelClass, allowedTools, lanePolicy) {
598
603
  const allTools = listRuntimeToolSpecs();
599
604
  const effectiveModelClass = modelClass ?? "frontier";
600
605
  const allowed = allowedTools ? new Set(allowedTools) : null;
@@ -621,6 +626,19 @@ export function buildFilteredToolCatalog(modelClass, allowedTools) {
621
626
  cost_class: costClass,
622
627
  });
623
628
  }
629
+ if (lanePolicy?.mutation_lane === "structural_edit"
630
+ && !(lanePolicy.waiver?.reason?.trim() && lanePolicy.waiver?.evidence_ref?.trim())) {
631
+ entries = entries.filter((entry) => {
632
+ if (!STRUCTURAL_LANE_FENCED.has(entry.name))
633
+ return true;
634
+ unavailable.push({
635
+ name: entry.name,
636
+ reason: "Disabled by structural_edit lane. Use astgrep_locate -> compile_structural_edit -> preview_structural_edit -> astgrep_rewrite, or attach an evidence-backed structural_edit_waiver to the step.",
637
+ reason_code: "structural_lane_fenced",
638
+ });
639
+ return false;
640
+ });
641
+ }
624
642
  if (effectiveModelClass === "small_local" || effectiveModelClass === "mid") {
625
643
  const priority = new Map(SMALL_LOCAL_PRIORITY_TOOL_NAMES.map((name, idx) => [name, idx]));
626
644
  entries.sort((a, b) => {
package/dist/schemas.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { extractFencedYamlBlock, parseTealConfigMarkdown } from "./gate-contract.js";
2
3
  import { existsSync } from "node:fs";
3
4
  import { isAbsolute, resolve } from "node:path";
4
5
  import { isInside, mapAceWorkspaceRelativePath } from "./helpers.js";
@@ -616,26 +617,26 @@ export function validateStatusEventsNdjsonContent(content) {
616
617
  }
617
618
  export function validateTealConfigContent(content) {
618
619
  const errors = [];
619
- const fenceCount = (content.match(/```/g) ?? []).length;
620
- const fencedBlocks = [...content.matchAll(/```([^\n]*)\n([\s\S]*?)```/g)];
621
- if (fenceCount !== 2 || fencedBlocks.length !== 1) {
622
- errors.push(`TEAL_CONFIG.md must contain exactly one fenced code block; found ${fencedBlocks.length}`);
620
+ const block = extractFencedYamlBlock(content);
621
+ if (!block) {
622
+ errors.push("TEAL_CONFIG.md must contain exactly one fenced code block; found 0 or multiple blocks");
623
623
  }
624
- const block = fencedBlocks[0];
625
624
  if (block) {
626
- const language = block[1].trim().toLowerCase();
627
- const yamlBody = block[2].trim();
628
- if (language !== "yaml") {
629
- errors.push(`TEAL_CONFIG.md active config block must be fenced as \`\`\`yaml, found "${block[1].trim() || "(none)"}"`);
625
+ if (block.language !== "yaml") {
626
+ errors.push(`TEAL_CONFIG.md active config block must be fenced as \`\`\`yaml, found "${block.language || "(none)"}"`);
630
627
  }
631
- if (!yamlBody) {
628
+ if (!block.yaml) {
632
629
  errors.push("TEAL_CONFIG.md YAML block is empty");
633
630
  }
634
- else {
635
- for (const section of ["modules", "pipelines"]) {
636
- if (!new RegExp(`^\\s*${section}:`, "m").test(yamlBody)) {
637
- errors.push(`TEAL_CONFIG.md YAML block is missing required top-level section "${section}:"`);
638
- }
631
+ else if (!parseTealConfigMarkdown(content)) {
632
+ errors.push("TEAL_CONFIG.md YAML block could not be parsed");
633
+ }
634
+ }
635
+ const parsed = parseTealConfigMarkdown(content);
636
+ if (parsed) {
637
+ for (const section of ["modules", "pipelines"]) {
638
+ if (!parsed[section] || typeof parsed[section] !== "object") {
639
+ errors.push(`TEAL_CONFIG.md YAML block is missing required top-level section "${section}:"`);
639
640
  }
640
641
  }
641
642
  }
@@ -22,6 +22,7 @@ import { openStore } from "./ace-packed-store.js";
22
22
  import { bakeAllCoreKnowledge } from "./knowledge-bake.js";
23
23
  import { bakeTopology } from "./topology-bake.js";
24
24
  import { buildCatalog } from "./catalog-builder.js";
25
+ import { migratePackageGateContract, recordPackageGateContractVersion, } from "./gate-contract-migration.js";
25
26
  import { HostFileMaterializer } from "./materializers/host-file-materializer.js";
26
27
  import { ProjectionManager } from "./materializers/projection-manager.js";
27
28
  import { TodoSyncer } from "./materializers/todo-syncer.js";
@@ -104,6 +105,11 @@ async function materializeStoreSurfaces(store, workspaceRoot, opts) {
104
105
  }
105
106
  // Artifacts that receive live timestamp substitution on first seed.
106
107
  const TIMESTAMPED_ARTIFACT_RELS = new Set(["agent-state/STATUS.md", "agent-state/EVIDENCE_LOG.md"]);
108
+ const GATE_CONTRACT_OPERATIONAL_ARTIFACT_RELS = [
109
+ "agent-state/TEAL_CONFIG.md",
110
+ "agent-state/QUALITY_GATES.md",
111
+ "agent-state/INTERFACE_REGISTRY.md",
112
+ ];
107
113
  async function seedOperationalArtifacts(store) {
108
114
  const bootstrapTs = new Date().toISOString();
109
115
  for (const relPath of OPERATIONAL_ARTIFACT_REL_PATHS) {
@@ -117,8 +123,23 @@ async function seedOperationalArtifacts(store) {
117
123
  await store.setBlob(operationalArtifactKey(relPath), content);
118
124
  }
119
125
  }
126
+ async function syncOperationalArtifacts(store, relPaths) {
127
+ for (const relPath of relPaths) {
128
+ const knowledgeKey = `knowledge/agent-state/${relPath.replace(/^agent-state\//, "")}`;
129
+ const content = (await store.getBlob(knowledgeKey)) ?? "";
130
+ await store.setBlob(operationalArtifactKey(relPath), content);
131
+ }
132
+ }
133
+ async function migrateExistingStoreGateContract(store, workspaceRoot, assetsRoot, warnings) {
134
+ const migration = await migratePackageGateContract(store, workspaceRoot, assetsRoot);
135
+ warnings.push(...migration.messages);
136
+ if (migration.changed) {
137
+ await syncOperationalArtifacts(store, GATE_CONTRACT_OPERATIONAL_ARTIFACT_RELS);
138
+ }
139
+ }
120
140
  export async function bootstrapStoreWorkspace(opts) {
121
141
  const { workspaceRoot, force = false } = opts;
142
+ const assetsRoot = getAssetsRoot();
122
143
  const resolved = await ensureCanonicalWorkspaceStore(workspaceRoot, {
123
144
  operationLabel: "bootstrapStoreWorkspace",
124
145
  });
@@ -131,13 +152,14 @@ export async function bootstrapStoreWorkspace(opts) {
131
152
  return { storePath, agents: 0, modules: { gates: 0, roles: 0, schemas: 0 }, materialized, warnings };
132
153
  }
133
154
  if (existsSync(storePath) && !force) {
134
- if (!(opts.includeMcpConfig ?? false) && !(opts.includeClientConfigBundle ?? false) && !opts.llm) {
135
- warnings.push(`Store already exists at ${storePath}. Use --force to reinitialize, or run 'ace migrate' to import existing state.`);
136
- return { storePath, agents: 0, modules: { gates: 0, roles: 0, schemas: 0 }, materialized, warnings };
137
- }
138
155
  const existingStore = await openStore(storePath);
139
156
  try {
140
- await seedOperationalArtifacts(existingStore);
157
+ await migrateExistingStoreGateContract(existingStore, workspaceRoot, assetsRoot, warnings);
158
+ if (!(opts.includeMcpConfig ?? false) && !(opts.includeClientConfigBundle ?? false) && !opts.llm) {
159
+ const agents = (await existingStore.listAgents()).length;
160
+ warnings.push(`Reused existing store at ${storePath}. Refreshed package gate contract state.`);
161
+ return { storePath, agents, modules: { gates: 0, roles: 0, schemas: 0 }, materialized, warnings };
162
+ }
141
163
  materialized.push(...(await materializeStoreSurfaces(existingStore, workspaceRoot, opts)));
142
164
  const agents = (await existingStore.listAgents()).length;
143
165
  warnings.push(`Reused existing store at ${storePath} and refreshed host materializations.`);
@@ -163,8 +185,8 @@ export async function bootstrapStoreWorkspace(opts) {
163
185
  include_mcp_config: opts.includeMcpConfig ?? false,
164
186
  include_client_config_bundle: opts.includeClientConfigBundle ?? false,
165
187
  });
166
- const assetsRoot = getAssetsRoot();
167
188
  const knowledge = await bakeAllCoreKnowledge(store, assetsRoot);
189
+ await recordPackageGateContractVersion(store);
168
190
  await bakeTopology(store, assetsRoot);
169
191
  await store.setJSON("state/handoffs/__index", []);
170
192
  await store.setJSON("state/todo/index", []);
@@ -215,7 +237,9 @@ export async function repairWorkspace(workspaceRoot) {
215
237
  const { storePath } = resolved;
216
238
  const warnings = [...resolved.warnings];
217
239
  const store = await openStore(storePath);
240
+ const assetsRoot = getAssetsRoot();
218
241
  try {
242
+ await migrateExistingStoreGateContract(store, workspaceRoot, assetsRoot, warnings);
219
243
  const hostPolicy = (await store.getJSON("meta/host_materialization")) ?? {};
220
244
  // Re-materialize host files
221
245
  const hostMat = new HostFileMaterializer(store, workspaceRoot);
@@ -0,0 +1,10 @@
1
+ import type { IAcePackedStore } from "./types.js";
2
+ export declare const PACKAGE_GATE_CONTRACT_VERSION = 2;
3
+ export declare const PACKAGE_GATE_CONTRACT_VERSION_KEY = "meta/package_gate_contract_version";
4
+ export interface GateContractMigrationResult {
5
+ changed: boolean;
6
+ messages: string[];
7
+ }
8
+ export declare function recordPackageGateContractVersion(store: IAcePackedStore): Promise<void>;
9
+ export declare function migratePackageGateContract(store: IAcePackedStore, workspaceRoot: string, assetsRoot: string): Promise<GateContractMigrationResult>;
10
+ //# sourceMappingURL=gate-contract-migration.d.ts.map