diff --git a/.grace/graph/main.xml b/.grace/graph/main.xml index 5f8b096..38bf8fa 100644 --- a/.grace/graph/main.xml +++ b/.grace/graph/main.xml @@ -9,7 +9,7 @@ HostCompatCORE_LOGICimplementedOwn the supported native OpenCode host window, the verified-host evidence list from src/hosts.json, and the verified/tolerated/unsupported classification consumed by runtime discovery, CLI inspection, and the live host gates; report upstream drift and fetch an exact host binary for the gates.src/lib/host-compat.ts, src/hosts.json, scripts/host-check.tsnoneV-M-HOST-COMPAT OpenCodeConfigToolsUTILITYimplementedMutate native OpenCode 2.0.18 runtime and vvoc config conservatively through the native fields (plugins array of string or {package, options} entries, agents, skills, providers, permissions), pin the combined base package registration once, migrate legacy /tui specs, preserve plugin entry options, materialize supported V1 plugin/agent/provider/command/small_model/tools shapes into the native V2 shape with a timestamped backup and refuse residual V1 shapes before any write, inspect the supported host window (>=2.0.18 <3.0.0) and its verified/tolerated classification, write managed agent/skill registrations plus the vvoc plugins modelIntent envelope for role intent, and render strict schema v3 including optional web providers, credentials, required direct Z.AI regions, an optional provider-neutral systemone connection with guardian decision-backend policy, and plugin-owned config shapes (hashline-edit routing, tool-history-compaction budgets with the recent-message window and saved-output keys, peak-hours mode/grace/schedules with revision-dated defaults, spec-guard mode). The zone also mirrors an existing managed vvoc entry in the native cli.json TUI client config to the pinned specifier (never creating one) and compares the client and server pins for drift reporting, reconciles legacy inline managed {file:...} agent prompts, deduplicates skills roots by real path, and reports read-only managed prompt/skill and deprecated permission-action drift.src/lib/opencode.ts, src/lib/opencode/paths.ts, src/lib/opencode/plugin-registration.ts, src/lib/opencode/agent-registrations.ts, src/lib/opencode/model-overrides.ts, src/lib/opencode/vvoc-config-io.ts, src/lib/opencode/inspection.ts, src/lib/opencode/config-migration.ts, src/lib/opencode/shared-utils.ts, src/lib/package.ts, src/lib/vvoc-config.ts, src/lib/model-roles.ts, src/lib/vvoc-paths.ts, schemas/vvoc/v3.jsonM-CONFIG-LAYERS, M-MODEL-ROLES, M-VVOC-PRESET-REGISTRY, M-CLI-MANAGED-AGENTS, M-CLI-MANAGED-SKILLS, M-CLI-AGENT-MODELSV-M-CLI-CONFIG ReleaseAutomationSCRIPTimplementedGuard file-local GRACE markup, package exports, and releases with expected-governed-file checks, source-path checks, deterministic OpenTUI Solid test preloading, isolated local TUI smoke launches, local version bumping, AI changelog summary generation, schema id consistency, exact-SHA CI publication, CI-result waiting, bounded npm metadata retry and gitHead verification, and authenticated local tag/GitHub Release finalization, version-derived latest/rc publication channel plumbing with contradiction rejection and pre-release marked rc GitHub Releases,bunfig.toml, scripts/check-grace-markup.ts, scripts/check-grace-markup.test.ts, scripts/check-exports.ts, scripts/tui-local.ts, scripts/release-bump.ts, scripts/release-bump.test.ts, scripts/release-check.ts, scripts/release-summary.ts, .github/workflows/publish.yml, schemas/vvoc/v3.jsonM-CLI-CONFIG, M-CLI-COMMANDS, @opentui/solid, git CLI, gh CLI, GitHub Actions, npm trusted publishingV-M-RELEASE-AUTOMATION - ManagedAgentsUTILITYimplementedDefine bundled vv-controller, enhancer, guardian, investigator, vv-implementer, vv-spec-reviewer, vv-code-reviewer, and read-only vv-reflector templates carrying correctness-obligation and reflection instructions (controller correctness leadership, pressure-versus-evidence distinction, stop/recovery/completion distinctions and reserved handoffs, bounded implementer impact investigation with worker-stop semantics, settled evidence, false-premise escalation, reviewer-finding conflict routing back to the controller, evidence-based reviewer verdicts with material verification gaps and scoped re-review guidance, and investigator property reporting), with the vv-controller working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections — settled-conclusion reopen triggers and false-premise handling included — delegated to the system-context-injection plugin's universal injection as their single source, load managed prompt text, and keep the shipped tracked-result protocol examples semantically validated against the runtime parser; worker templates copy the assignment header and state that the controller owns work-item lifecycle.src/lib/managed-agents.ts, templates/agents/*.mdM-CLI-CONFIGV-M-CLI-MANAGED-AGENTS + ManagedAgentsUTILITYimplementedDefine bundled managed primary and subagent templates and load them from bundled or scoped paths. The complete seven-section vv-controller template is the canonical wording source for task/scope, project context, repository memory, evidence discipline, working cycle, delegation/workflow, and communication; system-context-injection projects shared guidance from it. Preserve material assumptions, scope distinctions, diagnostic counterexamples, drift thresholds, worker packet obligations, bounded authority/recovery, acceptance distinctions and reserved handoffs. Native skill routing belongs to OpenCode and SDD artifact layout belongs to the existing skills. Worker/reviewer/investigator/reflection contracts retain their existing roles; shipped tracked-result examples remain parser-validated and workers copy the assignment header with controller-owned lifecycle.src/lib/managed-agents.ts, templates/agents/*.mdM-CLI-CONFIGV-M-CLI-MANAGED-AGENTS ManagedSkillsUTILITYimplementedDefine bundled vv-spec, vv-plan, vv-review, vv-execute, vv-reflect, and vv-handoff skill templates and reference loading — including the packaged on-demand nine-tool tool-contracts reference generated from the agent-tool catalog and the vv-reflect read-only session-graph extractor reference with a streaming archive mode — and the element-name COMPONENT identity format with snake_case fields and the plan-subset-of-spec invariant, with correctness obligations carried through spec ambiguity resolution, plan criteria, execution acceptance evidence distinct from bounded recovery, native plan registration boundaries, scoped re-review guidance, honest review reporting, and the literal VVOC_WORK_ITEM_ID launch/state contract.src/lib/managed-skills.ts, templates/skills/**/SKILL.md, templates/skills/**/references/*M-CLI-CONFIG, M-AGENT-TOOL-CONTRACT (generated reference)V-M-CLI-MANAGED-SKILLS AgentModelsUTILITYimplementedDefine supported vvoc model target IDs and shared model override validation helpers retained for current command and config compatibility.src/lib/agent-models.tsM-CLI-MANAGED-AGENTSV-M-CLI-AGENT-MODELS PluginToggleConfigUTILITYimplementedDefine vvoc-managed server and TUI plugin toggle names, default all-true config, default hashline edit-routing table, default tool-history-compaction entry (including the recent-message window and saved-output keys), revision-dated default peak-hours schedules and mode/grace entry, default spec-guard warn entry, the systemone provider toggle, conservative plugin entry materialization for sync/init, and pure isPluginEnabled helper.src/lib/plugin-toggle-config.tsnoneV-M-PLUGIN-TOGGLE-CONFIG @@ -23,7 +23,7 @@ GuardianPluginINTEGRATIONimplementedReview native OpenCode 2.0.18 permission evaluations through ctx.permission.hook("evaluate") with a constrained, settings-bound Guardian auxiliary model whose policy denies both built-in and canonical web tools, or with an opt-in provider-neutral System One decision provider when guardian.decisionBackend selects it and the systemone section, toggle, and ${VAR} key resolve; systemone answers (a low-risk noul and a risk score) map onto the same bounded low-risk verdict, and any provider failure defers; mutating effect only to auto-approve a bounded low-risk verdict and otherwise leaving the user’s manual or denied decision intact; unbound or disabled families defer.src/plugins/guardian/index.ts, templates/agents/guardian.mdM-NATIVE-RUNTIME, M-CONFIG-LAYERS, M-CLI-CONFIG, M-MODEL-ROLES, M-SYSTEMONE-PROVIDER, M-PLUGIN-TOGGLE-CONFIGV-M-PLUGIN-GUARDIAN SystemOneProviderCORE_LOGICimplementedProvide a provider-neutral client for the de-facto System One decision protocol (POST /v1/systemone): shape state and typed noul, choice, and score questions, decode typed answers with probabilities and confidence while ignoring unknown fields, apply a bounded timeout and retry count, and raise credential-safe typed errors; any compliant endpoint is selected by baseUrl and model with no vendor-specific branch and no default endpoint.src/lib/systemone.tsnoneV-M-SYSTEMONE-PROVIDER HashlineEditPluginINTEGRATIONimplementedRoute per-model edit tooling by registering hashline_edit and dsh str_replace_editor as native OpenCode 2.0.18 tools; resolve the session edit mode from the family-settings-projected vvoc routing config (apply_patch | edit | str_replace_editor | hashline_edit); expose exactly one plugin edit tool per actual session model while never touching the host edit/apply_patch contracts; require a native resource permission before any write; transform native read results with true line/hash/context anchors and freshness tracking; apply valid payloads literally; honor an explicit host permission denial and restore plugin-owned versus captured host tool definitions by ownership; and report bounded post-edit diffs with apply warnings and editMode telemetry.src/plugins/hashline-edit/*.tsM-NATIVE-RUNTIME, M-PLUGIN-TOGGLE-CONFIG, M-CONFIG-LAYERS, M-CLI-CONFIG, M-AGENT-TOOL-CONTRACTV-M-PLUGIN-HASHLINE-EDIT - SystemContextInjectionPluginINTEGRATIONimplementedInject universal primary guidance — including the correctness-obligations block for behavior changes with material-assumption discipline, settled-conclusion reopen triggers, false-premise handling, pressure-versus-evidence distinction, test results treated as evidence rather than the specification, repository-answerable question resolution, and honest residual uncertainty, without mandatory per-claim reasoning labels or restatement rituals — as the single source of the working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections for every eligible primary agent including vv-controller, whose bundled template carries no duplicate copies — and one family-settings-resolved orchestration policy into vv-controller at the request tail while static guidance stays in the system prefix; skill discovery is registered by install/sync through the native skills config, not by this plugin.src/plugins/system-context-injection/index.tsM-NATIVE-RUNTIME, M-CONFIG-LAYERS, M-ORCHESTRATION-PROFILES, M-CLI-MANAGED-AGENTS, M-PLUGIN-TOGGLE-CONFIGV-M-PLUGIN-SYSTEM-CONTEXT-INJECTION + SystemContextInjectionPluginINTEGRATIONimplementedLoad the bundled canonical controller template locally at startup and deliver its seven sections exactly once through exact-text deduplication against host-supplied system parts. Other eligible primary agents receive shared project, repository-memory, evidence and working-cycle sections plus authority and delivery fragments without controller-only task, dispatch/recovery or full communication policy. Preserve native-registry subagent exclusions, the explore-specific exception, disabled/unknown no-op and user-owned system content. Resolve the active controller orchestration policy from session family settings and append it only to the last user message; workflow protocols and native skill discovery retain their existing owners.src/plugins/system-context-injection/index.ts, src/plugins/system-context-injection/guidance.tsM-NATIVE-RUNTIME, M-CONFIG-LAYERS, M-ORCHESTRATION-PROFILES, M-CLI-MANAGED-AGENTS, M-PLUGIN-TOGGLE-CONFIGV-M-PLUGIN-SYSTEM-CONTEXT-INJECTION SecretsRedactionPluginINTEGRATIONimplementedRedact secrets including placeholder-resolved configured web apiKey values in native provider-bound request hooks (context/generate/compaction/title) under the family's resolved settings, then restore placeholders with copy-on-write semantics for frozen native tool inputs, SSE and non-SSE http.response bodies, and WebSocket frames — carrying placeholders split across SSE and WebSocket TEXT deltas with bounded carry while preserving opaque frames.src/plugins/secrets-redaction.ts, src/plugins/secrets-redaction/*.tsM-NATIVE-RUNTIME, M-CONFIG-LAYERS, M-CLI-CONFIG, M-PLUGIN-TOGGLE-CONFIG, M-ENV-SUBSTITUTIONV-M-PLUGIN-SECRETS-REDACTION SpecLintCORE_LOGICimplementedStrictly lint .vvoc spec-package XML artifacts through a content-addressed cache under XDG_CACHE_HOME keyed on every input hash plus a lint version: validate well-formedness over the htmlparser2 xmlMode tokenizer event stream, enforce attribute-free template contracts per artifact kind, component/task/wave/checkpoint identity patterns and uniqueness, bare-slug and task references, lifecycle-aware severity where draft incompleteness never blocks, the plan-components-subset-of-spec-components cross-file rule, the optional execution section with inline/classic/delegated modes, review_checkpoints with reviewer sets, scope paths, wave barriers, final coverage of tasks and write scopes, per-task write_scope requirements, and package layout checks; extractDelegatedPlanDefinition returns the typed obligations runtime registration reuses; the shared declared-path normalization and delegated obligation vocabulary are re-exported from M-WORKFLOW-CONTRACT.src/lib/spec-lint.ts, src/lib/spec-lint/parser.ts, src/lib/spec-lint/spec-rules.ts, src/lib/spec-lint/plan-rules.ts, src/lib/spec-lint/cross-file-rules.ts, src/lib/spec-lint/delegated-extraction.ts, src/lib/spec-lint/lint.ts, src/lib/spec-lint-cache.tshtmlparser2, M-CONFIG-LAYERSV-M-SPEC-LINT TelegramConfigResolverUTILITYimplementedResolve the optional canonical telegram section into gateway configuration: disabled-cause discrimination with value-free diagnostics, ${VAR} botToken resolution through the shared env-substitution helper with unset-reference reporting by name only, activity-window, maxTopics rotation-cap, and delivery-setting defaults, connectivity passthrough, and a stable credential-safe bot fingerprint for storage reset decisions.src/plugins/telegram/config.tssrc/lib/env-substitution.ts, src/lib/vvoc-config.tsV-M-TELEGRAM-CONFIG @@ -85,7 +85,7 @@ Preset command reads presets, validates selected name, normalizes role and orchestration assignments, applies declared fields atomically, and leaves OpenCode config untouched. Hashline plugin publishes closed edit input contracts, requests a native resource permission before any write, rewrites read output to line/hash anchors, rejects unknown, conflicting, or malformed fields before touching a file, validates current anchors on edit, applies batches, and rejects stale or malformed anchors while preserving literal empty-content deletion, null/empty-array deletion, boundary insertion, and `view_range` ending at `-1`. Guardian evaluates native permission requests through ctx.permission.hook("evaluate"), builds bounded review input from the actual action, resources, source and host history, runs the settings-bound Guardian auxiliary generation, interprets the verdict, and mutates the effect to allow only when policy declares low risk, otherwise leaving the user’s ask or deny decision intact. - System context injection resolves agent identity, selects one concrete orchestration policy for vv-controller, and appends universal plus profile-specific guidance only to eligible agents. + The bundled controller template supplies canonical wording to managed installation and startup-local guidance projection. Native agent identity selects complete controller sections or shared-primary fragments; exact-text deduplication preserves the supplied system parts. Session family settings select the controller policy delivered at the request tail, with the existing explore exception and other subagent exclusions. SecretsRedactionPlugin resolves the family's settings before provider dispatch, redacts provider-bound payloads in the native request hooks, restores placeholders after completions, restores them copy-on-write for frozen native tool input before execution, and restores SSE plus WebSocket frames while preserving opaque frames. Plugin list scans OpenCode config and displays source plus vvoc toggle status when selected vvoc config is valid. Config validate resolves selected vvoc.json, parses strict JSON, validates schema v3 and semantic role/preset constraints. diff --git a/.grace/verification/main.xml b/.grace/verification/main.xml index 3961a99..013dbfe 100644 --- a/.grace/verification/main.xml +++ b/.grace/verification/main.xml @@ -35,13 +35,13 @@ DF-ORCHESTRATION-PROFILE, DF-PRESET-SWITCH, DF-SYSTEM-CONTEXT, DF-WORKFLOW-GUIDANCEhighThe resolved family or startup profile resolves one concrete policy; vv-controller receives only its concrete policy; old v3 config defaults to balanced; preset writes are atomic; invalid explicit values fail without fallback. src/runtime/context.test.tssrc/runtime/session-settings.test.tssrc/runtime/snapshot-config.test.tssrc/runtime/model-registry.test.tssrc/runtime/auxiliary.test.tssrc/runtime/client.test.tssrc/runtime/permissions.test.tssrc/runtime/coordination.test.tssrc/runtime/context-inspection.test.tsbun test src/runtimebun run typecheckThe shared native runtime acquires and releases per exact plugin context, authenticates the same-instance client only through native service registration with a plugin-instance RPC challenge, awaits resource-specific permissions before effects, resolves each family through host-verified lineage to the root session’s metadata settings record (lazy birth from the effective configuration, strict decode with fail-soft fallback, read-modify-write merging that preserves foreign metadata keys), applies roles at creation through agent transforms plus a one-shot child reconciliation, migrates legacy bindings once with directory pruning only on full success, coordinates app/location identity across contexts, and serves the read-only context-inspection RPC with allowlisted output and value-free error codes.The declared TestFiles and Checks exercise this scenario deterministically.Reference-counted acquisition with idempotent release never leaks credentials or stops the host; permission denial, disconnect, and cancellation fail closed with zero side effects; invalid or unreadable settings fail soft to the live configuration with bounded diagnostics; a new family births the current configuration while an existing family keeps its recorded settings across restarts and config changes; birth is race-free under the per-family lock and cached per process; legacy migration converts live roots once, skips dead roots, keeps directories on write failure, and prunes only after a fully successful pass; and the RPC refuses unmeaningful settings views, out-of-location sessions, and unconvertible or oversized schemas instead of exposing raw state. - scripts/e2e-v2.test.tsbun test scripts/e2e-v2.test.tsbun run typecheckThe harness CLI enforces its list/core/full/tui mode contract with bounded outputs and nonzero exits, refuses missing hosts and unverified full parity, and guards loopback-only dispatch, scratch ownership, environment allow-listing and redaction, packed-artifact equivalence, live-handle process ownership, parity-row gating, and evidence totals without hand-authored results.The declared TestFiles and Checks exercise this scenario deterministically.Argument parsing, default-full refusal, missing-host refusal, injected core exit codes, mandatory-row gating with residual reason/cross-reference validation, evidence summaries, scratch marker cleanup, env credential redaction, foreign/exited pid refusal, and fixture guard forwarding fail closed deterministically; the real packed-host, full-parity, and PTY TUI tiers run externally through bun run e2e:v2 against the newest verified native OpenCode host binary (verified list in src/hosts.json). + scripts/e2e-v2.test.tsbun test scripts/e2e-v2.test.tsbun run typecheckThe harness CLI enforces its list/core/full/tui mode contract with bounded outputs and nonzero exits, refuses missing hosts and unverified full parity, and guards loopback-only dispatch, scratch ownership, environment allow-listing and redaction, packed-artifact equivalence, live-handle process ownership, parity-row gating, and evidence totals without hand-authored results. Aggregate system-context and peak-hours observations identify primary dispatch through the retained repository_memory instruction marker.The declared TestFiles and Checks exercise this scenario deterministically.Argument parsing, default-full refusal, missing-host refusal, injected core exit codes, mandatory-row gating with residual reason/cross-reference validation, evidence summaries, scratch marker cleanup, env credential redaction, foreign/exited pid refusal, and fixture guard forwarding fail closed deterministically; the real packed-host, full-parity, and PTY TUI tiers run externally through bun run e2e:v2 against the newest verified native OpenCode host binary (verified list in src/hosts.json). src/lib/model-roles.test.tsbun test src/lib/model-roles.test.tsbun x tsc --noEmitBuilt-in role IDs, references, concrete model selections, and invalid inputs resolve or fail deterministically.The declared TestFiles and Checks exercise this scenario deterministically. src/lib/config-layers.test.tsbun test src/lib/config-layers.test.tsbun run typecheckEnvironment, project, global, and default config layers resolve with the documented precedence and write boundaries.The declared TestFiles and Checks exercise this scenario deterministically.Global/project/effective vvoc and native OpenCode runtime sources honor the VVOC_CONFIG/OPENCODE_CONFIG/OPENCODE_CONFIG_DIR env precedence, json/jsonc selection, project-local write boundaries, and strict raw modelIntent envelope extraction with value-free validation errors. src/lib/host-compat.test.tsscripts/host-check.test.tsbun test src/lib/host-compat.test.ts scripts/host-check.test.tsbun run host:checkbun run contracts:host --probeThe supported host window is major-bounded (>=2.0.18 <3.0.0): 2.0.18 through 2.9.x are supported, prereleases and 1.x/3.x are refused, and the verified list from src/hosts.json separates verified from tolerated in-window hosts. The drift reporter classifies the newest stable upstream release and prints the next step, and the live probe authorizes exactly the selected host version with its SHA-256.The declared TestFiles and Checks exercise this scenario deterministically.Window parsing and classification are deterministic and data-driven; the verified list is the only evidence-bearing set; the drift report never claims a host is verified without an entry, and the live probe records schemaArgumentDescriptions and hostArgNormalization observations instead of asserting a host-specific forwarding shape. src/lib/opencode.test.tssrc/lib/opencode/config-migration.test.tssrc/lib/vvoc-config.test.tssrc/commands/sync.test.tsbun test src/lib/vvoc-config.test.ts src/lib/opencode.test.ts src/lib/opencode/config-migration.test.ts src/commands/sync.test.tsbun run lintbun run fmt:checkStrict vvoc and conservative native OpenCode runtime writes preserve user-owned configuration and reject invalid current documents, including host-gated materialization of supported V1 plugin/agent/provider/command/small_model/tools shapes into the native V2 shape with a timestamped backup and validate-before-write, and refusal of residual V1 shapes before any mutation. The strict schema v3 plugins union accepts the peak-hours entry (enabled, mode soft or hard, graceActiveSessions, schedules with HH:MM windows, timezone, and optional days) while rejecting malformed times, modes, and unknown keys, the embedded schema matches schemas/vvoc/v3.json, and install/init/sync conservatively materialize the peak-hours entry beside hashline-edit and tool-history-compaction. The strict schema v3 also accepts the optional provider-neutral systemone connection (baseUrl, model, optional ${VAR} apiKey, timeoutMs, maxRetries) and the guardian decisionBackend and systemone policy fields, rejecting a missing baseUrl or model and out-of-range values, with the embedded guardian and systemone schemas matching schemas/vvoc/v3.json. The cli.json TUI client config is mirrored only: an existing managed vvoc entry is rewritten to the pinned specifier with comments, client preferences, options, and unrelated entries preserved, nothing is created when no client pin exists, conflicting managed entries are refused, and inspection warns when the client pin drifts from the server pin. Prompt sync refreshes a vvoc-managed legacy inline {file:...} system prompt, preserves and reports user-owned inline sources, and deduplicates skills roots by real path; installation inspection warns about stale managed prompts/skills, duplicate skills roots, and deprecated permission actions while missing {file:} references remain problems.The declared TestFiles and Checks exercise this scenario deterministically.Native plugins-array mutation writes the pinned combined base package as a string or {package, options} entry, migrates legacy /tui specs, preserves comments, unrelated entries, and entry options, and carries role intent in the vvoc plugins options.modelIntent envelope instead of native model fields; strict schema-v3 parsing preserves optional web providers and credentials, requires explicit valid regions for direct zai providers, and rejects invalid providers, regions, or fields; the >=2.0.18 <3.0.0 host window check with its verified list, install/sync idempotence, parse state, and registration state are deterministic. bunfig.tomlscripts/check-grace-markup.test.tsscripts/tui-local.test.tsscripts/release-bump.test.tsscripts/release-check.test.tsscripts/release-summary.test.tsbun test scripts/check-grace-markup.test.ts scripts/tui-local.test.ts scripts/release-bump.test.ts scripts/release-check.test.ts scripts/release-summary.test.tsbun run grace:checkbun test src/tui/context/view.test.tsbun run scripts/check-exports.tsbun run release:checkbun run typecheckbun run fmt:checkbun run pack:checkRepository gates validate file-local GRACE markup, exports, summary generation, exact-SHA publication, npm metadata, and local finalization without premature tag mutation. Channel derivation maps rc pre-release versions to the rc dist-tag and everything else to latest, rejects a contradicting explicit channel, passes the channel through the publish workflow dispatch, publishes with the explicit npm dist-tag, and marks rc GitHub Releases as pre-release.The declared TestFiles and Checks exercise this scenario deterministically.Every expected governed TypeScript or TSX source and test has one balanced module contract and map plus exactly one current LAST_CHANGE entry, while the Bun test runner preloads @opentui/solid/preload before test module discovery; local release preparation pushes only the branch, captures and waits for the exact-SHA workflow run, retries delayed npm metadata, verifies gitHead, and creates the annotated tag/GitHub Release locally; publish.yml is explicit-dispatch-only, read-only for repository contents, and orders tests before npm publication without tag mutation. - src/lib/managed-agents.test.tsbun test src/lib/managed-agents.test.tsbun x tsc --noEmitBundled managed agent metadata, correctness-obligation instruction contracts with superseded-direction removal — stop/recovery/completion distinctions with reserved handoffs, repository-answerable question resolution, pressure-versus-evidence distinction, reviewer-finding conflict routing back to the controller, worker-stop semantics, scoped re-review guidance, and per-claim/restatement exclusions — plus the template-side dedup contract leaving the working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections to the plugin's universal injection, the shipped tracked-result protocol examples semantically validated against the runtime parser with a non-wi-1 assigned id, worker templates copying the assignment header with controller-owned lifecycle, and project/global prompt lookup remain deterministic and profile-neutral where required; tests assert instruction delivery, never model evaluation.The declared TestFiles and Checks exercise this scenario deterministically. + src/lib/managed-agents.test.tsbun test src/lib/managed-agents.test.tsbun x tsc --noEmitThe complete controller template contains exactly the seven approved sections in order, preserves repository memory, scope/evidence/assumption discipline, diagnostic counterexamples, drift thresholds, delegated packet obligations, bounded authority/recovery and truthful acceptance, and omits superseded sections and duplicated skill-routing/SDD artifact manuals. Existing managed metadata, worker/reviewer/investigator/reflection contracts, parser-validated tracked-result examples, controller-owned lifecycle headers and scoped prompt lookup remain covered. Tests assert instruction delivery and wording, never model behavior.The declared TestFiles and Checks exercise this scenario deterministically. src/lib/opencode.test.tssrc/lib/managed-skills.test.tssrc/lib/session-graph.test.tsbun test src/lib/opencode.test.tsbun test src/lib/managed-skills.test.tsbun test src/lib/session-graph.test.tsbun x tsc --noEmitbun run lintbun run fmt:checkManaged skill discovery, lookup, installation, synchronization, references, and workflow contracts — including correctness obligations across spec, plan, execute, and review stages, bounded recovery operations distinct from acceptance and rework, the native plan registration boundary, and scoped re-review guidance — remain deterministic, and the generated on-demand nine-tool tool-contracts reference plus the vv-reflect read-only session-graph extractor install through the existing reference loader and are included in the packed templates; the extractor runs against a synthetic OpenCode 2.x schema to pin target resolution, subagent-tree recursion, friction signals, changed-file semantics, archive filters, legacy part union/deduplication, deterministic JSON/Markdown output, and truncation markers; the shipped vv-execute and vv-review instructions carry the literal VVOC_WORK_ITEM_ID header, state map, standalone field rules, and launch-rejection guidance; tests assert instruction delivery, not model evaluation.The declared TestFiles and Checks exercise this scenario deterministically. src/lib/agent-models.test.tsbun test src/lib/agent-models.test.tsbun x tsc --noEmitbun run lintbun run fmt:checkbun run buildSupported model target IDs and override arguments validate and format deterministically.The declared TestFiles and Checks exercise this scenario deterministically. src/lib/plugin-toggle-config.test.tsbun test src/lib/plugin-toggle-config.test.tsbun x tsc --noEmitbun run lintbun run fmt:checkPlugin toggle names, defaults, materialization, and enablement stay conservative; Known plugin toggles default on, preserve explicit values, and reject unknown names. The systemone provider toggle is canonical, defaults on, and can be disabled as a kill switch without affecting other plugins.The declared TestFiles and Checks exercise this scenario deterministically. The peak-hours entry materializes conservatively: boolean or missing entries expand to the revision-dated default schedules, object entries keep user mode, grace, and schedules values without overwrite, and the shared defaults are never mutated. @@ -58,7 +58,7 @@ src/plugins/guardian.integration.test.tsbun test src/plugins/guardian.integration.test.tsbun x tsc --noEmitGuardian permission review remains bounded, role-resolved, web-tool constrained, backend-selectable, and fail-safe.The declared TestFiles and Checks exercise this scenario deterministically.Native permission.evaluate events mutate effect only within policy: unresolved or disabled families defer, low-risk verdicts below the threshold auto-allow through settings-bound auxiliary generation or through the opt-in System One provider (a low-risk noul gated by lowRiskThreshold and a risk score mapped linearly onto the approval threshold); any provider failure or malformed answer, uncertainty, inference failure, and empty verdicts fall back to the user’s ask/deny decision; a disabled section, unresolved ${VAR} key, or disabled systemone toggle keeps the fast backend without auto-approving from the provider; review input stays bounded and credential-safe, provider failures never auto-approve, and setup registers the evaluate hook and releases the shared runtime on cleanup. src/lib/systemone.test.tsbun test src/lib/systemone.test.tsbun x tsc --noEmitThe provider-neutral System One client shapes requests, decodes typed noul, choice, and score answers, and fails safely over an injected transport.The declared TestFiles and Checks exercise this scenario deterministically.POST {baseUrl}/v1/systemone receives state, model, and typed questions; noul, choice, and score answers decode with probabilities and confidence while unknown fields and provider extras are ignored; two distinct base URLs work through one client with no vendor branch; a bounded timeout and retry count apply and exhausted retries surface a typed error; 401 is not retried; invalid local request shapes are rejected before any call; and no error message contains the apiKey or the state. src/plugins/hashline-edit/normalize-edits.test.tssrc/plugins/hashline-edit/contracts.test.tssrc/plugins/hashline-edit.integration.test.tssrc/plugins/hashline-edit/edit-operations.test.tssrc/plugins/hashline-edit/validation.test.tssrc/plugins/hashline-edit/tool-description.test.tssrc/plugins/hashline-edit/routing.test.tssrc/plugins/hashline-edit/session-state.test.tssrc/plugins/hashline-edit/str-replace-editor.test.tsbun test src/plugins/hashline-edit/normalize-edits.test.ts src/plugins/hashline-edit/contracts.test.ts src/plugins/hashline-edit.integration.test.ts src/plugins/hashline-edit/edit-operations.test.ts src/plugins/hashline-edit/validation.test.ts src/plugins/hashline-edit/tool-description.test.ts src/plugins/hashline-edit/routing.test.ts src/plugins/hashline-edit/session-state.test.ts src/plugins/hashline-edit/str-replace-editor.test.tsbun x tsc --noEmitThe four-value routing vocabulary (apply_patch | edit | str_replace_editor | hashline_edit) resolves per family settings and session model, and native session tool-list visibility leaves exactly one existing-file editor visible — the host built-in edit stays visible for the edit cohort and is hidden for the str_replace_editor/hashline_edit cohorts, apply_patch is left to the host gate, new-file creation stays available, and the plugin registers only hashline_edit and str_replace_editor as native tools while requiring an awaited native resource permission before any write, honoring an explicit host permission denial (a session that exposes no edit-family tool is left untouched without throwing), and restoring plugin-owned definitions from the plugin itself rather than from the captured host map; hashline_edit and str_replace_editor publish closed operation-specific input contracts, reject unknown or conflicting fields and malformed ranges before mutation, and preserve hashline read anchors, three-part reference enforcement, unified replace with optional end, replace_range, lines:null/empty deletion, append/prepend boundary insertion, meaningful empty new_str, a view_range ending at -1, literal payload application, neighbor-duplication warnings, the dsh str_replace_editor contract, and bounded post-edit diff feedback. Post-release telemetry reruns the opencode.db baseline split by the editMode metadata tag.The declared TestFiles and Checks exercise this scenario deterministically. - src/plugins/system-context-injection.integration.test.tsbun test src/plugins/system-context-injection.integration.test.tsbun x tsc --noEmitEligible primary sessions receive universal guidance — including the correctness-obligations block for behavior changes with material-assumption discipline, settled-conclusion reopen triggers, false-premise handling, pressure-versus-evidence distinction, test results treated as evidence rather than the specification, repository-answerable question resolution, and honest residual uncertainty, without mandatory per-claim reasoning labels or restatement rituals — as the single source of the working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections, with the assembled bundled vv-controller template plus injection carrying each of them exactly once and the editing_workflow section including the merged template-only contract-reading and speculative-reroute lines — and the controller orchestration policy injected at the request tail while static guidance stays in the system prefix; tests assert deterministic instruction delivery, not model behavior.The declared TestFiles and Checks exercise this scenario deterministically. + src/plugins/system-context-injection.integration.test.tssrc/plugins/system-context-injection/guidance.test.tsbun test src/plugins/system-context-injection.integration.test.ts src/plugins/system-context-injection/guidance.test.tsbun x tsc --noEmitReal context-hook execution with the bundled managed controller retains its exact seven-section body and order after repeated injection for all four profiles, while the concrete profile policy appears once at the user tail. Other primaries retain shared memory, evidence, assumptions, diagnostic counterexample, drift thresholds, authority and delivery instructions without controller-only role/dispatch/recovery text. Missing or duplicated canonical sections and missing shared fragments reject projection explicitly. LF and CRLF templates project the same instructions, and deduplication preserves CRLF host text byte-for-byte. Existing user system text, disabled/unknown no-op, complete controller text with injection disabled, explore-specific guidance, other subagent/internal exclusions and fresh registry recovery remain covered. Tests establish deterministic delivery, not model behavior.The declared TestFiles and Checks exercise this scenario deterministically. src/plugins/secrets-redaction/engine.test.tssrc/plugins/secrets-redaction/index.test.tssrc/plugins/secrets-redaction/patterns.test.tssrc/plugins/secrets-redaction/session.test.tssrc/plugins/secrets-redaction/stream.test.tsbun test src/plugins/secrets-redaction/engine.test.ts src/plugins/secrets-redaction/index.test.ts src/plugins/secrets-redaction/patterns.test.ts src/plugins/secrets-redaction/session.test.ts src/plugins/secrets-redaction/stream.test.tsbun run typecheckbun run lintBuilt-in, custom, and configured secrets redact before provider exposure under the family’s resolved settings, restore copy-on-write for frozen native tool input and streamed provider responses — including placeholders split across SSE and WebSocket TEXT deltas with bounded lane carry — and preserve opaque frames.The declared TestFiles and Checks exercise this scenario deterministically.Built-in, regex, custom keyword, and configured web apiKey values redact before provider calls, with ${VAR} placeholders in web apiKey fields resolved from the environment so redaction covers the real key rather than the placeholder text; split placeholders restore across text pushes, SSE events, and byte chunks with per-lane scoping, bounded carry, byte-for-byte opaque/comment/malformed frame preservation, and non-SSE body restoration; placeholder restoration and session behavior remain deterministic. src/lib/spec-lint.test.tssrc/lib/spec-lint-cache.test.tssrc/lib/config-layers.test.tsbun test src/lib/spec-lint.test.ts src/lib/spec-lint-cache.test.ts src/lib/config-layers.test.tsbun run typecheckThe strict engine validates well-formedness, template contracts, identity patterns, references, lifecycle-aware severity, cross-file subset rules, layout checks, the optional delegated execution and review-checkpoint vocabulary with scope paths, reviewer sets, wave barriers, and final coverage, typed extractDelegatedPlanDefinition, and the content-addressed cache with content and version invalidation including rejection of old-version disk caches.The declared TestFiles and Checks exercise this scenario deterministically.Fixture corpus proves every violation class carries a specific rule with file and line, draft documents tolerate emptiness while approved documents do not, plan components must exist in the linked spec, legacy plans without execution stay valid, delegated obligations are required only for approved plans, the shipped reference templates lint clean, and cache runs hit on unchanged inputs and invalidate when any input hash or the lint version changes. src/plugins/spec-guard/index.test.tssrc/lib/plugin-toggle-config.test.tssrc/lib/vvoc-config.test.tssrc/lib/opencode.test.tsbun test src/plugins/spec-guard/index.test.ts src/lib/plugin-toggle-config.test.ts src/lib/vvoc-config.test.ts src/lib/opencode.test.tsbun run typecheckThe spec-guard plugin annotates reads of active artifacts, validates post-write state, refuses only enforce-mode ERROR writes, stays silent on archived files, and fails open on internal errors; strict schema v3 accepts the warn/enforce entry and sync materializes it conservatively.The declared TestFiles and Checks exercise this scenario deterministically.Hook-level tests with injected dependencies prove read verdict injection, archive silence, warn append, enforce refusal and edit prefixes, warning-only and draft states never failing, unreadable-file degradation, schema acceptance and rejection for the spec-guard arm, and canonical sync writes materializing the default warn entry without overwriting user values. @@ -67,7 +67,7 @@ src/plugins/tool-history-compaction/config.test.tssrc/plugins/tool-history-compaction/prune.test.tssrc/plugins/tool-history-compaction/read-slim.test.tssrc/plugins/tool-history-compaction/transform.test.tssrc/plugins/tool-history-compaction/saved-output.test.tssrc/plugins/tool-history-compaction.integration.test.tssrc/lib/plugin-toggle-config.test.tssrc/lib/opencode.test.tsbun test src/plugins/tool-history-compaction.integration.test.ts src/plugins/tool-history-compaction/config.test.ts src/plugins/tool-history-compaction/prune.test.ts src/plugins/tool-history-compaction/read-slim.test.ts src/plugins/tool-history-compaction/transform.test.ts src/plugins/tool-history-compaction/saved-output.test.ts src/lib/plugin-toggle-config.test.ts src/lib/opencode.test.tsbun run typecheckThe native session-context-hook transform-only plugin compacts old ephemeral tool outputs (read headers, head/marker/tail pruning) while retaining knowledge outputs under the family’s resolved settings, never touching inputs or part structure, protecting an absolute recency-time recent-message window plus per-call budget that retained tools do not consume, persisting full pruned outputs for recoverable markers, enforcing min-savings, and materializing strict vvoc config on sync.The declared TestFiles and Checks exercise this scenario deterministically.Deterministic idempotent rewrites over native message and tool-result parts; retained webfetch/search/skill outputs untouched and not consuming protection budget; the newest messages (recency-time window) never rewritten even under size/parallel/reordering pressure; old reads collapse to [Read <file>, lines X-Y] with fail-closed fallback; saved-output markers embed the persisted full-output path and the file exists with original content; error/compacted parts untouched; inputs byte-identical; sync materializes the default config conservatively and strict schema v3 accepts the new keys. src/plugins/analytics/index.test.tssrc/lib/analytics/store.test.tssrc/lib/analytics/metrics.test.tssrc/commands/analytics.test.tsbun test src/plugins/analytics src/lib/analytics src/commands/analytics.test.tsbun run typecheckNative step and compaction usage events append attributed usage and session records to the local store without fabricated zero tokens or double counting, gated per family by its resolved settings, and the cache-hit-rate CLI aggregates deterministically by date/week/month/session/model/provider/project/vvoc/OpenCode with coverage and filtering.The declared TestFiles and Checks exercise this scenario deterministically.Families whose settings disable the plugin record nothing while enabled families keep primary and child attribution distinct; missing or malformed reported usage never becomes a zero-token record; internal and aggregate host usage events are never double counted; token-weighted hit rate returns null instead of 0% when no step is cache eligible; and group-by/order/limit/since/JSON flag validation rejects invalid input by naming the flag. src/lib/peak-hours.test.tsbun test src/lib/peak-hours.test.tsbun run typecheckWindow parsing and evaluation with an injectable clock covers same-day, cross-midnight, timezone, and weekday cases with boundary dates and fail-open malformed input.The declared TestFiles and Checks exercise this scenario deterministically.Table-driven deterministic instants prove start-inclusive end-exclusive boundaries, cross-midnight head and tail matching with weekday gating on the opening day, timezone evaluation, provider alias resolution, suggestion filtering with dedupe, bounded formatting, and conservative plugin entry parsing with warnings for malformed pieces. - src/plugins/peak-hours/index.test.tssrc/lib/plugin-toggle-config.test.tssrc/lib/vvoc-config.test.tsbun test src/plugins/peak-hours/index.test.ts src/lib/plugin-toggle-config.test.ts src/lib/vvoc-config.test.tsbun run typecheckThe native session model.request hook applies hard blocks by provider peak windows with grace, exemptions, dynamic suggestions, and fail-open degradation; the default soft mode is pass-through with no prompt mutation; and a loopback real-host smoke proves hard-block zero-dispatch beside a soft negative control with native usage telemetry.The declared TestFiles and Checks exercise this scenario deterministically.Injected clock, session, and provider doubles prove the model.request hard throw raised before dispatch, naming provider, window end, wait, and off-peak suggestions, all-peak degradation, grace from persisted session fields, pre-window and parentID session exemptions, internal-kind/internal-agent and managed-subagent exemptions, per-provider mode overrides, fail-open session lookups, soft pass-through without prompt mutation, and disabled-entry or unknown-family-policy no-block behavior; schema tests prove strict entry validation with unchanged existing arms. + src/plugins/peak-hours/index.test.tssrc/lib/plugin-toggle-config.test.tssrc/lib/vvoc-config.test.tsbun test src/plugins/peak-hours/index.test.ts src/lib/plugin-toggle-config.test.ts src/lib/vvoc-config.test.tsbun run typecheckThe native session model.request hook applies hard blocks by provider peak windows with grace, exemptions, dynamic suggestions, and fail-open degradation; the default soft mode is pass-through with no prompt mutation; and a loopback real-host smoke proves hard-block zero-dispatch beside a soft negative control with native usage telemetry, using repository_memory to identify primary payloads and evidence_discipline to assert consolidated guidance.The declared TestFiles and Checks exercise this scenario deterministically.Injected clock, session, and provider doubles prove the model.request hard throw raised before dispatch, naming provider, window end, wait, and off-peak suggestions, all-peak degradation, grace from persisted session fields, pre-window and parentID session exemptions, internal-kind/internal-agent and managed-subagent exemptions, per-provider mode overrides, fail-open session lookups, soft pass-through without prompt mutation, and disabled-entry or unknown-family-policy no-block behavior; schema tests prove strict entry validation with unchanged existing arms. src/tui/peak-hours/banner.test.tsbun test src/tui/peak-hours/banner.test.tsbun run typecheckThe app_bottom banner registers toggle-gated and renders only while the current model’s provider is in peak, with suggestions and fail-soft failures.The declared TestFiles and Checks exercise this scenario deterministically.Marker-renderer tests prove banner text with and without suggestions, current-model resolution from session messages with config fallback, show and hide cases, warning coloring, plugin id and order, disabled toggle and entry gating, missing slot API tolerance, and registration failure tolerance without real OpenTUI rendering. src/plugins/web-tools/config.test.tssrc/lib/vvoc-config.test.tsbun test src/lib/vvoc-config.test.ts src/plugins/web-tools/config.test.tsbun run typecheckProvider defaults, credentials including ${VAR} placeholder substitution, explicit Z.AI regions, and invalid configuration resolve safely from the startup snapshot.The declared TestFiles and Checks exercise this scenario deterministically.Existing providers retain defaults and credential precedence; direct zai providers require international or china, resolve ZAI_API_KEY before section apiKey, preserve region metadata, and fail closed on missing regions without logging credentials; config apiKey ${VAR} placeholders resolve from the environment with canonical env vars still winning, and unset references yield no credential plus value-free startup warnings naming the config field and variable names. src/plugins/web-tools/http.test.tsbun test src/plugins/web-tools/http.test.tsbun run typecheckHTTP transport enforces redirects, timeout, abort, response-size bounds, and stable error taxonomy.The declared TestFiles and Checks exercise this scenario deterministically. diff --git a/README.md b/README.md index 7202aed..2aa62df 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ grep '' .vvoc/specs/*/plan.xml # dependency graph grep '/handoff.xml`, written from already-visible context only | | `vvoc-usage-analytics` | You ask about token usage, cache hit rate, costs, or caching regressions | Read-only analysis across `vvoc analytics`, the analytics JSONL, and historical `opencode.db` data | -Skills are loaded by OpenCode at session start through the native `skills` string array in `opencode.json(c)` — `vvoc install`/`sync` write the path reference and maintain the OpenCode skills-directory symlink — and the `vv-controller` agent's skill-trigger rules invoke them automatically when a request matches their conditions. +Skills are discovered by OpenCode through the native `skills` string array in `opencode.json(c)` — `vvoc install`/`sync` write the path reference and maintain the OpenCode skills-directory symlink. OpenCode supplies the available descriptions and loading instructions; the full skill is loaded when selected or explicitly invoked. The controller prompt does not repeat that catalog or its loading protocol. --- diff --git a/scripts/e2e-v2/core.ts b/scripts/e2e-v2/core.ts index 88a4a97..313e98f 100644 --- a/scripts/e2e-v2/core.ts +++ b/scripts/e2e-v2/core.ts @@ -3,7 +3,7 @@ // VERSION: 2.0.0 // START_MODULE_CONTRACT // PURPOSE: Orchestrate the isolated packed-package core real-host run: build, pack, install, prepare the fixture project, own the host lifecycle on an ownership-marked scratch, drive the core cases, and persist bounded machine-readable evidence. -// SCOPE: Ownership-marked scratch creation/removal, in-process loopback provider, packed fixture plugin materialization, exact-PID host startup/restart/teardown, bounded control-plane and native API driver, per-request provider attribution, case execution, and evidence writing. Default evidence paths resolve under the harness scratch base and never inside the workspace. It never signs services, never kills by name, and never dispatches to a non-loopback origin. +// SCOPE: Ownership-marked scratch creation/removal, in-process loopback provider, packed fixture plugin materialization, exact-PID host startup/restart/teardown, bounded control-plane and native API driver, per-request provider attribution using retained repository_memory guidance for primary dispatch, case execution, and evidence writing. Default evidence paths resolve under the harness scratch base and never inside the workspace. It never signs services, never kills by name, and never dispatches to a non-loopback origin. // DEPENDS: [node:fs/promises, node:net, node:path, node:url, scripts/e2e-v2/cases.ts, scripts/e2e-v2/host.ts, scripts/e2e-v2/provider.ts] // LINKS: [M-E2E-V2-HARNESS, V-M-E2E-V2-HARNESS] // ROLE: SCRIPT @@ -58,7 +58,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [C-SESSION-OWNED-SETTINGS T-006 - The restart-replay case now proves persisted family-settings metadata survives a real host restart and keeps driving the same resolution, replacing the snapshot-capture replay.] +// LAST_CHANGE: [DIRECT-FIX - System-context and peak-hours parity use the retained repository_memory marker after controller guidance consolidation.] // PREVIOUS: [Direct fix - Default evidence paths now resolve under the harness scratch base (vvoc-e2e-evidence) instead of the workspace GRACE change tree, so a run never leaves evidence files that turn `grace lint` red.] // END_CHANGE_SUMMARY @@ -1440,7 +1440,7 @@ export async function runAggregateParity(input: { await waitForProviderRequests(host.tracePath, 1, timeoutMs); await aggregatePrompt(host.api, host.projectDir, "aggregate primary probe"); const records = await waitForProviderRequests(host.tracePath, 2, timeoutMs); - const injected = traceHasSystemText(records, ""); + const injected = traceHasSystemText(records, ""); checks.push({ id: "plugin.system-context-injection", ok: injected, @@ -1478,7 +1478,7 @@ export async function runAggregateParity(input: { }; }; /** - * A PRIMARY dispatch carries injected guidance (``), which + * A PRIMARY dispatch carries injected guidance (``), which * system-context-injection never adds to title/compaction/internal requests. * Counting only these avoids attributing an exempt auxiliary dispatch to a * primary peak-hours bypass. @@ -1487,7 +1487,7 @@ export async function runAggregateParity(input: { records.filter( (record) => record.path?.endsWith("/chat/completions") && - JSON.stringify(record.body ?? "").includes(""), + JSON.stringify(record.body ?? "").includes(""), ).length; const openSession = async ( api: ReturnType, diff --git a/src/lib/managed-agents.test.ts b/src/lib/managed-agents.test.ts index 9894128..3507c74 100644 --- a/src/lib/managed-agents.test.ts +++ b/src/lib/managed-agents.test.ts @@ -2,7 +2,7 @@ // VERSION: 0.8.0 // START_MODULE_CONTRACT // PURPOSE: Verify vvoc-managed agent prompt template loading, scoped runtime lookup, and correctness-obligation instruction contracts. -// SCOPE: Bundled template reads, profile-neutral controller invariants, the controller template dedup contract that leaves working_state, assumption_discipline, editing_workflow, and reroute_on_evidence to the plugin's universal injection, controller correctness leadership, stop/recovery distinction with reserved handoffs, bounded implementer impact investigation with worker-stop semantics, settled evidence, false-premise escalation, and reviewer-finding conflict routing, evidence-based reviewer verdicts with initial-versus-scoped-re-review guidance, investigator property reporting, primary/subagent metadata checks, semantic agreement of the shipped tracked-result protocol examples with the runtime parser, scoped prompt resolution, and missing prompt failures. +// SCOPE: Bundled template reads, the complete canonical seven-section controller with preserved memory, verification, delegation, authority, recovery and handoff obligations; absence of superseded prompt sections and duplicated skill/artifact manuals; existing worker/reviewer/investigator contracts, metadata, protocol examples, scoped lookup and missing-prompt errors. // DEPENDS: [bun:test, node:fs/promises, node:os, node:path, src/lib/managed-agents.ts, src/lib/vvoc-paths.ts, src/plugins/workflow/protocol.ts] // LINKS: [M-CLI-MANAGED-AGENTS, M-WORKFLOW-PROTOCOL, V-M-CLI-MANAGED-AGENTS] // ROLE: TEST @@ -16,7 +16,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [Instruction deduplication - The vv-controller template contract now asserts the dedup contract (no working_state, assumption_discipline, editing_workflow, or reroute_on_evidence copies; the plugin's universal sections are the single source) and drops assertions on the removed template wordings.] +// LAST_CHANGE: [DIRECT-FIX - Pin the approved consolidated controller requirements and native skill/artifact ownership while retaining worker and lookup regression coverage.] // END_CHANGE_SUMMARY import { describe, expect, test } from "bun:test"; @@ -111,30 +111,46 @@ describe("managed agent prompts", () => { const template = await loadManagedAgentPromptTemplate("vv-controller"); expect(template).toStartWith("---\n"); expect(template).toContain("mode: primary"); - expect(template).toContain("You are the vv-controller primary agent."); + expect(template).toContain( + "You are vv-controller, a coding agent responsible for the user's requested outcome.", + ); expect(MANAGED_PRIMARY_AGENT_NAMES.filter((name) => name === "vv-controller")).toHaveLength(1); - expect(template).toContain(""); - expect(template).toContain(""); - expect(template).toContain(""); - expect(template).toContain(""); - expect(template).toContain(""); - expect(template).toContain(""); - expect(template).toContain("Match the user's language"); - expect(template).toContain("concrete system work policy supplied for this session"); - expect(template).toContain("`vv-review` performs findings-only independent review"); - expect(template).toContain("`vv-execute` validates an approved plan"); - expect(template).toContain("follows the mode selected by the user"); - - // The four formerly duplicated sections are owned by the plugin's universal - // injection; the bundled template must not carry competing copies. - for (const deduplicatedSection of [ - "", - "", - "", - "", + expect([...template.matchAll(/^<([a-z_]+)>$/gm)].map((match) => match[1])).toEqual([ + "task_and_scope", + "project_context", + "repository_memory", + "evidence_discipline", + "working_cycle", + "delegation_and_workflow", + "communication", + ]); + const normalized = template.replace(/\s+/g, " "); + expect(normalized).toContain("Use the user's language in replies"); + expect(normalized).toContain( + "Keep system-level prompts and workflow artifacts in English unless their owning format requires otherwise", + ); + expect(normalized).toContain("Follow the concrete work policy supplied for this session"); + expect(normalized).toContain( + "For questions, reviews, investigations, and planning requests, present the requested findings or plan", + ); + expect(normalized).toContain("Proceed to implementation when authorized by the user"); + for (const retired of [ + "core_principles", + "working_state", + "assumption_discipline", + "editing_workflow", + "reroute_on_evidence", + "skill_trigger_rule", + "source_and_authority", + "stop_and_recovery", + "plan_artifacts", + "final_response_format", + "task", ]) { - expect(template).not.toContain(deduplicatedSection); + expect(template).not.toContain(`<${retired}>`); } + expect(template).not.toContain(".vvoc/specs/"); + expect(template).not.toContain("`vv-spec`"); for (const inactivePolicyTerm of [ "direct_change", @@ -161,35 +177,38 @@ describe("managed agent prompts", () => { expect(template).not.toContain("leave a compact handoff"); expect(normalized).toContain( - "A worker stop or an exhausted bounded loop returns control to you for diagnosis: it suspends that work, it does not end the session", + "A worker stop or exhausted loop returns control to the controller for diagnosis; it does not itself end the session", ); - expect(normalized).toContain("never redispatch the unchanged stopped item"); - expect(normalized).toContain("never grant yourself unlimited retries"); - expect(normalized).toContain("it is not acceptance, not a passing review, and not completion"); expect(normalized).toContain( - "A handoff file is written only when the user asks for a transfer or the session genuinely ends", + "Do not redispatch an unchanged stopped item or grant unlimited retries", ); + expect(normalized).toContain("it is not acceptance, a passing review, or completion"); expect(normalized).toContain( - "Resolve repository-answerable technical questions yourself from the established code, contracts, and tests", + "Write a handoff file only when the user requests a transfer or the session genuinely ends", ); expect(normalized).toContain( - "only a genuine business-semantics fork needs a new user decision", + "Resolve repository-answerable technical questions from code, contracts, tests, and documentation yourself", ); + expect(normalized).toContain("ask the user for genuine business-semantics decisions"); }); test("vv-controller template carries profile-neutral correctness leadership", async () => { const template = await loadManagedAgentPromptTemplate("vv-controller"); const normalized = template.replace(/\s+/g, " "); - expect(template).toContain(""); - expect(normalized).toContain("Treat correctness obligations as yours to enforce"); - expect(normalized).toContain("Challenge incomplete task framing before handing work off"); - expect(normalized).toContain("material dependencies and at least one diagnostic scenario"); + expect(template).toContain(""); + expect(normalized).toContain("Own the correctness of delegated work"); + expect(normalized).toContain( + "Before dispatch, resolve missing consumer-visible acceptance criteria", + ); + expect(normalized).toContain( + "Give each bounded task its material dependencies and at least one diagnostic scenario", + ); expect(normalized).toContain( - "distinguish no discovered defect from sufficient support for a material claim", + "absence of discovered defects alone does not establish acceptance", ); expect(normalized).toContain( - "Tie substantive completion claims to observed evidence that actually exercises the changed behavior", + "Support substantive completion claims with observed evidence that exercises the behavior at risk", ); }); @@ -197,29 +216,27 @@ describe("managed agent prompts", () => { const template = await loadManagedAgentPromptTemplate("vv-controller"); const normalized = template.replace(/\s+/g, " "); - // working_state, assumption_discipline, and reroute_on_evidence wording now - // lives only in the plugin's universal injection; the assertions below cover - // the sections this template still carries. + expect(normalized).toContain("Surface false premises and investigate the actual mechanism"); + expect(normalized).toContain("Vague doubt or unsupported pressure alone is not a trigger"); expect(normalized).toContain( - "Distinguish unsupported disagreement or authority pressure from concrete new evidence", + "briefly explain the supported conclusion and ask for a specific discrepancy when needed", ); expect(normalized).toContain( - "retain a supported conclusion with a brief justification and ask for a specific discrepancy only when needed", + "Handle explicit user changes in requirements through the applicable scope and approval process", ); + expect(normalized).toContain("Reopen supported conclusions on concrete evidence"); + expect(normalized).toContain("An unchecked claim is not settled"); expect(normalized).toContain( - "route an explicit user change in requirements through the existing scope and approval process rather than a factual debate", + "Once the required checks support the material claims for the current files and inputs, proceed", ); expect(normalized).toContain( - "Move forward once the required checks substantiate the material claims for the current files and inputs", + "Treat test results as evidence against the request and contracts", ); expect(normalized).toContain( - "Interpret test results as evidence against the task and contracts", + "Preserve required fresh verification, independent review, and explicit acceptance", ); expect(normalized).toContain( - "Keep required fresh verification, independent review, and explicit acceptance intact", - ); - expect(normalized).toContain( - "a failing test that contradicts the agreed contract is a discrepancy to surface, not an automatic reason to rewrite code or tests", + "Surface a failing test that contradicts the agreed contract as a discrepancy before deciding whether the implementation or tests should change", ); // No blanket bans on apologizing or agreeing, and no mandatory per-claim rituals. @@ -229,6 +246,31 @@ describe("managed agent prompts", () => { expect(template).not.toContain("restate the requirement in one line in your own words"); }); + test("controller retains repository memory, bounded authority and acceptance distinctions", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + const normalized = template.replace(/\s+/g, " "); + for (const obligation of [ + ".vvoc/lessons/index.xml", + ".vvoc/runbooks/index.xml", + "Load only entries whose summary or applicability matches the task", + "Treat lessons and runbooks as advisory memory", + "vv-reflect skill to propose durable lessons or runbooks", + "Do not invent a native package or second lifecycle for a provided plan or conversation-scoped request", + "Do not impose a universal final-review pair", + "finite shared reserve", + "only after ordinary allowances are exhausted", + "Respect reserved stops, host permissions, and destructive, publication, or credential-operation boundaries", + "BLOCKED and NEEDS_CONTEXT suspend the affected work", + "Report completion as controller-accepted when no independent review was assigned", + "independently reviewed only after every registered review obligation passes for the current result", + "two major strategy changes for the same task, two review rounds without convergence", + "challenge at least one material assumption with a diagnostic counterexample", + "Distinguish write scope, impact scope, and verification scope", + "reconcile the result with every part of the request", + ]) + expect(normalized).toContain(obligation); + }); + test("loads bundled vv-implementer template with strict top-block protocol", async () => { const template = await loadManagedAgentPromptTemplate("vv-implementer"); expect(template).toStartWith("---\n"); diff --git a/src/lib/managed-agents.ts b/src/lib/managed-agents.ts index 912d255..7db53bf 100644 --- a/src/lib/managed-agents.ts +++ b/src/lib/managed-agents.ts @@ -2,7 +2,7 @@ // VERSION: 0.4.0 // START_MODULE_CONTRACT // PURPOSE: Describe vvoc-managed OpenCode agent prompts and load them from bundled templates or scoped vvoc config roots. -// SCOPE: Built-in primary/subagent metadata, managed prompt names, prompt file path resolution, bundled template loading (the vv-controller template delegates its working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections to the system-context-injection plugin's universal sections as their single source), and project/global prompt lookup. +// SCOPE: Built-in primary/subagent metadata, managed prompt names, prompt file path resolution, bundled template loading (including the complete canonical seven-section controller prompt also projected by system-context-injection), and project/global prompt lookup. // DEPENDS: [node:fs/promises, node:path, src/lib/vvoc-paths.ts] // LINKS: M-CLI-MANAGED-AGENTS, M-CLI-CONFIG, M-PLUGIN-GUARDIAN // ROLE: RUNTIME @@ -37,7 +37,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [Instruction deduplication - The bundled vv-controller template no longer carries working_state, assumption_discipline, editing_workflow, or reroute_on_evidence; the system-context-injection plugin's universal sections are their single source.] +// LAST_CHANGE: [DIRECT-FIX - The bundled controller template owns the approved consolidated wording; shared primary guidance is projected from it without changing managed prompt lookup or installation.] // END_CHANGE_SUMMARY import { readFile } from "node:fs/promises"; diff --git a/src/lib/opencode.test.ts b/src/lib/opencode.test.ts index a183183..e7911fe 100644 --- a/src/lib/opencode.test.ts +++ b/src/lib/opencode.test.ts @@ -2,7 +2,7 @@ // VERSION: 1.4.2 // START_MODULE_CONTRACT // PURPOSE: Verify OpenCode runtime/TUI config mutation and canonical vvoc config path/helpers. -// SCOPE: Runtime/TUI plugin specifier writes and legacy migration, OpenCode host compatibility, role-reference OpenCode defaults/agent/tool rewrites, managed prompt/plan scaffolding, canonical vvoc schema v3 writes, strict pre-role schema rejection, inspection, and scope-aware path resolution behavior. +// SCOPE: Runtime/TUI plugin specifier writes and legacy migration, OpenCode host compatibility, role-reference OpenCode defaults/agent/tool rewrites, managed prompt/plan scaffolding and complete canonical controller refresh, canonical vvoc schema v3 writes, strict pre-role schema rejection, inspection, and scope-aware path resolution behavior. // DEPENDS: [bun:test, jsonc-parser, src/lib/opencode.ts] // LINKS: [M-CLI-CONFIG, V-M-CLI-CONFIG] // ROLE: TEST @@ -14,7 +14,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [C-WORKFLOW-PROMPT-SYNC-DOCTOR-ARCHIVE T-002/T-004 - Added legacy inline managed prompt reconciliation, user-owned source reporting, and real-path skills-root deduplication coverage.] +// LAST_CHANGE: [DIRECT-FIX - Managed inline prompt reconciliation asserts the consolidated controller body and retained memory/delegation sections.] // END_CHANGE_SUMMARY import { describe, expect, test } from "bun:test"; @@ -1842,7 +1842,9 @@ describe("managed agent user ownership", () => { const legacy = await readFile(legacyPath, "utf8"); expect(legacy).toContain("Managed by vvoc"); expect(legacy).not.toContain("OLD BODY"); - expect(legacy).toContain("Your job is to own the user-facing task end to end"); + expect(legacy).toContain("Understand the task, complete authorized work, verify the result"); + expect(legacy).toContain(""); + expect(legacy).toContain(""); expect(results).toContainEqual( expect.objectContaining({ action: "updated", path: legacyPath }), ); diff --git a/src/plugins/peak-hours/index.test.ts b/src/plugins/peak-hours/index.test.ts index 1e35fc8..f87c7fa 100644 --- a/src/plugins/peak-hours/index.test.ts +++ b/src/plugins/peak-hours/index.test.ts @@ -2,7 +2,7 @@ // VERSION: 2.1.0 // START_MODULE_CONTRACT // PURPOSE: Verify native peak-hours plugin policy gating, hard blocks, grace, kind/agent exemptions, suggestions, soft pass-through, fail-open behavior, and an optional isolated real-host smoke that loads the actual built T-006 plugins on the pinned OpenCode 2.0.18 binary. -// SCOPE: Unconditional hook registration, per-family captured policy, native model.request hard blocking with dynamic suggestions and all-peak degradation, session-age and parentID grace, internal-kind/internal-agent/subagent exemptions, per-provider mode overrides, default production session/provider/agent registry adapters, lookup-failure fail-open, and a loopback-only real-host smoke proving primary payload context, native usage telemetry, and hard-block zero-dispatch with a soft negative control. +// SCOPE: Unconditional hook registration, per-family captured policy, native model.request hard blocking with dynamic suggestions and all-peak degradation, session-age and parentID grace, internal-kind/internal-agent/subagent exemptions, per-provider mode overrides, default production session/provider/agent registry adapters, lookup-failure fail-open, and a loopback-only real-host smoke using the retained repository_memory marker to prove primary payload context, native usage telemetry, and hard-block zero-dispatch with a soft negative control. // DEPENDS: [bun:test, node:fs, node:http, node:path, src/plugins/peak-hours/index.ts, src/lib/peak-hours.ts, src/lib/vvoc-config.ts] // LINKS: [M-PLUGIN-PEAK-HOURS, V-M-PLUGIN-PEAK-HOURS, M-E2E-V2-HARNESS] // ROLE: TEST @@ -32,7 +32,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [C-OPENCODE-V2-NATIVE T-006 attempt 2 - Added default-registry adapter coverage and the loopback-only real-host smoke for primary payload context, native usage telemetry, and hard-block zero dispatch.] +// LAST_CHANGE: [DIRECT-FIX - Primary-dispatch smoke checks use retained repository_memory and consolidated evidence_discipline markers.] // END_CHANGE_SUMMARY import { describe, expect, test } from "bun:test"; @@ -845,10 +845,10 @@ t006SmokeDescribe("real OpenCode 2.0.18 T-006 smoke (actual built plugins)", () (line) => line.event === "provider.request", ); const primary = requests.filter((line) => - JSON.stringify(line.body).includes(""), + JSON.stringify(line.body).includes(""), ); expect(primary.length).toBeGreaterThan(0); - expect(JSON.stringify(primary[0]!.body)).toContain(""); + expect(JSON.stringify(primary[0]!.body)).toContain(""); // Real native session.step.ended usage must land in the analytics store. let usage: Record | undefined; @@ -876,11 +876,11 @@ t006SmokeDescribe("real OpenCode 2.0.18 T-006 smoke (actual built plugins)", () const requests = providerTraceLines(started.providerTrace).filter( (line) => line.event === "provider.request", ); - // The primary context carries ``; a hard block must prevent + // The primary context carries ``; a hard block must prevent // any primary dispatch. Title/auxiliary requests never carry it, so this // marker counts primary dispatches only. const primaryDispatches = requests.filter((line) => - JSON.stringify(line.body).includes(""), + JSON.stringify(line.body).includes(""), ); expect(primaryDispatches).toHaveLength(0); } finally { diff --git a/src/plugins/system-context-injection.integration.test.ts b/src/plugins/system-context-injection.integration.test.ts index b3f128b..6a90cd8 100644 --- a/src/plugins/system-context-injection.integration.test.ts +++ b/src/plugins/system-context-injection.integration.test.ts @@ -1,8 +1,8 @@ // FILE: src/plugins/system-context-injection.integration.test.ts // VERSION: 2.0.0 // START_MODULE_CONTRACT -// PURPOSE: Verify native universal primary guidance injection, including correctness obligations and evidence discipline, and bound-family concrete vv-controller orchestration policy injection into native system parts. -// SCOPE: Correctness-obligations delivery, settled-conclusion reopen triggers, false-premise handling, pressure-versus-evidence distinction, per-profile controller context, primary isolation, explore guidance, built-in/managed/configured subagent exclusion, duplicate prevention, assembled vv-controller template plus injection dedup with each universal section exactly once and the merged template-only editing_workflow lines, native-registry mode exclusion, and unknown/disabled no-op. +// PURPOSE: Verify canonical seven-section controller assembly, shared primary guidance preservation, and concrete controller policy delivery at the request tail. +// SCOPE: Exact bundled-template assembly and repeated-hook idempotence across all profiles; preserved memory, evidence, assumptions, counterexample, drift thresholds and authority guidance; controller-only instruction isolation; explore and other subagent exclusions; registry recovery; disabled/unknown no-op. // DEPENDS: [bun:test, src/lib/managed-agents.ts, src/lib/orchestration.ts, src/lib/vvoc-config.ts, src/plugins/system-context-injection/index.ts] // LINKS: [M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-ORCHESTRATION-PROFILES, V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION] // ROLE: TEST @@ -19,12 +19,12 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [Instruction deduplication - The injected editing_workflow section absorbed the template-only contract-reading and speculative-reroute lines, and an assembled vv-controller template plus injection test pins each formerly duplicated universal section to exactly one occurrence.] +// LAST_CHANGE: [DIRECT-FIX - Pin the approved assembled controller prompt once, shared guidance for non-controller primaries, and existing policy/exclusion behavior.] // END_CHANGE_SUMMARY import { describe, expect, test } from "bun:test"; -import type { OrchestrationProfile } from "../lib/orchestration.js"; import { loadManagedAgentPromptTemplate } from "../lib/managed-agents.js"; +import type { OrchestrationProfile } from "../lib/orchestration.js"; import { createDefaultVvocConfig, type VvocConfig } from "../lib/vvoc-config.js"; import { createSystemContextInjectionPlugin } from "./system-context-injection/index.js"; @@ -143,14 +143,11 @@ describe("SystemContextInjectionPlugin", () => { const event = await harness.inject("build"); const text = systemText(event); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); - expect(text).toContain(""); + expect(text).toContain(""); + expect(text).toContain(""); + expect(text).toContain(""); + expect(text).toContain(""); + expect(text).toContain(""); expect(text).toContain(""); expect(text).toContain(".vvoc/lessons/index.xml"); expect(text).toContain(".vvoc/runbooks/index.xml"); @@ -159,7 +156,7 @@ describe("SystemContextInjectionPlugin", () => { expect(text).not.toContain("Work directly in the current session"); expect(text).not.toContain("Use the full tracked implementation and review workflow"); expect(text.replace(/\s+/g, " ")).toContain( - "prefer the `edit` tool over shell-based rewrites when it is available.", + "Prefer an available dedicated editing tool over shell-based rewrites.", ); }); @@ -168,30 +165,24 @@ describe("SystemContextInjectionPlugin", () => { const text = systemText(await harness.inject("build")).replace(/\s+/g, " "); expect(text).toContain( - "For behavior changes, run a compact correctness cycle before reporting done", - ); - expect(text).toContain( - "Separate write scope (what you may edit), impact scope (behavior that could change), and verification scope (what you actually check).", + "For behavior changes, state the intended effect, identify the directly affected consumers", ); + expect(text).toContain("Distinguish write scope, impact scope, and verification scope."); expect(text).toContain( - "Investigating directly affected consumers to understand impact is required; broadening writes beyond the approved scope is not", + "Investigating affected consumers is part of understanding impact; it does not authorize broader edits.", ); expect(text).toContain( "challenge at least one material assumption with a diagnostic counterexample", ); - expect(text).toContain("Choose verification at the level where the risk arises"); - expect(text).toContain( - "Derive test expectations from the contract and the request, not from the implementation's current output", - ); expect(text).toContain( - "ground mocks in the dependency's established contract rather than in whatever makes the change pass", + "Choose verification proportionate to the risk and at the level where it arises", ); - expect(text).toContain("The absence of a discovered defect is not proof of correctness"); + expect(text).toContain("Derive expectations from the request and contracts"); + expect(text).toContain("ground mocks in the established contracts of their dependencies"); + expect(text).toContain("absence of discovered defects alone does not establish acceptance"); + expect(text).toContain("Name unverified material conditions and remaining uncertainty"); expect(text).toContain( - "an unverified material condition is reported as remaining uncertainty, never presented as a passed check", - ); - expect(text).toContain( - "Unrelated informational or trivial documentation work needs none of this ceremony", + "Unrelated informational work and trivial documentation work need none of this correctness procedure", ); }); @@ -205,33 +196,29 @@ describe("SystemContextInjectionPlugin", () => { expect(raw).not.toContain("An unmarked claim counts as"); expect(text).not.toContain("restate the requirement in one line in your own words"); - expect(text).toContain("Do not make silent material assumptions."); + expect(text).toContain("State material assumptions explicitly"); + expect(text).toContain("carry their effects into the result report"); expect(text).toContain( - "If a material assumption is necessary, state it explicitly and carry its effect into the result report.", + "Stop and reconsider the approach when evidence invalidates it or a material assumption", ); - expect(text).toContain("If a material assumption later becomes false, stop and reroute."); expect(text).toContain( - "Resolve repository-answerable technical questions from the established code, contracts, and tests yourself", + "Resolve repository-answerable technical questions from code, contracts, tests, and documentation yourself", ); - expect(text).toContain("only a genuine business-semantics fork needs a user decision"); + expect(text).toContain("ask the user for genuine business-semantics decisions"); }); test("delivers evidence discipline with reopen triggers and false-premise handling", async () => { const harness = await makeHarness(); const text = systemText(await harness.inject("build")).replace(/\s+/g, " "); + expect(text).toContain("Surface false premises and investigate the actual mechanism"); expect(text).toContain( - "When a request assumes something that does not exist (a library, file, or behavior), surface the false premise", + "do not silently substitute another goal or add an unnecessary dependency to make a premise true", ); - expect(text).toContain( - "never silently substitute a different goal or add an unnecessary dependency to make the premise true", - ); - expect(text).toContain("A reopen or reroute trigger is concrete"); - expect(text).toContain("Vague doubt or unsupported pressure is not itself a trigger"); + expect(text).toContain("Reopen supported conclusions on concrete evidence"); + expect(text).toContain("Vague doubt or unsupported pressure alone is not a trigger"); expect(text).toContain("An unchecked claim is not settled"); - expect(text).toContain( - "Interpret test results as evidence against the request and established contracts, not as the authoritative specification", - ); + expect(text).toContain("Treat test results as evidence against the request and contracts"); }); test("injects primary-session system context for vv-controller", async () => { @@ -240,7 +227,7 @@ describe("SystemContextInjectionPlugin", () => { const system = systemText(event); const tail = tailText(event); - expect(system).toContain(""); + expect(system).toContain(""); expect(system).not.toContain("selectively delegate bounded repository search"); expect(system).not.toContain("Work directly in the current session"); expect(system).not.toContain("Use the full tracked implementation"); @@ -289,7 +276,7 @@ describe("SystemContextInjectionPlugin", () => { const event = await harness.inject("vv-controller"); const system = systemText(event); const tail = tailText(event); - expect(system).toContain(""); + expect(system).toContain(""); expect(system).not.toContain(expected); expect(tail).toContain(expected); for (const inactive of absent) expect(tail).not.toContain(inactive); @@ -327,9 +314,24 @@ describe("SystemContextInjectionPlugin", () => { test("non-controller primary agents receive universal guidance without orchestration policy", async () => { const harness = await makeHarness({ profile: "orchestrated" }); - for (const agent of ["build", "custom-primary"]) { + for (const agent of ["build", "enhancer", "custom-primary"]) { const text = systemText(await harness.inject(agent)); - expect(text).toContain(""); + const normalized = text.replace(/\s+/g, " "); + expect(text).toContain(""); + expect(text).toContain(""); + expect(text).toContain(".vvoc/lessons/index.xml"); + expect(text).toContain(".vvoc/runbooks/index.xml"); + expect(normalized).toContain("Treat lessons and runbooks as advisory memory"); + expect(normalized).toContain("vv-reflect skill to propose durable lessons or runbooks"); + expect(normalized).toContain("finite shared reserve"); + expect(normalized).toContain("Do not impose a universal final-review pair"); + expect(normalized).toContain("Report completion as controller-accepted"); + expect(text).not.toContain("You are vv-controller"); + expect(text).not.toContain(""); + expect(text).not.toContain(""); + expect(text).not.toContain(""); + expect(text).not.toContain("Own the correctness of delegated work"); + expect(text).not.toContain("BLOCKED and NEEDS_CONTEXT"); expect(text).not.toContain("Work directly in the current session"); expect(text).not.toContain("selectively delegate bounded repository search"); expect(text).not.toContain("Use the full tracked implementation and review workflow"); @@ -345,9 +347,9 @@ describe("SystemContextInjectionPlugin", () => { expect(text.replace(/\s+/g, " ")).toContain( "You are a repository search-and-discovery worker.", ); - expect(text).not.toContain(""); - expect(text).not.toContain(""); - expect(text).not.toContain(""); + expect(text).not.toContain(""); + expect(text).not.toContain(""); + expect(text).not.toContain(""); expect(text).not.toContain("Work directly in the current session"); expect(text).not.toContain("selectively delegate bounded repository search"); }); @@ -367,8 +369,8 @@ describe("SystemContextInjectionPlugin", () => { const system = systemText(event); expect(system).toContain("Existing system context."); - expect(system.match(//g)).toHaveLength(1); - expect(system.match(//g)).toHaveLength(1); + expect(system.match(//g)).toHaveLength(1); + expect(system.match(//g)).toHaveLength(1); expect(system.match(//g)).toHaveLength(1); expect(system).not.toContain("Keep architecture, critical code reading"); @@ -376,39 +378,87 @@ describe("SystemContextInjectionPlugin", () => { expect(tail.match(/Keep architecture, critical code reading/g)).toHaveLength(1); }); - test("assembled vv-controller template plus injection keeps each universal section once", async () => { - const harness = await makeHarness({ profile: "balanced" }); - const handler = harness.hooks.get("context"); - if (handler === undefined) throw new Error("no context hook registered"); + test("assembled controller preserves the approved seven sections once for every profile", async () => { const template = await loadManagedAgentPromptTemplate("vv-controller"); - const event: NativeContextEvent = { - sessionID: "session-vv-controller-assembled", - agent: "vv-controller", - system: [{ type: "text", text: template }], - messages: [{ role: "user", content: [] }], - }; - await handler(event); + const prompt = template.replace(/^---\n[\s\S]*?\n---\n/, "").trim(); + const tags = [ + "task_and_scope", + "project_context", + "repository_memory", + "evidence_discipline", + "working_cycle", + "delegation_and_workflow", + "communication", + ]; + expect([...prompt.matchAll(/^<([a-z_]+)>$/gm)].map((match) => match[1])).toEqual(tags); - const system = systemText(event); - // The bundled vv-controller template carries no copies of the four formerly - // duplicated sections; the universal injection is their single source, so - // the assembled system contains each tag exactly once. - for (const tag of [ - "", - "", - "", - "", - ]) { - expect(system.split(tag).length - 1).toBe(1); + for (const profile of ["single-session", "balanced", "orchestrated", "delegated"] as const) { + const harness = await makeHarness({ profile }); + const event = await harness.inject("vv-controller", prompt); + const once = structuredClone(event); + await harness.hooks.get("context")!(event); + // The host already supplied the complete canonical prompt: zero duplicated + // sections, reordered paragraphs, or extra universal wrappers may appear. + expect(event.system).toEqual([{ type: "text", text: prompt }]); + expect(event).toEqual(once); + for (const tag of tags) { + expect(systemText(event).split(`<${tag}>`).length - 1).toBe(1); + } + for (const retired of [ + "working_state", + "assumption_discipline", + "editing_workflow", + "reroute_on_evidence", + "skill_trigger_rule", + "plan_artifacts", + "execution_source_and_authority", + ]) { + expect(systemText(event)).not.toContain(`<${retired}>`); + } + expect(event.messages[0]!.content).toHaveLength(1); + await harness.cleanup(); } - expect(system).toContain( - "Reserve `bash` for tests, builds, git, and other non-file-edit commands.", - ); + }); + + test("disabled injection leaves the complete managed controller and existing host context intact", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + const harness = await makeHarness({ enabled: false }); + const event = await harness.inject("vv-controller", template); + expect(event.system).toEqual([{ type: "text", text: template }]); + expect(systemText(event)).toContain(""); + expect(systemText(event)).toContain(""); + expect(tailText(event)).toBe(""); + }); + + test("does not duplicate a host-supplied controller prompt with CRLF line endings", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + const prompt = template.replace(/\n/g, "\r\n"); + const harness = await makeHarness(); + const event = await harness.inject("vv-controller", prompt); + expect(event.system).toEqual([{ type: "text", text: prompt }]); + }); + + test("shared primary projection is idempotent without changing existing instructions", async () => { + const harness = await makeHarness(); + const event = await harness.inject("build", "User-owned primary instructions."); + const once = structuredClone(event); + await harness.hooks.get("context")!(event); + expect(event).toEqual(once); + expect(event.system[0]!.text).toBe("User-owned primary instructions."); + expect(event.system).toHaveLength(7); }); test("skips plugin-managed and managed subagents", async () => { const harness = await makeHarness(); - for (const agent of ["guardian", "vv-implementer"]) { + for (const agent of [ + "general", + "guardian", + "vv-implementer", + "vv-spec-reviewer", + "vv-code-reviewer", + "investigator", + "vv-reflector", + ]) { const event = await harness.inject(agent); expect(event.system).toEqual([]); } @@ -448,12 +498,12 @@ describe("SystemContextInjectionPlugin", () => { // First lookup fails transiently: the agent is treated as non-subagent (no // permanent poisoning) and universal guidance is injected. const first = await harness.inject("build"); - expect(systemText(first)).toContain(""); + expect(systemText(first)).toContain(""); // Recovery: the real envelope is parsed and the subagent mode is honored. const second = await harness.inject("reviewer"); expect(second.system).toEqual([]); const third = await harness.inject("build"); - expect(systemText(third)).toContain(""); + expect(systemText(third)).toContain(""); expect(calls).toBeGreaterThanOrEqual(3); }); @@ -477,39 +527,38 @@ describe("SystemContextInjectionPlugin", () => { expect((await unknown.inject("build")).system).toEqual([]); }); - test("keeps injected guidance wording stable", async () => { + test("preserves working state, drift thresholds, local editing contracts and memory", async () => { const harness = await makeHarness(); const text = systemText(await harness.inject("build")).replace(/\s+/g, " "); expect(text).not.toContain("proactively use the explore subagent"); expect(text).not.toContain("change_with_review"); expect(text).toContain( - "stabilize a compact working state before acting: goal, current route, constraints, non-goals when relevant, assumptions, verification target, current unknown, and reroute if.", - ); - expect(text).toContain("When new evidence invalidates the current route, stop and reroute."); - expect(text).toContain("Reuse stable domain terms from the user request and the repository."); - expect(text).toContain("Do not make silent material assumptions."); - expect(text).toContain("When repeated attempts do not converge, stop and summarize."); - expect(text).toContain( - "project-specific vocabulary, preferred patterns, boundaries, verification commands, architecture notes, or examples", + "maintain a compact working state: goal, current approach, constraints, relevant non-goals, assumptions, verification target, current unknowns, and reroute trigger", ); + expect(text).toContain("Every retry needs a named diagnosis of the prior failure"); + expect(text).toContain("continue with the canonical project term"); + expect(text).toContain("State material assumptions explicitly"); expect(text).toContain( - "Read the file first, then use exact `line#hash#anchor` refs from the latest `read` output when present.", + "two major strategy changes for the same task, two review rounds without convergence", ); expect(text).toContain( - "Reserve `bash` for tests, builds, git, and other non-file-edit commands.", + "pause and summarize what was learned, what remains unknown, and the safest next step", ); + expect(text).toContain("Prefer actual project guidance over generic vvoc defaults"); expect(text).toContain( - "Before editing, understand the relevant local contract, nearby tests, and surrounding code.", + "Before editing, read the target file and understand its local contract, nearby tests, surrounding code, and directly affected consumers", ); + expect(text).toContain("use current references from the latest read where required"); expect(text).toContain( - "If direct editing reveals unclear behavior or unexpectedly broad scope, stop and reroute instead of continuing speculatively.", + "Reserve shell commands for tests, builds, version control, and other non-file-edit operations", ); expect(text).toContain( - "inspect relevant index entries before debugging, fixing, changing behavior, operating on, architecting, or investigating repository-specific issues.", + "inspect relevant index entries before repository investigation, debugging, design, implementation, or operations", ); + expect(text).toContain("Load only entries whose summary or applicability matches the task"); expect(text).toContain( - "advisory agent-facing repository memory, not as stronger authority than explicit user instructions, code, tests, or repository-owned instructions.", + "Explicit user instructions, code, tests, and repository-owned contracts remain authoritative", ); }); }); diff --git a/src/plugins/system-context-injection/guidance.test.ts b/src/plugins/system-context-injection/guidance.test.ts new file mode 100644 index 0000000..61a4fac --- /dev/null +++ b/src/plugins/system-context-injection/guidance.test.ts @@ -0,0 +1,60 @@ +// FILE: src/plugins/system-context-injection/guidance.test.ts +// VERSION: 1.0.0 +// START_MODULE_CONTRACT +// PURPOSE: Prevent canonical prompt edits from silently dropping shared-primary instructions. +// SCOPE: Missing/duplicate sections, missing authority bullets and missing delivery guidance reject projection; prose reflow is preserved and CRLF templates retain the same instructions. +// DEPENDS: [bun:test, src/lib/managed-agents.ts, ./guidance.ts] +// LINKS: [M-PLUGIN-SYSTEM-CONTEXT-INJECTION, V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION] +// ROLE: TEST +// MAP_MODE: LOCALS +// END_MODULE_CONTRACT +// +// START_MODULE_MAP +// [test scenarios] - Canonical template projection rejects instruction-loss mutations. +// END_MODULE_MAP +// +// START_CHANGE_SUMMARY +// LAST_CHANGE: [DIRECT-FIX - Cover instruction-loss counterexamples in the single-source primary projection.] +// END_CHANGE_SUMMARY + +import { expect, test } from "bun:test"; +import { loadManagedAgentPromptTemplate } from "../../lib/managed-agents.js"; +import { createPrimarySystemContexts } from "./guidance.js"; + +test("rejects missing or duplicated repository-memory instructions", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + const memory = template.match(/[\s\S]*?<\/repository_memory>/)![0]; + for (const broken of [template.replace(memory, ""), `${template}\n${memory}`]) { + expect(() => createPrimarySystemContexts(broken)).toThrow("exactly one repository_memory"); + } +}); + +test("rejects loss of shared authority and delivery instead of silently omitting them", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + expect(() => + createPrimarySystemContexts( + template.replace("- Honor explicitly delegated autonomy", "- Removed autonomy"), + ), + ).toThrow("requires authority paragraph"); + expect(() => + createPrimarySystemContexts(template.replace("Keep scratch reasoning", "Removed delivery")), + ).toThrow("requires one delivery instruction"); +}); + +test("shared sections use the canonical wording rather than a separately maintained copy", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + const changed = template.replace( + "Treat lessons and runbooks as advisory memory.", + "Treat lessons and runbooks as advisory\nmemory.", + ); + const contexts = createPrimarySystemContexts(changed); + expect(contexts.controller.join("\n\n")).toContain("as advisory\nmemory."); + expect(contexts.universal.join("\n\n")).toContain("as advisory\nmemory."); +}); + +test("CRLF checkout preserves the canonical guidance", async () => { + const template = await loadManagedAgentPromptTemplate("vv-controller"); + expect(createPrimarySystemContexts(template.replace(/\n/g, "\r\n"))).toEqual( + createPrimarySystemContexts(template), + ); +}); diff --git a/src/plugins/system-context-injection/guidance.ts b/src/plugins/system-context-injection/guidance.ts new file mode 100644 index 0000000..66565f0 --- /dev/null +++ b/src/plugins/system-context-injection/guidance.ts @@ -0,0 +1,85 @@ +// FILE: src/plugins/system-context-injection/guidance.ts +// VERSION: 1.0.0 +// START_MODULE_CONTRACT +// PURPOSE: Project controller and shared-primary instruction sets from the canonical bundled controller template. +// SCOPE: LF/CRLF-tolerant extraction of the seven approved sections; shared project/memory/evidence/working-cycle sections; source/review/autonomy/reporting bullets and delivery sentence without controller role, dispatch or recovery instructions. Missing, duplicate or incomplete canonical fragments fail explicitly. +// DEPENDS: [templates/agents/vv-controller.md] +// LINKS: [M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-CLI-MANAGED-AGENTS, V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION] +// ROLE: RUNTIME +// MAP_MODE: EXPORTS +// END_MODULE_CONTRACT +// +// START_MODULE_MAP +// PrimarySystemContexts - Controller sections and shared-primary projection. +// createPrimarySystemContexts - Derives both sets from one supplied canonical template. +// END_MODULE_MAP +// +// START_CHANGE_SUMMARY +// LAST_CHANGE: [DIRECT-FIX - Single-source the approved wording while keeping controller-only instructions out of other primary agents.] +// END_CHANGE_SUMMARY + +export interface PrimarySystemContexts { + controller: readonly string[]; + universal: readonly string[]; +} + +// START_BLOCK_CANONICAL_PROJECTION +/** Derive guidance from the shipped template, never from a user's installed override. */ +export function createPrimarySystemContexts(template: string): PrimarySystemContexts { + const normalized = template.replace(/\r\n/g, "\n"); + const section = (name: string): string => { + const matches = [ + ...normalized.matchAll(new RegExp(`^<${name}>\\n[\\s\\S]*?^$`, "gm")), + ]; + if (matches.length !== 1) { + throw new Error(`controller guidance requires exactly one ${name} section`); + } + return matches[0]![0]; + }; + const project = section("project_context"); + const memory = section("repository_memory"); + const evidence = section("evidence_discipline"); + const cycle = section("working_cycle"); + const delegation = section("delegation_and_workflow"); + const communication = section("communication"); + const paragraphs = delegation.split("\n").slice(1, -1).join("\n").split("\n\n"); + const sharedAuthority = [ + "Keep requirement source,", + "Record and mechanically enforce", + "Honor explicitly delegated autonomy", + "Report completion as controller-accepted", + ].map((prefix) => { + const matches = paragraphs.filter((paragraph) => paragraph.startsWith(`- ${prefix}`)); + if (matches.length !== 1) + throw new Error(`controller guidance requires authority paragraph: ${prefix}`); + return matches[0]!; + }); + const delivery = communication.match(/Keep scratch reasoning[\s\S]*?stray markers\./g); + if (delivery?.length !== 1) + throw new Error("controller guidance requires one delivery instruction"); + + return { + controller: [ + section("task_and_scope"), + project, + memory, + evidence, + cycle, + delegation, + communication, + ], + universal: [ + project, + memory, + evidence, + cycle, + [ + "", + ...sharedAuthority, + "", + ].join("\n\n"), + ["", delivery[0], ""].join("\n"), + ], + }; +} +// END_BLOCK_CANONICAL_PROJECTION diff --git a/src/plugins/system-context-injection/index.ts b/src/plugins/system-context-injection/index.ts index 4d6e6b4..3d84fd8 100644 --- a/src/plugins/system-context-injection/index.ts +++ b/src/plugins/system-context-injection/index.ts @@ -1,9 +1,9 @@ // FILE: src/plugins/system-context-injection/index.ts // VERSION: 2.1.0 // START_MODULE_CONTRACT -// PURPOSE: Inject universal primary guidance, including correctness obligations and evidence discipline for behavior changes, and one bound-family-resolved concrete orchestration policy into vv-controller without polluting subagent prompts, through the native session context hook. -// SCOPE: Universal instructions with correctness obligations, material-assumption discipline, settled-conclusion reopen triggers, false-premise handling, and pressure-versus-evidence distinction, which are the single source for the vv-controller bundled template's working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections (the bundled template no longer carries them); vv-controller policy selection from the immutable captured family config; the explore-subagent guidance exception even though native registry mode is subagent; built-in, managed and native-registry subagent exclusion for every other agent; internal title/summary/compaction exclusion; per-request agent-mode reads that never poison on transient failure; idempotent injection into native system parts; lifecycle cleanup. Skill-path registration is NOT delivered here: the old runtime config.skills.paths registration has no native plugin config-transform surface, and the native document `skills: string[]` install/sync writes are owned by T007. -// DEPENDS: [@opencode/plugin, src/lib/managed-agents.ts, src/lib/orchestration.ts, src/runtime/context.ts, src/runtime/types.ts] +// PURPOSE: Deliver canonical controller guidance and its shared primary projection once, plus the session-settings-resolved controller policy at the request tail, through the native session context hook. +// SCOPE: Startup-local bundled guidance loading; line-ending-tolerant exact-text deduplication against the complete seven-section controller template without rewriting host content; shared project, memory, evidence, working-cycle, authority and delivery guidance for other primary agents; controller-only task/delegation/communication isolation; explore guidance exception; all other subagent and internal-agent exclusions; fresh registry reads; session settings and plugin toggle resolution; transient user-tail policy; lifecycle cleanup. Native skill discovery and workflow protocol injection retain their existing owners. +// DEPENDS: [@opencode/plugin, src/lib/managed-agents.ts, src/lib/orchestration.ts, src/runtime/context.ts, src/runtime/session-settings.ts, ./guidance.ts] // LINKS: [M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-ORCHESTRATION-PROFILES, M-CLI-MANAGED-AGENTS, M-NATIVE-RUNTIME, V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION] // ROLE: RUNTIME // MAP_MODE: EXPORTS @@ -17,12 +17,15 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [Instruction deduplication - The universal working_state, assumption_discipline, editing_workflow, and reroute_on_evidence sections are now the single source for vv-controller: the bundled template no longer carries them, and the editing_workflow section absorbed the template-only contract-reading and speculative-reroute lines.] +// LAST_CHANGE: [DIRECT-FIX - Use the approved controller template as the single wording source, project shared instructions to other primaries, and avoid reinjecting sections already supplied by the host.] // END_CHANGE_SUMMARY import { SystemPart } from "@opencode/ai"; import { Plugin } from "@opencode/plugin"; -import { MANAGED_SUBAGENT_NAMES } from "../../lib/managed-agents.js"; +import { + loadManagedAgentPromptTemplate, + MANAGED_SUBAGENT_NAMES, +} from "../../lib/managed-agents.js"; import { resolveOrchestrationPolicy, type ResolvedOrchestrationPolicy, @@ -34,6 +37,7 @@ import { type NativeSnapshotRuntime, } from "../../runtime/context.js"; import type { FamilySettingsView } from "../../runtime/session-settings.js"; +import { createPrimarySystemContexts, type PrimarySystemContexts } from "./guidance.js"; const BUILT_IN_SUBAGENTS = ["general"] as const; const PLUGIN_MANAGED_SUBAGENTS = ["guardian"] as const; @@ -42,101 +46,6 @@ const SELF_SUFFICIENT_PRIMARY_AGENTS = [] as const; const EXPLORE_SUBAGENT = "explore" as const; const VV_CONTROLLER_AGENT = "vv-controller" as const; -const UNIVERSAL_PRIMARY_SYSTEM_CONTEXTS = [ - [ - "", - "Reuse stable domain terms from the user request and the repository.", - "When the repository already has a canonical name for a concept, keep that name.", - "If the user's wording conflicts with repository terminology, map it once and continue with the repository's canonical term.", - "Keep stable domain terms across planning, implementation, review, and reporting.", - "", - ].join("\n"), - [ - "", - "Do not make silent material assumptions.", - "A material assumption is one that affects behavior, scope, API shape, schema, UX, data meaning, or verification.", - "If a material assumption is necessary, state it explicitly and carry its effect into the result report.", - "If a material assumption later becomes false, stop and reroute.", - "Resolve repository-answerable technical questions from the established code, contracts, and tests yourself; only a genuine business-semantics fork needs a user decision.", - "When a request assumes something that does not exist (a library, file, or behavior), surface the false premise and investigate the actual mechanism; continue only if the original goal stays unambiguous and within the approved scope, and never silently substitute a different goal or add an unnecessary dependency to make the premise true.", - "Report honest residual uncertainty: an unverified material condition is named as unverified, never presented as a passed check.", - "", - ].join("\n"), - [ - "", - "Execution ownership, requirement source, and authority are distinct: a native spec/plan package, a provided plan, and the current conversation are all valid sources, and none of them by itself implies a different ownership model.", - "Required reviews are exactly those declared by the selected source, explicitly requested by the user, or explicitly registered by the controller; do not invent a universal final-review pair.", - "When the user explicitly authorizes autonomous completion, record it as a bounded advance authority with a finite shared reserve. Use it only after ordinary allowances are exhausted, disclose material decisions without turning each into a blocking question, and still respect reserved stops, host permissions, and destructive, publication, or credential operations.", - "Report completion honestly as controller-accepted when no independent review was assigned, and as independently reviewed only when every registered obligation actually passed for the current result.", - "", - ].join("\n"), - [ - "", - "For behavior changes, run a compact correctness cycle before reporting done: state the target effect, derive the material properties that must be preserved from the request and established contracts, identify the directly affected consumers, challenge at least one material assumption with a diagnostic counterexample, choose verification proportionate to the risk, report the observed result, and name the remaining uncertainty.", - "Separate write scope (what you may edit), impact scope (behavior that could change), and verification scope (what you actually check). Investigating directly affected consumers to understand impact is required; broadening writes beyond the approved scope is not — request a scope decision instead.", - "Choose verification at the level where the risk arises. Derive test expectations from the contract and the request, not from the implementation's current output, and ground mocks in the dependency's established contract rather than in whatever makes the change pass.", - "Report honestly what was inspected, what was reasoned about, and what was executed as a check. The absence of a discovered defect is not proof of correctness: a substantive completion claim needs observed evidence, and an unverified material condition is reported as remaining uncertainty, never presented as a passed check.", - "Once required checks substantiate the material claims for the current files and inputs, move forward; recheck for changed inputs, uncovered material properties, conflicting evidence, or a mandatory gate, not for reassurance alone.", - "Interpret test results as evidence against the request and established contracts, not as the authoritative specification: a failing test that contradicts the agreed contract is a discrepancy to surface, not an automatic reason to rewrite code or tests.", - "Unrelated informational or trivial documentation work needs none of this ceremony.", - "", - ].join("\n"), - [ - "", - "For non-trivial work, stabilize a compact working state before acting: goal, current route, constraints, non-goals when relevant, assumptions, verification target, current unknown, and reroute if.", - "Check that state against the original request before and while acting; a constraint dropped early wastes the downstream chain.", - "Keep it compact and revise it when evidence changes.", - "Surface it explicitly when blocked, rerouting, or handing off to the user.", - "", - ].join("\n"), - [ - "", - "Before editing, understand the relevant local contract, nearby tests, and surrounding code.", - "When editing files, prefer the `edit` tool over shell-based rewrites when it is available.", - "Read the file first, then use exact `line#hash#anchor` refs from the latest `read` output when present.", - "Reserve `bash` for tests, builds, git, and other non-file-edit commands.", - "If direct editing reveals unclear behavior or unexpectedly broad scope, stop and reroute instead of continuing speculatively.", - "", - ].join("\n"), - [ - "", - "When new evidence invalidates the current route, stop and reroute.", - "Reroute when root cause or expected behavior remains unclear, scope crosses an unexpected boundary, or requirement ambiguity blocks safe progress.", - "When rerouting, state the current route, the trigger, the next route, and why the previous route is no longer safe.", - "A retry must carry a named diagnosis of the prior failure; a blank retry is the same attempt again.", - "A reopen or reroute trigger is concrete — contradictory evidence, a specific error or counterexample, changed requirements, or changed relevant inputs. Vague doubt or unsupported pressure is not itself a trigger: retain a supported conclusion with a brief justification and ask for a specific discrepancy only when needed, while an explicit user change in requirements follows the existing scope and approval process rather than a factual debate.", - "An unchecked claim is not settled: run the required checks before treating a conclusion as final.", - "", - ].join("\n"), - [ - "", - "When repeated attempts do not converge, stop and summarize.", - "Drift signals include repeated file reading without a stable path, repeated speculative fixes without stronger evidence, 2 major strategy changes for the same task, 2 review rounds without convergence, or requirement interpretation changing repeatedly during execution.", - "When drift signals accumulate, report what was learned, what remains unknown, and which route is now safest.", - "", - ].join("\n"), - [ - "", - "If the task context or repository provides project-specific vocabulary, preferred patterns, boundaries, verification commands, architecture notes, or examples, treat them as project-owned overlays.", - "Prefer those overlays over generic vvoc defaults when they do not conflict with the user's request.", - "Use only overlays provided in the task context or repository.", - "", - ].join("\n"), - [ - "", - "If `.vvoc/lessons/index.xml` or `.vvoc/runbooks/index.xml` exists, inspect relevant index entries before debugging, fixing, changing behavior, operating on, architecting, or investigating repository-specific issues.", - "Load only entry files whose slug, summary, or applicability signal appears relevant to the current task.", - "Treat `.vvoc/lessons` and `.vvoc/runbooks` as advisory agent-facing repository memory, not as stronger authority than explicit user instructions, code, tests, or repository-owned instructions.", - "After a long development, debugging, bugfix, ops, or investigation session with reusable findings, consider using the vv-reflect skill to propose durable lessons or runbooks.", - "", - ].join("\n"), - [ - "", - "Keep internal and scratch reasoning compact; anything user- or tool-facing must be clean, complete language with no half-compressed notation or stray markers.", - "", - ].join("\n"), -] as const; - const EXPLORE_SYSTEM_CONTEXTS = [ [ "", @@ -195,11 +104,14 @@ async function shouldInjectForAgent( } /** Returns the stable guidance injected into the system prefix for one agent. */ -function getSystemContextsForAgent(agentName: string | undefined): readonly string[] { +function getSystemContextsForAgent( + agentName: string | undefined, + guidance: PrimarySystemContexts, +): readonly string[] { if (agentName === EXPLORE_SUBAGENT) { return EXPLORE_SYSTEM_CONTEXTS; } - return UNIVERSAL_PRIMARY_SYSTEM_CONTEXTS; + return agentName === VV_CONTROLLER_AGENT ? guidance.controller : guidance.universal; } /** @@ -219,7 +131,9 @@ function getTailPolicyForAgent( // START_BLOCK_SYSTEM_CONTEXT_FORMATTING /** Native system part text already carrying the context, if any. */ function hasInjectedContext(parts: ReadonlyArray<{ text?: unknown }>, context: string): boolean { - return parts.some((part) => typeof part.text === "string" && part.text.includes(context)); + return parts.some( + (part) => typeof part.text === "string" && part.text.replace(/\r\n/g, "\n").includes(context), + ); } /** @@ -290,6 +204,9 @@ export function createSystemContextInjectionPlugin( return Plugin.define({ id: "vvoc.system-context-injection", setup: async (ctx) => { + const guidance = createPrimarySystemContexts( + await loadManagedAgentPromptTemplate("vv-controller"), + ); const acquire = options.acquireRuntime ?? ((c: NativeSnapshotContext) => acquireNativeSnapshotRuntime(c)); const runtime = await acquire(ctx); @@ -336,7 +253,7 @@ export function createSystemContextInjectionPlugin( if (Array.isArray(event.system)) { appendSystemContexts( event.system as Array<{ type: "text"; text: string }>, - getSystemContextsForAgent(agentName), + getSystemContextsForAgent(agentName, guidance), ); } const tail = getTailPolicyForAgent(agentName, policy); diff --git a/src/plugins/workflow.execution.integration.test.ts b/src/plugins/workflow.execution.integration.test.ts index c4159ee..fb85060 100644 --- a/src/plugins/workflow.execution.integration.test.ts +++ b/src/plugins/workflow.execution.integration.test.ts @@ -2,7 +2,7 @@ // VERSION: 1.0.0 // START_MODULE_CONTRACT // PURPOSE: Plugin-level integration coverage proving the three reachable execution paths (native-package, provided-plan, conversation-scoped) run through the common registry with real delegated launch/result/acceptance hooks, dynamic review obligations, honest completion, and fail-closed persistence. -// SCOPE: In-memory plugin domain integration with synthetic fixtures; no live model, SDK, or filesystem persistence writes. +// SCOPE: In-memory plugin domain integration with synthetic fixtures and canonical controller/skill instruction audit for source independence, bounded authority and reserved stops; no live model, SDK, or filesystem persistence writes. // DEPENDS: [bun:test, src/plugins/workflow/state.ts, src/plugins/workflow/delegated.ts, src/plugins/workflow/execution.ts, src/plugins/workflow/authority.ts, src/plugins/workflow/transactions.ts] // LINKS: [M-PLUGIN-WORKFLOW, M-WORKFLOW-EXECUTION, M-WORKFLOW-AUTHORITY, V-M-PLUGIN-WORKFLOW] // ROLE: TEST @@ -21,7 +21,7 @@ // END_MODULE_MAP // // START_CHANGE_SUMMARY -// LAST_CHANGE: [C-OPENCODE-V2-NATIVE wi-7 - Added direct work_item_decide coverage for generic failed-checkpoint rework: the public attempt must match the current accepted attempt (STALE_ATTEMPT otherwise), an unknown run stays RUN_NOT_FOUND, and a real generic failure reopens its covered task.] +// LAST_CHANGE: [DIRECT-FIX - Source-independent execution instruction checks follow the consolidated controller wording without weakening source, review or authority requirements.] // END_CHANGE_SUMMARY import { beforeEach, describe, expect, test } from "bun:test"; @@ -2331,10 +2331,15 @@ describe("instruction scenario audit", () => { /\s+/g, " ", ); - expect(controller).toContain(""); + expect(controller).toContain(""); expect(controller).not.toContain(""); - expect(controller).toContain("not a universal prerequisite"); - expect(controller).toContain("do not install a universal final-review pair"); + expect(controller).toContain( + "Keep requirement source, execution ownership, and authority distinct", + ); + expect(controller).toContain( + "Do not invent a native package or second lifecycle for a provided plan or conversation-scoped request", + ); + expect(controller).toContain("Do not impose a universal final-review pair"); expect(controller).toContain("Honor explicitly delegated autonomy"); }); @@ -2371,12 +2376,16 @@ describe("instruction scenario audit", () => { }); test("scenario enabling rules are present for no-files, reserved-stop, and publication-bound runs", async () => { - const controller = readFileSync(join(templatesDir, "agents/vv-controller.md"), "utf8"); + const controller = readFileSync(join(templatesDir, "agents/vv-controller.md"), "utf8").replace( + /\s+/g, + " ", + ); // Discussion/review requests must not become implementation; reserved stops stay binding. - expect(controller).toContain("user-reserved stops"); + expect(controller).toContain("Respect reserved stops"); expect(controller).toContain("host permissions"); + expect(controller).toContain("destructive, publication, or credential-operation boundaries"); // Provided-plan and conversation-scoped sources need no native package. - expect(controller).toContain("do not invent a native package"); + expect(controller).toContain("Do not invent a native package"); // Review-only requests remain findings rather than implementation. const review = await skillBody("vv-review"); expect(review).toContain("findings"); diff --git a/templates/agents/vv-controller.md b/templates/agents/vv-controller.md index f88f91c..23dc538 100644 --- a/templates/agents/vv-controller.md +++ b/templates/agents/vv-controller.md @@ -3,120 +3,179 @@ description: Primary vvoc controller that follows the concrete work policy selec mode: primary --- -You are the vv-controller primary agent. - -Your job is to own the user-facing task end to end: clarify unclear intent, gather the evidence you -need, present analysis or findings before acting, complete approved work, verify it freshly, and -report the outcome. - - -- Present before acting. For review, analysis, planning, or investigation requests, the findings or - plan are the result; do not silently proceed to implementation. -- Match the user's language in normal replies. Keep system-level prompts and workflow artifacts in - English unless their owning format requires otherwise. -- Prefer the smallest correct change that satisfies the request. -- Reuse repository terminology and project-owned overlays. -- State material assumptions explicitly. A material assumption affects behavior, scope, API shape, - schema, UX, data meaning, security, or verification. -- Require fresh verification evidence before making completion claims. -- If the approach is not converging, stop and summarize what is known, what remains unknown, and - the safest next step. -- Follow the concrete system work policy supplied for this session; do not invent, expose, or switch - to alternative orchestration rules. - - - -- Gather enough repository evidence before acting on unfamiliar code. -- Prefer existing project patterns, libraries, contracts, and established structure over novel - approaches. -- Keep changes within the requested and approved scope. -- Preserve user-owned configuration and fail closed rather than guessing when authoritative sources - conflict. -- Resolve repository-answerable technical questions yourself from the established code, contracts, - and tests; only a genuine business-semantics fork needs a new user decision. -- Distinguish unsupported disagreement or authority pressure from concrete new evidence; retain a - supported conclusion with a brief justification and ask for a specific discrepancy only when needed, - but route an explicit user change in requirements through the existing scope and approval process - rather than a factual debate. - - - -- Frame work by deriving its engineering obligations from the request and established contracts: the - target effect, the material properties that must be preserved, and the directly affected consumers. -- Challenge incomplete task framing before handing work off: missing acceptance criteria that a - consumer would notice, verification that cannot exercise the changed behavior, or expectations - copied from a preferred implementation. -- When handing off bounded tasks, include the material dependencies and at least one diagnostic - scenario that exercises the property at risk; a handoff that omits them is incomplete. -- Treat correctness obligations as yours to enforce: a DONE report, green general checks, or - reviewer agreement alone is not acceptance. Tie substantive completion claims to observed - evidence that actually exercises the changed behavior, and distinguish no discovered defect from - sufficient support for a material claim. -- Move forward once the required checks substantiate the material claims for the current files and - inputs. Recheck for changed inputs, uncovered material properties, conflicting evidence, or an - unmet mandatory gate — not solely for reassurance. -- Interpret test results as evidence against the task and contracts, not as the authoritative - specification: a failing test that contradicts the agreed contract is a discrepancy to surface, - not an automatic reason to rewrite code or tests. Keep required fresh verification, independent - review, and explicit acceptance intact. - - - -- `vv-spec` interviews the user, proposes a design, and creates an approved specification. -- `vv-plan` creates an implementation plan from an approved specification and does not implement. -- `vv-review` performs findings-only independent review and does not fix without subsequent user - confirmation. -- `vv-execute` validates an approved plan, asks for an explicit execution mode when needed, and - follows the mode selected by the user. -- When one of these skills is explicitly requested, load and follow that skill instead of recreating - its workflow in this base prompt. - - - -- Work ownership, requirement source, and delegated authority are distinct. Choosing the native - `.vvoc/specs` package workflow is a specialized decision, not a universal prerequisite. -- When the selected source is a native package, honor its own lifecycle approvals. When the source - is a provided plan or the current conversation, do not invent a native package or a second - lifecycle for it. -- Record explicitly assigned review obligations and enforce them mechanically; do not install a - universal final-review pair that the source, user, or an explicit controller decision did not ask - for. -- Honor explicitly delegated autonomy for reasonable reversible decisions within the recorded - scope, while respecting user-reserved stops and host permissions. - - - -Distinguish a worker stop, a suspended loop, controller diagnosis, authorized recovery, and actual -session completion. A worker stop or an exhausted bounded loop returns control to you for -diagnosis: it suspends that work, it does not end the session. Diagnose the stop yourself, then use -the session policy's supported recovery operation for that target — never redispatch the unchanged -stopped item and never grant yourself unlimited retries. Authorized recovery resumes or grants one -bounded unit; it is not acceptance, not a passing review, and not completion. A handoff file is -written only when the user asks for a transfer or the session genuinely ends — not for every failed -check. If work is truly blocked pending a user decision, say so and stop. - - - -- vvoc specification packages live at - `.vvoc/specs/YYYY-MM-DD-/{spec.xml, design-context.xml optional, plan.xml}`. -- `spec.xml` is normative. -- `design-context.xml` is explanatory and non-normative. -- `plan.xml` is the implementation plan derived from the approved specification. - - - -- Start with the outcome. -- For review, analysis, planning, or investigation, start with findings or the plan. -- Before claiming completion, check the goal against the result line by line, name any part that is - not met, and name the edge you did not check. -- Mention changed files and verification only when implementation occurred. -- Mention assumptions, skipped checks, blockers, or residual risks when they materially affect the - outcome. -- Suggest next steps only when they are natural and useful. - - - -Your current task is the ongoing user request. Determine whether the requested result is findings, -a plan, investigation, implementation, or clarification; follow the concrete system work policy for -this session; verify fresh evidence; and report the outcome. - +You are vv-controller, a coding agent responsible for the user's requested outcome. +Understand the task, complete authorized work, verify the result, and report it clearly. + + +Deliver the requested kind of result. For questions, reviews, investigations, and +planning requests, present the requested findings or plan. Proceed to implementation +when authorized by the user. + +Follow the concrete work policy supplied for this session and the workflow selected +by the user and applicable project instructions. Honor its approvals and boundaries; +do not invent or silently switch to another process. + +Prefer the smallest complete change that satisfies the request. Keep edits within +the requested and approved scope, preserve unrelated user work and configuration, +and obtain a scope decision before expanding the changes. + + + +Use the project's established terminology, architecture, patterns, libraries, and +conventions. Prefer actual project guidance over generic vvoc defaults when it does +not conflict with the user's request. Use only guidance supplied in the task or +found in the repository. + +Keep domain terms consistent across planning, implementation, review, and reporting. +If the user and repository name the same concept differently, clarify the mapping +once and continue with the canonical project term. + +Before editing, read the target file and understand its local contract, nearby tests, +surrounding code, and directly affected consumers. Resolve repository-answerable +technical questions from code, contracts, tests, and documentation yourself; ask +the user for genuine business-semantics decisions. + +Prefer an available dedicated editing tool over shell-based rewrites. Follow its +published contract and use current references from the latest read where required. +Reserve shell commands for tests, builds, version control, and other non-file-edit +operations. + + + +If `.vvoc/lessons/index.xml` or `.vvoc/runbooks/index.xml` exists, inspect relevant +index entries before repository investigation, debugging, design, implementation, +or operations. Load only entries whose summary or applicability matches the task. + +Treat lessons and runbooks as advisory memory. Explicit user instructions, code, +tests, and repository-owned contracts remain authoritative. + +After substantial work with reusable findings, consider using the available +vv-reflect skill to propose durable lessons or runbooks. + + + +For behavior changes, state the intended effect, identify the directly affected +consumers, and derive the material properties to preserve from the request and +established contracts. + +Distinguish write scope, impact scope, and verification scope. Investigating affected +consumers is part of understanding impact; it does not authorize broader edits. + +State material assumptions explicitly and carry their effects into the result report. +An assumption is material when it affects behavior, scope, API shape, schema, UX, +data meaning, security, or verification. Surface false premises and investigate the +actual mechanism. Continue only while the original goal remains unambiguous and +within approved scope; do not silently substitute another goal or add an unnecessary +dependency to make a premise true. + +When authoritative sources conflict, resolve the conflict before acting on it. + +For behavior changes, challenge at least one material assumption with a diagnostic +counterexample. Choose verification proportionate to the risk and at the level where +it arises. Derive expectations from the request and contracts, and ground mocks in +the established contracts of their dependencies. + +Support substantive completion claims with observed evidence that exercises the +behavior at risk. Distinguish what you inspected, inferred, and actually executed +as a check. A DONE report, passing general checks, reviewer agreement, or absence +of discovered defects alone does not establish acceptance. Name unverified material +conditions and remaining uncertainty. + +Treat test results as evidence against the request and contracts. Surface a failing +test that contradicts the agreed contract as a discrepancy before deciding whether +the implementation or tests should change. Preserve required fresh verification, +independent review, and explicit acceptance. + +Once the required checks support the material claims for the current files and +inputs, proceed. Repeat verification for changed inputs, uncovered material +properties, conflicting evidence, or an unmet mandatory gate—not for reassurance. + +Unrelated informational work and trivial documentation work need none of this +correctness procedure. + + + +For non-trivial work, maintain a compact working state: goal, current approach, +constraints, relevant non-goals, assumptions, verification target, current unknowns, +and reroute trigger. Check it against the original request before and during work. +Update it when evidence changes and surface it when blocked, rerouting, or handing off. + +Continue until the requested deliverable is complete or a concrete blocker requires +user input. Stop and reconsider the approach when evidence invalidates it or a +material assumption, root cause or expected behavior remains unclear, scope crosses +an unexpected boundary, or requirement ambiguity prevents safe progress. + +When rerouting, explain the current approach, the concrete trigger, the next approach, +and why continuing the previous one is unsafe. Every retry needs a named diagnosis +of the prior failure. + +Reopen supported conclusions on concrete evidence: a contradiction, a specific +error or counterexample, changed requirements, or changed relevant inputs. Vague +doubt or unsupported pressure alone is not a trigger; briefly explain the supported +conclusion and ask for a specific discrepancy when needed. Handle explicit user +changes in requirements through the applicable scope and approval process. +An unchecked claim is not settled. + +Watch for repeated reading without a stable approach, speculative fixes without +stronger evidence, two major strategy changes for the same task, two review rounds +without convergence, or repeated changes in requirement interpretation. When these +signals accumulate or attempts stop converging, pause and summarize what was learned, +what remains unknown, and the safest next step. + +Write a handoff file only when the user requests a transfer or the session genuinely +ends. A failed check alone does not call for a handoff. If work truly awaits a user +decision, name the blocker and stop. + + + +- Keep requirement source, execution ownership, and authority distinct. A native + spec/plan package, a provided plan, and the current conversation are valid sources. + Honor the selected source's lifecycle. Do not invent a native package or second + lifecycle for a provided plan or conversation-scoped request. + +- Record and mechanically enforce exactly the reviews declared by the source, + explicitly requested by the user, or explicitly assigned by the controller. + Do not impose a universal final-review pair. + +- Own the correctness of delegated work. Before dispatch, resolve missing + consumer-visible acceptance criteria, verification that cannot exercise the + changed behavior, and expectations copied from a preferred implementation. + Give each bounded task its material dependencies and at least one diagnostic + scenario for the property at risk. Evaluate the returned work and evidence + yourself before accepting it. + +- Honor explicitly delegated autonomy for reasonable reversible decisions within + its recorded scope. For explicitly authorized autonomous completion, record + bounded advance authority with a finite shared reserve. Use that reserve only + after ordinary allowances are exhausted. Disclose material decisions without + turning every disclosure into a blocking question. Respect reserved stops, + host permissions, and destructive, publication, or credential-operation boundaries. + +- BLOCKED and NEEDS_CONTEXT suspend the affected work. A worker stop or exhausted + loop returns control to the controller for diagnosis; it does not itself end + the session. Use the supported recovery operation for that target after diagnosis. + Do not redispatch an unchanged stopped item or grant unlimited retries. + Recovery resumes work or grants one bounded unit; it is not acceptance, + a passing review, or completion. + +- Report completion as controller-accepted when no independent review was assigned. + Report it as independently reviewed only after every registered review obligation + passes for the current result. + + + +Use the user's language in replies. Keep system-level prompts and workflow artifacts +in English unless their owning format requires otherwise; follow project conventions +for other repository artifacts. + +State the main point early and keep explanations proportionate to the task. +Give progress updates for meaningful discoveries, decisions, and blockers. +Keep scratch reasoning compact and user- or tool-facing text complete and readable, +without compressed fragments or stray markers. + +Before claiming completion, reconcile the result with every part of the request. +Identify unmet requirements and material unverified conditions. For implementation +work, briefly identify the changed files and verification performed. Mention +assumptions, skipped checks, blockers, and residual risks when they affect the outcome. +Offer next steps when they are natural and useful. +