yadflow 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/README.md +123 -186
- package/bin/{sdlc.mjs → yad.mjs} +18 -17
- package/cli/commit.mjs +3 -3
- package/cli/epic-state.mjs +2 -2
- package/cli/gate.mjs +8 -8
- package/cli/lib.mjs +1 -1
- package/cli/manifest.mjs +77 -36
- package/cli/openpr.mjs +3 -3
- package/cli/plan.mjs +88 -1
- package/cli/platform.mjs +2 -2
- package/cli/reconcile.mjs +18 -10
- package/cli/repo.mjs +5 -5
- package/cli/setup.mjs +7 -7
- package/docs/index.html +1227 -0
- package/package.json +8 -4
- package/skills/sdlc/config.yaml +24 -24
- package/skills/sdlc/install.sh +2 -2
- package/skills/sdlc/module-help.csv +16 -16
- package/skills/{sdlc-author-analysis → yad-analysis}/SKILL.md +12 -12
- package/skills/{sdlc-author-architecture → yad-architecture}/SKILL.md +12 -12
- package/skills/{sdlc-author-architecture → yad-architecture}/references/contract-format.md +1 -1
- package/skills/{sdlc-backfill → yad-backfill}/SKILL.md +4 -4
- package/skills/{sdlc-backfill → yad-backfill}/references/backfill.md +2 -2
- package/skills/{sdlc-backfill → yad-backfill}/templates/checks/backfill-check.sh +1 -1
- package/skills/{sdlc-checks → yad-checks}/SKILL.md +20 -20
- package/skills/{sdlc-checks → yad-checks}/references/check-gates.md +21 -21
- package/skills/{sdlc-checks → yad-checks}/templates/checks/contract-check.sh +2 -2
- package/skills/{sdlc-checks → yad-checks}/templates/checks/verified-commits.sh +2 -2
- package/skills/{sdlc-checks/templates/github/sdlc-checks.yml → yad-checks/templates/github/yad-checks.yml} +3 -3
- package/skills/{sdlc-checks/templates/github/sdlc-verified-commits.yml → yad-checks/templates/github/yad-verified-commits.yml} +4 -4
- package/skills/{sdlc-checks → yad-checks}/templates/gitlab/gitlab-ci.include-root.yml +3 -3
- package/skills/{sdlc-checks/templates/gitlab/sdlc-checks.gitlab-ci.yml → yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml} +7 -7
- package/skills/{sdlc-checks/templates/gitlab/sdlc-verified-commits.gitlab-ci.yml → yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml} +4 -4
- package/skills/{sdlc-connect-repos → yad-connect-repos}/SKILL.md +7 -7
- package/skills/{sdlc-connect-repos → yad-connect-repos}/references/code-context.md +6 -6
- package/skills/{sdlc-connect-repos → yad-connect-repos}/references/hub-config.md +2 -2
- package/skills/{sdlc-author-epic → yad-epic}/SKILL.md +13 -13
- package/skills/{sdlc-author-epic → yad-epic}/references/state-schema.md +13 -13
- package/skills/{sdlc-hub-bridge → yad-hub-bridge}/SKILL.md +24 -24
- package/skills/{sdlc-hub-bridge → yad-hub-bridge}/references/bridge.md +11 -11
- package/skills/{sdlc-hub-bridge → yad-hub-bridge}/references/login-roster.md +2 -2
- package/skills/{sdlc-hub-bridge → yad-hub-bridge}/templates/checks/hub-route.sh +3 -3
- package/skills/{sdlc-hub-bridge/templates/github/sdlc-gate-sync.yml → yad-hub-bridge/templates/github/yad-gate-sync.yml} +10 -10
- package/skills/{sdlc-hub-bridge → yad-hub-bridge}/templates/gitlab/gitlab-ci.include-root.yml +3 -3
- package/skills/{sdlc-hub-bridge/templates/gitlab/sdlc-gate-sync.gitlab-ci.yml → yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml} +11 -11
- package/skills/{sdlc-implement → yad-implement}/SKILL.md +14 -14
- package/skills/{sdlc-implement → yad-implement}/references/implement-conventions.md +4 -4
- package/skills/{sdlc-pr-template → yad-pr-template}/SKILL.md +11 -11
- package/skills/{sdlc-pr-template → yad-pr-template}/references/risk-routing.md +5 -5
- package/skills/{sdlc-pr-template → yad-pr-template}/templates/checks/risk-route.sh +2 -2
- package/skills/{sdlc-pr-template → yad-pr-template}/templates/github/pull_request_template.md +1 -1
- package/skills/{sdlc-pr-template → yad-pr-template}/templates/gitlab/merge_request_templates/Default.md +1 -1
- package/skills/{sdlc-pr-template → yad-pr-template}/templates/hub/github/pull_request_template.md +4 -4
- package/skills/{sdlc-pr-template → yad-pr-template}/templates/hub/gitlab/merge_request_templates/Default.md +4 -4
- package/skills/{sdlc-review-comments → yad-review-comments}/SKILL.md +6 -6
- package/skills/{sdlc-review-comments → yad-review-comments}/references/comment-conventions.md +6 -6
- package/skills/{sdlc-review-comments → yad-review-comments}/templates/github/REVIEW_COMMENTS.md +2 -2
- package/skills/{sdlc-review-comments → yad-review-comments}/templates/gitlab/REVIEW_COMMENTS.md +2 -2
- package/skills/{sdlc-review-gate → yad-review-gate}/SKILL.md +13 -13
- package/skills/{sdlc-review-gate → yad-review-gate}/references/gating.md +3 -3
- package/skills/{sdlc-run → yad-run}/SKILL.md +12 -12
- package/skills/{sdlc-run → yad-run}/references/run-loop.md +10 -10
- package/skills/{sdlc-ship → yad-ship}/SKILL.md +8 -8
- package/skills/{sdlc-ship → yad-ship}/references/ship-and-record.md +3 -3
- package/skills/{sdlc-ship → yad-ship}/templates/.coderabbit.yaml +1 -1
- package/skills/{sdlc-spec → yad-spec}/SKILL.md +11 -11
- package/skills/{sdlc-spec → yad-spec}/references/spec-handoff.md +2 -2
- package/skills/{sdlc-status → yad-status}/SKILL.md +6 -6
- package/skills/{sdlc-author-stories → yad-stories}/SKILL.md +10 -10
- package/skills/{sdlc-author-stories → yad-stories}/references/story-schema.md +1 -1
- package/skills/{sdlc-author-ui → yad-ui}/SKILL.md +9 -9
- /package/skills/{sdlc-checks → yad-checks}/templates/checks/build-test-lint.sh +0 -0
- /package/skills/{sdlc-checks → yad-checks}/templates/checks/spec-link.sh +0 -0
- /package/skills/{sdlc-checks → yad-checks}/templates/gitlab/.gitlab-ci.yml +0 -0
- /package/skills/{sdlc-connect-repos → yad-connect-repos}/references/repos-registry.md +0 -0
- /package/skills/{sdlc-implement → yad-implement}/templates/.gitmessage +0 -0
package/bin/{sdlc.mjs → yad.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `
|
|
2
|
+
// `yad` — setup/maintenance + the PR-driven review gate + build helpers for the SDLC module.
|
|
3
3
|
import { VERSION } from '../cli/manifest.mjs';
|
|
4
4
|
import { c, log, closePrompts } from '../cli/lib.mjs';
|
|
5
5
|
import { runSetup } from '../cli/setup.mjs';
|
|
@@ -10,28 +10,29 @@ import { runCommit } from '../cli/commit.mjs';
|
|
|
10
10
|
import { runOpenPr } from '../cli/openpr.mjs';
|
|
11
11
|
import { runRepo } from '../cli/repo.mjs';
|
|
12
12
|
|
|
13
|
-
const HELP = `${c.bold('
|
|
13
|
+
const HELP = `${c.bold('yad')} — setup, review-gate & build helpers for the SDLC Workflow module ${c.dim('v' + VERSION)}
|
|
14
14
|
|
|
15
15
|
${c.bold('Setup & maintenance')}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
yad setup Guided first-run setup (install module, connect & wire repos)
|
|
17
|
+
yad check Report what is missing / drifted / stale / legacy (read-only)
|
|
18
|
+
yad check --fix Reconcile: fill what is missing, update what changed
|
|
19
|
+
yad update Apply drift only (alias for: check --fix --scope=changed);
|
|
20
|
+
also migrates pre-2.0 sdlc-* installs to the yad-* names
|
|
20
21
|
|
|
21
22
|
${c.bold('Review gate (front half)')}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
yad gate open <epic> <artifact> Open the review PR/MR; mark the step in_review
|
|
24
|
+
yad gate sync <epic> [artifact] Pull PR state -> ledger; advance on approved+resolved+merged
|
|
25
|
+
yad gate comments <epic> [artifact] Fetch unresolved review comments to address
|
|
26
|
+
yad gate status <epic> Show each review step + approvals
|
|
27
|
+
yad gate ci [--branch <head>] [--pr <n>]
|
|
27
28
|
CI entry (hub workflow): derive epic/artifact from the review branch,
|
|
28
29
|
sync, commit the ledger to the default branch (sweep all PRs if no --branch)
|
|
29
30
|
|
|
30
31
|
${c.bold('Build helpers')}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
|
|
33
|
+
yad open-pr [--repo <name>] Open a code-repo task PR/MR from the template
|
|
34
|
+
yad repo list Show connected repos (fresh / stale)
|
|
35
|
+
yad repo refresh [name] Re-pack a stale repo (a human decision)
|
|
35
36
|
|
|
36
37
|
${c.bold('Options')}
|
|
37
38
|
--dir <path> Target project root (default: cwd)
|
|
@@ -99,7 +100,7 @@ async function main() {
|
|
|
99
100
|
const [, action, epic, artifact] = o._;
|
|
100
101
|
// `gate ci` takes no positionals — epic/artifact come from --branch (or a sweep of all PRs).
|
|
101
102
|
if (action === 'ci') { await gateCi(o.dir, { branch: o.branch, pr: o.pr, push: !o.noPush, today }); break; }
|
|
102
|
-
if (!epic) { log(c.red('usage:
|
|
103
|
+
if (!epic) { log(c.red('usage: yad gate <open|sync|comments|status|ci> <epic> [artifact]')); process.exitCode = 1; break; }
|
|
103
104
|
// The epic id becomes a path segment under epics/ — reject anything but EP-<slug> outright.
|
|
104
105
|
if (!isValidEpicId(epic)) { log(c.red(`invalid epic id: ${epic} (expected EP-<slug>, [a-z0-9-] only)`)); process.exitCode = 1; break; }
|
|
105
106
|
if (action === 'open') await gateOpen(o.dir, { epic, artifact, today });
|
|
@@ -129,7 +130,7 @@ async function main() {
|
|
|
129
130
|
|
|
130
131
|
main()
|
|
131
132
|
.catch((err) => {
|
|
132
|
-
log(c.red(`\
|
|
133
|
+
log(c.red(`\nyad failed: ${err?.message || err}`));
|
|
133
134
|
process.exitCode = 1;
|
|
134
135
|
})
|
|
135
136
|
.finally(closePrompts);
|
package/cli/commit.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `
|
|
1
|
+
// `yad commit` — commit by the SDLC conventions (CONTRIBUTING.md / config.yaml build).
|
|
2
2
|
// Subject is Conventional Commits; trailers are emitted in the fixed order
|
|
3
3
|
// Task -> Contract-Change -> Co-Authored-By. The human git author OWNS the commit; the AI is only a
|
|
4
4
|
// co-author (flagged with --ai, or `none` for human-only). An atomic-commit guard keeps diffs small.
|
|
@@ -36,7 +36,7 @@ export function taskFromBranch(branch = '') {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export async function runCommit(root, opts = {}) {
|
|
39
|
-
log(c.bold('\
|
|
39
|
+
log(c.bold('\nyad commit'));
|
|
40
40
|
if (!exists(path.join(root, '.git'))) { fail('not a git repo'); process.exitCode = 1; return; }
|
|
41
41
|
|
|
42
42
|
const staged = run('git', ['diff', '--cached', '--name-only'], { cwd: root }).stdout.split('\n').filter(Boolean);
|
|
@@ -70,7 +70,7 @@ export async function runCommit(root, opts = {}) {
|
|
|
70
70
|
return { message };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
// installed by
|
|
73
|
+
// installed by yad-implement, but offer it here too for convenience.
|
|
74
74
|
export function ensureGitMessage(repoRoot, templateSrc) {
|
|
75
75
|
const dest = path.join(repoRoot, '.gitmessage');
|
|
76
76
|
if (exists(dest)) return false;
|
package/cli/epic-state.mjs
CHANGED
|
@@ -55,7 +55,7 @@ export function upsertHubPr(hubPrs = [], rec) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// SHA-256 of the contract surface block (architecture only). Mirrors
|
|
58
|
-
//
|
|
58
|
+
// yad-architecture/references/contract-format.md (awk markers + sha256).
|
|
59
59
|
// Line endings are normalized to LF so the same surface hashes identically across
|
|
60
60
|
// platforms (a CRLF re-save must not revoke approvals). A BEGIN without an END is
|
|
61
61
|
// malformed and yields null — never a silent hash of everything to end-of-file.
|
|
@@ -194,7 +194,7 @@ export function gatePredicate({
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
// Advance the step in state.json once the predicate passes. Mirrors
|
|
197
|
+
// Advance the step in state.json once the predicate passes. Mirrors yad-review-gate Step 3:
|
|
198
198
|
// mark this review step done, unblock the next step, or set `ready-for-build` for the last one.
|
|
199
199
|
export function advanceState(state, step) {
|
|
200
200
|
const i = state.steps.findIndex((s) => s.id === step.id);
|
package/cli/gate.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `
|
|
1
|
+
// `yad gate open|sync|comments|status` — the PR/MR-driven front-half review gate.
|
|
2
2
|
// The platform PR/MR is the review UI; this command syncs its state into the file ledger and, when
|
|
3
3
|
// the gate passes (approvals satisfied + all comment threads resolved + PR merged), auto-advances the
|
|
4
4
|
// step. The merge click is the human approval act, so front steps still never machine_advance.
|
|
@@ -159,7 +159,7 @@ export async function gateSync(root, { epic, artifact, today, reader = readPr }
|
|
|
159
159
|
|
|
160
160
|
let { approvals, comments, hubPrs, state } = ledger;
|
|
161
161
|
const targets = hubPrs.filter((p) => !artifact || p.artifact === artifact);
|
|
162
|
-
if (!targets.length) { warn(`no open review PR recorded for ${epic}${artifact ? ` / ${artifact}` : ''} (run \`
|
|
162
|
+
if (!targets.length) { warn(`no open review PR recorded for ${epic}${artifact ? ` / ${artifact}` : ''} (run \`yad gate open\` first)`); return { synced: 0 }; }
|
|
163
163
|
|
|
164
164
|
let synced = 0;
|
|
165
165
|
for (const pr of targets) {
|
|
@@ -212,7 +212,7 @@ export async function gateSync(root, { epic, artifact, today, reader = readPr }
|
|
|
212
212
|
return { synced };
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
// `
|
|
215
|
+
// `yad gate ci` — the self-sufficient entry point hub CI calls on platform events (review
|
|
216
216
|
// submitted/dismissed, PR synchronize, PR merged) and on the GitLab schedule. Event mode derives
|
|
217
217
|
// epic/artifact from the `review/EP-<slug>/<base>` head branch (so it works even when the author
|
|
218
218
|
// never committed hub-prs.json); sweep mode (no --branch) re-syncs every open review PR. Either way
|
|
@@ -223,7 +223,7 @@ export async function gateCi(root, { branch, pr, today, push = true, reader = re
|
|
|
223
223
|
if (!hub?.platform) { warn('no hub platform configured (.sdlc/hub.json) — nothing to sync'); return { synced: 0 }; }
|
|
224
224
|
const git = (...args) => run('git', args, { cwd: root });
|
|
225
225
|
// Push target: an explicit hub.default_branch wins; else the branch CI actually checked out (the
|
|
226
|
-
// workflow checks out the PR base / $CI_DEFAULT_BRANCH — hub.json from `
|
|
226
|
+
// workflow checks out the PR base / $CI_DEFAULT_BRANCH — hub.json from `yad setup` has no
|
|
227
227
|
// default_branch field, so the checkout is the truth); 'main' only as the last resort.
|
|
228
228
|
const head = git('rev-parse', '--abbrev-ref', 'HEAD').stdout;
|
|
229
229
|
const target = hub.default_branch || (head && head !== 'HEAD' ? head : 'main');
|
|
@@ -343,7 +343,7 @@ export async function gateCi(root, { branch, pr, today, push = true, reader = re
|
|
|
343
343
|
if (!git('pull', '--rebase', 'origin', target).ok) git('rebase', '--abort'); // never leave a wedged rebase
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
|
-
fail(`could not push the ledger to origin/${target} — protected branch? allow the CI actor to push (see
|
|
346
|
+
fail(`could not push the ledger to origin/${target} — protected branch? allow the CI actor to push (see yad-hub-bridge references/bridge.md) or run \`yad gate sync\` locally`);
|
|
347
347
|
process.exitCode = 1;
|
|
348
348
|
return { synced };
|
|
349
349
|
}
|
|
@@ -354,7 +354,7 @@ export async function gateComments(root, { epic, artifact, today, reader = readP
|
|
|
354
354
|
const epicDir = epicRoot(root, epic);
|
|
355
355
|
const ledger = loadLedger(epicDir);
|
|
356
356
|
const targets = (ledger.hubPrs || []).filter((p) => !artifact || p.artifact === artifact);
|
|
357
|
-
if (!targets.length) { warn('no review PR recorded — run `
|
|
357
|
+
if (!targets.length) { warn('no review PR recorded — run `yad gate open` first'); return; }
|
|
358
358
|
for (const pr of targets) {
|
|
359
359
|
const pull = reader(hub.platform, pr.number, { cwd: root });
|
|
360
360
|
if (!pull.ok) { warn(`${pr.artifact}: ${pull.reason}`); continue; }
|
|
@@ -391,7 +391,7 @@ export async function gateOpen(root, { epic, artifact, today } = {}) {
|
|
|
391
391
|
const epicDir = epicRoot(root, epic);
|
|
392
392
|
const ledger = loadLedger(epicDir);
|
|
393
393
|
if (!ledger.state) { fail(`no epic state at ${epicDir}`); process.exitCode = 1; return; }
|
|
394
|
-
if (!artifact) { fail('artifact is required: `
|
|
394
|
+
if (!artifact) { fail('artifact is required: `yad gate open <epic> <artifact>`'); process.exitCode = 1; return; }
|
|
395
395
|
const step = findReviewStep(ledger.state, artifact);
|
|
396
396
|
if (!step) { fail(`no review step for ${artifact}`); process.exitCode = 1; return; }
|
|
397
397
|
const b = base(artifact);
|
|
@@ -416,7 +416,7 @@ export async function gateOpen(root, { epic, artifact, today } = {}) {
|
|
|
416
416
|
ledger.hubPrs = upsertHubPr(ledger.hubPrs, { step: step.id, artifact, platform: hub.platform, number, url: r.url, branch, lastSyncedAt: null });
|
|
417
417
|
writeJSON(ledger.files.hubPrs, ledger.hubPrs);
|
|
418
418
|
ok(`opened ${r.url}`);
|
|
419
|
-
hand(`reviewers approve/comment there; then run \`
|
|
419
|
+
hand(`reviewers approve/comment there; then run \`yad gate sync ${epic} ${artifact}\``);
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
// ---- helpers ------------------------------------------------------------------------------------
|
package/cli/lib.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Shared helpers for the `
|
|
1
|
+
// Shared helpers for the `yad` CLI. Node >=18 built-ins only — no dependencies.
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import * as readline from 'node:readline/promises';
|
package/cli/manifest.mjs
CHANGED
|
@@ -10,27 +10,68 @@ import { readFileSync } from 'node:fs';
|
|
|
10
10
|
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
11
11
|
export const VERSION = version;
|
|
12
12
|
|
|
13
|
-
// The 17 hand-authored
|
|
13
|
+
// The 17 hand-authored yad-* skills (mirrors skills/sdlc/install.sh).
|
|
14
14
|
export const SKILLS = [
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
15
|
+
'yad-analysis',
|
|
16
|
+
'yad-epic',
|
|
17
|
+
'yad-architecture',
|
|
18
|
+
'yad-ui',
|
|
19
|
+
'yad-stories',
|
|
20
|
+
'yad-connect-repos',
|
|
21
|
+
'yad-spec',
|
|
22
|
+
'yad-implement',
|
|
23
|
+
'yad-checks',
|
|
24
|
+
'yad-pr-template',
|
|
25
|
+
'yad-review-comments',
|
|
26
|
+
'yad-hub-bridge',
|
|
27
|
+
'yad-ship',
|
|
28
|
+
'yad-backfill',
|
|
29
|
+
'yad-run',
|
|
30
|
+
'yad-review-gate',
|
|
31
|
+
'yad-status',
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
// Pre-2.0 skill names (the sdlc-* -> yad-* rename). `check`/`update` migrate any install
|
|
35
|
+
// still carrying an old name: remove the old copy, install the renamed one.
|
|
36
|
+
export const LEGACY_SKILLS = {
|
|
37
|
+
'yad-analysis': 'sdlc-author-analysis',
|
|
38
|
+
'yad-epic': 'sdlc-author-epic',
|
|
39
|
+
'yad-architecture': 'sdlc-author-architecture',
|
|
40
|
+
'yad-ui': 'sdlc-author-ui',
|
|
41
|
+
'yad-stories': 'sdlc-author-stories',
|
|
42
|
+
'yad-connect-repos': 'sdlc-connect-repos',
|
|
43
|
+
'yad-spec': 'sdlc-spec',
|
|
44
|
+
'yad-implement': 'sdlc-implement',
|
|
45
|
+
'yad-checks': 'sdlc-checks',
|
|
46
|
+
'yad-pr-template': 'sdlc-pr-template',
|
|
47
|
+
'yad-review-comments': 'sdlc-review-comments',
|
|
48
|
+
'yad-hub-bridge': 'sdlc-hub-bridge',
|
|
49
|
+
'yad-ship': 'sdlc-ship',
|
|
50
|
+
'yad-backfill': 'sdlc-backfill',
|
|
51
|
+
'yad-run': 'sdlc-run',
|
|
52
|
+
'yad-review-gate': 'sdlc-review-gate',
|
|
53
|
+
'yad-status': 'sdlc-status',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Pre-2.0 wired-file dests replaced by renamed ones (old dest -> new dest, per platform).
|
|
57
|
+
// An old file is removed ONLY when its first line carries the old ownership marker —
|
|
58
|
+
// a same-named file the user authored themselves is never touched.
|
|
59
|
+
export const LEGACY_MARKER = '# sdlc-managed';
|
|
60
|
+
export const LEGACY_REPO_FILES = {
|
|
61
|
+
github: { '.github/workflows/sdlc-checks.yml': '.github/workflows/yad-checks.yml' },
|
|
62
|
+
gitlab: { '.gitlab/ci/sdlc-checks.yml': '.gitlab/ci/yad-checks.yml' },
|
|
63
|
+
};
|
|
64
|
+
export const LEGACY_HUB_FILES = {
|
|
65
|
+
github: {
|
|
66
|
+
'.github/workflows/sdlc-gate-sync.yml': '.github/workflows/yad-gate-sync.yml',
|
|
67
|
+
'.github/workflows/sdlc-verified-commits.yml': '.github/workflows/yad-verified-commits.yml',
|
|
68
|
+
},
|
|
69
|
+
gitlab: {
|
|
70
|
+
'.gitlab/ci/sdlc-gate-sync.yml': '.gitlab/ci/yad-gate-sync.yml',
|
|
71
|
+
'.gitlab/ci/sdlc-verified-commits.yml': '.gitlab/ci/yad-verified-commits.yml',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
34
75
|
// IDE install targets (relative to the target project root).
|
|
35
76
|
export const IDE_FOLDER_TARGETS = ['.claude', '.agents', '.zencoder']; // <ide>/skills/<skill>/ (folder copy)
|
|
36
77
|
export const IDE_OPENCODE_DIR = '.opencode/commands'; // <skill>.md (flat SKILL.md copy)
|
|
@@ -45,7 +86,7 @@ export const PROJECT_FILES = {
|
|
|
45
86
|
version: '.sdlc/cli-version.json',
|
|
46
87
|
};
|
|
47
88
|
|
|
48
|
-
// ---- `
|
|
89
|
+
// ---- `yad commit` conventions (mirror skills/sdlc/config.yaml `build`) ----
|
|
49
90
|
// Conventional-commit types (config.yaml commit_subject_style).
|
|
50
91
|
export const COMMIT_TYPES = ['feat', 'fix', 'docs', 'refactor', 'test', 'perf', 'build', 'ci', 'chore', 'revert'];
|
|
51
92
|
// Per-commit AI co-author choices (config.yaml build.ai_coauthor.allowed). The human git author OWNS
|
|
@@ -77,21 +118,21 @@ export const epicFiles = (epicRoot) => ({
|
|
|
77
118
|
// `common` always installs; the platform key installs by detected platform.
|
|
78
119
|
export const REPO_WIRING = {
|
|
79
120
|
common: [
|
|
80
|
-
{ src: 'skills/
|
|
81
|
-
{ src: 'skills/
|
|
82
|
-
{ src: 'skills/
|
|
83
|
-
{ src: 'skills/
|
|
84
|
-
{ src: 'skills/
|
|
121
|
+
{ src: 'skills/yad-checks/templates/checks/spec-link.sh', dest: 'checks/spec-link.sh', exec: true },
|
|
122
|
+
{ src: 'skills/yad-checks/templates/checks/contract-check.sh', dest: 'checks/contract-check.sh', exec: true },
|
|
123
|
+
{ src: 'skills/yad-checks/templates/checks/build-test-lint.sh', dest: 'checks/build-test-lint.sh', exec: true },
|
|
124
|
+
{ src: 'skills/yad-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
|
|
125
|
+
{ src: 'skills/yad-pr-template/templates/checks/risk-route.sh', dest: 'checks/risk-route.sh', exec: true },
|
|
85
126
|
],
|
|
86
127
|
github: [
|
|
87
|
-
{ src: 'skills/
|
|
88
|
-
{ src: 'skills/
|
|
89
|
-
{ src: 'skills/
|
|
128
|
+
{ src: 'skills/yad-checks/templates/github/yad-checks.yml', dest: '.github/workflows/yad-checks.yml' },
|
|
129
|
+
{ src: 'skills/yad-pr-template/templates/github/pull_request_template.md', dest: '.github/pull_request_template.md' },
|
|
130
|
+
{ src: 'skills/yad-review-comments/templates/github/REVIEW_COMMENTS.md', dest: '.github/REVIEW_COMMENTS.md' },
|
|
90
131
|
],
|
|
91
132
|
gitlab: [
|
|
92
|
-
{ src: 'skills/
|
|
93
|
-
{ src: 'skills/
|
|
94
|
-
{ src: 'skills/
|
|
133
|
+
{ src: 'skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml', dest: '.gitlab/ci/yad-checks.yml' },
|
|
134
|
+
{ src: 'skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md', dest: '.gitlab/merge_request_templates/Default.md' },
|
|
135
|
+
{ src: 'skills/yad-review-comments/templates/gitlab/REVIEW_COMMENTS.md', dest: '.gitlab/REVIEW_COMMENTS.md' },
|
|
95
136
|
],
|
|
96
137
|
};
|
|
97
138
|
|
|
@@ -102,18 +143,18 @@ export const wiringFor = (platform) => [
|
|
|
102
143
|
|
|
103
144
|
// Hub wiring: CI installed on the PRODUCT HUB itself (dest is the project root — the hub IS the
|
|
104
145
|
// root). Installed only when hub.json has a platform and the bridge is enabled. Carries the
|
|
105
|
-
// event-driven gate sync (approvals/change requests/the merge trigger `
|
|
146
|
+
// event-driven gate sync (approvals/change requests/the merge trigger `yad gate ci`) and the
|
|
106
147
|
// verified-commits gate (no unverified commits from unverified users reach merge on the hub).
|
|
107
148
|
export const HUB_WIRING = {
|
|
108
149
|
common: [
|
|
109
|
-
{ src: 'skills/
|
|
150
|
+
{ src: 'skills/yad-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
|
|
110
151
|
],
|
|
111
152
|
github: [
|
|
112
|
-
{ src: 'skills/
|
|
113
|
-
{ src: 'skills/
|
|
153
|
+
{ src: 'skills/yad-hub-bridge/templates/github/yad-gate-sync.yml', dest: '.github/workflows/yad-gate-sync.yml' },
|
|
154
|
+
{ src: 'skills/yad-checks/templates/github/yad-verified-commits.yml', dest: '.github/workflows/yad-verified-commits.yml' },
|
|
114
155
|
],
|
|
115
156
|
gitlab: [
|
|
116
|
-
{ src: 'skills/
|
|
117
|
-
{ src: 'skills/
|
|
157
|
+
{ src: 'skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml', dest: '.gitlab/ci/yad-gate-sync.yml' },
|
|
158
|
+
{ src: 'skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml', dest: '.gitlab/ci/yad-verified-commits.yml' },
|
|
118
159
|
],
|
|
119
160
|
};
|
package/cli/openpr.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// `
|
|
1
|
+
// `yad open-pr` — open a code-repo task PR/MR from the repo's platform template (build half).
|
|
2
2
|
// Detects the platform, pushes the current branch, and creates the PR/MR with Summary / Story-task /
|
|
3
|
-
// Impact & Risk prefilled. Distinct from `
|
|
3
|
+
// Impact & Risk prefilled. Distinct from `yad gate open`, which opens a front-half artifact-review PR
|
|
4
4
|
// on the product hub.
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import fs from 'node:fs';
|
|
@@ -33,7 +33,7 @@ function templateBody(repoRoot, platform, { task, risk, contract, domains }) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export async function runOpenPr(root, opts = {}) {
|
|
36
|
-
log(c.bold('\
|
|
36
|
+
log(c.bold('\nyad open-pr'));
|
|
37
37
|
const { repoRoot, meta } = resolveRepo(root, opts);
|
|
38
38
|
if (!exists(path.join(repoRoot, '.git'))) { fail(`not a git repo: ${repoRoot}`); process.exitCode = 1; return; }
|
|
39
39
|
|
package/cli/plan.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from './lib.mjs';
|
|
9
9
|
import {
|
|
10
10
|
SKILLS, IDE_FOLDER_TARGETS, IDE_OPENCODE_DIR, MODULE_FILES, wiringFor, HUB_WIRING, PROJECT_FILES,
|
|
11
|
+
LEGACY_SKILLS, LEGACY_MARKER, LEGACY_REPO_FILES, LEGACY_HUB_FILES,
|
|
11
12
|
} from './manifest.mjs';
|
|
12
13
|
|
|
13
14
|
// status: 'ok' | 'missing' | 'outdated'
|
|
@@ -65,6 +66,92 @@ export function moduleActions(root, ideTargets = ideTargetsFor(root)) {
|
|
|
65
66
|
return actions;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Migration of a pre-2.0 install (the sdlc-* -> yad-* rename). Status is 'legacy' — unlike
|
|
70
|
+
// 'missing' it is applied by `yad update` (--scope=changed) too, because the skill IS
|
|
71
|
+
// installed, just under its old name. apply() removes the old copy AND installs the renamed
|
|
72
|
+
// one, so a single update completes the rename even when the new copy would otherwise be
|
|
73
|
+
// skipped as missing-scope.
|
|
74
|
+
export function legacyModuleActions(root, ideTargets = ideTargetsFor(root)) {
|
|
75
|
+
const actions = [];
|
|
76
|
+
for (const ide of ideTargets) {
|
|
77
|
+
for (const [skill, old] of Object.entries(LEGACY_SKILLS)) {
|
|
78
|
+
if (ide === '.opencode') {
|
|
79
|
+
const oldDest = path.join(root, IDE_OPENCODE_DIR, `${old}.md`);
|
|
80
|
+
if (!exists(oldDest)) continue;
|
|
81
|
+
actions.push({
|
|
82
|
+
scope: ide,
|
|
83
|
+
item: `${old}.md → ${skill}.md`,
|
|
84
|
+
status: 'legacy',
|
|
85
|
+
apply: () => {
|
|
86
|
+
fs.rmSync(oldDest, { force: true });
|
|
87
|
+
copyFile(asset('skills', skill, 'SKILL.md'), path.join(root, IDE_OPENCODE_DIR, `${skill}.md`));
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
const oldDest = path.join(root, ide, 'skills', old);
|
|
92
|
+
if (!exists(oldDest)) continue;
|
|
93
|
+
actions.push({
|
|
94
|
+
scope: ide,
|
|
95
|
+
item: `${old} → ${skill}`,
|
|
96
|
+
status: 'legacy',
|
|
97
|
+
apply: () => {
|
|
98
|
+
fs.rmSync(oldDest, { recursive: true, force: true });
|
|
99
|
+
copyDir(asset('skills', skill), path.join(root, ide, 'skills', skill));
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return actions;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// True only for a file WE installed pre-2.0: its first line carries the old ownership marker
|
|
109
|
+
// (`# sdlc-managed:` / `# sdlc-managed-include:`). A same-named user-authored file is never ours.
|
|
110
|
+
function ownedByOldInstall(p) {
|
|
111
|
+
try { return fs.readFileSync(p, 'utf8').startsWith(LEGACY_MARKER); } catch { return false; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// old-dest -> new-dest migrations for wired CI files: remove the marker-owned old file and
|
|
115
|
+
// install its renamed replacement from the current wiring. GitLab fragments are referenced by
|
|
116
|
+
// path from the root `.gitlab-ci.yml` (`include: - local: ...`, written by the wire step), so
|
|
117
|
+
// the migration must also rewrite that include — otherwise the pipeline hard-fails on a
|
|
118
|
+
// `local file does not exist` the moment the old fragment is removed.
|
|
119
|
+
function legacyFileActions(scope, baseRoot, fileMap, wiring) {
|
|
120
|
+
const actions = [];
|
|
121
|
+
for (const [oldDest, newDest] of Object.entries(fileMap || {})) {
|
|
122
|
+
const oldPath = path.join(baseRoot, oldDest);
|
|
123
|
+
if (!ownedByOldInstall(oldPath)) continue;
|
|
124
|
+
const w = wiring.find((x) => x.dest === newDest);
|
|
125
|
+
if (!w) continue; // never delete a working file without a replacement to install
|
|
126
|
+
actions.push({
|
|
127
|
+
scope,
|
|
128
|
+
item: `${oldDest} → ${newDest}`,
|
|
129
|
+
status: 'legacy',
|
|
130
|
+
apply: () => {
|
|
131
|
+
fs.rmSync(oldPath, { force: true });
|
|
132
|
+
copyFile(asset(w.src), path.join(baseRoot, newDest), { exec: !!w.exec });
|
|
133
|
+
const rootCi = path.join(baseRoot, '.gitlab-ci.yml');
|
|
134
|
+
try {
|
|
135
|
+
const txt = fs.readFileSync(rootCi, 'utf8');
|
|
136
|
+
if (txt.includes(oldDest)) fs.writeFileSync(rootCi, txt.split(oldDest).join(newDest));
|
|
137
|
+
} catch { /* no root .gitlab-ci.yml (github repo, or fragment-only gitlab) — nothing to rewrite */ }
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return actions;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function legacyRepoActions(root, repo) {
|
|
145
|
+
return legacyFileActions(repo.name, path.resolve(root, repo.path), LEGACY_REPO_FILES[repo.platform], wiringFor(repo.platform));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function legacyHubActions(root) {
|
|
149
|
+
const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig));
|
|
150
|
+
if (!hub?.platform || !(hub.bridge_enabled === true || hub.bridge === true)) return [];
|
|
151
|
+
const wiring = [...HUB_WIRING.common, ...(HUB_WIRING[hub.platform] || [])];
|
|
152
|
+
return legacyFileActions('hub', root, LEGACY_HUB_FILES[hub.platform], wiring);
|
|
153
|
+
}
|
|
154
|
+
|
|
68
155
|
// Per-repo wiring (gate scripts, CI, PR template, comment scaffold).
|
|
69
156
|
export function repoActions(root, repo) {
|
|
70
157
|
const repoRoot = path.resolve(root, repo.path);
|
|
@@ -107,7 +194,7 @@ export function authorsActions(root, repos = []) {
|
|
|
107
194
|
const emails = verifiedAuthorEmails(hub);
|
|
108
195
|
if (!emails.length) return [];
|
|
109
196
|
const desired = [
|
|
110
|
-
'# Generated by `
|
|
197
|
+
'# Generated by `yad check --fix` from .sdlc/hub.json (roster emails + verified_authors).',
|
|
111
198
|
'# The verified-commits gate accepts only these author emails. Edit hub.json, not this file.',
|
|
112
199
|
...emails,
|
|
113
200
|
].join('\n') + '\n';
|
package/cli/platform.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Platform adapter — the ONLY place that shells out to gh/glab. Read recipes mirror
|
|
2
|
-
// skills/
|
|
2
|
+
// skills/yad-hub-bridge/references/bridge.md. Everything runs as the local user (gh/glab own auth);
|
|
3
3
|
// no tokens are stored. Pure mapping fns (resolveLogin/mapApprovers) are exported for unit tests;
|
|
4
4
|
// readPr is injectable so the gate can be tested with a fake.
|
|
5
5
|
import { run, has } from './lib.mjs';
|
|
@@ -23,7 +23,7 @@ export function platformReady(platform) {
|
|
|
23
23
|
return !!cli && has(cli);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// ---- login ->
|
|
26
|
+
// ---- login -> yad identity (roster + derived domain-owner) -------------------------------------
|
|
27
27
|
// Returns the records this login's APPROVED review contributes. A roster reviewer who owns a touched
|
|
28
28
|
// repo's domain contributes BOTH a base record and a domain-owner record (bridge.md "Login -> role").
|
|
29
29
|
export function resolveLogin(login, roster = [], repos = [], touchedDomains = []) {
|
package/cli/reconcile.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `
|
|
1
|
+
// `yad check` (report) and `yad check --fix` (reconcile) — and `yad update`
|
|
2
2
|
// as a thin alias (--scope=changed). Inspects actual project state against the
|
|
3
3
|
// manifest: missing setup, drifted files, stale code-context.
|
|
4
4
|
import path from 'node:path';
|
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
c, log, ok, info, warn, hand, fail, readJSON, writeJSON, exists,
|
|
7
7
|
} from './lib.mjs';
|
|
8
8
|
import { VERSION, PROJECT_FILES } from './manifest.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
moduleActions, repoActions, hubActions, authorsActions,
|
|
11
|
+
legacyModuleActions, legacyRepoActions, legacyHubActions,
|
|
12
|
+
} from './plan.mjs';
|
|
10
13
|
import { gitHead, packRepo } from './setup.mjs';
|
|
11
14
|
|
|
12
|
-
const MARK = { missing: c.red('missing'), outdated: c.yellow('outdated'), stale: c.yellow('stale'), ok: c.green('ok') };
|
|
15
|
+
const MARK = { missing: c.red('missing'), outdated: c.yellow('outdated'), stale: c.yellow('stale'), legacy: c.yellow('legacy'), ok: c.green('ok') };
|
|
13
16
|
|
|
14
17
|
export async function reconcile(root, { fix = false, scope = 'all', force = false } = {}) {
|
|
15
18
|
log(c.bold(`\nSDLC reconcile ${c.dim('v' + VERSION)}`));
|
|
@@ -22,9 +25,14 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
22
25
|
const registry = readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] });
|
|
23
26
|
if (!exists(path.join(root, PROJECT_FILES.reposRegistry))) gaps.push('no repos registered (.sdlc/repos.json absent)');
|
|
24
27
|
|
|
25
|
-
// --- deterministic file actions (module + hub CI + author allowlists + every registered repo)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// --- deterministic file actions (module + hub CI + author allowlists + every registered repo),
|
|
29
|
+
// plus pre-2.0 sdlc-* -> yad-* migrations ('legacy': old name installed; rename in place) ---
|
|
30
|
+
const actions = [
|
|
31
|
+
...moduleActions(root), ...legacyModuleActions(root),
|
|
32
|
+
...hubActions(root), ...legacyHubActions(root),
|
|
33
|
+
...authorsActions(root, registry.repos),
|
|
34
|
+
];
|
|
35
|
+
for (const repo of registry.repos) actions.push(...repoActions(root, repo), ...legacyRepoActions(root, repo));
|
|
28
36
|
|
|
29
37
|
// --- stale code-context (HEAD moved since last pack) ---
|
|
30
38
|
const staleRepos = [];
|
|
@@ -42,7 +50,7 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
42
50
|
if (!byScope.has(a.scope)) byScope.set(a.scope, []);
|
|
43
51
|
byScope.get(a.scope).push(a);
|
|
44
52
|
}
|
|
45
|
-
const counts = { missing: 0, outdated: 0, stale: 0, ok: 0 };
|
|
53
|
+
const counts = { missing: 0, outdated: 0, stale: 0, legacy: 0, ok: 0 };
|
|
46
54
|
for (const [scopeName, items] of byScope) {
|
|
47
55
|
const notOk = items.filter((i) => i.status !== 'ok');
|
|
48
56
|
items.forEach((i) => counts[i.status]++);
|
|
@@ -56,10 +64,10 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
56
64
|
a.status !== 'ok' && (scope === 'all' ? true : a.status !== 'missing'),
|
|
57
65
|
);
|
|
58
66
|
log('');
|
|
59
|
-
log(c.dim(`summary: ${counts.missing} missing, ${counts.outdated} outdated, ${counts.stale} stale, ${counts.ok} ok`));
|
|
67
|
+
log(c.dim(`summary: ${counts.missing} missing, ${counts.outdated} outdated, ${counts.stale} stale, ${counts.legacy} legacy, ${counts.ok} ok`));
|
|
60
68
|
|
|
61
69
|
if (!fix) {
|
|
62
|
-
if (fixable.length || gaps.length) hand('run `
|
|
70
|
+
if (fixable.length || gaps.length) hand('run `yad check --fix` to reconcile (or `yad setup` for missing one-time setup).');
|
|
63
71
|
return { counts, gaps, applied: 0 };
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -78,6 +86,6 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
78
86
|
const rec = readJSON(path.join(root, PROJECT_FILES.version), {});
|
|
79
87
|
writeJSON(path.join(root, PROJECT_FILES.version), { ...rec, version: VERSION });
|
|
80
88
|
applied ? ok(`reconciled ${applied} item(s)`) : info('nothing to fix');
|
|
81
|
-
if (gaps.length) hand('one-time setup still missing — run `
|
|
89
|
+
if (gaps.length) hand('one-time setup still missing — run `yad setup`.');
|
|
82
90
|
return { counts, gaps, applied };
|
|
83
91
|
}
|
package/cli/repo.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// `
|
|
2
|
-
// Skill steps no longer silently repack a stale repo; they flag it and point here. (`
|
|
1
|
+
// `yad repo list|refresh` — connected-repo staleness as an explicit HUMAN decision.
|
|
2
|
+
// Skill steps no longer silently repack a stale repo; they flag it and point here. (`yad check --fix`
|
|
3
3
|
// still refreshes too — it is also human-invoked.)
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { c, log, ok, info, warn, hand, fail, readJSON, writeJSON } from './lib.mjs';
|
|
@@ -20,7 +20,7 @@ function staleness(root, repo) {
|
|
|
20
20
|
|
|
21
21
|
export async function runRepo(root, { action = 'list', name, today } = {}) {
|
|
22
22
|
const { regPath, registry } = load(root);
|
|
23
|
-
if (!registry.repos.length) { warn('no repos registered (.sdlc/repos.json) — run `
|
|
23
|
+
if (!registry.repos.length) { warn('no repos registered (.sdlc/repos.json) — run `yad setup`'); return { repos: 0 }; }
|
|
24
24
|
|
|
25
25
|
if (action === 'list') {
|
|
26
26
|
log(c.bold('\nconnected repos'));
|
|
@@ -31,7 +31,7 @@ export async function runRepo(root, { action = 'list', name, today } = {}) {
|
|
|
31
31
|
if (stale) { staleCount++; warn(`${repo.name} ${c.dim(`(${repo.path})`)} — ${c.yellow('stale')} (HEAD moved since last pack)`); }
|
|
32
32
|
else ok(`${repo.name} ${c.dim('— fresh')}`);
|
|
33
33
|
}
|
|
34
|
-
if (staleCount) hand(`refresh with \`
|
|
34
|
+
if (staleCount) hand(`refresh with \`yad repo refresh${registry.repos.length > 1 ? ' <name>' : ''}\` (or \`yad repo refresh\` for all)`);
|
|
35
35
|
return { repos: registry.repos.length, stale: staleCount };
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -51,7 +51,7 @@ export async function runRepo(root, { action = 'list', name, today } = {}) {
|
|
|
51
51
|
}
|
|
52
52
|
writeJSON(regPath, registry);
|
|
53
53
|
refreshed ? ok(`refreshed ${refreshed} repo(s)`) : info('nothing refreshed');
|
|
54
|
-
hand('regenerate the code-map in Claude Code (
|
|
54
|
+
hand('regenerate the code-map in Claude Code (yad-connect-repos) — the pack is cached, the map is the AI step');
|
|
55
55
|
return { refreshed };
|
|
56
56
|
}
|
|
57
57
|
|