yadflow 3.9.1 → 3.9.2

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
- ## [3.9.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.9.0...v3.9.1) (2026-07-06)
1
+ ## [3.9.2](https://github.com/abdelrahmannasr/yadflow/compare/v3.9.1...v3.9.2) (2026-07-06)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * carry ship-backed story status flip in yad checkpoint ([#112](https://github.com/abdelrahmannasr/yadflow/issues/112)) ([478230c](https://github.com/abdelrahmannasr/yadflow/commit/478230cdfccafbcc80913dd3b0a69262a45cbea6))
6
+ * validate explicit --task id format in yad commit ([e3b0527](https://github.com/abdelrahmannasr/yadflow/commit/e3b05276b951dbeab6ce6bcb135e8228f73ede9d))
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/bin/yad.mjs CHANGED
@@ -124,7 +124,7 @@ ${c.bold('Options')}
124
124
  --dir <path> Target project root (default: cwd)
125
125
  --type <t> commit: feat|fix|docs|refactor|test|perf|build|ci|chore|revert
126
126
  -m, --message <s> commit: subject / PR title
127
- --task <id> commit: Task trailer (else derived from the branch)
127
+ --task <id> commit: Task trailer, a <story>-T<NN> id (else derived from the branch)
128
128
  --ai <id> commit: co-author — claude|copilot|cursor|coderabbit|none (default none)
129
129
  --contract-change commit/open-pr: mark the contract surface touched
130
130
  --risk <level> open-pr: low|medium|high (default low)
package/cli/commit.mjs CHANGED
@@ -2,12 +2,14 @@
2
2
  // Subject is Conventional Commits; trailers are emitted in the fixed order
3
3
  // Task -> Contract-Change -> Co-Authored-By. The human git author OWNS the commit; the AI is only a
4
4
  // co-author (flagged with --ai, or `none` for human-only). An atomic-commit guard keeps diffs small.
5
+ // An explicit --task id is validated against the spec-link gate contract (<story>-T<NN>) so a
6
+ // malformed trailer fails locally rather than after a push.
5
7
  import path from 'node:path';
6
8
  import fs from 'node:fs';
7
9
  import { c, log, ok, info, warn, fail, run, exists } from './lib.mjs';
8
10
  import {
9
11
  COMMIT_TYPES, AI_COAUTHORS, ATOMIC_FILE_LIMIT,
10
- TASK_TRAILER, CONTRACT_CHANGE_TRAILER, COAUTHOR_TRAILER, PROJECT_FILES,
12
+ TASK_TRAILER, CONTRACT_CHANGE_TRAILER, COAUTHOR_TRAILER, PROJECT_FILES, TASK_ID_RE,
11
13
  } from './manifest.mjs';
12
14
 
13
15
  // PURE — unit tested directly. Build the full commit message text.
@@ -16,6 +18,13 @@ export function buildCommitMessage({ type, subject, task, contractChange = false
16
18
  if (!subject || !subject.trim()) throw new Error('commit subject is required');
17
19
  if (!(ai in AI_COAUTHORS)) throw new Error(`unknown --ai "${ai}" (one of: ${Object.keys(AI_COAUTHORS).join(', ')})`);
18
20
  if (/\.$/.test(subject.trim())) throw new Error('subject must not end with a period');
21
+ // A malformed task id (e.g. a bare story `EP-x-S01` with no -T<NN>) commits fine locally but
22
+ // then fails the spec-link CI gate — forcing a history rewrite + force-push. Reject it here so
23
+ // it never enters a trailer. Well-formed branch-derived ids (taskFromBranch) are stricter and pass;
24
+ // a lowercase-suffix branch is caught here, matching the case-sensitive spec-link grep.
25
+ if (task && !TASK_ID_RE.test(task)) {
26
+ throw new Error(`invalid --task "${task}" (expected <story>-T<NN>, e.g. EP-x-S01-T02) — the spec-link CI gate would reject it`);
27
+ }
19
28
 
20
29
  const trailers = [];
21
30
  if (task) trailers.push(`${TASK_TRAILER}: ${task}`);
package/cli/manifest.mjs CHANGED
@@ -170,6 +170,10 @@ export const ATOMIC_FILE_LIMIT = 3;
170
170
  export const TASK_TRAILER = 'Task';
171
171
  export const CONTRACT_CHANGE_TRAILER = 'Contract-Change';
172
172
  export const COAUTHOR_TRAILER = 'Co-Authored-By';
173
+ // A Task trailer id must be a real <story>-T<NN>. Mirrors the spec-link gate contract
174
+ // (checks/spec-link.sh: `.+-T[0-9]+$`) so an explicit --task that would fail CI is
175
+ // rejected locally at commit time instead of after a push + history rewrite.
176
+ export const TASK_ID_RE = /.+-T\d+$/;
173
177
 
174
178
  // Per-epic ledger files under epics/<epic>/.sdlc/ (the file source of truth the gate reads/writes).
175
179
  export const epicFiles = (epicRoot) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yadflow",
3
- "version": "3.9.1",
3
+ "version": "3.9.2",
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 + 38 yad-* skills.",
5
5
  "type": "module",
6
6
  "author": "AbdelRahman Nasr",
@@ -20,7 +20,9 @@ and `yad-ship` use. It **never auto-advances**; it just commits.
20
20
  - **Subject** — `<type>: <lowercase imperative description, no trailing period>`; types are
21
21
  `feat|fix|docs|refactor|test|perf|build|ci|chore|revert`; proper nouns/acronyms keep their case.
22
22
  - **Task trailer** — required on a code repo (anchors the `spec-link` + `commit-message` gates). Given
23
- with `--task`, else derived from the branch (`feat/<story>-<task>-…`). Hub commits are not
23
+ with `--task` (a `<story>-T<NN>` id an explicit value is validated against the `spec-link`
24
+ contract, so a malformed id like a bare `EP-x-S01` fails locally instead of after a push), else
25
+ derived from the branch (`feat/<story>-<task>-…`). Hub commits are not
24
26
  task-scoped, so the trailer is optional there — a missing-Task warning is informational on the hub
25
27
  (`spec-link` is a code-repo gate) and only flags a real gate failure in a code repo.
26
28
  - **Contract-Change trailer** — `--contract-change` only when the diff touches the locked contract