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
package/cli/setup.mjs ADDED
@@ -0,0 +1,208 @@
1
+ // `sdlc setup` — the guided, idempotent first-run wizard.
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import {
5
+ c, log, step, ok, info, warn, hand, fail, ask, askYesNo, run, has,
6
+ exists, asset, copyFile, readJSON, writeJSON,
7
+ } from './lib.mjs';
8
+ import { VERSION, IDE_FOLDER_TARGETS, IDE_OPENCODE_DIR, PROJECT_FILES } from './manifest.mjs';
9
+ import { moduleActions, repoActions, hubActions, authorsActions } from './plan.mjs';
10
+
11
+ const ALL_IDES = [...IDE_FOLDER_TARGETS, '.opencode'];
12
+
13
+ export function detectPlatform(remoteUrl = '') {
14
+ if (/gitlab/i.test(remoteUrl)) return 'gitlab';
15
+ if (/github/i.test(remoteUrl)) return 'github';
16
+ return null;
17
+ }
18
+ export const gitHead = (cwd) => run('git', ['rev-parse', 'HEAD'], { cwd }).stdout || null;
19
+
20
+ // Containment: every repo path must live inside the project root — the registry path is later
21
+ // joined and executed against (repomix cwd, CI wiring), and even the read-only remote probe must
22
+ // not run against an arbitrary outside path. The path.sep-suffixed compare avoids the
23
+ // /proj vs /proj-evil prefix trap.
24
+ export function insideRoot(root, rpath) {
25
+ const projectRoot = path.resolve(root);
26
+ const resolved = path.resolve(projectRoot, rpath);
27
+ return resolved === projectRoot || resolved.startsWith(projectRoot + path.sep);
28
+ }
29
+
30
+ // Validate + record one code repo into the registry (the testable half of the connect loop).
31
+ // A path that is not a git repository is rejected and NOTHING is written — a registry entry with
32
+ // syncedHead:null would only surface later as an unexplained "unknown status" in the CI gates.
33
+ export function registerRepo(root, registry, { name, rpath, platform, domain_owner = '', default_branch = 'main', today = null }) {
34
+ if (!insideRoot(root, rpath)) {
35
+ warn(`${rpath} resolves outside the project root — skipped`);
36
+ return null;
37
+ }
38
+ const repoRoot = path.resolve(root, rpath);
39
+ const head = gitHead(repoRoot);
40
+ if (head === null) { warn(`${rpath} is not a git repository (or has no commits) — skipped`); return null; }
41
+ const remote = run('git', ['remote', 'get-url', 'origin'], { cwd: repoRoot });
42
+ let plat = (platform || '').toLowerCase();
43
+ if (!['github', 'gitlab'].includes(plat)) {
44
+ const detected = detectPlatform(remote.ok ? remote.stdout : '') || 'github';
45
+ if (plat) warn(`unknown platform '${platform}' — using ${detected}`);
46
+ plat = detected;
47
+ }
48
+ const repo = {
49
+ name, path: rpath, git_url: (remote.ok && remote.stdout) || null, platform: plat, domain_owner, default_branch,
50
+ connectedAt: today, lastSyncedAt: today,
51
+ syncedHead: head,
52
+ contextPack: `.sdlc/code-context/${name}/pack.md`,
53
+ codeMap: `.sdlc/code-context/${name}/code-map.md`,
54
+ source: 'repomix',
55
+ };
56
+ registry.repos.push(repo);
57
+ writeJSON(path.join(root, PROJECT_FILES.reposRegistry), registry);
58
+ return repo;
59
+ }
60
+
61
+ function applyActions(actions, { force = false } = {}) {
62
+ let changed = 0;
63
+ for (const a of actions) {
64
+ if (a.status === 'ok' && !force) continue;
65
+ a.apply();
66
+ changed++;
67
+ info(`${a.status === 'missing' ? 'installed' : 'updated'} ${a.scope}/${a.item}`);
68
+ }
69
+ if (!changed) info('already up to date');
70
+ return changed;
71
+ }
72
+
73
+ export async function runSetup(root, opts = {}) {
74
+ const total = 7;
75
+ log(c.bold(`\nSDLC Workflow setup ${c.dim('v' + VERSION)}`));
76
+ log(c.dim(`target: ${root}`));
77
+
78
+ // 1. Preflight
79
+ step(1, total, 'Preflight');
80
+ if (!exists(path.join(root, '.git'))) {
81
+ if (await askYesNo('Not a git repo. Run `git init` here?', true)) {
82
+ run('git', ['init'], { cwd: root });
83
+ ok('git initialized');
84
+ } else warn('continuing without git — hub detection will be skipped');
85
+ } else ok('git repo detected');
86
+ for (const tool of ['git', 'node']) has(tool) ? ok(`${tool} present`) : warn(`${tool} not found on PATH`);
87
+ if (!has('npx')) warn('npx not found — repomix packing will be skipped');
88
+
89
+ // 2. Install the module
90
+ step(2, total, 'Install the module (skills + _bmad registration)');
91
+ let ideTargets = opts.ideTargets;
92
+ if (!ideTargets) {
93
+ const present = ALL_IDES.filter((d) => exists(path.join(root, d)));
94
+ const def = (present.length ? present : ['.claude']).join(',');
95
+ const answer = await ask(`IDE targets to install ${c.dim('(comma-separated: ' + ALL_IDES.join(', ') + ')')}`, def);
96
+ ideTargets = answer.split(',').map((s) => s.trim()).filter(Boolean);
97
+ }
98
+ applyActions(moduleActions(root, ideTargets), { force: true });
99
+ ok(`module installed into: ${ideTargets.join(', ')}`);
100
+
101
+ // 3. Detect hub platform + roster
102
+ step(3, total, 'Hub platform & reviewer roster');
103
+ const hubPath = path.join(root, PROJECT_FILES.hubConfig);
104
+ if (exists(hubPath) && !(await askYesNo('hub.json exists — reconfigure?', false))) {
105
+ info('keeping existing .sdlc/hub.json');
106
+ } else {
107
+ const remote = run('git', ['remote', 'get-url', 'origin'], { cwd: root });
108
+ if (!remote.ok && exists(path.join(root, '.git'))) info('no origin remote — platform detection skipped');
109
+ let platform = detectPlatform(remote.ok ? remote.stdout : '');
110
+ platform = (await ask('Hub platform (github/gitlab/none)', platform || 'none')).toLowerCase();
111
+ if (!['github', 'gitlab', 'none'].includes(platform)) {
112
+ warn(`unknown platform '${platform}' — using none (file-only gate)`);
113
+ platform = 'none';
114
+ }
115
+ const roster = [];
116
+ if (await askYesNo('Add reviewers to the roster now?', true)) {
117
+ for (;;) {
118
+ const login = await ask(' reviewer platform login (blank to finish)', '');
119
+ if (!login) break;
120
+ const name = await ask(' sdlc name', login);
121
+ const role = await ask(' role (owner/reviewer/domain-owner)', 'reviewer');
122
+ const email = await ask(' commit email (verified-commits gate; blank to skip)', '');
123
+ roster.push({ login, name, role, ...(email ? { email } : {}) });
124
+ }
125
+ }
126
+ const default_branch = platform === 'none' ? 'main' : await ask('Hub default branch', 'main');
127
+ // `bridge_enabled` is the canonical flag (hub-config schema); keep the legacy `bridge` spelling
128
+ // for anything that still reads it.
129
+ const enabled = platform !== 'none';
130
+ writeJSON(hubPath, { platform: enabled ? platform : null, bridge_enabled: enabled, bridge: enabled, default_branch, roster });
131
+ ok(`wrote ${PROJECT_FILES.hubConfig} (${roster.length} reviewer(s))`);
132
+ }
133
+
134
+ // 4. Connect code repos
135
+ step(4, total, 'Connect code repos');
136
+ const regPath = path.join(root, PROJECT_FILES.reposRegistry);
137
+ const registry = readJSON(regPath, { repos: [] });
138
+ const known = new Set(registry.repos.map((r) => r.name));
139
+ if (await askYesNo(`Connect a code repo? ${c.dim(`(${registry.repos.length} already registered)`)}`, registry.repos.length === 0)) {
140
+ for (;;) {
141
+ const name = await ask(' repo name (blank to finish)', '');
142
+ if (!name) break;
143
+ if (known.has(name)) { warn(`${name} already registered — skipping`); continue; }
144
+ const rpath = await ask(' path (relative to project root)', `demo-repos/${name}`);
145
+ if (!insideRoot(root, rpath)) { warn(`${rpath} resolves outside the project root — skipped`); continue; }
146
+ const detected = run('git', ['remote', 'get-url', 'origin'], { cwd: path.resolve(root, rpath) });
147
+ const platform = (await ask(' platform (github/gitlab)', detectPlatform(detected.ok ? detected.stdout : '') || 'github')).toLowerCase();
148
+ const domain_owner = await ask(' domain owner', '');
149
+ const default_branch = await ask(' default branch', 'main');
150
+ const repo = registerRepo(root, registry, { name, rpath, platform, domain_owner, default_branch, today: opts.today ?? null });
151
+ if (!repo) continue;
152
+ known.add(name);
153
+ ok(`registered ${name}`);
154
+ packRepo(root, repo);
155
+ }
156
+ }
157
+
158
+ // 5. Wire each connected repo + the hub itself
159
+ step(5, total, 'Wire connected repos + the hub (CI gates, PR template, comment scaffold, gate-sync)');
160
+ if (registry.repos.length === 0) info('no repos to wire');
161
+ for (const repo of registry.repos) {
162
+ log(` ${c.bold(repo.name)} ${c.dim(`(${repo.platform})`)}`);
163
+ applyActions(repoActions(root, repo), { force: true });
164
+ }
165
+ // the hub: event-driven gate-sync CI, so platform approvals/merges drive `sdlc gate ci`
166
+ const hubWiring = hubActions(root);
167
+ if (hubWiring.length) {
168
+ log(` ${c.bold('hub')} ${c.dim('(gate-sync + verified-commits CI)')}`);
169
+ applyActions(hubWiring, { force: true });
170
+ }
171
+ // author allowlists for the verified-commits gate (hub + every repo), from the roster emails
172
+ applyActions(authorsActions(root, registry.repos), { force: true });
173
+
174
+ // 6. Optional CodeRabbit
175
+ step(6, total, 'AI review (CodeRabbit)');
176
+ for (const repo of registry.repos) {
177
+ const cr = path.join(path.resolve(root, repo.path), '.coderabbit.yaml');
178
+ if (exists(cr)) { info(`${repo.name}: .coderabbit.yaml present`); continue; }
179
+ if (await askYesNo(`Wire CodeRabbit (advisory) in ${repo.name}?`, false)) {
180
+ fs.writeFileSync(cr, 'reviews:\n high_level_summary: true\n poem: false\n');
181
+ ok(`${repo.name}: wrote .coderabbit.yaml`);
182
+ }
183
+ }
184
+
185
+ // 7. Summary + version stamp
186
+ step(7, total, 'Done');
187
+ writeJSON(path.join(root, PROJECT_FILES.version), { version: VERSION, ideTargets, updatedAt: opts.today ?? null });
188
+ ok(`stamped ${PROJECT_FILES.version} (v${VERSION})`);
189
+ log('');
190
+ log(c.bold('Next — AI-only steps (run in Claude Code):'));
191
+ hand('generate code-maps: run `sdlc-connect-repos` for each connected repo');
192
+ hand('author your first epic: run `sdlc-author-epic`');
193
+ log('');
194
+ log(c.dim('Re-run anytime: `sdlc check` (report) / `sdlc check --fix` (reconcile).'));
195
+ }
196
+
197
+ // Deterministic repomix pack (code-map generation itself is an AI step, handed off).
198
+ export function packRepo(root, repo) {
199
+ const repoRoot = path.resolve(root, repo.path);
200
+ const out = path.join(root, repo.contextPack);
201
+ if (!has('npx')) { warn(`${repo.name}: npx missing — skipped repomix pack`); return false; }
202
+ fs.mkdirSync(path.dirname(out), { recursive: true });
203
+ info(`${repo.name}: packing with repomix …`);
204
+ const r = run('npx', ['repomix@latest', '--compress', '--include-logs', '--style', 'markdown', '-o', out], { cwd: repoRoot });
205
+ if (r.ok) { ok(`${repo.name}: cached ${repo.contextPack}`); hand(`${repo.name}: generate the code-map in Claude Code (sdlc-connect-repos)`); return true; }
206
+ fail(`${repo.name}: repomix failed — ${r.stderr.split('\n')[0] || 'unknown error'}`);
207
+ return false;
208
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "yadflow",
3
+ "version": "1.0.1",
4
+ "description": "Gated, team, multi-repo SDLC workflow: author → review → build with a PR-driven review gate and a zero-dependency CLI (setup, gate, commit, open-pr, repo). A BMAD module + 17 skills.",
5
+ "type": "module",
6
+ "author": "AbdelRahman Nasr",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/abdelrahmannasr/sdlc-workflow.git"
11
+ },
12
+ "homepage": "https://github.com/abdelrahmannasr/sdlc-workflow#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/abdelrahmannasr/sdlc-workflow/issues"
15
+ },
16
+ "bin": {
17
+ "sdlc": "bin/sdlc.mjs"
18
+ },
19
+ "files": [
20
+ "bin/",
21
+ "cli/",
22
+ "!cli/test.mjs",
23
+ "skills/",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": true
34
+ },
35
+ "scripts": {
36
+ "sdlc": "node bin/sdlc.mjs",
37
+ "test": "node --test cli/test.mjs",
38
+ "prepublishOnly": "npm test"
39
+ },
40
+ "keywords": [
41
+ "sdlc",
42
+ "bmad",
43
+ "workflow",
44
+ "cli",
45
+ "spec-driven-development"
46
+ ],
47
+ "devDependencies": {
48
+ "@semantic-release/changelog": "^6.0.3",
49
+ "semantic-release": "^25.0.3"
50
+ }
51
+ }
@@ -0,0 +1,156 @@
1
+ # SDLC Workflow Module Configuration
2
+ # Hand-authored custom BMAD module (format mirrors _bmad/<module>/config.yaml, v6.8.0).
3
+ # Source of truth lives in {project-root}/skills/ and survives BMAD updates.
4
+
5
+ module_name: sdlc
6
+ module_title: "SDLC Workflow"
7
+ methodology: gated-team-multirepo-sdlc
8
+
9
+ # Where epic "thinking" artifacts and per-epic state live (the product repo).
10
+ product_root: "{project-root}"
11
+ epics_folder: "{project-root}/epics"
12
+
13
+ # Core configuration values (inherited convention from _bmad/config.toml).
14
+ project_name: sdlc-workflow
15
+ communication_language: English
16
+ document_output_language: English
17
+ output_folder: "{project-root}/_bmad-output"
18
+
19
+ # Default dials applied to every new step (build plan §2).
20
+ defaults:
21
+ assistance: review # none | review | heavy
22
+ automation: human_approve # human_approve | machine_advance
23
+ # Front steps (analysis [optional], epic, architecture, ui-design, stories) are locked to
24
+ # human_approve and may NOT be set to machine_advance in this version (build plan §1, §8.7).
25
+ front_steps_locked: true
26
+ # Each front authoring step opens its own branch at the start of the step (the <step> is the step id:
27
+ # analysis | epic | architecture | ui-design | stories). Git/greenfield-safe; distinct from the
28
+ # bridge's review branch (hub.artifact_branch). See sdlc-author-epic/references/state-schema.md.
29
+ front_authoring_branch: "<step>/EP-<slug>"
30
+
31
+ # Team review gate defaults (build plan §3 piece 2, §4).
32
+ review_gate:
33
+ default_reviewers: 1 # non-owner reviewers required (in addition to 1 owner) => owner + 1 reviewer
34
+ escalate_when: [contract, auth, payments] # escalate to domain owners
35
+ # PR-driven automation (the `sdlc gate` CLI). With a hub platform, the review rides the per-step
36
+ # PR/MR: `sdlc gate sync` maps platform reviews/threads into the file ledger and the step
37
+ # AUTO-ADVANCES on merge, once (a) the reviewer rule is met, (b) every comment thread is resolved,
38
+ # and (c) the review PR/MR is merged. The merge is the human approval act, so front steps still never
39
+ # machine_advance. The file ledger stays the source of truth; no platform / no gh|glab => file-only.
40
+ advance_on: merge # merge of the approved, fully-resolved review PR advances the step
41
+ revoke_on: artifact-change # re-hash the artifact (contract surface for architecture); a changed
42
+ # hash drops the bound approvals so reviewers re-approve. NOT per-commit.
43
+ block_on_unresolved_comments: true # any unresolved thread / CHANGES_REQUESTED holds it in_review
44
+
45
+ # Build half (Phase 3). Code repos are SEPARATE git repos (one .git each), not subfolders
46
+ # of the product repo — faithful to "per-repo specs in each code repo, contract singular in the
47
+ # product repo" (phase-3-build-plan.md, Cross-cutting). Documentation-as-config for sdlc-spec (Step A).
48
+ build:
49
+ code_repos_root: "{project-root}/demo-repos" # throwaway demo code repos for this build half
50
+ feature_id: story_id # specs/<story-id>/ — pinned to the permanent story ID, not Spec Kit's auto-slug
51
+ spec_layout: speckit # follow Spec Kit's native spec/plan/tasks layout
52
+ speckit_ceremony: [specify, clarify, plan, analyze, checklist, tasks] # heavy run, once per story per repo
53
+ # Step B (sdlc-implement) — the light per-task loop. One atomic task = one branch = one PR/MR.
54
+ branch_convention: "feat/<story-id>-<task-id>-<short-slug>" # e.g. feat/EP-istifta-inquiries-S01-T01-create-inquiry
55
+ commit_task_trailer: "Task: <story-id>-<task-id>" # final commit trailer; anchors the spec-link check (Step C)
56
+ contract_change_trailer: "Contract-Change: yes" # ONLY when the locked contract surface is touched (routes back to architecture gate)
57
+ # Commit subject + PR/MR title style (Conventional Commits — see CONTRIBUTING.md). PRs are squash-merged,
58
+ # so the title becomes the subject; both follow one rule. Type lowercase; description lowercase + imperative
59
+ # + no trailing period; proper nouns/acronyms keep their case.
60
+ commit_subject_style: "<type>: <lowercase imperative description, no trailing period>" # types: feat|fix|docs|refactor|test|perf|build|ci|chore|revert
61
+ pr_title_style: same_as_commit_subject # one atomic task = one branch = one PR/MR; title defaults to the task's commit subject
62
+ # Commit ownership + per-commit AI co-author (sdlc-implement installs the .gitmessage template).
63
+ # The human git author OWNS the commit; the assisting AI is recorded per-commit as a Co-Authored-By
64
+ # trailer, chosen by the owner from `ai_coauthor.allowed`. The AI is never the author. `none` is an
65
+ # explicit human-only choice. Trailer order is Task: -> Contract-Change: (if any) -> Co-Authored-By:.
66
+ commit_owner: git_author
67
+ ai_coauthor:
68
+ trailer: "Co-Authored-By"
69
+ required: false # the trailer is optional; owners pick `none` for human-only commits
70
+ allowed:
71
+ - { id: claude, name: "Claude", email: "noreply@anthropic.com" }
72
+ - { id: copilot, name: "GitHub Copilot", email: "copilot@users.noreply.github.com" }
73
+ - { id: cursor, name: "Cursor", email: "noreply@cursor.com" }
74
+ - { id: coderabbit, name: "CodeRabbit", email: "noreply@coderabbit.ai" }
75
+ - { id: none, name: "(no AI assistance)", email: "" }
76
+ # Step C (sdlc-checks) — the three CI gates that must pass before merge. CI-agnostic bash in checks/.
77
+ gates: [spec-link, contract-check, build-test-lint]
78
+ ci_platforms: [github, gitlab] # both wired from skills/sdlc-checks/templates/; GitHub is this product repo's platform
79
+ contract_surface_glob: "specs/*/contracts/**" # the repo's quoted contract slice; a diff here needs Contract-Change + a re-locked contract
80
+ # Step D (sdlc-pr-template) — platform-matched PR/MR template + risk routing.
81
+ pr_templates:
82
+ github: ".github/pull_request_template.md"
83
+ gitlab: ".gitlab/merge_request_templates/Default.md"
84
+ hub: # front-half artifact-review PR/MR on the product hub (sdlc-pr-template repo:hub)
85
+ github: ".github/pull_request_template.md"
86
+ gitlab: ".gitlab/merge_request_templates/Default.md"
87
+ risk_levels: [low, medium, high] # high (or a contract/auth/payments surface) routes to domain owners (sdlc-review-gate escalation)
88
+ # Step E (sdlc-ship) — AI review (advisory) + engineer review (the human gate) + ship.
89
+ ai_review: coderabbit # advisory first pass; never the authority (.coderabbit.yaml)
90
+ build_log: "epics/EP-<slug>/.sdlc/build-log.json" # append-only ship ledger (back-half analogue of approvals.json)
91
+ story_build_states: [in-build, shipped] # in-build = some tasks shipped; shipped = all tasks in tasks.md shipped
92
+ # Backfill (sdlc-backfill) — specs for already-built features in an existing repo.
93
+ backfill:
94
+ tool: repomix # the one true CLI subprocess: `npx repomix@latest` (Phase 0 / RESEARCH-NOTES §3)
95
+ pack_flags: "--compress --include <globs> --include-logs --style markdown" # one feature at a time; Secretlint by default
96
+ spec_location: "specs/backfill/<feature>/spec.md" # draft (verified: false) until a human approves
97
+ gate_scope: touched-features # block a change only until the features it touches have approved specs
98
+
99
+ # Code context (sdlc-connect-repos) — the front/"brain" phases are made code-aware. Code repos are
100
+ # connected to the product hub once (or any time a new repo is added), and an AI-readable picture of
101
+ # each is cached so epic/architecture/ui/stories consider what already exists in the code. The product
102
+ # repo is the FRONT-PHASE TOOLCHAIN HUB: repomix (and Impeccable) are installed/run here and target the
103
+ # connected code repos by path — code repos need no install for this. (The build-half CI gates are the
104
+ # exception: they live INSIDE each code repo and run in that repo's CI — see build.gates above.)
105
+ code_context:
106
+ registry: "{project-root}/.sdlc/repos.json" # project-wide repo registry (NOT per-epic)
107
+ cache_dir: "{project-root}/.sdlc/code-context" # per-repo pack.md + code-map.md live here
108
+ tools: [repomix, impeccable] # front-phase toolchain, installed in the product hub
109
+ pack_flags: "--compress --include-logs --style markdown" # reuse backfill flags; Secretlint by default
110
+ staleness: head-sha # stale when a repo's HEAD != registry syncedHead
111
+ refresh: human # a stale repo is a HUMAN decision: front-phase
112
+ # skills FLAG it and stop (pointing at
113
+ # `sdlc repo refresh <repo>`) — they never silently
114
+ # re-pack. `sdlc repo list` shows fresh/stale;
115
+ # `sdlc repo refresh` and `sdlc check --fix` re-pack.
116
+ load_in_front_phases: true # epic, architecture, ui-design, stories read the maps
117
+ # Auth: `sdlc-connect-repos` clones/fetches as the LOCAL user (SSH key or git credential helper),
118
+ # works for both github and gitlab (and self-hosted), and stores NO tokens in the registry.
119
+ platforms: [github, gitlab]
120
+
121
+ # Hub platform + front-half review bridge (sdlc-connect-repos `detect-hub`; sdlc-review-gate + sdlc-hub-bridge).
122
+ # The product hub is itself a git repo on a platform. With the bridge enabled, the front-half review/
123
+ # comment/approval cycle runs through a real PR/MR on the hub: a review PR is opened per artifact, reviewers
124
+ # approve/comment on the platform (their own gh/glab auth — NO stored tokens), and `sdlc-review-gate action: sync`
125
+ # pulls that state into the file ledger (approvals.json/reviews/*.md) and runs the UNCHANGED gate predicate.
126
+ # The file ledger stays the source of truth. Degrades to the file-only gate when there is no platform / no CLI.
127
+ hub:
128
+ config: "{project-root}/.sdlc/hub.json" # hub platform + reviewer roster (committed, like repos.json)
129
+ pr_ledger: "{project-root}/epics/EP-<slug>/.sdlc/hub-prs.json" # per-step review-PR record (sibling of approvals.json)
130
+ bridge: true # master enable; false => file-only front gates everywhere
131
+ platforms: [github, gitlab] # detected from the hub's own `git remote get-url origin`
132
+ artifact_branch: "review/EP-<slug>/<artifact-base>" # branch a review PR is opened on, per artifact
133
+ # roster (in hub.json) maps a platform login -> sdlc name + role; domain-owners are DERIVED from
134
+ # repos.json (a roster name that equals a repo's domain_owner owns that repo's domain) — never duplicated.
135
+
136
+ # Phase 4 (automation) — the SECOND dial made real. Until Phase 4 nothing read this dial; the
137
+ # orchestrator (sdlc-run) now does. Governing rule: automation is EARNED per step, with trust-log
138
+ # evidence, and is reversible in one move (phase-4-build-plan.md §"one principle"). Front states are
139
+ # never listed in back_steps and can never be flipped — the engineer keeps authority over decisions.
140
+ automation:
141
+ # The only steps that MAY be automated, safest-end first (phase-4-build-plan.md build order).
142
+ # Phase 4a ships the engine + earns `checks` (Step B). `tasks`/`implement` advance are Phase 4b.
143
+ back_steps: [spec, tasks, implement, checks]
144
+ default: human_approve # every back step starts manual; machine_advance must be earned
145
+ # A back step is a CANDIDATE for machine_advance only once its trust-log slice clears this bar.
146
+ # "It seems fine" is not evidence (phase-4-build-plan.md §"Explicitly NOT").
147
+ trust_threshold:
148
+ min_runs: 5 # at least this many recorded runs at the step
149
+ min_approved_unchanged: 0.8 # >= this fraction "approved-unchanged" over those runs
150
+ # Hard lock — the dial-setter REFUSES machine_advance for these, regardless of trust evidence.
151
+ # The front authoring steps (already locked:true in state.json; analysis is optional) + the human
152
+ # merge gate.
153
+ locked_steps: [analysis, epic, architecture, ui-design, stories, engineer-review]
154
+ # Kill switch (phase-4-build-plan.md §Safety): true => every step forced to human_approve
155
+ # system-wide, no per-step edits. One line, instantly reversible. Toggle via `sdlc-run action: kill`.
156
+ kill_switch: false
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Install the hand-authored `sdlc` BMAD module into the IDE skill dirs.
3
+ #
4
+ # Source of truth: {project-root}/skills/ (survives `bmad-method` updates).
5
+ # This script copies the sdlc-* skills into the four IDE locations the BMAD installer uses,
6
+ # and registers the module under _bmad/sdlc/. Re-run after any `bmad-method install/update`.
7
+ #
8
+ # Usage: bash skills/sdlc/install.sh
9
+ set -euo pipefail
10
+
11
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12
+ cd "$ROOT"
13
+
14
+ SKILLS=(sdlc-author-analysis sdlc-author-epic sdlc-author-architecture sdlc-author-ui sdlc-author-stories sdlc-connect-repos sdlc-spec sdlc-implement sdlc-checks sdlc-pr-template sdlc-review-comments sdlc-hub-bridge sdlc-ship sdlc-backfill sdlc-run sdlc-review-gate sdlc-status)
15
+
16
+ echo "Installing sdlc module from $ROOT/skills ..."
17
+
18
+ # 1. Folder-per-skill IDEs: .claude, .agents, .zencoder
19
+ for ide in .claude .agents .zencoder; do
20
+ for s in "${SKILLS[@]}"; do
21
+ if [ -d "skills/$s" ]; then
22
+ # Remove any stale install first, then copy the whole skill folder
23
+ # (SKILL.md + references/). rm -rf guarantees no double-nesting on cp -R.
24
+ rm -rf "$ide/skills/$s"
25
+ cp -R "skills/$s" "$ide/skills/$s"
26
+ echo " $ide/skills/$s"
27
+ fi
28
+ done
29
+ done
30
+
31
+ # 2. opencode: single flat command file per skill (SKILL.md -> commands/<name>.md).
32
+ # Note: opencode commands are flat files, so the skills' references/ folders are
33
+ # NOT carried here. Each SKILL.md is self-contained for its operative rules
34
+ # (schema, gate predicate); references/ only adds supplementary detail.
35
+ if [ -d ".opencode/commands" ]; then
36
+ for s in "${SKILLS[@]}"; do
37
+ if [ -f "skills/$s/SKILL.md" ]; then
38
+ cp "skills/$s/SKILL.md" ".opencode/commands/$s.md"
39
+ echo " .opencode/commands/$s.md (references/ not included — flat command)"
40
+ fi
41
+ done
42
+ fi
43
+
44
+ # 3. Register the module under _bmad/sdlc/ (regenerable; _bmad/ is rebuilt on update).
45
+ mkdir -p "_bmad/sdlc"
46
+ cp "skills/sdlc/config.yaml" "_bmad/sdlc/config.yaml"
47
+ cp "skills/sdlc/module-help.csv" "_bmad/sdlc/module-help.csv"
48
+ echo " _bmad/sdlc/{config.yaml,module-help.csv}"
49
+
50
+ echo "Done. Skills available: ${SKILLS[*]}"
51
+ echo "Note: _bmad/ is regenerated by BMAD updates — re-run this script afterwards."
@@ -0,0 +1,17 @@
1
+ module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs
2
+ SDLC Workflow,sdlc-author-epic,Author Epic,AE,"Front state 1: shape an idea with analyst then pm into epic.md; assign EP-<slug> ID and seed .sdlc state. Never auto-advances.",,{idea: one-line feature idea},1-front,,sdlc-review-gate,true,epics/EP-<slug>/,epic.md state.json
3
+ SDLC Workflow,sdlc-review-gate,Team Review Gate,RG,"Reusable review+approve gate for all four reviews. Shares an artifact for review, records comments and approvals as files, enforces owner + 1 reviewer (escalates on contract/auth/payments; per-repo routing for stories), advances state only when approved.",,{artifact: file under the epic} {action: open|comment|approve|advance},1-front,,,true,epics/EP-<slug>/reviews/,reviews/*.md approvals.json state.json
4
+ SDLC Workflow,sdlc-author-architecture,Author Architecture,AA,"Front state 3: with the architect author architecture.md and the locked contract.md; hash-lock the contract surface. Never auto-advances.",,{epic: EP-<slug>},1-front,sdlc-review-gate,sdlc-review-gate,true,epics/EP-<slug>/,architecture.md contract.md contract-lock.json state.json
5
+ SDLC Workflow,sdlc-author-ui,Author UI Design,AU,"Front state 5: with the ux-designer author ui-design.md and DESIGN.md, driving Impeccable slash-commands when installed. Never auto-advances.",,{epic: EP-<slug>},1-front,sdlc-review-gate,sdlc-review-gate,true,epics/EP-<slug>/,ui-design.md DESIGN.md state.json
6
+ SDLC Workflow,sdlc-author-stories,Author Stories,AS,"Front state 7: with the pm break the epic into repo-tagged stories with stable EP-<slug>-S0N IDs, one file each under stories/. Never auto-advances.",,{epic: EP-<slug>},1-front,sdlc-review-gate,sdlc-review-gate,true,epics/EP-<slug>/stories/,stories/EP-<slug>-S0N.md state.json
7
+ SDLC Workflow,sdlc-connect-repos,Connect Code Repos,CR,"Setup/maintenance: connect code repos to the product hub so the front/brain phases are code-aware. Registers each repo (GitHub or GitLab, local-user auth, no stored tokens) in .sdlc/repos.json and caches a Repomix pack + a lightweight code-map (existing endpoints/events/data-models/modules, secret-scanned). Idempotent and refreshable; staleness tracked by HEAD sha. Run at setup or any time a new repo is added. Not a gated state — never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {repo: <name>} {path: <path-or-absolute>} {git_url: <ssh-or-https>} {domain_owner: <who>},0-setup,,sdlc-author-epic,false,.sdlc/,repos.json code-context/<repo>/pack.md code-context/<repo>/code-map.md
8
+ SDLC Workflow,sdlc-spec,Author Spec,SP,"Build-half Step A: for one ready-for-build story and one of its repos, run the heavy Spec Kit ceremony once (specify→clarify→plan→analyze→checklist→tasks) inside that code repo, writing specs/<story-id>/ in Spec Kit's layout (drives /speckit.* when installed, else hand-authors and records speckit: not-installed). References the locked contract; never re-invents the surface. Writes link.md back to the story. Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <one of story.repos>},3-build,sdlc-review-gate,,false,demo-repos/<repo>/specs/<story-id>/,spec.md research.md data-model.md contracts/ plan.md tasks.md link.md
9
+ SDLC Workflow,sdlc-implement,Implement Task,IM,"Build-half Step B: with the dev lens, implement ONE atomic task from a story's tasks.md as a small diff (<=3 files) on its own branch feat/<story>-<task>-<slug> in the code repo. Diff stays inside the task's declared files (flag and STOP if it grows beyond them). Commit ends with a Task: <story>-<task> trailer; add Contract-Change: yes only when the locked contract surface is touched (routes back to the architecture gate). Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <one of story.repos>} {task: T0N},3-build,sdlc-spec,,false,demo-repos/<repo>/,branch+commit per atomic task
10
+ SDLC Workflow,sdlc-checks,Check Gates,CK,"Build-half Step C: wire and run the three production-safety CI gates on a code repo — spec-link (every change links a real story/spec via its Task trailer), contract-check (a contract-surface change without Contract-Change + an updated re-locked contract FAILS and routes back to the architecture gate), and build/test/lint. CI-agnostic bash invoked by GitHub Actions and GitLab CI. Blocking in CI; the human still owns the merge. Never auto-advances.",,{repo: <one of an epic's repos>} {action: wire|run} {base: target branch},3-build,sdlc-implement,,false,demo-repos/<repo>/,checks/*.sh .github/workflows/sdlc-checks.yml .gitlab-ci.yml
11
+ SDLC Workflow,sdlc-pr-template,PR/MR Template,PT,"Build-half Step D: detect a code repo's platform and commit the matching PR/MR template (.github/pull_request_template.md or .gitlab/merge_request_templates/Default.md) with an Impact & Risk block. A high risk level (or a touched contract/auth/payments surface) routes the review to domain owners — the same escalation sdlc-review-gate applies. risk-route.sh prints the required reviewers from a PR body. Never auto-advances.",,{repo: <one of an epic's repos>} {action: wire|route} {body: PR description file},3-build,sdlc-checks,,false,demo-repos/<repo>/,.github/pull_request_template.md .gitlab/merge_request_templates/Default.md checks/risk-route.sh
12
+ SDLC Workflow,sdlc-review-comments,Review Comment Templates,RC,"Install platform-matched PR/MR review-comment scaffolds (committed REVIEW_COMMENTS.md) into a code repo or the product hub, so reviewers leave structured, attributable feedback whose **name (role)** headers map cleanly into the SDLC file ledger. GitHub Saved Replies / GitLab comment templates are per-user, so a committed doc is the repo-level mechanism. Never auto-advances.",,{repo: <one of an epic's repos | hub>} {action: wire},3-build,,,false,<repo>/.github|.gitlab/,REVIEW_COMMENTS.md
13
+ SDLC Workflow,sdlc-hub-bridge,Hub Review Bridge,HB,"The templated PR/MR bridge for the front-half review gate: when the product hub has a platform (.sdlc/hub.json), open a review PR/MR on the hub for an authored artifact, set required reviewers/labels from the routing rule, and provide the read-only gh/glab recipes sdlc-review-gate's sync uses to pull platform comments + approvals into the file ledger. Local-user auth, no stored tokens; file ledger stays the source of truth; degrades to file-only when no platform/CLI. Never auto-advances.",,{epic: EP-<slug>} {artifact: epic.md|architecture.md|ui-design.md|stories/} {action: open|route},1-front,sdlc-review-gate,sdlc-review-gate,false,epics/EP-<slug>/.sdlc/,hub-prs.json
14
+ SDLC Workflow,sdlc-ship,Review & Ship,SH,"Build-half Step E: wire an advisory AI first-pass (CodeRabbit) on the PR, record the human engineer review with the same human_approve discipline as the front gates (owner + 1 reviewer, escalating to domain owners on high risk / contract / auth / payments), and on merge record the ship in epics/<epic>/.sdlc/build-log.json and update the story state. AI review is advisory, never the authority; the human owns the merge. Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {task: T0N} {repo: <repo>} {action: ai-review|approve|ship},3-build,sdlc-pr-template,,false,epics/EP-<slug>/.sdlc/,build-log.json story-status
15
+ SDLC Workflow,sdlc-backfill,Backfill Specs,BF,"Build-half Step G: generate specs for already-built features in an existing repo. Confirm Repomix (npx repomix CLI), pack ONE feature (compress + git logs, secret-scan), feed to AI with a 'describe what exists, do not invent' prompt, write a DRAFT spec marked verified: false. Human approval (reuse sdlc-review-gate) makes it real. Boundary auto-proposed and human-confirmed. A change is blocked only until the features it touches have approved specs. Never auto-advances.",,{repo: <repo>} {feature: <name + globs>} {action: pack|draft|approve|gate},3-build,,,false,demo-repos/<repo>/specs/backfill/<feature>/,spec.md backfill-check.sh
16
+ SDLC Workflow,sdlc-run,Run (Automation),RN,"Phase 4 orchestrator: drive a story's back-half loop (spec→tasks→implement→checks) in one code repo, reading each step's automation dial from build-state — on machine_advance it advances on its own, on human_approve it stops for a human. Records every run in the trust log. Realizes Step B (a clean checks pass auto-advances to the engineer review when earned; any FAIL / scope overrun / contract touch HALTS). set-dial earns/reverts a step's automation (machine_advance gated by the trust threshold; front states and engineer-review refused); kill/unkill toggles the system-wide kill switch. Front states and the human merge gate never auto-advance.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <repo>} {action: run|set-dial|kill|unkill} {step: <back-step>} {to: human_approve|machine_advance},4-automate,sdlc-spec,sdlc-ship,false,epics/EP-<slug>/.sdlc/,build-state/<story-id>.json trust-log.json
17
+ SDLC Workflow,sdlc-status,SDLC Status,SS,"Read-only: print the full front-state chain, per-step dials, contract lock, story repo tags, and pending approvals at the active gate. For stories in the build half, also print each back-half step's automation dial and status, the trust record (runs / % approved-unchanged / earned vs gathering evidence), and the system-wide kill-switch state.",,{epic: EP-<slug>},1-front,,,false,,
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: sdlc-author-analysis
3
+ description: 'Optional front state 1 of the gated SDLC. With the analyst, pressure-test a feature idea and write the discovery brief into analysis.md. Assigns the EP-<slug> ID and seeds .sdlc/ state (the 10-step chain that puts analysis before epic). Never auto-advances — hands off to the team review gate. Optional: if skipped, the epic step does this shaping inline. Use when the user says "analyse the idea", "start with analysis", or "author the analysis".'
4
+ ---
5
+
6
+ # SDLC — Author Analysis (optional front state 1)
7
+
8
+ **Goal:** Produce a human-authored, AI-assisted `analysis.md` — the analyst's discovery brief that
9
+ shapes the feature **before** the epic — assign its stable `EP-<slug>` ID, and initialise the per-epic
10
+ state machine in `.sdlc/`. This is a **front state**: human-authored with AI assist and **never
11
+ auto-advances**. When the analysis is drafted, control passes to `sdlc-review-gate`.
12
+
13
+ This step is **optional**. When it runs, it is the entry point: it assigns the ID and seeds the
14
+ **10-step** chain (`analysis` before `epic`). When it is **skipped**, `sdlc-author-epic` does the same
15
+ analyst shaping inline and seeds the **8-step** chain — no behaviour change for teams that skip it.
16
+
17
+ This skill enforces the build plan's core rules: all state lives in files; IDs are generated by the
18
+ engine (never typed by hand); front steps are locked to `human_approve`.
19
+
20
+ ## Conventions
21
+
22
+ - `{project-root}` resolves from the project working directory.
23
+ - Analysis artifacts live under `{project-root}/epics/EP-<slug>/` (build plan §6).
24
+ - Speak in the configured `communication_language`; write documents in `document_output_language`.
25
+
26
+ ## On Activation
27
+
28
+ ### Step 1 — Get the idea
29
+ Ask the user for a one-line feature idea if not provided. If `.sdlc/state.json` already exists for the
30
+ target epic, analysis was already seeded (or the epic is past it) — stop and point the user at
31
+ `sdlc-status`. The entry point seeds state exactly once.
32
+
33
+ ### Step 2 — Shape the idea (assist: analyst)
34
+ Adopt the **analyst** lens (`bmad-agent-analyst`, Mary) to pressure-test the idea in depth: who is the
35
+ user, what problem, what already exists, what options are on the table, what signals success, what is
36
+ out of scope, and what the recommendation to the epic is. This is the discovery the epic will build on.
37
+
38
+ ### Step 2b — Load existing-code context (make the brain code-aware)
39
+ Read the registry `{project-root}/.sdlc/repos.json` (`config.yaml` `code_context`). For **every
40
+ connected repo** (the epic's `repos` are not chosen yet), load the lightweight code-map
41
+ `{project-root}/.sdlc/code-context/<repo>/code-map.md`. Use it so the analysis's **Current state** and
42
+ **Options** reflect what is **already built** — reference existing behaviour rather than re-proposing it.
43
+
44
+ - **Greenfield-safe:** if `repos.json` is absent or empty, note "no repos connected" and proceed.
45
+ - **Staleness:** if a repo's current HEAD (`git -C <path> rev-parse HEAD`) ≠ its registry `syncedHead`,
46
+ warn and suggest `sdlc repo refresh <repo>` (a human decision — flag and stop, never auto-refresh);
47
+ stamp `code-context: stale` in the frontmatter.
48
+ - **Traceability:** record which maps you loaded in the analysis frontmatter `code-context:` field.
49
+
50
+ ### Step 3 — Generate the Epic ID (engine-assigned, never by hand)
51
+ Derive `EP-<slug>` where `slug` is **2–4 lowercase words joined by hyphens**, drawn from the idea
52
+ (e.g. `EP-istifta-inquiries`). Lowercase except the fixed `EP` prefix. **The ID is assigned once and
53
+ never renamed** — renaming breaks every downstream link (build plan §6b). Check
54
+ `{project-root}/epics/` for collisions; if the slug exists, append a distinguishing word.
55
+
56
+ ### Step 4 — Open the authoring branch
57
+ Open the analysis authoring branch `analysis/EP-<slug>` per the shared procedure
58
+ (`references/state-schema.md` → "Authoring branches"): git-safe (skip with a note if `{project-root}`
59
+ is not a git work tree), check out the branch if it exists, else create it from the hub's default
60
+ branch. Author and commit `analysis.md` on it. This is **distinct** from the bridge's `review/…` branch.
61
+
62
+ ### Step 5 — Write the analysis (assist: analyst)
63
+ Write `{project-root}/epics/EP-<slug>/analysis.md` using EXACTLY this template:
64
+
65
+ ```markdown
66
+ ---
67
+ id: EP-<slug>
68
+ artifact: analysis
69
+ status: draft
70
+ owner:
71
+ code-context: { repos: [], loaded: <YYYY-MM-DD or none> } # which code-maps informed this analysis (Step 2b)
72
+ ---
73
+
74
+ ## Problem
75
+ <!-- the problem this feature addresses, who feels it -->
76
+
77
+ ## Users / personas
78
+ ## Current state (what already exists)
79
+ <!-- code-aware, from the code-maps loaded in Step 2b -->
80
+
81
+ ## Options / opportunities
82
+ ## Risks & constraints
83
+ ## Success signals
84
+ ## Recommendation (hand-off to the epic)
85
+ <!-- the framing the epic should carry forward -->
86
+ ```
87
+
88
+ Fill the body with the user; leave `owner` for the user to set.
89
+
90
+ ### Step 6 — Seed the state machine
91
+ Create `{project-root}/epics/EP-<slug>/.sdlc/state.json` describing the full **10-step** front-state
92
+ sequence (analysis before epic), all steps defaulting to `automation: human_approve`, with every
93
+ authoring step **locked**. Use this exact shape (see `references/state-schema.md`):
94
+
95
+ ```json
96
+ {
97
+ "epicId": "EP-<slug>",
98
+ "createdAt": "<YYYY-MM-DD>",
99
+ "currentStep": "analysis-review",
100
+ "steps": [
101
+ { "id": "analysis", "type": "author", "artifact": "analysis.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "done", "risk_tags": [] },
102
+ { "id": "analysis-review", "type": "review+approve", "artifact": "analysis.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "in_review", "risk_tags": [] },
103
+ { "id": "epic", "type": "author", "artifact": "epic.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
104
+ { "id": "epic-review", "type": "review+approve", "artifact": "epic.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
105
+ { "id": "architecture", "type": "author", "artifact": "architecture.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
106
+ { "id": "architecture-review","type": "review+approve", "artifact": "architecture.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": ["contract"] },
107
+ { "id": "ui-design", "type": "author", "artifact": "ui-design.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
108
+ { "id": "ui-design-review", "type": "review+approve", "artifact": "ui-design.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
109
+ { "id": "stories", "type": "author", "artifact": "stories/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
110
+ { "id": "stories-review", "type": "review+approve", "artifact": "stories/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ Notes:
116
+ - `analysis-review` carries no `risk_tags` — it is the **base** rule (owner + 1 reviewer).
117
+ - `architecture-review` carries `risk_tags: ["contract"]` so the gate escalates it by default
118
+ (build plan §4): the contract review needs domain owners, not just owner + 1.
119
+ - Also create an empty approvals ledger `{project-root}/epics/EP-<slug>/.sdlc/approvals.json`
120
+ and an empty comments ledger `{project-root}/epics/EP-<slug>/.sdlc/comments.json`, each containing
121
+ `[]`, and the `reviews/` directory.
122
+
123
+ ### Step 7 — Stop at the gate (do NOT advance)
124
+ Report: epic ID, the path to `analysis.md`, and that the next action is **review** via
125
+ `sdlc-review-gate` (base rule: owner + 1 reviewer). **Never mark the analysis-review step approved
126
+ here** — only real reviewers do that through the gate. Front states do not auto-advance. When the
127
+ analysis gate passes, control moves to `sdlc-author-epic`, which reads `analysis.md` as input. When the
128
+ hub has a platform, the gate opens a review PR on the hub (via `sdlc-hub-bridge`) and
129
+ `sdlc-review-gate action: sync` pulls platform approvals/comments into the ledger; otherwise the review
130
+ is recorded file-only.
131
+
132
+ ## Reference
133
+ - State schema, the two chain shapes, and the authoring-branch procedure:
134
+ `../sdlc-author-epic/references/state-schema.md`.
135
+ - The epic step that consumes this analysis: `../sdlc-author-epic/SKILL.md`.
136
+ - Connecting code repos + the code-context the brain reads: `../sdlc-connect-repos/SKILL.md`.