yadflow 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +21 -0
  3. package/README.md +559 -0
  4. package/bin/sdlc.mjs +135 -0
  5. package/cli/commit.mjs +81 -0
  6. package/cli/epic-state.mjs +220 -0
  7. package/cli/gate.mjs +456 -0
  8. package/cli/lib.mjs +142 -0
  9. package/cli/manifest.mjs +119 -0
  10. package/cli/openpr.mjs +65 -0
  11. package/cli/plan.mjs +127 -0
  12. package/cli/platform.mjs +151 -0
  13. package/cli/reconcile.mjs +83 -0
  14. package/cli/repo.mjs +61 -0
  15. package/cli/setup.mjs +208 -0
  16. package/package.json +51 -0
  17. package/skills/sdlc/config.yaml +156 -0
  18. package/skills/sdlc/install.sh +51 -0
  19. package/skills/sdlc/module-help.csv +17 -0
  20. package/skills/sdlc-author-analysis/SKILL.md +136 -0
  21. package/skills/sdlc-author-architecture/SKILL.md +180 -0
  22. package/skills/sdlc-author-architecture/references/contract-format.md +72 -0
  23. package/skills/sdlc-author-epic/SKILL.md +154 -0
  24. package/skills/sdlc-author-epic/references/state-schema.md +187 -0
  25. package/skills/sdlc-author-stories/SKILL.md +109 -0
  26. package/skills/sdlc-author-stories/references/story-schema.md +46 -0
  27. package/skills/sdlc-author-ui/SKILL.md +113 -0
  28. package/skills/sdlc-backfill/SKILL.md +91 -0
  29. package/skills/sdlc-backfill/references/backfill.md +66 -0
  30. package/skills/sdlc-backfill/templates/checks/backfill-check.sh +42 -0
  31. package/skills/sdlc-checks/SKILL.md +138 -0
  32. package/skills/sdlc-checks/references/check-gates.md +168 -0
  33. package/skills/sdlc-checks/templates/checks/build-test-lint.sh +14 -0
  34. package/skills/sdlc-checks/templates/checks/contract-check.sh +62 -0
  35. package/skills/sdlc-checks/templates/checks/spec-link.sh +38 -0
  36. package/skills/sdlc-checks/templates/checks/verified-commits.sh +120 -0
  37. package/skills/sdlc-checks/templates/github/sdlc-checks.yml +45 -0
  38. package/skills/sdlc-checks/templates/github/sdlc-verified-commits.yml +22 -0
  39. package/skills/sdlc-checks/templates/gitlab/.gitlab-ci.yml +40 -0
  40. package/skills/sdlc-checks/templates/gitlab/gitlab-ci.include-root.yml +7 -0
  41. package/skills/sdlc-checks/templates/gitlab/sdlc-checks.gitlab-ci.yml +47 -0
  42. package/skills/sdlc-checks/templates/gitlab/sdlc-verified-commits.gitlab-ci.yml +21 -0
  43. package/skills/sdlc-connect-repos/SKILL.md +159 -0
  44. package/skills/sdlc-connect-repos/references/code-context.md +92 -0
  45. package/skills/sdlc-connect-repos/references/hub-config.md +77 -0
  46. package/skills/sdlc-connect-repos/references/repos-registry.md +62 -0
  47. package/skills/sdlc-hub-bridge/SKILL.md +119 -0
  48. package/skills/sdlc-hub-bridge/references/bridge.md +136 -0
  49. package/skills/sdlc-hub-bridge/references/login-roster.md +42 -0
  50. package/skills/sdlc-hub-bridge/templates/checks/hub-route.sh +50 -0
  51. package/skills/sdlc-hub-bridge/templates/github/sdlc-gate-sync.yml +63 -0
  52. package/skills/sdlc-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +7 -0
  53. package/skills/sdlc-hub-bridge/templates/gitlab/sdlc-gate-sync.gitlab-ci.yml +64 -0
  54. package/skills/sdlc-implement/SKILL.md +143 -0
  55. package/skills/sdlc-implement/references/implement-conventions.md +103 -0
  56. package/skills/sdlc-implement/templates/.gitmessage +17 -0
  57. package/skills/sdlc-pr-template/SKILL.md +86 -0
  58. package/skills/sdlc-pr-template/references/risk-routing.md +54 -0
  59. package/skills/sdlc-pr-template/templates/checks/risk-route.sh +44 -0
  60. package/skills/sdlc-pr-template/templates/github/pull_request_template.md +30 -0
  61. package/skills/sdlc-pr-template/templates/gitlab/merge_request_templates/Default.md +32 -0
  62. package/skills/sdlc-pr-template/templates/hub/github/pull_request_template.md +36 -0
  63. package/skills/sdlc-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +37 -0
  64. package/skills/sdlc-review-comments/SKILL.md +63 -0
  65. package/skills/sdlc-review-comments/references/comment-conventions.md +55 -0
  66. package/skills/sdlc-review-comments/templates/github/REVIEW_COMMENTS.md +49 -0
  67. package/skills/sdlc-review-comments/templates/gitlab/REVIEW_COMMENTS.md +49 -0
  68. package/skills/sdlc-review-gate/SKILL.md +196 -0
  69. package/skills/sdlc-review-gate/references/gating.md +79 -0
  70. package/skills/sdlc-run/SKILL.md +109 -0
  71. package/skills/sdlc-run/references/run-loop.md +121 -0
  72. package/skills/sdlc-ship/SKILL.md +86 -0
  73. package/skills/sdlc-ship/references/ship-and-record.md +67 -0
  74. package/skills/sdlc-ship/templates/.coderabbit.yaml +19 -0
  75. package/skills/sdlc-spec/SKILL.md +119 -0
  76. package/skills/sdlc-spec/references/spec-handoff.md +101 -0
  77. package/skills/sdlc-status/SKILL.md +92 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: sdlc-connect-repos
