@vegastack/skills 0.9.1 → 0.11.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.
Files changed (85) hide show
  1. package/README.md +8 -3
  2. package/dist/index.js +5 -5
  3. package/package.json +1 -1
  4. package/skill/dev-architect/SKILL.md +96 -0
  5. package/skill/dev-architect/agents/openai.yaml +4 -0
  6. package/skill/dev-architect/references/ai-agents.md +89 -0
  7. package/skill/dev-architect/references/conventions.md +93 -0
  8. package/skill/{architect → dev-architect}/references/data.md +43 -44
  9. package/skill/dev-architect/references/infra.md +98 -0
  10. package/skill/dev-architect/references/mobile.md +75 -0
  11. package/skill/{architect → dev-architect}/references/pinned-facts.md +17 -16
  12. package/skill/dev-architect/references/principles.md +117 -0
  13. package/skill/{architect → dev-architect}/references/security.md +37 -44
  14. package/skill/dev-architect/references/stack.md +38 -0
  15. package/skill/dev-architect/references/web.md +102 -0
  16. package/skill/{architect → dev-architect}/refresh/REFRESH.md +8 -6
  17. package/skill/{architect → dev-architect}/refresh/sources.json +5 -10
  18. package/skill/dev-chronicle/SKILL.md +45 -0
  19. package/skill/dev-chronicle/agents/openai.yaml +4 -0
  20. package/skill/dev-chronicle/references/conventions.md +93 -0
  21. package/skill/dev-chronicle/refresh/REFRESH.md +3 -0
  22. package/skill/dev-chronicle/refresh/sources.json +6 -0
  23. package/skill/dev-debug/SKILL.md +43 -0
  24. package/skill/dev-debug/agents/openai.yaml +4 -0
  25. package/skill/dev-debug/references/conventions.md +93 -0
  26. package/skill/dev-debug/references/loop-ladder.md +20 -0
  27. package/skill/dev-debug/refresh/REFRESH.md +3 -0
  28. package/skill/dev-debug/refresh/sources.json +6 -0
  29. package/skill/dev-implement/SKILL.md +41 -36
  30. package/skill/dev-implement/references/conventions.md +93 -0
  31. package/skill/dev-implement/references/ledger-and-resume.md +27 -0
  32. package/skill/dev-implement/scripts/evidence-check.mjs +57 -0
  33. package/skill/dev-implement/scripts/lib/gh.mjs +93 -0
  34. package/skill/dev-implement/scripts/preflight.mjs +101 -0
  35. package/skill/dev-intake/SKILL.md +39 -33
  36. package/skill/dev-intake/references/brief-template.md +27 -12
  37. package/skill/dev-intake/references/conventions.md +93 -0
  38. package/skill/dev-intake/scripts/brief-lint.mjs +87 -0
  39. package/skill/dev-plan/SKILL.md +53 -0
  40. package/skill/dev-plan/agents/openai.yaml +4 -0
  41. package/skill/dev-plan/references/conventions.md +93 -0
  42. package/skill/dev-plan/references/plan-format.md +54 -0
  43. package/skill/dev-plan/refresh/REFRESH.md +3 -0
  44. package/skill/dev-plan/refresh/sources.json +6 -0
  45. package/skill/dev-plan/scripts/plan-lint.mjs +86 -0
  46. package/skill/dev-review/SKILL.md +69 -0
  47. package/skill/dev-review/agents/openai.yaml +4 -0
  48. package/skill/dev-review/assets/review-known-patterns.md.template +30 -0
  49. package/skill/dev-review/references/conventions.md +93 -0
  50. package/skill/dev-review/references/cross-agent.md +39 -0
  51. package/skill/dev-review/references/dispatch-prompts.md +104 -0
  52. package/skill/dev-review/references/security-axis.md +33 -0
  53. package/skill/dev-review/refresh/REFRESH.md +3 -0
  54. package/skill/dev-review/refresh/sources.json +6 -0
  55. package/skill/dev-setup/SKILL.md +14 -9
  56. package/skill/dev-setup/assets/agents-section.md.template +2 -2
  57. package/skill/dev-setup/assets/dev-profile.md.template +23 -5
  58. package/skill/dev-setup/references/conventions.md +93 -0
  59. package/skill/dev-setup/references/stack-playbooks.md +1 -1
  60. package/skill/dev-ship/SKILL.md +14 -7
  61. package/skill/dev-ship/references/conventions.md +93 -0
  62. package/skill/dev-ship/references/runbook.md +1 -1
  63. package/skill/dev-ship/scripts/ship-gate.mjs +213 -0
  64. package/skill/dev-status/SKILL.md +45 -0
  65. package/skill/dev-status/agents/openai.yaml +4 -0
  66. package/skill/dev-status/references/conventions.md +93 -0
  67. package/skill/dev-status/refresh/REFRESH.md +3 -0
  68. package/skill/dev-status/refresh/sources.json +6 -0
  69. package/skill/dev-status/scripts/status.mjs +152 -0
  70. package/skill/skill-maintainer/references/release-ops.md +3 -3
  71. package/skill/skillify/SKILL.md +1 -1
  72. package/skill/skillify/references/eval-playbook.md +6 -0
  73. package/skill-integrity.json +93 -33
  74. package/skill/architect/SKILL.md +0 -68
  75. package/skill/architect/agents/openai.yaml +0 -4
  76. package/skill/architect/assets/adr-template.md +0 -21
  77. package/skill/architect/assets/arch-template.md +0 -20
  78. package/skill/architect/references/advisory.md +0 -102
  79. package/skill/architect/references/ai-agents.md +0 -95
  80. package/skill/architect/references/infra.md +0 -128
  81. package/skill/architect/references/mobile.md +0 -78
  82. package/skill/architect/references/principles.md +0 -91
  83. package/skill/architect/references/project-profile.md +0 -37
  84. package/skill/architect/references/stack.md +0 -38
  85. package/skill/architect/references/web.md +0 -152
