yadflow 3.5.2 → 3.5.3

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,4 +1,9 @@
1
- ## [3.5.2](https://github.com/abdelrahmannasr/yadflow/compare/v3.5.1...v3.5.2) (2026-07-02)
1
+ ## [3.5.3](https://github.com/abdelrahmannasr/yadflow/compare/v3.5.2...v3.5.3) (2026-07-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fill the PR spec dir and summary from the task and commit ([a402a54](https://github.com/abdelrahmannasr/yadflow/commit/a402a545715eeea8aef2dde5426eb7f2538687a0))
2
7
 
3
8
  # [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
4
9
 
package/cli/openpr.mjs CHANGED
@@ -62,7 +62,7 @@ function codeTaskTemplate(platform) {
62
62
  }
63
63
  }
64
64
 
65
- export function templateBody(repoRoot, platform, { task, risk, contract, domains, stage }) {
65
+ export function templateBody(repoRoot, platform, { task, summary, risk, contract, domains, stage }) {
66
66
  // hub-tooling: the hub's own template is artifact-review — use the bundled code-task template so the
67
67
  // body matches the shape the hub `pr-template` gate demands for a non-review head.
68
68
  let base;
@@ -74,12 +74,24 @@ export function templateBody(repoRoot, platform, { task, risk, contract, domains
74
74
  : path.join(repoRoot, '.github/pull_request_template.md');
75
75
  base = exists(tplPath) ? fs.readFileSync(tplPath, 'utf8') : codeTaskTemplate(platform);
76
76
  }
77
+ // The Spec line's placeholder is the STORY dir `specs/EP-<slug>-S0N/` (no `-T0N`), so the task
78
+ // regex below never touches it — derive the story from the task (task minus its `-T0N` suffix).
79
+ const story = task ? task.replace(/-T\d+$/i, '') : null;
77
80
  // Fill the obvious fields; leave the rest of the committed template intact for the author.
78
- return base
79
- .replace(/EP-<slug>-S0N-T0N/g, task || 'EP-<slug>-S0N-T0N')
80
- .replace(/(\*\*Risk level:\*\*)\s*\w+/i, `$1 ${risk}`)
81
- .replace(/(\*\*Contract surface touched:\*\*)\s*\w+/i, `$1 ${contract ? 'yes' : 'no'}`)
82
- .replace(/(\*\*Domains \/ repos touched:\*\*).*/i, `$1 ${domains || '<repo>'}`);
81
+ // Function replacers (not string replacements) so a `$` in interpolated free text — a commit-derived
82
+ // summary or a repo name — is inserted verbatim, never read as a `$1`/`$&` replacement token.
83
+ let out = base
84
+ .replace(/EP-<slug>-S0N-T0N/g, () => task || 'EP-<slug>-S0N-T0N')
85
+ .replace(/specs\/EP-<slug>-S0N\//g, () => `specs/${story || 'EP-<slug>-S0N'}/`)
86
+ .replace(/(\*\*Risk level:\*\*)\s*\w+/i, (_m, g1) => `${g1} ${risk}`)
87
+ .replace(/(\*\*Contract surface touched:\*\*)\s*\w+/i, (_m, g1) => `${g1} ${contract ? 'yes' : 'no'}`)
88
+ .replace(/(\*\*Domains \/ repos touched:\*\*).*/i, (_m, g1) => `${g1} ${domains || '<repo>'}`);
89
+ if (summary) {
90
+ // Replace the "## Summary" heading + its optional guidance comment with the real summary.
91
+ // `\r?\n` tolerates a CRLF-checked-out template (Windows core.autocrlf) so the fill never no-ops.
92
+ out = out.replace(/(## Summary\r?\n)(?:<!--[\s\S]*?-->\r?\n)?/, (_m, g1) => `${g1}${summary}\n`);
93
+ }
94
+ return out;
83
95
  }
84
96
 
85
97
  export async function runOpenPr(root, opts = {}) {
@@ -124,9 +136,20 @@ export async function runOpenPr(root, opts = {}) {
124
136
  if (!push.ok) { fail(`git push failed — ${push.stderr.split('\n')[0] || 'unknown'}`); process.exitCode = 1; return; }
125
137
 
126
138
  const task = opts.task || taskFromBranch(branch);
127
- const title = opts.title || run('git', ['log', '-1', '--format=%s'], { cwd: repoRoot }).stdout || `task ${task || branch}`;
139
+ const subject = run('git', ['log', '-1', '--format=%s'], { cwd: repoRoot }).stdout;
140
+ const title = opts.title || subject || `task ${task || branch}`;
141
+ // Summary = commit subject (Conventional-Commits "type:" prefix stripped) + body paragraphs,
142
+ // dropping the Task / Contract-Change / Co-Authored-By trailer lines. `run().stdout` is trimmed
143
+ // but keeps internal newlines, so a multi-line body survives.
144
+ const subjectText = subject.replace(/^[a-z]+(\([^)]*\))?!?:\s*/i, '');
145
+ const bodyText = run('git', ['log', '-1', '--format=%b'], { cwd: repoRoot }).stdout
146
+ .split('\n')
147
+ .filter((l) => !/^(Task|Contract-Change|Co-Authored-By):/i.test(l.trim()))
148
+ .join('\n')
149
+ .trim();
150
+ const summary = [subjectText, bodyText].filter(Boolean).join('\n\n') || undefined;
128
151
  const body = templateBody(repoRoot, platform, {
129
- task, risk: opts.risk || 'low', contract: !!opts.contractChange, domains: meta?.name, stage,
152
+ task, summary, risk: opts.risk || 'low', contract: !!opts.contractChange, domains: meta?.name, stage,
130
153
  });
131
154
 
132
155
  // Auto-assign from the hub roster, scoped to this repo: assignee = the committer (resolved from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yadflow",
3
- "version": "3.5.2",
3
+ "version": "3.5.3",
4
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, thread, reconcile). A BMAD module + 37 yad-* skills.",
5
5
  "type": "module",
6
6
  "author": "AbdelRahman Nasr",