3
+ description: 'Connects code repos to the product hub so the front/"brain" phases are code-aware. Registers N code repos (GitHub or GitLab, local-user auth, no stored tokens) into the project-wide .sdlc/repos.json, then caches an AI-readable picture of each — a compressed Repomix pack and a lightweight code-map (existing endpoints/events/data-models/modules), secret-scanned. Run at one-time setup or any time a new repo is added. Reusable, idempotent, refreshable; staleness is tracked by HEAD sha. Use when the user says "connect a repo", "connect the code repos", "refresh the code context", or "list connected repos".'
4
+ ---
5
+
6
+ # SDLC — Connect Code Repos (make the brain code-aware)
7
+
8
+ **Goal:** Give the front/"brain" phases (`sdlc-author-epic` → `-architecture` → `-ui` → `-stories`)
9
+ full context about what **already exists** in the code, so the AI does not author a contract, UI, or
10
+ stories that contradict or duplicate what is built. This skill **connects** code repos to the product
11
+ hub and caches an AI-readable picture of each. It is the product → code half of the 2-way link (the
12
+ code → product half is the existing `link.md` back-pointer each spec carries).
13
+
14
+ This is **setup/maintenance**, not a gated front state — it never touches `.sdlc/state.json` or any
15
+ epic's approvals. It only writes the project-wide registry and the per-repo context cache.
16
+
17
+ ## Conventions
18
+
19
+ - `{project-root}` resolves from the project working directory (the **product hub**).
20
+ - The **product repo is the front-phase toolchain hub** (`config.yaml` `code_context`): Repomix (and
21
+ Impeccable, later) are installed/run **here** and target the connected code repos **by path**. The
22
+ code repos themselves need no install for this. (The build-half CI gates are the exception — they
23
+ live inside each code repo; see `sdlc-checks`.)
24
+ - **Repomix is a true CLI subprocess** (Phase 0 / RESEARCH-NOTES §3): `npx repomix@latest [flags]` —
25
+ NOT a slash-command. It secret-scans by default (Secretlint).
26
+ - Registry: `{project-root}/.sdlc/repos.json` (project-wide, shared across all epics — NOT per-epic).
27
+ - Per-repo cache: `{project-root}/.sdlc/code-context/<repo>/` holds `pack.md` + `code-map.md`.
28
+
29
+ ## Inputs
30
+
31
+ - `action` — `connect` | `refresh` | `list` | `disconnect` | `detect-hub` | `roster` (default `connect`).
32
+ - `repo` — the repo's short name (the key used in stories' `repos:` tag, e.g. `backend`).
33
+ - `login`, `name`, `role` — for `roster` (set the hub's reviewer-roster mapping login → name → role).
34
+ - `path` — local path to the code repo (relative to `{project-root}` or absolute). For local repos.
35
+ - `git_url` — optional remote (SSH or HTTPS; GitHub or GitLab). Used when the repo is not yet on disk.
36
+ - `domain_owner` — the engineer who owns this repo's domain (drives per-repo review routing later).
37
+
38
+ ## On Activation
39
+
40
+ ### Step 1 — Resolve the repo and its auth (GitHub + GitLab, local user)
41
+ Determine where the code is:
42
+ - If `path` is given and is a git repo (`.git` present) → use it in place.
43
+ - Else if `git_url` is given → **clone it as the local user** into a working location
44
+ (`{code_repos_root}/<repo>/` by `config.yaml` `build.code_repos_root`, or a path the user names):
45
+ ```
46
+ git clone <git_url> <dest>
47
+ ```
48
+ Authentication is **the local user's own** — SSH keys or the git credential helper already on this
49
+ device. This works identically for **GitHub and GitLab** (and self-hosted instances); the skill
50
+ **stores no tokens**. If `gh`/`glab` are installed and authenticated they may be used, but plain
51
+ `git` over the user's credentials is the baseline. If the clone/fetch fails on auth, STOP and tell
52
+ the user to authenticate (`gh auth login` / `glab auth login` / add an SSH key) — never embed a token.
53
+ - Detect the platform from the URL host: `github.com` → `github`, `gitlab.com`/self-hosted GitLab →
54
+ `gitlab`; record it as `platform`. A local-only repo with no remote records `platform: null`.
55
+
56
+ ### Step 2 — Pack the repo (Repomix, the full cached context layer)
57
+ From the code repo, run (flags from `config.yaml` `code_context.pack_flags`):
58
+ ```
59
+ npx repomix@latest --compress --include-logs --style markdown -o {project-root}/.sdlc/code-context/<repo>/pack.md
60
+ ```
61
+ `--compress` (Tree-sitter structural compression) keeps it small and signal-dense; `--include-logs`
62
+ adds recent git history; Secretlint secret-scans by default. **If a secret is reported, STOP and have
63
+ it redacted before any AI reads the pack.** Pack the whole repo, or the source boundary from the
64
+ project's constitution if one is defined. (If `npx repomix` is unavailable, degrade: hand-assemble the
65
+ same context — the repo's source tree + recent git log — and record `source: repomix-unavailable`.)
66
+
67
+ ### Step 3 — Build the code-map (the lightweight index layer)
68
+ Feed the pack to the AI with the **"describe what exists, do not invent"** instruction
69
+ (`references/code-context.md`) and write `{project-root}/.sdlc/code-context/<repo>/code-map.md`: a small
70
+ index of **stack/conventions, entry points, public endpoints/APIs, events, data models/entities, and
71
+ module layout**. Mark anything unclear `<!-- unverified: ... -->`; never fill gaps with invented
72
+ behaviour. This is the cheap artifact every front phase loads by default (the full pack is read only
73
+ when a phase needs depth).
74
+
75
+ ### Step 4 — Record the repo in the registry
76
+ Upsert the repo into `{project-root}/.sdlc/repos.json` (create the file if absent). Record the current
77
+ HEAD sha as `syncedHead` (this drives staleness):
78
+ ```json
79
+ {
80
+ "repos": [
81
+ {
82
+ "name": "<repo>",
83
+ "path": "<path rel. to project-root>",
84
+ "git_url": "<url or null>",
85
+ "platform": "github|gitlab|null",
86
+ "domain_owner": "<owner or null>",
87
+ "default_branch": "<branch>",
88
+ "connectedAt": "<YYYY-MM-DD>",
89
+ "lastSyncedAt": "<YYYY-MM-DD>",
90
+ "syncedHead": "<git HEAD sha at pack time>",
91
+ "contextPack": ".sdlc/code-context/<repo>/pack.md",
92
+ "codeMap": ".sdlc/code-context/<repo>/code-map.md",
93
+ "source": "repomix"
94
+ }
95
+ ]
96
+ }
97
+ ```
98
+ `connect` is **idempotent** — re-running it for an existing repo refreshes its entry in place. Adding a
99
+ new repo later is the same `connect` action.
100
+
101
+ ### Step 5 — Report
102
+ Report the connected repo, its `platform`, the pack + code-map paths, the secret-scan result, and that
103
+ the front phases will now load this repo's code-map. Nothing auto-advances; this is setup.
104
+
105
+ ## Other actions
106
+
107
+ - **`refresh`** — re-run Steps 2–4 for an already-connected repo (after its code moves). Updates
108
+ `syncedHead` + `lastSyncedAt`. Same machinery as `connect`.
109
+ - **`list`** — print every registry entry with a **fresh/stale** flag: compare each repo's current HEAD
110
+ (`git -C <path> rev-parse HEAD`) to its `syncedHead`; differ ⇒ **stale** (suggest `refresh`).
111
+ - **`disconnect`** — remove the repo from the registry and delete its cache dir. Leaves the **code repo
112
+ itself untouched**.
113
+
114
+ ## Hub detection + reviewer roster (the front-half review bridge)
115
+
116
+ The hub is itself a git repo on a platform. These actions record that so the front-half review/comment/
117
+ approval cycle can run through a real PR/MR on the hub (`sdlc-review-gate` + `sdlc-hub-bridge`). They
118
+ write only `{project-root}/.sdlc/hub.json` (`config.yaml` `hub.config`) — never an epic's state/approvals.
119
+
120
+ - **`detect-hub`** — detect the hub's own platform and upsert `.sdlc/hub.json`. Run
121
+ `git remote get-url origin` **on the hub** and read the host with the SAME logic Step 1 uses for code
122
+ repos: `github.com` → `github`, GitLab host → `gitlab`, no remote → `platform: null`. Record
123
+ `git_url`, `default_branch`, `detectedAt`, and `bridge_enabled: true` (preserve an existing roster).
124
+ Auth is the local user's own `gh`/`glab`/git; **store no tokens**. Idempotent — safe to re-run.
125
+ - **`roster`** — set one roster entry mapping a platform `login` → SDLC `name` + `role`
126
+ (`owner` | `reviewer`). Upsert by `login`. `domain-owner` is **not** set here — it is derived when a
127
+ roster `name` equals a repo's `domain_owner` in `repos.json` (see `references/hub-config.md`). An
128
+ unmapped login degrades to a plain `reviewer`, never auto-promoted to owner/domain-owner.
129
+
130
+ If the hub has no remote (`platform: null`) or the bridge is disabled, the front-half gate runs
131
+ file-only with no error — the bridge is purely additive.
132
+
133
+ ## Live on-demand (the third context layer)
134
+ The cached pack + map are the default. When a front phase needs an **area** not in the map, it may
135
+ re-run Repomix **live**, scoped to that area:
136
+ ```bash
137
+ npx repomix@latest --compress --include "<area globs>" --style markdown -o -
138
+ ```
139
+ Same CLI, invoked ad hoc — no registry write. A **stale repo** (HEAD ≠ `syncedHead`) is different: the
140
+ phase **flags it and stops**, pointing the human at `sdlc repo refresh <repo>` (or `sdlc check --fix`) —
141
+ it does not silently re-pack. Refreshing the cache is a human decision. Documented in
142
+ `references/code-context.md`.
143
+
144
+ ## Hard rules
145
+
146
+ - **Local-user auth only; store no tokens.** Clone/fetch as the user running the command; never embed
147
+ credentials in the registry. Works for GitHub and GitLab alike.
148
+ - **Secret-scan before any AI sees the code.** Secretlint runs by default; a hit STOPS the pack.
149
+ - **Describe what exists; never invent.** The code-map records built behaviour, not a design.
150
+ - **Setup, not a gate.** Never touch `.sdlc/state.json`, approvals, or the contract lock from here.
151
+ - **Idempotent + refreshable.** `connect`/`refresh` are safe to re-run; staleness is HEAD-sha based.
152
+
153
+ ## Reference
154
+ - Registry schema + freshness rule: `references/repos-registry.md`.
155
+ - Hub config + reviewer roster (the review bridge): `references/hub-config.md`.
156
+ - Repomix command, secret-scan, degrade path, the code-map prompt, and live on-demand:
157
+ `references/code-context.md`.
158
+ - The repomix discipline this reuses (one-feature-at-a-time variant): `../sdlc-backfill/references/backfill.md`.
159
+ - Repos convention + per-repo review routing: `../sdlc-author-stories/references/story-schema.md`.
@@ -0,0 +1,92 @@
1
+ # Code context — Repomix pack, the code-map prompt, and live on-demand
2
+
3
+ How `sdlc-connect-repos` turns a connected code repo into an AI-readable picture the front/"brain"
4
+ phases can read. Three layers, same Repomix machinery as `sdlc-backfill` (this is the repo-wide
5
+ variant; backfill is the one-feature-at-a-time variant).
6
+
7
+ ## Layer 1 — the cached pack (full context)
8
+
9
+ Run from the product hub, targeting the connected repo (flags from `config.yaml`
10
+ `code_context.pack_flags`):
11
+
12
+ ```
13
+ npx repomix@latest --compress --include-logs --style markdown \
14
+ -o {project-root}/.sdlc/code-context/<repo>/pack.md
15
+ ```
16
+
17
+ - `--compress` — Tree-sitter structural compression (keeps the pack small and signal-dense).
18
+ - `--include-logs` — recent git history (default 50; `--include-logs-count N` to change).
19
+ - `--style markdown` — human/AI-readable; the default output is `repomix-output.xml`.
20
+ - **Secretlint runs by default** — if a secret is reported, **STOP and redact** before any AI reads the
21
+ pack. Never let a secret reach the model or the cache.
22
+
23
+ Pack the whole repo, or the source boundary defined in the project's constitution. If `npx repomix` is
24
+ unavailable, degrade: hand-assemble the same context (the repo's source tree + recent git log) and
25
+ record `source: repomix-unavailable` in the registry entry.
26
+
27
+ ## Layer 2 — the code-map (lightweight index, the default the brain reads)
28
+
29
+ Feed the pack to the AI with the **"describe what exists, do not invent"** instruction (the same
30
+ discipline as backfill) and write `{project-root}/.sdlc/code-context/<repo>/code-map.md`:
31
+
32
+ > You are indexing an ALREADY-BUILT repo from its packed source + git history. Describe ONLY what the
33
+ > code actually provides. Do NOT invent capabilities, do NOT propose changes, do NOT fill gaps with
34
+ > assumptions. Where something is unclear from the code, mark it `<!-- unverified: ... -->` rather than
35
+ > guessing. Produce a SHORT index a brain phase can scan, not a full spec.
36
+
37
+ Index these sections (omit a section if the repo has none):
38
+
39
+ ```markdown
40
+ ---
41
+ repo: <repo>
42
+ artifact: code-map
43
+ syncedHead: <sha>
44
+ generated: <YYYY-MM-DD>
45
+ source: repomix
46
+ ---
47
+
48
+ ## Stack & conventions
49
+ <!-- language(s), framework(s), notable conventions a new feature must follow -->
50
+
51
+ ## Entry points
52
+ <!-- how the app starts / is invoked; top-level modules -->
53
+
54
+ ## Public endpoints / APIs
55
+ <!-- method + path (or RPC/handler) + one-line purpose — the surface a new contract must not collide with -->
56
+
57
+ ## Events
58
+ <!-- event names + producer/consumer, if any -->
59
+
60
+ ## Data models / entities
61
+ <!-- the entities + key fields already persisted -->
62
+
63
+ ## Module layout
64
+ <!-- the main directories/modules and what each owns -->
65
+ ```
66
+
67
+ The code-map is deliberately small so every front phase can load it cheaply. The full `pack.md` is read
68
+ only when a phase needs depth (the architecture phase, primarily).
69
+
70
+ ## Layer 3 — live on-demand (a specific area, not a stale repo)
71
+
72
+ When a front phase needs an area not captured in the code-map, it may re-pack that **slice** live,
73
+ scoped to the area, without writing the cache:
74
+
75
+ ```bash
76
+ npx repomix@latest --compress --include "<area globs>" --style markdown -o -
77
+ ```
78
+
79
+ This is for a one-off look at a specific area. **Staleness is a human decision, not an automatic
80
+ side-effect:** when a repo is stale (HEAD ≠ `syncedHead`), the phase **flags it and stops** —
81
+ "`<repo>` is stale; run `sdlc repo refresh <repo>` to re-pack the cache + `syncedHead`" — rather than
82
+ silently re-packing the whole repo. A phase never refreshes the registry on its own; the human runs
83
+ `sdlc repo refresh` (or `sdlc check --fix`).
84
+
85
+ ## Why this stays DRY with backfill
86
+
87
+ `sdlc-backfill` already documents the exact Repomix command, the Secretlint discipline, the
88
+ `repomix: unavailable` degrade, and the "describe what exists, do not invent" prompt
89
+ (`../sdlc-backfill/references/backfill.md`). This skill reuses all of it; the only differences are
90
+ **scope** (repo-wide index vs one feature) and **output** (a lightweight code-map for the brain vs a
91
+ human-approved feature spec). Backfill produces a *verified* spec that gates changes; connect produces a
92
+ *context* artifact that informs design — they are complementary.
@@ -0,0 +1,77 @@
1
+ # Hub config — schema, detection, and the reviewer roster
2
+
3
+ The hub config is the product hub's record of **its own** platform (so the front-half review/comment/
4
+ approval cycle can run through a real PR/MR on the hub) and the **reviewer roster** that maps a platform
5
+ login to an SDLC name + role. It is a single object for the hub itself — the sibling of the per-repo
6
+ `repos.json` registry (see `repos-registry.md`), kept separate so it never pollutes that array.
7
+
8
+ ## Location
9
+
10
+ `{project-root}/.sdlc/hub.json`
11
+
12
+ (`config.yaml` `hub.config`.) Created/updated by `sdlc-connect-repos action: detect-hub`.
13
+
14
+ ## Schema
15
+
16
+ ```json
17
+ {
18
+ "platform": "github", // github | gitlab (from the hub's own remote host); null when local-only
19
+ "git_url": "https://github.com/abdelrahmannasr/sdlc-workflow.git",
20
+ "default_branch": "main",
21
+ "bridge_enabled": true, // open review PRs/MRs on the hub for front-half reviews
22
+ "detectedAt": "2026-06-08", // last detect-hub run (YYYY-MM-DD)
23
+ "roster": [
24
+ { "login": "abdelrahmannasr", "name": "alice", "role": "owner" },
25
+ { "login": "bob-gh", "name": "bob", "role": "reviewer" }
26
+ ]
27
+ }
28
+ ```
29
+
30
+ ## The roster — login → name → role
31
+
32
+ The roster is how a platform identity (a GitHub/GitLab **login**) becomes an SDLC **name + role** in the
33
+ file ledger (`approvals.json` / `comments.json`). Roles are the same three the gate already uses:
34
+ `owner | reviewer | domain-owner`.
35
+
36
+ - **`login`** — the platform username whose PR review / approval is being mapped.
37
+ - **`name`** — the SDLC name written into the ledger (the same names used across `approvals.json`,
38
+ `comments.json`, and `epic.md` `owner`). Keep it stable.
39
+ - **`role`** — the person's default role: `owner` or `reviewer`.
40
+
41
+ **`domain-owner` is DERIVED, never duplicated here.** A roster entry whose `name` equals a repo's
42
+ `domain_owner` in `repos.json` is treated as that repo's domain-owner **when that repo is a touched
43
+ domain for the step under review**. `repos.json` stays the single source of domain ownership; the roster
44
+ only resolves the login → name link so the derivation can run.
45
+
46
+ - **Unmapped login fallback.** A login absent from the roster maps to `name: <login>`, `role: reviewer`,
47
+ and is flagged `<!-- unverified login: <login> -->` in the review record (mirrors the code-map
48
+ `unverified` convention). An unmapped login is **never** auto-promoted to `owner` or `domain-owner` —
49
+ it stays a plain reviewer until a human adds it to the roster, so a stranger cannot satisfy the
50
+ owner/domain-owner requirement.
51
+
52
+ ## Detection
53
+
54
+ `detect-hub` reuses the same host-detection logic this skill already applies to code repos:
55
+ run `git remote get-url origin` **on the hub itself** and read the host —
56
+ `github.com` → `github`, `gitlab.com`/self-hosted GitLab → `gitlab`, no remote → `platform: null`.
57
+ Auth is the **local user's own** `gh`/`glab`/git credentials; **no tokens are ever stored** (same rule
58
+ as the registry). `detect-hub` upserts `hub.json` in place — it is idempotent and safe to re-run.
59
+
60
+ ## Bridge enable / degradation
61
+
62
+ - `bridge_enabled: true` **and** a non-null `platform` **and** `gh`/`glab` authenticated → the front-half
63
+ review opens a PR/MR on the hub and `sdlc-review-gate action: sync` pulls platform state into the ledger.
64
+ - `bridge_enabled: false`, `platform: null`, or no/unauthenticated CLI → the gate falls back to the
65
+ existing **file-only** flow with no error. The file ledger is the source of truth in both modes.
66
+ - The master switch `config.yaml` `hub.bridge: false` disables the bridge globally regardless of `hub.json`.
67
+
68
+ ## Git tracking
69
+
70
+ Commit `hub.json` — it is small, reviewable, and carries no secrets (logins and names only, never tokens).
71
+ This mirrors how `repos.json` and the per-epic `.sdlc/` state are committed.
72
+
73
+ ## Greenfield
74
+
75
+ A brand-new hub has no `hub.json`. That is valid — the front-half gate runs file-only until `detect-hub`
76
+ records a platform. The bridge is purely additive; nothing about authoring or the gate predicate changes.
77
+ ```
@@ -0,0 +1,62 @@
1
+ # Repos registry — schema + freshness rule
2
+
3
+ The registry is the product hub's record of which code repos are connected and where their cached
4
+ code-context lives. It is **project-wide** (shared across every epic), so it lives at the product root,
5
+ not under any `epics/EP-<slug>/.sdlc/`.
6
+
7
+ ## Location
8
+
9
+ `{project-root}/.sdlc/repos.json`
10
+
11
+ (`config.yaml` `code_context.registry`.) Create the file and its parent `.sdlc/` on the first
12
+ `connect`.
13
+
14
+ ## Schema
15
+
16
+ ```json
17
+ {
18
+ "repos": [
19
+ {
20
+ "name": "backend", // short name = the key used in stories' `repos:` tag
21
+ "path": "demo-repos/backend", // path to the code repo, rel. to {project-root} (or absolute)
22
+ "git_url": "git@github.com:org/backend.git", // optional remote; SSH or HTTPS; GitHub or GitLab; null if local-only
23
+ "platform": "github", // github | gitlab (from the URL host); null when local-only
24
+ "domain_owner": "carol", // engineer who owns this repo's domain (review routing)
25
+ "default_branch": "main",
26
+ "connectedAt": "2026-06-08", // first connect (YYYY-MM-DD)
27
+ "lastSyncedAt": "2026-06-08", // last connect/refresh
28
+ "syncedHead": "5bd7e8d…", // code repo HEAD sha at last pack — drives staleness
29
+ "contextPack": ".sdlc/code-context/backend/pack.md",
30
+ "codeMap": ".sdlc/code-context/backend/code-map.md",
31
+ "source": "repomix" // repomix | repomix-unavailable (degraded pack)
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ ## Rules
38
+
39
+ - **`name`** is the join key. It MUST match the names used in epic/story `repos:` tags so the front
40
+ phases can map `epic.repos` → registry entries → code-maps. Keep it stable.
41
+ - **Auth is never stored.** No tokens, passwords, or PATs in the registry. `git_url` is a plain remote;
42
+ `connect` clones/fetches as the local user (SSH key or git credential helper).
43
+ - **`connect` upserts by `name`** — re-connecting an existing repo refreshes its entry in place; it
44
+ never creates a duplicate.
45
+ - **`syncedHead`** is the authority for freshness. A repo is **stale** when
46
+ `git -C <path> rev-parse HEAD` ≠ `syncedHead`. `list` flags it; `refresh` clears it.
47
+ - **`disconnect`** removes the entry and deletes `{project-root}/.sdlc/code-context/<name>/`. The code
48
+ repo on disk is never touched.
49
+
50
+ ## Git tracking
51
+
52
+ Commit the **registry** (`repos.json`) and each repo's **`code-map.md`** — they are small, reviewable,
53
+ and are what the front phases actually read (a diff on a code-map shows when a repo's surface moved).
54
+ **Ignore** the full Repomix `pack.md` — it is large and regenerable (`action: refresh`). The product
55
+ hub's `.gitignore` carries `.sdlc/code-context/*/pack.md` for this. This mirrors how the per-epic
56
+ `.sdlc/` state (state.json, approvals.json, build-log.json) is committed.
57
+
58
+ ## Greenfield
59
+
60
+ A brand-new product hub has no `repos.json` (or an empty `{ "repos": [] }`). That is valid — the front
61
+ phases treat "no repos connected" as "nothing to consider yet" and proceed unchanged. The registry
62
+ appears the first time `connect` runs.
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: sdlc-hub-bridge
3
+ description: 'The templated PR/MR bridge for the front-half review gate. When the product hub has a platform (.sdlc/hub.json), it opens a review PR/MR on the hub for an authored artifact (the optional analysis / epic / architecture+contract / ui-design / stories), sets the required reviewers/labels from the routing rule, and provides the read-only gh/glab recipes that sdlc-review-gate uses to pull platform comments + approvals back into the file ledger. Can also wire event-driven sync on the hub: a CI workflow that runs `sdlc gate ci` whenever a reviewer approves / requests changes / a human merges, committing the ledger to the default branch. Local-user auth only — no stored tokens. The file ledger stays the source of truth; degrades to the file-only gate when there is no platform / no CLI. Use when the user says "open the review PR", "route the review", "wire the gate sync", or it is invoked by sdlc-review-gate open/sync.'
4
+ ---
5
+
6
+ # SDLC — Hub Review Bridge (the templated PR/MR bridge)
7
+
8
+ **Goal:** Run the front-half review/comment/approval cycle through a **real PR/MR on the product hub**,
9
+ without changing the gate's predicate or making the file ledger optional. The bridge is an **alternate
10
+ input path** into `sdlc-review-gate`: it opens a review PR for an artifact, reviewers approve/comment on
11
+ the platform with **their own** `gh`/`glab` auth, and the gate's `sync` action (which calls this skill's
12
+ read recipes) maps that platform state into `approvals.json` / `comments.json` / `reviews/*.md`, then
13
+ runs the **unchanged** predicate. The file ledger remains the source of truth.
14
+
15
+ This skill owns the **branch/PR mechanics + the gh/glab recipes** (mirroring how `sdlc-pr-template`
16
+ keeps platform mechanics out of the gate). `sdlc-review-gate` *calls* it; it never advances a step.
17
+
18
+ ## Conventions
19
+
20
+ - `{project-root}` is the **product hub**. Hub platform + reviewer roster live in `.sdlc/hub.json`
21
+ (`config.yaml` `hub.config`; schema in `../sdlc-connect-repos/references/hub-config.md`).
22
+ - Per-step review-PR record: `epics/EP-<slug>/.sdlc/hub-prs.json` (`config.yaml` `hub.pr_ledger`) — a
23
+ sibling ledger to `approvals.json`, so `state.json`'s locked step shape is untouched.
24
+ - The review-PR body is the hub template from `sdlc-pr-template` (`templates/hub/<platform>/…`).
25
+ - Branch per artifact: `review/EP-<slug>/<artifact-base>` (`config.yaml` `hub.artifact_branch`).
26
+ - **Local-user auth only; store no tokens.** Use the user's own `gh`/`glab`. If neither is installed/
27
+ authenticated, STOP this path and tell the gate to fall back to file-only — never embed a credential.
28
+
29
+ ## Inputs
30
+
31
+ - `epic` — the `EP-<slug>` under review.
32
+ - `artifact` — the artifact file (`analysis.md` | `epic.md` | `architecture.md` | `ui-design.md` | `stories/`).
33
+ - `action` — `open` | `route` | `wire` (default `route`). (`sync`'s ledger writes live in
34
+ `sdlc-review-gate`; this skill provides the read recipes `sync` calls — see `references/bridge.md`.)
35
+
36
+ ## Preconditions (the bridge runs only when all hold)
37
+
38
+ `.sdlc/hub.json` exists with a non-null `platform`, `bridge_enabled: true`, `config.yaml` `hub.bridge:
39
+ true`, and `gh` (GitHub) / `glab` (GitLab) installed **and authenticated**. If any fails, report that the
40
+ gate proceeds **file-only** (no error) and stop.
41
+
42
+ ## On Activation
43
+
44
+ ### Step 1 — Resolve platform + routing
45
+ Read `.sdlc/hub.json` for the platform and roster, `epics/<epic>/epic.md` for `repos` + `owner`, and the
46
+ matching `review+approve` step's `risk_tags` from `.sdlc/state.json`. Compute the **required reviewers**
47
+ with `route` (below) — the same rule `sdlc-review-gate` enforces: base (owner + 1 reviewer); escalated
48
+ (`risk_tags` ∩ {contract,auth,payments}, or the stories step) adds a domain-owner per touched repo. Map
49
+ each required domain-owner to a platform `login` via the roster (a roster `name` equal to a repo's
50
+ `domain_owner` in `repos.json` is that repo's domain-owner).
51
+
52
+ ### Step 2 — `open` (create the review PR/MR)
53
+ 1. From the hub default branch, create `review/EP-<slug>/<artifact-base>` and ensure the artifact file
54
+ (and, for architecture, `contract.md` + `.sdlc/contract-lock.json`) is committed on it. Push as the
55
+ local user.
56
+ 2. Open the PR/MR with `gh`/`glab` using the hub body template (`sdlc-pr-template` `templates/hub/…`),
57
+ filled with the epic, artifact, gate step, owner, `epic.repos`, and the step's risk tags.
58
+ 3. **Request the required reviewers** (their logins) and add a `domain:<repo>` label per touched repo so
59
+ per-repo routing is legible on the PR.
60
+ 4. Upsert a record into `epics/<epic>/.sdlc/hub-prs.json`:
61
+ ```json
62
+ { "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab",
63
+ "number": <n>, "url": "<pr/mr url>", "branch": "review/EP-<slug>/<artifact-base>", "lastSyncedAt": null }
64
+ ```
65
+ 5. Report the PR/MR URL and the required reviewers. **Do not** record approvals or advance — reviewers
66
+ act on the platform; `sdlc-review-gate action: sync` pulls it back.
67
+
68
+ ### Step 3 — `route` (print required reviewers)
69
+ Compute and print the required reviewers as above. Use `templates/checks/hub-route.sh <body>` to parse a
70
+ PR/MR body's Impact & Risk block when given one; otherwise derive from `epic.repos` + the step's
71
+ `risk_tags`. Advisory only — it routes the human review, it does not approve.
72
+
73
+ ### Step 4 — `wire` (event-driven sync on the hub)
74
+ Install the hub CI that turns platform actions — a review **approval**, a **change request**, a review
75
+ dismissal, or the human **merge** — into an automatic `sdlc gate ci` run that syncs the ledger and
76
+ commits it to the hub's default branch. No more waiting on a manual `sdlc gate sync`; the manual command
77
+ remains valid and is the fallback whenever CI cannot push.
78
+
79
+ 1. Run `sdlc check --fix` (the wiring is manifest-driven, like `sdlc-checks`): with a platform +
80
+ enabled bridge in `.sdlc/hub.json` it installs
81
+ - GitHub → `.github/workflows/sdlc-gate-sync.yml` (from `templates/github/sdlc-gate-sync.yml`)
82
+ - GitLab → `.gitlab/ci/sdlc-gate-sync.yml` (from `templates/gitlab/sdlc-gate-sync.gitlab-ci.yml`)
83
+ - plus the hub-side **verified-commits** gate (`checks/verified-commits.sh` + its workflow/fragment,
84
+ owned by `sdlc-checks`) so review PRs accept only signed commits from roster-known authors
85
+ 2. **GitLab only — two one-time steps** (see the fragment's header for the exact recipes):
86
+ - add `include: - local: '.gitlab/ci/sdlc-gate-sync.yml'` to the root `.gitlab-ci.yml`, or write
87
+ `templates/gitlab/gitlab-ci.include-root.yml` as the root when none exists;
88
+ - create the 15-minute pipeline **schedule** (variable `SDLC_GATE_SYNC=true`) and the masked
89
+ `SDLC_GATE_TOKEN` project-access-token variable (`read_api` + `write_repository`). GitLab fires no
90
+ pipeline on an approval alone, so the schedule is the path that picks approvals up (≤ ~15 min
91
+ latency); MR events and the merge are near-immediate.
92
+ 3. Commit the workflow to the hub. GitHub needs nothing else — the ephemeral `github.token` reads the
93
+ PR and pushes the ledger. If the default branch is protected, see the workflow header for the
94
+ bypass / PAT options; until then the run fails visibly and manual `sdlc gate sync` still works.
95
+
96
+ ## Hard rules
97
+
98
+ - **Local-user auth only; store no tokens.** Reviewers use their own `gh`/`glab`.
99
+ - **The bridge is an input path, never the authority.** It opens PRs and reads state; the **file ledger
100
+ is the source of truth** and the gate predicate (in `sdlc-review-gate`) is unchanged.
101
+ - **The bridge never approves on a reviewer's behalf.** Reviewers approve/merge with their own auth. The
102
+ step advances when a human **merges** the approved, fully-resolved review PR (the merge is that human
103
+ act) — `sdlc gate sync` records the approvals + resolution + merged state and advances; unresolved
104
+ comments or a changed artifact hold it `in_review`. The mechanical sync is the `sdlc gate` CLI.
105
+ - **CI never approves and never merges.** The wired workflow only runs `gate ci` — the same sync +
106
+ unchanged predicate — and commits the **ledger files only** to the default branch; the artifact lands
107
+ on the default branch exclusively via the human merge. Front gates stay permanently human.
108
+ - **The CI tokens are the one documented bend of "no stored tokens".** GitHub uses the platform's own
109
+ ephemeral `github.token` (nothing stored). GitLab requires a stored masked `SDLC_GATE_TOKEN`
110
+ project access token because `CI_JOB_TOKEN` can neither read the approvals API nor push — say so
111
+ when wiring, and scope it to `read_api` + `write_repository` only.
112
+ - **Degrade gracefully.** No platform / disabled bridge / no CLI → the gate runs file-only with no error.
113
+
114
+ ## Reference
115
+ - PR-body→ledger mapping, the read-only gh/glab recipes, idempotent re-sync, contract re-lock handling:
116
+ `references/bridge.md`.
117
+ - Roster schema, login→role resolution, unverified-login fallback, per-repo routing: `references/login-roster.md`.
118
+ - The gate this feeds (open hook + sync action + unchanged predicate): `../sdlc-review-gate/SKILL.md`, `../sdlc-review-gate/references/gating.md`.
119
+ - The hub PR/MR body template + the code-repo routing analogue: `../sdlc-pr-template/`.
@@ -0,0 +1,136 @@
1
+ # The bridge — PR/MR ↔ ledger mapping, read recipes, idempotency
2
+
3
+ The bridge maps platform review state onto the **same file records** the manual gate writes, so the gate
4
+ predicate (`../sdlc-review-gate/references/gating.md`) runs unchanged. The bridge only changes the
5
+ *input path*; it never changes what passing the gate means.
6
+
7
+ ## State mapping (platform → ledger)
8
+
9
+ | Platform review state | Ledger effect |
10
+ |---|---|
11
+ | GitHub review `APPROVED` / GitLab MR approval (`approved_by`) | an `approved` record in `approvals.json`, role resolved from the roster (owner/reviewer) or derived domain-owner, tagged `"source": "bridge"` |
12
+ | GitHub `COMMENTED` / `CHANGES_REQUESTED`; GitLab discussions/notes | a line under `## <name> (<role>)` in `reviews/<artifact>--<date>--comments.md` + a `comments.json` record; **never** an approval. `CHANGES_REQUESTED` is also flagged as blocking in the comments file |
13
+ | GitHub review dismissed / GitLab approval revoked | the prior bridge `approved` record for that approver is removed on re-sync (see idempotency) |
14
+
15
+ `approvals.json` records from the bridge carry `"source": "bridge"`; **manual** approvals have no such
16
+ tag and are **never** touched by `sync` — the two coexist.
17
+
18
+ ## Read recipes (read-only, local-user auth — no tokens)
19
+
20
+ **GitHub** (`gh`, the reviewer/runner's own auth):
21
+ ```
22
+ gh pr view <n> --json reviews,comments,reviewDecision,latestReviews
23
+ gh api repos/{owner}/{repo}/pulls/{n}/comments # inline review comments
24
+ ```
25
+ - `reviews[].state` ∈ {APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED}; `reviews[].author.login` is
26
+ the login to resolve. Use `latestReviews` so a superseded earlier review doesn't double-count.
27
+
28
+ **GitLab** (`glab` / `glab api`):
29
+ ```
30
+ glab mr view <n>
31
+ glab api projects/:id/merge_requests/:iid/approvals # approved_by[].user.username
32
+ glab api projects/:id/merge_requests/:iid/notes # discussion notes (comments)
33
+ ```
34
+
35
+ All commands run as the **local user**; the bridge stores no tokens. If the CLI is missing/unauthenticated
36
+ or the remote is unreachable, the bridge stops and the gate falls back to file-only (no error).
37
+
38
+ ## Login → role resolution (order)
39
+
40
+ 1. Roster (`.sdlc/hub.json`) maps `login` → `name` + base `role` (owner/reviewer).
41
+ 2. If that `name` equals a repo's `domain_owner` in `repos.json` **and** that repo is a touched domain
42
+ for this step → also emit a `domain-owner` record with `domain: <repo>`.
43
+ 3. Login not in the roster → `name: <login>`, `role: reviewer`, flagged
44
+ `<!-- unverified login: <login> -->`. **Never** auto-promoted to owner/domain-owner.
45
+
46
+ (Full detail + per-repo routing: `login-roster.md`.)
47
+
48
+ ## Idempotent re-sync
49
+
50
+ - Key bridge approvals on `(step, approver, role, domain)`. On re-sync, **upsert** — do not append a
51
+ duplicate. Remove any bridge approval whose platform review was dismissed/revoked.
52
+ - Key synced comments on the platform comment id so the same comment is not appended twice.
53
+ - Update the step's `hub-prs.json` `lastSyncedAt` after a successful sync.
54
+ - Running `sync` twice with no platform change is a no-op on the ledger.
55
+
56
+ ## Contract re-lock invalidates prior platform approvals too
57
+
58
+ For the **architecture+contract** review, the gate already drops approvals when the contract-surface hash
59
+ no longer matches `.sdlc/contract-lock.json`. The bridge extends this to platform-sourced approvals:
60
+ `sync` discards bridge `approved` records for the architecture step dated **before** the new lock, and
61
+ posts a comment on the review PR noting "contract re-locked — re-approval required". The escalation
62
+ (`risk_tags: ["contract"]` → a domain-owner per repo) is unchanged.
63
+
64
+ ## CHANGES_REQUESTED & unresolved threads hold the gate
65
+
66
+ `CHANGES_REQUESTED` and any **unresolved review thread** are recorded as comments and surfaced as
67
+ **blocking**. Under the PR-driven gate they actively hold the step `in_review`: the predicate does not
68
+ pass while any thread is unresolved, even if the approval counts are met. The owner addresses the
69
+ comments, replies, the reviewer **resolves** their thread, then `sync` runs again.
70
+
71
+ ## Merge advances; an artifact change revokes approvals
72
+
73
+ - **Merge → advance.** When the reviewer rule is satisfied, every thread is resolved, **and the review
74
+ PR/MR is merged**, `sync` marks the step `done` and unblocks the next step. The merge is the human
75
+ approval act — there is no separate machine advance. (`sdlc gate sync` performs this deterministically.)
76
+ - **Revoke on artifact change.** Each bridge approval is stamped with the content hash it was given
77
+ against (the file bytes; the locked contract surface for architecture). On re-sync, an approval whose
78
+ stamped hash ≠ the current hash is **dropped** — the reviewer must re-approve the changed artifact. A
79
+ genuinely newer review (later `submittedAt`) re-stamps against the new hash. This is "revoke only when
80
+ the artifact changed", not "revoke on any PR commit".
81
+
82
+ ## Event-driven sync (hub CI)
83
+
84
+ The `wire` action (SKILL.md Step 4) installs a CI workflow on the hub so the platform events drive
85
+ `sdlc gate ci` instead of waiting on a manual `sdlc gate sync`. The CLI entry is self-sufficient: it
86
+ derives the epic + artifact from the `review/EP-<slug>/<artifact-base>` head branch and takes the PR/MR
87
+ number from the event payload — it even upserts the `hub-prs.json` entry when the author never committed
88
+ it, so the first CI commit converges the views. `sdlc gate ci` with no `--branch` sweeps every open
89
+ review PR (the scheduled path).
90
+
91
+ | Platform event | CI action |
92
+ |---|---|
93
+ | review submitted (approve / changes requested) or dismissed | `gate ci --branch <head> --pr <n>` → sync the ledger; the predicate may hold or pass |
94
+ | PR/MR `synchronize` (new commits on the review branch) | same — promptly re-stamps/revokes approvals on artifact change |
95
+ | PR/MR closed **and merged** (the human act) | same — predicate sees `merged: true` and advances the step |
96
+ | GitLab schedule (`*/15 * * * *`, `SDLC_GATE_SYNC=true`) | `gate ci` sweep — the **only** GitLab path that sees a bare approval (≤ ~15 min latency) |
97
+
98
+ **The overlay.** Pre-merge, the artifact under review exists only on the review branch, while the
99
+ ledger lives on the default branch. `gate ci` checks out the default branch, fetches the head ref and
100
+ overlays just the artifact paths (for architecture: `architecture.md` + `contract.md` +
101
+ `.sdlc/contract-lock.json`; for stories: `stories/`) so `artifactHash` binds each approval to **what the
102
+ reviewers actually approved** — then drops the overlay before committing. Only
103
+ `epics/<epic>/.sdlc/*.json` + `reviews/*.md` are committed (message `chore(gate): … [skip ci]`); the
104
+ artifact reaches the default branch exclusively via the human merge.
105
+
106
+ **Loop prevention & races.** The GitHub triggers (`pull_request_review`, `pull_request`) cannot fire on
107
+ a push to the default branch, `[skip ci]` guards every other workflow on both platforms, and a
108
+ repo-wide concurrency group serializes runs; the ledger push retries with a rebase (ledger-only commits
109
+ across epics touch disjoint files).
110
+
111
+ **Tokens.**
112
+ - GitHub: the ephemeral `github.token` with `contents: write` + `pull-requests: read` — nothing stored.
113
+ - GitLab: a masked `SDLC_GATE_TOKEN` project access token (`read_api` + `write_repository`) — the one
114
+ documented bend of the no-stored-tokens rule; `CI_JOB_TOKEN` can neither read the approvals API nor
115
+ push.
116
+ - Protected default branch (GitHub): prefer a ruleset bypass for Actions; else a fine-grained PAT as
117
+ `SDLC_GATE_TOKEN` passed to `actions/checkout`; else leave it — the run fails **visibly** and manual
118
+ `sdlc gate sync` remains the fallback. Never wire branch protection that couples the merge itself to
119
+ the gate.
120
+
121
+ **Manual sync stays first-class.** `sdlc gate sync <epic> [artifact]` is unchanged and always valid —
122
+ the CI is the same sync on a trigger, and the file ledger is still the source of truth.
123
+
124
+ ### Manual end-to-end verification (GitHub)
125
+
126
+ 1. On a scratch hub: `sdlc setup` (platform github, roster with a second account) → `sdlc check --fix`
127
+ installs `.github/workflows/sdlc-gate-sync.yml`; commit + push it.
128
+ 2. Author an epic → `sdlc gate open EP-x epic.md` → the review PR opens.
129
+ 3. Second account **approves** → the Actions run commits `approvals.json` to the default branch.
130
+ 4. Second account **requests changes** → the run records the blocking comment; `sdlc gate status EP-x`
131
+ (after `git pull`) shows the gate held.
132
+ 5. Resolve the thread, approve again, a human **merges** → the closed-event run advances `state.json`
133
+ (`epic-review: done`, `currentStep: architecture`).
134
+ 6. `git pull` locally — `sdlc gate status EP-x` matches the platform history.
135
+
136
+ GitLab variant: same flow on an MR; a bare approval appears after the next schedule tick.