@@ -0,0 +1,93 @@
1
+ # Workflow conventions
2
+
3
+ The single spec for the artifacts every dev-family skill reads and writes. One home per rule: skills cite this file, never restate it. Everything here is harness-neutral.
4
+
5
+ ## Comment metadata markers
6
+
7
+ Every workflow-generated issue comment opens with an invisible HTML marker followed by a human heading:
8
+
9
+ ```markdown
10
+ <!-- vsk:v1 type=<type> rev=<n> [key=value ...] -->
11
+ ## <Human title> (v<n>)
12
+ ```
13
+
14
+ | type | required keys | instances |
15
+ |---|---|---|
16
+ | `approval` | `scope=<brief\|brief+plan\|plan>` | one per approval event |
17
+ | `plan` | `rev` | one, edited in place |
18
+ | `ledger` | `branch` | one, edited in place |
19
+ | `evidence` | `rev branch sha` | one, edited in place |
20
+ | `review` | `round sha agent=<claude\|codex> verdict=<clean\|needs-fixes>` | one per review cycle, rounds appended inside |
21
+ | `decision` | — | one per decision proposal |
22
+ | `handback` | — | one per stop event |
23
+
24
+ `rev=<n>` and the matching `(v<n>)` heading suffix appear only on revisable artifacts — the brief (issue description), `plan`, and `evidence` — starting at `rev=1`/`(v1)`. Single-event comments (`approval`, `decision`, `handback`) and the `ledger` carry neither. Scripts and agents locate comments strictly by marker, never by heading text. A comment without its marker does not count as the artifact — there is no legacy fallback.
25
+
26
+ ## Operator identity
27
+
28
+ Every human reference in every artifact — approvals, revisions, decisions, changelog attributions, review adjudications — is written `operator (<github-username>)`:
29
+
30
+ - Approval: `Approved by operator (<username>) on DD-MM-YYYY: "<their words>"`
31
+ - Register line: `- DD-MM-YYYY operator (<username>) — <decision>`
32
+
33
+ ## Revision markers
34
+
35
+ Any artifact edited after its first approval: the heading gains `(v2)`, the marker gains `rev=2`, and a `Revisions:` line is appended at the bottom — `v2 — DD-MM-YYYY: <what changed>, per operator (<username>) correction`. Existing revision lines are never rewritten.
36
+
37
+ ## Scope classes
38
+
39
+ Set at intake, applied as a label, announced with its reason (operator can override):
40
+
41
+ - **`research`** — a question to answer; throwaway code allowed, never merged. No branch/PR/changelog; findings + recommendation are the evidence comment.
42
+ - **`quick-build`** — small change and the flow being changed already exists in the repo to read. Brief (description) + plan (comment) are drafted in the same conversation; **one approval covers both**; then straight to `ready`.
43
+ - **`full-plan`** — big or new ground. Brief approval → `needs-plan` → a separate, fresh-grounded planning session posts the plan → `needs-operator` → "plan approved" → `ready`. Multi-deliverable work becomes an epic; each sub-issue is classified independently.
44
+
45
+ Scope calls are revisited through the one-way ratchet, whose rules and mechanics live in the `dev-plan` skill — the one home for upgrade/downgrade behavior.
46
+
47
+ ## Labels
48
+
49
+ State — exactly one per issue (creation colors live in dev-setup's labels row, their one home):
50
+
51
+ | label | meaning |
52
+ |---|---|
53
+ | `needs-operator` | waiting on the operator: a question, a brief or plan to approve, a proposal |
54
+ | `needs-plan` | brief approved; waiting for the planning stage (full-plan only) |
55
+ | `ready` | fully approved — an agent may start |
56
+ | `working` | claimed, in progress; the ledger comment shows live progress |
57
+ | `for-operator` | done — evidence posted, awaiting operator review |
58
+
59
+ Modifiers (may coexist with the state label): `risky` · scope `research` / `quick-build` / `full-plan` · `epic` (map parents, only where the org has no native Epic issue type).
60
+
61
+ ## Titles, types, hierarchy
62
+
63
+ - **Title prefixes** on issues, branches, and PRs identically: dev.md's `branch:` knob type list (that knob stays the list's one home) plus `research:` for research issues. PR title = issue title.
64
+ - **Native issue types** where the org defines them: Feature (feat) · Bug (fix) · Task (docs/chore/refactor/research) · Epic for parents (label fallback otherwise).
65
+ - **Hierarchy:** epic parent = map only (Destination · Decisions so far as one-line gists · Not clear yet · Out of scope), children attached as native sub-issues; issues = the unit of work (brief in description, own approvals/branch/PR/evidence); tasks = checkboxes **in the plan comment only**. Blockers use native issue dependencies; phases use milestones. Only issues — never epics — get `ready`. GitHub caps issue bodies and comments at ~65,536 characters; what a plan nearing that cap means is the `dev-plan` ratchet's call.
66
+
67
+ ## The ledger
68
+
69
+ Maintained by the implement session as one comment, edited in place:
70
+
71
+ ```markdown
72
+ <!-- vsk:v1 type=ledger branch=<branch> -->
73
+ ## Ledger — <branch>
74
+ - Task <N>: complete (commits <base7>..<head7>[, review clean | K parked])
75
+ - Task <N>: fix round <R>/3 (<X> addressed, <Y> open — <one-liners>; commits <a>..<b>)
76
+ - Ruling: <what> — <why> — cost if wrong: <cost>
77
+ - Task <N>: parked — <finding> — Ruling: <why the code stands>
78
+ - Deferred minor: <one-liner>
79
+ ```
80
+
81
+ **Resume protocol:** a fresh, compacted, or (operator-handed) takeover session reads, in order: the brief → the plan comment → the ledger → `git log` on the branch — **nothing else**. Tasks with a `complete` line are DONE, never re-executed; a task whose last line is a fix round resumes at the next round. After compaction, trust the ledger and `git log` over recollection. Every `Ruling:` line surfaces in the evidence comment — a ruling that dies with the session was a decision made in secret.
82
+
83
+ ## `.vegastack/.tmp/` workspace
84
+
85
+ All transitory artifacts — subagent reports, review packages, plan drafts, extracted diffs — live at `.vegastack/.tmp/<issue-number>-<title-slug>/` (pre-issue intake drafts, which have no number yet: `.vegastack/.tmp/intake-<slug>/`), kept out of git by a self-ignoring `.gitignore` (`printf '*\n' > .vegastack/.tmp/.gitignore`, created on first use). Subagents write full reports to files there and return only short status — a dead subagent's findings survive on disk, and the primary session never holds full reports in context. The workspace lives in the working tree (never under `.git/`, which harnesses protect from writes).
86
+
87
+ ## Verification gate
88
+
89
+ Before claiming any status: **IDENTIFY** the command that proves the claim → **RUN** it fresh and complete → **READ** the full output and exit code → only then claim, with the evidence. "Should pass", a previous run, or a subagent's say-so are never evidence. Guard scripts follow the same doctrine: machine-verifiable facts **block** (exit 2 with the reason); regex or judgment heuristics only **warn** — no AI inference inside guards, and an unverifiable state fails closed.
90
+
91
+ ## Plain-language collaboration
92
+
93
+ Every skill run ends with a simple-language summary: what happened, which paths were taken — cross-agent invocations announced at trigger time AND summarized at the end — and what is worth the operator double-checking. Use mermaid or ASCII diagrams in issues wherever a picture beats prose. A vague or self-contradicting operator answer gets pushback with concrete options, never silent absorption.
@@ -33,4 +33,4 @@ A bot PR has no issue, no brief, no evidence comment — and merging it is still
33
33
 
