Skip to content

v3.0.0

Choose a tag to compare

@VelimirMueller VelimirMueller released this 20 Oct 23:54
· 57 commits to develop since this release
Immutable release. Only release title and notes can be modified.
v3.0.0
37e3d90

Breaking

  • The lean code profile is the default. A server with no setting lists 6 tools (find_symbol, get_file_context, who_imports, exports_in, search_files, query): about 850 tokens of tool definitions per model call instead of about 12,300. Every positive benchmark run used this profile. The old default, 97 tools (toolsets code, code-admin, scrum), is now the full profile. (#178)
  • The sprint process is opt-in. CODE_CONTEXT_PROFILE picks the profile: code (default), full or all (103 tools, adds fun and stream). CODE_CONTEXT_TOOLSETS still adds single toolsets (code-admin, scrum, fun, stream, all); it only adds and never removes a toolset of the profile. Without full, the 2.x meaning of CODE_CONTEXT_TOOLSETS=fun,stream (the default plus fun and stream) becomes code plus fun and stream.
  • Flair follows the sprint tools. The flair header and footer are on when the scrum toolset is on (full, all) and off otherwise, so they are off by default now. OVERDRIVE_FLAIR=0/1 still forces it.
  • Two entry points in one package: the code index and the sprint process. code-context (dist/server/index.js) is the index: its tool contract is frozen, with no setting it lists the 6 code tools. code-context-sprint (dist/server/sprint.js) loads the same server with CODE_CONTEXT_PROFILE=full as its default (97 tools); the scrum, bridge and dump/restore tools and the dashboard ride with it. A known CODE_CONTEXT_PROFILE still wins on both entries. (#179)
  • The browser logs in to the dashboard with an HttpOnly cookie from a one-time launch URL. The page no longer carries the token: window.__DASHBOARD_TOKEN__ is gone, and GET / without a session answers 401 with a short page that says how to log in. The dashboard prints a launch URL at startup (http://localhost:<port>/auth/launch?code=<random>); a bearer holder mints a fresh one with POST /api/auth/launch, and node scripts/dashboard-launch-url.mjs <db> does that after the /health nonce check from #246. A code works once and expires after 5 minutes. Opening it starts a session with a random 32-byte id, held in dashboard memory only, and sets cc_dashboard_session_<port> (HttpOnly; SameSite=Strict; Path=/; Max-Age=43200), then redirects to /. A session ends after 12 h, on POST /api/auth/logout, or when the dashboard restarts. The cookie never holds the token: cookies reach every localhost port, so another local service that sees it gets one session for at most 12 h, not the token. /api/events takes the cookie or the Authorization header; ?token= is refused with 401. Because a cookie reaches every localhost port, a cookie-authenticated write must send the exact dashboard Origin, and a request the browser marks Sec-Fetch-Site: same-site or cross-site is refused. The bearer header keeps working for the MCP server, the hooks and scripts (TM-01, TM-02). (#243)
  • The index entry no longer creates, migrates or seeds the scrum tables. Under the code profile it opens context.db with the code-index tables only and never loads src/scrum, src/dashboard or src/bridge. A sprint toolset (full, all, or CODE_CONTEXT_TOOLSETS with scrum, fun or stream) loads them through src/server/sprint-toolsets.ts. test/boundary.test.ts fails on any other import from src/server into the sprint side. (#179)
  • The custom bridge action is gone. POST /api/bridge/actions answers 400 for custom, and each forwarded action needs a payload that matches its schema; see Security. (#244)

Added

  • health reports profile and toolsets, and ignored_settings for a value the server did not understand. The server logs the profile at start and a WARN line for an ignored value.
  • setup writes the profile into .mcp.json. It sets CODE_CONTEXT_PROFILE=full in the code-context entry env, because setup installs the sprint process. --profile code|full|all picks another one. An existing CODE_CONTEXT_PROFILE is kept, a 2.x CODE_CONTEXT_TOOLSETS=code stays lean, and other keys of the entry (env, type, …) are no longer dropped. --name and --profile values are no longer taken as the target path.
  • /kickoff checks the profile first. Step 0a calls health and stops with the exact setting when the scrum or code-admin toolset is off. It names the code-context-sprint entry as the fix.
  • New bins code-context, code-context-sprint and vlm-code-context-mcp. vlm-code-context-mcp is an alias of code-context, so npx -y vlm-code-context-mcp ./context.db starts the index. code-context-mcp stays the setup command. (#179)
  • setup installs the entry that matches the profile. full and all write dist/server/sprint.js into .mcp.json, code writes dist/server/index.js. Setup still writes CODE_CONTEXT_PROFILE: the setting is explicit, health shows it, and the entry still works when pointed at index.js. (#179)
  • CI runs on pull requests to release branches (release/**), not only on main. (#179)
  • The public contract is written down and CI enforces it. docs/CONTRACT.md freezes the 3.x surface: the bins and their flags, the profiles and env settings, the names, input schemas and default output of the 6 code tools, the names and input schemas of the sprint tools (their output is best effort), the server instructions and the context.db rules (who owns which table; a minor release may add tables and columns, never drop or rename). Snapshots in test/contract/snapshots/ pin it; npm run contract:update rewrites them. The new CI job contract fails when a PR changes a snapshot without a major version bump in package.json or the label contract-change-approved (scripts/check-contract-change.mjs). A change to a tool description only is not a contract change. (#180)

Migration

  • An install that uses the sprint tools sets CODE_CONTEXT_PROFILE=full in the server env (.mcp.json → mcpServers["code-context"].env, or --mcp-config, or the shell). npx code-context-mcp setup . does it for a project: in update mode it writes full when the entry has no profile setting yet.
  • CODE_CONTEXT_TOOLSETS=code keeps working and changes nothing now; remove it when you like.
  • /kickoff in an existing project keeps its old copy (setup never overwrites a command file). Delete .claude/commands/kickoff.md and run setup again to get Step 0a.
  • Pick an entry (#179). code-context-sprint is canonical for the sprint process, code-context for the index:
    • You use the sprint process: change the command to code-context-sprint (or the args to …/dist/server/sprint.js <db>). npx code-context-mcp setup . does it in update mode.
    • You only read code: keep dist/server/index.js (now code-context) and remove CODE_CONTEXT_PROFILE. You get the lean profile.
    • An existing .mcp.json with dist/server/index.js and CODE_CONTEXT_PROFILE=full keeps working in 3.x: the index entry loads the sprint toolsets when the profile asks for them.
  • Open the dashboard through a launch URL (#243). Plain http://localhost:3333 no longer logs a browser in. Run /ccui, or open the launch URL the dashboard prints at startup; mint a new one with node node_modules/vlm-code-context-mcp/scripts/dashboard-launch-url.mjs <db>. A script that used /api/events?token= sends Authorization: Bearer <token> instead. A session lasts 12 h; after that, or after a dashboard restart, run /ccui again.
  • One shared context.db. Both entries open the same file. The index entry owns the code-index tables (files, exports, dependencies, directories, changes, indexed_repos); the sprint entry owns the scrum tables and the schema_versions stamp. A database the index entry created gets its scrum tables, stamped at the latest version, on the first start of the sprint entry. Both entries refuse a database stamped by a newer version.

Fixed

  • The live bench scores only the final answer block. The task checkers read the answer from the last fenced code block (or the text after an Answer:/Result:/Final answer: line), so a name a session mentions in explanation prose no longer counts as a wrong answer; --score-prose restores the old behaviour for comparison. The tool counter is the server's code profile (now including who_imports and exports_in) instead of a hand-kept list, and npm run bench:rescore prints the old and new success rate per arm. (#261)
  • The VitePress docs build passes again. The three dead links that failed npm run build in docs/ — ../README in docs/ARCHITECTURE.md and docs/REFERENCE.md, ../BENCHMARK-GUIDE in docs/user-guide.md — point at the GitHub files now, and CI builds the docs on pull requests that touch docs/, so a dead link cannot land unnoticed again. (#264)
  • The MCP server and the dashboard share one auth token again. Both read or create .code-context/dashboard.token in the directory of the context.db they open (dashboardTokenRoot in src/dashboard/auth.ts); earlier each used its own cwd, so the two processes held different tokens and every MCP notify call got 401 and the dashboard did not refresh live. The MCP server passes its db path to the notify code at start (setDashboardDbPath); the cwd is used only when no db path is known. CODE_CONTEXT_DASHBOARD_TOKEN still wins when set. Migration: an old .code-context/dashboard.token beside a launcher cwd stays in place and is now ignored; delete it, or set CODE_CONTEXT_DASHBOARD_TOKEN for both processes. (#235)
  • Restore and import never replace an existing row. restore_database, import_from_file and POST /api/restore share one restore (src/scrum/restore.ts) that inserts with ON CONFLICT DO NOTHING instead of INSERT OR REPLACE. Earlier a dump row with a clashing id, or since v27 a clashing ticket ref, silently replaced the existing ticket, and the delete behind REPLACE also removed the children of a replaced row (a replaced sprint lost its tickets). A row equal to the existing one counts as unchanged; any other clash is listed and the existing row stays. By default a clash refuses the whole restore (MCP error, HTTP 409) and nothing is written; on_conflict: "skip" keeps the existing rows and restores the rest; dry_run: true only reports. (#230)
  • Every ticket ref is unique across the database. Schema v27 adds a unique index on tickets.ticket_ref; earlier only UNIQUE(sprint_id, ticket_ref) held, so two tickets without a sprint could share a T-<n>. The MCP create_ticket and start_sprint tools and POST /api/tickets take refs from one function (nextTicketRef, in the locked transaction from #215); create_ticket without ticket_ref now gets the next free T-<n>, and a hand-set ref that another ticket has is refused. Existing installs migrate on the next start: per shared ref the oldest ticket keeps it, the others get the next free ref, and the ticket history (event_log) keeps the old ref. All installs that share one database must update together, because an older version refuses a v27 database. (#222)
  • The ticket notes and description e2e specs pass without retries. test/e2e/ticket-notes.spec.ts and test/e2e/ticket-description.spec.ts each create their own done sprint and ticket and open the board on that sprint; earlier they added tickets to the shared seeded sprint with Date.now() titles, and failed 6 of 20 runs under --repeat-each=10 --retries=0. The sprint setup moves to createOwnSprint in test/e2e/helpers.ts, which the Kanban spec uses too. (#229)
  • The Kanban drag e2e spec passes without retries. test/e2e/kanban-drag.spec.ts creates its own sprint and ticket, opens the board on that sprint and drags with the mouse on the card's row; earlier it shared the seeded sprint with the other specs, so the card moved below the fold and the drop missed when it ran after them or with --repeat-each. (#220)
  • Every dashboard view shows the stored milestone status. The MILESTONES skill fallback maps an ACTIVE or IN PROGRESS heading to active, and the Planning page milestone card has no in_progress colour; the dashboard now uses only planned, active and completed. (#221)
  • A milestone update never reports success for a deleted milestone. PUT /api/milestone/:id checks the row and writes it in one statement (UPDATE … WHERE deleted_at IS NULL RETURNING *) and answers 404 when no row changed; earlier a delete between the check and the update gave 200 for a write that changed nothing. The handler moves to src/dashboard/handlers/planning.ts. (#214)
  • An active milestone on the Dashboard sprint list shows the active colour. SprintList colours each milestone header and progress bar by the status the database stores (planned, active, completed); earlier it checked in_progress, which the database never stores, so active milestones showed the planned colour. (#211)
  • A new ticket never gets the ref of a deleted ticket. POST /api/tickets counts all ticket rows, deleted rows too, to make the next T-<n> ref, and skips a ref that a row already has; earlier a soft-deleted newest ticket gave its ref again and the insert failed with 500. (#210)
  • A ticket description can be cleared from the dashboard editor. PATCH /api/ticket/:id accepts description as a string or null, the same rule as notes; earlier it refused the null that the editor sends for an empty description, so the old text stayed. The change gets a revision row and an event log row with a NULL new value. (#206)
  • A milestone edit from the dashboard is saved. Each milestone card on the Planning page has an Edit form for the status and the target date. The store sends status and target_date to PUT /api/milestone/:id, with the values the database accepts (planned, active, completed); earlier it sent targetDate, which the route ignored, and in_progress, which the route refused. null clears the target date or the description. The route returns the saved row and answers 404 for a missing milestone. (#205)
  • A new ticket without a priority is saved. POST /api/tickets uses P2, the default of the database column and of the MCP create_ticket tool; earlier it used 0, which the database rule refuses. An empty or unknown priority gets 400. The route returns the saved row. (#204)
  • A new milestone from the dashboard is saved. The Planning page has a New milestone form (name, optional target date). It sends name and target_date, the fields POST /api/milestones parses; earlier the store sent title, so the route answered 400. The route returns the saved row. (#199)
  • A Kanban drag changes the ticket status. The board sends PATCH /api/ticket/:id with the new status, the same route as the ticket editor. Earlier it called PATCH /api/ticket/:id/status, which does not exist. A drop on Done or Not Done goes back to its column with the server error, because the QA gate sets these statuses. (#198)
  • Ticket notes from the dashboard edit form are saved. PATCH /api/ticket/:id accepts notes (a string or null) and records the change like the other fields. The edit form sends the notes through this route; earlier it sent them to a route without a notes field, so the edit was lost. (#197)
  • A bad POST body no longer stops the dashboard. /api/notify/event, /api/claude-output and /api/claude-step answer 400 on invalid JSON, an empty body or a body that is not an object, and 413 on a body over 5 MB. Other /api/* write routes answer 400 on invalid JSON instead of 500. An outer guard answers any other request error with 500, so the process never gets an unhandled rejection. (#188)
  • reset_agents and reset_skills do not run npm run build in your project. The tools spawn no process, so they work in a project without a build script and write nothing to the JSON-RPC stream. The delete and the re-seed run in one transaction; when a step fails, the old rows stay. (#187)

Changed

  • CLI change: code-context-reindex --root defaults to the current directory. Earlier the default was ~/Desktop/Workspace/dev, a path that exists only on the author's machine. Pass --root <dir> to scan another directory. The help text (--help) names the default. (#176)
  • Current Claude models in the agent catalog. KNOWN_AGENT_MODELS (src/scrum/agent-model.ts) and the dashboard picker (src/dashboard/app/src/lib/constants.ts) offer claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5 and claude-haiku-5-5. Factory seeds move to claude-fable-5-1 (dev roles), claude-opus-5-5 (QA) and claude-sonnet-5-5 (other roles, and the default for a new agent without a model). The earlier ids (claude-fable-5, claude-opus-5, claude-sonnet-5, claude-haiku-4-5, claude-opus-4-8, claude-sonnet-4-6) stay accepted, so existing agents keep their model. Seeding never overwrites an existing agents table: existing installs keep their models until you change them with update_agent, in the dashboard, or with reset_agents. Tier routing is unchanged. (#174)
  • Node.js 22 or newer is the supported range. package.json declares "engines": { "node": ">=22" }; Node.js 24 LTS is recommended. CI tests 22, 24 and the current LTS. The minimum stays 22 until 3.0.0. (#173)
  • @remotion/player is no longer a direct dev dependency, and the stale navigation guide is gone. Only the deleted VisionPlayer imported the player; @remotion/cli still installs it for npx remotion render. docs/ux/navigation-migration-guide.md is deleted, because its samples imported removed modules; the UX index, CONTRIBUTING, STYLEGUIDE and two dated plans no longer name removed modules. (#231)
  • Unreachable dashboard modules are removed, and CI fails on new ones. 27 files that no page reached are deleted, among them CapacityPlanningView, GanttChart, GoogleAdsStrategy, SprintPlanner, VisionPlayer, hooks/usePlanning.ts and the components/molecules, components/organisms, hooks and stores barrel files. The new npm run check:dashboard-reachable follows the imports from src/main.tsx with the TypeScript module resolver and the app tsconfig, and the CI lint job runs it. No page changes. (#223)
  • The pre-commit hook type-checks, and package.json drops unused entries. lint-staged (now lint-staged.config.mjs) runs tsc --noEmit once per commit when a staged file is under src/ (-p src/dashboard/app for dashboard app files), then ESLint and Prettier on the staged files; it never runs npm run build. Earlier the hook ran only ESLint and Prettier, while CONTRIBUTING.md and STYLEGUIDE.md said it ran tsc --noEmit and npm run build; the docs now match the hook. The unused gemma-brain dev dependency and the start-gemma script are removed. The Remotion dev dependencies stay: generate_vision_animation prints npx remotion commands for src/remotion/, and docs/guide/architecture.md now says so. (#175)
  • CI type-checks the dashboard app. The test job runs npm run typecheck:dashboard (tsc --noEmit -p src/dashboard/app), so a type error in the dashboard app fails CI; the root tsc --noEmit does not include the app. The 9 type errors it found are fixed: Vite client types for import.meta.env, the steps(2) easing function instead of a string, the optional sprint_id on RetroFinding, a margin wrapper instead of an unknown Skeleton prop, and an agent check in TeamManagementModal. (#213)
  • Unused planning components are removed from the dashboard. SprintPlanningView, PlanningWizard, MilestoneList, PlanningDashboard with EnhancedGanttChart, SprintBurndownView, EnhancedCapacityView and SprintTableView, and PlanningInsights were not shown on any page and still used the old milestone status in_progress and PUT for ticket edits. No page changes. (#212)
  • CI runs lint, format, the dashboard tests and the Playwright specs on every PR. A new lint job fails on ESLint errors and runs Prettier on the files the change touches (npm run format:changed). The test job runs npm run test:frontend. A new e2e job runs the 35 Playwright specs against a seeded dashboard on a throwaway database (npm run test:e2e, port 4317, never a reused server). Every workflow declares permissions: and pins actions by commit SHA. A tracked .husky/pre-commit runs lint-staged (ESLint + Prettier on staged files). (#191)
  • One transactional function moves a sprint to its next phase. update_sprint, advance_sprint and the dashboard advance and sprint update routes call transitionSprint (src/scrum/sprint-transition.ts). It writes the status, the frozen commitment, velocity_completed, one auto-analysis finding (role system), the discovery archive, completed epics and milestone progress in one transaction. When a step fails, nothing changes and the error names the step; the dashboard answers 500. The MCP tools now also complete epics and update milestone progress on rest. get_discovery_coverage writes nothing. (#189)
  • Typed rows and request bodies in the MCP server, the scrum tools and the dashboard write routes. src/scrum/rows.ts has one row type per scrum table, and the queries in src/server and src/scrum use them; @typescript-eslint/no-explicit-any is an error there. Each dashboard POST, PUT and PATCH route parses its body with zod and answers 400 with the field name when a type is wrong; earlier a wrong type gave a 500 or a bad row. (#190)

Security

  • The bridge forwards only schema-checked actions to the agent, never free-form ones. The custom action ("Execute custom action based on payload", free JSON) is removed, so a leaked dashboard token can no longer give the agent arbitrary instructions (TM-03). The hook forwards run_kickoff, advance_sprint, assign_ticket, update_ticket, create_ticket and plan_sprint only; each has a strict payload schema in src/bridge/actions.ts (known keys, typed, strings length-capped, no control characters or backticks), and every payload is capped at 4 KB. POST /api/bridge/actions answers 400 for an unknown action or a payload that does not match, and the hook checks every row again before injection, because a row can also come from execute, an import or a restore; a row that fails is marked failed and never reaches additionalContext. The injected text frames the payload as data and tells the agent to confirm each action with the user before it acts. The hook now marks forwarded rows claimed, not completed, and expires a claim after one hour. Other actions, such as ticket_changed, are no longer forwarded. (#244)
  • The dashboard token never reaches .env.local or the built assets. On start the dashboard writes only VITE_DASHBOARD_PORT to src/dashboard/app/.env.local (mode 0600, npm 2.13.0 shipped a test token that vite build had inlined from that file); the Vite dev server reads .code-context/dashboard.token itself (beside the db from VITE_DASHBOARD_DB, default context.db, or CODE_CONTEXT_DASHBOARD_TOKEN) and adds the Authorization: Bearer header on its /api proxy, so the page needs no token in dev. token.ts reads VITE_DASHBOARD_TOKEN only behind import.meta.env.DEV, so a production build drops it and uses the injected window.__DASHBOARD_TOKEN__ only. npm run build finishes with scripts/check-dist-token.mjs, which fails when a dist/dashboard/assets file contains a 64-hex value within 200 chars of __DASHBOARD_TOKEN__ / VITE_DASHBOARD_TOKEN or the current token; the CI test job runs it too (TM-07). (#247)
  • Bridge hook follow-up: the hook command written by setup shell-quotes both paths, and the cwd-relative .mcp.json fallback is gone — the db comes from --db/CODE_CONTEXT_DB only. (#245)
  • The dashboard bearer token can no longer be redirected through the database or replayed through a captured /health answer. The MCP notify client reads the dashboard port from .code-context/dashboard.port (mode 0600, written by the dashboard beside dashboard.token at startup) and never from the _dashboard_port skills row, which any db writer (execute, an import, a restore) could change to receive the Authorization: Bearer header on the next notify (TM-05); the row is still written for legacy UI readers. Before the token is sent, the client GETs http://127.0.0.1:<port>/health?nonce=<fresh 16-byte hex> — never localhost, which can resolve to ::1 — and proceeds only when the unauthenticated JSON answers server: "code-context-dashboard" and a proof equal to hex(HMAC-SHA256(DASHBOARD_TOKEN, nonce + "\n" + "code-context-dashboard")), compared in constant time. There is no static hash and no verification cache: every notify re-runs its own challenge over the same keep-alive http.Agent, so a listener that replayed a captured /health value or that cannot compute the HMAC never receives the header. A failure stays silent apart from one WARN per process. On clean shutdown (SIGINT/SIGTERM and server close) the dashboard deletes the port file, so a client treats a missing file as "no dashboard" and sends nothing. (#246)
  • The indexer refuses secret-shaped files. isDeniedFileName now denies the extensions .pem .key .p12 .pfx .jks .keystore .asc .gpg .tfstate .tfstate.backup .kdbx .ovpn and the names id_rsa* id_dsa* id_ecdsa* id_ed25519* credentials credentials.json credentials.yml credentials.yaml secrets.yml secrets.yaml secrets.json service-account*.json *-service-account*.json terraform.tfvars *.tfvars token.json oauth*.json .netrc .htpasswd .pgpass .npmrc .pypirc .git-credentials known_hosts authorized_keys, matched case-insensitively on the basename, so a key, certificate or credential never reaches context.db or agent context. A reindex drops rows the new rule refuses, and CODE_CONTEXT_INDEX_ALLOW (comma-separated basenames or *.ext globs) opts a name back in. (#249)
  • export_to_file and import_from_file stay inside the allowed roots. Both tools resolve the target path (realpath of the parent dir + basename, symlinks resolved) and refuse anything outside the allowed roots — the same sandbox as index_directory (cwd plus CODE_CONTEXT_ALLOWED_ROOTS), so the dump cannot write to or read from arbitrary files (TM-10). export_to_file refuses an existing target unless the new overwrite: true argument is passed. Setup adds code-context-dump*.json to the project .gitignore beside context.db*, so the default dump is not committed by accident. (#250)
  • The dashboard fallback page escapes agent-written markdown, and every HTML response sends a CSP. simpleMarkdown() in the legacy dashboard.html (served when the built SPA is missing) escapes & < > " ' before it adds any tag, so vision or milestone text like <img src=x onerror=…> renders as text instead of running same-origin. Every HTML response now sends Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<random>'; object-src 'none'; frame-ancestors 'none'; base-uri 'self', plus X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer; the inline window.__DASHBOARD_TOKEN__ script and the legacy page's inline script carry the per-response nonce. The SSE and JSON API routes are unchanged. (#252)
  • A repo-local .git/config cannot run commands during indexing. Every git call the server makes passes -c core.fsmonitor=false and -c core.hooksPath=/dev/null, and diff calls also pass --no-ext-diff --no-textconv, so a repo that arrives as an archive with core.fsmonitor = <command> cannot run that command on ls-files, diff, log or rev-parse. One helper (gitArgs in src/server/git.ts) builds the args; the indexer, the freshness guard, checkCommitFormat and the dashboard all use it. (#251)
  • The database, its sidecars, every backup and the export dump are created with mode 0600. The MCP server, the dashboard, setup and reindex open context.db through openDbSecure (src/secure-file.ts), which creates the db and its -wal/-shm siblings owner-only whatever the process umask is — SQLite creates the siblings lazily with 0644 & ~umask, so the helper pre-creates them as 0600 before the open. Earlier macOS defaults gave 0644, so every other OS user could read the full content of every indexed file and the scrum data (TM-08). The pre-migration context.db.bak, the --force backup context.db.bak-<ts> and the export_to_file dump are 0600 too. An existing db with group/other read bits is never chmod'ed silently: it gets one WARN line in the session log telling you to run chmod 600. (#248)
  • Docs site dependencies: vite >= 6.4.3 and esbuild >= 0.25.0 in docs/package-lock.json, closing the four Dependabot alerts (GHSA-fx2h-pf6j-xcff, GHSA-v6wh-96g9-6wx3, GHSA-4w7w-66w2-5vf9, GHSA-67mh-4wv8-2f99). (#262)
  • execute cannot write the gate columns. The tool refuses any INSERT, UPDATE, REPLACE or upsert that writes tickets.status, tickets.qa_verified, tickets.verified_by, a tickets.github_* column, tickets.deleted_at, sprints.status, sprints.deleted_at, sprints.archived_at, a milestones.github_* column, milestones.archived_at, epics.archived_at or a projects.github_* column. DELETE on tickets and sprints, REPLACE / OR REPLACE on these tables and an INSERT into them without a column list are refused too, because they reset the gate columns. The error names the column and the tool that runs the gates (update_ticket, update_sprint / advance_sprint, update_milestone, update_epic, set_project, sync_github, or the dashboard delete and archive actions). Earlier a free UPDATE set a ticket to DONE, verified it or pointed it at any GitHub issue, which the dashboard and the gate tools refuse. The check (src/server/sql-gate-columns.ts) parses tokens, not text: quoted names ("x", `x`, [x], 'x'), schema prefixes, aliases, row-value SET (a, b) = …, UPDATE … FROM, IS [NOT] DISTINCT FROM and ON CONFLICT … DO UPDATE SET resolve to the real column; a statement it cannot read on a gated table is refused, and so is an UPDATE or upsert with a top-level comma anywhere after its SET list, or a name = before its WHERE. A hand-written parser cannot match SQLite's grammar exactly (RETURNING, for one, is also a valid column name), so unknown shapes are refused. Writes to other columns and every SELECT stay free. (#181)
  • execute also gates the archive preconditions, the sync_github target and the parent deletes. The tool now refuses writes to milestones.status and epics.status (the archive check of update_milestone / update_epic reads them), to projects.name and projects.repo (with the already gated projects.github_* columns, sync_github resolves its owner/repo and board from them), and DELETE on projects, milestones and epics. A milestone or epic DELETE set tickets.milestone_id / tickets.epic_id to NULL (ON DELETE SET NULL); a deleted and re-created project key skipped the set_project check that keeps a synced project on its repo. The error names update_milestone, update_epic or set_project. The execute tool description now lists the gated columns and these tools. (#263)
  • The bridge hook reads its database from configuration, never from a directory walk. The PreToolUse hook (src/bridge/hook.ts) resolves context.db from the CODE_CONTEXT_DB environment variable, the --db argument, or the code-context entry in the project's .mcp.json — setup now writes the absolute db path into the hook command. With no configured db it exits silently, and it refuses a db owned by another user or reached through a symlink, so a context.db planted in a subdirectory cannot inject its pending_actions into the agent context (TM-04). (#245)