@golden-frijoles/kit 1.3.0 → 1.5.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.
|
@@ -44,6 +44,19 @@ export const REGISTRY = Object.freeze([
|
|
|
44
44
|
question:
|
|
45
45
|
'An account adds flags you can roll out and turn off, each bet measured on its read date, every product in one place and an outcome report you can send. Sign in now (recommended: frijoles login, then frijoles init), or later?',
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
// one-bet-wired D6 — the one place an account is suggested outside setup: refine's Measure question, when the
|
|
49
|
+
// founder wants to know if a feature worked and is not signed in. Asked once per project; the answer is kept here.
|
|
50
|
+
key: 'measure.signIn',
|
|
51
|
+
module: 'Measure',
|
|
52
|
+
// Asked by refine's Measure step, never by setup, and only when the founder is signed out: `never-yet` keeps
|
|
53
|
+
// `frijoles doctor` from reporting Measure as unconfigured for everyone (verifier, #341).
|
|
54
|
+
askWhen: 'never-yet',
|
|
55
|
+
default: null,
|
|
56
|
+
choices: ['later', 'now'],
|
|
57
|
+
question:
|
|
58
|
+
'Measuring needs a Golden Frijoles account: it serves the flag and counts who used it. Sign in now (opens your browser once), or later (the bet is saved here either way)?',
|
|
59
|
+
},
|
|
47
60
|
{
|
|
48
61
|
key: 'board.sink',
|
|
49
62
|
module: 'Plan',
|
package/dist/lib/config.mjs
CHANGED
|
@@ -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-10 against 1.3.3.
|
|
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.3';
|
|
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
|
|
@@ -162,6 +162,31 @@ export function validateFlagKey(fm) {
|
|
|
162
162
|
// grounded-bets D1 — `grounded:` is the founder's word at Stage 1.5: true (traced to a North Star input), false (funded
|
|
163
163
|
// anyway, and `grounded_reason` says why), or absent/null (a Bug, a Chore, or an epic refined before it existed).
|
|
164
164
|
// Whether a bet COUNTS as grounded is derived from its target (bets-grounded.mjs, D2), never from this field alone.
|
|
165
|
+
// one-bet-wired D1 — a bet's measurement: what its flag funnel reads. Optional as a whole; once any field is set it
|
|
166
|
+
// needs a flag and an adoption event, the only segment is `everyone` (named segments are tars-segments), and the window
|
|
167
|
+
// is 1–90 whole days. Event names are tokens: what the SDK sends.
|
|
168
|
+
export const BET_FIELDS = ['target_segment', 'adopted_event', 'retained_event', 'retention_days', 'satisfied_event'];
|
|
169
|
+
export const BET_SEGMENTS = ['everyone'];
|
|
170
|
+
const EVENT_NAME = /^[A-Za-z0-9_.:$-]{1,200}$/;
|
|
171
|
+
|
|
172
|
+
/** The bet's measurement fields → offenses (`contract-bet-invalid`). All absent is fine. */
|
|
173
|
+
export function validateBet(fm) {
|
|
174
|
+
const offenses = [];
|
|
175
|
+
const bad = (detail) => offenses.push({ rule: 'contract-bet-invalid', detail });
|
|
176
|
+
const has = (key) => fm[key] !== undefined && fm[key] !== null;
|
|
177
|
+
if (!BET_FIELDS.some(has)) return offenses;
|
|
178
|
+
if (!has('flag_key')) bad('a bet with measurement needs a flag_key (the flag whose funnel it reads)');
|
|
179
|
+
if (!has('adopted_event')) bad('a bet with measurement needs an adopted_event (what counts as adopting)');
|
|
180
|
+
if (has('target_segment') && !BET_SEGMENTS.includes(fm.target_segment))
|
|
181
|
+
bad(`target_segment: "${fm.target_segment}" is not one of ${BET_SEGMENTS.join(' | ')} (named segments come with tars-segments)`);
|
|
182
|
+
for (const key of ['adopted_event', 'retained_event', 'satisfied_event'])
|
|
183
|
+
if (has(key) && !(typeof fm[key] === 'string' && EVENT_NAME.test(fm[key])))
|
|
184
|
+
bad(`${key}: "${fm[key]}" is not an event name (letters, digits, _ . : $ -)`);
|
|
185
|
+
if (has('retention_days') && !(Number.isInteger(fm.retention_days) && fm.retention_days >= 1 && fm.retention_days <= 90))
|
|
186
|
+
bad(`retention_days: "${fm.retention_days}" is not a whole number of days from 1 to 90`);
|
|
187
|
+
return offenses;
|
|
188
|
+
}
|
|
189
|
+
|
|
165
190
|
/** `grounded:` as a boolean: the frontmatter readers keep a bare `true` as the string "true". Null when absent or neither. */
|
|
166
191
|
export function groundedValue(v) {
|
|
167
192
|
if (v === true || v === 'true') return true;
|
|
@@ -401,6 +426,7 @@ export function validateEpicFrontmatter(parsed, ctx = {}) {
|
|
|
401
426
|
offenses.push(...validateResultFields(fm));
|
|
402
427
|
offenses.push(...validateFlagKey(fm));
|
|
403
428
|
offenses.push(...validateGrounded(fm));
|
|
429
|
+
offenses.push(...validateBet(fm));
|
|
404
430
|
offenses.push(...validateLockedAt(fm));
|
|
405
431
|
if (isInt(fm.sprints_total) && isInt(ctx.sprintCount) && fm.sprints_total !== ctx.sprintCount)
|
|
406
432
|
offenses.push({
|
package/dist/roadmap-extract.mjs
CHANGED
|
@@ -178,6 +178,22 @@ export function flagFields(fm, readme) {
|
|
|
178
178
|
return { flag_key: !blank && FLAG_KEY_RE.test(raw) ? raw : null, flag_note: note || null };
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* one-bet-wired D1 — the bet's measurement as pushed: each field as written when it is well-formed, else null (the
|
|
183
|
+
* contract names a bad one). Defaults are the reader's (an absent window is 7 days), never written here.
|
|
184
|
+
*/
|
|
185
|
+
export function betFields(fm) {
|
|
186
|
+
const token = (v) => (typeof v === 'string' && /^[A-Za-z0-9_.:$-]{1,200}$/.test(v.trim()) ? v.trim() : null);
|
|
187
|
+
const days = Number(fm.retention_days);
|
|
188
|
+
return {
|
|
189
|
+
target_segment: fm.target_segment === 'everyone' ? 'everyone' : null,
|
|
190
|
+
adopted_event: token(fm.adopted_event),
|
|
191
|
+
retained_event: token(fm.retained_event),
|
|
192
|
+
retention_days: Number.isInteger(days) && days >= 1 && days <= 90 ? days : null,
|
|
193
|
+
satisfied_event: token(fm.satisfied_event),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
181
197
|
/**
|
|
182
198
|
* grounded-bets D9 — the founder's grounding, as recorded at Stage 1.5: `grounded` true | false | null and, when false,
|
|
183
199
|
* `grounded_reason`. Anything else is null (the contract names it); the reason travels only with false.
|
|
@@ -648,6 +664,7 @@ export function buildRows({
|
|
|
648
664
|
...resultFields(epicFm, stage === 'Shipped' ? statusDay : null),
|
|
649
665
|
...flagFields(epicFm, readme),
|
|
650
666
|
...groundedFields(epicFm),
|
|
667
|
+
...betFields(epicFm),
|
|
651
668
|
});
|
|
652
669
|
|
|
653
670
|
// 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": "1.
|
|
3
|
+
"version": "1.5.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": {
|