@golden-frijoles/kit 0.33.0 → 0.35.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.
|
@@ -54,10 +54,10 @@ export const AGENT_BIN = {
|
|
|
54
54
|
// Harmless here since AGY_MODEL/AGY_FALLBACK_MODEL below are always valid, listed model names (checked via
|
|
55
55
|
// `agy models`), but it means a future typo in either constant would silently review with the WRONG model
|
|
56
56
|
// instead of failing loud — watch for that if either constant is ever edited.
|
|
57
|
-
// agy-doctor: last verified 2026-10-
|
|
57
|
+
// agy-doctor: last verified 2026-10-07 against 1.3.1.
|
|
58
58
|
// ^ machine-managed marker — `node scripts/cross-agent-doctor.mjs agy --fix` rewrites it (with the constant
|
|
59
59
|
// below) after a green live contract probe. Don't hand-edit the marker's shape.
|
|
60
|
-
export const AGY_PINNED = '1.3.
|
|
60
|
+
export const AGY_PINNED = '1.3.1';
|
|
61
61
|
|
|
62
62
|
// agy's `--print` mode prints NOTHING unless `--model` names a model — and, crucially, it ALSO prints
|
|
63
63
|
// nothing (exit 0, empty stdout — the error lands only in agy's log, see --log-file) when the model is
|
|
@@ -140,6 +140,25 @@ export function validateResultFields(fm) {
|
|
|
140
140
|
return offenses;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// one-epic-page D11 — the epic's flag, decided at groom Stage 6b and copied into the README by the scaffold. The SDK's
|
|
144
|
+
// flag-key grammar (`FLAG_KEY` in apps/web/lib/flag-admin-operation.ts). Optional: `flag_key: null` (no flag) is the
|
|
145
|
+
// default and never an error; a value that is not a key is named, never pushed (the extract sends null for it).
|
|
146
|
+
export const FLAG_KEY_RE = /^[a-z][a-z0-9_.-]{0,127}$/;
|
|
147
|
+
|
|
148
|
+
/** `flag_key:` → offenses (`contract-flag-key-invalid`). Absent or null is fine. */
|
|
149
|
+
export function validateFlagKey(fm) {
|
|
150
|
+
const v = fm.flag_key;
|
|
151
|
+
if (v === undefined || v === null) return [];
|
|
152
|
+
// The string "null" fits the grammar but means "no flag": named here as the push schema refuses it (codex, #297).
|
|
153
|
+
if (typeof v === 'string' && v !== 'null' && FLAG_KEY_RE.test(v)) return [];
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
rule: 'contract-flag-key-invalid',
|
|
157
|
+
detail: `flag_key: "${v}" is not a flag key (a lowercase letter, then a-z 0-9 _ . -; at most 128) — or null`,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
|
|
143
162
|
// live-build-view D10 — `locked_at:` is stamped by `scripts/epic-phase.mjs lock` when the architecture lock is written;
|
|
144
163
|
// the build view reads its absence as "Locking architecture". Optional (no epic before it has one), an ISO date-time
|
|
145
164
|
// string when present: the command writes `"2026-10-03T20:34:34Z"` (quoted, so the frontmatter parser keeps the colons).
|
|
@@ -355,6 +374,7 @@ export function validateEpicFrontmatter(parsed, ctx = {}) {
|
|
|
355
374
|
}
|
|
356
375
|
offenses.push(...validateFinopsFields(fm));
|
|
357
376
|
offenses.push(...validateResultFields(fm));
|
|
377
|
+
offenses.push(...validateFlagKey(fm));
|
|
358
378
|
offenses.push(...validateLockedAt(fm));
|
|
359
379
|
if (isInt(fm.sprints_total) && isInt(ctx.sprintCount) && fm.sprints_total !== ctx.sprintCount)
|
|
360
380
|
offenses.push({
|
package/dist/roadmap-extract.mjs
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
FINOPS_NUMERIC_FIELDS,
|
|
74
74
|
RESULT_DAY_FIELDS,
|
|
75
75
|
RESULT_FIELDS,
|
|
76
|
+
FLAG_KEY_RE,
|
|
76
77
|
RESULT_NUMERIC_FIELDS,
|
|
77
78
|
VERDICTS,
|
|
78
79
|
} from './lib/roadmap-contract.mjs';
|
|
@@ -161,6 +162,21 @@ export function resultFields(fm, shippedAt) {
|
|
|
161
162
|
return out;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* one-epic-page D11 — an epic's flag: `flag_key` off its README frontmatter (the SDK's key grammar, else null — the
|
|
167
|
+
* contract names a bad one) and `flag_note`, the README's own `**Flag:** …` line, which is where an epic with no flag
|
|
168
|
+
* says why. Both null when absent; nothing is inferred from prose about a flag the frontmatter does not name.
|
|
169
|
+
*/
|
|
170
|
+
export function flagFields(fm, readme) {
|
|
171
|
+
const raw = typeof fm.flag_key === 'string' ? fm.flag_key.trim() : '';
|
|
172
|
+
// A QUOTED `flag_key: "null"` (or `~`) arrives as a string, and "null" fits the key grammar — so the blank
|
|
173
|
+
// spellings are refused before the pattern is asked. (Unquoted `null` is already null from this file's reader.)
|
|
174
|
+
const blank = raw === '' || raw === 'null' || raw === '~';
|
|
175
|
+
const line = /^\*\*Flag:\*\*[ \t]*(.+)$/m.exec(readme ?? '');
|
|
176
|
+
const note = line ? line[1].replace(/[*`]/g, '').trim().slice(0, 280) : '';
|
|
177
|
+
return { flag_key: !blank && FLAG_KEY_RE.test(raw) ? raw : null, flag_note: note || null };
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
function parseFrontmatter(md) {
|
|
165
181
|
if (!md.startsWith('---')) return {};
|
|
166
182
|
const end = md.indexOf('\n---', 3);
|
|
@@ -583,6 +599,7 @@ export function buildRows({
|
|
|
583
599
|
}
|
|
584
600
|
}
|
|
585
601
|
|
|
602
|
+
const readme = readFileSync(join(e.path, 'README.md'), 'utf8');
|
|
586
603
|
// Epic row
|
|
587
604
|
rows.push({
|
|
588
605
|
name: epicTitle(e.path, e.slug),
|
|
@@ -605,7 +622,7 @@ export function buildRows({
|
|
|
605
622
|
build_order_num: buildOrderNum(buildOrder),
|
|
606
623
|
doc_link: readmePath,
|
|
607
624
|
epic_slug: null,
|
|
608
|
-
goal: firstParagraph(
|
|
625
|
+
goal: firstParagraph(readme, ['Why', 'Goal', 'Problem']),
|
|
609
626
|
sprints: boardSprints.map((sp) => ({ n: sp.n, title: sp.title, done: sp.done, total: sp.total })),
|
|
610
627
|
links: {
|
|
611
628
|
readme: readmePath,
|
|
@@ -618,6 +635,7 @@ export function buildRows({
|
|
|
618
635
|
shipped_at: stage === 'Shipped' ? statusDay : null,
|
|
619
636
|
...finopsFields(epicFm),
|
|
620
637
|
...resultFields(epicFm, stage === 'Shipped' ? statusDay : null),
|
|
638
|
+
...flagFields(epicFm, readme),
|
|
621
639
|
});
|
|
622
640
|
|
|
623
641
|
// Sprint rows (one per sprint-N.md), related to the Epic by slug. boardSprints already carries
|
|
@@ -76,13 +76,13 @@ enforced enum — a present-but-unrecognized value fails the board, it never fal
|
|
|
76
76
|
from a brain-dump).
|
|
77
77
|
2. **Scope** — `groom` fills out the Definition-of-Ready (appetite included) and flips
|
|
78
78
|
`status: ready`.
|
|
79
|
-
3. **Fund + scaffold, in one answer** — the approval gate is the betting table. On "
|
|
79
|
+
3. **Fund + scaffold, in one answer** — the approval gate is the betting table. On "Approve the plan", `groom`
|
|
80
80
|
runs its own `fund.mjs` (a row in the month's `Roadmap/bets/wave-YYYY-MM.md`, created on first use,
|
|
81
81
|
recording what the bet displaced; `underwritten_by:`; a `build_order` placed with `--next` or
|
|
82
82
|
`--after <slug>`, renumbering only the queue) and then `scaffold-epic.mjs` (the epic/sprint docs; the
|
|
83
83
|
seed gets `epic:` + `status: scaffolded`), committed together. Both ship inside the `groom` skill,
|
|
84
84
|
`golden-frijoles` plugin. A fixed-scope seed scaffolds from its slug alone, its acceptance criteria
|
|
85
|
-
becoming sprint 1's stories. "
|
|
85
|
+
becoming sprint 1's stories. "Park it" leaves the seed `ready` and scaffolds nothing.
|
|
86
86
|
**No file ever moves between folders** — the frontmatter carries the state.
|
|
87
87
|
|
|
88
88
|
Filenames are kebab-case and match `slug`. Audits live in `audits/`, never in `seeds/`.
|
|
@@ -93,10 +93,11 @@ and session context. Fixed appetite, variable scope.
|
|
|
93
93
|
| **L** | a multi-wave epic | per-wave: each wave is re-bet at the boundary |
|
|
94
94
|
|
|
95
95
|
Four rules: **an exhausted bet returns to shaping**, never extends in flight; **the approval gate is the
|
|
96
|
-
betting table** —
|
|
97
|
-
cycle file `Roadmap/bets/wave-YYYY-MM.md` recording what it displaced, `underwritten_by:`, and a
|
|
98
|
-
position), in the same commit as the scaffold, while
|
|
99
|
-
|
|
96
|
+
betting table** — **Approve the plan** in `groom`'s Plan gate funds it in the same answer (`fund.mjs`: a row in
|
|
97
|
+
the month's cycle file `Roadmap/bets/wave-YYYY-MM.md` recording what it displaced, `underwritten_by:`, and a
|
|
98
|
+
build position), in the same commit as the scaffold, while **Park it** leaves it `ready` and scaffolds nothing
|
|
99
|
+
(the gates' words: groom `references/gates.md`); **nothing scaffolded is unfunded** — `build-order.mjs` fails a
|
|
100
|
+
live bet with no `underwritten_by:`;
|
|
100
101
|
and **uphill work stays on the strongest model**. An L bet is re-bet at each wave boundary with one line
|
|
101
102
|
when its builder stops there ("fund the next wave of `<slug>`? what does it displace?"; yes runs `fund.mjs`
|
|
102
103
|
again, position kept). Fixed scope (appetite S) and reactive/ops work go through the same gate, straight to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@golden-frijoles/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "The scripts the Golden Frijoles skills run, packaged so they work in any repo: planning, build-order, reporting and verification rails. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|