From ec63a873a7fcd90c84839011286b5e070e7f55b3 Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard Date: Fri, 17 Apr 2026 15:12:00 +0100 Subject: [PATCH 1/6] fix(workflow): feature branches from dev, no auto-sync, proper PR flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove auto-sync main to dev from preflight — user controls via PR approval - Workflow now runs on feature branches (feat/, fix/, chore/) not dev directly - Preflight rejects protected branches (main/release/hotfix) - Create-pr: PR from feature branch to release (not dev to release) - Updated description with correct run command: archon workflow run dev-pipeline --from dev --branch feat/issue-N-slug "msg" Co-Authored-By: Claude Opus 4.6 (1M context) --- .archon/workflows/dev-pipeline.yaml | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/.archon/workflows/dev-pipeline.yaml b/.archon/workflows/dev-pipeline.yaml index 7764ab6..c646632 100644 --- a/.archon/workflows/dev-pipeline.yaml +++ b/.archon/workflows/dev-pipeline.yaml @@ -5,6 +5,17 @@ description: | Does: preflight -> research -> plan -> implement/review/commit/critique loop -> roadmap -> package -> test -> file-issues -> create-pr NOT for: quick one-off fixes, documentation-only changes, manual exploration + Run with: + archon workflow run dev-pipeline --from dev --branch feat/issue-- "implement issue #" + + Examples: + archon workflow run dev-pipeline --from dev --branch feat/issue-12-mcp-prompts "implement issue #12" + archon workflow run dev-pipeline --from dev --branch fix/issue-15-unresolved-imports "investigate issue #15" + archon workflow run dev-pipeline --from dev --branch feat/issue-13-incremental-reindex "implement issue #13" + + Branch naming: feat/ for features, fix/ for bugs, chore/ for maintenance. + Always base off dev (--from dev) to get the latest code. + provider: claude model: sonnet effort: high @@ -22,14 +33,17 @@ nodes: PASS=true echo "=== Pre-flight Checks ===" - # Git branch + # Git branch — should be a feature branch (feat/, fix/, chore/) not main/release/hotfix BRANCH=$(git branch --show-current) - if [ "$BRANCH" = "dev" ]; then - echo "PASS: on dev branch" - else - echo "FAIL: on $BRANCH, expected dev" - PASS=false - fi + case "$BRANCH" in + main|release|hotfix) + echo "FAIL: on protected branch $BRANCH — use --branch feat/issue-N-slug" + PASS=false + ;; + *) + echo "PASS: on branch $BRANCH" + ;; + esac # Working tree DIRTY=$(git status --porcelain | grep -v '^\?\?' | head -1) @@ -69,21 +83,6 @@ nodes: PASS=false fi - # Sync main to dev so Archon worktrees have latest code - echo "Syncing main to dev..." - git push origin dev 2>/dev/null || true - MAIN_SHA=$(git rev-parse origin/main) - DEV_SHA=$(git rev-parse origin/dev) - if [ "$MAIN_SHA" != "$DEV_SHA" ]; then - echo "WARN: main is behind dev — creating sync PR..." - gh pr create --base main --head dev --title "chore: sync dev to main" --body "Auto-sync before workflow run" 2>/dev/null || true - gh pr merge --merge --admin 2>/dev/null || true - git fetch origin - echo "PASS: main synced to dev" - else - echo "PASS: main already in sync with dev" - fi - if [ "$PASS" = "true" ]; then echo "READY" else @@ -477,21 +476,23 @@ nodes: depends_on: [file-issues] context: fresh prompt: | - Create a pull request from dev to release. + Create a pull request from the current feature branch to release. Package version: $package.output Test results: $test-cycle.output ## Instructions - 1. Push dev: + 1. Push the current branch: ```bash - git push origin dev + BRANCH=$(git branch --show-current) + git push origin "$BRANCH" ``` 2. Get commit list: ```bash - git log --oneline release..dev + BRANCH=$(git branch --show-current) + git log --oneline release.."$BRANCH" ``` 3. Get PyPI version: @@ -526,7 +527,8 @@ nodes: in the PR body — GitHub will auto-close the issue when the PR is merged. Do NOT manually close the issue — let GitHub handle it via the PR merge. ```bash - gh pr create --base release --head dev --title ": " --body "$(cat <<'EOF' + BRANCH=$(git branch --show-current) + gh pr create --base release --head "$BRANCH" --title ": " --body "$(cat <<'EOF' Closes # ## Summary From f262810dc6e9b5fe6e6a755b4cb33e1b7dcd55a8 Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard Date: Fri, 17 Apr 2026 15:15:07 +0100 Subject: [PATCH 2/6] fix(workflow): branch from hotfix, PR to hotfix Feature branches created from hotfix and PRs target hotfix. Co-Authored-By: Claude Opus 4.6 (1M context) --- .archon/workflows/dev-pipeline.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.archon/workflows/dev-pipeline.yaml b/.archon/workflows/dev-pipeline.yaml index c646632..7869154 100644 --- a/.archon/workflows/dev-pipeline.yaml +++ b/.archon/workflows/dev-pipeline.yaml @@ -6,15 +6,15 @@ description: | NOT for: quick one-off fixes, documentation-only changes, manual exploration Run with: - archon workflow run dev-pipeline --from dev --branch feat/issue-- "implement issue #" + archon workflow run dev-pipeline --from hotfix --branch feat/issue-- "implement issue #" Examples: - archon workflow run dev-pipeline --from dev --branch feat/issue-12-mcp-prompts "implement issue #12" - archon workflow run dev-pipeline --from dev --branch fix/issue-15-unresolved-imports "investigate issue #15" - archon workflow run dev-pipeline --from dev --branch feat/issue-13-incremental-reindex "implement issue #13" + archon workflow run dev-pipeline --from hotfix --branch feat/issue-12-mcp-prompts "implement issue #12" + archon workflow run dev-pipeline --from hotfix --branch fix/issue-15-unresolved-imports "investigate issue #15" + archon workflow run dev-pipeline --from hotfix --branch feat/issue-13-incremental-reindex "implement issue #13" Branch naming: feat/ for features, fix/ for bugs, chore/ for maintenance. - Always base off dev (--from dev) to get the latest code. + Always base off hotfix (--from hotfix) to get the latest code. provider: claude model: sonnet @@ -476,7 +476,7 @@ nodes: depends_on: [file-issues] context: fresh prompt: | - Create a pull request from the current feature branch to release. + Create a pull request from the current feature branch to hotfix. Package version: $package.output Test results: $test-cycle.output @@ -492,7 +492,7 @@ nodes: 2. Get commit list: ```bash BRANCH=$(git branch --show-current) - git log --oneline release.."$BRANCH" + git log --oneline hotfix.."$BRANCH" ``` 3. Get PyPI version: @@ -528,7 +528,7 @@ nodes: Do NOT manually close the issue — let GitHub handle it via the PR merge. ```bash BRANCH=$(git branch --show-current) - gh pr create --base release --head "$BRANCH" --title ": " --body "$(cat <<'EOF' + gh pr create --base hotfix --head "$BRANCH" --title ": " --body "$(cat <<'EOF' Closes # ## Summary From 2bd1e4f54aaea6fd17cf7949900750b491481856 Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard Date: Fri, 17 Apr 2026 15:17:28 +0100 Subject: [PATCH 3/6] fix(commands): align create-pr, preflight, sync with hotfix-based workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create-pr: feature branch → hotfix (was dev → release) - preflight: check for feature branch, reject protected branches (was checking for dev) - sync: hotfix → dev → main → release flow (was main → everything) All three commands now match the Archon workflow YAML. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/commands/create-pr.md | 53 ++++++++++++++++++++--------- .claude/commands/preflight.md | 4 +-- .claude/commands/sync.md | 63 ++++++++++++++++++++--------------- 3 files changed, 75 insertions(+), 45 deletions(-) diff --git a/.claude/commands/create-pr.md b/.claude/commands/create-pr.md index a5654c0..599b7d1 100644 --- a/.claude/commands/create-pr.md +++ b/.claude/commands/create-pr.md @@ -1,28 +1,30 @@ --- -description: Create a pull request from dev to release with release details +description: Create a pull request from feature branch to hotfix with release details allowed-tools: Bash(git:*), Bash(gh:*), Bash(grep:*), Bash(curl:*) --- # Create PR (Step 12 — final workflow step) -Create a pull request from `dev` to `release` with a full description of what was implemented. +Create a pull request from the current feature branch to `hotfix` with a full description of what was implemented. ## Process ### 1. Verify state ```bash -git branch --show-current # must be dev +git branch --show-current # must be a feature branch (feat/, fix/, chore/) git status -s # should be clean -git log --oneline release..dev # commits to include +git log --oneline hotfix..HEAD # commits to include ``` If uncommitted changes exist: STOP, run `/commit` first. +If on a protected branch (main/release/hotfix): STOP, wrong branch. -### 2. Push dev +### 2. Push the feature branch ```bash -git push origin dev +BRANCH=$(git branch --show-current) +git push origin "$BRANCH" ``` ### 3. Get PyPI version (if packaged) @@ -33,22 +35,41 @@ PYPI_VERSION=$(curl -s https://pypi.org/pypi/cognitx-codegraph/json 2>/dev/null ### 4. Analyze commits -Review ALL commits between `release` and `dev`: +Review ALL commits between `hotfix` and the feature branch: ```bash -git log --oneline release..dev -git diff --stat release..dev +BRANCH=$(git branch --show-current) +git log --oneline hotfix.."$BRANCH" +git diff --stat hotfix.."$BRANCH" ``` Identify: type of change (feat/fix/refactor), scope, key changes. -### 5. Create PR +### 5. Comment on the source issue (if applicable) +If an issue number was mentioned, add an implementation summary: ```bash -gh pr create --base release --head dev --title ": " --body "$(cat <<'EOF' +gh issue comment --body "## Implementation Complete +**Package:** cognitx-codegraph=={version} +### What was done +- +### Tests +- {N} tests passing +This issue will be automatically closed when the PR is merged." +``` + +### 6. Create PR + +Include `Closes #N` in the body so GitHub auto-closes the issue on merge. +Do NOT manually close the issue. + +```bash +BRANCH=$(git branch --show-current) +gh pr create --base hotfix --head "$BRANCH" --title ": " --body "$(cat <<'EOF' +Closes # + ## Summary - - -- ## Test plan - [x] Unit tests: {N} passed @@ -62,22 +83,22 @@ gh pr create --base release --head dev --title ": " - PyPI: `cognitx-codegraph=={version}` Install: `pip install cognitx-codegraph=={version}` -🤖 Generated with [Claude Code](https://claude.com/claude-code) +Generated with [Claude Code](https://claude.com/claude-code) EOF )" ``` -### 6. Report +### 7. Report ``` PR Created ---------- URL: Title: -Base: release ← dev +Base: hotfix <- <feature-branch> Commits: {N} PyPI: {version} Done. Workflow complete. -To sync other branches (main, hotfix), run /sync manually. +To sync other branches, run /sync manually. ``` diff --git a/.claude/commands/preflight.md b/.claude/commands/preflight.md index b7c6092..73253b8 100644 --- a/.claude/commands/preflight.md +++ b/.claude/commands/preflight.md @@ -15,7 +15,7 @@ git branch --show-current git status -s git log --oneline -3 ``` -- Must be on `dev` branch +- Must be on a feature branch (feat/, fix/, chore/) — NOT on main, release, hotfix, or dev - Working tree should be clean (untracked `.claude/` files are OK) ### 2. Neo4j container @@ -48,7 +48,7 @@ docker ps --format '{{.Names}} {{.Status}}' | grep codegraph-neo4j ``` Pre-flight Report ----------------- -Git branch: dev ✅ / ❌ (on {branch}) +Git branch: feat/issue-N-slug ✅ / ❌ (on {protected branch}) Working tree: clean ✅ / dirty ❌ Neo4j: running ✅ / down ❌ (fixed ✅) Python venv: OK ✅ / missing ❌ (fixed ✅) diff --git a/.claude/commands/sync.md b/.claude/commands/sync.md index 3dd8595..a51411b 100644 --- a/.claude/commands/sync.md +++ b/.claude/commands/sync.md @@ -1,58 +1,67 @@ --- -description: Merge PR and sync all branches (main, dev, release, hotfix) +description: Sync branches after a PR is merged — propagate hotfix to dev, main, release argument-hint: [PR-number] allowed-tools: Bash(git:*), Bash(gh:*) --- -# Sync Branches (Step 13) +# Sync Branches (standalone utility — not part of the workflow) -**PR**: $ARGUMENTS (if empty, finds the most recent open PR) +**PR**: $ARGUMENTS (if empty, finds the most recent merged PR) -Merge the PR and bring all protected branches up to date. +After you approve and merge a PR into hotfix, run this to propagate changes to the other branches. + +## Branch flow + +``` +hotfix (receives PRs from feature branches) + ↓ sync +dev (development branch) + ↓ sync +main (stable reference) + ↓ sync +release (release branch) +``` ## Process -### 1. Find PR +### 1. Find the merged PR ```bash -# If argument given, use it. Otherwise find most recent. -gh pr list --base main --state open --limit 1 +# If argument given, use it. Otherwise find most recent merged PR to hotfix. +gh pr list --base hotfix --state merged --limit 1 ``` -### 2. Merge PR +### 2. Fetch latest ```bash -gh pr merge <N> --merge --admin +git fetch origin ``` -Uses `--admin` to bypass branch protection (repo admin required). - -### 3. Sync dev to main +### 3. Sync dev to hotfix ```bash -git fetch origin git checkout dev git pull origin dev -git merge origin/main --ff-only +git merge origin/hotfix --no-edit git push origin dev ``` -### 4. Sync release +### 4. Sync main to dev ```bash -git checkout release -git pull origin release --no-rebase --no-edit -git merge origin/main --no-edit -git push origin release +git checkout main +git pull origin main +git merge origin/dev --no-edit +git push origin main ``` -### 5. Sync hotfix +### 5. Sync release to main ```bash -git checkout hotfix -git pull origin hotfix --no-rebase --no-edit +git checkout release +git pull origin release git merge origin/main --no-edit -git push origin hotfix +git push origin release ``` ### 6. Return to dev @@ -69,10 +78,10 @@ Branches Synced ┌──────────┬──────────┬─────────────────┐ │ Branch │ SHA │ State │ ├──────────┼──────────┼─────────────────┤ -│ main │ {sha} │ ← merged PR │ -│ dev │ {sha} │ ← in sync │ -│ release │ {sha} │ ← merged main │ -│ hotfix │ {sha} │ ← merged main │ +│ hotfix │ {sha} │ ← PR merged │ +│ dev │ {sha} │ ← synced │ +│ main │ {sha} │ ← synced │ +│ release │ {sha} │ ← synced │ └──────────┴──────────┴─────────────────┘ Done. All branches contain the latest work. From 4e18296e91b44b7bb8821e32cb3ab7eefd6c3a24 Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard <egouilliard@leyton.com> Date: Fri, 17 Apr 2026 15:20:08 +0100 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20final=20consistency=20pass=20?= =?UTF-8?q?=E2=80=94=20all=20references=20now=20hotfix-based?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dev-pipeline.yaml: comment said "release", now "hotfix" - implement.md: said "must be dev", now "must be feature branch" - diagram .md + .html: said "dev → release", now "feature → hotfix" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .archon/workflows/dev-pipeline-diagram.html | 141 ++++++++++++++++++++ .archon/workflows/dev-pipeline-diagram.md | 106 +++++++++++++++ .archon/workflows/dev-pipeline.yaml | 2 +- .claude/commands/implement.md | 3 +- 4 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 .archon/workflows/dev-pipeline-diagram.html create mode 100644 .archon/workflows/dev-pipeline-diagram.md diff --git a/.archon/workflows/dev-pipeline-diagram.html b/.archon/workflows/dev-pipeline-diagram.html new file mode 100644 index 0000000..08b0cbf --- /dev/null +++ b/.archon/workflows/dev-pipeline-diagram.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="UTF-8"> +<title>Dev Pipeline Workflow + + + + +

