Commit Graph

75 Commits

Author SHA1 Message Date
José Almeida cbc573b0a0 fix(ssh): reject invalid ssh_port + retry in-net keyscan + restore D5 + doc count
executor_ssh.targetFromMetadata: reject malformed / out-of-range
ssh_port instead of silently falling back to 0 → 22 in the resolver.
"abc", "0", "70000" all now return a "must be 1-65535" error so a
broken config can't masquerade as a successful launch routed through
port 22. Cubic P2.

scanFingerprintInsideNetwork (bastion fixture): retry on empty output.
Mirrors the host-side scanFingerprintViaHost retry loop — sshd accepts
TCP before host-key negotiation completes on a cold container, so the
keyscan can come back empty on the first try. Bound at 15s; surfaces
"status=..., stderr=..." on final failure for diagnosis. Cubic P2.

executor-crud D5 (changing config…): restore the seeded executor in a
try/finally. The test pinned the executor to host=10.255.255.1 to
verify the snapshot stayed sticky on the live session, but never put
the real host back, so any later spec in the same worker that reused
seedData.sshExecutorId would dial the unroutable address and hang.
Cubic P2.

e2e-plan.md: replace the "roughly 75" estimate with the actual 80 /
17-files current count plus the ~2.4 min wall-clock. Cubic P3.
2026-05-18 00:05:15 +01:00
José Almeida 274e039a0f docs(spec): correct SSH recovery keying after pool removal
The spec said ResumeRemoteInstance re-opens 'per (host, user) — one
connection serves all surviving sessions on that host', which was
the pool design. The current executor gives each session its own
SSH client, so the resume keys on the full (host, port, user,
identity_*, proxy_jump, host_fingerprint) tuple. That way two
profiles on the same host with different keys can resume side by
side without merging. Cubic P2.
2026-05-18 00:05:15 +01:00
José Almeida 21cc4304a6 chore(e2e): rename docker project to containers + add e2e README
The Docker e2e Playwright project was the natural home for any test
that needs a real Docker daemon on the host — but its name implied it
was only about the Docker executor. SSH e2e tests (incoming in
follow-up commits) belong here too, since they use a Dockerised sshd
container as the SSH executor's remote target.

