yadflow 4.0.0-next.3 → 4.0.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 +699 -0
- package/README.md +85 -22
- package/bin/commands.mjs +3 -1
- package/bin/yad.mjs +183 -29
- package/cli/capture.mjs +4 -2
- package/cli/checkpoint.mjs +10 -10
- package/cli/claims.mjs +4 -4
- package/cli/commit.mjs +48 -12
- package/cli/docs.mjs +15 -13
- package/cli/doctor.mjs +557 -141
- package/cli/epic-state.mjs +41 -18
- package/cli/errors.mjs +3 -2
- package/cli/find-product.mjs +197 -0
- package/cli/fold.mjs +4 -4
- package/cli/gate-local.mjs +13 -13
- package/cli/gate.mjs +111 -103
- package/cli/history.mjs +13 -13
- package/cli/hook.mjs +201 -30
- package/cli/index-command.mjs +6 -6
- package/cli/lib.mjs +91 -29
- package/cli/manifest.mjs +171 -104
- package/cli/migrate.mjs +208 -17
- package/cli/mode.mjs +22 -22
- package/cli/next.mjs +6 -6
- package/cli/openpr.mjs +32 -24
- package/cli/people.mjs +22 -10
- package/cli/plan.mjs +718 -45
- package/cli/platform.mjs +13 -7
- package/cli/product-index.mjs +4 -4
- package/cli/{hubcommit.mjs → productcommit.mjs} +11 -7
- package/cli/protection.mjs +19 -9
- package/cli/reconcile.mjs +98 -23
- package/cli/repo-publish.mjs +22 -16
- package/cli/repo.mjs +128 -33
- package/cli/report.mjs +4 -2
- package/cli/review.mjs +26 -19
- package/cli/setup.mjs +105 -36
- package/cli/ship.mjs +1 -1
- package/cli/skip.mjs +4 -4
- package/cli/tidy.mjs +7 -7
- package/cli/update-commit.mjs +22 -19
- package/cli/workspace.mjs +495 -0
- package/package.json +7 -4
- package/skills/sdlc/config.yaml +14 -13
- package/skills/sdlc/module-help.csv +8 -8
- package/skills/yad-analysis/SKILL.md +6 -4
- package/skills/yad-architecture/SKILL.md +4 -3
- package/skills/yad-backfill/SKILL.md +11 -5
- package/skills/yad-backfill/references/backfill.md +33 -3
- package/skills/yad-backfill/templates/checks/backfill-check.sh +200 -17
- package/skills/yad-change/SKILL.md +1 -1
- package/skills/yad-checks/SKILL.md +48 -27
- package/skills/yad-checks/references/check-gates.md +213 -53
- package/skills/yad-checks/templates/checks/commit-message.sh +39 -13
- package/skills/yad-checks/templates/checks/contract-check.sh +472 -23
- package/skills/yad-checks/templates/checks/epic-open.sh +398 -8
- package/skills/yad-checks/templates/checks/ledger-guard.sh +75 -25
- package/skills/yad-checks/templates/checks/lineage-check.sh +391 -7
- package/skills/yad-checks/templates/checks/product-checkout.sh +141 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +399 -8
- package/skills/yad-checks/templates/checks/risk-map-check.sh +35 -3
- package/skills/yad-checks/templates/checks/spec-link.sh +36 -3
- package/skills/yad-checks/templates/checks/verified-commits.sh +26 -2
- package/skills/yad-checks/templates/github/yad-checks.yml +33 -13
- package/skills/yad-checks/templates/github/{yad-hub-checks.yml → yad-product-checks.yml} +14 -10
- package/skills/yad-checks/templates/github/yad-update-guard.yml +1 -1
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -0
- package/skills/yad-checks/templates/gitlab/{yad-hub-checks.gitlab-ci.yml → yad-product-checks.gitlab-ci.yml} +16 -16
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +2 -2
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.mjs +130 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.mjs +117 -0
- package/skills/yad-checks/templates/hooks/yad-capture.mjs +117 -0
- package/skills/yad-commit/SKILL.md +18 -7
- package/skills/yad-connect-design/SKILL.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +1 -1
- package/skills/yad-connect-docs/SKILL.md +16 -11
- package/skills/yad-connect-docs/references/docs-registry.md +8 -8
- package/skills/yad-connect-learning/SKILL.md +1 -1
- package/skills/yad-connect-learning/references/learning-registry.md +1 -1
- package/skills/yad-connect-repos/SKILL.md +31 -8
- package/skills/yad-connect-repos/references/code-context.md +1 -1
- package/skills/yad-connect-repos/references/{hub-config.md → product-config.md} +19 -11
- package/skills/yad-connect-repos/references/repos-registry.md +1 -1
- package/skills/yad-connect-testing/SKILL.md +1 -1
- package/skills/yad-connect-testing/references/testing-registry.md +1 -1
- package/skills/yad-discovery/SKILL.md +2 -2
- package/skills/yad-discovery/references/discovery-schema.md +1 -1
- package/skills/yad-docs/SKILL.md +1 -1
- package/skills/yad-docs/references/data-mapping.md +1 -1
- package/skills/yad-docs/templates/app/package-lock.json +8 -8
- package/skills/yad-docs/templates/app/package.json +1 -1
- package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +2 -2
- package/skills/yad-docs-overview/references/pipeline-model.md +3 -3
- package/skills/yad-engineer-review/SKILL.md +2 -2
- package/skills/yad-engineer-review/references/ship-and-record.md +3 -3
- package/skills/yad-epic/SKILL.md +6 -4
- package/skills/yad-epic/references/state-schema.md +13 -12
- package/skills/yad-open-pr/SKILL.md +1 -1
- package/skills/yad-pair-review/SKILL.md +5 -5
- package/skills/yad-pair-review/references/session-state.md +2 -2
- package/skills/yad-pr-template/SKILL.md +14 -12
- package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -16
- package/skills/yad-pr-template/templates/checks/pr-title.sh +15 -13
- package/skills/yad-pr-template/templates/{hub → product}/github/pull_request_template.md +2 -2
- package/skills/yad-pr-template/templates/{hub → product}/gitlab/merge_request_templates/Default.md +2 -2
- package/skills/{yad-hub-bridge → yad-product-bridge}/SKILL.md +15 -15
- package/skills/{yad-hub-bridge → yad-product-bridge}/references/bridge.md +15 -10
- package/skills/{yad-hub-bridge → yad-product-bridge}/references/login-roster.md +2 -2
- package/skills/{yad-hub-bridge/templates/checks/hub-route.sh → yad-product-bridge/templates/checks/product-route.sh} +2 -2
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/github/yad-gate-sync.yml +29 -16
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/gitlab-ci.include-root.yml +1 -1
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/yad-gate-sync.gitlab-ci.yml +5 -3
- package/skills/yad-report/SKILL.md +1 -1
- package/skills/yad-review-companion/SKILL.md +3 -3
- package/skills/yad-review-gate/SKILL.md +22 -23
- package/skills/yad-review-gate/references/gating.md +13 -13
- package/skills/yad-run/SKILL.md +2 -2
- package/skills/yad-run/references/run-loop.md +2 -2
- package/skills/yad-spec/SKILL.md +2 -1
- package/skills/yad-spec/references/spec-handoff.md +19 -10
- package/skills/yad-stories/SKILL.md +2 -2
- package/skills/yad-sync-repos/SKILL.md +19 -3
- package/skills/yad-test-cases/SKILL.md +2 -2
- package/skills/yad-ui/SKILL.md +2 -2
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +0 -91
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +0 -100
- package/skills/yad-checks/templates/hooks/yad-capture.sh +0 -53
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// ledger-guard HARNESS HOOK — the local half of the CI gate of the same name (#171).
|
|
2
|
+
//
|
|
3
|
+
// The gate ledger is CI-owned in verified mode: `checks/ledger-guard.sh` rejects any non-bot commit
|
|
4
|
+
// that changes `epics/*/.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or `epics/*/reviews/*.md`,
|
|
5
|
+
// and the same files for the Foundation in `foundation/.sdlc/` and `foundation/reviews/` (E75).
|
|
6
|
+
// This hook says so at the moment an agent tries the edit, instead of twenty minutes later in a
|
|
7
|
+
// failed pipeline, and names the command that owns the transition (`yad gate open`).
|
|
8
|
+
//
|
|
9
|
+
// This file is only the ADAPTER. It locates `yad` and hands the tool-call payload to
|
|
10
|
+
// `yad hook ledger-guard`, which holds the decision. The contract it passes through:
|
|
11
|
+
//
|
|
12
|
+
// stdin the harness's tool-call payload as JSON (optional)
|
|
13
|
+
// exit 0 allow
|
|
14
|
+
// exit 2 deny, reason on stderr
|
|
15
|
+
//
|
|
16
|
+
// A NODE SCRIPT, NOT A SHELL SCRIPT (E113), so it runs the same on Windows, macOS and Linux. Node is
|
|
17
|
+
// the one thing yadflow already needs. It imports nothing outside Node itself, because it runs from the
|
|
18
|
+
// Product, where yadflow may not be installed at all.
|
|
19
|
+
//
|
|
20
|
+
// Wired by `yad check --fix` for every harness that can refuse a write BEFORE it lands:
|
|
21
|
+
// Claude Code a `PreToolUse` hook in `.claude/settings.json`, pointing straight at THIS script
|
|
22
|
+
// Cursor a `preToolUse` hook in `.cursor/hooks.json`, pointing at `ledger-guard-cursor.mjs`
|
|
23
|
+
//
|
|
24
|
+
// DO NOT WIRE THIS SCRIPT INTO CURSOR DIRECTLY, and check the same thing before wiring it into any
|
|
25
|
+
// other harness by hand. Cursor's `preToolUse` is a PERMISSION hook: it wants a JSON verdict on
|
|
26
|
+
// stdout, and treats an empty or off-schema answer as a refusal. This script prints NOTHING when it
|
|
27
|
+
// allows — so wired there directly it would block every file write in the project, which is the
|
|
28
|
+
// opposite of the fail-open stance below. `ledger-guard-cursor.mjs` exists to speak that protocol.
|
|
29
|
+
//
|
|
30
|
+
// A harness that reads the EXIT CODE can use this script as it stands: `node <product>/hooks/ledger-guard.mjs`.
|
|
31
|
+
//
|
|
32
|
+
// FAIL-OPEN: if no `yad` can be found, this ALLOWS and says why on stderr. A guardrail that blocked
|
|
33
|
+
// every edit the moment an install went sideways would be worse than the problem. The CI gate fails
|
|
34
|
+
// CLOSED and is what actually protects the ledger.
|
|
35
|
+
import { spawnSync } from 'node:child_process';
|
|
36
|
+
import fs from 'node:fs';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
|
|
40
|
+
// ---- yad-resolve (the same block in every hooks/*.mjs; a test keeps the copies identical) ----------
|
|
41
|
+
// The Product root is this script's grandparent — `<product>/hooks/<script>.mjs` — found from the
|
|
42
|
+
// script's OWN location, never from the harness's working directory or its project-root variable
|
|
43
|
+
// (whose form on Windows, `C:\…` or `/c/…`, no harness documents). `realpathSync` so a script reached
|
|
44
|
+
// through a symbolic link still finds its own Product.
|
|
45
|
+
const PRODUCT_ROOT = path.dirname(path.dirname(fs.realpathSync(fileURLToPath(import.meta.url))));
|
|
46
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
47
|
+
|
|
48
|
+
// A command found on PATH, or null. On Windows the npm launchers are `.cmd` files.
|
|
49
|
+
function onPath(name) {
|
|
50
|
+
const exts = IS_WINDOWS ? ['.cmd', '.exe', ''] : [''];
|
|
51
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
52
|
+
if (!dir) continue;
|
|
53
|
+
for (const ext of exts) {
|
|
54
|
+
const full = path.join(dir, name + ext);
|
|
55
|
+
try { if (fs.statSync(full).isFile()) return full; } catch { /* not here */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Windows cannot start a `.cmd` launcher without a shell (Node refuses since 18.20.2), so those run
|
|
62
|
+
// through one, each word in double quotes. A Windows path cannot hold a `"`, so the quoting is whole.
|
|
63
|
+
const quoteForCmd = (word) => `"${String(word).replace(/"/g, '')}"`;
|
|
64
|
+
|
|
65
|
+
// How to run `yad`, cheapest and most specific first: an explicit override, then the copy installed in
|
|
66
|
+
// this Product, then `yad` on PATH, then a network-free npx. `--no-install` matters — a hook runs on
|
|
67
|
+
// every tool call and must never pause an agent to download a package. Returns
|
|
68
|
+
// `{ file, args, shell }` or null.
|
|
69
|
+
function resolveYad() {
|
|
70
|
+
// YAD_BIN is commonly an interpreter plus a script ("node /path/to/yad.mjs"). Split on spaces, with a
|
|
71
|
+
// double-quoted word kept whole — Node on Windows usually lives under `C:\Program Files\`.
|
|
72
|
+
const override = [...(process.env.YAD_BIN || '').matchAll(/"([^"]*)"|(\S+)/g)].map((m) => m[1] ?? m[2]).filter(Boolean);
|
|
73
|
+
if (override.length) {
|
|
74
|
+
const [file, ...args] = override;
|
|
75
|
+
return { file, args, shell: IS_WINDOWS && /\.(cmd|bat)$/i.test(file) };
|
|
76
|
+
}
|
|
77
|
+
const local = path.join(PRODUCT_ROOT, 'node_modules', 'yadflow', 'bin', 'yad.mjs');
|
|
78
|
+
if (fs.existsSync(local)) return { file: process.execPath, args: [local], shell: false };
|
|
79
|
+
const yad = onPath('yad');
|
|
80
|
+
if (yad) {
|
|
81
|
+
if (!IS_WINDOWS || !/\.(cmd|bat)$/i.test(yad)) return { file: yad, args: [], shell: false };
|
|
82
|
+
// An npm global install puts the package beside its launcher: run the script with this Node
|
|
83
|
+
// rather than through a shell whenever it is there.
|
|
84
|
+
const script = path.join(path.dirname(yad), 'node_modules', 'yadflow', 'bin', 'yad.mjs');
|
|
85
|
+
if (fs.existsSync(script)) return { file: process.execPath, args: [script], shell: false };
|
|
86
|
+
return { file: yad, args: [], shell: true };
|
|
87
|
+
}
|
|
88
|
+
const npx = onPath('npx');
|
|
89
|
+
if (npx) return { file: npx, args: ['--no-install', 'yadflow'], shell: IS_WINDOWS && /\.(cmd|bat)$/i.test(npx) };
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Run the resolved `yad` with `args` appended. `options` go to spawnSync.
|
|
94
|
+
function runYad(yad, args, options) {
|
|
95
|
+
const all = [...yad.args, ...args];
|
|
96
|
+
if (yad.shell) return spawnSync([yad.file, ...all].map(quoteForCmd).join(' '), { ...options, shell: true, windowsHide: true });
|
|
97
|
+
return spawnSync(yad.file, all, { ...options, windowsHide: true });
|
|
98
|
+
}
|
|
99
|
+
// ---- end yad-resolve ---------------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
const yad = resolveYad();
|
|
102
|
+
if (!yad) {
|
|
103
|
+
process.stderr.write(` • yad hook: no \`yad\` on PATH and none installed in ${PRODUCT_ROOT} — allowing (install yadflow to re-arm the ledger guard)\n`);
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Run it rather than hand the process over, so the exit code can be mapped. ONLY an explicit deny (2)
|
|
108
|
+
// blocks: a `yad` that is present but cannot run — an `npx --no-install` with no yadflow to find, a
|
|
109
|
+
// crash, a broken install — must not read as a refusal. Fail-open is the whole stance of this hook;
|
|
110
|
+
// the CI gate is what fails closed. stdin, stdout and stderr are all the harness's own.
|
|
111
|
+
const r = runYad(yad, ['hook', 'ledger-guard', ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
112
|
+
if (r.status === 2) process.exit(2);
|
|
113
|
+
if (r.status !== 0) {
|
|
114
|
+
const why = r.error ? r.error.message : `exited ${r.status ?? r.signal}`;
|
|
115
|
+
process.stderr.write(` • yad hook: \`yad hook ledger-guard\` ${why} — allowing (run \`yad doctor\` to check the install)\n`);
|
|
116
|
+
}
|
|
117
|
+
process.exit(0);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// yad-capture HARNESS HOOK — background capture of Shape artifacts (E43).
|
|
2
|
+
//
|
|
3
|
+
// Runs AFTER an agent edits a file, and snapshots every changed artifact of the Product onto the
|
|
4
|
+
// person's private `yad/wip/<name>/<epic>` branches — built with git plumbing, so the checkout, the
|
|
5
|
+
// index and the current branch are never touched. The push runs in the background, at most once
|
|
6
|
+
// every few minutes. The decision lives in `yad capture --hook`; this file only locates `yad`.
|
|
7
|
+
//
|
|
8
|
+
// Wired by `yad check --fix`, in BOTH ledger modes:
|
|
9
|
+
// Claude Code a `PostToolUse` hook in `.claude/settings.json`: node "$CLAUDE_PROJECT_DIR/hooks/yad-capture.mjs" --format claude
|
|
10
|
+
// Cursor an `afterFileEdit` hook in `.cursor/hooks.json`: node hooks/yad-capture.mjs
|
|
11
|
+
// Any other harness: run `node <product>/hooks/yad-capture.mjs` (or `yad capture --hook`) after a file write.
|
|
12
|
+
//
|
|
13
|
+
// A NODE SCRIPT, NOT A SHELL SCRIPT (E113), so it runs the same on Windows, macOS and Linux.
|
|
14
|
+
//
|
|
15
|
+
// The contract: it ALWAYS exits 0 — a capture must never block or slow an agent. A problem is one line on
|
|
16
|
+
// stderr. Stdout carries ONE thing, and only with `--format claude`: when a file this edit changed is also
|
|
17
|
+
// being edited by someone else (E46 claims), a PostToolUse JSON note the agent reads. Otherwise it prints
|
|
18
|
+
// nothing. The format is passed by the entry and never guessed from the environment: Cursor sets
|
|
19
|
+
// `CLAUDE_PROJECT_DIR` too, as an alias. `YAD_CAPTURE=0`, or `"capture": false` in the Product config,
|
|
20
|
+
// turns it off.
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
// ---- yad-resolve (the same block in every hooks/*.mjs; a test keeps the copies identical) ----------
|
|
27
|
+
// The Product root is this script's grandparent — `<product>/hooks/<script>.mjs` — found from the
|
|
28
|
+
// script's OWN location, never from the harness's working directory or its project-root variable
|
|
29
|
+
// (whose form on Windows, `C:\…` or `/c/…`, no harness documents). `realpathSync` so a script reached
|
|
30
|
+
// through a symbolic link still finds its own Product.
|
|
31
|
+
const PRODUCT_ROOT = path.dirname(path.dirname(fs.realpathSync(fileURLToPath(import.meta.url))));
|
|
32
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
33
|
+
|
|
34
|
+
// A command found on PATH, or null. On Windows the npm launchers are `.cmd` files.
|
|
35
|
+
function onPath(name) {
|
|
36
|
+
const exts = IS_WINDOWS ? ['.cmd', '.exe', ''] : [''];
|
|
37
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
38
|
+
if (!dir) continue;
|
|
39
|
+
for (const ext of exts) {
|
|
40
|
+
const full = path.join(dir, name + ext);
|
|
41
|
+
try { if (fs.statSync(full).isFile()) return full; } catch { /* not here */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Windows cannot start a `.cmd` launcher without a shell (Node refuses since 18.20.2), so those run
|
|
48
|
+
// through one, each word in double quotes. A Windows path cannot hold a `"`, so the quoting is whole.
|
|
49
|
+
const quoteForCmd = (word) => `"${String(word).replace(/"/g, '')}"`;
|
|
50
|
+
|
|
51
|
+
// How to run `yad`, cheapest and most specific first: an explicit override, then the copy installed in
|
|
52
|
+
// this Product, then `yad` on PATH, then a network-free npx. `--no-install` matters — a hook runs on
|
|
53
|
+
// every tool call and must never pause an agent to download a package. Returns
|
|
54
|
+
// `{ file, args, shell }` or null.
|
|
55
|
+
function resolveYad() {
|
|
56
|
+
// YAD_BIN is commonly an interpreter plus a script ("node /path/to/yad.mjs"). Split on spaces, with a
|
|
57
|
+
// double-quoted word kept whole — Node on Windows usually lives under `C:\Program Files\`.
|
|
58
|
+
const override = [...(process.env.YAD_BIN || '').matchAll(/"([^"]*)"|(\S+)/g)].map((m) => m[1] ?? m[2]).filter(Boolean);
|
|
59
|
+
if (override.length) {
|
|
60
|
+
const [file, ...args] = override;
|
|
61
|
+
return { file, args, shell: IS_WINDOWS && /\.(cmd|bat)$/i.test(file) };
|
|
62
|
+
}
|
|
63
|
+
const local = path.join(PRODUCT_ROOT, 'node_modules', 'yadflow', 'bin', 'yad.mjs');
|
|
64
|
+
if (fs.existsSync(local)) return { file: process.execPath, args: [local], shell: false };
|
|
65
|
+
const yad = onPath('yad');
|
|
66
|
+
if (yad) {
|
|
67
|
+
if (!IS_WINDOWS || !/\.(cmd|bat)$/i.test(yad)) return { file: yad, args: [], shell: false };
|
|
68
|
+
// An npm global install puts the package beside its launcher: run the script with this Node
|
|
69
|
+
// rather than through a shell whenever it is there.
|
|
70
|
+
const script = path.join(path.dirname(yad), 'node_modules', 'yadflow', 'bin', 'yad.mjs');
|
|
71
|
+
if (fs.existsSync(script)) return { file: process.execPath, args: [script], shell: false };
|
|
72
|
+
return { file: yad, args: [], shell: true };
|
|
73
|
+
}
|
|
74
|
+
const npx = onPath('npx');
|
|
75
|
+
if (npx) return { file: npx, args: ['--no-install', 'yadflow'], shell: IS_WINDOWS && /\.(cmd|bat)$/i.test(npx) };
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Run the resolved `yad` with `args` appended. `options` go to spawnSync.
|
|
80
|
+
function runYad(yad, args, options) {
|
|
81
|
+
const all = [...yad.args, ...args];
|
|
82
|
+
if (yad.shell) return spawnSync([yad.file, ...all].map(quoteForCmd).join(' '), { ...options, shell: true, windowsHide: true });
|
|
83
|
+
return spawnSync(yad.file, all, { ...options, windowsHide: true });
|
|
84
|
+
}
|
|
85
|
+
// ---- end yad-resolve ---------------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
// Drain the payload the harness sends on stdin, so it never sees a broken pipe — but never wait on it: not
|
|
88
|
+
// at all from a terminal (someone ran this by hand), and at most a second on a pipe that never closes.
|
|
89
|
+
function drainStdin() {
|
|
90
|
+
if (process.stdin.isTTY) return Promise.resolve();
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
const done = () => { clearTimeout(timer); process.stdin.pause(); resolve(); };
|
|
93
|
+
const timer = setTimeout(done, 1000);
|
|
94
|
+
process.stdin.on('data', () => {});
|
|
95
|
+
process.stdin.on('end', done);
|
|
96
|
+
process.stdin.on('error', done);
|
|
97
|
+
process.stdin.resume();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function main() {
|
|
102
|
+
await drainStdin();
|
|
103
|
+
if (process.env.YAD_CAPTURE === '0') return;
|
|
104
|
+
const yad = resolveYad();
|
|
105
|
+
if (!yad) {
|
|
106
|
+
process.stderr.write(` • yad capture: no \`yad\` on PATH and none installed in ${PRODUCT_ROOT} — nothing captured\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Only `--format claude` is passed through; anything else on the command line is not ours to forward.
|
|
110
|
+
const argv = process.argv.slice(2);
|
|
111
|
+
const i = argv.indexOf('--format');
|
|
112
|
+
const format = i >= 0 && argv[i + 1] === 'claude' ? ['--format', 'claude'] : [];
|
|
113
|
+
// stdin is closed for the child: the payload was drained above and capture reads none.
|
|
114
|
+
runYad(yad, ['capture', '--hook', ...format, '--dir', PRODUCT_ROOT], { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main().catch((e) => { process.stderr.write(` • yad capture: ${e?.message || e}\n`); }).finally(() => process.exit(0));
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-commit
|
|
3
|
-
description: 'Build helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer order (Task → Contract-Change, plus an OPTIONAL Co-Authored-By), and an atomic-file guard (≤3 files). By default the commit carries NO AI footer: the human git author owns it, and a Co-Authored-By trailer is added ONLY when --ai <id> is explicitly passed (claude|copilot|cursor|coderabbit; default none = human-only). The flag is the sole switch — never add the footer on the AI''s own initiative. Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
|
|
3
|
+
description: 'Build helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer order (Task → Contract-Change → Ledger-Override, plus an OPTIONAL Co-Authored-By), and an atomic-file guard (≤3 files). By default the commit carries NO AI footer: the human git author owns it, and a Co-Authored-By trailer is added ONLY when --ai <id> is explicitly passed (claude|copilot|cursor|coderabbit; default none = human-only). The flag is the sole switch — never add the footer on the AI''s own initiative. Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Commit by Convention (Build helper)
|
|
7
7
|
|
|
8
8
|
**Goal:** Turn ONE staged atomic change into a single commit that satisfies the project conventions
|
|
9
9
|
(`CONTRIBUTING.md` / `config.yaml` `build`): a Conventional-Commits subject, the fixed trailer order
|
|
10
|
-
`Task → Contract-Change → Co-Authored-By` (the
|
|
11
|
-
`--ai`), and the atomic-file guard. This is the standalone commit step — the same engine `yad-implement`
|
|
10
|
+
`Task → Contract-Change → Ledger-Override → Co-Authored-By` (the override only with `--manual`; the footer
|
|
11
|
+
**off by default** — added only via an explicit `--ai`), and the atomic-file guard. This is the standalone commit step — the same engine `yad-implement`
|
|
12
12
|
and `yad-ship` use. It **never auto-advances**; it just commits.
|
|
13
13
|
|
|
14
14
|
## Conventions
|
|
@@ -20,8 +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` (a `<story>-T<NN>` id
|
|
24
|
-
contract, so a malformed id like a bare `EP-x-S01`
|
|
23
|
+
with `--task` (a `<story>-T<NN>` id, the story an `EP-<slug>-S<n>` with a lowercase slug — an explicit
|
|
24
|
+
value is validated against the `spec-link` contract, so a malformed id like a bare `EP-x-S01` or an
|
|
25
|
+
`EP-Demo-S01-T01` fails locally instead of after a push), else
|
|
25
26
|
derived from the branch (`feat/<story>-<task>-…`). Product commits are not
|
|
26
27
|
task-scoped, so the trailer is optional there — a missing-Task warning is informational on the Product
|
|
27
28
|
(`spec-link` is a code-repo gate) and only flags a real gate failure in a code repo.
|
|
@@ -40,6 +41,12 @@ and `yad-ship` use. It **never auto-advances**; it just commits.
|
|
|
40
41
|
the `Co-Authored-By` trailer appears only when this flag names a tool).
|
|
41
42
|
- `task` — Task trailer (optional; derived from the branch when omitted).
|
|
42
43
|
- `contractChange` — flag; mark the contract surface touched.
|
|
44
|
+
- `manual` + `reason` — the ledger override (E49), for a PERSON only. On a verified Product a local
|
|
45
|
+
pre-commit hook refuses a commit that changes a CI-owned file (the gate ledger, the Product index).
|
|
46
|
+
`--manual --reason "<why>"` commits past that hook and records the reason as a `Ledger-Override:`
|
|
47
|
+
trailer. It is refused when nothing staged is a file that hook refuses. The `ledger-guard` check on
|
|
48
|
+
the pull request still fails the commit, and quotes the reason. A reason that starts with `-` is
|
|
49
|
+
passed as `--reason=-…`.
|
|
43
50
|
|
|
44
51
|
## On Activation
|
|
45
52
|
|
|
@@ -50,7 +57,7 @@ staged, split it into separate commits rather than passing `--force`.
|
|
|
50
57
|
### Step 2 — Commit by convention
|
|
51
58
|
Run the CLI from the repo root:
|
|
52
59
|
```
|
|
53
|
-
yad commit --type <type> -m "<subject>" [--ai <id>] [--task <id>] [--contract-change] [--dry-run]
|
|
60
|
+
yad commit --type <type> -m "<subject>" [--ai <id>] [--task <id>] [--contract-change] [--manual --reason "<why>"] [--dry-run]
|
|
54
61
|
```
|
|
55
62
|
Use `--dry-run` first to preview the exact message (subject + trailer block) without committing. The
|
|
56
63
|
CLI validates the type, rejects a trailing period, and emits the trailers in the fixed order.
|
|
@@ -65,8 +72,12 @@ the architecture gate. To also open the PR/MR in the same step, use `yad-ship`.
|
|
|
65
72
|
- **No AI footer by default.** A `Co-Authored-By` trailer is written ONLY when `--ai <id>` is explicitly
|
|
66
73
|
passed; the default is a clean human-only commit. Never add it on your own initiative.
|
|
67
74
|
- **The human author owns the commit.** The AI is at most a `Co-Authored-By` footer, never the author.
|
|
68
|
-
- **Trailer order is fixed:** `Task → Contract-Change → Co-Authored-By` (the
|
|
75
|
+
- **Trailer order is fixed:** `Task → Contract-Change → Ledger-Override → Co-Authored-By` (the override only
|
|
76
|
+
with `--manual`, the footer only when `--ai` is given).
|
|
69
77
|
- **Never widen the contract here.** A contract touch is flagged (`--contract-change`), not hidden.
|
|
78
|
+
- **Never pass `--manual` on your own initiative.** Committing past the ledger hook is a person's decision.
|
|
79
|
+
Pass it only when the user asked for it, with the reason they gave. `--manual` needs `--reason`, and the
|
|
80
|
+
reason must be one line.
|
|
70
81
|
|
|
71
82
|
## Reference
|
|
72
83
|
- Branch/commit conventions + the file-boundary rule: `../yad-implement/references/implement-conventions.md`.
|
|
@@ -26,7 +26,7 @@ markdown-only exactly as before.
|
|
|
26
26
|
shape as Impeccable's slash-commands, not Repomix's `npx`. The skill detects the MCP and degrades when
|
|
27
27
|
it is absent; it never installs an MCP server.
|
|
28
28
|
- Registry: `{project-root}/.sdlc/design.json` (project-wide, shared across all epics — NOT per-epic),
|
|
29
|
-
the sibling of `.sdlc/repos.json` and `.sdlc/
|
|
29
|
+
the sibling of `.sdlc/repos.json` and `.sdlc/product.json`.
|
|
30
30
|
- Per-epic screen→frame links are written later by `yad-ui` (`epics/EP-<slug>/.sdlc/design-links.json`),
|
|
31
31
|
not here.
|
|
32
32
|
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
|
|
@@ -47,7 +47,7 @@ root, not under any `epics/EP-<slug>/.sdlc/`.
|
|
|
47
47
|
## Git tracking
|
|
48
48
|
|
|
49
49
|
Commit the **registry** (`design.json`) — it is small, reviewable, and holds no secrets (references
|
|
50
|
-
only). This mirrors how `repos.json` and `
|
|
50
|
+
only). This mirrors how `repos.json` and `product.json` are committed.
|
|
51
51
|
|
|
52
52
|
## Greenfield
|
|
53
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-connect-docs
|
|
3
|
-
description: 'Connects a docs/Pages publishing target to the Product so the interactive-docs steps can build and deploy the generated SPA — not just commit its source. Registers the target into the project-wide .sdlc/docs.json (GitHub Pages / GitLab Pages / build-only), auto-detecting the platform from .sdlc/
|
|
3
|
+
description: 'Connects a docs/Pages publishing target to the Product so the interactive-docs steps can build and deploy the generated SPA — not just commit its source. Registers the target into the project-wide .sdlc/docs.json (GitHub Pages / GitLab Pages / build-only), auto-detecting the platform from .sdlc/product.json and resolving the Vite base path, with local-user auth and no stored tokens. Detects whether gh/glab is present and degrades to build-only when absent. Run at setup or any time the publish target changes. Reusable, idempotent, refreshable. Use when the user says "connect docs", "connect Pages", "refresh the docs connection", or "list the docs connection".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Connect a Docs/Pages Target (make the docs steps publishable)
|
|
@@ -26,7 +26,7 @@ at a local `dist/` — build-only, no publish, exactly as before.
|
|
|
26
26
|
never installed by this skill, never given a token. Absent ⇒ degrade to build-only (`source:
|
|
27
27
|
"unavailable"`).
|
|
28
28
|
- Registry: `{project-root}/.sdlc/docs.json` (project-wide, shared across all epics + the overview —
|
|
29
|
-
NOT per-epic), the sibling of `.sdlc/
|
|
29
|
+
NOT per-epic), the sibling of `.sdlc/product.json`, `.sdlc/repos.json`, and `.sdlc/design.json`.
|
|
30
30
|
- Per-epic / overview build manifests (`docs-build.json`) are written later by `yad-docs` /
|
|
31
31
|
`yad-docs-overview`, not here. This skill describes the *connection*; it does not build.
|
|
32
32
|
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
|
|
@@ -34,17 +34,21 @@ at a local `dist/` — build-only, no publish, exactly as before.
|
|
|
34
34
|
## Inputs
|
|
35
35
|
|
|
36
36
|
- `action` — `connect` (default) | `refresh` | `list` | `disconnect`.
|
|
37
|
-
- `target` — `github-pages` | `gitlab-pages` | `none`. Default **auto-detected** from `.sdlc/
|
|
37
|
+
- `target` — `github-pages` | `gitlab-pages` | `none`. Default **auto-detected** from `.sdlc/product.json`
|
|
38
38
|
`platform` (github → `github-pages`, gitlab → `gitlab-pages`, null/no Product → `none`).
|
|
39
|
-
- `scope` — `
|
|
40
|
-
Product repo, one connected code repo, or a dedicated docs repo).
|
|
39
|
+
- `scope` — `product` (default) | `<repo-name>` | `dedicated`. Where the Pages site is published from (the
|
|
40
|
+
Product repo, one connected code repo, or a dedicated docs repo). `hub` is the old name for `product`:
|
|
41
|
+
a `docs.json` written before 4.0 says `"scope": "hub"`, and it means the same thing — read it as
|
|
42
|
+
`product`, and write `product` the next time you write the file. A connected code repo that is itself named
|
|
43
|
+
`product` is recorded by its registry `path` (for example `repos/product`), never its name, so it is not read
|
|
44
|
+
as the Product.
|
|
41
45
|
- `public` — `true` (default) | `false`. Whether the published site is public.
|
|
42
46
|
- `base_path` — optional explicit override of the Vite `base` (otherwise resolved, Step 2).
|
|
43
47
|
|
|
44
48
|
## On Activation
|
|
45
49
|
|
|
46
50
|
### Step 1 — Resolve the target + detect the platform (the publish adapter)
|
|
47
|
-
Determine the `target`. If not given, read `{project-root}/.sdlc/hub.json` `platform` and map it the same
|
|
51
|
+
Determine the `target`. If not given, read `{project-root}/.sdlc/product.json` (`.sdlc/hub.json` on an older Product that has only that name) `platform` and map it the same
|
|
48
52
|
way the Product bridge maps repos: `github` → `github-pages`, `gitlab` → `gitlab-pages`, `null`/no Product →
|
|
49
53
|
`none` (deliberate build-only). Reject a `target` value outside the three providers (fall back to the
|
|
50
54
|
detected default with a warning, the way `registerRepo` falls back on an unknown platform).
|
|
@@ -61,8 +65,9 @@ tokens**; everything in the registry is a plain reference. Do **not** install a
|
|
|
61
65
|
|
|
62
66
|
### Step 2 — Decide the publish scope + resolve the base path
|
|
63
67
|
Resolve `scope` → `publishRepo`:
|
|
64
|
-
- `
|
|
65
|
-
- `<repo-name>` → publish from that connected code repo (must exist in `.sdlc/repos.json`).
|
|
68
|
+
- `product` (default; `hub` in an older `docs.json`) → publish from the Product repo (read its name from `product.json` `git_url`).
|
|
69
|
+
- `<repo-name>` → publish from that connected code repo (must exist in `.sdlc/repos.json`). A value with a `/` in
|
|
70
|
+
it is that repo's registry `path` (the form a repo named `product` is recorded in).
|
|
66
71
|
- `dedicated` → a dedicated docs repo the user names (recorded as `publishRepo`).
|
|
67
72
|
|
|
68
73
|
Resolve `basePath` (the Vite `base`) per the table in `references/docs-registry.md`:
|
|
@@ -78,8 +83,8 @@ Upsert into `{project-root}/.sdlc/docs.json` (create the file + parent `.sdlc/`
|
|
|
78
83
|
```json
|
|
79
84
|
{
|
|
80
85
|
"target": "github-pages",
|
|
81
|
-
"scope": "
|
|
82
|
-
"publishRepo": "<repo or
|
|
86
|
+
"scope": "product",
|
|
87
|
+
"publishRepo": "<repo or Product name>",
|
|
83
88
|
"basePath": "/<repo>/",
|
|
84
89
|
"public": true,
|
|
85
90
|
"auth": "user",
|
|
@@ -89,7 +94,7 @@ Upsert into `{project-root}/.sdlc/docs.json` (create the file + parent `.sdlc/`
|
|
|
89
94
|
}
|
|
90
95
|
```
|
|
91
96
|
|
|
92
|
-
- `target: "none"` records a deliberate build-only project: `{ "target": "none", "scope": "
|
|
97
|
+
- `target: "none"` records a deliberate build-only project: `{ "target": "none", "scope": "product",
|
|
93
98
|
"publishRepo": null, "basePath": "/", "source": "unavailable", ... }`.
|
|
94
99
|
- `connect` is **idempotent** — re-running it overwrites the single connection in place (a project has
|
|
95
100
|
one docs target at a time; switching targets is just another `connect`).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# `.sdlc/docs.json` — the docs/Pages registry
|
|
2
2
|
|
|
3
3
|
Project-wide, shared across every epic's docs site **and** the project overview site (NOT per-epic).
|
|
4
|
-
The sibling of `.sdlc/
|
|
4
|
+
The sibling of `.sdlc/product.json`, `.sdlc/repos.json`, and `.sdlc/design.json`. Written by
|
|
5
5
|
`yad-connect-docs`; read by `yad-docs`, `yad-docs-overview`, `yad-docs-sync`, and the `yad docs` CLI.
|
|
6
6
|
Holds **no credentials** — every field is a plain reference. Auth is always the local user's own
|
|
7
7
|
`gh`/`glab`/git session.
|
|
@@ -11,8 +11,8 @@ Holds **no credentials** — every field is a plain reference. Auth is always th
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
13
13
|
"target": "github-pages | gitlab-pages | none",
|
|
14
|
-
"scope": "
|
|
15
|
-
"publishRepo": "<repo or
|
|
14
|
+
"scope": "product | <repo-name> | dedicated",
|
|
15
|
+
"publishRepo": "<repo or Product name>",
|
|
16
16
|
"basePath": "/<repo>/",
|
|
17
17
|
"public": true,
|
|
18
18
|
"auth": "user",
|
|
@@ -25,7 +25,7 @@ Holds **no credentials** — every field is a plain reference. Auth is always th
|
|
|
25
25
|
| Field | Meaning |
|
|
26
26
|
|-------|---------|
|
|
27
27
|
| `target` | The publish adapter. `none` = deliberate build-only (no publish, no error). |
|
|
28
|
-
| `scope` | Where the Pages site publishes from: the `
|
|
28
|
+
| `scope` | Where the Pages site publishes from: the `product` repo, one connected `<repo-name>`, or a `dedicated` docs repo. A file written before 4.0 says `hub`, the old name for `product`; read it the same way. A code repo that is itself named `product` is recorded by its registry `path` (for example `repos/product`), so it is not read as the Product. |
|
|
29
29
|
| `publishRepo` | The concrete repo name resolved from `scope`. `null` when `target: "none"`. |
|
|
30
30
|
| `basePath` | The Vite `base` substituted into each generated site (resolution table below). Normalized to a leading + trailing `/`. |
|
|
31
31
|
| `public` | Whether the published site is public. |
|
|
@@ -36,15 +36,15 @@ Holds **no credentials** — every field is a plain reference. Auth is always th
|
|
|
36
36
|
`target: "none"` records `{ "target": "none", "publishRepo": null, "basePath": "/", "source":
|
|
37
37
|
"unavailable", ... }`.
|
|
38
38
|
|
|
39
|
-
## Platform auto-detection (from `.sdlc/
|
|
39
|
+
## Platform auto-detection (from `.sdlc/product.json`)
|
|
40
40
|
|
|
41
|
-
When `target` is not given, map the Product's `platform` the same way `yad-connect-repos` maps a repo host:
|
|
41
|
+
When `target` is not given, read `.sdlc/product.json` (`.sdlc/hub.json` on an older Product that has only that name) and map the Product's `platform` the same way `yad-connect-repos` maps a repo host:
|
|
42
42
|
|
|
43
|
-
| `
|
|
43
|
+
| `product.json` `platform` | default `target` |
|
|
44
44
|
|-----------------------|------------------|
|
|
45
45
|
| `github` | `github-pages` |
|
|
46
46
|
| `gitlab` | `gitlab-pages` |
|
|
47
|
-
| `null` / no
|
|
47
|
+
| `null` / no settings file | `none` (build-only) |
|
|
48
48
|
|
|
49
49
|
## Base-path resolution table
|
|
50
50
|
|
|
@@ -27,7 +27,7 @@ explains the concept. The learning layer is **purely opt-in and never blocks a g
|
|
|
27
27
|
an MCP like the design/testing tools (DeepTutor ships no MCP server). The skill detects the **binary on
|
|
28
28
|
PATH** and degrades when it is absent; it never installs DeepTutor.
|
|
29
29
|
- Registry: `{project-root}/.sdlc/learning.json` (project-wide, shared across all epics — NOT per-epic),
|
|
30
|
-
the sibling of `.sdlc/repos.json`, `.sdlc/
|
|
30
|
+
the sibling of `.sdlc/repos.json`, `.sdlc/product.json`, `.sdlc/design.json`, and `.sdlc/testing.json`.
|
|
31
31
|
- Per-epic, per-member learning records + rendered tutorials are written later by `yad-learn`
|
|
32
32
|
(`epics/EP-<slug>/.sdlc/learning-records.json` and `epics/EP-<slug>/learning/`), not here.
|
|
33
33
|
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
|
|
@@ -51,7 +51,7 @@ root, not under any `epics/EP-<slug>/.sdlc/`.
|
|
|
51
51
|
## Git tracking
|
|
52
52
|
|
|
53
53
|
Commit the **registry** (`learning.json`) — it is small, reviewable, and holds no secrets (references
|
|
54
|
-
only). This mirrors how `repos.json`, `
|
|
54
|
+
only). This mirrors how `repos.json`, `product.json`, `design.json`, and `testing.json` are committed.
|
|
55
55
|
|
|
56
56
|
## Greenfield
|
|
57
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-connect-repos
|
|
3
|
-
description: 'Connects code repos to the Product so the front/"brain" phases are code-aware. Registers N code repos (GitHub or GitLab, local-user auth, no stored tokens) into the project-wide .sdlc/repos.json, then caches an AI-readable picture of each — a compressed Repomix pack and a lightweight code-map (existing endpoints/events/data-models/modules), secret-scanned. Run at one-time setup or any time a new repo is added. Reusable, idempotent, refreshable; staleness is tracked by HEAD sha. `yad repo refresh --push` publishes the refreshed code-maps + registry to the Product default branch as a chore(
|
|
3
|
+
description: 'Connects code repos to the Product so the front/"brain" phases are code-aware. Registers N code repos (GitHub or GitLab, local-user auth, no stored tokens) into the project-wide .sdlc/repos.json, then caches an AI-readable picture of each — a compressed Repomix pack and a lightweight code-map (existing endpoints/events/data-models/modules), secret-scanned. Run at one-time setup or any time a new repo is added. Reusable, idempotent, refreshable; staleness is tracked by HEAD sha. `yad repo refresh --push` publishes the refreshed code-maps + registry to the Product default branch as a chore(product): sync code-context [skip ci] audit commit. Also drafts each code repo''s risk map (.sdlc/risk-map — a level per directory, no names), classified by reading the code and marked guessed for a person to confirm. Use when the user says "connect a repo", "connect the code repos", "refresh the code context", "list connected repos", or "push the code-map refresh".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Connect Code Repos (make the brain code-aware)
|
|
@@ -28,7 +28,7 @@ repo itself — a draft of that repo's risk map (Step 3b), which the team commit
|
|
|
28
28
|
|
|
29
29
|
## Inputs
|
|
30
30
|
|
|
31
|
-
- `action` — `connect` | `refresh` | `list` | `disconnect` | `detect-
|
|
31
|
+
- `action` — `connect` | `refresh` | `list` | `disconnect` | `detect-product` (default `connect`; `detect-hub`, the old name, still works).
|
|
32
32
|
- `repo` — the repo's short name (the key used in stories' `repos:` tag, e.g. `backend`).
|
|
33
33
|
- `path` — local path to the code repo (relative to `{project-root}` or absolute). For local repos.
|
|
34
34
|
It must resolve inside the **workspace** — the project root's parent — so the standard layout, where
|
|
@@ -50,6 +50,9 @@ repo itself — a draft of that repo's risk map (Step 3b), which the team commit
|
|
|
50
50
|
directly in `$HOME` — with the Product at `~/product` the workspace becomes `~` and every home-dir
|
|
51
51
|
sibling turns into a registrable repo. The workspace directory itself (`..`) is never registrable.
|
|
52
52
|
- `git_url` — optional remote (SSH or HTTPS; GitHub or GitLab). Used when the repo is not yet on disk.
|
|
53
|
+
`yad join` and `yad repo clone` clone only a network address (`https://`, `http://`, `ssh://`,
|
|
54
|
+
`git://`, `user@host:path`, or an ssh alias `host:path`); a local path is refused unless
|
|
55
|
+
`YAD_ALLOW_LOCAL_REMOTES=1` is set, and then only a folder that exists outside the workspace.
|
|
53
56
|
|
|
54
57
|
yadflow keeps **no list of people** (E62): no roster, no roles, no repo owners, no commit emails. Do not
|
|
55
58
|
ask for them and do not write them.
|
|
@@ -59,7 +62,13 @@ ask for them and do not write them.
|
|
|
59
62
|
### Step 1 — Resolve the repo and its auth (GitHub + GitLab, local user)
|
|
60
63
|
Determine where the code is:
|
|
61
64
|
- If `path` is given and is a git repo (`.git` present) → use it in place.
|
|
62
|
-
-
|
|
65
|
+
- A teammate joining an existing Product gets every registered repo cloned at its recorded `path` by
|
|
66
|
+
`yad join <url>` (E79) — no need to clone them one by one here.
|
|
67
|
+
- A repo that is **already registered** but missing on this machine (a teammate registered it after you
|
|
68
|
+
joined) → run **`yad repo clone [<name>]`** (E81). It clones at the recorded `path`, with the same
|
|
69
|
+
checks as `join`, and never touches a repo that is already there. `yad repo list` and `yad doctor`
|
|
70
|
+
name such a repo as not cloned. Clone by hand only a repo that is **not registered yet**.
|
|
71
|
+
- Else if `git_url` is given (a repo not registered yet) → **clone it as the local user** into a working location
|
|
63
72
|
(`{code_repos_root}/<repo>/` by `config.yaml` `build.code_repos_root`, or a path the user names):
|
|
64
73
|
```
|
|
65
74
|
git clone <git_url> <dest>
|
|
@@ -152,7 +161,7 @@ the Shape phases will now load this repo's code-map. Nothing auto-advances; this
|
|
|
152
161
|
`syncedHead` + `lastSyncedAt`. Same machinery as `connect`. Once the AI has regenerated the
|
|
153
162
|
`code-map.md` (Step 3), publish it to the Product with **`yad repo refresh <repo> --push`**: it
|
|
154
163
|
commits the tracked code-maps + `.sdlc/repos.json` (never the gitignored `pack.md`) as one
|
|
155
|
-
audit-trail commit `chore(
|
|
164
|
+
audit-trail commit `chore(product): sync code-context — <repos> by @<login> [skip ci]` and pushes it
|
|
156
165
|
straight to the Product's **default branch** (add `--allow-branch` to commit on a non-default branch).
|
|
157
166
|
This is the code-context analogue of `yad checkpoint` — human-owned machine state, no Task trailer,
|
|
158
167
|
no Co-Authored-By.
|
|
@@ -164,10 +173,13 @@ the Shape phases will now load this repo's code-map. Nothing auto-advances; this
|
|
|
164
173
|
## Product detection (the Shape review bridge)
|
|
165
174
|
|
|
166
175
|
The Product is itself a git repo on a platform. This action records that so the Shape review/comment/
|
|
167
|
-
approval cycle can run through a real PR/MR on the Product (`yad-review-gate` + `yad-
|
|
168
|
-
writes only `{project-root}/.sdlc/hub.json` (`config.yaml` `product.config` (older projects: `hub.config`)) — never an epic's state/approvals.
|
|
176
|
+
approval cycle can run through a real PR/MR on the Product (`yad-review-gate` + `yad-product-bridge`). It
|
|
177
|
+
writes only `{project-root}/.sdlc/product.json` and its older name `.sdlc/hub.json` (`config.yaml` `product.config` (older projects: `hub.config`)) — never an epic's state/approvals.
|
|
169
178
|
|
|
170
|
-
- **`detect-
|
|
179
|
+
- **`detect-product`** — detect the Product's own platform and upsert the Product settings: read
|
|
180
|
+
`.sdlc/product.json` (`.sdlc/hub.json` on an older Product that has only that name), then write
|
|
181
|
+
`.sdlc/product.json` and copy it byte for byte to `.sdlc/hub.json` (`cp .sdlc/product.json .sdlc/hub.json`;
|
|
182
|
+
both until v5 — two that differ by even a space are refused, YAD-STATE-008 — E122). Run
|
|
171
183
|
`git remote get-url origin` **on the Product** and read the host with the SAME logic Step 1 uses for code
|
|
172
184
|
repos: `github.com` → `github`, GitLab host → `gitlab`, no remote → `platform: null`. Record
|
|
173
185
|
`git_url`, `default_branch`, `detectedAt`, and **all three of** `ledger`, `bridge_enabled` and
|
|
@@ -204,6 +216,17 @@ platform login that gave it. The platform, not a stored list, decides who can ap
|
|
|
204
216
|
If the Product has no remote (`platform: null`) or the verified ledger is disabled, the Shape gate runs
|
|
205
217
|
local with no error — the verified ledger is purely additive.
|
|
206
218
|
|
|
219
|
+
## The Product-link record (E120)
|
|
220
|
+
Each connected code repo also carries `.sdlc/product-link.json` — the Product's `git_url`, the `path`
|
|
221
|
+
where CI checks the Product out (`.yad/product` by default) and its `default_branch`. It is not written
|
|
222
|
+
here: `yad setup`, `yad check --fix` and `yad update` write it (from the Product's own settings, with
|
|
223
|
+
any user name or password taken out of `git_url`) into every connected repo, and `--push` commits it
|
|
224
|
+
to each repo's default branch — a record on disk that is not committed as it stands is committed by the
|
|
225
|
+
next `--push`, though `yad check` reads it as `ok`. The Product-reading gates read it from
|
|
226
|
+
the base; `checks/product-checkout.sh` uses it with the `YAD_PRODUCT_TOKEN` secret. After connecting a
|
|
227
|
+
repo, run `yad check --fix --push`; `yad doctor` warns `repos:product-link-missing` until it is on
|
|
228
|
+
the repo's default branch.
|
|
229
|
+
|
|
207
230
|
## Live on-demand (the third context layer)
|
|
208
231
|
The cached pack + map are the default. When a Shape phase needs an **area** not in the map, it may
|
|
209
232
|
re-run Repomix **live**, scoped to that area:
|
|
@@ -229,7 +252,7 @@ it does not silently re-pack. Refreshing the cache is a human decision. Document
|
|
|
229
252
|
## Reference
|
|
230
253
|
- Registry schema + freshness rule: `references/repos-registry.md`.
|
|
231
254
|
- The risk map — format, rubric, what the agent may change: `references/risk-map.md`.
|
|
232
|
-
- Product config (the review bridge): `references/
|
|
255
|
+
- Product config (the review bridge): `references/product-config.md`.
|
|
233
256
|
- Repomix command, secret-scan, degrade path, the code-map prompt, and live on-demand:
|
|
234
257
|
`references/code-context.md`.
|
|
235
258
|
- The repomix discipline this reuses (one-feature-at-a-time variant): `../yad-backfill/references/backfill.md`.
|
|
@@ -81,7 +81,7 @@ side-effect:** when a repo is stale (HEAD ≠ `syncedHead`), the phase **flags i
|
|
|
81
81
|
"`<repo>` is stale; run `yad repo refresh <repo>` to re-pack the cache + `syncedHead`" — rather than
|
|
82
82
|
silently re-packing the whole repo. A phase never refreshes the registry on its own; the human runs
|
|
83
83
|
`yad repo refresh` (or `yad check --fix`). After the AI regenerates the code-map, `yad repo refresh
|
|
84
|
-
--push` publishes the refreshed code-maps + registry to the Product's default branch as a `chore(
|
|
84
|
+
--push` publishes the refreshed code-maps + registry to the Product's default branch as a `chore(product): sync
|
|
85
85
|
code-context … [skip ci]` audit commit (never the pack's content; `--allow-branch` overrides the branch
|
|
86
86
|
guard). The `pack.md` is gitignored — `yad repo refresh`/`yad setup` scaffold
|
|
87
87
|
`.sdlc/code-context/*/pack.md` into the Product `.gitignore` (so a regenerated pack never dirties the tree),
|