@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -8
- package/dist/chunks/{steering-DkA51qDa.mjs → steering-Cem5i6Xl.mjs} +421 -1
- package/dist/chunks/{testkit-zGjNeQNL.mjs → testkit-DfWZQCWQ.mjs} +63 -16
- package/dist/gtd.bundle.mjs +484 -18
- package/dist/gtd.core.mjs +117 -110
- package/dist/steering.mjs +2 -2
- package/dist/testkit/gherkin.mjs +1 -1
- package/dist/testkit.mjs +1 -1
- package/dist/workflow-kit.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.ts +13 -1
- package/src/cli/Program.ts +23 -4
- package/src/edge/Edge.ts +23 -9
- package/src/emit/SteeringMode.ts +21 -1
- package/src/flows/index.ts +1 -0
- package/src/flows/runtime.test.ts +2 -1
- package/src/flows/runtime.ts +8 -2
- package/src/steering/Declared.test.ts +218 -0
- package/src/steering/Declared.ts +607 -0
- package/src/steering/FORMATS.md +3 -0
- package/src/steering/index.ts +12 -0
- package/src/steering/internal.ts +1 -0
- package/src/twins/EmittedScriptRecognizer.ts +5 -3
- package/src/twins/EmittedScripts.test.ts +19 -0
- package/src/workflow/SteeringFormats.ts +4 -0
- package/src/workflow/Workflow.ts +2 -0
- package/src/workflow/index.ts +5 -1
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|