@pmelab/gtd 64.0.0 → 64.1.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.
@@ -7415,7 +7415,7 @@ function isFinite(annotations) {
7415
7415
  * @category constructors
7416
7416
  * @since 4.0.0
7417
7417
  */
7418
- function isPattern(regExp, annotations) {
7418
+ function isPattern$1(regExp, annotations) {
7419
7419
  const copy = new globalThis.RegExp(regExp);
7420
7420
  const payload = {
7421
7421
  source: copy.source,
@@ -7626,7 +7626,7 @@ function fromRefinement(ast, refinement) {
7626
7626
  }
7627
7627
  /** @internal */
7628
7628
  function isStringFinite(annotations) {
7629
- return isPattern(isStringFiniteRegExp, {
7629
+ return isPattern$1(isStringFiniteRegExp, {
7630
7630
  expected: "a string representing a finite number",
7631
7631
  representation: {
7632
7632
  id: "effect/schema/isStringFinite",
@@ -10043,7 +10043,7 @@ var init_Channel = __esmMin((() => {
10043
10043
  }));
10044
10044
  //#endregion
10045
10045
  //#region node_modules/effect/dist/internal/glob.js
10046
- var maxExpansions, maxExpandedLength, maxBraceDepth, compile$1, isUnc, compilePattern, globstarNode, splitSegments, drive, isDrive, isRoot, isAbsolute$1, normalize, compileSegment, range, posixClasses, parsePosixClass, parseClass, expandBraces, matchBraceGroups, parseBraceSequence, countExpansions, expandSequence, matchToken, matchWildcard, closure, step, union, accepts, canDescend, needsDirectory, join$1, isMissing, isIgnorable, isNotLink, byName, make$9, walk;
10046
+ var maxExpansions, maxExpandedLength, maxBraceDepth, compile$1, isUnc, compilePattern, globstarNode, splitSegments, drive, isDrive, isRoot, isAbsolute$1, normalize, compileSegment, range, posixClasses, parsePosixClass, parseClass, expandBraces, matchBraceGroups, parseBraceSequence, countExpansions, expandSequence, matchToken, matchWildcard, closure, step, union, accepts, canDescend, needsDirectory, join$1, isMissing, isIgnorable, isNotLink, byName, make$9, walk$1;
10047
10047
  var init_glob = __esmMin((() => {
10048
10048
  init_Array();
10049
10049
  init_Effect();
@@ -10447,8 +10447,8 @@ var init_glob = __esmMin((() => {
10447
10447
  isIgnorable = (error) => isMissing(error) || error.reason._tag === "PermissionDenied";
10448
10448
  isNotLink = (error) => isIgnorable(error) || error.reason._tag === "Unknown";
10449
10449
  byName = /*#__PURE__*/ mapInput(String$4, (entry) => entry.name);
10450
- make$9 = (fs) => (pattern, options) => walk(fs, compile$1([pattern], options?.dot ?? false), options);
10451
- walk = /*#__PURE__*/ fnUntraced(function* (fs, compiled, options) {
10450
+ make$9 = (fs) => (pattern, options) => walk$1(fs, compile$1([pattern], options?.dot ?? false), options);
10451
+ walk$1 = /*#__PURE__*/ fnUntraced(function* (fs, compiled, options) {
10452
10452
  if (compiled.initial.size === 0) return [];
10453
10453
  const root = options?.root ?? "";
10454
10454
  const excludes = compile$1(options?.exclude ?? [], true);
@@ -31157,6 +31157,423 @@ Nothing here is validated — free-form has no structure to conform to.
31157
31157
  };
31158
31158
  }));
31159
31159
  //#endregion
31160
+ //#region src/steering/Declared.ts
31161
+ var FENCE_RE, HEADING_RE, ITEM_RE, CHECKBOX_RE, walk, headingOf, indentOf, markerKind, markerList, titleMatches, titleShown, regionOf, readItems, readCheckbox, finishItem, parseList, sectionSentence, CHECKBOX_SENTENCE, idSentence, listInstructions, listSample, bodyOf, sectionHeadingProblem, parseSections, sectionInstructions, NAME_RE, pattern, encode, isPattern, checkName, withThreads, listFormat, sectionFormat, decodePatterns, formatFromSpec;
31162
+ var init_Declared = __esmMin((() => {
31163
+ init_Footnotes();
31164
+ init_freeform();
31165
+ FENCE_RE = /^( *)(`{3,}|~{3,})/;
31166
+ HEADING_RE = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/;
31167
+ ITEM_RE = /^( *)([-*+]|\d{1,9}[.)])(?:[ \t]+(.*))?$/;
31168
+ CHECKBOX_RE = /^\[([ xX])\](?:[ \t]+|$)/;
31169
+ walk = (content) => {
31170
+ const raw = content.split(/\r?\n/);
31171
+ const lines = [];
31172
+ const problems = [];
31173
+ let open;
31174
+ for (const [index, text] of raw.entries()) {
31175
+ const fence = FENCE_RE.exec(text);
31176
+ if (open === void 0) {
31177
+ if (fence !== null) {
31178
+ open = {
31179
+ char: fence[2][0],
31180
+ length: fence[2].length,
31181
+ line: index,
31182
+ indent: fence[1].length
31183
+ };
31184
+ lines.push({
31185
+ index,
31186
+ text,
31187
+ fenced: true,
31188
+ fenceIndent: open.indent,
31189
+ opens: true
31190
+ });
31191
+ } else lines.push({
31192
+ index,
31193
+ text,
31194
+ fenced: false
31195
+ });
31196
+ continue;
31197
+ }
31198
+ lines.push({
31199
+ index,
31200
+ text,
31201
+ fenced: true,
31202
+ fenceIndent: open.indent
31203
+ });
31204
+ if (fence !== null && fence[2][0] === open.char && fence[2].length >= open.length && text.slice(fence[0].length).trim() === "") open = void 0;
31205
+ }
31206
+ if (open !== void 0) problems.push({
31207
+ line: open.line,
31208
+ message: "code fence is never closed"
31209
+ });
31210
+ return {
31211
+ lines,
31212
+ problems
31213
+ };
31214
+ };
31215
+ headingOf = (line) => {
31216
+ if (line.fenced) return void 0;
31217
+ const match = HEADING_RE.exec(line.text);
31218
+ return match === null ? void 0 : {
31219
+ line: line.index,
31220
+ level: match[1].length,
31221
+ title: (match[2] ?? "").trim()
31222
+ };
31223
+ };
31224
+ indentOf = (text) => /^ */.exec(text)[0].length;
31225
+ markerKind = (marker) => marker === "-" || marker === "*" || marker === "+" ? marker : "1.";
31226
+ markerList = (markers) => markers.map((m) => `\`${m}\``).join(" or ");
31227
+ titleMatches = (spec, title) => typeof spec.title === "string" ? spec.title.trim().toLowerCase() === title.toLowerCase() : spec.title.test(title);
31228
+ titleShown = (spec) => typeof spec.title === "string" ? spec.title : spec.example;
31229
+ regionOf = (spec, lines) => {
31230
+ const section = spec.section;
31231
+ if (section === void 0) return {
31232
+ region: {
31233
+ start: 0,
31234
+ end: lines.length
31235
+ },
31236
+ problems: []
31237
+ };
31238
+ const problems = [];
31239
+ const headings = lines.flatMap((line) => headingOf(line) ?? []);
31240
+ const matching = headings.filter((h) => titleMatches(section, h.title));
31241
+ const wanted = "#".repeat(section.level);
31242
+ for (const h of matching.filter((h) => h.level !== section.level)) problems.push({
31243
+ line: h.line,
31244
+ message: `"${h.title}" is a level-${h.level} heading — write it as \`${wanted} ${h.title}\``
31245
+ });
31246
+ const atLevel = matching.filter((h) => h.level === section.level);
31247
+ for (const h of atLevel.slice(1)) problems.push({
31248
+ line: h.line,
31249
+ message: `"${h.title}" repeats the section on line ${atLevel[0].line + 1} — keep one`
31250
+ });
31251
+ const first = atLevel[0];
31252
+ if (first === void 0) {
31253
+ if (section.required !== false && matching.length === 0) problems.push({ message: `missing the \`${wanted} ${titleShown(section)}\` section` });
31254
+ return { problems };
31255
+ }
31256
+ const next = headings.find((h) => h.line > first.line && h.level <= section.level);
31257
+ return {
31258
+ region: {
31259
+ start: first.line + 1,
31260
+ end: next?.line ?? lines.length
31261
+ },
31262
+ problems
31263
+ };
31264
+ };
31265
+ readItems = (spec, lines, region) => {
31266
+ const markers = spec.markers ?? ["-"];
31267
+ const wrap = spec.wrap ?? "join";
31268
+ const problems = [];
31269
+ const drafts = [];
31270
+ let firstKind;
31271
+ let current;
31272
+ let blank = false;
31273
+ let prose = [];
31274
+ const continueWith = (line, text) => {
31275
+ if (wrap === "forbid") {
31276
+ problems.push({
31277
+ line: line.index,
31278
+ message: "one line per item — this line continues the one above"
31279
+ });
31280
+ return;
31281
+ }
31282
+ current.parts.push(text.trim());
31283
+ };
31284
+ for (const line of lines.slice(region.start, region.end)) {
31285
+ const text = line.text;
31286
+ if (line.fenced) {
31287
+ if (current !== void 0 && (line.fenceIndent ?? 0) >= 2) {
31288
+ blank = false;
31289
+ continue;
31290
+ }
31291
+ if (line.opens === true && current !== void 0) {
31292
+ prose.push(line.index);
31293
+ current = void 0;
31294
+ } else if (line.opens !== true && ITEM_RE.test(text) && indentOf(text) < 2) problems.push({
31295
+ line: line.index,
31296
+ message: "a list item inside a code fence is not read as an item"
31297
+ });
31298
+ blank = false;
31299
+ continue;
31300
+ }
31301
+ if (text.trim() === "") {
31302
+ blank = true;
31303
+ continue;
31304
+ }
31305
+ const item = ITEM_RE.exec(text);
31306
+ const indent = indentOf(text);
31307
+ if (item !== null && indent < 2) {
31308
+ for (const p of prose) problems.push({
31309
+ line: p,
31310
+ message: "text between items — make it part of an item, or move it out of the list"
31311
+ });
31312
+ prose = [];
31313
+ const kind = markerKind(item[2]);
31314
+ if (!markers.includes(kind)) problems.push({
31315
+ line: line.index,
31316
+ message: `\`${item[2]}\` is not an allowed marker — use ${markerList(markers)}`
31317
+ });
31318
+ else if (firstKind !== void 0 && kind !== firstKind) problems.push({
31319
+ line: line.index,
31320
+ message: `\`${item[2]}\` mixes markers in one list — the list started with \`${firstKind}\``
31321
+ });
31322
+ firstKind ??= kind;
31323
+ current = {
31324
+ line: line.index,
31325
+ parts: [(item[3] ?? "").trim()]
31326
+ };
31327
+ drafts.push(current);
31328
+ blank = false;
31329
+ continue;
31330
+ }
31331
+ if (current === void 0) {
31332
+ blank = false;
31333
+ continue;
31334
+ }
31335
+ if (item !== null) problems.push({
31336
+ line: line.index,
31337
+ message: "nested list — this format is flat, one level of items"
31338
+ });
31339
+ else if (indent >= 2) continueWith(line, text);
31340
+ else if (!blank) {
31341
+ problems.push({
31342
+ line: line.index,
31343
+ message: "continuation line is not indented — indent it under its item, or separate it with a blank line"
31344
+ });
31345
+ continueWith(line, text);
31346
+ } else {
31347
+ prose.push(line.index);
31348
+ current = void 0;
31349
+ }
31350
+ blank = false;
31351
+ }
31352
+ return {
31353
+ drafts,
31354
+ problems
31355
+ };
31356
+ };
31357
+ readCheckbox = (spec, draft, problems) => {
31358
+ const checkbox = spec.checkbox ?? "forbid";
31359
+ const text = draft.parts.join(" ").trim();
31360
+ const box = CHECKBOX_RE.exec(text);
31361
+ if (box === null) {
31362
+ if (checkbox === "require") problems.push({
31363
+ line: draft.line,
31364
+ message: "every item starts with a checkbox — write `- [ ] …`"
31365
+ });
31366
+ return checkbox === "forbid" ? { text } : {
31367
+ text,
31368
+ checked: false
31369
+ };
31370
+ }
31371
+ if (checkbox === "forbid") problems.push({
31372
+ line: draft.line,
31373
+ message: "no checkboxes in this list — drop the `[ ]`"
31374
+ });
31375
+ const rest = text.slice(box[0].length).trim();
31376
+ return checkbox === "forbid" ? { text: rest } : {
31377
+ text: rest,
31378
+ checked: box[1] !== " "
31379
+ };
31380
+ };
31381
+ finishItem = (spec, draft, problems) => {
31382
+ const { text, ...checked } = readCheckbox(spec, draft, problems);
31383
+ const item = spec.item;
31384
+ if (text === "") {
31385
+ problems.push({
31386
+ line: draft.line,
31387
+ message: "empty item"
31388
+ });
31389
+ return;
31390
+ }
31391
+ if (item === void 0) return {
31392
+ line: draft.line,
31393
+ text,
31394
+ ...checked
31395
+ };
31396
+ const at = text.indexOf(item.separator);
31397
+ const id = at < 0 ? "" : text.slice(0, at).trim();
31398
+ if (at < 0 || !item.id.test(id)) {
31399
+ problems.push({
31400
+ line: draft.line,
31401
+ message: `item has no ID — start it \`${item.example}${item.separator} …\``
31402
+ });
31403
+ return;
31404
+ }
31405
+ const body = text.slice(at + item.separator.length).trim();
31406
+ if (body === "") problems.push({
31407
+ line: draft.line,
31408
+ message: `empty item — ${id} has no text`
31409
+ });
31410
+ return {
31411
+ line: draft.line,
31412
+ text: body,
31413
+ id,
31414
+ ...checked
31415
+ };
31416
+ };
31417
+ parseList = (spec, content) => {
31418
+ const walked = walk(content);
31419
+ const { region, problems: sectionProblems } = regionOf(spec, walked.lines);
31420
+ const problems = [...walked.problems, ...sectionProblems];
31421
+ if (region === void 0) return {
31422
+ items: [],
31423
+ problems
31424
+ };
31425
+ const read = readItems(spec, walked.lines, region);
31426
+ problems.push(...read.problems);
31427
+ const items = read.drafts.flatMap((draft) => finishItem(spec, draft, problems) ?? []);
31428
+ if (spec.ids?.unique === true) {
31429
+ const seen = /* @__PURE__ */ new Map();
31430
+ for (const item of items) {
31431
+ if (item.id === void 0) continue;
31432
+ const first = seen.get(item.id);
31433
+ if (first === void 0) seen.set(item.id, item.line);
31434
+ else problems.push({
31435
+ line: item.line,
31436
+ message: `duplicate ID ${item.id} — first on line ${first + 1}`
31437
+ });
31438
+ }
31439
+ }
31440
+ problems.sort((a, b) => (a.line ?? -1) - (b.line ?? -1));
31441
+ return {
31442
+ items,
31443
+ problems
31444
+ };
31445
+ };
31446
+ sectionSentence = (spec) => {
31447
+ if (spec.section === void 0) return "The whole document is the list.";
31448
+ return `Write the list under one \`${`${"#".repeat(spec.section.level)} ${titleShown(spec.section)}`}\` heading; it ends at the next heading of the same or a higher level.`;
31449
+ };
31450
+ CHECKBOX_SENTENCE = {
31451
+ forbid: "Do not use checkboxes.",
31452
+ strip: "",
31453
+ require: "Start every item with a checkbox: `[ ]`, or `[x]` when done."
31454
+ };
31455
+ idSentence = (spec) => {
31456
+ if (spec.item === void 0) return "";
31457
+ const unique = spec.ids?.unique === true ? ", unique within the list" : "";
31458
+ return `Start every item with its ID (like \`${spec.item.example}\`${unique}), then \`${spec.item.separator}\`, then the text.`;
31459
+ };
31460
+ listInstructions = (spec) => [
31461
+ sectionSentence(spec),
31462
+ `Write each item as a ${markerList(spec.markers ?? ["-"])} list item, all with the same marker.`,
31463
+ CHECKBOX_SENTENCE[spec.checkbox ?? "forbid"],
31464
+ idSentence(spec),
31465
+ (spec.wrap ?? "join") === "join" ? "Continue a long item on lines indented two spaces under it." : "Keep every item on one line.",
31466
+ "Keep the list flat: no nested items, and no text between items. Items inside a code fence are not read."
31467
+ ].filter((sentence) => sentence !== "").join(" ");
31468
+ listSample = (spec) => {
31469
+ const item = `${(spec.markers ?? ["-"])[0] === "1." ? "1." : (spec.markers ?? ["-"])[0]} ${spec.checkbox === "require" ? "[ ] " : ""}${spec.item !== void 0 ? `${spec.item.example}${spec.item.separator} ` : ""}Describe the item.\n`;
31470
+ return spec.section === void 0 ? item : `${"#".repeat(spec.section.level)} ${titleShown(spec.section)}\n\n${item}`;
31471
+ };
31472
+ bodyOf = (lines, start, end) => lines.slice(start, end).map((l) => l.text).join("\n").trim();
31473
+ sectionHeadingProblem = (spec, headings, i, firstLine) => {
31474
+ const h = headings[i];
31475
+ if (h.level < spec.level && headings.slice(0, i).some((p) => p.level === spec.level)) return `"${h.title}" is a level-${h.level} heading — sections are \`${"#".repeat(spec.level)}\``;
31476
+ if (h.level !== spec.level) return null;
31477
+ if (h.title === "") return "section heading has no title";
31478
+ const first = firstLine.get(h.title.toLowerCase());
31479
+ return spec.title === "unique" && first !== void 0 ? `"${h.title}" repeats the section on line ${first + 1} — titles are unique` : void 0;
31480
+ };
31481
+ parseSections = (spec, content) => {
31482
+ const walked = walk(content);
31483
+ const problems = [...walked.problems];
31484
+ const headings = walked.lines.flatMap((line) => headingOf(line) ?? []);
31485
+ const sections = [];
31486
+ const firstLine = /* @__PURE__ */ new Map();
31487
+ for (const [i, h] of headings.entries()) {
31488
+ const problem = sectionHeadingProblem(spec, headings, i, firstLine);
31489
+ if (problem === null) continue;
31490
+ if (problem !== void 0) problems.push({
31491
+ line: h.line,
31492
+ message: problem
31493
+ });
31494
+ if (h.level !== spec.level || h.title === "") continue;
31495
+ if (!firstLine.has(h.title.toLowerCase())) firstLine.set(h.title.toLowerCase(), h.line);
31496
+ const end = headings.slice(i + 1).find((n) => n.level <= spec.level)?.line ?? walked.lines.length;
31497
+ sections.push({
31498
+ line: h.line,
31499
+ title: h.title,
31500
+ body: bodyOf(walked.lines, h.line + 1, end)
31501
+ });
31502
+ }
31503
+ problems.sort((a, b) => (a.line ?? -1) - (b.line ?? -1));
31504
+ return {
31505
+ sections,
31506
+ problems
31507
+ };
31508
+ };
31509
+ sectionInstructions = (spec) => {
31510
+ const hashes = "#".repeat(spec.level);
31511
+ return [
31512
+ `Write each section under a \`${hashes} Title\` heading.`,
31513
+ spec.title === "unique" ? "No two sections share a title." : "",
31514
+ spec.level > 1 ? `Use deeper headings inside a section only; no heading above \`${hashes}\` after the first section.` : "",
31515
+ "Close every code fence."
31516
+ ].filter((s) => s !== "").join(" ");
31517
+ };
31518
+ NAME_RE = /^[a-z][a-z0-9-]*$/;
31519
+ pattern = (re) => new RegExp(re.source, re.flags.replace(/[gy]/g, ""));
31520
+ encode = (value) => value instanceof RegExp ? {
31521
+ source: value.source,
31522
+ flags: value.flags
31523
+ } : Array.isArray(value) ? value.map(encode) : typeof value === "object" && value !== null ? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, encode(v)])) : value;
31524
+ isPattern = (value) => typeof value === "object" && value !== null && typeof value.source === "string" && typeof value.flags === "string";
31525
+ checkName = (name) => {
31526
+ if (typeof name !== "string" || !NAME_RE.test(name)) throw new Error(`format name must be lowercase letters, digits and "-" — got ${JSON.stringify(name)}`);
31527
+ return name;
31528
+ };
31529
+ withThreads = (name, spec, kind, parse, instructions, sample) => ({
31530
+ ...freeFormFormat,
31531
+ openThreads: openThreadsOf,
31532
+ name,
31533
+ spec: JSON.stringify({
31534
+ kind,
31535
+ ...encode(spec)
31536
+ }),
31537
+ instructions,
31538
+ sample,
31539
+ parse,
31540
+ validate: (content) => [...parse(content).problems, ...threadFindings(content)]
31541
+ });
31542
+ listFormat = (spec) => {
31543
+ const name = checkName(spec.name);
31544
+ const normalized = {
31545
+ ...spec,
31546
+ ...spec.section !== void 0 && typeof spec.section.title !== "string" ? { section: {
31547
+ ...spec.section,
31548
+ title: pattern(spec.section.title)
31549
+ } } : {},
31550
+ ...spec.item !== void 0 ? { item: {
31551
+ ...spec.item,
31552
+ id: pattern(spec.item.id)
31553
+ } } : {}
31554
+ };
31555
+ if ((normalized.markers ?? ["-"]).length === 0) throw new Error(`format "${name}": markers is empty`);
31556
+ if (normalized.item !== void 0 && !normalized.item.id.test(normalized.item.example)) throw new Error(`format "${name}": item.example "${normalized.item.example}" does not match item.id`);
31557
+ const section = normalized.section;
31558
+ if (section !== void 0 && typeof section.title !== "string" && (section.example === void 0 || !section.title.test(section.example))) throw new Error(`format "${name}": a RegExp section.title needs a section.example it matches`);
31559
+ const format = withThreads(name, normalized, "list", (content) => parseList(normalized, content), listInstructions(normalized), listSample(normalized));
31560
+ if (format.parse(format.sample).problems.length > 0) throw new Error(`format "${name}": its own sample does not validate`);
31561
+ return format;
31562
+ };
31563
+ sectionFormat = (spec) => {
31564
+ const name = checkName(spec.name);
31565
+ const hashes = "#".repeat(spec.level);
31566
+ return withThreads(name, spec, "section", (content) => parseSections(spec, content), sectionInstructions(spec), `${hashes} First section\n\nWhat this section says.\n`);
31567
+ };
31568
+ decodePatterns = (value, key) => (key === "title" || key === "id") && isPattern(value) ? new RegExp(value.source, value.flags) : Array.isArray(value) ? value.map((v) => decodePatterns(v)) : typeof value === "object" && value !== null ? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, decodePatterns(v, k)])) : value;
31569
+ formatFromSpec = (spec) => {
31570
+ const { kind, ...rest } = decodePatterns(JSON.parse(spec));
31571
+ if (kind === "list") return listFormat(rest);
31572
+ if (kind === "section") return sectionFormat(rest);
31573
+ throw new Error(`not a declared format spec: ${JSON.stringify(kind)}`);
31574
+ };
31575
+ }));
31576
+ //#endregion
31160
31577
  //#region src/steering/registry.ts
31161
31578
  var REGISTRY, BUILT_IN_MODE_NAMES, steeringFormatFor, steeringFormatOrFreeForm, unansweredQuestions, viewOf, openThreads;
31162
31579
  var init_registry = __esmMin((() => {
@@ -31385,6 +31802,7 @@ var init_steering = __esmMin((() => {
31385
31802
  init_review();
31386
31803
  init_Edits();
31387
31804
  init_freeform();
31805
+ init_Declared();
31388
31806
  init_registry();
31389
31807
  init_qa();
31390
31808
  init_Footnotes();
@@ -32729,6 +33147,8 @@ const replay = async (raw) => {
32729
33147
  const seededValidateCommand = (mode) => `gtd check ${mode} "$GTD_FILE"`;
32730
33148
  /** True when `command` is exactly `mode`'s seeded template (compared literally, not rendered) — distinguishes gtd's own seeding from a user override. */
32731
33149
  const isSeededValidateCommand = (mode, command) => command === seededValidateCommand(mode);
33150
+ /** The `validate:` command for a declared format: its spec travels on the command line, so `gtd check` loads no workflow to find it. */
33151
+ const declaredValidateCommand = (mode, spec) => `gtd check ${mode} "$GTD_FILE" --spec '${spec.replace(/'/g, "'\\''")}'`;
32732
33152
  //#endregion
32733
33153
  //#region src/steering/internal.ts
32734
33154
  var init_internal = __esmMin((() => {
@@ -32736,6 +33156,7 @@ var init_internal = __esmMin((() => {
32736
33156
  init_Footnotes();
32737
33157
  init_MarkdownTree();
32738
33158
  init_registry();
33159
+ init_Declared();
32739
33160
  }));
32740
33161
  //#endregion
32741
33162
  //#region src/workflow/compile.ts
@@ -34147,7 +34568,7 @@ const modeContradictionSkipNotice = (mode) => {
34147
34568
  //#endregion
34148
34569
  //#region src/emit/SteeringMode.ts
34149
34570
  init_dist$1();
34150
- init_steering();
34571
+ init_internal();
34151
34572
  /**
34152
34573
  * `format` alone (no live/external verdict) when there's no validator at
34153
34574
  * all; a live in-process validator for a `"builtin"` validate OR a command
@@ -34187,8 +34608,27 @@ const capabilitiesFor = (mode, format, validate) => {
34187
34608
  * The `"unknown"` arm carries the whole refusal message, naming what IS known
34188
34609
  * when `def` is given — this is the one place that message is built, so it
34189
34610
  * can never drift from what actually resolved.
34611
+ *
34612
+ * `spec` is a declared format's: it alone decides, validating through
34613
+ * `gtd check --spec` with no `format:` half.
34190
34614
  */
34191
- const resolveMode = (def, state, mode) => {
34615
+ const resolveMode = (def, state, mode, spec) => {
34616
+ if (spec !== void 0) {
34617
+ const format = formatFromSpec(spec);
34618
+ return {
34619
+ kind: "resolved",
34620
+ mode,
34621
+ format,
34622
+ validate: {
34623
+ kind: "command",
34624
+ command: declaredValidateCommand(mode, spec)
34625
+ },
34626
+ capabilities: {
34627
+ format,
34628
+ liveValidate: format.validate
34629
+ }
34630
+ };
34631
+ }
34192
34632
  const declared = def?.modes?.[mode];
34193
34633
  const format = steeringFormatFor(mode);
34194
34634
  if (declared === void 0 && format === void 0) return {
@@ -34859,7 +35299,10 @@ const commonOf = (step) => {
34859
35299
  actor: step.actor,
34860
35300
  ...optional("label", options.label),
34861
35301
  ...optional("file", options.file),
34862
- ...optional("mode", options.mode)
35302
+ ...typeof options.mode === "object" ? {
35303
+ mode: options.mode.name,
35304
+ modeSpec: options.mode.spec
35305
+ } : optional("mode", options.mode)
34863
35306
  };
34864
35307
  };
34865
35308
  const promptDef = (common, request, skills, access) => ({
@@ -34935,6 +35378,13 @@ const hintsOf = (def) => ({
34935
35378
  ...optional("skills", def.skills),
34936
35379
  ...optional("access", def.access)
34937
35380
  });
35381
+ /** A step's mode is a name the workflow knows, or a declared format named apart from all of them. */
35382
+ const modeFaultOf = (def, stepName, stepDef) => {
35383
+ if (stepDef.mode === void 0) return void 0;
35384
+ const known = def.modes[stepDef.mode] !== void 0;
35385
+ if (stepDef.modeSpec !== void 0) return known ? `gtd: config: step "${stepName}": declared format "${stepDef.mode}" has the name of a mode this workflow already knows — rename the format` : void 0;
35386
+ return known ? void 0 : `gtd: config: step "${stepName}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`;
35387
+ };
34938
35388
  const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
34939
35389
  /** Per-model token totals, highest-cost first (ties broken by model name). */
34940
35390
  const costByModel = (entries) => {
@@ -35026,7 +35476,8 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
35026
35476
  const { reached, step } = resting;
35027
35477
  const { stepDef, state, memory } = restFacts(pin, step, reached.trace, run);
35028
35478
  yield* (yield* Narrator).narrate(`rest resolved: ${state} (awaits ${stepDef.actor})`);
35029
- if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$8(/* @__PURE__ */ new Error(`gtd: config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
35479
+ const modeFault = modeFaultOf(def, step.name, stepDef);
35480
+ if (modeFault !== void 0) return yield* fail$8(new Error(modeFault));
35030
35481
  const changes = ref === void 0 ? (yield* pendingChanges(git, stepDef.approve === true)).map((e) => ({
35031
35482
  status: normalizeStatus(e.status),
35032
35483
  path: e.path
@@ -36644,8 +37095,8 @@ const runRepinCommand = (json, out) => gen(function* () {
36644
37095
  * `gtd check <mode>` invocation instead, so there's always something concrete
36645
37096
  * to name.
36646
37097
  */
36647
- const selfValidateCommand = (def, state, mode, file) => {
36648
- const resolved = resolveMode(def, state, mode);
37098
+ const selfValidateCommand = (def, state, mode, file, spec) => {
37099
+ const resolved = resolveMode(def, state, mode, spec);
36649
37100
  const validate = resolved.kind === "resolved" ? resolved.validate : void 0;
36650
37101
  return commandForFile(validate?.kind === "command" ? validate.command : seededValidateCommand(mode), file);
36651
37102
  };
@@ -36663,7 +37114,7 @@ const resolveValidateScript = (rest) => gen(function* () {
36663
37114
  const file = rest.hints.file;
36664
37115
  const mode = rest.stepDef.mode;
36665
37116
  if (file === void 0 || mode === void 0) return void 0;
36666
- const resolved = resolveMode(rest.def, rest.state, mode);
37117
+ const resolved = resolveMode(rest.def, rest.state, mode, rest.stepDef.modeSpec);
36667
37118
  if (resolved.kind === "unknown") return yield* fail$8(new Error(resolved.message));
36668
37119
  const { script } = yield* validateScriptFor(resolved, file);
36669
37120
  return {
@@ -36694,7 +37145,7 @@ const runNextCommand = (json, out) => gen(function* () {
36694
37145
  if (json.kind === "document") out.write(renderBeatJson(fields));
36695
37146
  else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
36696
37147
  else {
36697
- const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file) : void 0;
37148
+ const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file, rest.stepDef.modeSpec) : void 0;
36698
37149
  if (DEMAND_BRIEFING[fields.kind].suppressHeader) {
36699
37150
  const shown = renderBeatPlain({
36700
37151
  ...fields,
@@ -36721,8 +37172,8 @@ const runValidateCommand = (json, out) => gen(function* () {
36721
37172
  * Findings ride the failing Effect's message, so the shared refusal path
36722
37173
  * reports them on stderr.
36723
37174
  */
36724
- const runCheckCommand = (mode, file, openQuestions, openThreadsFlag) => gen(function* () {
36725
- const format = steeringFormatFor(mode);
37175
+ const runCheckCommand = (mode, file, openQuestions, openThreadsFlag, spec) => gen(function* () {
37176
+ const format = spec !== void 0 ? yield* declaredFormat(spec) : steeringFormatFor(mode);
36726
37177
  if (format === void 0) return yield* fail$8(new GtdUsageError(`gtd check: unknown mode "${mode}" — known modes: ${BUILT_IN_MODE_NAMES.join(", ")}`));
36727
37178
  if (openThreadsFlag) {
36728
37179
  if (openQuestions) return yield* fail$8(/* @__PURE__ */ new Error("gtd check: --open-threads and --open-questions are mutually exclusive"));
@@ -36741,6 +37192,10 @@ const runCheckCommand = (mode, file, openQuestions, openThreadsFlag) => gen(func
36741
37192
  if (errors.length === 0) return;
36742
37193
  return yield* fail$8(/* @__PURE__ */ new Error(`gtd check: ${file} is not valid under mode "${mode}" (${errors.length} finding(s)):\n` + errors.join("\n")));
36743
37194
  });
37195
+ const declaredFormat = (spec) => try_({
37196
+ try: () => formatFromSpec(spec),
37197
+ catch: (error) => new GtdUsageError(`gtd check: --spec is not a declared format: ${error.message}`)
37198
+ });
36744
37199
  const existingFile = (file) => flatMap(Workspace, (workspace) => {
36745
37200
  const content = workspace.atPath(file);
36746
37201
  return content === void 0 ? fail$8(/* @__PURE__ */ new Error(`gtd check: no such file: ${file}`)) : succeed$2(content);
@@ -36910,7 +37365,7 @@ const dispatchVoidCommand = (command, json, out) => {
36910
37365
  case "validate": return runValidateCommand(json, out);
36911
37366
  case "check":
36912
37367
  if (command.mode === void 0 || command.file === void 0) return runCodeThreadsCheckCommand();
36913
- return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false);
37368
+ return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false, command.spec);
36914
37369
  case "uncheck": return runUncheckCommand(command.file);
36915
37370
  case "guide": return runGuideCommand(out);
36916
37371
  case "bundle": return runBundleCommand(out);
@@ -37206,6 +37661,16 @@ const FLAGS = [
37206
37661
  valueHint: "",
37207
37662
  help: "Instead of <mode>'s findings, list the open threads in <file>; exits 1 when any are open. Alone, lists the open code-comment threads in the files the current process changed"
37208
37663
  },
37664
+ {
37665
+ name: "--spec",
37666
+ arity: 1,
37667
+ repeatable: false,
37668
+ scope: (kind) => kind === "check",
37669
+ decode: ([raw]) => succeed$6(raw ?? ""),
37670
+ scopeError: "gtd: --spec is only valid for `gtd check`",
37671
+ valueHint: "<json>",
37672
+ help: "Check against the declared format this spec describes, named <mode>, instead of a built-in one; validation scripts pass it for a step whose mode is a declared format"
37673
+ },
37209
37674
  {
37210
37675
  name: "--verbose",
37211
37676
  arity: 0,
@@ -37451,7 +37916,7 @@ The fixed answerer reads --answers and leaves out the questions it does not cove
37451
37916
  arity: { names: ["mode", "file"] },
37452
37917
  group: "Driver plumbing",
37453
37918
  summary: "Check a steering file's format",
37454
- details: `Run the steering format <mode> (qa, review or findings) over <file> and print each finding on a line of its own; exits 1 when there are any, or when <file> does not exist. An unknown <mode> is a usage error (exit 2). Reads no workflow and runs from any directory — emitted validation scripts call it.`
37919
+ details: `Run the steering format <mode> (qa, review or findings, or the declared format --spec describes) over <file> and print each finding on a line of its own; exits 1 when there are any, or when <file> does not exist. An unknown <mode> is a usage error (exit 2). Reads no workflow and runs from any directory — emitted validation scripts call it.`
37455
37920
  },
37456
37921
  {
37457
37922
  token: "uncheck",
@@ -37825,7 +38290,8 @@ const parseArgv = (argv) => {
37825
38290
  ...restPositionals[0] !== void 0 ? { mode: restPositionals[0] } : {},
37826
38291
  ...restPositionals[1] !== void 0 ? { file: restPositionals[1] } : {},
37827
38292
  ...bag["--open-questions"] !== void 0 ? { openQuestions: bag["--open-questions"] } : {},
37828
- ...bag["--open-threads"] !== void 0 ? { openThreads: bag["--open-threads"] } : {}
38293
+ ...bag["--open-threads"] !== void 0 ? { openThreads: bag["--open-threads"] } : {},
38294
+ ...bag["--spec"] !== void 0 ? { spec: bag["--spec"] } : {}
37829
38295
  },
37830
38296
  json,
37831
38297
  verbose