@golden-frijoles/kit 0.33.0 → 0.34.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-06 against 1.3.0.
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.0';
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({
@@ -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(readFileSync(join(e.path, 'README.md'), 'utf8'), ['Why', 'Goal', 'Problem']),
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@golden-frijoles/kit",
3
- "version": "0.33.0",
3
+ "version": "0.34.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": {