Dev Pipeline Workflow

+

Run with: archon workflow run dev-pipeline "implement issue #N"

+ +
+flowchart TD + START(["archon workflow run dev-pipeline"]) --> PF + + PF["0. PREFLIGHT
Neo4j + venv + tests + git"] + PF -->|"exit 0"| R + PF -->|"exit 1"| STOP(["WORKFLOW STOPS"]) + + R["1-2. RESEARCH
gh issue view
codegraph queries
nlm CLI / context7"] + R --> P + + P["3. PLAN
Read plan_local template
Write plan file"] + P --> DC + + subgraph LOOP1 ["dev-cycle — loop node (max 10 iters, 10min idle timeout)"] + direction TB + I["4. IMPLEMENT
Execute plan tasks
pytest after each change"] + I --> RV + + RV["5. REVIEW
feature-dev:code-reviewer
codegraph arch-check"] + RV -->|"issues"| FX["FIX issues"] + FX --> RV + RV -->|"0 issues"| CM + + CM["6. COMMIT
feat/fix/...(scope): why
Co-Authored-By"] + CM --> CR + + CR["7. CRITIQUE
Check acceptance criteria
codegraph structural verify"] + CR -->|"FAIL"| I + end + + DC --> LOOP1 + LOOP1 -->|"CYCLE_COMPLETE +
until_bash: pytest=0"| RM + + RM["8. ROADMAP
Update ROADMAP.md"] + RM --> PK + + PK["9. PACKAGE
Bump 0.1.X+1
python -m build
twine upload"] + PK --> TC + + subgraph LOOP2 ["test-cycle — loop node (max 10 iters, 10min idle timeout)"] + direction TB + T1["Unit tests + compileall"] + T1 --> T2["Install test (fresh venv)"] + T2 --> T3["Self-index (dogfood)"] + T3 --> T4["Leytongo (real-world TS)"] + T4 --> T5["Arch check"] + T5 -->|"fail"| TF["Fix + re-package"] + TF --> T1 + end + + TC --> LOOP2 + LOOP2 -->|"TESTS_PASS +
until_bash: pytest=0"| FI + + FI["11. FILE ISSUES
gh issue create
for discoveries"] + FI --> PR + + PR["12. CREATE PR
feature to hotfix
+ PyPI version link"] + PR --> DONE(["DONE"]) + + style PF fill:#1a472a,color:#fff,stroke:#2ea043 + style STOP fill:#6e1212,color:#fff,stroke:#da3633 + style DONE fill:#1a472a,color:#fff,stroke:#2ea043 + style LOOP1 fill:#0d1b2a22,stroke:#1f6feb,stroke-width:2px,color:#c9d1d9 + style LOOP2 fill:#0d1b2a22,stroke:#1f6feb,stroke-width:2px,color:#c9d1d9 + style CR fill:#3d2200,color:#fff,stroke:#d29922 + style T5 fill:#3d2200,color:#fff,stroke:#d29922 + style I fill:#1a1a3d,color:#fff,stroke:#8957e5 + style RV fill:#1a1a3d,color:#fff,stroke:#8957e5 + style CM fill:#1a1a3d,color:#fff,stroke:#8957e5 +
+ +

Safety Mechanisms

+ +
+flowchart LR + subgraph "Each loop exits when ANY of these fire" + A["Agent signals
promise COMPLETE"] + B["until_bash
pytest exit 0"] + C["max_iterations: 10"] + D["idle_timeout: 10min"] + end + A & B --> AND{"BOTH
required"} + AND --> EXIT["Loop exits"] + C --> EXIT + D --> EXIT + + style AND fill:#3d2200,color:#fff,stroke:#d29922 + style EXIT fill:#1a472a,color:#fff,stroke:#2ea043 +
+ +

Node Details

+ + + + + + + + + + + + +
StepNode IDTypeModelKey Tools
0preflightbash-docker, pytest, codegraph
1-2researchpromptopusgh, codegraph CLI, nlm CLI, context7
3planpromptopusRead, Write (plan file)
4-7dev-cycleloopworkflow defaultAll tools, code-reviewer agent
8roadmappromptworkflow defaultRead, Edit
9packagepromptworkflow defaultbuild, twine, curl
10test-cycleloopworkflow defaultpytest, codegraph, pip
11file-issuespromptworkflow defaultgh issue create
12create-prpromptworkflow defaultgh pr create
+ +

How to Run

+

archon workflow run dev-pipeline "implement issue #12 — expose queries.md as @mcp.prompt() templates"

+ + + + diff --git a/.archon/workflows/dev-pipeline-diagram.md b/.archon/workflows/dev-pipeline-diagram.md new file mode 100644 index 0000000..0e35efb --- /dev/null +++ b/.archon/workflows/dev-pipeline-diagram.md @@ -0,0 +1,106 @@ +# Dev Pipeline Workflow + +## Overview + +```mermaid +flowchart TD + START([Start: archon workflow run dev-pipeline]) --> PF + + PF["/preflight
bash node"] + PF -->|PASS| R + PF -->|FAIL| STOP([Workflow stops]) + + R["/research
codegraph queries + nlm CLI
+ context7 MCP"] + R --> P + + P["/plan
Read plan_local template
Write plan file directly"] + P --> DC + + subgraph IMPL_LOOP ["Implementation Loop (max 10 iterations)"] + direction TB + I["Step 4: IMPLEMENT
Execute plan tasks
Validate after each change"] + I --> RV + RV["Step 5: REVIEW
code-reviewer agent
+ codegraph arch-check
+ dead-code check"] + RV -->|issues found| FIX["Fix issues"] + FIX --> RV + RV -->|clean| C + C["Step 6: COMMIT
Conventional commit
+ Co-Authored-By"] + C --> CR + CR["Step 7: CRITIQUE
Verify acceptance criteria
+ codegraph structural checks"] + CR -->|FAIL: gaps found| I + end + + DC{dev-cycle
loop node} --> IMPL_LOOP + IMPL_LOOP -->|"CYCLE_COMPLETE
+ until_bash passes"| RM + + RM["/roadmap
Update ROADMAP.md"] + RM --> PK + + PK["/package
Bump version + build + PyPI"] + PK --> TC + + subgraph TEST_LOOP ["Test Loop (max 10 iterations)"] + direction TB + T1["Stage 1: Unit tests
pytest + compileall"] + T1 --> T2 + T2["Stage 2: Install test
Fresh venv + pip install"] + T2 --> T3 + T3["Stage 3: Self-index
codegraph index on itself"] + T3 --> T4 + T4["Stage 4: Leytongo
Real-world TS project"] + T4 --> T5 + T5["Stage 5: Arch check
codegraph arch-check"] + T5 -->|any failure| TFIX["Fix + re-package"] + TFIX --> T1 + end + + TC{test-cycle
loop node} --> TEST_LOOP + TEST_LOOP -->|"TESTS_PASS
+ until_bash passes"| FI + + FI["/file-issues
Create GitHub issues for
out-of-scope discoveries"] + FI --> PR + + PR["/create-pr
feature → hotfix
with PyPI version link"] + PR --> DONE([Done]) + + style PF fill:#2d5016,color:#fff + style STOP fill:#8b0000,color:#fff + style DONE fill:#2d5016,color:#fff + style DC fill:#1a3a5c,color:#fff + style TC fill:#1a3a5c,color:#fff + style CR fill:#5c3a1a,color:#fff + style T5 fill:#5c3a1a,color:#fff + style IMPL_LOOP fill:#0d1b2a11,stroke:#1a3a5c,stroke-width:2px + style TEST_LOOP fill:#0d1b2a11,stroke:#1a3a5c,stroke-width:2px +``` + +## Safety Mechanisms + +```mermaid +flowchart LR + subgraph "Loop Exit Conditions" + A["Agent signals
<promise>COMPLETE</promise>"] + B["until_bash
pytest exit code 0"] + C["max_iterations: 10
failsafe"] + D["idle_timeout: 10min
hang detection"] + end + + A --> EXIT{Exit loop} + B --> EXIT + C --> EXIT + D --> EXIT +``` + +## Node Details + +| Step | Node ID | Type | Model | Key Tools | +|------|---------|------|-------|-----------| +| 0 | `preflight` | bash | - | docker, pytest, codegraph | +| 1-2 | `research` | prompt | opus | gh, codegraph CLI, nlm CLI, context7 | +| 3 | `plan` | prompt | opus | Read, Write (plan file) | +| 4-7 | `dev-cycle` | loop | opus | All tools, code-reviewer agent | +| 8 | `roadmap` | prompt | sonnet | Read, Edit | +| 9 | `package` | prompt | sonnet | build, twine, curl | +| 10 | `test-cycle` | loop | opus | pytest, codegraph, pip | +| 11 | `file-issues` | prompt | sonnet | gh issue create | +| 12 | `create-pr` | prompt | sonnet | gh pr create | diff --git a/.archon/workflows/dev-pipeline.yaml b/.archon/workflows/dev-pipeline.yaml index 7869154..ac3fb00 100644 --- a/.archon/workflows/dev-pipeline.yaml +++ b/.archon/workflows/dev-pipeline.yaml @@ -469,7 +469,7 @@ nodes: List all issues created with their numbers and titles. # ═══════════════════════════════════════════════════════════════ - # Step 12: Create PR to release + # Step 12: Create PR to hotfix # ═══════════════════════════════════════════════════════════════ - id: create-pr diff --git a/.claude/commands/implement.md b/.claude/commands/implement.md index c28449d..0ba0edd 100644 --- a/.claude/commands/implement.md +++ b/.claude/commands/implement.md @@ -26,10 +26,11 @@ Create one first: /plan_local "feature description" ## Phase 2: Verify Starting State ```bash -git branch --show-current # must be dev +git branch --show-current # must be a feature branch (feat/, fix/, chore/) git status -s # should be clean ``` +If on a protected branch (main/release/hotfix/dev): STOP, wrong branch. If dirty: warn user, don't proceed until resolved. ## Phase 3: Execute From e23f698b8871472425afd622f5f3c0d0460548b1 Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard Date: Fri, 17 Apr 2026 15:24:49 +0100 Subject: [PATCH 5/6] Revert "chore: bump version to 0.1.1" This reverts commit 4d8d8e2357998c4dcc4d37e6a15afc09f68cb259. --- codegraph/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegraph/pyproject.toml b/codegraph/pyproject.toml index 2e8a397..78f53d3 100644 --- a/codegraph/pyproject.toml +++ b/codegraph/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cognitx-codegraph" -version = "0.1.1" +version = "0.1.0" description = "Code knowledge graph for Claude Code & AI coding agents — index TypeScript, Python, NestJS, FastAPI, React into Neo4j and query architecture in Cypher. Note: v0.2.0 is deprecated, use 0.1.x." readme = "README.md" requires-python = ">=3.10" From 2229e1af4bb0ccfb4725ad163b665b03e95e79fb Mon Sep 17 00:00:00 2001 From: Edouard Gouilliard Date: Fri, 17 Apr 2026 15:24:49 +0100 Subject: [PATCH 6/6] Revert "feat(mcp): expose queries.md as MCP prompt templates (#12)" This reverts commit 8b13807ba58c966c66aabb8e2eaef769bfff849a. --- codegraph/codegraph/mcp.py | 85 +------------------------------------ codegraph/tests/test_mcp.py | 77 --------------------------------- 2 files changed, 1 insertion(+), 161 deletions(-) diff --git a/codegraph/codegraph/mcp.py b/codegraph/codegraph/mcp.py index dd1f2fd..2d61c26 100644 --- a/codegraph/codegraph/mcp.py +++ b/codegraph/codegraph/mcp.py @@ -33,9 +33,7 @@ from __future__ import annotations import os -import re -from pathlib import Path -from typing import Any, NamedTuple, Optional +from typing import Any, Optional from mcp.server.fastmcp import FastMCP from neo4j import READ_ACCESS, Driver, GraphDatabase @@ -44,86 +42,6 @@ from .utils.neo4j_json import clean_row -# ── Query prompt helpers ──────────────────────────────────────────── - -_QUERIES_MD = Path(__file__).resolve().parent.parent / "queries.md" - - -class _QueryEntry(NamedTuple): - name: str - description: str - cypher: str - - -def _slugify(text: str) -> str: - """Lowercase, replace non-alnum runs with '-', strip leading/trailing '-'.""" - text = text.lower() - text = re.sub(r"[^a-z0-9]+", "-", text) - return text.strip("-") - - -def _parse_queries_md(text: str) -> list[_QueryEntry]: - """Parse headings + fenced cypher blocks from *text* into QueryEntry list. - - Multi-block sections get disambiguated names: first block keeps the base - slug, subsequent blocks get ``-2``, ``-3`` suffixes. - """ - entries: list[_QueryEntry] = [] - heading_counts: dict[str, int] = {} - current_heading = "" - lines = text.splitlines() - i = 0 - while i < len(lines): - line = lines[i] - if line.startswith("## "): - current_heading = line[3:].strip() - elif line.strip() == "```cypher": - cypher_lines: list[str] = [] - i += 1 - while i < len(lines) and lines[i].strip() != "```": - cypher_lines.append(lines[i]) - i += 1 - cypher = "\n".join(cypher_lines).strip() - # first // comment becomes description; fall back to heading - description = current_heading - for cl in cypher_lines: - stripped = cl.strip() - if stripped.startswith("//"): - description = stripped[2:].strip() - break - slug = _slugify(current_heading) - count = heading_counts.get(slug, 0) + 1 - heading_counts[slug] = count - name = slug if count == 1 else f"{slug}-{count}" - entries.append(_QueryEntry(name=name, description=description, cypher=cypher)) - i += 1 - return entries - - -def _register_query_prompts(server: FastMCP) -> None: - """Parse queries.md and register each Cypher block as an MCP prompt. - - No-op if queries.md is missing (e.g. inside a pip-installed wheel that - didn't bundle the file). - """ - from mcp.server.fastmcp.prompts.base import Prompt - - if not _QUERIES_MD.is_file(): - return - entries = _parse_queries_md(_QUERIES_MD.read_text()) - for entry in entries: - def _make_fn(cypher: str): - def fn() -> str: - return cypher - return fn - prompt = Prompt.from_function( - _make_fn(entry.cypher), - name=entry.name, - description=entry.description, - ) - server.add_prompt(prompt) - - # ── Configuration ─────────────────────────────────────────────────── _URI = os.environ.get("CODEGRAPH_NEO4J_URI", "bolt://localhost:7688") @@ -205,7 +123,6 @@ def _run_read(cypher: str, **params: Any) -> list[dict]: # ── FastMCP server + tools ────────────────────────────────────────── mcp = FastMCP("codegraph") -_register_query_prompts(mcp) @mcp.tool() diff --git a/codegraph/tests/test_mcp.py b/codegraph/tests/test_mcp.py index 623f6cb..4521c75 100644 --- a/codegraph/tests/test_mcp.py +++ b/codegraph/tests/test_mcp.py @@ -490,80 +490,3 @@ def test_new_tools_surface_service_unavailable(monkeypatch, call): out = call() assert len(out) == 1 and "error" in out[0] assert "Neo4j is unreachable" in out[0]["error"] - - -# ── query prompt parsing ──────────────────────────────────────────── - - -def test_parse_queries_md_extracts_all_blocks(): - text = mcp_mod._QUERIES_MD.read_text() - entries = mcp_mod._parse_queries_md(text) - assert len(entries) == 29 - - -def test_parse_queries_md_single_block_section(): - md = "## My Section\n\n```cypher\n// My description\nMATCH (n) RETURN n\n```\n" - entries = mcp_mod._parse_queries_md(md) - assert len(entries) == 1 - assert entries[0].name == "my-section" - assert entries[0].description == "My description" - assert "MATCH (n) RETURN n" in entries[0].cypher - - -def test_parse_queries_md_multi_block_naming(): - md = ( - "## Foo\n\n" - "```cypher\n// first\nMATCH (a) RETURN a\n```\n\n" - "```cypher\n// second\nMATCH (b) RETURN b\n```\n\n" - "```cypher\n// third\nMATCH (c) RETURN c\n```\n" - ) - entries = mcp_mod._parse_queries_md(md) - assert len(entries) == 3 - assert entries[0].name == "foo" - assert entries[1].name == "foo-2" - assert entries[2].name == "foo-3" - - -def test_parse_queries_md_comment_as_description(): - md = "## Section\n\n```cypher\n// Extracted description\nMATCH (n) RETURN n\n```\n" - entries = mcp_mod._parse_queries_md(md) - assert entries[0].description == "Extracted description" - - -def test_parse_queries_md_heading_fallback_when_no_comment(): - md = "## My Heading\n\n```cypher\nMATCH (n) RETURN n\n```\n" - entries = mcp_mod._parse_queries_md(md) - assert entries[0].description == "My Heading" - - -def test_parse_queries_md_empty_input(): - assert mcp_mod._parse_queries_md("") == [] - - -def test_slugify(): - assert mcp_mod._slugify("4. Impact analysis: who depends on X?") == "4-impact-analysis-who-depends-on-x" - assert mcp_mod._slugify("Schema overview") == "schema-overview" - assert mcp_mod._slugify(" Leading & trailing ") == "leading-trailing" - - -# ── query prompt registration ─────────────────────────────────────── - - -def test_query_prompts_registered_on_server(): - prompts = mcp_mod.mcp._prompt_manager._prompts - assert len(prompts) >= 29 - - -def test_query_prompt_renders_cypher(): - prompts = mcp_mod.mcp._prompt_manager._prompts - assert "schema-overview" in prompts - result = prompts["schema-overview"].fn() - assert "CALL db.labels()" in result - - -def test_register_query_prompts_skips_missing_file(monkeypatch, tmp_path): - from mcp.server.fastmcp import FastMCP as _FastMCP - monkeypatch.setattr(mcp_mod, "_QUERIES_MD", tmp_path / "nonexistent.md") - fresh = _FastMCP("test-skip") - mcp_mod._register_query_prompts(fresh) - assert len(fresh._prompt_manager._prompts) == 0