yadflow 2.5.0 → 2.7.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 +8 -2
- package/README.md +65 -22
- package/bin/yad.mjs +27 -1
- package/cli/docs.mjs +298 -0
- package/cli/doctor.mjs +1 -0
- package/cli/manifest.mjs +23 -2
- package/cli/ship.mjs +37 -0
- package/docs/index.html +44 -13
- package/package.json +2 -2
- package/skills/sdlc/config.yaml +26 -2
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +11 -4
- package/skills/yad-checks/references/check-gates.md +58 -2
- package/skills/yad-checks/templates/checks/commit-message.sh +82 -0
- package/skills/yad-checks/templates/github/yad-checks.yml +27 -0
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +36 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +20 -0
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +39 -0
- package/skills/yad-commit/SKILL.md +66 -0
- package/skills/yad-connect-docs/SKILL.md +132 -0
- package/skills/yad-connect-docs/references/docs-registry.md +74 -0
- package/skills/yad-docs/SKILL.md +159 -0
- package/skills/yad-docs/references/data-mapping.md +75 -0
- package/skills/yad-docs/references/theme-map.md +69 -0
- package/skills/yad-docs/templates/app/README.md +31 -0
- package/skills/yad-docs/templates/app/eslint.config.js +23 -0
- package/skills/yad-docs/templates/app/index.html +17 -0
- package/skills/yad-docs/templates/app/package-lock.json +4030 -0
- package/skills/yad-docs/templates/app/package.json +35 -0
- package/skills/yad-docs/templates/app/public/favicon.svg +28 -0
- package/skills/yad-docs/templates/app/public/logo.svg +39 -0
- package/skills/yad-docs/templates/app/public/vite.svg +1 -0
- package/skills/yad-docs/templates/app/src/App.tsx +98 -0
- package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +101 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/AnimatedMessage.tsx +101 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/ConnectionLine.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +216 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +153 -0
- package/skills/yad-docs/templates/app/src/components/Controls/PlaybackBar.tsx +284 -0
- package/skills/yad-docs/templates/app/src/components/Controls/StepDetail.tsx +167 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/HandlerLogicSnippet.tsx +41 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/RequestPayloadPreview.tsx +46 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +88 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/StatusCard.tsx +76 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/TriggerEventCard.tsx +45 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocPageShell.tsx +80 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocSectionCard.tsx +55 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocTableOfContents.tsx +79 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/RoleCard.tsx +67 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/ApiReferenceSection.tsx +108 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/CancelabilitySection.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/CriticalRunbookSection.tsx +177 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DataMigrationSection.tsx +102 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DbSchemaSection.tsx +98 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DeploymentGuideSection.tsx +104 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DriverIntegrationSection.tsx +127 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/ExecutiveSummarySection.tsx +69 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/FlowOverviewSection.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/FlowPathsChecklistSection.tsx +96 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/MiddlewareChainSection.tsx +107 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/MonitoringAlertingSection.tsx +106 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/NotificationLocalizationSection.tsx +102 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/PMRoadmapSection.tsx +133 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/PerformanceTestingSection.tsx +91 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/RiderIntegrationSection.tsx +99 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/SecuritySection.tsx +74 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/StatusMachineSection.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/TestPlanSection.tsx +163 -0
- package/skills/yad-docs/templates/app/src/components/Logs/SystemLogsTerminal.tsx +126 -0
- package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/Reference/BullMQJobsList.tsx +60 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DecisionTreeView.tsx +49 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DeeplinkActionsChips.tsx +69 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DriverUIStatesTable.tsx +61 -0
- package/skills/yad-docs/templates/app/src/components/Reference/FeatureFlagMatrix.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/Reference/RiderUIStatesTable.tsx +61 -0
- package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +217 -0
- package/skills/yad-docs/templates/app/src/components/Reference/StakeholderToggle.tsx +41 -0
- package/skills/yad-docs/templates/app/src/components/Reference/TroubleshootingSection.tsx +93 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +148 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/SidebarFooter.tsx +40 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/StepList.tsx +234 -0
- package/skills/yad-docs/templates/app/src/components/shared/Badge.tsx +28 -0
- package/skills/yad-docs/templates/app/src/components/shared/CommandPalette.tsx +213 -0
- package/skills/yad-docs/templates/app/src/components/shared/Icon.tsx +21 -0
- package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +42 -0
- package/skills/yad-docs/templates/app/src/data/components.ts +74 -0
- package/skills/yad-docs/templates/app/src/data/docSections.ts +231 -0
- package/skills/yad-docs/templates/app/src/data/paths.ts +2319 -0
- package/skills/yad-docs/templates/app/src/data/referenceData.ts +392 -0
- package/skills/yad-docs/templates/app/src/data/roles.ts +145 -0
- package/skills/yad-docs/templates/app/src/data/types.ts +79 -0
- package/skills/yad-docs/templates/app/src/hooks/useAnimationQueue.ts +41 -0
- package/skills/yad-docs/templates/app/src/hooks/usePlayback.ts +100 -0
- package/skills/yad-docs/templates/app/src/hooks/useStakeholderFilter.ts +10 -0
- package/skills/yad-docs/templates/app/src/index.css +121 -0
- package/skills/yad-docs/templates/app/src/main.tsx +13 -0
- package/skills/yad-docs/templates/app/src/pages/RoleSelectPage.tsx +34 -0
- package/skills/yad-docs/templates/app/src/pages/StakeholderDocPage.tsx +98 -0
- package/skills/yad-docs/templates/app/src/pages/SubPathDetailPage.tsx +282 -0
- package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +42 -0
- package/skills/yad-docs/templates/app/src/store/useFlowStore.ts +197 -0
- package/skills/yad-docs/templates/app/src/utils/iconMap.ts +46 -0
- package/skills/yad-docs/templates/app/tsconfig.app.json +28 -0
- package/skills/yad-docs/templates/app/tsconfig.json +7 -0
- package/skills/yad-docs/templates/app/tsconfig.node.json +26 -0
- package/skills/yad-docs/templates/app/vite.config.ts +10 -0
- package/skills/yad-docs-overview/SKILL.md +129 -0
- package/skills/yad-docs-overview/references/pipeline-model.md +102 -0
- package/skills/yad-docs-sync/SKILL.md +99 -0
- package/skills/yad-docs-sync/references/staleness.md +81 -0
- package/skills/yad-engineer-review/SKILL.md +86 -0
- package/skills/{yad-ship → yad-engineer-review}/references/ship-and-record.md +2 -2
- package/skills/{yad-ship → yad-engineer-review}/templates/.coderabbit.yaml +1 -1
- package/skills/yad-epic/references/state-schema.md +1 -1
- package/skills/yad-implement/SKILL.md +1 -1
- package/skills/yad-implement/references/implement-conventions.md +1 -1
- package/skills/yad-open-pr/SKILL.md +72 -0
- package/skills/yad-pr-template/templates/checks/pr-template.sh +62 -0
- package/skills/yad-pr-template/templates/checks/pr-title.sh +51 -0
- package/skills/yad-run/SKILL.md +2 -2
- package/skills/yad-run/references/run-loop.md +4 -4
- package/skills/yad-ship/SKILL.md +44 -66
- package/skills/yad-spec/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
# [2.
|
|
1
|
+
# [2.7.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.6.0...v2.7.0) (2026-06-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* drop unused today param from runDocs (lint) ([1c255f1](https://github.com/abdelrahmannasr/yadflow/commit/1c255f1c848b9571502e29b142a07366612d638c))
|
|
7
|
+
* publish per-epic docs sites in CI + check shell-version staleness ([9862646](https://github.com/abdelrahmannasr/yadflow/commit/9862646fbe910de8ad8248a5f1bb586a5604b18f))
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
### Features
|
|
5
11
|
|
|
6
|
-
*
|
|
12
|
+
* add interactive documentation skills + yad docs CLI ([4bf7a25](https://github.com/abdelrahmannasr/yadflow/commit/4bf7a25c38e28ef47704a8e2f5acec2f724e4e29))
|
|
7
13
|
|
|
8
14
|
# [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
|
|
9
15
|
|
package/README.md
CHANGED
|
@@ -60,7 +60,10 @@ human**. Detailed walkthroughs for each phase follow below.
|
|
|
60
60
|
| `skills/yad-pr-template/` | Build Step D: install the platform PR/MR template + risk routing (code repos **and** the hub). |
|
|
61
61
|
| `skills/yad-review-comments/` | Install platform-matched PR/MR review-comment scaffolds (code repos and the hub). |
|
|
62
62
|
| `skills/yad-hub-bridge/` | The templated PR/MR **review bridge**: open a review PR/MR on the hub and sync platform approvals/comments into the file ledger. |
|
|
63
|
-
| `skills/yad-
|
|
63
|
+
| `skills/yad-commit/` | Build helper: commit ONE staged atomic change by the conventions (Conventional subject, trailers, `--ai` footer, ≤3-file guard). |
|
|
64
|
+
| `skills/yad-open-pr/` | Build helper: open a code-repo task PR/MR from the committed template (push, prefill, roster auto-assign). |
|
|
65
|
+
| `skills/yad-ship/` | Build helper: commit **and** open the task PR/MR in one step (`yad commit` then `yad open-pr`). |
|
|
66
|
+
| `skills/yad-engineer-review/` | Build Step E: AI review (advisory) → engineer review → merge + record in the build log. |
|
|
64
67
|
| `skills/yad-backfill/` | Generate a human-verified spec for already-built code (Repomix), gated per touched feature. |
|
|
65
68
|
| `skills/yad-run/` | Phase 4 orchestrator: drive a story's back half on the `automation` dial; kill switch. |
|
|
66
69
|
| `skills/yad-status/` | Read-only view: front chain, build-half dials, trust record, fleet roll-up. |
|
|
@@ -94,13 +97,15 @@ with `npx` from your **product hub** repo — no clone needed.
|
|
|
94
97
|
| `yad gate ci [--branch <head>] [--pr <n>]` | The CI entry the hub workflow calls on review/merge events: derive the epic/artifact from the `review/EP-*` branch, run the same sync, and commit **only the ledger** to the hub default branch (sweep every open review PR when no `--branch`). |
|
|
95
98
|
| `yad commit --type <t> -m <subject>` | Commit by the SDLC convention — Conventional subject, `Task`/`Contract-Change`/`Co-Authored-By` trailers, atomic-file guard. |
|
|
96
99
|
| `yad open-pr [--repo <name>]` | Open a code-repo **task** PR/MR from the repo's platform template (build half). |
|
|
100
|
+
| `yad ship --type <t> -m <subject>` | Commit **and** open the task PR/MR in one step (`yad commit` then `yad open-pr`). |
|
|
97
101
|
| `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. |
|
|
98
102
|
| `npx yadflow --version` | Print the installed CLI version. |
|
|
99
103
|
|
|
100
104
|
Flags: `--dir <path>` targets a project other than the cwd; `--force` re-copies unchanged files (or
|
|
101
105
|
bypasses the commit atomic guard). Commit flags: `--type`, `-m/--message`, `--task`, `--ai
|
|
102
106
|
<claude\|copilot\|cursor\|coderabbit\|none>`, `--contract-change`, `--dry-run`. `open-pr` flags:
|
|
103
|
-
`--repo`, `--risk <low\|medium\|high>`, `--contract-change`.
|
|
107
|
+
`--repo`, `--risk <low\|medium\|high>`, `--contract-change`. `ship` takes the union of the `commit`
|
|
108
|
+
and `open-pr` flags (it runs `open-pr` only if the commit lands).
|
|
104
109
|
|
|
105
110
|
### The PR-driven review gate
|
|
106
111
|
|
|
@@ -128,7 +133,7 @@ simultaneous advancements can be lost; the next event or scheduled sweep re-sync
|
|
|
128
133
|
### What `setup` walks you through (10 steps)
|
|
129
134
|
|
|
130
135
|
1. **Preflight** — confirm the hub is a git repo (offers `git init`); check `git`/`node`/`npx`.
|
|
131
|
-
2. **Install the module** — copy all
|
|
136
|
+
2. **Install the module** — copy all 29 `yad-*` skills into the IDE skill dirs you pick
|
|
132
137
|
(`.claude/`, `.agents/`, `.zencoder/`, `.opencode/`) and register `_bmad/sdlc/`.
|
|
133
138
|
3. **Hub platform & roster** — detect GitHub/GitLab from the remote; record reviewers → `.sdlc/hub.json`.
|
|
134
139
|
4. **Connect a design tool** — record the design tool (Figma / pencil / none) → `.sdlc/design.json` so
|
|
@@ -184,7 +189,7 @@ with a fix-it hint per finding. Failures carry stable, greppable codes, also pri
|
|
|
184
189
|
|
|
185
190
|
Filing a bug? Attach `yad doctor --json` — it contains no secrets (names, paths, and check results only).
|
|
186
191
|
|
|
187
|
-
## Agent skills (all
|
|
192
|
+
## Agent skills (all 29)
|
|
188
193
|
|
|
189
194
|
The CLI **installs and wires** the module; the skills below are the **agents you invoke by name** in your
|
|
190
195
|
AI IDE (e.g. *“run `yad-epic`”*) to actually do the work. State lives in files you can also edit
|
|
@@ -213,6 +218,27 @@ directly. Each skill stops at a gate and never auto-advances unless a step has *
|
|
|
213
218
|
tokens), detecting the **DeepTutor CLI on PATH** (a subprocess like Repomix — DeepTutor ships no MCP)
|
|
214
219
|
and degrading to **harness-native** tutoring when absent. Idempotent and refreshable; one connection
|
|
215
220
|
per project.
|
|
221
|
+
- **`yad-connect-docs`** — Connects a docs/Pages target (GitHub Pages / GitLab Pages, auto-detected from
|
|
222
|
+
`hub.json`) so the generated documentation sites can deploy. Records the target + scope + base path in
|
|
223
|
+
`.sdlc/docs.json` (local-user auth, no stored tokens), degrading to **build-only** when no Pages host /
|
|
224
|
+
CLI is present. Idempotent and refreshable; one connection per project.
|
|
225
|
+
|
|
226
|
+
### Living documentation (generated, themed, auto-kept-fresh)
|
|
227
|
+
|
|
228
|
+
- **`yad-docs`** — Generates an **interactive documentation site** for an epic (a React + Vite + Tailwind
|
|
229
|
+
SPA: an animated front-stage flow canvas + role-based stakeholder doc pages) from the authored
|
|
230
|
+
artifacts — `epic.md`, `architecture.md`, the locked `contract.md`, `ui-design.md`, the stories — into
|
|
231
|
+
`epics/EP-<slug>/docs-site/`, themed by the **connected design system** (`DESIGN.md` / `design.json`
|
|
232
|
+
tokens → the site's CSS). The content lives in generated `src/data/*.ts`; the shell is a vendored
|
|
233
|
+
template. An **output enrichment, never a gate** — it never touches epic state, approvals, or the
|
|
234
|
+
contract lock. `generate` / `refresh` / `deploy`.
|
|
235
|
+
- **`yad-docs-overview`** — Generates the project **SDLC-overview site** (`docs/sdlc-site/`) — every
|
|
236
|
+
stage from setup → ship as flow paths / system components / stakeholder roles, reusing the same shell —
|
|
237
|
+
superseding the hand-maintained `docs/index.html`.
|
|
238
|
+
- **`yad-docs-sync`** — Keeps the sites fresh: detects staleness (a content hash of the authored
|
|
239
|
+
artifacts + the connected repos' HEAD shas vs each site's build manifest), regenerates + redeploys, and
|
|
240
|
+
can wire a CI job that rebuilds on push. Generalizes the rule that feature work must hand-update the
|
|
241
|
+
docs — the overview now regenerates whenever the skill set / pipeline changes.
|
|
216
242
|
|
|
217
243
|
### The learning layer (cross-cutting — any member, any stage)
|
|
218
244
|
|
|
@@ -276,15 +302,25 @@ directly. Each skill stops at a gate and never auto-advances unless a step has *
|
|
|
276
302
|
(≤3 files) on its own branch. The diff stays inside the files the task declared (flag and STOP if it
|
|
277
303
|
would grow). Commit ends with the task ID; `Contract-Change: yes` only if it touches the locked
|
|
278
304
|
contract surface.
|
|
279
|
-
- **`yad-checks`** — Step C, the production-safety gates. Wire and run
|
|
305
|
+
- **`yad-checks`** — Step C, the production-safety gates. Wire and run the CI gates: **spec-link**
|
|
280
306
|
(every change links a real story/spec), **contract-check** (a contract-surface diff without a
|
|
281
|
-
re-locked contract FAILS),
|
|
307
|
+
re-locked contract FAILS), **build/test/lint**, **verified-commits** (signed + roster-known authors),
|
|
308
|
+
and the **pattern gates** — **commit-message** (Conventional subject + trailer order), **pr-title**,
|
|
309
|
+
and **pr-template** (the PR/MR body uses the template). Profile-aware (`code`|`hub`), so they run on
|
|
310
|
+
both code repos and the product hub. CI-agnostic bash for GitHub Actions and GitLab CI.
|
|
282
311
|
- **`yad-pr-template`** — Step D. Detect the repo's platform and commit the matching PR/MR template with
|
|
283
312
|
an Impact & Risk block; high risk (or a contract/auth/payments surface) routes the review to domain
|
|
284
|
-
owners. Includes `risk-route.sh
|
|
285
|
-
- **`yad-
|
|
286
|
-
|
|
287
|
-
|
|
313
|
+
owners. Includes `risk-route.sh` plus the `pr-title.sh` / `pr-template.sh` gate scripts.
|
|
314
|
+
- **`yad-commit`** — build helper. Commit ONE staged atomic change by the conventions (Conventional
|
|
315
|
+
subject, `Task → Contract-Change → Co-Authored-By` trailers, the `--ai` co-author footer, the ≤3-file
|
|
316
|
+
atomic guard). Drives `yad commit`.
|
|
317
|
+
- **`yad-open-pr`** — build helper. Open a code-repo task PR/MR from the committed template: push the
|
|
318
|
+
branch, prefill the body, auto-assign the repo-scoped roster. Drives `yad open-pr`.
|
|
319
|
+
- **`yad-ship`** — build helper. Commit **and** open the task PR/MR in one step (`yad commit` then
|
|
320
|
+
`yad open-pr`; the PR step runs only if the commit lands). Drives `yad ship`.
|
|
321
|
+
- **`yad-engineer-review`** — Step E. AI review (CodeRabbit, advisory) → engineer review (the human gate,
|
|
322
|
+
owner + 1 reviewer with the same escalation) → on merge, record the ship in the epic build-log and
|
|
323
|
+
update the story state so the epic → story → task → PR chain stays traceable.
|
|
288
324
|
- **`yad-backfill`** — Step G. Generate specs for already-built features in an existing repo so new work
|
|
289
325
|
doesn't break them: pack one feature at a time with Repomix, write a DRAFT spec, require human approval
|
|
290
326
|
before it counts. A change is blocked only until the features it touches have approved specs.
|
|
@@ -382,12 +418,16 @@ build half by hand”** below.
|
|
|
382
418
|
11. `yad-implement story:<id> repo:<repo> task:<T0N>` → one atomic task = one branch = one commit
|
|
383
419
|
(repeat per task). Commit by convention with **`yad commit --type <t> -m <subject> [--ai <tool>]`**
|
|
384
420
|
(Task/Contract-Change/Co-Authored-By trailers, atomic-file guard).
|
|
385
|
-
12. `yad-checks repo:<repo> action: run` → spec-link, contract-check, build/test/lint,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
in
|
|
421
|
+
12. `yad-checks repo:<repo> action: run` → spec-link, contract-check, build/test/lint, verified-commits
|
|
422
|
+
(platform-Verified signature + roster-allowlisted author), and commit-message must pass. (The
|
|
423
|
+
`pr-title` / `pr-template` gates need the PR title + body, so they run in CI once the PR exists —
|
|
424
|
+
step 13.)
|
|
425
|
+
13. Open the PR/MR from the wired template with **`yad open-pr --repo <repo> [--risk <level>]`** (or do
|
|
426
|
+
12+13 in one step with **`yad ship --type <t> -m <subject> --repo <repo>`**). The PR's CI now also
|
|
427
|
+
runs the `pr-title` and `pr-template` gates; `yad-pr-template repo:<repo> action: route` prints the
|
|
428
|
+
required reviewers from the Impact & Risk block.
|
|
429
|
+
14. `yad-engineer-review` → `ai-review` (advisory) → `approve` (the human engineer gate) → `ship` (merge,
|
|
430
|
+
record in `build-log.json`, update story status to `in-build`/`shipped`).
|
|
391
431
|
- **Multi-repo:** repeat 10–14 in each repo, all from the **one** locked contract.
|
|
392
432
|
- **Existing code:** `yad-backfill` first, to produce a human-verified spec for a built feature.
|
|
393
433
|
|
|
@@ -514,16 +554,19 @@ the product repo. Code repos are **separate git repos** under `demo-repos/<repo>
|
|
|
514
554
|
(`feat/<story>-<task>-…`) = one PR. The diff stays inside the files the task declared. Commit with
|
|
515
555
|
**`yad commit`** — it builds the conventional subject, derives the `Task:` trailer from the branch
|
|
516
556
|
(add `--contract-change` only if the locked surface is touched), appends an optional `--ai` co-author,
|
|
517
|
-
and refuses a non-atomic stage. Open the PR with **`yad open-pr --repo <repo>`** (template prefilled)
|
|
518
|
-
|
|
557
|
+
and refuses a non-atomic stage. Open the PR with **`yad open-pr --repo <repo>`** (template prefilled),
|
|
558
|
+
or do both in one step with **`yad ship`** (commit then open-pr).
|
|
559
|
+
3. **Check gates** — `yad-checks` wires the CI gates (GitHub + GitLab) that must pass before merge:
|
|
519
560
|
**spec-link** (links a real story/spec), **contract-check** (a contract-surface change without
|
|
520
561
|
`Contract-Change` + a re-locked contract FAILS, routing back to the architecture gate),
|
|
521
|
-
**build/test/lint
|
|
562
|
+
**build/test/lint**, **verified-commits**, and the **pattern gates** **commit-message** / **pr-title**
|
|
563
|
+
/ **pr-template** (profile-aware `code`|`hub`, so they also run on the product hub). They fail closed
|
|
564
|
+
on a bad base ref.
|
|
522
565
|
4. **PR/MR template + risk routing** — `yad-pr-template` drops the platform-matched template with an
|
|
523
566
|
Impact & Risk block; `high` risk (or a contract/auth/payments surface) routes the review to domain
|
|
524
567
|
owners (`risk-route.sh`), the same escalation as the gate.
|
|
525
|
-
5. **AI review → engineer review →
|
|
526
|
-
the authority); a human engineer approves (owner + 1 reviewer, escalating to domain owners); on
|
|
568
|
+
5. **AI review → engineer review → merge** — `yad-engineer-review`: CodeRabbit is an advisory first pass
|
|
569
|
+
(never the authority); a human engineer approves (owner + 1 reviewer, escalating to domain owners); on
|
|
527
570
|
merge the ship is recorded in `.sdlc/build-log.json` and the story state becomes `in-build` →
|
|
528
571
|
`shipped`. The epic → story → task → PR → mergeCommit chain is traceable both ways.
|
|
529
572
|
|
|
@@ -551,7 +594,7 @@ with their dials, per repo) and `trust-log.json` (every run's verdict). See
|
|
|
551
594
|
- **Drive a story's back half:** `yad-run {story} {repo}` walks `spec → tasks → implement → checks`,
|
|
552
595
|
reading each step's dial. On `machine_advance` it advances on its own; on `human_approve` it stops
|
|
553
596
|
for a human; on any FAIL, scope overrun, or contract-surface touch it **halts and pulls in a human**.
|
|
554
|
-
It always stops at the engineer review (`yad-
|
|
597
|
+
It always stops at the engineer review (`yad-engineer-review`), which is never automated.
|
|
555
598
|
- **Read the trust log:** `yad-status {epic}` shows each back step's dial, status, and trust record —
|
|
556
599
|
runs, % `approved-unchanged`, and whether that clears the threshold (`automation.trust_threshold` in
|
|
557
600
|
`config.yaml`, default ≥5 runs and ≥80% unchanged). The engineer review records each run's verdict
|
package/bin/yad.mjs
CHANGED
|
@@ -8,7 +8,9 @@ import { gateOpen, gateSync, gateComments, gateStatus, gateCi } from '../cli/gat
|
|
|
8
8
|
import { isValidEpicId } from '../cli/epic-state.mjs';
|
|
9
9
|
import { runCommit } from '../cli/commit.mjs';
|
|
10
10
|
import { runOpenPr } from '../cli/openpr.mjs';
|
|
11
|
+
import { runShip } from '../cli/ship.mjs';
|
|
11
12
|
import { runRepo } from '../cli/repo.mjs';
|
|
13
|
+
import { runDocs } from '../cli/docs.mjs';
|
|
12
14
|
import { runDoctor } from '../cli/doctor.mjs';
|
|
13
15
|
|
|
14
16
|
const HELP = `${c.bold('yad')} — setup, review-gate & build helpers for the SDLC Workflow module ${c.dim('v' + VERSION)}
|
|
@@ -34,9 +36,16 @@ ${c.bold('Review gate (front half)')}
|
|
|
34
36
|
${c.bold('Build helpers')}
|
|
35
37
|
yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
|
|
36
38
|
yad open-pr [--repo <name>] Open a code-repo task PR/MR from the template
|
|
39
|
+
yad ship --type <t> -m <subject> Commit AND open the task PR/MR in one step
|
|
37
40
|
yad repo list Show connected repos (fresh / stale)
|
|
38
41
|
yad repo refresh [name] Re-pack a stale repo (a human decision)
|
|
39
42
|
|
|
43
|
+
${c.bold('Interactive docs (generated sites)')}
|
|
44
|
+
yad docs list Show the docs target + per-site freshness
|
|
45
|
+
yad docs build [--epic <id>|--overview] npm-build a generated doc site
|
|
46
|
+
yad docs deploy [--epic <id>|--overview] Build + report the Pages deploy
|
|
47
|
+
yad docs sync [--check|--refresh|--wire] Staleness sweep; --wire installs the Pages CI
|
|
48
|
+
|
|
40
49
|
${c.bold('Options')}
|
|
41
50
|
--dir <path> Target project root (default: cwd)
|
|
42
51
|
--type <t> commit: feat|fix|docs|refactor|test|perf|build|ci|chore|revert
|
|
@@ -46,6 +55,9 @@ ${c.bold('Options')}
|
|
|
46
55
|
--contract-change commit/open-pr: mark the contract surface touched
|
|
47
56
|
--risk <level> open-pr: low|medium|high (default low)
|
|
48
57
|
--repo <name> open-pr: target a registered repo by name
|
|
58
|
+
--epic <id> docs: target one epic's site (EP-<slug>)
|
|
59
|
+
--overview docs: target the project SDLC-overview site
|
|
60
|
+
--check/--refresh/--wire docs sync: report stale / rebuild / install Pages CI
|
|
49
61
|
--dry-run commit: print the message, do not commit
|
|
50
62
|
--force commit: bypass the atomic-file guard / re-copy unchanged files
|
|
51
63
|
--branch <head> gate ci: the review PR/MR head branch (review/EP-<slug>/<artifact>)
|
|
@@ -54,7 +66,7 @@ ${c.bold('Options')}
|
|
|
54
66
|
-h, --help Show this help
|
|
55
67
|
-v, --version Print version`;
|
|
56
68
|
|
|
57
|
-
const VALUE_FLAGS = new Set(['--dir', '--type', '--message', '--task', '--ai', '--risk', '--repo', '--platform', '--base', '--title', '--scope', '--branch', '--pr']);
|
|
69
|
+
const VALUE_FLAGS = new Set(['--dir', '--type', '--message', '--task', '--ai', '--risk', '--repo', '--platform', '--base', '--title', '--scope', '--branch', '--pr', '--epic']);
|
|
58
70
|
|
|
59
71
|
function parseArgs(argv) {
|
|
60
72
|
const o = { _: [], dir: process.cwd(), fix: false, force: false, scope: 'all' };
|
|
@@ -64,6 +76,10 @@ function parseArgs(argv) {
|
|
|
64
76
|
else if (a === '--force') o.force = true;
|
|
65
77
|
else if (a === '--contract-change') o.contractChange = true;
|
|
66
78
|
else if (a === '--no-push') o.noPush = true;
|
|
79
|
+
else if (a === '--overview') o.overview = true;
|
|
80
|
+
else if (a === '--check') o.check = true;
|
|
81
|
+
else if (a === '--refresh') o.refresh = true;
|
|
82
|
+
else if (a === '--wire') o.wire = true;
|
|
67
83
|
else if (a === '--dry-run') o.dryRun = true;
|
|
68
84
|
else if (a === '--json') o.json = true;
|
|
69
85
|
else if (a === '-h' || a === '--help') o.help = true;
|
|
@@ -123,11 +139,21 @@ async function main() {
|
|
|
123
139
|
case 'open-pr':
|
|
124
140
|
await runOpenPr(o.dir, { repo: o.repo, platform: o.platform, base: o.base, title: o.title || o.message, task: o.task, risk: o.risk, contractChange: o.contractChange });
|
|
125
141
|
break;
|
|
142
|
+
case 'ship':
|
|
143
|
+
await runShip(o.dir, { type: o.type, message: o.message, task: o.task, ai: o.ai, contractChange: o.contractChange, dryRun: o.dryRun, force: o.force, repo: o.repo, platform: o.platform, base: o.base, title: o.title, risk: o.risk });
|
|
144
|
+
break;
|
|
126
145
|
case 'repo': {
|
|
127
146
|
const [, action, name] = o._;
|
|
128
147
|
await runRepo(o.dir, { action: action || 'list', name, today });
|
|
129
148
|
break;
|
|
130
149
|
}
|
|
150
|
+
case 'docs': {
|
|
151
|
+
const [, action] = o._;
|
|
152
|
+
if (o.epic && !isValidEpicId(o.epic)) { log(c.red(`invalid epic id: ${o.epic} (expected EP-<slug>, [a-z0-9-] only)`)); process.exitCode = 1; break; }
|
|
153
|
+
const sync = o.wire ? 'wire' : o.refresh ? 'refresh' : 'check';
|
|
154
|
+
await runDocs(o.dir, { action: action || 'list', epic: o.epic, overview: o.overview, sync, today });
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
131
157
|
default:
|
|
132
158
|
log(c.red(`unknown command: ${cmd}`));
|
|
133
159
|
log(HELP);
|
package/cli/docs.mjs
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// `yad docs list|build|deploy|sync` — the build/deploy/staleness mechanics for the interactive
|
|
2
|
+
// documentation sites generated by the yad-docs / yad-docs-overview skills. The CONTENT generation
|
|
3
|
+
// (reading epic/architecture/contract/ui/stories → writing src/data/*.ts and theming index.css) is
|
|
4
|
+
// the AI step inside those skills; this module only does the mechanical parts: npm build (a
|
|
5
|
+
// subprocess, like yad-spec shelling npx repomix), the platform Pages wiring (reusing platform.mjs),
|
|
6
|
+
// and the manifest-hash staleness check (reusing the head-sha idea from repo.mjs). It NEVER touches
|
|
7
|
+
// epic state, approvals, or the contract lock — docs are an output enrichment, not a gate.
|
|
8
|
+
//
|
|
9
|
+
// Pure mapping fns (deployTargetFromHub / siteBasePath / docsArtifactHash / docsStale / pagesWorkflow)
|
|
10
|
+
// are exported for unit tests; the side-effecting runDocs orchestrates them.
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { c, log, ok, info, warn, hand, fail, readJSON, run, has, exists } from './lib.mjs';
|
|
15
|
+
import { PROJECT_FILES, VERSION } from './manifest.mjs';
|
|
16
|
+
import { detectPlatform, platformReady } from './platform.mjs';
|
|
17
|
+
import { gitHead } from './setup.mjs';
|
|
18
|
+
import { contractSurfaceHash } from './epic-state.mjs';
|
|
19
|
+
|
|
20
|
+
// ---- registry + site locations ------------------------------------------------------------------
|
|
21
|
+
function loadDocs(root) {
|
|
22
|
+
const regPath = path.join(root, PROJECT_FILES.docsConfig);
|
|
23
|
+
return { regPath, docs: readJSON(regPath, null) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Per-epic site lives beside the epic artifacts; the overview is project-level under docs/.
|
|
27
|
+
export function siteDir(root, { epic, overview } = {}) {
|
|
28
|
+
if (overview) return path.join(root, 'docs/sdlc-site');
|
|
29
|
+
return path.join(root, 'epics', epic, 'docs-site');
|
|
30
|
+
}
|
|
31
|
+
export function manifestPath(root, { epic, overview } = {}) {
|
|
32
|
+
if (overview) return path.join(siteDir(root, { overview }), '.docs-build.json');
|
|
33
|
+
return path.join(root, 'epics', epic, '.sdlc/docs-build.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---- pure: platform/target + base path ----------------------------------------------------------
|
|
37
|
+
// hub.json platform -> the default Pages target (github-pages | gitlab-pages | none/build-only).
|
|
38
|
+
export function deployTargetFromHub(hub = {}) {
|
|
39
|
+
const platform = hub?.platform || detectPlatform(hub?.git_url || '');
|
|
40
|
+
if (platform === 'github') return 'github-pages';
|
|
41
|
+
if (platform === 'gitlab') return 'gitlab-pages';
|
|
42
|
+
return 'none';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The Vite `base` for one site: join the project base path (from docs.json) with the per-site
|
|
46
|
+
// subpath. Per-epic sites nest under epics/<id>/; the overview is the Pages root. Always a
|
|
47
|
+
// leading+trailing slash so it works as a Vite base and a router basename.
|
|
48
|
+
export function siteBasePath(docs = {}, { epic, overview } = {}) {
|
|
49
|
+
const root = (docs.basePath || '/').replace(/\/+$/, '') || '';
|
|
50
|
+
const sub = overview ? '' : epic ? `/epics/${epic}` : '';
|
|
51
|
+
const joined = `${root}${sub}`.replace(/\/+/g, '/');
|
|
52
|
+
return joined ? `${joined.replace(/\/$/, '')}/` : '/';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- pure: staleness ----------------------------------------------------------------------------
|
|
56
|
+
// The artifacts whose content a per-epic site is generated from (any that exist contribute).
|
|
57
|
+
// contract.md is deliberately EXCLUDED here: it is folded in via its CONTRACT-SURFACE block hash
|
|
58
|
+
// (docsArtifactHash's `extra`), so docs-staleness tracks the same locked surface as the contract
|
|
59
|
+
// lock — non-surface prose edits to contract.md don't needlessly mark the docs stale.
|
|
60
|
+
export function docsArtifactFiles(root, epic) {
|
|
61
|
+
const epicRoot = path.join(root, 'epics', epic);
|
|
62
|
+
const flat = ['epic.md', 'architecture.md', 'ui-design.md', 'DESIGN.md', 'test-cases.md']
|
|
63
|
+
.map((f) => path.join(epicRoot, f));
|
|
64
|
+
const storiesDir = path.join(epicRoot, 'stories');
|
|
65
|
+
const stories = exists(storiesDir)
|
|
66
|
+
? fs.readdirSync(storiesDir).filter((f) => f.endsWith('.md')).sort().map((f) => path.join(storiesDir, f))
|
|
67
|
+
: [];
|
|
68
|
+
return [...flat, ...stories].filter(exists).sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// sha256 over the (sorted) artifact files' bytes plus an optional `extra` string (the contract
|
|
72
|
+
// surface hash) — the content baseline a site was built from. Deterministic: the file list is
|
|
73
|
+
// sorted and each file is length-prefixed so concatenation is unambiguous (a + bc never collides
|
|
74
|
+
// with ab + c).
|
|
75
|
+
export function docsArtifactHash(files = [], extra = '') {
|
|
76
|
+
const h = createHash('sha256');
|
|
77
|
+
for (const f of [...files].sort()) {
|
|
78
|
+
const buf = fs.readFileSync(f);
|
|
79
|
+
h.update(`${path.basename(f)}:${buf.length}\n`);
|
|
80
|
+
h.update(buf);
|
|
81
|
+
}
|
|
82
|
+
if (extra) h.update(`\ncontract-surface:${extra}`);
|
|
83
|
+
return 'sha256:' + h.digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Current HEAD sha per repo the epic touches (mirrors repo.mjs head-sha staleness).
|
|
87
|
+
export function repoHeadsFor(root, repos = [], registry = { repos: [] }) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const name of repos) {
|
|
90
|
+
const entry = (registry.repos || []).find((r) => r.name === name);
|
|
91
|
+
if (!entry) continue;
|
|
92
|
+
out[name] = gitHead(path.resolve(root, entry.path)) || null;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Compare a build manifest to the current world; list the concrete reasons it is stale.
|
|
98
|
+
export function docsStale(manifest, { artifactHash, repoHeads = {}, templateVersion } = {}) {
|
|
99
|
+
const reasons = [];
|
|
100
|
+
if (!manifest) return { stale: true, reasons: ['never built'] };
|
|
101
|
+
if (artifactHash && manifest.artifactHash && artifactHash !== manifest.artifactHash) {
|
|
102
|
+
reasons.push('authored artifacts changed');
|
|
103
|
+
}
|
|
104
|
+
for (const [repo, head] of Object.entries(repoHeads)) {
|
|
105
|
+
const was = (manifest.repoHeads || {})[repo];
|
|
106
|
+
if (head && was && head !== was) reasons.push(`repo ${repo} HEAD advanced`);
|
|
107
|
+
}
|
|
108
|
+
if (templateVersion && manifest.templateVersion && templateVersion !== manifest.templateVersion) {
|
|
109
|
+
reasons.push(`doc shell upgraded (${manifest.templateVersion} → ${templateVersion})`);
|
|
110
|
+
}
|
|
111
|
+
return { stale: reasons.length > 0, reasons };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---- pure: the Pages CI workflow (committed by `yad docs sync --wire`) ---------------------------
|
|
115
|
+
// GitHub Actions deploy-pages, or a GitLab `pages` job. Both assemble a single `public/` tree: the
|
|
116
|
+
// overview at the root and every per-epic site under `epics/<id>/` — matching siteBasePath's nesting
|
|
117
|
+
// (overview at `<base>/`, epics at `<base>/epics/EP-<slug>/`). A concurrency group prevents the
|
|
118
|
+
// deploy from retriggering. The shared shell script keeps the two platforms byte-for-byte aligned.
|
|
119
|
+
const BUILD_PUBLIC = [
|
|
120
|
+
'mkdir -p public',
|
|
121
|
+
'if [ -d docs/sdlc-site ]; then (cd docs/sdlc-site && npm ci && npm run build) && cp -r docs/sdlc-site/dist/. public/; fi',
|
|
122
|
+
'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/"; done',
|
|
123
|
+
];
|
|
124
|
+
export function pagesWorkflow(platform) {
|
|
125
|
+
if (platform === 'gitlab') {
|
|
126
|
+
return `# yad-managed — built by \`yad docs sync --wire\`. include it from .gitlab-ci.yml:
|
|
127
|
+
# include: { local: .gitlab/ci/yad-docs.yml }
|
|
128
|
+
# Edit the skill, not this file.
|
|
129
|
+
pages:
|
|
130
|
+
stage: deploy
|
|
131
|
+
image: node:20
|
|
132
|
+
rules:
|
|
133
|
+
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
134
|
+
script:
|
|
135
|
+
${BUILD_PUBLIC.map((l) => ` - '${l.replace(/'/g, "'\\''")}'`).join('\n')}
|
|
136
|
+
artifacts:
|
|
137
|
+
paths: [public]
|
|
138
|
+
resource_group: pages
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
return `# yad-managed — built by \`yad docs sync --wire\`. Edit the skill, not this file.
|
|
142
|
+
name: yad-docs
|
|
143
|
+
on:
|
|
144
|
+
push:
|
|
145
|
+
branches: [main, master]
|
|
146
|
+
workflow_dispatch:
|
|
147
|
+
permissions:
|
|
148
|
+
contents: read
|
|
149
|
+
pages: write
|
|
150
|
+
id-token: write
|
|
151
|
+
concurrency:
|
|
152
|
+
group: yad-docs-pages
|
|
153
|
+
cancel-in-progress: true
|
|
154
|
+
jobs:
|
|
155
|
+
build-deploy:
|
|
156
|
+
runs-on: ubuntu-latest
|
|
157
|
+
environment:
|
|
158
|
+
name: github-pages
|
|
159
|
+
url: \${{ steps.deployment.outputs.page_url }}
|
|
160
|
+
steps:
|
|
161
|
+
- uses: actions/checkout@v4
|
|
162
|
+
- uses: actions/setup-node@v4
|
|
163
|
+
with:
|
|
164
|
+
node-version: 20
|
|
165
|
+
- name: Build the overview + per-epic sites into ./public
|
|
166
|
+
run: |
|
|
167
|
+
${BUILD_PUBLIC.map((l) => ` ${l}`).join('\n')}
|
|
168
|
+
- uses: actions/configure-pages@v5
|
|
169
|
+
- uses: actions/upload-pages-artifact@v3
|
|
170
|
+
with:
|
|
171
|
+
path: public
|
|
172
|
+
- id: deployment
|
|
173
|
+
uses: actions/deploy-pages@v4
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
export function pagesWorkflowPath(platform) {
|
|
177
|
+
return platform === 'gitlab' ? '.gitlab/ci/yad-docs.yml' : '.github/workflows/yad-docs.yml';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---- build (subprocess) -------------------------------------------------------------------------
|
|
181
|
+
function buildSite(dir) {
|
|
182
|
+
if (!exists(dir)) { warn(`no generated site at ${path.relative(process.cwd(), dir)} — run the yad-docs skill first`); return { ok: false, missing: true }; }
|
|
183
|
+
if (!has('npm')) { warn('npm not on PATH — cannot build; the CI workflow will build on push'); return { ok: false, noNpm: true }; }
|
|
184
|
+
const install = exists(path.join(dir, 'package-lock.json')) ? ['ci'] : ['install'];
|
|
185
|
+
log(` ${c.dim('$')} npm ${install[0]} ${c.dim(`(${path.relative(process.cwd(), dir)})`)}`);
|
|
186
|
+
const i = run('npm', install, { cwd: dir, stdio: 'inherit' });
|
|
187
|
+
if (!i.ok) { fail('npm install failed'); return { ok: false }; }
|
|
188
|
+
const b = run('npm', ['run', 'build'], { cwd: dir, stdio: 'inherit' });
|
|
189
|
+
if (!b.ok) { fail('npm run build failed'); return { ok: false }; }
|
|
190
|
+
return { ok: true, dist: path.join(dir, 'dist') };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---- orchestration ------------------------------------------------------------------------------
|
|
194
|
+
export async function runDocs(root, { action = 'list', epic, overview, sync } = {}) {
|
|
195
|
+
const { docs } = loadDocs(root);
|
|
196
|
+
const targets = overview ? [{ overview: true }] : epic ? [{ epic }] : enumerateSites(root);
|
|
197
|
+
|
|
198
|
+
if (action === 'list') {
|
|
199
|
+
if (!docs) { warn('no docs target connected (.sdlc/docs.json) — run yad-connect-docs'); }
|
|
200
|
+
else {
|
|
201
|
+
log(c.bold('\ndocs target'));
|
|
202
|
+
info(`target ${c.cyan(docs.target)} scope ${docs.scope} base ${docs.basePath} ${docs.source === 'unavailable' ? c.yellow('(build-only)') : ''}`);
|
|
203
|
+
}
|
|
204
|
+
log(c.bold('\ngenerated sites'));
|
|
205
|
+
if (!targets.length) { info('none generated yet'); return { sites: 0 }; }
|
|
206
|
+
for (const t of targets) reportFreshness(root, t);
|
|
207
|
+
return { sites: targets.length };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (action === 'build' || action === 'deploy') {
|
|
211
|
+
let built = 0;
|
|
212
|
+
for (const t of targets) {
|
|
213
|
+
const r = buildSite(siteDir(root, t));
|
|
214
|
+
if (r.ok) { built++; ok(`built ${label(t)} ${c.dim('→ ' + path.relative(root, r.dist))}`); }
|
|
215
|
+
}
|
|
216
|
+
if (action === 'deploy') {
|
|
217
|
+
const platform = docs ? (docs.target === 'gitlab-pages' ? 'gitlab' : docs.target === 'github-pages' ? 'github' : null) : null;
|
|
218
|
+
if (!platform || !platformReady(platform)) {
|
|
219
|
+
hand('no Pages platform/CLI — built locally only; commit + push so the CI workflow can publish (yad docs sync --wire)');
|
|
220
|
+
} else {
|
|
221
|
+
ok(`deploy via the ${platform} Pages workflow on push (yad docs sync --wire installs it)`);
|
|
222
|
+
if (docs?.basePath) info(`will publish under ${docs.basePath}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { built };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (action === 'sync') {
|
|
229
|
+
if (sync === 'wire') return wirePages(root, docs);
|
|
230
|
+
// check (default) + refresh both compute staleness; refresh additionally rebuilds.
|
|
231
|
+
const registry = readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] });
|
|
232
|
+
let stale = 0;
|
|
233
|
+
for (const t of targets) {
|
|
234
|
+
const s = freshness(root, t, registry);
|
|
235
|
+
if (s.stale) {
|
|
236
|
+
stale++;
|
|
237
|
+
warn(`${label(t)} — ${c.yellow('stale')}: ${s.reasons.join('; ')}`);
|
|
238
|
+
if (sync === 'refresh') buildSite(siteDir(root, t));
|
|
239
|
+
} else ok(`${label(t)} ${c.dim('— fresh')}`);
|
|
240
|
+
}
|
|
241
|
+
if (stale && sync !== 'refresh') hand('regenerate content with the yad-docs / yad-docs-overview skill (the AI step), then `yad docs deploy`');
|
|
242
|
+
return { stale };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
fail(`unknown docs action: ${action} (list | build | deploy | sync)`);
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
return {};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---- helpers ------------------------------------------------------------------------------------
|
|
251
|
+
function enumerateSites(root) {
|
|
252
|
+
const out = [];
|
|
253
|
+
if (exists(siteDir(root, { overview: true }))) out.push({ overview: true });
|
|
254
|
+
const epicsDir = path.join(root, 'epics');
|
|
255
|
+
if (exists(epicsDir)) {
|
|
256
|
+
for (const e of fs.readdirSync(epicsDir).sort()) {
|
|
257
|
+
if (exists(path.join(epicsDir, e, 'docs-site'))) out.push({ epic: e });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
function label(t) { return t.overview ? 'overview (docs/sdlc-site)' : `epic ${t.epic}`; }
|
|
263
|
+
|
|
264
|
+
function freshness(root, t, registry) {
|
|
265
|
+
const manifest = readJSON(manifestPath(root, t), null);
|
|
266
|
+
if (t.overview) {
|
|
267
|
+
// The overview is generated from the project pipeline definition, not an epic's artifacts.
|
|
268
|
+
const files = ['skills/sdlc/config.yaml', 'skills/sdlc/module-help.csv', 'docs/diagrams/sdlc-overview.mmd']
|
|
269
|
+
.map((f) => path.join(root, f)).filter(exists);
|
|
270
|
+
return docsStale(manifest, { artifactHash: docsArtifactHash(files), templateVersion: VERSION });
|
|
271
|
+
}
|
|
272
|
+
const epicMeta = readJSON(path.join(root, 'epics', t.epic, '.sdlc/state.json'), {});
|
|
273
|
+
const repos = epicMeta.repos || [];
|
|
274
|
+
const surface = contractSurfaceHash(path.join(root, 'epics', t.epic)); // null when no locked surface
|
|
275
|
+
return docsStale(manifest, {
|
|
276
|
+
artifactHash: docsArtifactHash(docsArtifactFiles(root, t.epic), surface || ''),
|
|
277
|
+
repoHeads: repoHeadsFor(root, repos, registry),
|
|
278
|
+
templateVersion: VERSION,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function reportFreshness(root, t) {
|
|
282
|
+
const registry = readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] });
|
|
283
|
+
const s = freshness(root, t, registry);
|
|
284
|
+
if (s.stale) warn(`${label(t)} — ${c.yellow('stale')}: ${s.reasons.join('; ')}`);
|
|
285
|
+
else ok(`${label(t)} ${c.dim('— fresh')}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function wirePages(root, docs) {
|
|
289
|
+
const platform = docs?.target === 'gitlab-pages' ? 'gitlab' : 'github';
|
|
290
|
+
const rel = pagesWorkflowPath(platform);
|
|
291
|
+
const dest = path.join(root, rel);
|
|
292
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
293
|
+
fs.writeFileSync(dest, pagesWorkflow(platform));
|
|
294
|
+
ok(`wired ${rel} ${c.dim(`(${platform} Pages)`)}`);
|
|
295
|
+
if (platform === 'gitlab') hand(`include it from .gitlab-ci.yml: include: { local: ${rel} }`);
|
|
296
|
+
else hand('commit it; set the repo Pages source to "GitHub Actions" so it publishes on push');
|
|
297
|
+
return { wired: rel };
|
|
298
|
+
}
|
package/cli/doctor.mjs
CHANGED
|
@@ -195,6 +195,7 @@ export function ciTagsChecks(checks, root, hub, registry) {
|
|
|
195
195
|
fragments.push(
|
|
196
196
|
{ scope: 'hub', file: '.gitlab/ci/yad-gate-sync.yml', path: path.join(root, '.gitlab/ci/yad-gate-sync.yml') },
|
|
197
197
|
{ scope: 'hub', file: '.gitlab/ci/yad-verified-commits.yml', path: path.join(root, '.gitlab/ci/yad-verified-commits.yml') },
|
|
198
|
+
{ scope: 'hub', file: '.gitlab/ci/yad-hub-checks.yml', path: path.join(root, '.gitlab/ci/yad-hub-checks.yml') },
|
|
198
199
|
);
|
|
199
200
|
}
|
|
200
201
|
for (const repo of registry?.repos || []) {
|