* fix(release): add repository field for npm provenance
The publish-npm step in release run 25350606233 (v0.39.1) failed for all
6 packages with:
npm error code E422
npm error 422 Unprocessable Entity
Error verifying sigstore provenance bundle: Failed to validate
repository information: package.json: "repository.url" is "",
expected to match "https://github.com/kdlbs/kandev" from provenance
We added `--provenance` to `npm publish` earlier so the registry attaches
a sigstore attestation linking each tarball to its source repo + workflow
run. The attestation is signed using info from the OIDC token (which
includes the repo). At publish time, npm cross-checks the package.json's
`repository.url` against the OIDC repo claim — if the package.json
doesn't have one, or it doesn't match, the publish is rejected.
Both the runtime template (package-npm-runtime.sh) and the main
apps/cli/package.json were missing `repository`. Adding `repository` +
`homepage` to both. The runtime template gets it inline via the
heredoc; the main package.json gets static fields.
This fix unblocks the next release. v0.39.1 itself can't be republished
(version conflict on npm if it had landed; on GitHub release it already
exists). The next release will be v0.39.2 and will go all the way through.
Side state notes:
- v0.39.1 git tag + GitHub release exist with all 5 platform tarballs.
- Homebrew tap was updated to v0.39.1 — `brew install` works as v0.39.1.
- npm has nothing at v0.39.1 (all 6 publishes 422'd).
- v0.39.2 will replace the npm side; Homebrew formula will move forward;
v0.39.1 GitHub release stays as a historical artifact.
* fix(release): re-chmod cli.js after artifact download (lost in zip roundtrip)
`brew install kdlbs/kandev/kandev` followed by `kandev` failed with:
/opt/homebrew/bin/kandev: line 2:
/opt/homebrew/Cellar/kandev/0.39.1/libexec/cli/bin/cli.js: Permission denied
cannot execute: Undefined error: 0
Inspected the published v0.39.1 macos-arm64 tarball:
-rwxr-xr-x kandev/bin/kandev (755 — executable, correct)
-rwxr-xr-x kandev/bin/agentctl (755 — executable, correct)
-rw-r--r-- kandev/cli/bin/cli.js (644 — NOT executable)
Root cause: `actions/upload-artifact@v4` + `actions/download-artifact@v4`
zip-encode the artifact during upload and extract during download.
**Executable bits do not survive that round-trip.** The Go binaries
(kandev, agentctl) keep their +x because they're built inside the same
build-bundles job (no artifact crossing); cli.js is built in build-cli,
uploaded as an artifact, downloaded in build-bundles, and emerges as 644.
The chmod +x in package-cli.sh runs before upload, so it's effectively
erased.
Fix: re-apply chmod +x to dist/kandev/cli/bin/cli.js right before
tarballing, in the Package bundle step (which runs in build-bundles
after the artifact download).
Could not have been caught by the Tier 1.1 local smoke test we did
before merging — that test cp -R'd the cli/ dir directly without an
artifact roundtrip, so the chmod survived. This bug only manifests in
CI's matrix builds.
Manual recovery for v0.39.1 brew users:
chmod +x /opt/homebrew/Cellar/kandev/0.39.1/libexec/cli/bin/cli.js
Or wait for v0.39.2 and `brew upgrade kandev`.
* 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.
- Initialize comments.items array in setCommentsTaskId and addComment
- Fixes race condition where WebSocket notifications arrived before API call completed
- Add comment handlers for WebSocket API (comment.add, comment.list)
- Update e2e-test.sh to focus on comment system testing
- Test multi-turn conversation flow (3 turns)
- Display comment content and metadata in test output
- Reduce test timeouts for faster execution
- Fix various orchestrator and lifecycle manager issues
- Add CODE_REVIEW_ISSUES.md documenting known issues
- Add permission stream support to backend agentctl client
- StreamPermissions() connects to permission WebSocket
- RespondToPermission() sends responses via HTTP POST
- PermissionNotification type for permission request data
- Connect lifecycle manager to permission stream
- handlePermissionStream() receives permission requests
- publishPermissionRequest() forwards to event bus
- RespondToPermission() and RespondToPermissionByTaskID() methods
- Add WebSocket handler for permission.respond action
- Validates task_id, pending_id, option_id
- Routes response through orchestrator → executor → lifecycle manager
- Add permission WebSocket actions
- ActionPermissionRequested for notifications
- ActionPermissionRespond for user responses
- Update executor interface with RespondToPermissionByTaskID()
- Update mock implementations in tests
- Add permission response test to e2e-test.sh
- Fix e2e-test.sh ID extraction (strip newlines)
The full permission flow is now wired up:
agent → agentctl → backend → frontend → user → backend → agentctl → agent
Auto-approve mode is still enabled by default until frontend is ready.
- Add Comment model and CRUD operations (repository, service layers)
- Add WAITING_FOR_INPUT task state for agent input requests
- Add WebSocket actions for comment.add, comment.get, comment.list
- Add ActionCommentAdded and ActionInputRequested notifications
- Update PromptAgent to return PromptResult with stop reason
- Add e2e test for agent input request flow
- Add PR template for the repository
This enables agents to request input from users by returning
stopReason 'needs_input' which transitions the task to
WAITING_FOR_INPUT state and creates an agent comment.
- Add Step 13: Verify Session ID in Task Metadata
- Check that auggie_session_id is present in task metadata after completion
- Update test summary to include session ID verification
- Add AgentStatusReady status for when agent finishes a prompt
- Add AgentReady event published when prompt completes
- PromptAgent accepts RUNNING/READY states, marks READY when done
- Add /complete endpoint to explicitly complete a task
- E2E test polls for READY status instead of file creation
- E2E test validates multi-turn conversation flow
- Use acp-go-sdk for all ACP communication in agentctl
- Mark agent as completed when Prompt() returns successfully
- Update task state to COMPLETED when agent completes
- Update task state to FAILED when agent fails and retries exhausted
- Enhanced E2E test to verify agent actually creates files
- E2E test now validates full flow: task creation -> agent work -> file creation -> task completion
- Add proper JSON-RPC message type discrimination in readLoop():
- Response: id + (result|error), no method
- Request: id + method (e.g., session/request_permission)
- Notification: method, no id
- Add permission request types to types.go:
- RequestPermissionParams, PermissionOption
- RequestPermissionResult, PermissionOutcome
- Implement handleRequestPermission in session manager:
- Auto-approves by selecting first 'allow' option
- Handles workspace indexing and tool permissions
- Add augment_provider.go for session credential injection
- Add repository_url field to task model for workspace mounting
- Create scripts/e2e-test.sh for automated testing
- Update documentation with ACP protocol details
Features:
- Unified Go binary running Task Service, Agent Manager, and Orchestrator
- SQLite persistence for boards, columns, and tasks
- Docker-based agent execution with ACP message streaming
- Augment Agent integration with session resumption support
- RESTful API for all operations (port 8080)
- In-memory event bus for service communication
Architecture:
- cmd/kandev: Single entry point for all services
- internal/task: Task, board, and column management
- internal/agent: Docker container lifecycle and ACP streaming
- internal/orchestrator: Agent coordination and monitoring
- internal/events: In-memory pub/sub event bus
Documentation:
- README.md: Project overview and quick start
- docs/ARCHITECTURE.md: System architecture (current + planned)
- docs/GETTING_STARTED.md: Development setup guide
- docs/IMPLEMENTATION_PLAN.md: Original microservices plan
- backend/NEXT_STEPS.md: Development roadmap