34
34
  ## Guard failure at ship time
35
35
 
36
- A local `guard:` failure (missing changelog entry, tag/version mismatch) means the branch or release prep is incomplete: route it to dev-implement's corrections loop, get the evidence comment updated, then resume at the failed step. Never edit release artifacts inline just to get past a guard.
36
+ A local `guard:` failure (missing changelog entry, tag/version mismatch) means the branch or release prep is incomplete: route it to dev-implement's corrections loop, get the evidence comment updated, then resume at the failed step. Never edit release artifacts inline just to get past a guard. `ship-gate.mjs` speaks the same language: exit 0 pass · 1 pass-with-warnings (read them twice, they never block) · 2 blocked with its reasons printed — a 2 routes to corrections exactly like a failing `guard:` line.
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ // dev-ship guard, run at Gate 1 before a PR (and re-run before merge): the
3
+ // deterministic facts that make a hand-back shippable. Facts block; the
4
+ // rationalization scan over the evidence text only warns — regex heuristics
5
+ // never block. Self-contained (ships with dev-ship; no cross-skill imports).
6
+ //
7
+ // Exit codes: 0 pass · 1 pass-with-warnings · 2 blocked (reasons printed).
8
+ // Usage: node ship-gate.mjs --issue <n> --branch <name> [--repo o/r] [--dev-md <path>]
9
+ // [--base main] [--allow-no-changelog "<reason>"] --json
10
+ import { execFileSync } from 'node:child_process';
11
+ import { readFileSync } from 'node:fs';
12
+ import { resolve } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const RATIONALIZATIONS = [
16
+ /skip(ping)? tests? for now/i,
17
+ /pre-existing (issue|bug)/i,
18
+ /fix (this|it) later/i,
19
+ /(tests?|coverage) (is|are) (failing|broken) but/i,
20
+ ];
21
+
22
+ function sh(cmd, args) {
23
+ // VSK_GH is a TEST SEAM (stubs gh in unit tests); git always runs real.
24
+ const bin = cmd === 'gh' ? (process.env.VSK_GH || 'gh') : cmd;
25
+ return execFileSync(bin, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } }).trim();
26
+ }
27
+
28
+ // Adjudication means OPEN FINDINGS were ruled on at the loop cap. Routine
29
+ // ledger vocabulary ("Ruling:", a mid-build "parked", "nothing parked") must
30
+ // not lift a needs-fixes block — only "adjudicat*" or a finding-tied park
31
+ // ("Finding [N] ... parked") counts.
32
+ export function reviewAdjudicated(evidenceBody) {
33
+ const section = /\*\*Review:\*\*[\s\S]*?(?=\n\*\*[A-Z]|\nBranch:|$)/.exec(evidenceBody ?? '')?.[0] ?? '';
34
+ return /adjudicat/i.test(section) || /finding \[\d+\][^\n]*parked/i.test(section);
35
+ }
36
+
37
+ export function parseMarker(body) {
38
+ const match = /<!--\s*vsk:v1\s+([^>]*?)\s*-->/.exec(body ?? '');
39
+ if (!match) return null;
40
+ const keys = {};
41
+ for (const pair of match[1].split(/\s+/)) {
42
+ const eq = pair.indexOf('=');
43
+ if (eq > 0) keys[pair.slice(0, eq)] = pair.slice(eq + 1);
44
+ }
45
+ return { keys };
46
+ }
47
+
48
+ // An entry means an ADDED "## " heading in the file-scoped diff — a deleted
49
+ // file or a typo edit to an old entry is not a new entry.
50
+ export function chronicleEntryAdded(fileDiff) {
51
+ return /^\+## /m.test(fileDiff ?? '');
52
+ }
53
+
54
+ // Pure evaluation over gathered facts — unit tests drive this directly.
55
+ export function evaluateShipGate(facts) {
56
+ const blocks = [];
57
+ const warns = [];
58
+ const {
59
+ evidence, // { body } | null
60
+ reviewVerdict, // 'clean' | 'needs-fixes' | null
61
+ adjudicated, // boolean: evidence Review section carries adjudication rulings
62
+ headSha, // short sha of the branch head
63
+ diffText, // full diff vs base
64
+ changelogTouched, // boolean: diff adds a changelog/changeset entry
65
+ // chronicleOn/chronicleTouched (via facts.*): dev.md chronicle knob and
66
+ // whether the diff adds a "## " chronicle entry heading
67
+ allowNoChangelog, // reason string | undefined
68
+ checkExit, // number | null (null = no check command configured)
69
+ } = facts;
70
+
71
+ if (!evidence) {
72
+ blocks.push('no evidence comment (marker type=evidence) on the issue');
73
+ return { blocks, warns };
74
+ }
75
+ const marker = parseMarker(evidence.body);
76
+ const evidenceSha = marker?.keys?.sha ?? '';
77
+
78
+ if (!/^[0-9a-f]{7,40}$/.test(evidenceSha)) {
79
+ blocks.push(`evidence marker carries no valid sha= (found "${evidenceSha || 'nothing'}") — the shipped revision must be named`);
80
+ } else if (!headSha.startsWith(evidenceSha) && !evidenceSha.startsWith(headSha)) {
81
+ // Strict equality, no reconciliation window: the corrections loop updates
82
+ // the evidence comment (Docs line AND sha) after every change, so a
83
+ // mismatched sha means unrecorded work. An "edited since the commit"
84
+ // window was spoofable by any comment edit and was removed.
85
+ blocks.push(`branch head ${headSha} moved past evidence sha ${evidenceSha} — the corrections loop must re-verify and update the evidence comment (Docs line + new sha) before shipping`);
86
+ }
87
+
88
+ if (!changelogTouched && !allowNoChangelog) {
89
+ blocks.push('no changelog/changeset entry in the diff and no --allow-no-changelog reason given');
90
+ }
91
+
92
+ if (facts.chronicleOn && !facts.chronicleTouched && !allowNoChangelog) {
93
+ blocks.push('dev.md says chronicle: on but the diff adds no .vegastack/chronicle.md entry (the same --allow-no-changelog reason covers docs/test-only branches)');
94
+ }
95
+ if (allowNoChangelog && (!changelogTouched || (facts.chronicleOn && !facts.chronicleTouched))) {
96
+ warns.push(`--allow-no-changelog exercised ("${allowNoChangelog}") — it excused: ${[!changelogTouched ? 'changelog' : null, facts.chronicleOn && !facts.chronicleTouched ? 'chronicle' : null].filter(Boolean).join(' + ')}`);
97
+ }
98
+
99
+ if (reviewVerdict !== 'clean' && !adjudicated) {
100
+ blocks.push(`latest review verdict is ${reviewVerdict ?? 'absent'} and the evidence Review section carries no adjudication`);
101
+ }
102
+
103
+ if (facts.checkoutMismatch) {
104
+ blocks.push(facts.checkoutMismatch);
105
+ }
106
+ if (facts.checkMissing) {
107
+ warns.push('dev.md has no check command on its commands: line — the fresh-run gate could not run; verify by hand');
108
+ }
109
+ if (checkExit !== null && checkExit !== 0) {
110
+ blocks.push(`the project check command exited ${checkExit} on a fresh run — a claim is never trusted, always re-proven`);
111
+ }
112
+
113
+ // Added lines only, and only the REAL tag shape ([DEBUG- + hex): docs that
114
+ // document the tag write placeholders like [DEBUG-<4hex>] and must not block.
115
+ if (/^\+(?!\+\+).*\[DEBUG-[0-9a-f]{4}\]/m.test(diffText)) {
116
+ blocks.push('the diff adds [DEBUG- tagged instrumentation — dev-debug cleanup was skipped');
117
+ }
118
+
119
+ for (const pattern of RATIONALIZATIONS) {
120
+ const hit = pattern.exec(evidence.body);
121
+ if (hit) warns.push(`rationalization wording in evidence: "${hit[0]}" — heuristics never block, but read it twice`);
122
+ }
123
+
124
+ return { blocks, warns };
125
+ }
126
+
127
+ export function gatherFacts(flags) {
128
+ const repo = flags.repo || sh('gh', ['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']);
129
+ const base = flags.base || 'main';
130
+ const branch = flags.branch;
131
+ const comments = JSON.parse(sh('gh', ['api', `repos/${repo}/issues/${flags.issue}/comments`, '--paginate']));
132
+
133
+ let evidence = null;
134
+ let reviewVerdict = null;
135
+ for (const comment of comments) {
136
+ const marker = parseMarker(comment.body);
137
+ if (marker?.keys?.type === 'evidence') evidence = { body: comment.body, updatedAt: comment.updated_at };
138
+ if (marker?.keys?.type === 'review') reviewVerdict = marker.keys.verdict ?? null;
139
+ }
140
+ const adjudicated = reviewAdjudicated(evidence?.body);
141
+
142
+ const headSha = sh('git', ['rev-parse', '--short=7', branch]);
143
+ const diffText = sh('git', ['diff', `${base}...${branch}`]);
144
+ // The fresh check run and dev.md read use the WORKING TREE — they prove
145
+ // nothing unless the checkout is the branch under review.
146
+ const checkoutSha = sh('git', ['rev-parse', 'HEAD']);
147
+ const branchSha = sh('git', ['rev-parse', branch]);
148
+ const checkoutMismatch = checkoutSha === branchSha
149
+ ? null
150
+ : `the current checkout (${checkoutSha.slice(0, 7)}) is not the branch under review (${branch} @ ${branchSha.slice(0, 7)}) — run ship-gate from that branch so the fresh check proves the right code`;
151
+
152
+ const devMd = readFileSync(flags['dev-md'] || '.vegastack/dev.md', 'utf8');
153
+ const changelogKnob = (/^changelog:\s*(\S+)/m.exec(devMd) || [])[1] ?? 'none';
154
+ // Added files/lines only — a deleted changeset or the +++ diff header must
155
+ // not count as an entry.
156
+ const changelogTouched = changelogKnob === 'none'
157
+ ? true
158
+ : changelogKnob === 'changesets'
159
+ ? /^\+\+\+ b\/\.changeset\/(?!config)/m.test(diffText)
160
+ : /^\+(?!\+\+)[^\n]*\S/m.test(sh('git', ['diff', `${base}...${branch}`, '--', 'CHANGELOG.md']) || '');
161
+
162
+ const chronicleOn = /^chronicle:\s*on\s*(#|$)/m.test(devMd);
163
+ const chronicleTouched = chronicleEntryAdded(sh('git', ['diff', `${base}...${branch}`, '--', '.vegastack/chronicle.md']) || '');
164
+
165
+ let checkExit = null;
166
+ const checkCmd = (/^commands:.*?check\s+`([^`]+)`/m.exec(devMd) || [])[1];
167
+ const checkMissing = !checkCmd;
168
+ if (checkCmd) {
169
+ try {
170
+ execFileSync('sh', ['-c', checkCmd], { stdio: ['ignore', 'pipe', 'pipe'] });
171
+ checkExit = 0;
172
+ } catch (error) {
173
+ checkExit = error.status ?? 1;
174
+ }
175
+ }
176
+
177
+ return {
178
+ evidence, reviewVerdict, adjudicated, headSha, diffText,
179
+ changelogTouched, chronicleOn, chronicleTouched,
180
+ allowNoChangelog: flags['allow-no-changelog'], checkExit, checkMissing, checkoutMismatch,
181
+ };
182
+ }
183
+
184
+ const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
185
+ if (invokedDirectly) {
186
+ const argv = process.argv.slice(2);
187
+ const json = argv.includes('--json');
188
+ const get = (flag) => { const i = argv.indexOf(flag); return i === -1 ? undefined : argv[i + 1]; };
189
+ const flags = {
190
+ issue: get('--issue'), branch: get('--branch'), repo: get('--repo'), base: get('--base'),
191
+ 'dev-md': get('--dev-md'), 'allow-no-changelog': get('--allow-no-changelog'), json,
192
+ };
193
+ let outcome;
194
+ if (!flags.issue || !flags.branch) {
195
+ outcome = { blocks: ['usage: ship-gate.mjs --issue <n> --branch <name> [--json]'], warns: [] };
196
+ } else {
197
+ try {
198
+ outcome = evaluateShipGate(gatherFacts(flags));
199
+ } catch (error) {
200
+ outcome = { blocks: [`cannot verify: ${error.message}`], warns: [] };
201
+ }
202
+ }
203
+ const ok = outcome.blocks.length === 0;
204
+ const exitCode = ok ? (outcome.warns.length ? 1 : 0) : 2;
205
+ if (json) {
206
+ console.log(JSON.stringify({ guard: 'ship-gate', ok, ...outcome }, null, 2));
207
+ } else {
208
+ console.log(`ship-gate: ${ok ? (outcome.warns.length ? 'pass with warnings' : 'pass') : 'BLOCKED'}`);
209
+ for (const b of outcome.blocks) console.log(` block: ${b}`);
210
+ for (const w of outcome.warns) console.log(` warn: ${w}`);
211
+ }
212
+ process.exit(exitCode);
213
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: dev-status
3
+ description: The operator's board — whose move is it, across every issue in the dev workflow. Use when asked "status", "what needs me", "where are we", "what's in flight", "anything stale?", "what should I look at next", or for a board overview of needs-operator / needs-plan / ready / working / for-operator issues. Not for the project's history ("catch me up" is dev-chronicle), implementing or reviewing anything, or repo bootstrap (dev-setup).
4
+ ---
5
+
6
+ # dev-status
7
+
8
+ One question, answered from deterministic data: **whose move is it?** The bundled script gathers; this skill orders and narrates — it never invents state, and an unverifiable board is reported as exactly that.
9
+
10
+ Nearest neighbors: `dev-chronicle` answers "how did we get here"; this skill answers "what needs whom right now". Formats read via the `dev-setup` skill's `references/conventions.md` markers.
11
+
12
+ ## Gather
13
+
14
+ ```
15
+ node <path-to-this-skill>/scripts/status.mjs --stale-days 3 --json
16
+ ```
17
+
18
+ Read-only; it returns the board (open issues per state label with age, scope, risky), task progress `x/y` from plan-comment checkboxes, ledger movement for `working` issues (stale = no ledger edit within `--stale-days`, default 3), open PRs with check state, pending unrecorded `Decision:` proposals, and the last chronicle entry. Exit 2 = cannot verify (offline, unauthenticated) — report the gap plainly and stop; never render a guessed board.
19
+
20
+ ## Render — names, never bare numbers
21
+
22
+ ```markdown
23
+ ## Status — <repo> · DD-MM
24
+
25
+ Needs you (N):
26
+ - <linked title> — <state> <age>d: <one line: what it waits for and the word needed>
27
+ Waiting on plan (N): - <linked title> — needs-plan <age>d
28
+ Ready to build (N): - <linked title> — <scope>
29
+ In flight (N): - <linked title> — working, task <x>/<y>, ledger moved <n>d ago
30
+ Stale (N): - <linked title> — working <age>d, ledger silent <n>d → check or reclaim
31
+ Open PRs (N): - <linked title> — checks <green|pending-or-red|no-checks>
32
+ Pending decisions (N): "<gist>" (<linked issue>) — records at that issue's merge
33
+ Last chronicle chapter: <date> — <title>
34
+ Next: <the single most valuable operator action, and why>
35
+ ```
36
+
37
+ - **Needs you** first (for-operator + needs-operator merged, oldest first) — it's the operator's queue; everything else is context.
38
+ - Sections with zero entries are omitted, not rendered empty. A completely quiet board is one line: "Nothing needs you — <n> issues ready for agents, nothing in flight."
39
+ - `risky` issues get their flag shown inline wherever they appear.
40
+ - **Next** is one line, chosen not computed-looking: the action that unblocks the most (a plan approval blocking several ready issues beats a lone review).
41
+ - <linked title> means a markdown link wrapping the issue/PR title around its URL; numbers ride inside the link, never stand alone.
42
+
43
+ ## Honesty rules
44
+
45
+ Data comes only from the script; ordering, the wait-reason one-liners, and Next are the skill's judgment — clearly judgment, never dressed as data. A stale `working` issue is a fact to surface, not an accusation: "check or reclaim" is the operator's call (a takeover still needs their explicit handover, per dev-implement). Close with the plain-language summary only when invoked as part of a larger run; standalone, the report IS the summary.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "dev-status"
3
+ short_description: "The operator's board — whose move is it"
4
+ default_prompt: "Use $dev-status to show what needs me."
@@ -0,0 +1,93 @@
1
+ # Workflow conventions
2
+
3
+ The single spec for the artifacts every dev-family skill reads and writes. One home per rule: skills cite this file, never restate it. Everything here is harness-neutral.
4
+
5
+ ## Comment metadata markers
6
+
7
+ Every workflow-generated issue comment opens with an invisible HTML marker followed by a human heading:
8
+
9
+ ```markdown
10
+ <!-- vsk:v1 type=<type> rev=<n> [key=value ...] -->
11
+ ## <Human title> (v<n>)
12
+ ```
13
+
14
+ | type | required keys | instances |
15
+ |---|---|---|
16
+ | `approval` | `scope=<brief\|brief+plan\|plan>` | one per approval event |
17
+ | `plan` | `rev` | one, edited in place |
18
+ | `ledger` | `branch` | one, edited in place |
19
+ | `evidence` | `rev branch sha` | one, edited in place |
20
+ | `review` | `round sha agent=<claude\|codex> verdict=<clean\|needs-fixes>` | one per review cycle, rounds appended inside |
21
+ | `decision` | — | one per decision proposal |
22
+ | `handback` | — | one per stop event |
23
+
24
+ `rev=<n>` and the matching `(v<n>)` heading suffix appear only on revisable artifacts — the brief (issue description), `plan`, and `evidence` — starting at `rev=1`/`(v1)`. Single-event comments (`approval`, `decision`, `handback`) and the `ledger` carry neither. Scripts and agents locate comments strictly by marker, never by heading text. A comment without its marker does not count as the artifact — there is no legacy fallback.
25
+
26
+ ## Operator identity
27
+
28
+ Every human reference in every artifact — approvals, revisions, decisions, changelog attributions, review adjudications — is written `operator (<github-username>)`:
29
+
30
+ - Approval: `Approved by operator (<username>) on DD-MM-YYYY: "<their words>"`
31
+ - Register line: `- DD-MM-YYYY operator (<username>) — <decision>`
32
+
33
+ ## Revision markers
34
+
35
+ Any artifact edited after its first approval: the heading gains `(v2)`, the marker gains `rev=2`, and a `Revisions:` line is appended at the bottom — `v2 — DD-MM-YYYY: <what changed>, per operator (<username>) correction`. Existing revision lines are never rewritten.
36
+
37
+ ## Scope classes
38
+
39
+ Set at intake, applied as a label, announced with its reason (operator can override):
40
+
41
+ - **`research`** — a question to answer; throwaway code allowed, never merged. No branch/PR/changelog; findings + recommendation are the evidence comment.
42
+ - **`quick-build`** — small change and the flow being changed already exists in the repo to read. Brief (description) + plan (comment) are drafted in the same conversation; **one approval covers both**; then straight to `ready`.
43
+ - **`full-plan`** — big or new ground. Brief approval → `needs-plan` → a separate, fresh-grounded planning session posts the plan → `needs-operator` → "plan approved" → `ready`. Multi-deliverable work becomes an epic; each sub-issue is classified independently.
44
+
45
+ Scope calls are revisited through the one-way ratchet, whose rules and mechanics live in the `dev-plan` skill — the one home for upgrade/downgrade behavior.
46
+
47
+ ## Labels
48
+
49
+ State — exactly one per issue (creation colors live in dev-setup's labels row, their one home):
50
+
51
+ | label | meaning |
52
+ |---|---|
53
+ | `needs-operator` | waiting on the operator: a question, a brief or plan to approve, a proposal |
54
+ | `needs-plan` | brief approved; waiting for the planning stage (full-plan only) |
55
+ | `ready` | fully approved — an agent may start |
56
+ | `working` | claimed, in progress; the ledger comment shows live progress |
57
+ | `for-operator` | done — evidence posted, awaiting operator review |
58
+
59
+ Modifiers (may coexist with the state label): `risky` · scope `research` / `quick-build` / `full-plan` · `epic` (map parents, only where the org has no native Epic issue type).
60
+
61
+ ## Titles, types, hierarchy
62
+
63
+ - **Title prefixes** on issues, branches, and PRs identically: dev.md's `branch:` knob type list (that knob stays the list's one home) plus `research:` for research issues. PR title = issue title.
64
+ - **Native issue types** where the org defines them: Feature (feat) · Bug (fix) · Task (docs/chore/refactor/research) · Epic for parents (label fallback otherwise).
65
+ - **Hierarchy:** epic parent = map only (Destination · Decisions so far as one-line gists · Not clear yet · Out of scope), children attached as native sub-issues; issues = the unit of work (brief in description, own approvals/branch/PR/evidence); tasks = checkboxes **in the plan comment only**. Blockers use native issue dependencies; phases use milestones. Only issues — never epics — get `ready`. GitHub caps issue bodies and comments at ~65,536 characters; what a plan nearing that cap means is the `dev-plan` ratchet's call.
66
+
67
+ ## The ledger
68
+
69
+ Maintained by the implement session as one comment, edited in place:
70
+
71
+ ```markdown
72
+ <!-- vsk:v1 type=ledger branch=<branch> -->
73
+ ## Ledger — <branch>
74
+ - Task <N>: complete (commits <base7>..<head7>[, review clean | K parked])
75
+ - Task <N>: fix round <R>/3 (<X> addressed, <Y> open — <one-liners>; commits <a>..<b>)
76
+ - Ruling: <what> — <why> — cost if wrong: <cost>
77
+ - Task <N>: parked — <finding> — Ruling: <why the code stands>
78
+ - Deferred minor: <one-liner>
79
+ ```
80
+
81
+ **Resume protocol:** a fresh, compacted, or (operator-handed) takeover session reads, in order: the brief → the plan comment → the ledger → `git log` on the branch — **nothing else**. Tasks with a `complete` line are DONE, never re-executed; a task whose last line is a fix round resumes at the next round. After compaction, trust the ledger and `git log` over recollection. Every `Ruling:` line surfaces in the evidence comment — a ruling that dies with the session was a decision made in secret.
82
+
83
+ ## `.vegastack/.tmp/` workspace
84
+
85
+ All transitory artifacts — subagent reports, review packages, plan drafts, extracted diffs — live at `.vegastack/.tmp/<issue-number>-<title-slug>/` (pre-issue intake drafts, which have no number yet: `.vegastack/.tmp/intake-<slug>/`), kept out of git by a self-ignoring `.gitignore` (`printf '*\n' > .vegastack/.tmp/.gitignore`, created on first use). Subagents write full reports to files there and return only short status — a dead subagent's findings survive on disk, and the primary session never holds full reports in context. The workspace lives in the working tree (never under `.git/`, which harnesses protect from writes).
86
+
87
+ ## Verification gate
88
+
89
+ Before claiming any status: **IDENTIFY** the command that proves the claim → **RUN** it fresh and complete → **READ** the full output and exit code → only then claim, with the evidence. "Should pass", a previous run, or a subagent's say-so are never evidence. Guard scripts follow the same doctrine: machine-verifiable facts **block** (exit 2 with the reason); regex or judgment heuristics only **warn** — no AI inference inside guards, and an unverifiable state fails closed.
90
+
91
+ ## Plain-language collaboration
92
+
93
+ Every skill run ends with a simple-language summary: what happened, which paths were taken — cross-agent invocations announced at trigger time AND summarized at the end — and what is worth the operator double-checking. Use mermaid or ASCII diagrams in issues wherever a picture beats prose. A vague or self-contradicting operator answer gets pushback with concrete options, never silent absorption.
@@ -0,0 +1,3 @@
1
+ # Refresh contract — dev-status
2
+
3
+ Evergreen: this skill asserts no version pins, numeric vendor limits, or dated claims — the gh CLI surfaces it shells to are the long-stable subcommands the family already treats as durable. Revisit if a future edit introduces a volatile fact.
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "retrievalBaseline": "2026-08-28",
4
+ "note": "Evergreen waiver recorded in REFRESH.md; sources deliberately empty.",
5
+ "sources": []
6
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ // dev-status data gatherer: everything the board report needs, deterministically,
3
+ // read-only, markers-only. The skill renders; this script never invents state.
4
+ //
5
+ // Usage: node status.mjs [--repo o/r] [--stale-days 3] [--dev-md <path>] --json
6
+ import { execFileSync } from 'node:child_process';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const DEFAULT_LABELS = ['needs-operator', 'needs-plan', 'ready', 'working', 'for-operator', 'risky', 'research', 'quick-build', 'full-plan', 'epic'];
12
+
13
+ // The labels: knob lists names positionally (5 states, risky, 3 scopes, epic —
14
+ // the dev-profile template order); a project that renamed labels still parses.
15
+ export function readKnobs(devMdText) {
16
+ const labelsLine = /^labels:\s*([^\n#]+)/m.exec(devMdText ?? '')?.[1]?.trim();
17
+ const names = labelsLine ? labelsLine.split(/\s+/) : DEFAULT_LABELS;
18
+ const labels = names.length >= 10 ? names : DEFAULT_LABELS;
19
+ return {
20
+ states: labels.slice(0, 5),
21
+ risky: labels[5],
22
+ scopes: labels.slice(6, 9),
23
+ register: /^decisions:\s*(\S+)/m.exec(devMdText ?? '')?.[1] ?? '.vegastack/decisions.md',
24
+ };
25
+ }
26
+
27
+ function gh(args) {
28
+ // env spread at call time: some runtimes pass a startup env snapshot to
29
+ // children, which would hide the VSK_GH/GH_STUB_DIR test seam.
30
+ const out = execFileSync(process.env.VSK_GH || 'gh', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
31
+ return JSON.parse(out);
32
+ }
33
+
34
+ export function ageDays(iso, now = Date.now()) {
35
+ return Math.floor((now - Date.parse(iso)) / 86_400_000);
36
+ }
37
+
38
+ export function parseMarker(body) {
39
+ const match = /<!--\s*vsk:v1\s+([^>]*?)\s*-->/.exec(body ?? '');
40
+ if (!match) return null;
41
+ const keys = {};
42
+ for (const pair of match[1].split(/\s+/)) {
43
+ const eq = pair.indexOf('=');
44
+ if (eq > 0) keys[pair.slice(0, eq)] = pair.slice(eq + 1);
45
+ }
46
+ return { keys };
47
+ }
48
+
49
+ // Count plan-comment checkboxes: [done, total]. No plan comment → null.
50
+ export function taskProgress(comments) {
51
+ for (const c of comments ?? []) {
52
+ if (parseMarker(c.body)?.keys?.type === 'plan') {
53
+ const done = (c.body.match(/^- \[x\]/gim) ?? []).length;
54
+ const total = done + (c.body.match(/^- \[ \]/gm) ?? []).length;
55
+ return total > 0 ? [done, total] : null;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+
61
+ // Latest ledger comment's updated_at → staleness signal for working issues.
62
+ export function ledgerMovedAt(comments) {
63
+ let at = null;
64
+ for (const c of comments ?? []) {
65
+ if (parseMarker(c.body)?.keys?.type === 'ledger') at = c.updated_at;
66
+ }
67
+ return at;
68
+ }
69
+
70
+ // Decision proposals not yet in the register: marker type=decision comments
71
+ // (and evidence **Decision:** lines) whose text isn't in the register file.
72
+ export function pendingDecisions(comments, registerText) {
73
+ const pending = [];
74
+ for (const c of comments ?? []) {
75
+ const type = parseMarker(c.body)?.keys?.type;
76
+ if (type === 'decision') {
77
+ const gist = (c.body.split('\n').find((l) => l.trim() && !l.startsWith('<!--') && !l.startsWith('#')) ?? '').trim();
78
+ if (gist && !(registerText ?? '').includes(gist.slice(0, 60))) pending.push(gist);
79
+ }
80
+ if (type === 'evidence') {
81
+ const m = /\*\*Decision:\*\*\s*([^\n]+)/.exec(c.body);
82
+ if (m && !/^none\b/i.test(m[1].trim()) && !(registerText ?? '').includes(m[1].trim().slice(0, 60))) pending.push(m[1].trim());
83
+ }
84
+ }
85
+ return pending;
86
+ }
87
+
88
+ // CheckRuns carry `conclusion`; StatusContexts carry `state`. An empty rollup
89
+ // is "no-checks", never green.
90
+ export function checksState(rollup) {
91
+ if (!rollup || rollup.length === 0) return 'no-checks';
92
+ const ok = (c) => ['SUCCESS', 'NEUTRAL', 'SKIPPED'].includes(c.conclusion ?? c.state ?? '');
93
+ return rollup.every(ok) ? 'green' : 'pending-or-red';
94
+ }
95
+
96
+ export function gatherStatus({ repo, staleDays = 3, devMdPath = '.vegastack/dev.md', chroniclePath = '.vegastack/chronicle.md', now = Date.now() } = {}) {
97
+ const resolvedRepo = repo || gh(['repo', 'view', '--json', 'nameWithOwner']).nameWithOwner;
98
+ const devMdText = existsSync(devMdPath) ? readFileSync(devMdPath, 'utf8') : '';
99
+ const knobs = readKnobs(devMdText);
100
+ const board = {};
101
+ for (const label of knobs.states) {
102
+ board[label] = gh(['issue', 'list', '-R', resolvedRepo, '--label', label, '--state', 'open',
103
+ '--json', 'number,title,url,updatedAt,labels,assignees']).map((i) => ({
104
+ number: i.number, title: i.title, url: i.url,
105
+ ageDays: ageDays(i.updatedAt, now),
106
+ scope: (i.labels ?? []).map((l) => l.name).find((n) => knobs.scopes.includes(n)) ?? null,
107
+ risky: (i.labels ?? []).some((l) => l.name === knobs.risky),
108
+ }));
109
+ }
110
+
111
+ // Enrich working + for-operator issues with comment-derived signals.
112
+ const registerText = existsSync(knobs.register) ? readFileSync(knobs.register, 'utf8') : '';
113
+ const decisions = [];
114
+ for (const bucket of [knobs.states[3], knobs.states[4]]) {
115
+ for (const issue of board[bucket]) {
116
+ const comments = gh(['api', `repos/${resolvedRepo}/issues/${issue.number}/comments`, '--paginate']);
117
+ issue.tasks = taskProgress(comments);
118
+ const moved = ledgerMovedAt(comments);
119
+ issue.ledgerAgeDays = moved ? ageDays(moved, now) : null;
120
+ issue.stale = bucket === knobs.states[3] && (issue.ledgerAgeDays === null || issue.ledgerAgeDays >= staleDays);
121
+ decisions.push(...pendingDecisions(comments, registerText).map((d) => ({ issue: issue.number, gist: d })));
122
+ }
123
+ }
124
+
125
+ const prs = gh(['pr', 'list', '-R', resolvedRepo, '--json', 'number,title,url,statusCheckRollup'])
126
+ .map((p) => ({
127
+ number: p.number, title: p.title, url: p.url,
128
+ checks: checksState(p.statusCheckRollup),
129
+ }));
130
+
131
+ let lastChronicle = null;
132
+ if (existsSync(chroniclePath)) {
133
+ const m = /^## (\d{2}-\d{2}-\d{4}) — (.+)$/m.exec(readFileSync(chroniclePath, 'utf8'));
134
+ if (m) lastChronicle = { date: m[1], title: m[2] };
135
+ }
136
+
137
+ return { repo: resolvedRepo, staleDays, board, prs, pendingDecisions: decisions, lastChronicle };
138
+ }
139
+
140
+ const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
141
+ if (invokedDirectly) {
142
+ const argv = process.argv.slice(2);
143
+ const get = (f) => { const i = argv.indexOf(f); return i === -1 ? undefined : argv[i + 1]; };
144
+ try {
145
+ const staleDaysRaw = Number(get('--stale-days'));
146
+ const data = gatherStatus({ repo: get('--repo'), staleDays: Number.isFinite(staleDaysRaw) && staleDaysRaw >= 1 ? staleDaysRaw : 3, devMdPath: get('--dev-md') });
147
+ console.log(JSON.stringify(data, null, argv.includes('--json') ? 2 : 0));
148
+ } catch (error) {
149
+ console.error(`status: cannot verify — ${error.message}`);
150
+ process.exit(2);
151
+ }
152
+ }
@@ -8,8 +8,8 @@ Content is advisory prose and decision tables — no rule IDs, no machine-extrac
8
8
 
9
9
  | Bump | Content change |
10
10
  |---|---|
11
- | MAJOR | Removing or renaming a skill. A breaking change to a per-project profile format (e.g. `.vegastack/arch.md`) that invalidates existing committed profiles. |
12
- | MINOR | New reference file or reference section. New or changed recorded decision (e.g. a new "use/not/why" row, a new red line). New skill. |
11
+ | MAJOR | Removing a skill. A breaking change to the per-project profile format (`.vegastack/dev.md`) that invalidates existing committed profiles — the operator may also declare any other change major. |
12
+ | MINOR | Renaming a skill (default — the operator declares major when the break warrants it). New reference file or reference section. New or changed recorded decision (e.g. a new "use/not/why" row, a new red line). New skill. |
13
13
  | PATCH | Factual refreshes: pinned-fact updates, version pins, vendor mechanism names, URLs, registry checksums. Wording clarifications that don't change the recorded decision. Test/fixture-only changes. |
14
14
 
15
15
  Installer/CLI changes follow ordinary semver on the same package version; a release takes the highest bump either side requires.
@@ -44,7 +44,7 @@ Skill names are consumer-facing identifiers — treat a rename as a stable-ID br
44
44
 
45
45
  1. Rename the directory and the frontmatter `name` in the same commit — they must always stay equal.
46
46
  2. Update every wiring point in the same PR: the skill's entry in `packages/cli/packaging.json`, the root README skills table row, and any cross-skill or docs links.
47
- 3. Changeset: MAJOR. Note the migration in `CHANGELOG.md`: copies installed under the old name are orphaned, and installer operations addressed to the old name stop resolving once the shipped manifest no longer knows it.
47
+ 3. Changeset: MINOR by default — major only when the operator declares it. Either way, note the migration in `CHANGELOG.md`: copies installed under the old name are orphaned, and installer operations addressed to the old name stop resolving once the shipped manifest no longer knows it.
48
48
  4. Registry source IDs inside `refresh/sources.json` are skill-internal and unaffected, but every `affected` ref must still resolve to a real reference in the renamed tree.
49
49
  5. Re-run `node packages/cli/scripts/validate-skill.mjs skills/<new-name>` and the skill's tests — name/directory equality is validated.
50
50