yadflow 4.5.0 → 4.5.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.
- package/CHANGELOG.md +13 -0
- package/cli/docs.mjs +21 -5
- package/cli/doctor.mjs +17 -0
- package/cli/lib.mjs +5 -2
- package/package.json +1 -1
- package/skills/sdlc/module-help.csv +2 -2
- package/skills/yad-docs-overview/SKILL.md +1 -1
- package/skills/yad-docs-sync/SKILL.md +11 -10
- package/skills/yad-docs-sync/references/staleness.md +11 -13
- package/skills/yad-product-bridge/SKILL.md +4 -0
- package/skills/yad-product-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +18 -0
- package/skills/yad-reconcile/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [4.5.1](https://github.com/abdelrahmannasr/yadflow/compare/v4.5.0...v4.5.1) (2026-10-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **docs:** a failed site build fails the Pages deploy ([32dd88f](https://github.com/abdelrahmannasr/yadflow/commit/32dd88f69dc1140977e1a2c126f94078aaa5f2ea))
|
|
7
|
+
* **docs:** doctor judges only the failure handling, on the wired platform (review 2) ([987a753](https://github.com/abdelrahmannasr/yadflow/commit/987a7532951f61aec530550e2a9cab9aa4e0795f))
|
|
8
|
+
* **docs:** escape the apostrophe that broke the overview site build ([fe7dcc8](https://github.com/abdelrahmannasr/yadflow/commit/fe7dcc81b2772dc577659e4afd2b85bec2bff81a))
|
|
9
|
+
* **docs:** name every failed site, quote GitLab lines as YAML, doctor warns on an old wired workflow (review 1) ([d525e87](https://github.com/abdelrahmannasr/yadflow/commit/d525e87961413a10ba16122dfc3811ac3d5361a7))
|
|
10
|
+
* **gitlab:** check the git-lfs download against its SHA-256 ([#327](https://github.com/abdelrahmannasr/yadflow/issues/327) review 1) ([01763a2](https://github.com/abdelrahmannasr/yadflow/commit/01763a25fdc47eea298592f3ab453341c2c14196))
|
|
11
|
+
* **gitlab:** install git-lfs in yad-gate-sync when the Product uses LFS ([#327](https://github.com/abdelrahmannasr/yadflow/issues/327)) ([fcb55fb](https://github.com/abdelrahmannasr/yadflow/commit/fcb55fb6a3c2d96be067cb7c027644424784181d))
|
|
12
|
+
* **windows:** leave the npm launcher name unquoted so npx finds itself ([#326](https://github.com/abdelrahmannasr/yadflow/issues/326)) ([b042848](https://github.com/abdelrahmannasr/yadflow/commit/b0428484f371a03cc5e722158481faf51004b519))
|
|
13
|
+
|
|
1
14
|
# [4.5.0](https://github.com/abdelrahmannasr/yadflow/compare/v4.4.0...v4.5.0) (2026-10-06)
|
|
2
15
|
|
|
3
16
|
|
package/cli/docs.mjs
CHANGED
|
@@ -144,12 +144,28 @@ export function docsStale(manifest, { artifactHash, repoHeads = {}, shellVersion
|
|
|
144
144
|
// mounts under `app/`, and every per-epic site nests under `epics/<id>/` — matching siteBasePath
|
|
145
145
|
// (overview at `<base>/app/`, epics at `<base>/epics/EP-<slug>/`). A concurrency group prevents the
|
|
146
146
|
// deploy from retriggering. The shared shell script keeps the two platforms byte-for-byte aligned.
|
|
147
|
+
// A site that fails to build fails the run: CI runs the script under `set -e`, which does not stop on a
|
|
148
|
+
// failure inside an `&&` chain, so a failed site used to be silently left out while the deploy still went
|
|
149
|
+
// green — yadflow's own site lost its root page that way for days. Each chain ends in `|| failed=…`, so
|
|
150
|
+
// every site is still tried (as `yad docs build` does) and the last line fails the run, naming them all.
|
|
151
|
+
// A red run uploads nothing, so the last good deployment stays live.
|
|
152
|
+
// A YAML single-quoted scalar: a quote inside is written twice, and nothing else is escaped (a backslash
|
|
153
|
+
// is literal). The shell's '\'' is not YAML — it would make GitLab reject the whole pipeline.
|
|
154
|
+
export const yamlQuoted = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
155
|
+
const PAGES_FAIL_LINE = 'yad-docs: these sites did not build, so nothing is deployed:';
|
|
147
156
|
const BUILD_PUBLIC = [
|
|
148
157
|
'mkdir -p public',
|
|
149
|
-
'
|
|
150
|
-
'if [ -d docs/
|
|
151
|
-
'
|
|
158
|
+
'failed=',
|
|
159
|
+
'if [ -d docs/sdlc-site ]; then (cd docs/sdlc-site && npm ci && npm run build) && mkdir -p public/app && cp -r docs/sdlc-site/dist/. public/app/ && cp docs/sdlc-site/public/report.html public/index.html && cp docs/sdlc-site/public/report.html public/report.html || failed="$failed docs/sdlc-site"; fi',
|
|
160
|
+
'if [ -d docs/tutorial-site ]; then (cd docs/tutorial-site && npm ci && npm run build) && mkdir -p public/tutorial && cp -r docs/tutorial-site/dist/. public/tutorial/ || failed="$failed docs/tutorial-site"; fi',
|
|
161
|
+
'for d in epics/*/docs-site; do [ -d "$d" ] || continue; id=$(basename "$(dirname "$d")"); (cd "$d" && npm ci && npm run build) && mkdir -p "public/epics/$id" && cp -r "$d/dist/." "public/epics/$id/" || failed="$failed $d"; done',
|
|
162
|
+
`if [ -n "$failed" ]; then echo "${PAGES_FAIL_LINE}$failed" >&2; exit 1; fi`,
|
|
152
163
|
];
|
|
164
|
+
// The platform `--wire` writes for: the Product's docs target, GitHub unless it says GitLab.
|
|
165
|
+
export const wiredPagesPlatform = (docs) => (docs?.target === 'gitlab-pages' ? 'gitlab' : 'github');
|
|
166
|
+
// Does a wired Pages workflow fail its deploy when a site fails to build? Read by `yad doctor`. Only this
|
|
167
|
+
// line is looked for — not the whole file — so a Dependabot pin bump or an edit elsewhere is not news.
|
|
168
|
+
export const pagesWorkflowFailsOnBuild = (text) => text.includes(PAGES_FAIL_LINE);
|
|
153
169
|
export function pagesWorkflow(platform) {
|
|
154
170
|
if (platform === 'gitlab') {
|
|
155
171
|
return `# yad-managed — built by \`yad docs sync --wire\`. include it from .gitlab-ci.yml:
|
|
@@ -161,7 +177,7 @@ pages:
|
|
|
161
177
|
rules:
|
|
162
178
|
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
163
179
|
script:
|
|
164
|
-
${BUILD_PUBLIC.map((l) => ` -
|
|
180
|
+
${BUILD_PUBLIC.map((l) => ` - ${yamlQuoted(l)}`).join('\n')}
|
|
165
181
|
artifacts:
|
|
166
182
|
paths: [public]
|
|
167
183
|
resource_group: pages
|
|
@@ -347,7 +363,7 @@ function reportFreshness(root, t) {
|
|
|
347
363
|
}
|
|
348
364
|
|
|
349
365
|
function wirePages(root, docs) {
|
|
350
|
-
const platform = docs
|
|
366
|
+
const platform = wiredPagesPlatform(docs);
|
|
351
367
|
const rel = pagesWorkflowPath(platform);
|
|
352
368
|
const dest = path.join(root, rel);
|
|
353
369
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
package/cli/doctor.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { checkCodeowners, codeownersFindings } from './codeowners-command.mjs';
|
|
|
22
22
|
import { RISK_MAP_FILE } from './riskmap.mjs';
|
|
23
23
|
import { readProtection, protectionLine, protectionJSON, hideAddresses } from './protection.mjs';
|
|
24
24
|
import { soloTeamHint, TEAM_CMD } from './people.mjs';
|
|
25
|
+
import { pagesWorkflowPath, pagesWorkflowFailsOnBuild, wiredPagesPlatform } from './docs.mjs';
|
|
25
26
|
import { indexFreshness, INDEX_FILE } from './product-index.mjs';
|
|
26
27
|
import { productGit, resolveDefaultBranch } from './productcommit.mjs';
|
|
27
28
|
import { readOwners } from './owners.mjs';
|
|
@@ -379,6 +380,7 @@ export function projectChecks(checks, root, { headCount = null } = {}) {
|
|
|
379
380
|
// Background capture (E43), on any Product in either ledger mode: is the post-edit hook wired, and will
|
|
380
381
|
// a push to the `yad/wip/*` branches start the team's own CI?
|
|
381
382
|
if (exists(productPath)) captureChecks(root, checks, readJSON(productPath, null));
|
|
383
|
+
docsWorkflowChecks(root, checks);
|
|
382
384
|
if (exists(productPath)) lineEndingChecks(root, checks);
|
|
383
385
|
|
|
384
386
|
// design.json: parse + shape + tool + MCP confirmation (absent is the normal markdown-only default —
|
|
@@ -2697,6 +2699,21 @@ export function captureChecks(root, checks, cfg) {
|
|
|
2697
2699
|
}
|
|
2698
2700
|
}
|
|
2699
2701
|
|
|
2702
|
+
// The Pages workflow `yad docs sync --wire` wrote. Nothing else rewrites it, so a Product wired by yadflow
|
|
2703
|
+
// 4.5.0 or older keeps a script that deploys a site with a page missing when a site fails to build. Only
|
|
2704
|
+
// that one behaviour is judged — not the whole file, which Dependabot bumps and a team may edit — and only
|
|
2705
|
+
// in the file `--wire` writes for this Product's platform (it never touches the other one). A file
|
|
2706
|
+
// without our header is the team's own.
|
|
2707
|
+
export function docsWorkflowChecks(root, checks) {
|
|
2708
|
+
const rel = pagesWorkflowPath(wiredPagesPlatform(readJSON(path.join(root, PROJECT_FILES.docsConfig), null)));
|
|
2709
|
+
let text;
|
|
2710
|
+
try { text = fs.readFileSync(path.join(root, rel), 'utf8'); } catch { return; }
|
|
2711
|
+
if (!text.startsWith('# yad-managed — built by `yad docs sync --wire`')) return;
|
|
2712
|
+
if (pagesWorkflowFailsOnBuild(text)) check(checks, 'docs-workflow', 'project', 'ok', `${rel} fails the deploy when a site fails to build`);
|
|
2713
|
+
else check(checks, 'docs-workflow', 'project', 'warn', `${rel} still deploys when a site fails to build — the site then goes out with a page missing`,
|
|
2714
|
+
'run `yad docs sync --wire` and commit the file; it rewrites the whole file, so re-apply any change your team made to it');
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2700
2717
|
// The team's own GitHub workflow files that a push to a `yad/wip/*` branch would start. A line reader, not
|
|
2701
2718
|
// a YAML parser, confined to the TOP-LEVEL `on:` block (a job named `push` is not a trigger). It reads the
|
|
2702
2719
|
// usual spellings — `on: push`, `on: [push, …]`, and a `push:` key under `on:` — and decides the way
|
package/cli/lib.mjs
CHANGED
|
@@ -357,11 +357,14 @@ export function run(cmd, args = [], opts = {}) {
|
|
|
357
357
|
export const has = (cmd) => run(process.platform === 'win32' ? 'where' : 'which', [cmd]).ok;
|
|
358
358
|
|
|
359
359
|
// An npm launcher (`npm`, `npx`) is a `.cmd` file on Windows, which Node will not start without a shell
|
|
360
|
-
// (since 18.20.2) — a plain spawn fails with EINVAL there. So on Windows it runs through one, each
|
|
360
|
+
// (since 18.20.2) — a plain spawn fails with EINVAL there. So on Windows it runs through one, each argument in
|
|
361
361
|
// double quotes (a Windows path cannot hold a `"`); everywhere else it is spawned directly (E113).
|
|
362
|
+
// The launcher name itself stays bare: a `.cmd` found on PATH through a QUOTED name reads `%~dp0` as the
|
|
363
|
+
// current folder, so `"npx"` looked for npx-cli.js inside the repo being packed (#326). Callers pass a
|
|
364
|
+
// fixed name (`npm`, `npx`), never user input.
|
|
362
365
|
export function launcherInvocation(cmd, args, platform = process.platform) {
|
|
363
366
|
if (platform !== 'win32') return { cmd, args, shell: false };
|
|
364
|
-
return { cmd: [cmd, ...args
|
|
367
|
+
return { cmd: [cmd, ...args.map((a) => `"${String(a).replace(/"/g, '')}"`)].join(' '), args: [], shell: true };
|
|
365
368
|
}
|
|
366
369
|
export function runLauncher(cmd, args = [], opts = {}) {
|
|
367
370
|
const inv = launcherInvocation(cmd, args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"description": "Shared project memory and workflows for teams building with AI: a versioned record of plans, decisions, contracts and reviews across repos, human-approved review gates, and a zero-dependency `yad` CLI plus 38 skills for your AI assistant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|
|
@@ -31,9 +31,9 @@ SDLC Workflow,yad-report,Report Issue,RP,"Self issue reporter: when a yad flow b
|
|
|
31
31
|
SDLC Workflow,yad-connect-docs,Connect Docs Target,DX,"Setup/maintenance: connect a docs/Pages publishing target so the interactive-docs steps can deploy the generated site (not just commit its source). Auto-detects the platform from .sdlc/product.json (github->github-pages, gitlab->gitlab-pages, null->build-only), resolves the Vite base path, and records the target in .sdlc/docs.json (local-user auth, no stored tokens). Degrades to build-only when gh/glab is absent. Idempotent and refreshable; one connection per project. Not a gated state — never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {target: github-pages|gitlab-pages|none} {scope: product|<repo>|dedicated} {base_path: <override>},0-setup,,yad-docs,false,.sdlc/,docs.json
|
|
32
32
|
SDLC Workflow,yad-docs,Author Docs Site,DS,"Generate the per-epic interactive documentation SPA (animated flow canvas + role-based stakeholder doc pages) from the epic's approved artifacts (epic, architecture, locked contract, ui-design, stories, code-context, test-cases), themed by the connected design system, into epics/EP-<slug>/docs-site/. Copies the vendored React/Vite/Tailwind shell verbatim and generates src/data/*.ts deterministically; drives the yad docs build/deploy CLI to publish to Pages (or build-only). An OUTPUT ENRICHMENT — never a gate; never mutates state.json, approvals, or the contract lock.",,{epic: EP-<slug>} {action: generate|refresh|deploy} {login_gate: true|false},4-automate,yad-review-gate,,false,epics/EP-<slug>/docs-site/,docs-site/ docs-build.json
|
|
33
33
|
SDLC Workflow,yad-docs-overview,Docs Overview Site,DO,"Generate the project SDLC-overview interactive site (docs/sdlc-site/) — every stage from setup to ship modeled as flow paths, system components, and stakeholder roles — reusing the same vendored shell, themed with yadflow's brand palette. Reads config.yaml + module-help.csv + the overview diagram as the pipeline source. Folds the hand-maintained docs/index.html report into the site as report.html (linked from the nav). Not a gate — a project-level enrichment that regenerates whenever the skill set / pipeline changes.",,{action: generate|deploy},4-automate,,,false,docs/sdlc-site/,sdlc-site/ .docs-build.json
|
|
34
|
-
SDLC Workflow,yad-docs-sync,Docs Sync,DY,"Maintenance/CI: keep the generated doc sites fresh. Detect staleness (a content hash of the approved artifacts + the connected repos' HEAD shas + for a per-epic site the doc-shell version, vs each site's build manifest), report which sites drifted and why, regenerate + redeploy the stale ones, and wire a CI job that
|
|
34
|
+
SDLC Workflow,yad-docs-sync,Docs Sync,DY,"Maintenance/CI: keep the generated doc sites fresh. Detect staleness (a content hash of the approved artifacts + the connected repos' HEAD shas + for a per-epic site the doc-shell version, vs each site's build manifest), report which sites drifted and why, regenerate + redeploy the stale ones, and wire a CI job that builds and deploys every site on push (one deploy at a time; a site that fails to build fails the run). Generalizes the rule that feature work must hand-update docs/index.html + diagrams + skill counts. Refresh is always a human/CI decision; never a gate.",,{action: check|refresh|wire} {epic: EP-<slug>},4-automate,,,false,epics/EP-<slug>/.sdlc/,docs-build.json yad-docs.yml
|
|
35
35
|
SDLC Workflow,yad-change,Change/Defect Intake,CH,"Phase 6 post-lock change management: the INTAKE + TRIAGE step of a feature thread. Classifies the change DEPTH (defect-fix / behavioral-no-surface / contract-surface / new-capability), seeds a NEW EP-<slug> change-epic threaded to its parent (lineage frontmatter kind+type/parent/thread/inherits/supersedes ; then `yad epic new --parent` writes the state.json whose inherited steps are carried by reference and only the changed steps run, plus a pointer-lock contract-lock.json when architecture is inherited), and records the intake in change.json (escape_stage + root_cause for defects). For hotfixes it records the ship-first exception and opens reconcile-debt.json. Never auto-advances — hands off to the normal authoring skills + yad-review-gate.",,{parent: EP-<slug>} {title: one-line} {type: change|defect|hotfix} {origin: production|staging|qa|review} {severity: sev1..sev4} {description: text} {affected: artifacts},6-change,,yad-review-gate,false,epics/EP-<slug>/,epic.md state.json change.json reconcile-debt.json contract-lock.json
|
|
36
36
|
SDLC Workflow,yad-timeline,Feature Timeline,TL,"Render a feature THREAD (its linked epics, genesis->changes->defects) as an evolution view (the vendored React/Vite/Tailwind shell HTML + a TIMELINE.md summary) AND resolve the inheritance chain into the authoritative current artifact set (thread-resolved.md: the winning source per artifact + the resolved contract-lock hash) — the composed source-of-truth AI/humans read for the next change. Reads frontmatter lineage + each change.json + the build ledger (folded build-log.json UNIONed with every build-log/ shard). An OUTPUT ENRICHMENT — never a gate; never mutates state.",,{thread: EP-<genesis>} {action: generate|deploy},6-change,,,false,epics/EP-<genesis>/,timeline-site/ thread-resolved.md TIMELINE.md
|
|
37
37
|
SDLC Workflow,yad-defects,Quality-Gap Report,DF,"Generate a per-epic AND per-thread defect/bug report (same vendored shell + DEFECTS.md). Walks the thread for every defect-type change-epic + each change.json defect block + shipped regressions in the build ledger (folded build-log.json UNIONed with every build-log/ shard), aggregates by escape_stage (the SDLC gate that should have caught it) and root_cause, and visualizes WHERE quality gaps systematically come from (e.g. % of thread defects that escaped at the test-cases gate) so the team can harden the originating stage. An OUTPUT ENRICHMENT — never a gate; never mutates state.",,{epic: EP-<slug> | thread: EP-<genesis>} {action: generate|deploy},6-change,,,false,epics/EP-<slug>/,defects-site/ DEFECTS.md
|
|
38
|
-
SDLC Workflow,yad-reconcile,Change Reconciler,RE,"Maintenance/CI (mirrors yad-docs-sync — never a gate): detect post-lock DRIFT/ORPHANS — shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread — plus open hotfix reconcile debt, and report which thread drifted and why. refresh points at yad-change to open a reconcile change-epic (or yad-stub first, to anchor orphan brownfield code that has no epic at all) — never silent; wire commits advisory CI ([skip ci] + concurrency
|
|
38
|
+
SDLC Workflow,yad-reconcile,Change Reconciler,RE,"Maintenance/CI (mirrors yad-docs-sync — never a gate): detect post-lock DRIFT/ORPHANS — shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread — plus open hotfix reconcile debt, and report which thread drifted and why. refresh points at yad-change to open a reconcile change-epic (or yad-stub first, to anchor orphan brownfield code that has no epic at all) — never silent; wire commits advisory CI ([skip ci] + concurrency). The actual merge BLOCK is the lineage-check / reconcile-debt gates; this only discovers.",,{action: check|refresh|wire} {thread: EP-<genesis>},6-change,,,false,epics/EP-<genesis>/.sdlc/,(report) reconcile-debt.json yad-reconcile.yml
|
|
39
39
|
SDLC Workflow,yad-stub,Stub Genesis Epic,SG,"Phase 6 brownfield helper: mint a STUB genesis epic for an already-built feature that has no epic in the Product, so a defect/change can thread off it TODAY (yad-change requires a real parent and dead-ends without one). Creates the smallest real thread anchor — a tiny epic.md (type feature, thread:self, verified:false, stub:backfill-pending) + a seeded state.json (kind:stub / currentStep:backfill-pending) + empty ledgers — never inventing behaviour. Defects thread off it immediately (gates pass; the bug list is derived by the thread rollup); yad-backfill + its promote step later flip it into a real feature epic. Never auto-advances.",,{feature: <name>} {repos: [<repo>]} {description: one-line},6-change,,yad-change,false,epics/EP-<slug>/,epic.md state.json approvals.json comments.json
|
|
@@ -150,4 +150,4 @@ under `<base>/app/` and cross-linked. Never touches any epic state.
|
|
|
150
150
|
`yad-*` skill in pipeline order + its phase + outputs: `references/pipeline-model.md`.
|
|
151
151
|
- The per-epic counterpart (shell, determinism, theming): `../yad-docs/SKILL.md`.
|
|
152
152
|
- The connected docs target + base-path resolution: `../yad-connect-docs/SKILL.md`.
|
|
153
|
-
- The staleness
|
|
153
|
+
- The staleness reconciler (reports stale sites, refreshes on request, wires the Pages build): `../yad-docs-sync/SKILL.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-docs-sync
|
|
3
|
-
description: 'The maintenance/CI reconciler for the generated docs sites — mirroring the `yad check` / `yad gate ci` drift pattern. Recomputes each site''s freshness hashes (per-epic: the artifact hash + repo HEADs; overview: config.yaml + module-help.csv + the overview diagram + skill count) and compares them to each docs-build.json baseline: a site is stale when any hash differs or its shell template is out of date. `--check` (default, read-only) reports which sites are stale and WHY; `--refresh` regenerates + redeploys each stale site; `--wire` commits the CI workflow that
|
|
3
|
+
description: 'The maintenance/CI reconciler for the generated docs sites — mirroring the `yad check` / `yad gate ci` drift pattern. Recomputes each site''s freshness hashes (per-epic: the artifact hash + repo HEADs; overview: config.yaml + module-help.csv + the overview diagram + skill count) and compares them to each docs-build.json baseline: a site is stale when any hash differs or its shell template is out of date. `--check` (default, read-only) reports which sites are stale and WHY; `--refresh` regenerates + redeploys each stale site; `--wire` commits the CI workflow that builds and deploys every site on push. Refresh is always a human/CI decision, never silent; docs are never a gate. Use when the user says "sync the docs", "check docs staleness", "refresh stale docs sites", or "wire the docs CI".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Sync the Docs Sites (the staleness reconciler)
|
|
@@ -67,17 +67,17 @@ A missing `docs-build.json` (a site never generated) counts as **stale → needs
|
|
|
67
67
|
rebuild failed: `yad docs sync --refresh` exits 1 when a stale site's npm install or build fails, and
|
|
68
68
|
its `--json` `sites` rows say which (`built: false` with an `error`). **Never silent** — refresh
|
|
69
69
|
is a deliberate human/CI act, surfaced exactly like `yad repo refresh`.
|
|
70
|
-
- **`wire`** — commit the CI workflow (Step 4) that
|
|
70
|
+
- **`wire`** — commit the CI workflow (Step 4) that builds and deploys every site on push.
|
|
71
71
|
|
|
72
72
|
### Step 4 — `wire`: the CI auto-rebuild workflow
|
|
73
73
|
Commit the platform-matched workflow (GitHub `.github/workflows/yad-docs.yml`, or a GitLab `pages` job at
|
|
74
74
|
`.gitlab/ci/yad-docs.yml` that must be `include:`d from the root `.gitlab-ci.yml` —
|
|
75
75
|
`include: { local: .gitlab/ci/yad-docs.yml }`, the same fragment+include shape as the `yad-checks` gates):
|
|
76
|
-
- on push
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
- on a push to the default branch, **build every committed site and deploy them**; it commits nothing,
|
|
77
|
+
so it cannot retrigger itself, and a **concurrency group** keeps one docs deploy at a time;
|
|
78
|
+
- a site that fails to build **fails the run** (every failed site is named) and nothing is deployed, so
|
|
79
|
+
the last good site stays live. `yad doctor` warns (`docs-workflow`) when the wired file still deploys after a failed site build (as yadflow 4.5.0 and older wrote it) — re-run
|
|
80
|
+
`yad docs sync --wire`. See `references/staleness.md`.
|
|
81
81
|
|
|
82
82
|
### Step 5 — Report
|
|
83
83
|
Report per target: **fresh** or **stale (why)**; for `refresh`, what was regenerated + the deploy URL or
|
|
@@ -91,14 +91,15 @@ Report per target: **fresh** or **stale (why)**; for `refresh`, what was regener
|
|
|
91
91
|
`contract-lock.json`. Staleness blocks nothing in the SDLC; it only flags out-of-date docs.
|
|
92
92
|
- **HEAD-sha staleness, reused.** Repo drift uses the exact `repos.json` `syncedHead`-vs-current-HEAD
|
|
93
93
|
rule. The overview uses config + manifest + diagram + skill-count.
|
|
94
|
-
- **
|
|
95
|
-
and
|
|
94
|
+
- **CI builds and deploys; it never commits.** The wired workflow builds every committed site on push
|
|
95
|
+
and deploys them, under one concurrency group; it makes no commit, so it cannot retrigger itself. A
|
|
96
|
+
site that fails to build fails the run, and nothing is deployed.
|
|
96
97
|
- **Reconcile, don't re-author.** This skill detects drift and delegates regeneration to `yad-docs` /
|
|
97
98
|
`yad-docs-overview`; it does not generate `src/data/*.ts` itself.
|
|
98
99
|
|
|
99
100
|
## Reference
|
|
100
101
|
- The manifest schema (per-epic + overview), the exact hash inputs, the head-sha staleness rule, and the
|
|
101
|
-
CI
|
|
102
|
+
CI note: `references/staleness.md`.
|
|
102
103
|
- The generators this delegates to: `../yad-docs/SKILL.md`, `../yad-docs-overview/SKILL.md`.
|
|
103
104
|
- The docs target it deploys to: `../yad-connect-docs/SKILL.md`.
|
|
104
105
|
- The drift / refresh discipline this mirrors: `../yad-connect-repos/SKILL.md` (HEAD-sha staleness).
|
|
@@ -65,18 +65,16 @@ A site is **stale** when ANY holds:
|
|
|
65
65
|
`check` reports which of these tripped and why; `refresh` regenerates + redeploys; neither blocks any
|
|
66
66
|
SDLC step (docs are never a gate).
|
|
67
67
|
|
|
68
|
-
## CI
|
|
68
|
+
## CI note
|
|
69
69
|
|
|
70
70
|
The `wire` workflow (`.github/workflows/yad-docs.yml`, or the GitLab `pages` job at
|
|
71
|
-
`.gitlab/ci/yad-docs.yml` included from the root `.gitlab-ci.yml`)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Together these make the CI rebuild idempotent: a push that moves an artifact deploys exactly one fresh
|
|
82
|
-
site, and the resulting bot commit does not start another round.
|
|
71
|
+
`.gitlab/ci/yad-docs.yml` included from the root `.gitlab-ci.yml`) builds every committed site and
|
|
72
|
+
deploys them on each push to the default branch. It does not check staleness and commits nothing:
|
|
73
|
+
regenerating a site's `src/data/*.ts` stays a local `yad docs sync --refresh` that a person commits.
|
|
74
|
+
So it cannot start itself again; a **concurrency group** (`yad-docs-pages`, cancel-in-progress) keeps
|
|
75
|
+
it to one deploy at a time.
|
|
76
|
+
|
|
77
|
+
A site that fails to build fails the run, and the run names every site that failed. A red run uploads
|
|
78
|
+
nothing, so the last good deployment stays live. (Before this, a failed site was silently left out and
|
|
79
|
+
the run went green — that is how a site could lose a page.) `yad doctor` warns (`docs-workflow`) when the wired file still deploys after a failed site build (as yadflow 4.5.0 and older wrote it); run
|
|
80
|
+
`yad docs sync --wire` again and commit it.
|
|
@@ -146,6 +146,10 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
146
146
|
case set the variable to a real tag. The variable lives in project settings, so it survives
|
|
147
147
|
every `yad` sync. Single value only — `tags: [$VAR]` is one tag equal to the whole variable,
|
|
148
148
|
not a comma-split.
|
|
149
|
+
- **Git LFS needs nothing extra.** For a Product that uses Git LFS, the runner installs LFS git
|
|
150
|
+
hooks that fail without `git-lfs`, and the job's `node:20` image has none — so the job downloads a
|
|
151
|
+
pinned `git-lfs` itself before its checkout (#327; a Product without LFS skips it). A
|
|
152
|
+
`before_script` added by hand to install `git-lfs` can be removed after `yad update`.
|
|
149
153
|
3. Commit the workflow to the Product. GitHub needs nothing else — the ephemeral `github.token` reads the
|
|
150
154
|
PR and pushes the merge advance to the default branch, and the workflow's reconcile **schedule**
|
|
151
155
|
runs automatically (no setup) as the safety net that recovers a merge whose run failed transiently.
|
|
@@ -81,6 +81,24 @@ yad-gate-sync:
|
|
|
81
81
|
# jq streams glab's concatenated per-page --paginate arrays natively.
|
|
82
82
|
- JQ_VERSION=1.7.1
|
|
83
83
|
- curl -fsSL "https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/jq-linux-amd64" -o /usr/local/bin/jq && chmod +x /usr/local/bin/jq
|
|
84
|
+
# Pinned git-lfs, ONLY for a Product that uses Git LFS (issue #327). The runner clones an LFS repo
|
|
85
|
+
# with its own git-lfs and installs LFS git hooks (post-checkout, pre-push, …) that exit with an
|
|
86
|
+
# error when git-lfs is missing — and node:20 has none. So the `git checkout` below failed, and so
|
|
87
|
+
# would the push of the advance. A Product without LFS skips the download. Only the clone's own
|
|
88
|
+
# signs count (its hooks, its .git/config, its .gitattributes) — never a global git setting.
|
|
89
|
+
# The download is checked against the release's published SHA-256 (sha256sums.asc) before it is
|
|
90
|
+
# unpacked: this job holds a token that can push the default branch, and git-lfs then runs on every
|
|
91
|
+
# checkout and push. One command per line, not an `&&` chain — `set -e` does not stop on a failure
|
|
92
|
+
# inside a chain, so a bad checksum would fall through to the checkout.
|
|
93
|
+
- |
|
|
94
|
+
GIT_LFS_VERSION=3.8.0
|
|
95
|
+
GIT_LFS_SHA256=e455e00f15d9b95661b8d53498ffb0c3367962cf1ec73c31ab7369516cd6ab8d
|
|
96
|
+
if ! command -v git-lfs >/dev/null 2>&1 && { grep -qs git-lfs "$(git rev-parse --git-path hooks)"/* || git config --local --get-regexp '^filter\.lfs\.' >/dev/null 2>&1 || grep -qs 'filter=lfs' .gitattributes; }; then
|
|
97
|
+
curl -fsSL "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-amd64-v${GIT_LFS_VERSION}.tar.gz" -o /tmp/git-lfs.tar.gz
|
|
98
|
+
echo "${GIT_LFS_SHA256} /tmp/git-lfs.tar.gz" | sha256sum -c -
|
|
99
|
+
tar -xzf /tmp/git-lfs.tar.gz -C /tmp "git-lfs-${GIT_LFS_VERSION}/git-lfs"
|
|
100
|
+
install -m 0755 "/tmp/git-lfs-${GIT_LFS_VERSION}/git-lfs" /usr/local/bin/git-lfs
|
|
101
|
+
fi
|
|
84
102
|
- git config user.name "yad-gate-sync" && git config user.email "yad-gate-sync@noreply.${CI_SERVER_HOST}"
|
|
85
103
|
- git remote set-url origin "https://oauth2:${SDLC_GATE_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
|
86
104
|
- export GITLAB_TOKEN="$SDLC_GATE_TOKEN" GITLAB_HOST="$CI_SERVER_URL"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-reconcile
|
|
3
|
-
description: 'Phase 6 maintenance/CI — the change reconciler (mirrors yad-docs-sync; NEVER a gate). Detects post-lock DRIFT and ORPHANS across feature threads: shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread, a broken lineage (cycle, missing parent, or a thread cache that disagrees with the computed root), and open hotfix reconcile debt — reporting which thread drifted and WHY. `check` (default, read-only) reports; `refresh` points the human at opening a reconcile change-epic with yad-change (never silent); `wire` commits an advisory CI job ([skip ci] + concurrency
|
|
3
|
+
description: 'Phase 6 maintenance/CI — the change reconciler (mirrors yad-docs-sync; NEVER a gate). Detects post-lock DRIFT and ORPHANS across feature threads: shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread, a broken lineage (cycle, missing parent, or a thread cache that disagrees with the computed root), and open hotfix reconcile debt — reporting which thread drifted and WHY. `check` (default, read-only) reports; `refresh` points the human at opening a reconcile change-epic with yad-change (never silent); `wire` commits an advisory CI job ([skip ci] + concurrency) that runs the check on push. The hard merge BLOCK is the lineage-check / epic-open / reconcile-debt CI gates — this only DISCOVERS. Use when the user says "reconcile the changes", "check for thread drift", "is anything shipped without an epic", or "find open hotfix debt".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Change Reconciler (Phase 6, the drift/orphan sweep)
|
|
@@ -65,7 +65,7 @@ that feature — then thread the reconcile change off that stub (and run `yad-ba
|
|
|
65
65
|
|
|
66
66
|
### Step 3 — `wire` (advisory CI, no block)
|
|
67
67
|
Commit an advisory CI job that runs `yad reconcile --check` on push, carrying `[skip ci]` on any commit
|
|
68
|
-
it makes and a concurrency group
|
|
68
|
+
it makes and a concurrency group, so it never retriggers itself. The job **reports**;
|
|
69
69
|
it never blocks. The blocking enforcement is the `yad-checks` gates, not this.
|
|
70
70
|
|
|
71
71
|
### Step 4 — Report
|