yadflow 2.10.0 → 2.11.0

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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- # [2.10.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.9.0...v2.10.0) (2026-06-15)
1
+ # [2.11.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.10.0...v2.11.0) (2026-06-15)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * **checks:** cap jest/vitest test workers in connected-repo CI gates ([#66](https://github.com/abdelrahmannasr/yadflow/issues/66)) ([7a16d51](https://github.com/abdelrahmannasr/yadflow/commit/7a16d51eb135c3240d3e94012f844c8b74210bd9))
6
+ * add yad-sync-repos switch every connected repo to its default branch + ff pull ([#67](https://github.com/abdelrahmannasr/yadflow/issues/67)) ([0abdcdf](https://github.com/abdelrahmannasr/yadflow/commit/0abdcdf80c8a0a6bfd8c94129fde90f1d35ec365)), closes [#30](https://github.com/abdelrahmannasr/yadflow/issues/30)
7
7
 
8
8
  # [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
9
9
 
package/README.md CHANGED
@@ -100,6 +100,7 @@ with `npx` from your **product hub** repo — no clone needed.
100
100
  | `yad open-pr [--repo <name>]` | Open a code-repo **task** PR/MR from the repo's platform template (build half). |
101
101
  | `yad ship --type <t> -m <subject>` | Commit **and** open the task PR/MR in one step (`yad commit` then `yad open-pr`). |
102
102
  | `yad repo list` / `yad repo refresh [name]` | List connected repos as **fresh / stale**, and re-pack a stale one — staleness is now an explicit human decision, never an automatic skill side-effect. |
103
+ | `yad repo sync [name]` | Switch every connected repo to its **default branch** and fast-forward it from origin (one or all). Dirty repos are skipped, never overwritten; fast-forward only. |
103
104
  | `npx yadflow --version` | Print the installed CLI version. |
104
105
 
105
106
  Flags: `--dir <path>` targets a project other than the cwd; `--force` re-copies unchanged files (or
@@ -134,7 +135,7 @@ simultaneous advancements can be lost; the next event or scheduled sweep re-sync
134
135
  ### What `setup` walks you through (10 steps)
135
136
 
136
137
  1. **Preflight** — confirm the hub is a git repo (offers `git init`); check `git`/`node`/`npx`.
137
- 2. **Install the module** — copy all 29 `yad-*` skills into the IDE skill dirs you pick
138
+ 2. **Install the module** — copy all 30 `yad-*` skills into the IDE skill dirs you pick
138
139
  (`.claude/`, `.agents/`, `.zencoder/`, `.opencode/`) and register `_bmad/sdlc/`.
139
140
  3. **Hub platform & roster** — detect GitHub/GitLab from the remote; record reviewers → `.sdlc/hub.json`.
140
141
  Edit the roster any time afterwards with `yad roster` (no need to re-run the whole wizard).
@@ -191,7 +192,7 @@ with a fix-it hint per finding. Failures carry stable, greppable codes, also pri
191
192
 
192
193
  Filing a bug? Attach `yad doctor --json` — it contains no secrets (names, paths, and check results only).
193
194
 
194
- ## Agent skills (all 29)
195
+ ## Agent skills (all 30)
195
196
 
196
197
  The CLI **installs and wires** the module; the skills below are the **agents you invoke by name** in your
197
198
  AI IDE (e.g. *“run `yad-epic`”*) to actually do the work. State lives in files you can also edit
@@ -204,6 +205,11 @@ directly. Each skill stops at a gate and never auto-advances unless a step has *
204
205
  `.sdlc/repos.json`, then caches an AI-readable picture of each — a compressed Repomix pack and a
205
206
  lightweight code-map (existing endpoints/events/data-models/modules), secret-scanned. Idempotent and
206
207
  refreshable; staleness tracked by HEAD sha.
208
+ - **`yad-sync-repos`** — Brings every connected repo up to date in one shot: switches each repo in
209
+ `.sdlc/repos.json` to its `default_branch` and fast-forwards it from origin (local-user git, no stored
210
+ tokens). A working-tree-only maintenance op — never a gate, never writes the registry. A dirty repo is
211
+ skipped and reported (never overwritten); a diverged branch is left for manual resolution. Drives
212
+ `yad repo sync [name]`.
207
213
  - **`yad-connect-design`** — Connects a design tool (Figma-first, pluggable) so the UI step can
208
214
  materialize the actual feature design (mobile screens / web pages) inside it, alongside the Markdown.
209
215
  Records the tool + project/file references in `.sdlc/design.json` (local-user / MCP-session auth, no
package/cli/manifest.mjs CHANGED
@@ -10,7 +10,7 @@ import { readFileSync } from 'node:fs';
10
10
  const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
11
11
  export const VERSION = version;
12
12
 
13
- // The 29 hand-authored yad-* skills (mirrors skills/sdlc/install.sh).
13
+ // The 30 hand-authored yad-* skills (mirrors skills/sdlc/install.sh).
14
14
  export const SKILLS = [
15
15
  'yad-analysis',
16
16
  'yad-epic',
@@ -19,6 +19,7 @@ export const SKILLS = [
19
19
  'yad-stories',
20
20
  'yad-test-cases',
21
21
  'yad-connect-repos',
22
+ 'yad-sync-repos',
22
23
  'yad-connect-design',
23
24
  'yad-connect-testing',
24
25
  'yad-connect-learning',
package/cli/repo.mjs CHANGED
@@ -1,8 +1,10 @@
1
- // `yad repo list|refresh` — connected-repo staleness as an explicit HUMAN decision.
2
- // Skill steps no longer silently repack a stale repo; they flag it and point here. (`yad check --fix`
3
- // still refreshes too it is also human-invoked.)
1
+ // `yad repo list|refresh|sync` — connected-repo maintenance as explicit HUMAN decisions.
2
+ // list/refresh: staleness of the cached code-context pack (HEAD != syncedHead). Skill steps no longer
3
+ // silently repack a stale repo; they flag it and point here. (`yad check --fix` refreshes too — also
4
+ // human-invoked.) sync: switch every connected repo to its registry default_branch and fast-forward
5
+ // it to origin — a working-tree-only op (it never writes the registry; a dirty tree is skipped).
4
6
  import path from 'node:path';
5
- import { c, log, ok, info, warn, hand, fail, readJSON, writeJSON } from './lib.mjs';
7
+ import { c, log, ok, info, warn, hand, fail, readJSON, writeJSON, run } from './lib.mjs';
6
8
  import { PROJECT_FILES } from './manifest.mjs';
7
9
  import { gitHead, packRepo } from './setup.mjs';
8
10
 
@@ -18,6 +20,18 @@ function staleness(root, repo) {
18
20
  return { head, stale: !!stale, unknown: !head };
19
21
  }
20
22
 
23
+ // ---- git helpers for `sync` (local-user auth only — never embed credentials) ----
24
+ const git = (cwd, ...args) => run('git', args, { cwd });
25
+ const hasRemote = (cwd, remote = 'origin') => git(cwd, 'remote', 'get-url', remote).ok;
26
+ const isDirty = (cwd) => { const r = git(cwd, 'status', '--porcelain'); return r.ok && r.stdout.length > 0; };
27
+ const currentBranch = (cwd) => { const r = git(cwd, 'rev-parse', '--abbrev-ref', 'HEAD'); return r.ok ? r.stdout : null; };
28
+ // Registry default_branch wins; else origin/HEAD; else 'main'.
29
+ function defaultBranch(cwd, repo) {
30
+ if (repo.default_branch) return repo.default_branch;
31
+ const r = git(cwd, 'symbolic-ref', '--short', 'refs/remotes/origin/HEAD');
32
+ return r.ok && r.stdout ? r.stdout.replace(/^origin\//, '') : 'main';
33
+ }
34
+
21
35
  export async function runRepo(root, { action = 'list', name, today } = {}) {
22
36
  const { regPath, registry } = load(root);
23
37
  if (!registry.repos.length) { warn('no repos registered (.sdlc/repos.json) — run `yad setup`'); return { repos: 0 }; }
@@ -55,7 +69,44 @@ export async function runRepo(root, { action = 'list', name, today } = {}) {
55
69
  return { refreshed };
56
70
  }
57
71
 
58
- fail(`unknown repo action: ${action} (list | refresh)`);
72
+ if (action === 'sync') {
73
+ const targets = name ? registry.repos.filter((r) => r.name === name) : registry.repos;
74
+ if (name && !targets.length) { fail(`unknown repo: ${name}`); process.exitCode = 1; return { synced: 0 }; }
75
+ log(c.bold('\nsync connected repos'));
76
+ let synced = 0, skipped = 0;
77
+ for (const repo of targets) {
78
+ const repoRoot = path.resolve(root, repo.path);
79
+ const tag = `${repo.name} ${c.dim(`(${repo.path})`)}`;
80
+ if (!gitHead(repoRoot)) { warn(`${tag} — not a git repo / HEAD unreadable — skipped`); skipped++; continue; }
81
+ if (isDirty(repoRoot)) { warn(`${repo.name} — ${c.yellow('dirty')} → SKIPPED (commit/stash first)`); skipped++; continue; }
82
+ const branch = defaultBranch(repoRoot, repo);
83
+ const remote = hasRemote(repoRoot);
84
+ if (remote) {
85
+ const f = git(repoRoot, 'fetch', 'origin', branch, '--prune');
86
+ if (!f.ok) { warn(`${repo.name} — fetch failed (${(f.stderr.split('\n')[0]) || 'auth?'}) — skipped`); skipped++; continue; }
87
+ }
88
+ if (currentBranch(repoRoot) !== branch) {
89
+ const co = git(repoRoot, 'checkout', branch);
90
+ if (!co.ok) { warn(`${repo.name} — cannot switch to ${branch} (${co.stderr.split('\n')[0] || 'no such branch'}) — skipped`); skipped++; continue; }
91
+ }
92
+ if (remote) {
93
+ const before = gitHead(repoRoot);
94
+ const m = git(repoRoot, 'merge', '--ff-only', `origin/${branch}`);
95
+ if (!m.ok) { warn(`${repo.name} — ${c.yellow('diverged')} on ${branch} → not fast-forwarded (resolve manually)`); skipped++; continue; }
96
+ ok(`${repo.name} ${c.dim('—')} ${before === gitHead(repoRoot) ? `already current on ${branch}` : `switched to ${branch}, pulled (ff)`}`);
97
+ } else {
98
+ ok(`${repo.name} ${c.dim('—')} on ${branch} (local-only, no remote)`);
99
+ }
100
+ synced++;
101
+ }
102
+ // A pulled repo's HEAD moves, so its cached code-context pack goes stale — point the human at refresh.
103
+ const staleCount = registry.repos.filter((r) => staleness(root, r).stale).length;
104
+ info(`synced ${synced}, skipped ${skipped}`);
105
+ if (staleCount) hand(`${staleCount} repo(s) now have a stale code-context pack — \`yad repo refresh\` to repack`);
106
+ return { synced, skipped, stale: staleCount };
107
+ }
108
+
109
+ fail(`unknown repo action: ${action} (list | refresh | sync)`);
59
110
  process.exitCode = 1;
60
111
  return {};
61
112
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yadflow",
3
- "version": "2.10.0",
4
- "description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo). A BMAD module + 29 yad-* skills.",
3
+ "version": "2.11.0",
4
+ "description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo). A BMAD module + 30 yad-* skills.",
5
5
  "type": "module",
6
6
  "author": "AbdelRahman Nasr",
7
7
  "license": "MIT",
@@ -118,6 +118,9 @@ code_context:
118
118
  # `yad repo refresh <repo>`) — they never silently
119
119
  # re-pack. `yad repo list` shows fresh/stale;
120
120
  # `yad repo refresh` and `yad check --fix` re-pack.
121
+ # `yad repo sync [<repo>]` (yad-sync-repos) switches every connected repo to its default_branch and
122
+ # fast-forwards it from origin before work starts — working-tree only (never writes the registry);
123
+ # a dirty repo is skipped, a diverged branch is left for manual resolution (fast-forward only).
121
124
  load_in_front_phases: true # epic, architecture, ui-design, stories read the maps
122
125
  # Auth: `yad-connect-repos` clones/fetches as the LOCAL user (SSH key or git credential helper),
123
126
  # works for both github and gitlab (and self-hosted), and stores NO tokens in the registry.
@@ -11,7 +11,7 @@ set -euo pipefail
11
11
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12
12
  cd "$ROOT"
13
13
 
14
- SKILLS=(yad-analysis yad-epic yad-architecture yad-ui yad-stories yad-test-cases yad-connect-repos yad-connect-design yad-connect-testing yad-connect-learning yad-connect-docs yad-docs yad-docs-overview yad-docs-sync yad-learn yad-spec yad-implement yad-checks yad-pr-template yad-review-comments yad-hub-bridge yad-commit yad-open-pr yad-ship yad-engineer-review yad-backfill yad-run yad-review-gate yad-status)
14
+ SKILLS=(yad-analysis yad-epic yad-architecture yad-ui yad-stories yad-test-cases yad-connect-repos yad-sync-repos yad-connect-design yad-connect-testing yad-connect-learning yad-connect-docs yad-docs yad-docs-overview yad-docs-sync yad-learn yad-spec yad-implement yad-checks yad-pr-template yad-review-comments yad-hub-bridge yad-commit yad-open-pr yad-ship yad-engineer-review yad-backfill yad-run yad-review-gate yad-status)
15
15
 
16
16
  echo "Installing sdlc module from $ROOT/skills ..."
17
17
 
@@ -6,7 +6,8 @@ SDLC Workflow,yad-architecture,Author Architecture,AA,"Front state 3: with the a
6
6
  SDLC Workflow,yad-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,yad-review-gate,yad-review-gate,true,epics/EP-<slug>/,ui-design.md DESIGN.md state.json
7
7
  SDLC Workflow,yad-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,yad-review-gate,yad-review-gate,true,epics/EP-<slug>/stories/,stories/EP-<slug>-S0N.md state.json
8
8
  SDLC Workflow,yad-test-cases,Author Test Cases,TC,"Front state 9 (PARALLEL, non-blocking): opens when the stories gate passes — the epic is already ready-for-build, so the build half can start at the same time the tester works here. With the test architect (Murat) author test-cases.md covering the approved stories, and — when a testing tool is connected (.sdlc/testing.json) — generate/link the actual automation tests in it, recording test-links.json; otherwise produce the test-case artifact only. Its review never moves currentStep off ready-for-build. Never auto-advances.",,{epic: EP-<slug>},1-front,yad-review-gate,yad-review-gate,true,epics/EP-<slug>/,test-cases.md test-links.json state.json
9
- SDLC Workflow,yad-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,,yad-epic,false,.sdlc/,repos.json code-context/<repo>/pack.md code-context/<repo>/code-map.md
9
+ SDLC Workflow,yad-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,,yad-sync-repos,false,.sdlc/,repos.json code-context/<repo>/pack.md code-context/<repo>/code-map.md
10
+ SDLC Workflow,yad-sync-repos,Sync Connected Repos,SR,"Setup/maintenance: bring every connected repo up to date in one shot — switch each repo in .sdlc/repos.json to its default_branch and fast-forward it from origin (local-user git, no stored tokens). Working-tree only; never a gate and never writes the registry. A dirty repo is skipped and reported (never overwritten); a diverged branch is left for manual resolution (fast-forward only). After pulling, a repo's cached pack goes stale — points the human at yad repo refresh.",,{action: sync} {repo: <name>},0-setup,yad-connect-repos,yad-connect-design,false,,(none — working-tree only)
10
11
  SDLC Workflow,yad-connect-design,Connect Design Tool,CD,"Setup/maintenance: connect a design tool (Figma-first, pluggable) to the product hub so the UI design step can materialize the actual feature design (mobile screens / web pages) inside it, alongside ui-design.md + DESIGN.md. Records the tool + project/file references in .sdlc/design.json (local-user / MCP-session auth, no stored tokens), detecting whether a design-tool MCP is available and degrading to markdown-only when absent. Idempotent and refreshable; one connection per project. Not a gated state — never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {tool: figma|pencil|none} {project_url: <team/project/file url>} {files: {web,mobile}},0-setup,,yad-ui,false,.sdlc/,design.json
11
12
  SDLC Workflow,yad-connect-testing,Connect Testing Tool,CT,"Setup/maintenance: connect a testing tool (Playwright-first, pluggable) to the product hub so the test-cases step can implement the actual automation tests in it, alongside test-cases.md. Records the tool + project/suite references in .sdlc/testing.json (local-user / MCP-session auth, no stored tokens), detecting whether a testing-tool MCP is available and degrading to artifacts-only when absent. Idempotent and refreshable; one connection per project. Not a gated state — never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {tool: playwright|cypress|pytest|none} {project_url: <project/config reference>} {suites: {<repo>}},0-setup,,yad-test-cases,false,.sdlc/,testing.json
12
13
  SDLC Workflow,yad-connect-learning,Connect Learning Tool,CL,"Setup/maintenance: connect a learning/tutoring tool (DeepTutor-first, pluggable) so the cross-cutting learning layer can tutor any team member, at any SDLC stage, in the context of what is being built. Records the tool + an optional grounded knowledge base in .sdlc/learning.json (local-user auth, no stored tokens), detecting whether the DeepTutor CLI is on PATH (a subprocess like Repomix, not an MCP) and degrading to harness-native tutoring when absent. Idempotent and refreshable; one connection per project. Not a gated state — never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {tool: deeptutor|none} {kb: <name>} {ground: true|false},0-setup,,yad-learn,false,.sdlc/,learning.json
@@ -1,14 +1,15 @@
1
1
  ---
2
2
  name: yad-commit
3
- description: 'Build-half helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer block (Task → Contract-Change Co-Authored-By), and an atomic-file guard (≤3 files). The human git author OWNS the commit; an assisting AI is recorded only as a Co-Authored-By footer, chosen per-commit with --ai (claude|copilot|cursor|coderabbit|none default none, human-only). Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
3
+ description: 'Build-half helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer order (Task → Contract-Change, plus an OPTIONAL Co-Authored-By), and an atomic-file guard (≤3 files). By default the commit carries NO AI footer: the human git author owns it, and a Co-Authored-By trailer is added ONLY when --ai <id> is explicitly passed (claude|copilot|cursor|coderabbit; default none = human-only). The flag is the sole switch — never add the footer on the AI''s own initiative. Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
4
4
  ---
5
5
 
6
6
  # SDLC — Commit by Convention (build-half helper)
7
7
 
8
8
  **Goal:** Turn ONE staged atomic change into a single commit that satisfies the project conventions
9
9
  (`CONTRIBUTING.md` / `config.yaml` `build`): a Conventional-Commits subject, the fixed trailer order
10
- `Task → Contract-Change → Co-Authored-By`, and the atomic-file guard. This is the standalone commit
11
- step — the same engine `yad-implement` and `yad-ship` use. It **never auto-advances**; it just commits.
10
+ `Task → Contract-Change → Co-Authored-By` (the footer **off by default** added only via an explicit
11
+ `--ai`), and the atomic-file guard. This is the standalone commit step — the same engine `yad-implement`
12
+ and `yad-ship` use. It **never auto-advances**; it just commits.
12
13
 
13
14
  ## Conventions
14
15
 
@@ -23,14 +24,17 @@ step — the same engine `yad-implement` and `yad-ship` use. It **never auto-adv
23
24
  task-scoped, so the trailer is optional there.
24
25
  - **Contract-Change trailer** — `--contract-change` only when the diff touches the locked contract
25
26
  surface; it routes the change back to the architecture gate.
26
- - **AI co-author footer**`--ai <id>` records the assisting tool as a `Co-Authored-By` trailer. The
27
- human is always the author; `--ai none` (the default) is an explicit human-only commit.
27
+ - **AI co-author footer — OFF by default.** No `Co-Authored-By` trailer is written unless `--ai <id>`
28
+ explicitly names a tool. `--ai none` (the default) produces a clean human-only commit. The flag is the
29
+ **only** switch that adds the footer — never add it on the AI's own initiative just because a tool
30
+ helped author the diff. The human is always the author.
28
31
 
29
32
  ## Inputs
30
33
 
31
34
  - `type` — Conventional-Commits type (required).
32
35
  - `message` — the subject text (required), `-m "<subject>"`.
33
- - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none`).
36
+ - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none` = **no footer**;
37
+ the `Co-Authored-By` trailer appears only when this flag names a tool).
34
38
  - `task` — Task trailer (optional; derived from the branch when omitted).
35
39
  - `contractChange` — flag; mark the contract surface touched.
36
40
 
@@ -55,8 +59,10 @@ the architecture gate. To also open the PR/MR in the same step, use `yad-ship`.
55
59
  ## Hard rules
56
60
 
57
61
  - **One staged atomic change = one commit.** Never bundle; never exceed the file boundary silently.
58
- - **The human author owns the commit.** The AI is only a `Co-Authored-By` footer, chosen per-commit.
59
- - **Trailer order is fixed:** `Task → Contract-Change Co-Authored-By`.
62
+ - **No AI footer by default.** A `Co-Authored-By` trailer is written ONLY when `--ai <id>` is explicitly
63
+ passed; the default is a clean human-only commit. Never add it on your own initiative.
64
+ - **The human author owns the commit.** The AI is at most a `Co-Authored-By` footer, never the author.
65
+ - **Trailer order is fixed:** `Task → Contract-Change → Co-Authored-By` (the footer only when `--ai` is given).
60
66
  - **Never widen the contract here.** A contract touch is flagged (`--contract-change`), not hidden.
61
67
 
62
68
  ## Reference
@@ -96,9 +96,9 @@ A contract change means the diff alters the agreed cross-repo shape itself.
96
96
  ### Step 6 — Commit on the task branch
97
97
  Stage only the declared files. Commit with the convention: a conventional subject, a short body, and a
98
98
  `Task: <story>-<task>` trailer (plus `Contract-Change: yes` if Step 5 applies). The human author owns the
99
- commit; if an AI tool helped author this diff, add its `Co-Authored-By:` line from
100
- `config.yaml` `build.ai_coauthor.allowed` (the `.gitmessage` template scaffolds the choices). Keep all
101
- trailers in one contiguous block. Do not commit sibling tasks' work.
99
+ commit; **no `Co-Authored-By:` footer by default** add one only when the human explicitly asks
100
+ (`yad commit --ai <id>` / the `.gitmessage` choices from `config.yaml` `build.ai_coauthor.allowed`),
101
+ never on the AI's own initiative. Keep all trailers in one contiguous block. Do not commit sibling tasks' work.
102
102
 
103
103
  ### Step 7 — Report; the advance decision belongs to the dial (Phase 4)
104
104
  Report: the branch name, the files changed, how the change satisfies the task's acceptance criterion,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yad-ship
3
- description: 'Build-half helper of the gated SDLC — commit AND open the task PR/MR in one step. A thin orchestration over yad-commit then yad-open-pr: commit the staged atomic change by the conventions (Conventional-Commits subject, Task → Contract-Change Co-Authored-By trailers, --ai footer, ≤3-file atomic guard), then push the branch and open the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the `yad ship` CLI; never merges. Use when the user says "ship this task", "commit and open the PR", or "commit and raise the MR". (For the engineer review + merge, use yad-engineer-review.)'
3
+ description: 'Build-half helper of the gated SDLC — commit AND open the task PR/MR in one step. A thin orchestration over yad-commit then yad-open-pr: commit the staged atomic change by the conventions (Conventional-Commits subject, Task → Contract-Change trailers, an OPTIONAL Co-Authored-By footer that is OFF by default and added only when --ai <id> is explicitly passed, ≤3-file atomic guard), then push the branch and open the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the `yad ship` CLI; never merges. Use when the user says "ship this task", "commit and open the PR", or "commit and raise the MR". (For the engineer review + merge, use yad-engineer-review.)'
4
4
  ---
5
5
 
6
6
  # SDLC — Commit + Open PR/MR (build-half helper)
@@ -16,8 +16,8 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
16
16
  task branch with the atomic change **already staged** (`git add`).
17
17
  - Inherits every convention of the two steps it wraps:
18
18
  - Commit: subject `<type>: <lowercase imperative, no trailing period>`, fixed trailer order
19
- `Task → Contract-Change → Co-Authored-By`, the `--ai` co-author footer, the ≤3-file atomic guard
20
- (`../yad-commit/SKILL.md`).
19
+ `Task → Contract-Change → Co-Authored-By` (the footer **off by default** added only via an explicit
20
+ `--ai`), the ≤3-file atomic guard (`../yad-commit/SKILL.md`).
21
21
  - PR/MR: pushed branch, the committed template prefilled, title defaulting to the commit subject,
22
22
  roster auto-assign, risk routing (`../yad-open-pr/SKILL.md`).
23
23
  - **Order matters:** the PR/MR is opened **only if the commit lands**. A failed commit, a tripped
@@ -26,7 +26,8 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
26
26
  ## Inputs
27
27
 
28
28
  - `type` / `message` — the commit type + subject (required), `--type <t> -m "<subject>"`.
29
- - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none`).
29
+ - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none` = **no
30
+ footer**; the `Co-Authored-By` trailer appears only when this flag names a tool).
30
31
  - `task` — Task trailer (optional; derived from the branch when omitted).
31
32
  - `contractChange` — flag; marks the contract surface touched (commit trailer + PR escalation).
32
33
  - `repo` / `risk` / `base` / `platform` / `title` — PR/MR options (see `yad-open-pr`).
@@ -54,6 +55,8 @@ the engineer review and merge are Step E (`yad-engineer-review`).
54
55
  ## Hard rules
55
56
 
56
57
  - **One staged atomic task = one commit = one PR/MR.** Never bundle; never open from the default branch.
58
+ - **No AI footer by default.** The wrapped commit writes a `Co-Authored-By` trailer ONLY when `--ai <id>`
59
+ is explicitly passed; never add it on the AI's own initiative.
57
60
  - **No PR without a landed commit.** A failed/`--dry-run` commit stops the step before pushing.
58
61
  - **High risk routes to domain owners** — the same escalation as the gate.
59
62
  - **Shipping here never merges.** The human owns the merge in `yad-engineer-review`.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: yad-sync-repos
3
+ description: 'Brings every connected code repo up to date in one shot: switches each repo in .sdlc/repos.json to its registry default_branch and fast-forwards it to the latest from origin (local-user git, no stored tokens). A working-tree-only maintenance op — never a gate; it reads the registry but never writes it. A repo with uncommitted local changes is skipped and reported, never overwritten; a diverged branch is left for manual resolution (fast-forward only). After pulling, a repo''s cached code-context pack goes stale, so it points the human at `yad repo refresh`. Use when the user says "sync the repos", "switch all repos to the default branch", "pull latest on every repo", or "get the latest from the default branch".'
4
+ ---
5
+
6
+ # SDLC — Sync Connected Repos (one command, every repo on its default branch)
7
+
8
+ **Goal:** Before front/build work starts, get every connected code repo onto its default branch at the
9
+ latest commit, so nobody implements on a stale or wrong branch. This is the deterministic counterpart to
10
+ the `sync-before-implementation` discipline: one command instead of N manual `git checkout && git pull`.
11
+
12
+ This is **setup/maintenance, not a gated state** — it never touches `.sdlc/state.json`, any epic's
13
+ approvals, or the contract lock. It reads the project-wide registry and only mutates each repo's working
14
+ tree (branch + fast-forward). It writes **nothing** back to the registry.
15
+
16
+ ## Conventions
17
+
18
+ - `{project-root}` resolves from the project working directory (the **product hub**).
19
+ - Registry: `{project-root}/.sdlc/repos.json` (project-wide; the same file `yad-connect-repos` writes).
20
+ Each repo entry supplies `path` (local path, relative to `{project-root}` or absolute) and
21
+ `default_branch` (the branch to land on).
22
+ - **Local-user auth only.** Fetch as the user running the command — their SSH key or git credential
23
+ helper. Store no tokens. Works for GitHub and GitLab alike. If a fetch fails on auth, the repo is
24
+ skipped (STOP and tell the user to authenticate); never embed a credential.
25
+ - The deterministic half is the **`yad repo sync` CLI command** — `yad repo sync` (all repos) or
26
+ `yad repo sync <name>` (one). This skill drives that command and explains the result.
27
+
28
+ ## Inputs
29
+
30
+ - `action` — `sync` (this skill's only action).
31
+ - `repo` — optional short name (the registry `name`, e.g. `backend`) to sync a single repo; omit for all.
32
+
33
+ ## On Activation
34
+
35
+ Run `yad repo sync [<repo>]`. For each target repo it performs, in order:
36
+
37
+ ### Step 1 — Locate and validate the repo
38
+ Resolve the local path from the registry. If the path is not a git repo (no readable `HEAD`), warn and
39
+ skip — there is nothing to switch.
40
+
41
+ ### Step 2 — Skip a dirty working tree (never overwrite local work)
42
+ If `git status --porcelain` is non-empty, the repo has uncommitted changes: **skip it and warn**
43
+ (`<name> dirty → SKIPPED (commit/stash first)`). The skill never stashes, resets, or force-checks-out —
44
+ preserving local work is a hard rule.
45
+
46
+ ### Step 3 — Determine the default branch
47
+ Use the registry `default_branch`. If absent, fall back to the remote's `origin/HEAD`, else `main`.
48
+
49
+ ### Step 4 — Fetch the latest
50
+ If the repo has an `origin` remote: `git fetch origin <default_branch> --prune`. A local-only repo
51
+ (no remote) skips the fetch and just switches branch.
52
+
53
+ ### Step 5 — Switch to the default branch
54
+ If not already on it, `git checkout <default_branch>` (git creates the tracking branch from `origin/`
55
+ when needed).
56
+
57
+ ### Step 6 — Fast-forward only
58
+ `git merge --ff-only origin/<default_branch>`. If the branch has diverged (a real merge would be
59
+ required), **do not pull** — warn (`<name> diverged → not fast-forwarded`) and leave it for the human.
60
+ Never create a merge commit, rebase, or force.
61
+
62
+ ### Step 7 — Report
63
+ Per repo: `switched to <branch>, pulled (ff)` / `already current` / `SKIPPED (...)`. Pulling moves HEAD,
64
+ so any repo whose `HEAD` now differs from its registry `syncedHead` has a **stale code-context pack** —
65
+ the command ends by pointing the human at `yad repo refresh` to repack (that is a separate human
66
+ decision; this skill never repacks or writes the registry).
67
+
68
+ ## Hard rules
69
+
70
+ - **Dirty = skip.** Never stash, reset, or discard uncommitted changes.
71
+ - **Fast-forward only.** A diverged branch is reported and left alone — no merge/rebase/force.
72
+ - **Local-user auth; store no tokens.** Fetch as the user; an auth failure skips that repo.
73
+ - **Working-tree only; not a gate.** Never write `.sdlc/repos.json`, `.sdlc/state.json`, approvals, or
74
+ the contract lock. Refreshing the cached pack is the separate, human-invoked `yad repo refresh`.
75
+
76
+ ## Reference
77
+ - Registry schema + the HEAD-sha freshness rule this reports against: `../yad-connect-repos/references/repos-registry.md`.
78
+ - Connecting / refreshing repos and the code-context cache: `../yad-connect-repos/SKILL.md`.