diff --git a/CHANGELOG.md b/CHANGELOG.md index d60d60b..7075ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,20 @@ All notable changes to this project. 0.5.0 is the first public release of the v5 ## [Unreleased] +## [0.6.0] — 2026-08-12 + +The first release since Operator started managing its own repository. On 2026-07-04 the repo `operator` was registered as a managed project, and most of the engine work below was discovered, planned, implemented, and reviewed by the engine running against its own code — the closed loop's first real proof. The rest is the onboarding half: a guided setup screen, the UI in the Docker stack, and the documentation a public repository owes a first-time reader. + ### Added +- **Self-managed operation** — `.operator/` registers this repository as a managed project with eight quality analyzers (`code-quality`, `consistency`, `orchestration-reliability`, `prompt-quality`, `resilience-boundaries`, `security`, `staleness`, `test-strategy`), per-area role contexts for `engine/`, `app/`, and `packages/`, and retrospective rules. The `staleness` analyzer specifically hunts obsolete data and behavior drift — documentation that describes code that no longer exists. +- **Queue-fill schedule** — research is driven by backlog depth rather than by the clock. The discovery selector refills the finding queue toward a target pending count and backs off exponentially after consecutive empty runs, so an exhausted analyzer set stops burning cycles instead of re-running hourly. A run skipped because another holds the lock is a full no-op and does not pollute the backoff counter. +- **Transient CI-failure detection** — a red pipeline is now classified before the review agent is engaged. Infrastructure-shaped failures (runner allocation, network, timeouts) re-run the pipeline; only a genuine test or gate failure spends an agent invocation on a fix. +- **Retrospective orphan reconciliation and rejection-driven learning** — the weekly retrospective stage reaps work items stranded by a closed or abandoned PR, and feeds rejected findings back into the analyzer prompt that produced them, so a false-positive class stops recurring instead of being re-filed every cycle. +- **Per-comment review-thread dispositions** — the supervisor now answers and resolves every inline review comment individually, with an explicit disposition per thread. Previously a review round could be reported handled while individual threads sat unanswered. +- **Self-hosted-runner push deploy** — `Build Image` publishes to GHCR and redeploys to the operator's own VM through a self-hosted runner, so every merge and every nightly CLI refresh lands on the running instance. Watchtower moved behind an opt-in Compose profile for deployments without a runner. +- **AOP parse diagnostics** — unknown `EMIT` keys stripped by the schema now surface as warning diagnostics with did-you-mean hints, and `applyAgentEvents` logs a diagnostic per record. An agent that misspells a field learns about it instead of silently losing the record. +- **Research analyzer output in execution logs** — each analyzer's agent output is captured through the history sink and visible on the execution timeline. - **Guided setup screen (`/setup`) in `@operator/app`** — the instance-side half of onboarding, which until now meant hand-writing `config/repos.yaml` and `.env.local` and hoping the host had the right CLIs. Four re-openable steps: create or select the engine's SQLite state file (created on demand, so the screen works before the engine has ever booted); check this host for `git`, for every agent CLI the configured roles actually route to, and for a git-host token — including an optional one-shot verification against GitHub of a token that is never stored; register the managed repository through the existing validated `/api/kv/repos/{id}` write path; and start the first cycle. The required CLIs are derived from `agent-providers` / `agent-roles` (falling back to the shipped `agents.yaml` on an unseeded database), so a single-vendor deployment is asked for one CLI, not for whatever the default ships with. `POST /api/setup/preflight` is the one new endpoint; the screen adds no second write path. - **`operator-app` service in `deployment/docker-compose.yml`** — the UI now runs in the Docker stack from the same image and the same state volume as the engine, published on `127.0.0.1:3000` (it edits engine configuration and has no authentication of its own; `OPERATOR_APP_BIND` opens it once a proxy is in front). `deployment/Dockerfile` pre-builds the app, and `deploy.sh` and the Watchtower label scope now cover both containers so the UI cannot drift behind the engine image. - **`SECURITY.md`** — supported version line, GitHub private vulnerability reporting as the only channel, expected response times, and a scope note naming the engine's blast radius (repo-scoped token, external agent CLI, pushes confined to `config/repos.yaml`). No e-mail channel. @@ -14,24 +26,63 @@ All notable changes to this project. 0.5.0 is the first public release of the v5 ### Changed +- **`--help` reports the real version.** The CLI banner still carried retired `Operator V3` branding and a hardcoded version string; it is now derived from `package.json`. +- **The orphan reconciler reaps stuck tasks, not only findings.** Any kind stranded by a closed PR is reconciled, rather than tasks accumulating in a permanent in-progress state. +- **The `## Execution Summary` block is optional.** Reviewer agents were contractually required to emit it, and a missing block failed an otherwise successful stage; on success the supervisor's own AOP summary is used instead. +- **The init scout's verify gate must cover every modifiable stack**, not just the primary one — a polyglot repository whose secondary stack had no gate could ship unverified changes. +- **The analyst's dedup window is bounded to recent open findings.** Comparing each candidate against the entire historical corpus grew unbounded with the backlog. +- **Global context auto-detect order has a single source of truth** (`GLOBAL_CONTEXT_ORDER`). The order was previously restated in four bundled prompts, and they had already drifted from the code. - **`LICENSE` copyright holder** is now the legal entity, `MB Ainova Systems`. The MIT body is unchanged and GitHub still detects the license as MIT. ### Removed +- **`dailyResearchHour` engine default.** Research moved to the queue-fill schedule; the key was read by nothing and implied a cadence the engine no longer had. +- **Directory-as-status convention in bundled content.** A work item's status lives in its frontmatter; the parallel convention of encoding it in the containing directory drifted, and merged findings never reached the queue. - **`@anthropic-ai/sdk` dependency.** It was declared but imported nowhere — the engine is an orchestrator and reaches Claude through the `@anthropic-ai/claude-code` CLI it spawns, not through the SDK. Its entry in `knip.json`'s `ignoreDependencies` had been masking it from the dead-code gate; that entry is gone too, so the gate now watches the package if it ever returns. ### Fixed +- **Agent spawns failed with `ENOENT` on Windows.** The `claude` CLI is installed as a shim; the engine now resolves it to its real executable before spawning. +- **CRLF frontmatter was parsed as garbage.** The work-item parser mishandled `\r\n` line endings and quoted values, so a checkout on Windows produced items with empty statuses. The same bug lived independently in the discovery selector's analyzer parser and is now pinned by a cross-boundary regression test. +- **Research findings were silently dropped.** Three independent AOP defects: a child item without a parent was rejected (research findings have no parent by construction); a block whose free-text field contained a colon failed to parse and now falls back to a lenient parse; and records that failed validation were discarded with no log line at all. `improver.md` also emitted un-fenced `EMIT` markers that never parsed. +- **A closed PR's stale label latched a work item's status.** A PR label is now honoured only while its PR is open, so a label left behind on a closed PR no longer overrides the terminal state. +- **Agent-authored commits were silently lost.** When an agent committed inside the workspace itself, `persist` did not detect the commit and never pushed it — a review fix could be made and then discarded. +- **A supervisor's in-place fix was rejected by its own red CI.** The red run belonged to the pre-fix commit; the stage now keeps the item in review instead of failing on stale check results. +- **CI check observation swallowed errors and truncated results.** `listForRef` and `getCheckRuns` were unpaginated, so a large PR's checks were cut off, and a failed fetch degraded to "no checks" with no warning. Both now paginate, `GitHubVCS` has a logger, and a degrade is reported at WARN. +- **Expected `404` existence probes were logged at ERROR.** Checking whether a label exists is control flow, not a failure; Octokit request-log errors carrying a `404` now route to DEBUG while real failures stay ERROR. +- **Primitive-layer KV write failures were swallowed** with no WARN and no logger in the dependency set — a lost write left no trace. +- **A failed stage did not advance the queue-fill backoff.** A stage error is never evidence of output, so it now advances the throttle and bumps the backoff counter like an empty run. +- **The init scout emitted POSIX `cd`-subshell chains** that fail on `cmd.exe`, so generated init and verify commands were unrunnable on Windows hosts. - **A fresh Docker deployment could not write its own state volume.** The image drops to the unprivileged `operator` user but never created `/var/lib/operator`, so Docker initialised the named volume root-owned and the first start died on `EACCES: permission denied, mkdir '/var/lib/operator/state'` — `docker compose up` on a clean host had never worked. The image now creates the mount point owned by that user while still root, which is what Docker copies ownership from. An **existing** volume keeps its current ownership; fix one in place with `docker run --rm -v operator-state:/v alpine chown -R 10001:37 /v`. - **A failed `LocalStorageBundle` open leaked its SQLite handle.** SQLite opens lazily, so `new Database(path)` succeeds for a path that is not a database and the failure only surfaces on the first statement — after which the handle had no owner to close it, holding a lock on the file for the rest of the process. Probing an arbitrary path is exactly what the app's connection test and the new setup preflight do, so the constructor now closes the handle before rethrowing. - **Overlapping engine cycles corrupted the shared workspace.** `Daemon.start` registered the interval before awaiting the bootstrap cycle, and `IntervalScheduler`'s re-entrancy flag only saw cycles it launched itself — so the first tick started a second cycle alongside a bootstrap cycle that was still running an agent. Both cycles shared one git clone per repo, and `runStage`'s lock is keyed on the stage *name*, so a second cycle's `research` stage could check out its branch under a first cycle's running `creator`. The creator's commit then landed on the research branch, its own branch was pushed empty, and GitHub's `422 No commits between` was swallowed as an empty diff — the stage reported success with no PR. Three changes close it: `Daemon.runCycle` now owns the re-entrancy decision, `Engine.processProject` takes a `workspace:{repoId}` lock for the whole repo pass, and `persist` refuses to commit when HEAD has drifted off the branch the stage prepared (`WS_BRANCH_DRIFT`). - **README pointed at a `sync.sh` that does not exist.** The Contributing section told readers to run `bash intelligence/scripts/sync.sh`; the script lives at `intelligence/sync/scripts/sync.sh`. The same stale path sat in a `.gitignore` comment. Every relative link in `README.md`, `CONTRIBUTING.md`, and `SECURITY.md` now resolves to a file in the tree. +- **The Kubernetes manifest in `docs/deployment.md` pinned an image tag that is never published.** `Build Image` pushes `:latest` and `:` only; there is no version-numbered tag, and a copy-pasted `:0.5` would not pull. - **README described a GitHub Actions workflow that was never committed.** The "Automation status" section documented an `orchestrator` workflow on a 5-minute cron, together with a `gh workflow run orchestrator.yml` command. No such workflow exists in `.github/workflows/`, in the git history, or on the remote. The section now describes what actually runs: Operator as an always-on daemon, with `Tests` and `Build Image` as the two CI workflows. ### Security +- **The agent subprocess inherited the operator's git-host token.** The environment denylist stripped four hardcoded GitHub variable names, so a repository configured with any other `tokenEnvVar` handed its token to every spawned agent CLI. The configured `tokenEnvVar` — for both the VCS and the tracker — is now stripped by name. +- **Structured log data was not redacted.** `redactValue` was applied to log messages but not to the structured fields beside them, so a token in a log object's payload reached the sink verbatim. It is now wired through `wrapPino`. - **Dependency alerts cleared to zero** (`npm audit`: 0, from 8 locally / 15 on the default branch). In-range upgrades cover `js-yaml` 4.1.1 → 4.3.1 (quadratic-complexity DoS via merge keys), `next` 15.5.15 → 15.5.23 (SSRF in rewrites and Server Actions, Server Function endpoint disclosure, cache confusion, image-optimization DoS), `brace-expansion`, and `vite`. Three transitive pins that no in-range upgrade could reach are handled by `overrides` in the root `package.json`: `esbuild` → `^0.28.1` (dev-server arbitrary file read on Windows), and, inside `next`, `postcss` → `^8.5.26` (source-map path traversal, XSS in stringify output) and `sharp` → `^0.35.3` (inherited libvips CVEs). The overrides keep the app on the `next` 15 line — upgrading to 16 was the only alternative npm offered and it is a breaking change for no security gain. +### Internal + +- **`master` is protected for human and assistant development too.** Code reaches it through a feature branch and a PR; the single exception is a change whose diff is entirely documentation or `intelligence/` content. Recorded once in the project profile (`protected_branches`, `direct_push_paths`, `pr_flow`). +- **A long-term architect review gate over the operator's own open PRs.** Run under the owner's account, it fact-checks each change, squash-merges the ones that are clearly correct, and holds the rest by label. It never merges a protected surface, a change it cannot verify, or one carrying an unanswered human review thread. +- **Files over the 200-line cap in `engine/pipeline/**` split** — `run-stage.ts` and the AOP planner stage extracted their composers. Two further splits are still open. +- **The Docker AI Sandbox profile was added and then withdrawn** pending a settled runtime choice. It ships in no form in this release. + +### Verification + +- 1984 tests across 128 files (1 skipped), up from 1173 at 0.5.0. +- Coverage 92.69% statements / 84.4% branches / 92.4% functions / 93.5% lines, above the 90% gate. +- `npm run typecheck`, `npm run lint` (eslint + ts-prune + knip), and `npm test` all green on the release commit; `ts-prune` and `knip` both report zero orphans. + +### Upgrading from 0.5.0 + +No state migration. `dailyResearchHour` may be dropped from any local `engine-defaults` override; leaving it in place is harmless, since the key is now ignored. An existing Docker state volume keeps its current ownership and is not repaired by the image fix — see the volume-permission entry above for the one-line `chown`. + ## 0.5.0 — 2026-06-23 First public release of the v5 architecture rebuild. The v4 line was abandoned mid-migration after accumulated dead code and duplicated stage plumbing made further iteration unsafe. v5 restarts with a single composition root, a generic stage loop, and an observability UI that ships from day one. diff --git a/README.md b/README.md index 4bbfea1..175b636 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Closed-loop SDLC engine that autonomously discovers issues, plans fixes, implements code, verifies, delivers, observes, and learns. **Orchestrator, not agent** — schedules work and invokes external agent CLIs (Claude Code, OpenCode, …) through a single generic stage loop. -**Status: 0.5.0 — first public release.** The v5 architecture rebuild is complete and running; this is the first public cut. See [CHANGELOG.md](CHANGELOG.md) for release notes. The v4 implementation was abandoned mid-migration — v5 collapses all pipeline work into `runStage` + 10 primitives, puts state in a pluggable KV model, and ships an observability UI from day one. +**Status: 0.6.0 — self-managed.** The v5 architecture rebuild is complete and running, and the engine now manages this repository itself. See [CHANGELOG.md](CHANGELOG.md) for release notes. The v4 implementation was abandoned mid-migration — v5 collapses all pipeline work into `runStage` + 10 primitives, puts state in a pluggable KV model, and ships an observability UI from day one. ## Before you run it — blast radius diff --git a/SECURITY.md b/SECURITY.md index 321ee2c..16b0356 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,11 +4,11 @@ | Version | Supported | |---------|-----------| -| 0.5.x | Yes — current release line | -| < 0.5 | No | +| 0.6.x | Yes — current release line | +| < 0.6 | No | Only the current release line receives security fixes. Operator is pre-1.0; upgrade -to the latest 0.5.x before reporting. +to the latest 0.6.x before reporting. ## Reporting a vulnerability diff --git a/app/package.json b/app/package.json index e88d6f2..a742c0e 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "@operator/app", - "version": "0.5.0", + "version": "0.6.0", "private": true, "scripts": { "dev": "next dev", diff --git a/docs/deployment.md b/docs/deployment.md index 5dfac5b..ce41464 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -148,7 +148,9 @@ spec: spec: containers: - name: engine - image: ghcr.io/ainova-systems/operator-autopilot/operator-engine:0.5 + # `Build Image` publishes `:latest` and `:`. There is no + # version-numbered tag — pin to a SHA for a reproducible rollout. + image: ghcr.io/ainova-systems/operator-autopilot/operator-engine:latest command: ["npx", "tsx", "engine/entry.ts"] env: - { name: OPERATOR_DIR, value: /var/lib/operator } diff --git a/package-lock.json b/package-lock.json index e68eb7c..9968e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@operator/engine", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@operator/engine", - "version": "0.5.0", + "version": "0.6.0", "workspaces": [ "packages/*", "app" @@ -40,7 +40,7 @@ }, "app": { "name": "@operator/app", - "version": "0.5.0", + "version": "0.6.0", "dependencies": { "@operator/adapters": "*", "@operator/core": "*", @@ -7295,7 +7295,7 @@ }, "packages/adapters": { "name": "@operator/adapters", - "version": "0.5.0", + "version": "0.6.0", "dependencies": { "@operator/core": "*", "better-sqlite3": "^12.0.0", @@ -7304,7 +7304,7 @@ }, "packages/core": { "name": "@operator/core", - "version": "0.5.0", + "version": "0.6.0", "dependencies": { "zod": "^4.0.0" } diff --git a/package.json b/package.json index 2da3154..f149027 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@operator/engine", - "version": "0.5.0", + "version": "0.6.0", "description": "Operator — closed-loop SDLC engine", "type": "module", "engines": { diff --git a/packages/adapters/package.json b/packages/adapters/package.json index e58416d..e2d15b7 100644 --- a/packages/adapters/package.json +++ b/packages/adapters/package.json @@ -1,6 +1,6 @@ { "name": "@operator/adapters", - "version": "0.5.0", + "version": "0.6.0", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/core/package.json b/packages/core/package.json index 59654f2..a71e7f0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@operator/core", - "version": "0.5.0", + "version": "0.6.0", "private": true, "type": "module", "main": "./src/index.ts",