Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,37 @@ concurrency:
cancel-in-progress: true

jobs:
# Detects which path groups changed so downstream jobs can be skipped.
# On direct pushes to main every job always runs (full validation before merge).
detect-changes:
name: Detect changed paths
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
governance: ${{ steps.filter.outputs.governance }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
governance:
- 'rules/**'
- 'docs/**'
- 'scripts/**'
- 'prompt-budget.yml'
- 'AGENTS.md'

governance:
name: Governance lints
needs: detect-changes
# Always run on push to main; on PRs only when governance files changed.
if: github.event_name == 'push' || needs.detect-changes.outputs.governance == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -25,6 +54,8 @@ jobs:

frontend:
name: Frontend (vitest + eslint + build)
needs: detect-changes
if: github.event_name == 'push' || needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
defaults:
run:
Expand All @@ -48,6 +79,8 @@ jobs:

backend-sqlite:
name: Backend (SQLite driver)
needs: detect-changes
if: github.event_name == 'push' || needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -64,6 +97,8 @@ jobs:

backend-postgres:
name: Backend (PostgreSQL driver)
needs: detect-changes
if: github.event_name == 'push' || needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
services:
postgres:
Expand Down
173 changes: 47 additions & 126 deletions DECISIONS.md

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions DECISIONS_ARCHIVE.md

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Native PM — Makefile

.PHONY: all build build-backend build-anpm build-connector test test-local lint dev serve clean release docker-build docker-up docker-down lint-governance lint-rules lint-docs budget-report validate-prompt-budget decisions-conflict-check test-frontend pre-pr pre-pr-fast
.PHONY: all build build-backend build-anpm build-connector test test-local test-affected test-affected-backend test-affected-frontend lint dev serve clean release docker-build docker-up docker-down lint-governance lint-rules lint-docs budget-report validate-prompt-budget decisions-conflict-check test-frontend pre-pr pre-pr-fast

# Default
all: build
Expand All @@ -23,6 +23,16 @@ test:
test-local:
cd backend && go test ./... -v -count=1

# Run only tests for packages affected by current git changes.
# Uses SQLite by default; pass TEST_DATABASE_URL=postgres://... to override.
test-affected-backend:
bash scripts/test-affected.sh

test-affected-frontend:
cd frontend && npm run test:affected

test-affected: test-affected-backend test-affected-frontend

test-integration:
cd backend && go test ./... -v -tags=integration -count=1

Expand Down
37 changes: 32 additions & 5 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"syscall"
"time"

activitypkg "github.com/screenleon/agent-native-pm/internal/activity"
"github.com/screenleon/agent-native-pm/internal/config"
"github.com/screenleon/agent-native-pm/internal/connector"
"github.com/screenleon/agent-native-pm/internal/database"
"github.com/screenleon/agent-native-pm/internal/events"
"github.com/screenleon/agent-native-pm/internal/git"
Expand Down Expand Up @@ -105,6 +107,9 @@ func main() {
accountBindingStore := store.NewAccountBindingStore(db, settingsBox)
localConnectorStore := store.NewLocalConnectorStore(db, dialect)

// Phase 3B stores
contextSnapshotStore := store.NewContextSnapshotStore(db)

// Phase 4 stores
userStore := store.NewUserStore(db)
sessionStore := store.NewSessionStore(db, userStore)
Expand All @@ -127,7 +132,9 @@ func main() {
return planning.NewSettingsBackedPlannerWithBindings(taskStore, documentStore, driftSignalStore, syncRunStore, agentRunStore, planningSettingsStore, accountBindingStore, userID, cfg.PlanningMaxResponseBytes)
}).WithLocalConnectorStore(localConnectorStore).
WithAccountBindings(accountBindingStore).
WithNotifications(notificationStore)
WithNotifications(notificationStore).
WithRoleSuggester(connector.SuggestRole).
WithContextSnapshotStore(contextSnapshotStore)
planningSettingsHandler := handlers.NewPlanningSettingsHandler(planningSettingsStore)
syncHandler := handlers.NewSyncHandler(syncRunStore, syncService, projectStore)
agentRunHandler := handlers.NewAgentRunHandler(agentRunStore, projectStore)
Expand All @@ -141,16 +148,32 @@ func main() {
accountBindingHandler := handlers.NewAccountBindingHandler(accountBindingStore).
WithLocalMode(cfg.LocalMode).
WithLocalConnectorStore(localConnectorStore)
// Phase 6c PR-4: broker is created here (before localConnectorHandler) so
// planning-run-changed SSE events can be wired without a forward reference.
notificationBroker := events.NewBroker()
notificationStore.SetBroker(notificationBroker)

// Phase 6c PR-4: activity hub for connector execution-phase visibility.
activityHub := activitypkg.NewHub(localConnectorStore)
// Restore persisted activity snapshots so the hub has initial state after
// a server restart (best-effort: log and continue on error).
if activities, restoreErr := localConnectorStore.ListActivities(); restoreErr == nil {
activityHub.RestoreFromDB(activities)
} else {
slog.Warn("connector activity restore failed", "err", restoreErr)
}
connectorActivityHandler := handlers.NewConnectorActivityHandler(activityHub, localConnectorStore, projectStore)

localConnectorHandler := handlers.NewLocalConnectorHandler(localConnectorStore, planningRunStore, requirementStore, backlogCandidateStore, agentRunStore).
WithProjectStore(projectStore).
WithNotificationStore(notificationStore).
WithContextBuilder(planning.NewProjectContextBuilder(taskStore, documentStore, driftSignalStore, syncRunStore, agentRunStore)).
WithAccountBindingStore(accountBindingStore).
WithTaskStore(taskStore)
WithTaskStore(taskStore).
WithBroker(notificationBroker).
WithSnapshotSaver(contextSnapshotStore)

// Phase 4 handlers
notificationBroker := events.NewBroker()
notificationStore.SetBroker(notificationBroker)
// Phase 4 handlers (notificationBroker moved above with localConnectorHandler)
userHandler := handlers.NewUserHandler(userStore, sessionStore)
notificationHandler := handlers.NewNotificationHandler(notificationStore).
WithBroker(notificationBroker, sessionStore)
Expand Down Expand Up @@ -204,6 +227,7 @@ func main() {
PlanningSettingsHandler: planningSettingsHandler,
AccountBindingHandler: accountBindingHandler,
LocalConnectorHandler: localConnectorHandler,
ConnectorActivityHandler: connectorActivityHandler,
ProjectRepoMappingHandler: repoMappingHandler,
UserHandler: userHandler,
NotificationHandler: notificationHandler,
Expand Down Expand Up @@ -238,6 +262,9 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

// Purge idle connector activity entries older than 5 min (DECISIONS 2026-04-25 §(g)).
activityHub.StartPurge(ctx)

// Start listener first so we catch port-in-use errors before the goroutine.
ln, err := net.Listen("tcp", bindAddr)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions backend/db/migrations/031_connector_activity.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- +migrate Down
-- SQLite pre-3.35 has no DROP COLUMN. Use table rebuild or leave as no-op dev-only.
7 changes: 7 additions & 0 deletions backend/db/migrations/031_connector_activity.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Phase 6c PR-4: connector activity tracking columns.
-- current_activity_json holds the latest ConnectorActivity snapshot as JSON
-- (empty string = no activity recorded).
-- current_activity_at is the server timestamp of the last activity update
-- (NULL until the connector first reports activity).
ALTER TABLE local_connectors ADD COLUMN current_activity_json TEXT NOT NULL DEFAULT '';
ALTER TABLE local_connectors ADD COLUMN current_activity_at TIMESTAMP;
2 changes: 2 additions & 0 deletions backend/db/migrations/032_planning_context_snapshots.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP INDEX IF EXISTS idx_ctx_snapshots_run;
DROP TABLE IF EXISTS planning_context_snapshots;
14 changes: 14 additions & 0 deletions backend/db/migrations/032_planning_context_snapshots.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Phase 3B PR-1: planning context snapshots table.
-- Stores serialized PlanningContextV2 payloads for audit and replay.
-- planning_run_id FK cascades so snapshots are removed with their parent run.
CREATE TABLE planning_context_snapshots (
id TEXT PRIMARY KEY,
pack_id TEXT NOT NULL,
planning_run_id TEXT NOT NULL REFERENCES planning_runs(id) ON DELETE CASCADE,
schema_version TEXT NOT NULL DEFAULT 'context.v2',
snapshot TEXT NOT NULL DEFAULT '',
sources_bytes INTEGER NOT NULL DEFAULT 0,
dropped_counts TEXT NOT NULL DEFAULT '{}',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_ctx_snapshots_run ON planning_context_snapshots(planning_run_id);
2 changes: 2 additions & 0 deletions backend/db/migrations/033_planning_runs_pack_id.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- SQLite does not support DROP COLUMN; no-op for SQLite compatibility.
SELECT 1;
4 changes: 4 additions & 0 deletions backend/db/migrations/033_planning_runs_pack_id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Phase 3B PR-1: add context_pack_id to planning_runs.
-- Populated at run-creation time with a UUID that correlates the run to its
-- planning_context_snapshots row. Empty string until a snapshot is written.
ALTER TABLE planning_runs ADD COLUMN context_pack_id TEXT NOT NULL DEFAULT '';
2 changes: 2 additions & 0 deletions backend/db/migrations/034_candidate_feedback.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- SQLite does not support DROP COLUMN — no-op
SELECT 1;
2 changes: 2 additions & 0 deletions backend/db/migrations/034_candidate_feedback.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE backlog_candidates ADD COLUMN feedback_kind TEXT NOT NULL DEFAULT '';
ALTER TABLE backlog_candidates ADD COLUMN feedback_note TEXT NOT NULL DEFAULT '';
Loading
Loading