- Rename project: docker -> containers, widen testMatch to also include
  tests/ssh/*.spec.ts.
- Rename env flag: KANDEV_E2E_DOCKER -> KANDEV_E2E_CONTAINERS, with the
  old name kept as a deprecated alias for one release so local scripts
  and CI configs don't break overnight.
- Rename CI job: e2e-docker -> e2e-containers, switch its blob report
  name and the merge-job dependency.
- Add apps/web/e2e/README.md explaining the project layout, what the
  containers project covers (Docker executor + SSH executor), why it
  was renamed, and how to run it locally.
- Add docs/specs/ssh-executor/e2e-plan.md tracking the SSH e2e
  coverage we'll implement on top of this rename (real sshd container,
  no mocks, 17 spec files covering ~75 cases). Includes a "Deferred"
  section that flags arm64 as something to revisit when
  agentctl-linux-arm64 lands in the build pipeline.
2026-05-17 23:31:16 +01:00
José Almeida 02b4ad15f4 docs: add SSH executor v1 spec
Captures the design behind the new SSH executor: why ("run on a Linux
box I already own" between local and Sprites), the remote layout that
mirrors local ~/.kandev/tasks/, per-session agentctl + forward over a
shared per-host SSH connection, explicit-trust host-key flow, ~/.ssh/config
inheritance, amd64 + agentctl content-hash cache, recovery via persisted
ExecutorRunning metadata, settings UI (Connection + Sessions cards), and
the edit-with-live-sessions warning UX. Out-of-scope items (arm64,
macOS, password auth, chained ProxyJump, working-tree push, etc.) are
called out explicitly with the follow-ups they unblock.
2026-05-17 23:29:40 +01:00
Carlos Florêncio c62739d3c1 feat(cli): kandev service install for systemd and launchd (#926) 2026-05-17 21:12:20 +01:00
Carlos Florêncio 0b2085688b feat(office): autonomous agent management layer (#914) 2026-05-16 20:07:57 +01:00
Carlos Florêncio 2544fd85f8 feat(image): publish universal image flavor with toolchains + customize docs (#891) 2026-05-13 20:45:29 +01:00
Carlos Florêncio 211e8615fb fix: persist container auth, restore tasks filter, refine PR status UI (#883) 2026-05-12 18:06:14 +01:00
João Salavisa 45a46f9899 feat: support tasks without a repository (#850)
* feat: support tasks without a repository

Lets users create a task with no repository attached — useful for research,
brainstorming, design discussions, or pointing the agent at an existing
non-git folder. The create-task dialog gains a 3-mode segmented control
(Repo / URL / None); "None" mode replaces the chip row with a folder
picker that browses the host filesystem via a new GET /api/v1/fs/list-dir
endpoint and either commits an absolute path or falls back to a kandev-
managed scratch workspace at <homeDir>/tasks/<workspaceID>/<taskID>/.

The worktree executor is hidden from the picker in no-repo mode (it
inherently needs a repo); the lifecycle manager also short-circuits
worktree mode server-side as a belt-and-suspenders guard. The Changes
panel auto-closes for repo-less tasks since it has nothing to show, and
the cumulative-diff handler returns null instead of erroring when no
base commit exists.

Spec + plan added at docs/specs/tasks-without-repositories/.

* fix(backend): anchor folder picker to discovery roots (CodeQL path traversal)

CodeQL flagged the new GET /api/v1/fs/list-dir handler for piping a
user-controlled path straight into os.Stat / os.ReadDir. Restrict the
listing to paths inside the configured discovery roots (defaults to
$HOME, same allowlist as repository discovery), reject anything outside
with ErrPathNotAllowed → HTTP 403, and clip the breadcrumb's "up"
button at the root so the picker can't be used to walk above the
allowed prefix.

Tests cover: rejection of paths outside roots, traversal escapes (../),
default-to-first-root, and parent exposure for nested paths.

* fix(backend): use os.Root for folder picker (recognized CodeQL sanitizer)

Previous attempt anchored to discovery roots via isPathAllowed, but
CodeQL's data flow doesn't model that helper as a sanitizer and kept
flagging os.Stat / os.ReadDir. Switch to os.Root (Go 1.24+): open the
matching discovery root once and run Stat/Open/ReadDir against the
relative path within it. The OS itself enforces containment — symlinks
and traversal cannot escape — and CodeQL recognizes os.Root as safe.

* fix: address PR review feedback

- Changes panel auto-close now distinguishes "task loading" from
  "task has no repos" (returns boolean | null), so the panel isn't
  permanently removed during the first-render window before task
  data lands in the store.
- Reset noRepository / workspacePath in resetDiscoveryState so
  reopening the dialog after a None-mode task doesn't carry the
  flag forward.
- Folder-picker breadcrumb: dropped dead `i > 0 && i > 1` condition
  (always equivalent to `i > 1`); chevrons now render between every
  segment as expected.
- list-dir HTTP handler returns a generic error message instead of
  forwarding raw OS errors that leak host paths.
- Document the dataDir → kandevHome inversion convention next to
  the scratch-path computation so future readers don't mistake the
  filepath.Dir call for a fragile derivation.

* fix: address CodeRabbit review nitpicks

- Trim whitespace on workspace_path before persisting to task metadata
  so a value of "   " doesn't get stored as a non-empty path.
- Extract the workspace_path metadata key into models.MetaKeyWorkspacePath
  alongside the existing MetaKeyAgentProfileID / MetaKeyExecutorProfileID
  so the set/read sites can't drift apart.
- Folder picker: reset cached listing when the popover closes, so
  reopening reloads from `value` instead of showing the last browsed
  directory the user navigated away from without committing.
- Submit payload: send `workspace_path: undefined` instead of an empty
  string when the trimmed value is empty, matching the noRepository=false
  case and keeping the API contract clear.

* fix: scratch path lands in kandev home, not its parent

Claude caught a real bug — `m.dataDir` is set from `cfg.ResolvedHomeDir()`
in cmd/kandev/agents.go, not `ResolvedDataDir()` (the field name is
misleading and the comment was wrong). My previous use of
`filepath.Dir(m.dataDir)` therefore walked one level above the kandev
home, so scratch dirs landed at e.g. `~/tasks/<ws>/<task>` instead of
`~/.kandev/tasks/<ws>/<task>` — and at the project root in dev mode
where `KANDEV_HOME_DIR=<project>/.kandev-dev`, which is what the user
saw on their machine.

Use `m.dataDir` directly (it's already the kandev home), document the
field's misnamed-but-correct semantics, and tighten the test fixtures.

Also:
- folder-picker trigger gets `cursor-pointer` (CodeRabbit nitpick).

* fix: pass workspacePath through handleCreateWithoutAgent too

The "create without agent" path called buildCreateTaskPayload without
the new workspacePath field, so picking a folder + clicking "Create
without starting" silently dropped the path. Now matches the
performCreate (with-agent) path.

* fix: satisfy DialogFormState in new-subtask form stub

PR #852 (merged after this branch was created) introduced
new-subtask-form-state.ts which stubs out a DialogFormState for the
subtask dialog. Adding noRepository / setNoRepository / workspacePath /
setWorkspacePath to the type made that stub incomplete, breaking the
Next.js production build (caught by CI, missed by tsc --noEmit).

Add the four inert fields to the stub, alongside the existing fresh-
branch inert fields, and pull both groups into named constants to
keep the useMemo body under the per-function line cap.

* fix: keep toggle-github-url testid as alias on URL-mode button

The new source-mode segmented control replaced the previous
"or paste a GitHub URL" text link, which had data-testid="toggle-github-url".
The existing e2e suite (create-task-github-url.spec.ts and the new
subtask.spec.ts from #852) still selects the URL toggle by that testid.
Keep "toggle-github-url" as the testid on the URL-mode button so both
suites pass without a sweep across all e2e files. Click semantics are
unchanged — single click enters URL mode.

* test(e2e): update toggle assertion for new source-mode segmented control

create-task-github-url.spec.ts:892 was checking the old text-link
toggle's two-way behavior with text labels ("or paste a GitHub URL" /
"use a workspace repository"). The new segmented control has explicit
mode buttons; assert aria-checked instead of label text. The other 9
clicks on `toggle-github-url` in this file still work via the legacy
testid alias on the URL-mode button.

* refactor: extract useActiveTaskHasRepos to its own hook file

dockview-desktop-layout.tsx grew to 609 lines with the new hook +
the Changes panel guard, over the 600-line cap documented in
CLAUDE.md (lint passed but the convention is real). Move the hook
to hooks/domains/kanban/use-active-task-has-repos.ts. File drops
to 590 lines.

* chore: gitignore .claude/scheduled_tasks.lock runtime file

* fix(web): lock chip-row min-height so modal doesn't shift on mode toggle

The chip row uses flex-wrap; Repo mode (chip + branch + add button +
segmented control) sometimes occupies a different vertical extent than
None mode (single folder picker + segmented control), causing the
create-task modal to jump a few pixels when toggling between sources.
Set min-h-9 so the row always reserves at least one chip-row of
height regardless of the active mode.

* feat: let folder picker browse anywhere, drop allowed-roots gate

The picker is for choosing a starting folder for a repo-less task —
users legitimately want /tmp, /var/log/foo, or any directory they have
read access to. Coupling it to the repo-discover endpoint's
discoveryRoots ($HOME by default) was too tight: paths like /tmp
returned 403 "path is not within allowed roots".

Decouple ListDirectory from discoveryRoots. The repo-discover endpoint
(DiscoverLocalRepositories, ValidateLocalRepositoryPath,
LocalRepositoryStatus, ListBranches) stays anchored to the allowlist
since picking a repo to import is a workspace-scoped operation; the
folder picker trusts the local-process boundary instead.

CodeQL is kept happy by routing the FS operations through os.Root
opened at "/" — the Go stdlib sanitizer for path injection still
recognises it (containment to / is trivially satisfied for every
absolute path on the host).

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-12 01:10:22 +01:00
Carlos Florêncio ba24c2963d refactor(logger): close rotating sink on shutdown, add config docs (#877) 2026-05-11 22:55:25 +01:00
Carlos Florêncio f97ac8bbc1 feat: streaming agent install, PTY login terminal, docker container UX (#869) 2026-05-11 17:52:05 +01:00
João Salavisa 7e0b46c227 feat(slack): cookie-mode integration that triages threads via a utility agent (#775)
* feat(slack): add cookie-mode integration that triages messages via utility agent

Adds an install-wide Slack integration that polls the connected user's
own messages for `!kandev <instruction>`, hands the thread context to a
configured utility agent with the Kandev MCP server attached, and
creates the resulting task. Auth uses the user's xoxc- token + d cookie
so it works on locked-down Enterprise Slack tenants where bot installs
aren't possible.

Shared infra:
- ACP host-utility executor accepts McpServers on session/new
- New MCP tool list_repositories_kandev so the triage agent can pick the
  right repo
- Template engine exposes Slack-specific variables for prompt injection
- secretadapter + healthpoll reused from jira/linear

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(slack): address PR review feedback round 9

- Drop dead `var _ = time.Second` no-op and its misleading comment in
  trigger_test.go.
- `RecordAuthHealth` now invalidates the cached client when the probe
  fails, so the poller self-recovers from credential rotation without
  requiring a re-save.
- Stable secondary sort key (`workspace_id DESC`) on the legacy →
  singleton migration so promoted-row selection is deterministic on
  `updated_at` ties.
- Extract `categorySlack` constant in `engine.go` and update the
  `VariableInfo.Category` doc-comment to list the full set of values.
- Realign `slack-integration/spec.md` with what shipped: cookie-mode +
  `!kandev` triage as v1, with bot install / User OAuth / reaction &
  slash & shortcut triggers moved under Future scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 09:03:42 +01:00
José Almeida 379230892b feat: brew install + npm sibling channels via OIDC trusted publishing (#806)
* chore: ignore local secret file

* feat(cli): replace self-update with package-manager-owned runtime resolution

The CLI no longer downloads runtime bundles from GitHub on first run. Instead:

- A new resolveRuntime() in apps/cli/src/runtime.ts resolves the bundle in
  three steps: KANDEV_BUNDLE_DIR env var (Homebrew wrapper, tests) → installed
  @kdlbs/runtime-{platform} npm package → cached download (only when
  --runtime-version is given explicitly).
- apps/cli/src/run.ts is split: launch logic separated from download logic.
  The download path stays for the explicit --runtime-version flag only.
- The --version flag now prints CLI semver (was: runtime tag selector). The
  old behavior moves to --runtime-version, documented as advanced/debug.
- The npm self-update prompt (apps/cli/src/update.ts) is removed entirely.
- apps/cli/package.json declares optionalDependencies for five
  @kdlbs/runtime-{platform} packages and engines.npm: ">=7" (required for
  optional-dep resolution during npx).
- Adds esbuild + a "bundle" script that produces dist/cli.bundle.js — a
  single-file CJS bundle with all Node deps inlined. The Homebrew formula
  uses this; the npm package keeps the unbundled dist/cli.js.

Tests: 130/130 passing. Bundle smoke-tested in a directory with no
node_modules to confirm tree-kill is correctly inlined.

* feat(release): unified vX.Y.Z release flow with brew + npm sibling channels

GitHub release artifacts become the canonical build output. npm and Homebrew
are independent sibling channels — both consume the same GitHub release
tarballs; neither depends on the other.

Workflow changes (.github/workflows/release.yml):

- Trigger is now workflow_dispatch only (maintainer-only, gated by repo write
  access). Inputs: bump dropdown (patch/minor/major) + dry_run checkbox.
- Single job graph orchestrates everything in one run:
  prepare (bump, PR, merge, tag) → build-web/build-cli → build-bundles (5
  platforms) → docker → publish-release → publish-npm + update-homebrew-tap.
- The prepare job inlines version bump, optionalDependencies sync, CHANGELOG
  generation, PR create + squash-merge, and tag push. Replaces what
  release-pr.sh used to do locally.
- Tag-push trigger removed; everything runs from the dispatch flow.

New scripts (called by the workflow):

- package-cli.sh: builds + esbuild-bundles the CLI for inclusion in release
  bundles (cli/bin/cli.js + cli/dist/cli.bundle.js + package.json).
- package-npm-runtime.sh: generates per-platform @kdlbs/runtime-* npm
  packages from release bundles. Sets npm os/cpu fields so npm 7+ skips
  non-matching platforms during npx kandev install.
- publish-npm.sh: verifies GitHub assets exist, downloads them, repackages,
  publishes all 5 @kdlbs/runtime-* packages + main kandev@X.Y.Z.
- update-homebrew-tap.sh: clones kdlbs/homebrew-kandev via SSH using the
  HOMEBREW_TAP_DEPLOY_KEY repo secret, generates Formula/kandev.rb pointing
  at the GitHub release assets, pushes directly to main.

Removed scripts:

- release.sh and release-pr.sh — both are redundant with the new workflow.

Required CI secrets:
- NPM_TOKEN: granular access token with publish access to kandev + @kdlbs/*
- HOMEBREW_TAP_DEPLOY_KEY: SSH private key for kdlbs/homebrew-kandev

* docs: brew install, runtime resolution, and GHA-driven release flow

- README.md: add Homebrew quick start (brew install kdlbs/kandev/kandev),
  update NPX docs to require npm 7+, document the three update paths.
- docs/cli.md: rewrite architecture diagram to show three-step runtime
  resolution. Replace --version <tag> docs with --version (CLI semver) +
  --runtime-version <tag> (advanced/debug). Remove KANDEV_NO_UPDATE_PROMPT.
- apps/cli/README.md: add Homebrew install path; document
  package-manager-owned updates.
- apps/cli/README_internal.md: rewrite to describe the npm package vs
  GitHub bundle artifact-shape distinction and the three-step resolver.
  Document the GHA-driven release UI (Run workflow + bump dropdown).
- AGENTS.md: add Release & Versioning Convention section describing the
  unified single-SemVer flow, the sibling channel model, and the GHA flow.

* fix(release): release-bundle bin/cli.js must require the esbuild bundle

The previous package-cli.sh copied apps/cli/bin/cli.js verbatim into the
release bundle. That file requires `../dist/cli.js` (unbundled), which
only works when node_modules is present alongside it.

In the GitHub release bundle (used by Homebrew and manual installs),
there is no node_modules — `require('tree-kill')` and `require('tar')`
inside dist/cli.js would crash at startup.

Now package-cli.sh writes a release-specific bin/cli.js that requires
`../dist/cli.bundle.js` instead. The bundled file has all Node deps
inlined via esbuild, so it runs standalone in the Cellar.

The npm package keeps its own bin/cli.js (in apps/cli/bin/) requiring
the unbundled dist/cli.js, since npm installs tree-kill etc. normally.

Caught while smoke-testing the release bundle locally with
KANDEV_BUNDLE_DIR — backend + web boot succeeded after this fix.

* feat(release): switch npm publishing to Trusted Publishers (OIDC)

The previous flow required an NPM_TOKEN secret. Local testing with that
token failed with EOTP because the granular access token still required a
2FA OTP on publish despite the account-level setting change. npm now
recommends Trusted Publishers (OIDC) over long-lived tokens.

Workflow change (release.yml):
- publish-npm job declares `permissions: id-token: write` so GitHub Actions
  can mint OIDC tokens.
- Removes NODE_AUTH_TOKEN env var. The npm CLI auto-detects the OIDC
  environment and exchanges the GitHub OIDC token for a short-lived
  npmjs.com publish credential.
- Comment block updated to reflect the new auth model. NPM_TOKEN is no
  longer listed in required secrets (already deleted from repo secrets).

Script change (publish-npm.sh):
- Updates `npm publish` calls to include `--provenance` so each published
  package ships with a signed attestation linking it back to this commit
  and workflow run.
- Header comment explains the OIDC auth model.

Setup required on npmjs.com (per package, before first OIDC publish):
For each of the 6 packages — kandev plus @kdlbs/runtime-{linux-x64,
linux-arm64,darwin-x64,darwin-arm64,win32-x64} — go to the package's
"Settings → Publishing access" and add a Trusted Publisher pointing at
this repo (kdlbs/kandev), workflow filename release.yml, and (optionally)
an environment.

* fix(release): pin optionalDependencies to bootstrap version in committed source

CI was failing because apps/cli/package.json declared optionalDependencies
at 0.16.0 but only 0.0.0-bootstrap exists on npm for those packages. The
lockfile couldn't resolve, and `pnpm install --frozen-lockfile` rejected
the working tree.

Pin optionalDependencies to 0.0.0-bootstrap in the committed source so the
lockfile resolves cleanly. The release workflow now bumps them to the real
version inside publish-npm.sh, just before publishing main kandev — at
which point the matching @kdlbs/runtime-*@VERSION tarballs have already
been published earlier in the same step. This avoids the chicken-and-egg
where the prepare job tried to bump optional deps to a version that didn't
yet exist on npm.

Workflow changes:
- prepare job: only bumps the main package version, not optional deps.
- publish-npm.sh: rewrites optionalDependencies to $VERSION right before
  the main `npm publish`, after the 5 runtime packages have been published.

The published kandev@X.Y.Z still has its optionalDependencies pinned to
matching @kdlbs/runtime-*@X.Y.Z. Only the local source uses the bootstrap
pin for development convenience.

* fix: address PR review comments on release flow + runtime resolution

PR reviewers (Greptile, Claude, CodeRabbit) flagged six concrete issues.
Each is addressed below.

Critical:
- publish-npm.sh: previously exited 0 even when runtime packages failed to
  publish, which would leave kandev@VERSION on the registry pointing at
  @kdlbs/runtime-*@VERSION that never got published. Now: capture full npm
  output on failure, distinguish "already published" (idempotent skip)
  from real failures, abort before publishing main kandev if any runtime
  fails.
- release.yml: build-bundles referenced needs.prepare.outputs.tag without
  declaring prepare in its needs array. Static analysis flagged this.
  Added prepare to the needs list.

Major:
- update-homebrew-tap.sh: the formula's write_env_script only injected
  KANDEV_BUNDLE_DIR. run.ts:174 reads KANDEV_VERSION to display the
  running version in startup logs; without it Homebrew users would see
  "release: (env)" instead of "release: 0.17.0". Pass KANDEV_VERSION:
  version.to_s through the wrapper.
- runtime.ts: the catch {} block silently swallowed validateBundle errors,
  hiding "package installed but corrupt" cases behind the generic
  "no runtime found" message. Split the lookup so MODULE_NOT_FOUND falls
  through, but validation errors propagate.
- run.ts: the --runtime-version code path skipped validateBundle, missing
  the file-existence checks that the npm/env paths get. Added it.

Minor:
- release.yml: workflow-level permissions defaulted to write for everyone.
  Tightened to read-only at the workflow level; jobs that need writes
  (prepare, docker, publish-release) declare them explicitly.
- args.test.ts: added missing tests for --runtime-version with no value
  and --runtime-version followed by another flag, parallel to the
  existing --port tests.

Tests: 132 passing (was 130; +2 new).

* fix: address second-round PR review (cleanup traps, host-key pinning, supply chain)

Latest review (Claude + Greptile) flagged three security/cleanup items
on top of the previous fix commit. All three addressed below.

publish-npm.sh:
- Add `trap 'rm -rf "$WORK_DIR"' EXIT` immediately after `mktemp -d` so
  the tmpdir is cleaned up even when `pnpm build` or `npm publish` fails
  partway through (set -e would otherwise abort before the explicit
  rm at the end).
- Remove redundant `pnpm build` before `npm publish` — the `prepublishOnly`
  script in package.json already runs the build.

update-homebrew-tap.sh:
- Combine `cleanup_ssh` + WORK_DIR cleanup into one EXIT trap so `git clone`
  / `git push` failures don't leak the tmpdir.
- Pin GitHub's published SSH host keys (ed25519, ECDSA, RSA) into a temp
  `known_hosts` file instead of using `accept-new` + `/dev/null`. This
  closes the "trust any host key on first connect" gap. The pinned keys
  come from https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints.

release.yml:
- Replace the curl|tar git-cliff install with `orhun/git-cliff-action@v4`
  (already used in publish-release). The action pins to a digest, so we
  no longer need to verify a tarball checksum manually. Removes a
  supply-chain attack surface where a tampered git-cliff binary could run
  during `prepare` (which has contents:write + pull-requests:write).

* revert: keep curl install for git-cliff, add sha512 verification instead

The previous commit (18b2a5c) replaced our `curl | tar` install with
orhun/git-cliff-action@v4 under the banner "supply chain". On reflection
that swap doesn't actually improve anything:

- @v4 is a moving tag, not a digest pin — same trust as a tagged curl URL.
- Both depend on trusting the same maintainer (orhun).
- The action adds a third-party step we don't audit.

The legitimate concern from the review was "no integrity verification on
the downloaded tarball." Address that directly: pin the asset version,
embed its published sha512, and abort if the download doesn't match.
The hash for v2.6.1's x86_64-linux-gnu tarball came from
https://github.com/orhun/git-cliff/releases/download/v2.6.1/<asset>.sha512.

This restores the original setup with a real integrity check and one
fewer external dependency.

* fix: address remaining PR nits (env restore, sha256 download dir)

runtime.test.ts: drop the redundant `delete process.env.KANDEV_BUNDLE_DIR`
in afterEach — `process.env = { ...origEnv }` already restores the
original environment. The redundant delete would silently un-set the var
if a developer had it in their shell, leaking test side effects across
suites.

update-homebrew-tap.sh: fetch_sha256 now downloads into $WORK_DIR instead
of /tmp/. The EXIT trap covers WORK_DIR, so the .sha256 files are cleaned
up automatically. Avoids leaking 5 tmpfiles per release run on long-lived
runners.

* fix: address third-round PR comments

- update-homebrew-tap.sh: Homebrew formula was calling
  `bin.write_env_script` on the bin directory with target `cli/bin/cli.js`,
  which would create the wrapper at `$HOMEBREW_PREFIX/bin/cli.js` (named
  after the target's basename) instead of `$HOMEBREW_PREFIX/bin/kandev`.
  Result: `brew install kandev` would have produced `command not found`.
  Use `(bin/"kandev").write_env_script(...)` to name the wrapper
  explicitly. Caught by Claude as a P0 blocker.

- publish-npm.sh: main `kandev` publish lacked the EPUBLISHCONFLICT
  idempotency handling that the runtime-package loop has. If publish-npm
  was re-run after a partial failure (e.g. the runtime packages succeeded
  but the main package failed and was then manually published), the
  re-run would fail on the main package even though the version was
  already on the registry. Now: capture output, treat
  "already published" as success, fail loudly with full output on real
  errors. Caught by Greptile as P1.

* refactor(release): direct push to main instead of PR-and-merge dance

The prepare job was opening a release branch, creating a PR, and
immediately squash-merging it via gh CLI. That pattern was carried over
from the old release-pr.sh, but in this workflow it earns nothing:

- main has no branch protection, so direct push is allowed.
- The PR was force-merged in the same step (no `--auto`, no waiting for
  required checks), so it provided no pre-merge validation.
- Discoverability is fine without a PR — git tag, GitHub release, workflow
  run, and main commit are all visible.

The PR-and-merge sequence had ~5 GitHub API roundtrips, each a potential
failure point, plus added 30+ lines of YAML and required pull-requests:
write on the job. Replaced with a straight-line commit + tag + push:

  git fetch origin main && git checkout main && git reset --hard origin/main
  ... bump pkg.json, regen CHANGELOG, npm install ...
  git add ... && git commit && git tag -a && git push origin main "$TAG"

Job permissions tightened to contents: write only.

Race condition note: if main advances between fetch and push, the push
fails non-fast-forward — same outcome as the old PR pattern, where the
auto-merge would have failed. For workflow_dispatch releases with the
maintainer actively driving, the race is essentially never going to
happen, and the recovery (re-run the workflow) is the same.

The dry-run path still bumps + regenerates CHANGELOG inside the runner
so the maintainer can verify the computed values look right, just
without committing or pushing anything.

* chore(release): drop verbose comment about direct-push rationale

* docs: drop 'no first-run download' note from user-facing docs

The phrasing only makes sense to readers who knew the previous model
required a first-run download. New users have no baseline to compare
against, so it adds noise. Removed from docs/cli.md and apps/cli/README.md
(which renders on npmjs.com). Kept in apps/cli/README_internal.md as a
design note for contributors.

* Revert "chore: ignore local secret file"

The /secret entry was added defensively after the bootstrap test, but it
clutters .gitignore with a project-specific path that nobody else needs.
Drop it; secrets stay out of the repo by other means (don't commit them).

* fix: address third-pass review (stale workflow comments + missing test)

release.yml:
- The header comment block and dry_run input description still referenced
  the PR-and-merge step that was removed in 53c2e45. Updated both to
  describe the direct-commit flow.

runtime.test.ts:
- The runtime resolver's npm-package branch (step 2 — the primary path
  after npx kandev@latest or npm install -g) had no positive coverage. In
  the test env, require.resolve for @kdlbs/runtime-* throws
  MODULE_NOT_FOUND, so the existing tests only exercised step 1 (env var)
  and step 3 (cache via --runtime-version), plus the no-runtime-found
  fallthrough.

  Added two tests using vi.spyOn on Module._resolveFilename to point at a
  fake bundle inside tmpDir:
    - happy path: installed npm package resolves to source=npm with the
      right bundleDir
    - corrupt-bundle path: validateBundle errors propagate (rather than
      being swallowed and falling through to "no runtime found")

  These cover the real-world scenarios users will hit on either install
  channel.

Tests: 132 → 134.

* fix(release): push commit + tag atomically

Without --atomic, git push can succeed for the tag ref and fail for the
main ref on a non-fast-forward, leaving an orphaned tag at the pre-bump
commit. The orphan then trips the 'Tag already exists on origin' guard
on the next re-run, requiring manual tag deletion to unblock.

--atomic makes the multi-ref push succeed-or-fail as a unit.

Caught by Greptile as P1.

* refactor(cli): drop runtime.ts step 3 — cache fallback was unreachable

resolveRuntime's runtimeVersion parameter and cache-fallback step were
dead code from production's perspective. The --runtime-version path is
handled entirely in run.ts (findCachedRelease + downloadRuntimeVersion +
findBundleRoot + validateBundle), and run.ts only ever called
resolveRuntime() with no argument. The parameter and step 3 only existed
because tests exercised them.

Two parallel "compute cache dir" implementations (runtime.ts:65 and
run.ts:157) were a drift hazard if the cache layout ever changes.

Changes:
- Drop the runtimeVersion parameter from resolveRuntime.
- Drop the cache step from runtime.ts entirely; remove the unused
  CACHE_DIR + findBundleRoot imports.
- Narrow RuntimeSource from "env" | "npm" | "cache" to "env" | "npm",
  and drop the now-unused tag field from ResolvedRuntime.
- Remove the three "cache fallback" tests from runtime.test.ts. The
  --runtime-version cache lookup is still covered by run.test.ts via
  findCachedRelease tests.

Tests: 134 → 131 (three deleted tests are obsolete, the production
behavior they were verifying is now covered by run.test.ts).

* chore(test): drop dead mock setup left over from runtime.ts step 3 removal

After feb7c5a removed the cache fallback from runtime.ts, the test file
had two dangling artifacts:

- The vi.mock("./constants", ...) block + the mockCacheDir holder + the
  mockCacheDir.value = tmpDir assignment in beforeEach. runtime.ts no
  longer imports anything from ./constants, so the mock was a no-op
  that would mislead readers tracing why constants are mocked here.
- The PLATFORM = "macos-arm64" constant was unused — platform
  determinism is already enforced by the vi.mock("./platform", ...)
  block one section above.

Caught by Claude and the github-code-quality bot.
2026-05-05 00:43:34 +01:00
Carlos Florêncio cc934adb85 fix: make executors_running the single source of truth for agent_execution_id (#799) 2026-05-03 22:17:23 +01:00
Carlos Florêncio 1eae869054 feat: add improve kandev in-app contribution flow (#740) 2026-05-01 15:18:18 +01:00
Carlos Florêncio 183efad419 feat: expose MCP server to external coding agents (#732) 2026-04-29 10:37:44 +01:00
Carlos Florêncio 4277ef495a fix(web): self-recover dockview layout from corrupt persisted state (#729) 2026-04-29 09:56:26 +01:00
Carlos Florêncio 22ebba30b3 feat(skills): add /spec for writing feature specs (#700) 2026-04-25 17:26:14 +01:00
Carlos Florêncio 74d44bd26b feat(acp): support claude-acp Monitor tool and fix incremental tool_call updates (#698) 2026-04-25 16:26:20 +01:00
Carlos Florêncio 7076a40805 chore: change default ports to avoid common conflicts (#681) 2026-04-23 18:09:53 +01:00
Carlos Florêncio cee5b0ad07 docs: refresh README, roadmap, and workflow templates (#624) 2026-04-17 19:42:12 +01:00
Carlos Florêncio 814efdfa67 fix(cli): isolate dev mode state when running inside a kandev task (#617) 2026-04-17 19:41:29 +01:00
Carlos Florêncio 204fac1cca feat: acp-first profiles, models, and modes (#566) 2026-04-15 13:22:32 +01:00
Carlos Florêncio 7ad7736e07 fix: move frontend off port 3000 and silence reverse-proxy panic logs (#568) 2026-04-09 18:50:43 +01:00
Carlos Florêncio 663919a52b fix(agents): update gemini ACP flag and claude-agent-acp package org (#482) 2026-03-28 23:11:21 +00:00
Carlos Florêncio 8651e96097 feat: add file-based knowledge system with decision log and plan storage (#479) 2026-03-28 21:25:36 +00:00
Carlos Florêncio bcbb6826cd docs: remove beta status and replace screenshots with demo gif (#462) 2026-03-24 22:25:09 +00:00
Carlos Florêncio 7397916562 docs: add readme screenshots and update agent protocols to ACP (#456) 2026-03-23 20:38:34 +00:00
Carlos Florêncio 42cdb56889 fix: use kandev home dir for worktrees, repos, sessions instead of data dir (#405) 2026-03-10 19:40:50 +00:00
Carlos Florêncio b48471e063 feat: single-port architecture and browser warning fixes (#390) 2026-03-08 13:10:39 +00:00
Carlos Florêncio e62498ba05 fix: correct default data directory to ~/.kandev/data (#389) 2026-03-08 11:25:21 +00:00
Carlos Florêncio 0da44478d1 feat: add port proxy, symlink file save fix, and remote executor improvements (#358) 2026-03-07 14:53:21 +00:00
João Salavisa 07b39efde2 feat(auggie): enable native session resume with ACP session/load (#380) 2026-03-06 10:41:05 +00:00
Carlos Florêncio c81fb77f72 ci: improve docker image and KANDEV_DATA_DIR env var (#370) 2026-03-05 20:31:17 +00:00
Carlos Florêncio 87a4438037 fix: ui improvements for pr panel, git operations, and task sidebar (#359) 2026-03-03 22:52:47 +00:00
Carlos Florêncio cd96c6f699 feat: install agents on env preparation remote executors (#352) 2026-03-02 22:47:21 +00:00
Carlos Florêncio 44811da702 feat: web e2e tests (#304) 2026-03-02 08:54:58 +00:00
Carlos Florêncio 9e995ba1b8 feat: add Dockerfile, K8s manifests, and deployment docs (#303) 2026-02-27 16:38:53 +00:00
Carlos Florêncio 829236f1be feat: chat improvements (#280) 2026-02-25 02:10:16 +00:00
Carlos Florêncio ba1df2086d feat: remove unnecessary files 2026-02-24 10:40:39 +00:00
Nuno Nobre d82aec1c0e docs: update and review main documentation (#259) 2026-02-22 10:17:51 +00:00
Carlos Florêncio a23ab5be3a feat: remote executors + secrets (#257) 2026-02-21 22:14:56 +00:00
Carlos Florêncio 122094fa5d feat: e2e tests 2026-02-21 22:05:24 +00:00
Carlos Florêncio a2e9390e66 feat: improve claude code 2026-02-21 22:05:24 +00:00
Carlos Florêncio 01b0e9edf1 feat: improve acp + tracing (#258) 2026-02-20 17:30:14 +00:00
Carlos Florêncio 90bec9aaa0 feat: update readme & agent TUI reliability (#239) 2026-02-17 22:59:53 +00:00
Carlos Florêncio 9baa985bbf feat: improve db abstraction (#238) 2026-02-17 17:37:55 +00:00
Carlos Florêncio fc1c328e4a feat: improved stats (#227) 2026-02-16 12:00:54 +00:00
Carlos Florêncio cad2d783b3 feat: improve windows support (#225)
release / build-web (push) Has been cancelled
release / build-bundles (aarch64-linux-gnu-gcc, arm64, linux, ubuntu-latest, linux-arm64) (push) Has been cancelled
release / build-bundles (amd64, darwin, macos-13, macos-x64) (push) Has been cancelled
release / build-bundles (amd64, linux, ubuntu-latest, linux-x64) (push) Has been cancelled
release / build-bundles (amd64, windows, windows-latest, windows-x64) (push) Has been cancelled
release / build-bundles (arm64, darwin, macos-14, macos-arm64) (push) Has been cancelled
feat: windows support
2026-02-15 18:16:21 +00:00
João Salavisa e5cd199447 chore: remove outdated documentation and scripts (#175) 2026-02-04 10:18:44 +00:00