Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
6d71657
Add runner abstraction, state machine, and ensemble gate extensions
ericlitman Mar 17, 2026
037e6fd
Add pipeline config: WORKFLOW.md, prompt templates, and hook scripts
ericlitman Mar 17, 2026
e21b7e0
Fix AI SDK provider integration bugs (ESM imports, model IDs, cleanup)
ericlitman Mar 17, 2026
40e4f0a
fix: add permissionMode to Claude Code runner + flat E2E config
ericlitman Mar 17, 2026
e6f95bd
fix: stage machine execution — early exit, stage-aware prompts, works…
ericlitman Mar 17, 2026
49d8577
fix: R1 adversarial review — tighten [STAGE_COMPLETE] sentinel detection
ericlitman Mar 17, 2026
34c3cc0
fix: R2 adversarial review — workspace preservation + gate claiming
ericlitman Mar 17, 2026
ff4b7d6
fix: include PR diff in ensemble reviewer prompt + calibrate PASS/FAI…
ericlitman Mar 17, 2026
90554af
fix: retry transient reviewer errors + "error" verdict for infrastruc…
ericlitman Mar 17, 2026
397557d
Merge pull request #1 from ericlitman/fix/stage-machine-execution
ericlitman Mar 17, 2026
b9242c7
fix: rate-limit text detection + escalation comment for ensemble gate
ericlitman Mar 17, 2026
f459819
fix: log warning on escalation comment failure instead of silent catch
ericlitman Mar 17, 2026
d9427e9
Merge pull request #3 from ericlitman/fix/stress-test-bugs-b1-b4
ericlitman Mar 17, 2026
b9a915a
feat: Linear state sync — stages update issue state automatically
ericlitman Mar 18, 2026
6f1ec6a
feat: workpad system — structured progress tracking on Linear tickets…
ericlitman Mar 18, 2026
48b2ad5
feat: Phase 12 — Review pipeline + feedback loop (#5)
ericlitman Mar 18, 2026
60cacd3
fix: Phase 20 — stress test bugs (heartbeat, merge race, hook resilie…
ericlitman Mar 20, 2026
030639c
Add before_remove hook for automatic branch/PR cleanup
ericlitman Mar 20, 2026
3294abe
feat(MOB-38): capture cache and reasoning token details in CodexUsage…
ericlitman Mar 20, 2026
754b37a
feat(MOB-39): add stage_completed structured log event with per-stage…
ericlitman Mar 20, 2026
3c82749
feat: multi-product Linear org + per-product WORKFLOW files (Decision…
ericlitman Mar 20, 2026
860046d
feat(SYMPH-1): add per-turn token delta logging and prompt size measu…
ericlitman Mar 20, 2026
55cda09
feat(SYMPH-3): add noCacheTokens to stage_completed event and gracefu…
ericlitman Mar 20, 2026
8d4e5b7
feat(SYMPH-4): add shutdown_complete structured log event with drain …
ericlitman Mar 20, 2026
6b7bdcc
feat(SYMPH-5): add poll_tick_completed structured log event with disp…
ericlitman Mar 20, 2026
5835549
feat(SYMPH-6): add poll_tick_completed structured log event with disp…
ericlitman Mar 20, 2026
ec6106d
feat(SYMPH-7): add stage-level token aggregation to stage_completed e…
ericlitman Mar 21, 2026
153e728
feat(SYMPH-8): add accumulator fields and summation logic (#17)
ericlitman Mar 21, 2026
548b79a
feat(SYMPH-9): emit accumulated token totals in stage_completed event…
ericlitman Mar 21, 2026
0192e7a
feat(SYMPH-11): add StageRecord and ExecutionHistory types and state …
ericlitman Mar 21, 2026
8b10547
feat(SYMPH-12): thread agent message through failure signal handling …
ericlitman Mar 21, 2026
1ba387f
chore: add env, workflows, WORKFLOW-TOYS, deploy-skills (#23)
ericlitman Mar 21, 2026
63d1e88
fix: preserve delayType on retry to prevent false escalation (#25)
ericlitman Mar 21, 2026
1aacd7a
chore: add CLAUDE.md per D38 standard (#24)
ericlitman Mar 21, 2026
5a06f98
feat: rewrite WORKFLOW hooks for git worktree isolation (#26)
ericlitman Mar 21, 2026
034fb6d
feat: add pipeline-halt dispatch guard (D35 Layer 4) (#28)
ericlitman Mar 21, 2026
c8e61ab
security: remove API keys and worktree artifacts from public repo (#27)
ericlitman Mar 21, 2026
705c35e
fix: persist pipeline logs to disk via --logs-root (#29)
ericlitman Mar 21, 2026
22e7bf9
fix: use bare clone refs in worktree hooks (#30)
ericlitman Mar 21, 2026
25b1064
feat(SYMPH-19): surface pipeline_stage, activity_summary, and rework_…
ericlitman Mar 21, 2026
cf5e41b
feat(SYMPH-20): add stage_duration_seconds and tokens_per_turn to Run…
ericlitman Mar 21, 2026
d336723
feat(SYMPH-13): post review findings comment on agent review failure …
ericlitman Mar 21, 2026
e2f3c1c
feat(SYMPH-21): add turn history ring buffer to LiveSession (#35)
ericlitman Mar 21, 2026
691cb70
feat(SYMPH-14): accumulate stage records in execution history (#36)
ericlitman Mar 21, 2026
ec84ef2
feat(SYMPH-22): add cumulative ticket stats to RuntimeSnapshot and da…
ericlitman Mar 21, 2026
b8ca3e4
feat(SYMPH-15): post execution report on terminal state (#38)
ericlitman Mar 21, 2026
a36fed9
feat(SYMPH-23): add expandable detail rows to running sessions table …
ericlitman Mar 21, 2026
6ed4cfd
feat(SYMPH-24): add health indicator to RuntimeSnapshot and dashboard…
ericlitman Mar 21, 2026
c77face
feat(SYMPH-16): add unit tests for review findings comment (#40)
ericlitman Mar 21, 2026
73532bb
feat(SYMPH-17): add unit tests for execution report (#42)
ericlitman Mar 21, 2026
e41ba16
feat(SYMPH-28): add analyze subcommand to symphony-ctl (#43)
ericlitman Mar 21, 2026
e964110
feat(SYMPH-31): add CRG to all WORKFLOWs and template (#44)
ericlitman Mar 21, 2026
edc3998
feat(SYMPH-30): add creation mutex to WorkspaceManager (#45)
ericlitman Mar 21, 2026
cbcefd5
feat(SYMPH-32): enable auto-delete and add prune-branches subcommand …
ericlitman Mar 21, 2026
ceeb4ed
fix: add global error handlers to prevent silent pipeline crashes (#32)
ericlitman Mar 21, 2026
91c093a
fix: update repo URLs from ericlitman to mobilyze-llc (#47)
ericlitman Mar 21, 2026
f4c4e61
feat(SYMPH-37): preserve accordion expand/collapse state across SSE u…
ericlitman Mar 21, 2026
d9af82a
feat(SYMPH-34): add investigation brief generation to investigate sta…
ericlitman Mar 21, 2026
9715709
feat(SYMPH-36): use cumulative stage token totals in dashboard snapsh…
ericlitman Mar 21, 2026
8fea678
config: bump max concurrent workers from 3 to 5 (#51)
ericlitman Mar 21, 2026
26fb328
feat(SYMPH-38): add agent context section to dashboard detail panel (…
ericlitman Mar 21, 2026
20223d0
feat(SYMPH-35): update implement stage prompt and before_run hook to …
ericlitman Mar 21, 2026
01e0308
feat(SYMPH-40): migrate resolve_team_from_project and resolve_state_i…
ericlitman Mar 21, 2026
6b17bf8
feat(SYMPH-41): migrate trivial issue creation to issueCreate GraphQL…
ericlitman Mar 21, 2026
6043fe3
feat(SYMPH-42): migrate parent and sub-issue creation to issueCreate/…
ericlitman Mar 21, 2026
29c7791
feat(SYMPH-46): add first_dispatched_at to RuntimeSnapshotRunningRow …
ericlitman Mar 21, 2026
72ec271
fix: enforce blockedBy check for all active states, not just Todo (#58)
ericlitman Mar 21, 2026
99b18b1
feat(SYMPH-45): add issueFirstDispatchedAt to domain model and orches…
ericlitman Mar 21, 2026
77662a9
feat(SYMPH-52): fix GraphQL type in resolve_all_states from String! t…
ericlitman Mar 21, 2026
6b37b4a
feat(SYMPH-57): consolidate spec-gen to produce 1-2 sub-issues for ST…
ericlitman Mar 21, 2026
b8fa131
feat(SYMPH-43): add post-creation verification layer (#69)
ericlitman Mar 21, 2026
b68f954
feat(SYMPH-58): switch investigate and implement stages to Opus model…
ericlitman Mar 21, 2026
b7d9b23
feat(SYMPH-47): render Pipeline column in dashboard (#65)
ericlitman Mar 21, 2026
67f2cb8
feat(SYMPH-56): implement fast-track label-based stage routing (#70)
ericlitman Mar 21, 2026
c336dc7
feat(SYMPH-50): add Resume-state blocked issue test (#71)
ericlitman Mar 21, 2026
2dfb9bd
fix: convert all timestamps from UTC to US Eastern (#72)
ericlitman Mar 21, 2026
42cb84e
feat(SYMPH-53): create sub-issues in Backlog and promote unblocked to…
ericlitman Mar 21, 2026
f4dbcbf
feat(SYMPH-61): fix freeze-and-queue.sh hanging on markdown with back…
ericlitman Mar 21, 2026
16b0e00
feat(SYMPH-60): add auto-close parent issue on terminal state (#74)
ericlitman Mar 21, 2026
9909b98
feat(SYMPH-54): update dry-run output to reflect Backlog to Todo flow…
ericlitman Mar 21, 2026
735f0a0
feat(SYMPH-63): refactor sub-issue creation to interleave relations (…
ericlitman Mar 21, 2026
50c6bec
feat(SYMPH-66): add rebase failure class and orchestrator routing (#79)
ericlitman Mar 21, 2026
f6e2452
feat(SYMPH-67): add rebase flow support to WORKFLOW prompts and hooks…
ericlitman Mar 21, 2026
11457d5
feat(SYMPH-64): update dry-run output to reflect interleaved flow (#80)
ericlitman Mar 21, 2026
ea3c091
feat(SYMPH-70): Replace turn history with recent activity feed (#82)
ericlitman Mar 21, 2026
50731f6
feat(SYMPH-69): Add issue title, fix UTC timestamps, add completed/fa…
ericlitman Mar 21, 2026
9ac31c1
ops: add sops secrets and update launchd plist config (#83)
ericlitman Mar 22, 2026
7958145
fix: bind dashboard server to 0.0.0.0 for network access (#84)
ericlitman Mar 22, 2026
52f6ee9
Fix: resolve symlinks in symphony-ctl SCRIPT_DIR (#85)
ericlitman Mar 22, 2026
7388a9b
fix: resolve symlink in symphony-ctl before deriving SYMPHONY_ROOT (#86)
ericlitman Mar 22, 2026
209aefa
feat(SYMPH-76): fix 3 bugs in freeze-and-queue.sh (#88)
ericlitman Mar 22, 2026
c63184b
feat(SYMPH-73): scaffold Slack bot module wiring Chat SDK → AI SDK → …
ericlitman Mar 22, 2026
54afb62
feat(SYMPH-78): defer project assignment in freeze-and-queue.sh (#89)
ericlitman Mar 22, 2026
fb1195b
fix: update symphony-ctl to use $LOG_DIR instead of hardcoded /tmp pa…
ericlitman Mar 22, 2026
f8cec15
feat(SYMPH-80): version module and display surfaces (#92)
ericlitman Mar 22, 2026
ee95781
feat(SYMPH-81): CI auto-bump calver workflow (#93)
ericlitman Mar 22, 2026
8d0c8c4
fix: derive default WORKFLOW path from SYMPHONY_PROJECT (#94)
ericlitman Mar 23, 2026
0f9849b
feat(SYMPH-74): add session continuity and channel-project mapping (#95)
ericlitman Mar 23, 2026
aedea86
SYMPH-71: Optimize merge stage with merge queue context (#96)
ericlitman Mar 23, 2026
da44a24
ops: add symphony-deploy script and retire deploy-skills.sh (#97)
ericlitman Mar 23, 2026
ad721e0
style: switch ops dashboard to dark mode (#98)
ericlitman Mar 23, 2026
290e5d7
feat(SYMPH-75): extract chunking, reactions, streaming modules and ad…
ericlitman Mar 23, 2026
8f77519
feat(SYMPH-85): Fix pipeline PR creation/merge targeting upstream ins…
ericlitman Mar 23, 2026
e5c7837
feat(SYMPH-87): Create webhook server entry point and update ops tool…
ericlitman Mar 23, 2026
2070d11
ops: checkout main before pull in symphony-deploy (#102)
ericlitman Mar 23, 2026
da6e0ba
fix: dashboard bugs — calver version, completed counts, title wrappin…
ericlitman Mar 23, 2026
2a1aba1
feat(SYMPH-91): Replace Chat SDK with @slack/bolt Socket Mode (#104)
ericlitman Mar 23, 2026
cd0e5f0
feat(SYMPH-92): Add progressive streaming and markdown formatting (#105)
ericlitman Mar 23, 2026
a98d3d1
feat(SYMPH-93): Update env config and add slack-bot test coverage (#106)
ericlitman Mar 23, 2026
9023100
feat(SYMPH-112): create test-alpha foundation module (#107)
ericlitman Mar 23, 2026
ae4738b
chore: update encrypted secrets (#108)
ericlitman Mar 23, 2026
9bcbe77
ops: add slack bridge restart to symphony-deploy + fix sops dotenv fo…
ericlitman Mar 23, 2026
c89adda
ops: set SOPS_AGE_KEY_FILE default in symphony-deploy (#110)
ericlitman Mar 23, 2026
6ac28d8
fix(ops): preserve quotes in .env JSON values during plist generation…
ericlitman Mar 23, 2026
56c6aa7
chore: update encrypted secrets (#112)
ericlitman Mar 23, 2026
54c31ac
feat(SYMPH-116): Create symphony-onboard script (#114)
ericlitman Mar 24, 2026
8ac5cfb
feat(SYMPH-117): Create port registry and fix WORKFLOW port collision…
ericlitman Mar 24, 2026
b741f9f
fix: sync spec-gen skills with claude-config canonical source (#118)
ericlitman Mar 24, 2026
a58ef49
feat(notifications): add Slack pipeline notifications (D56) (#116)
ericlitman Mar 24, 2026
adb44fc
feat(SYMPH-119): add feature-level scenario matching, empty-scenarios…
ericlitman Mar 24, 2026
0e5259c
feat(SYMPH-122): rewrite CLI interface and add missing onboard steps …
ericlitman Mar 24, 2026
b37d358
feat(SYMPH-123): normalize TOYS to lowercase and register in launcher…
ericlitman Mar 24, 2026
49ab186
feat(SYMPH-125): fix stale notification history for terminal completi…
ericlitman Mar 24, 2026
e5edd7b
feat(SYMPH-126): fix pipeline_stopped completedCount overcounting (#125)
ericlitman Mar 24, 2026
2a0203a
feat(SYMPH-127): thread env through to startCliHost (#126)
ericlitman Mar 24, 2026
178b627
feat(SYMPH-129): token history extraction + JSONL persistence (#127)
ericlitman Mar 24, 2026
6ec7177
feat(SYMPH-130): trend analysis + outlier detection with Linear-enric…
ericlitman Mar 24, 2026
2d7fd75
feat(SYMPH-131): HTML report + Slack digest + daily orchestration + l…
ericlitman Mar 24, 2026
77258b8
feat(SYMPH-134): Remove synthetic noise and widen context extraction …
ericlitman Mar 24, 2026
6b14684
feat(SYMPH-135): show last tool call in dashboard activity column (#131)
ericlitman Mar 24, 2026
a36101c
feat(SYMPH-139): Replace Slack message builder with narrative digest …
ericlitman Mar 25, 2026
92ef5af
feat(SYMPH-141): Scaffold Vite+ React project for token report UI (#134)
ericlitman Mar 25, 2026
36dd9f2
feat(SYMPH-147): capture per-type tokens (input/output) on stage arch…
ericlitman Mar 25, 2026
77a148b
feat(SYMPH-142): extract Token Report v2 design reference JSX components
Mar 25, 2026
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
15 changes: 15 additions & 0 deletions .env.enc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
LINEAR_API_KEY=ENC[AES256_GCM,data:vRXUs2ShBKmEyxD9yH09OwgtfJ7puOjSHPStCBnHDRN5ITAdkewg5p2JigOF4z9h,iv:2/NtDU6z33IU9T2N3IPlEjQ81OqsWn33cn8Bv9HNRpM=,tag:0De7RGq8d5yYBLAGpHvxgA==,type:str]
#ENC[AES256_GCM,data:FwVCNi0RYYsvqQ93h603+VqUTFSH4w==,iv:Ll9h37dJfAseA6oWWa2M84BSr6BNF4vdUvMsNLHgcyo=,tag:5mrtQtkKFk8myXaRgTGKGA==,type:comment]
SLACK_BOT_TOKEN=ENC[AES256_GCM,data:npHvNX/41Tzp9GzWKZJ/JMSotiZcMw8CpBx9Eoje4LKjWKDsyCV4cTAytdY8miyl7b9eRll5XcoUOhw=,iv:8zHB0uz1Xa8bmk6R0GlfRfA7RGoR2Bce8DrMnh89AFE=,tag:NAMlJrgJrc8cgRKIsU8cMw==,type:str]
SLACK_APP_TOKEN=ENC[AES256_GCM,data:+xX0KtSwC8Y/iTD1W5HCC8g0o/SWHNnRzJViEtrFZhrQUgt6EdkNo12FBtfIhZWpYyDJzkysdCX62DuPbLd6GvtYjVDIpulUYkF/71ehKM7TgdQYTlMSkLrlzSGxx734PuI=,iv:8d/JCXBm1TJLj83vVyZVf0Ejzlyb2hDRVnhLcKvp66U=,tag:Y7JaH+6joe8LW3dPaFtneA==,type:str]
#ENC[AES256_GCM,data:wL5ZCt4ipsixSaDkQK9nt62XdhvT+RIUlSulqHT/MGJx6FTtgfSjBLXSN88XXOWQygt6Pm18oA==,iv:SK+mmfjiRt5cq/ZCL8WScpjHLKU0a7yKIpi1nx6D5B8=,tag:0WAl/zsnxK726gbxei8N9Q==,type:comment]
#ENC[AES256_GCM,data:Lwg+4NujXm5WJDzgLyB8SCg/p2x8zpewQyWk7EewcbRzRcc4JNxsoqHKRyWgx7u4gOgi5sD3iphdw4ZHVGgtgoLoQFi+6MdazOpKsdzoapE8a5Cww+oWeJ8kJ1Y=,iv:2Nk/IBWOPDYXiTYXVBHyNvgAdxeU8DwSJcUx75CYnBQ=,tag:wgNcbkmUNxRYAb4Y4shsmA==,type:comment]
CHANNEL_PROJECT_MAP=ENC[AES256_GCM,data:nlIWKDmFmZAgwTJJnpOnMiondw4rfoILu1QOTLWtXSvxngL7fEQlvTdxM8FP9SnJiS8eorDbiUm+MWeknu3EVfU0E3kLJvY1AooRB2c3pYHKvLm4KuDK5XOFHhS5TSrZsm0EQWxJ54vSMvKRlA5qD4WSv/OT6dFlgkRm1QkJpCSoDbJMm4Tfmc4NFiueYQ9ftkHkgSZtK2RlGWliz2/rYKX2QlKCb3+h8rJchGbeYk11ob5MThztO66LKJ46KppRk3gG/5asqKof/tRGKepcrMOPQguNRI7OoTYDpstNkOWlbmVuwsBca5aYS6xuTdW2J4LiQNKnFMT6hoxSsZxrHIZ0pQ+kUQgAhfpdKcGEAKG1wOdriLWuz0N+1+Q=,iv:VcnNVyQTocHSPhw/ZG/gzei7xsQWEt5yP6tSEosxHTA=,tag:hvn88f983LBwXwOKG6H0lA==,type:str]
#ENC[AES256_GCM,data:OvZDECuGRW87fnNR/BQUlC25xe0ol8pBcm3hSIcN54NaK2UVg+v2zyxwseTUDcwxRe1U,iv:nylZjuu3I0LitRMpDpzi7tTwFkvSSWKUoN8P1xqWHRk=,tag:sopVFpFrIaz9S8HOMGFliQ==,type:comment]
CLAUDE_MODEL=ENC[AES256_GCM,data:hbkZYw==,iv:9yzUxI/bXKLlNTuVTLICdobV8+QZDuntByhetG3QmBw=,tag:ynabmmqv//0tSajcjhcvFw==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxU3lVRmx5QnBhN29PRGRH\nTXhRd2RrYS91Y2JuelJqN2pXd1NTck9LYWpzCnBlWWlhNGhiTXBGREJkY2FqNUF6\nbDIvZ24xTXhZUEtPbXNNTGQ3RkFHZW8KLS0tIER0azVIalhOOUlqUjhpNzlqOHVE\nOUdiemtsWC90NzlRZUlXblpYSHJ1VEEKYFK5o+QjcTZh8awX7zM1gtiFGOxhENJa\noyrkrO0sbb+wftQ5VnS540JgGfjZ41woig2NR6BpRc0xfDyVm54aKw==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age17ud00xq42ckzfpmhxtwz4zy0vc50lsa3jfvulrt5zhe2pd94p9kqy775uc
sops_lastmodified=2026-03-23T20:36:48Z
sops_mac=ENC[AES256_GCM,data:vloTsrC6A7AYF/W+7WPRrejfK2oeq0G9zPmAGeAcvndv9eW59SVccL8gwiX7L2cp/1T/X/KLIsfEtO4j7aXOtIn2OFRLCgoXLgSbzVR1zoUQIY4sub8fcgQJBWYfwc4jdLbuaCcr4oKhdioneURNVE6a8L0zH79l7bGEHPPatFk=,iv:f75NhCH/eo9Eag+9rdTthd9KdJr5B0v+HHhf3dXpm74=,tag:b1KKr/7CJjz/MQLgXkI4KA==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.12.2
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Slack bot credentials
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-level-token

# Channel ID → project directory mapping (JSON object)
# Example: {"C0123456789":"/home/user/projects/my-app","C9876543210":"/home/user/projects/other-app"}
CHANNEL_PROJECT_MAP={}

# Claude Code model identifier (optional, defaults to "sonnet")
# Valid values: sonnet, opus, haiku
CLAUDE_MODEL=sonnet
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
pull_request:
merge_group:

jobs:
test:
Expand Down
202 changes: 202 additions & 0 deletions .github/workflows/post-merge-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
name: Post-Merge Gate

on:
push:
branches:
- main

permissions:
contents: write

jobs:
gate:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.30.2

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
id: lint
run: pnpm lint

- name: Typecheck
id: typecheck
if: always() && steps.lint.outcome != 'cancelled'
run: pnpm typecheck

- name: Test
id: test
if: always() && steps.typecheck.outcome != 'cancelled'
run: pnpm test

- name: Build
id: build
if: always() && steps.test.outcome != 'cancelled'
run: pnpm build

- name: Bump calver version
if: success()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Retry loop handles race when concurrent merges push at the same time
for ATTEMPT in 1 2 3; do
git pull --rebase origin main

# Read current version after pulling latest
CURRENT_VERSION=$(node -p "require('./package.json').version")
TODAY=$(date -u +"%Y.%m.%d")

# Extract date prefix and sequence from current version
CURRENT_PREFIX=$(echo "$CURRENT_VERSION" | cut -d. -f1-3)
CURRENT_SEQ=$(echo "$CURRENT_VERSION" | cut -d. -f4)

# Determine next sequence
if [ "$CURRENT_PREFIX" = "$TODAY" ]; then
NEXT_SEQ=$((CURRENT_SEQ + 1))
else
NEXT_SEQ=1
fi

NEXT_VERSION="${TODAY}.${NEXT_SEQ}"

# Update package.json version field directly (avoids npm lockfile side-effects)
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '${NEXT_VERSION}';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
"

git add package.json
git commit -m "chore: bump calver to ${NEXT_VERSION} [skip ci]"

if git push; then
echo "::notice::Bumped version to ${NEXT_VERSION}"
exit 0
fi

echo "::warning::Push failed (attempt ${ATTEMPT}/3), retrying..."
git reset --soft HEAD~1
done

echo "::error::Failed to push calver bump after 3 attempts"
exit 1

- name: Create Linear issue on failure
if: failure()
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
run: |
COMMIT_SHA="${{ github.sha }}"
SHORT_SHA="${COMMIT_SHA:0:7}"
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

# Extract PR number from merge commit message (GitHub format: "Merge pull request #N ..." or squash "... (#N)")
PR_NUMBER=$(git log -1 --pretty=%s | grep -oP '#\K[0-9]+' | head -1)
PR_INFO=""
if [ -n "$PR_NUMBER" ]; then
PR_INFO="**PR:** #${PR_NUMBER}"
fi

# Determine which steps failed
FAILED_STEPS=""
if [ "${{ steps.lint.outcome }}" = "failure" ]; then
FAILED_STEPS="${FAILED_STEPS}\n- Lint"
fi
if [ "${{ steps.typecheck.outcome }}" = "failure" ]; then
FAILED_STEPS="${FAILED_STEPS}\n- Typecheck"
fi
if [ "${{ steps.test.outcome }}" = "failure" ]; then
FAILED_STEPS="${FAILED_STEPS}\n- Test"
fi
if [ "${{ steps.build.outcome }}" = "failure" ]; then
FAILED_STEPS="${FAILED_STEPS}\n- Build"
fi

TITLE="pipeline-halt: post-merge gate failure on ${SHORT_SHA}"

BODY="## Post-Merge Gate Failure\n\n**Commit:** ${COMMIT_SHA}\n${PR_INFO}\n**Run:** ${RUN_URL}\n\n**Failed steps:**${FAILED_STEPS}"

# Look up SYMPH team ID and pipeline-halt label via Linear API
TEAM_QUERY='{ "query": "{ teams(filter: { key: { eq: \"SYMPH\" } }) { nodes { id } } }" }'
TEAM_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: ${LINEAR_API_KEY}" \
-d "$TEAM_QUERY")
TEAM_ID=$(echo "$TEAM_RESPONSE" | jq -r '.data.teams.nodes[0].id')

if [ -z "$TEAM_ID" ] || [ "$TEAM_ID" = "null" ]; then
echo "::error::Failed to look up SYMPH team ID from Linear API"
exit 1
fi

# Find or create the pipeline-halt label
LABEL_QUERY='{ "query": "{ issueLabels(filter: { name: { eq: \"pipeline-halt\" } }) { nodes { id } } }" }'
LABEL_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: ${LINEAR_API_KEY}" \
-d "$LABEL_QUERY")
LABEL_ID=$(echo "$LABEL_RESPONSE" | jq -r '.data.issueLabels.nodes[0].id')

LABEL_IDS_PARAM=""
if [ -n "$LABEL_ID" ] && [ "$LABEL_ID" != "null" ]; then
LABEL_IDS_PARAM=", labelIds: [\"${LABEL_ID}\"]"
else
# Create the label on the SYMPH team
CREATE_LABEL_QUERY=$(cat <<GRAPHQL
{ "query": "mutation { issueLabelCreate(input: { name: \"pipeline-halt\", color: \"#eb5757\", teamId: \"${TEAM_ID}\" }) { issueLabel { id } success } }" }
GRAPHQL
)
CREATE_LABEL_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: ${LINEAR_API_KEY}" \
-d "$CREATE_LABEL_QUERY")
NEW_LABEL_ID=$(echo "$CREATE_LABEL_RESPONSE" | jq -r '.data.issueLabelCreate.issueLabel.id')
if [ -n "$NEW_LABEL_ID" ] && [ "$NEW_LABEL_ID" != "null" ]; then
LABEL_IDS_PARAM=", labelIds: [\"${NEW_LABEL_ID}\"]"
fi
fi

# Escape the body for JSON
ESCAPED_BODY=$(echo -e "$BODY" | jq -Rs .)

# Create the issue
CREATE_ISSUE_QUERY=$(cat <<GRAPHQL
{ "query": "mutation { issueCreate(input: { teamId: \"${TEAM_ID}\", title: \"${TITLE}\", description: ${ESCAPED_BODY}${LABEL_IDS_PARAM} }) { issue { id identifier url } success } }" }
GRAPHQL
)

ISSUE_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: ${LINEAR_API_KEY}" \
-d "$CREATE_ISSUE_QUERY")

ISSUE_URL=$(echo "$ISSUE_RESPONSE" | jq -r '.data.issueCreate.issue.url')
ISSUE_ID=$(echo "$ISSUE_RESPONSE" | jq -r '.data.issueCreate.issue.identifier')

if [ -z "$ISSUE_URL" ] || [ "$ISSUE_URL" = "null" ]; then
echo "::error::Failed to create Linear issue"
echo "Response: $ISSUE_RESPONSE"
exit 1
fi

echo "::notice::Created Linear issue ${ISSUE_ID}: ${ISSUE_URL}"
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ on:
push:
tags:
- "v*"
merge_group:
workflow_dispatch:

permissions:
contents: write

jobs:
release:
if: false
runs-on: ubuntu-latest

steps:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ coverage/
*.tgz

.worktrees/
.env
pipeline-config/workflows/workspaces/
pipeline-config/workspaces/
3 changes: 3 additions & 0 deletions .sops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
creation_rules:
- path_regex: \.env(\.enc)?$
age: age17ud00xq42ckzfpmhxtwz4zy0vc50lsa3jfvulrt5zhe2pd94p9kqy775uc
132 changes: 132 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Symphony-ts

Autonomous development pipeline orchestrator. Fork of OasAIStudio/symphony-ts, maintained at github.com/mobilyze-llc/symphony-ts. Symphony reads work items from Linear, creates isolated per-issue workspaces, runs coding agents (Claude Code, Codex, Gemini) inside those workspaces, and handles retries, state transitions, and operator observability. It is the scheduling layer in a 4-stage pipeline: investigate, implement, review, merge.

## Project Overview

Symphony-ts is a CLI tool (no dev server). It polls a Linear project board for eligible issues, clones target repos into deterministic workspaces, renders LiquidJS prompt templates with issue context, dispatches agent runs, and manages the full lifecycle including retry/rework with failure classification. WORKFLOW.md files define per-product pipeline configuration. Hook scripts handle workspace setup and git sync.

## Architecture

```
src/
├── agent/ # Agent runner abstraction, prompt builder (LiquidJS rendering)
├── cli/ # CLI entrypoint (main.ts) — parses args, bootstraps orchestrator
├── codex/ # Codex app-server integration
├── config/ # WORKFLOW.md parsing, config resolution, defaults, file watcher
├── domain/ # Core domain model (issues, states, transitions)
├── errors/ # Error types and failure classification
├── logging/ # Structured logging
├── observability/ # Dashboard server (SSE), runtime metrics
├── orchestrator/ # Core loop, gate handler, runtime host — the main scheduling engine
├── runners/ # Agent runtime implementations (claude-code, gemini, factory)
├── shared/ # Shared utilities
├── tracker/ # Linear API client, GraphQL queries, state normalization
└── workspace/ # Workspace lifecycle (create, hooks, path safety)

pipeline-config/
├── hooks/ # Shell scripts: after-create.sh (clone + install), before-run.sh (git sync)
├── prompts/ # LiquidJS templates: investigate, implement, review, merge, global
├── templates/ # WORKFLOW and CLAUDE.md templates
├── workflows/ # Per-product WORKFLOW configs (symphony, jony-agent, hs-*, stickerlabs, household, TOYS)
└── workspaces/ # Runtime workspace directories (UUID-named, gitignored)

tests/ # Vitest test suite, mirrors src/ structure + fixtures/
dist/ # Compiled output (generated, not committed)
```

**Data flow**: WORKFLOW.md (YAML frontmatter + LiquidJS body) -> config resolver -> orchestrator polls Linear -> creates workspace (after-create hook clones repo) -> renders prompt template with issue context -> dispatches agent run -> agent works in isolated workspace -> orchestrator manages state transitions back to Linear.

**Key architectural decisions**:
- In-memory state only (no BullMQ/Redis) -- designed for 2-3 concurrent workers
- `strictVariables: true` on LiquidJS -- all template variables must be in render context
- Orchestrator is deliberately "dumb" -- review intelligence, failure classification, and feedback injection live in the agent layer (prompts + skills), not here
- `permissionMode: "bypassPermissions"` required for headless agent runs

## Build & Run

```bash
# Install dependencies
pnpm install

# Build (compiles TypeScript to dist/)
pnpm build # or: npm run build

# Run the pipeline for a specific product
./run-pipeline.sh <product>
# Products: symphony, jony-agent, hs-data, hs-ui, hs-mobile, stickerlabs, household

# Run directly (after building)
node dist/src/cli/main.js <workflow-path> --acknowledge-high-trust-preview

# Type check only
pnpm typecheck # or: npx tsc --noEmit

# Lint
pnpm lint # Biome check

# Auto-format
pnpm format # Biome format
```

No dev server -- this is a CLI tool. The D40 port table does not apply.

## Conventions

- **Runtime**: Node.js >= 22, pnpm >= 10, TypeScript strict mode, ES2023 target
- **Module system**: ESM (`"type": "module"`), NodeNext module resolution
- **Imports**: `import type { ... }` for type-only imports (`verbatimModuleSyntax: true`), `.js` extensions required for NodeNext
- **Formatting**: Biome -- spaces (not tabs), double quotes, semicolons always, trailing commas
- **Naming**: kebab-case for file names, PascalCase for types/interfaces, camelCase for functions/variables
- **Validation**: Zod for config/input validation at I/O boundaries
- **Templates**: LiquidJS for prompt rendering -- always pass all required variables (strictVariables is on)
- **Strict TS options**: `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `useUnknownInCatchVariables`, `noImplicitOverride`

## Testing

- **Framework**: Vitest
- **Run tests**: `pnpm test` (runs all 347 tests once via `node scripts/test.mjs`)
- **Watch mode**: `pnpm test:watch`
- **Location**: `tests/` directory, mirrors `src/` structure (e.g., `tests/orchestrator/core.test.ts` covers `src/orchestrator/core.ts`)
- **Fixtures**: `tests/fixtures/` for shared test data
- **Coverage**: All new code must have tests. Critical paths (orchestrator, config resolution, tracker) have thorough coverage.
- **Naming**: Test files named after the module they cover; individual test cases named after observable behavior.

## Pipeline Notes

### Critical: dist/ staleness

**The pipeline runs from compiled `dist/`, NOT source.** If you modify source files but forget to rebuild, your changes will not take effect. `run-pipeline.sh` includes a staleness check that compares `src/` timestamps against `dist/src/cli/main.js`. Use `--auto-build` to rebuild automatically, or `--skip-build-check` to bypass.

### Auto-generated files (never edit directly)
- `dist/` -- compiled output, regenerated by `pnpm build`
- `pipeline-config/workspaces/` -- runtime workspace directories (UUID-named)
- `pnpm-lock.yaml` -- dependency lock file (regenerated by `pnpm install`)

### Required environment variables
- `LINEAR_API_KEY` -- Linear API token for tracker integration (loaded from `.env` by `run-pipeline.sh`)
- `REPO_URL` -- target repo URL for workspace cloning (set per-product in `run-pipeline.sh`, or override via env)

### Fragile areas
- **`active_states` in WORKFLOW configs** must include ALL states set during execution (In Progress, In Review, Blocked, Resume). This bug has been hit 3 times -- missing a state causes silent failures.
- **LiquidJS `strictVariables: true`** -- any variable referenced in a prompt template that is not passed in the render context will throw. Always verify template variables match the context passed by `prompt-builder.ts`.
- **`scheduleRetry`** is used for both failures AND continuations -- the max retry limit must only count actual failures, not continuation retries.
- **Hook scripts** run with `cwd: workspacePath`, NOT the WORKFLOW.md location. Relative paths in hooks resolve against the workspace.
- **`issue.state`** is a string in LiquidJS context (via `toTemplateIssue`), not an object. Template conditionals must compare against string values.
- **`stall_timeout_ms`** default (5 min) is too short for Claude Code agents. Set to 900000 (15 min) in WORKFLOW configs.
- **Linear project slug** is the `slugId` UUID, not the team key.

### Verify commands (must pass before any PR)
```bash
pnpm test # All 347 tests pass
pnpm build # Compiles without errors
pnpm typecheck # No type errors
pnpm lint # Biome passes
```

### Scope boundaries
- Do NOT add BullMQ, Redis, or external queue infrastructure -- in-memory state is a deliberate design choice at current scale
- Do NOT move review intelligence or failure classification into the orchestrator -- these belong in the agent layer (prompts + skills)
- Do NOT modify hook scripts without testing against actual workspace creation flow
- Do NOT commit secrets to `.env` in public contexts (current repo is private; audit before making public)
- Every non-Claude-Code component should be designed for removal when Anthropic ships equivalent features
Loading