@pmelab/gtd 48.0.0 → 50.0.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.
Files changed (45) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +31 -20
  3. package/dist/chunks/{reserved-DF1HD6jL.mjs → reserved-CdOzKFVX.mjs} +150 -24
  4. package/dist/chunks/{review-Bv40kZu9.mjs → review-BChXEl2Y.mjs} +132 -24
  5. package/dist/chunks/{steering-D7hWeMgE.mjs → steering-DtUVUz9l.mjs} +49 -13
  6. package/dist/chunks/{tail-D89fU_wf.mjs → tail-DRM2eC1z.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +22 -5
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +1 -1
  10. package/dist/web/main.js +27 -3
  11. package/dist/workflow-kit.mjs +1 -1
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +16 -9
  14. package/dist/workflows/fix.mjs +3 -3
  15. package/dist/workflows/review.mjs +3 -3
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +338 -62
  18. package/package.json +1 -1
  19. package/src/steering/index.ts +2 -0
  20. package/src/steering/review.test.ts +87 -0
  21. package/src/steering/review.ts +75 -8
  22. package/src/workflows/feature/coverage.feature +3 -3
  23. package/src/workflows/feature/fast-path.feature +2 -2
  24. package/src/workflows/feature/feature.ts +10 -3
  25. package/src/workflows/feature/flag-mode.feature +2 -2
  26. package/src/workflows/feature/lens-review.feature +128 -13
  27. package/src/workflows/feature/one-step-land.feature +2 -2
  28. package/src/workflows/feature/refactor-absorbs.feature +2 -2
  29. package/src/workflows/feature/refactor-first.feature +2 -2
  30. package/src/workflows/feature/refactor-rebuild.feature +2 -2
  31. package/src/workflows/feature/refactor-review.feature +6 -6
  32. package/src/workflows/feature/review-answers.feature +51 -5
  33. package/src/workflows/feature/review-gate.feature +23 -6
  34. package/src/workflows/feature/reviewer-walkthrough.feature +240 -0
  35. package/src/workflows/feature/seam-rejection.feature +2 -2
  36. package/src/workflows/feature/seam.feature +2 -2
  37. package/src/workflows/feature/severity-budget.feature +111 -0
  38. package/src/workflows/feature/severity-sorted-review.feature +197 -0
  39. package/src/workflows/feature/slice-findings.feature +68 -5
  40. package/src/workflows/feature/slicing.feature +6 -6
  41. package/src/workflows/feature/standards.feature +29 -1
  42. package/src/workflows/shared/review.ts +226 -25
  43. package/src/workflows/shared/steps.ts +3 -2
  44. package/src/workflows/shared/text.test.ts +216 -0
  45. package/src/workflows/shared/text.ts +201 -19
@@ -15089,6 +15089,9 @@ const qaDescriptor = {
15089
15089
  const ANSWER_LEAD = "Tick exactly one:";
15090
15090
  const ANSWER_FIX = "fix as suggested";
15091
15091
  const ANSWER_WONT_FIX = "won't fix";
15092
+ /** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
15093
+ const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/;
15094
+ const severityLine = (label) => `_${label}_`;
15092
15095
  /**
15093
15096
  * The canonical sample. Every detail here is load-bearing for the formatter
15094
15097
  * round-trip: both footnote bodies exceed 80 characters so a reflow actually
@@ -15379,12 +15382,26 @@ const answerListOf = (content, body) => {
15379
15382
  })
15380
15383
  };
15381
15384
  };
15385
+ /** A leading severity line is the chunk's tier, never its prose. */
15386
+ const splitSeverity = (content, run) => {
15387
+ const first = run[0];
15388
+ const label = first?.type === "paragraph" ? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1] : void 0;
15389
+ if (label === void 0) return { leadingRun: run };
15390
+ return {
15391
+ severity: {
15392
+ label,
15393
+ line: toLspPosition(first.position.start).line
15394
+ },
15395
+ leadingRun: run.slice(1)
15396
+ };
15397
+ };
15382
15398
  /** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
15383
15399
  const parseChunkBody = (content, lines, title, body) => {
15384
15400
  const answer = answerListOf(content, body);
15385
15401
  const isAnswer = (n) => answer !== void 0 && (n === answer.list || n === body[answer.lead]);
15386
15402
  const firstPointerIndex = body.findIndex((n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)));
15387
- const leadingRun = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
15403
+ const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
15404
+ const { severity, leadingRun } = splitSeverity(content, run);
15388
15405
  const descriptionNodes = blockNodesOfRun(content, leadingRun);
15389
15406
  const descriptionInline = collapseImages(blockRunInline(content, leadingRun));
15390
15407
  const description = leadingRun.filter((n) => n.type !== "footnoteDefinition").map((n) => sourceText(content, n)).join(" ").trim();
@@ -15403,6 +15420,7 @@ const parseChunkBody = (content, lines, title, body) => {
15403
15420
  files,
15404
15421
  errors,
15405
15422
  ...answer === void 0 ? {} : { answers: answer.options },
15423
+ ...severity === void 0 ? {} : { severity },
15406
15424
  boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0)
15407
15425
  };
15408
15426
  };
@@ -15439,7 +15457,7 @@ const parseChangesets = (tree, content, lines) => {
15439
15457
  const errors = [];
15440
15458
  for (const { heading, headingLine, body } of splitChunks(tree)) {
15441
15459
  const title = headingText$1(content, heading);
15442
- const { description, descriptionNodes, descriptionInline, files, answers, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
15460
+ const { description, descriptionNodes, descriptionInline, files, answers, severity, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
15443
15461
  errors.push(...bodyErrors);
15444
15462
  if (files.length === 0 && boxed) errors.push({
15445
15463
  message: `Chunk "${title}" has no file pointers`,
@@ -15453,7 +15471,8 @@ const parseChangesets = (tree, content, lines) => {
15453
15471
  descriptionInline,
15454
15472
  files,
15455
15473
  headingLine,
15456
- ...answers === void 0 ? {} : { answers }
15474
+ ...answers === void 0 ? {} : { answers },
15475
+ ...severity === void 0 ? {} : { severity }
15457
15476
  });
15458
15477
  }
15459
15478
  if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
@@ -15754,6 +15773,7 @@ const reviewView = (content) => {
15754
15773
  detail: chunk.description,
15755
15774
  detailInline: chunk.descriptionInline,
15756
15775
  body: chunk.descriptionNodes,
15776
+ ...chunk.severity === void 0 ? {} : { status: chunk.severity.label },
15757
15777
  anchor: {
15758
15778
  kind: "chunk",
15759
15779
  index: chunkIndex
@@ -15901,15 +15921,19 @@ const reviewDescriptor = {
15901
15921
  openThreads: openThreadsOf
15902
15922
  };
15903
15923
  const pointerLabel = (f) => `${f.path}${f.line === void 0 ? "" : `#${f.line}${f.rangeEnd === void 0 ? "" : `-${f.rangeEnd}`}`}`;
15904
- const chunkNote = (chunk, prior) => chunk.description === (prior?.description ?? "") || prior === void 0 && chunk.answers !== void 0 ? [] : [{
15905
- line: chunk.headingLine,
15906
- note: {
15907
- kind: "chunk",
15908
- anchor: chunk.title,
15909
- before: prior?.description ?? "",
15910
- text: chunk.description
15911
- }
15912
- }];
15924
+ const chunkProse = (chunk, retiered) => [chunk?.description ?? "", retiered && chunk?.severity !== void 0 ? severityLine(chunk.severity.label) : ""].filter((p) => p !== "").join("\n\n");
15925
+ const chunkNote = (chunk, prior) => {
15926
+ const retiered = chunk.severity !== void 0 && chunk.severity.label !== prior?.severity?.label;
15927
+ return chunkProse(chunk, retiered) === chunkProse(prior, retiered) || prior === void 0 && chunk.answers !== void 0 ? [] : [{
15928
+ line: chunk.headingLine,
15929
+ note: {
15930
+ kind: "chunk",
15931
+ anchor: chunk.title,
15932
+ before: chunkProse(prior, retiered),
15933
+ text: chunkProse(chunk, retiered)
15934
+ }
15935
+ }];
15936
+ };
15913
15937
  const pointerNotes = (chunk, prior) => chunk.files.flatMap((file) => {
15914
15938
  const label = pointerLabel(file);
15915
15939
  const was = prior?.files.find((f) => pointerLabel(f) === label);
@@ -16019,6 +16043,18 @@ const reviewDecisions = (before, after) => {
16019
16043
  return old?.choice === decision.choice && old.text === decision.text ? [] : [decision];
16020
16044
  });
16021
16045
  };
16046
+ /** Each `##` chunk in document order: its title, 0-based heading line and how many hunk pointers it holds. */
16047
+ const reviewChunks = (content) => parseReviewDoc(content).changesets.map((c) => ({
16048
+ title: c.title,
16049
+ line: c.headingLine,
16050
+ pointers: c.files.length,
16051
+ ...c.severity === void 0 ? {} : { severityLine: c.severity.line },
16052
+ files: c.files.map((f) => ({
16053
+ path: f.path,
16054
+ ...f.line === void 0 ? {} : { line: f.line },
16055
+ ...f.rangeEnd === void 0 ? {} : { rangeEnd: f.rangeEnd }
16056
+ }))
16057
+ }));
16022
16058
  //#endregion
16023
16059
  //#region src/steering/Edits.ts
16024
16060
  /** Splices `edits` into `content`, sorted last-to-first so an earlier range's offset is never invalidated by a later edit. Positions are 0-based line/character over `\n`-split lines. */
@@ -16387,4 +16423,4 @@ const viewOf = (format, content) => ({
16387
16423
  /** Every OPEN thread — last entry is the agent's — as `{ name, line, firstMe }` (0-based line). `[]` for a format with no thread concept. */
16388
16424
  const openThreads = (format, content) => format.openThreads?.(content) ?? [];
16389
16425
  //#endregion
16390
- export { parseFootnotes as C, headingSectionBodies as D, getParseCount as E, headingSections as O, openThreadFindings as S, parseThreadsWithFindings as T, unansweredNotes as _, unansweredQuestions as a, FOOTNOTE_ACTION_TITLE as b, stripCodeThreads as c, ANSWER_FIX as d, ANSWER_LEAD as f, reviewRisks as g, reviewNotes as h, steeringFormatOrFreeForm as i, formatFinding as k, freeFormFormat as l, reviewDecisions as m, openThreads as n, viewOf as o, ANSWER_WONT_FIX as p, steeringFormatFor as r, parseCodeThreads as s, BUILT_IN_MODE_NAMES as t, applySteeringEdits as u, FREE_TEXT_PLACEHOLDER as v, parseThreads as w, THREAD_REPLY_ACTION_TITLE as x, isAnswered as y };
16426
+ export { headingSections as A, THREAD_REPLY_ACTION_TITLE as C, parseThreadsWithFindings as D, parseThreads as E, getParseCount as O, FOOTNOTE_ACTION_TITLE as S, parseFootnotes as T, reviewRisks as _, unansweredQuestions as a, FREE_TEXT_PLACEHOLDER as b, stripCodeThreads as c, ANSWER_FIX as d, ANSWER_LEAD as f, reviewNotes as g, reviewDecisions as h, steeringFormatOrFreeForm as i, formatFinding as j, headingSectionBodies as k, freeFormFormat as l, reviewChunks as m, openThreads as n, viewOf as o, ANSWER_WONT_FIX as p, steeringFormatFor as r, parseCodeThreads as s, BUILT_IN_MODE_NAMES as t, applySteeringEdits as u, severityLine as v, openThreadFindings as w, isAnswered as x, unansweredNotes as y };
@@ -1,6 +1,6 @@
1
1
  import { answered, head, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
2
2
  import { lensEntriesOf, lensOf, qualityLap, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { A as reviewQuality, D as fixRisks, E as fixQuality, J as handOffAwaitReviewMessage, St as withLapFindings, d as healthy, j as reviewing, k as reviewMissing, m as QUALITY, nt as qualityFindingsFile, rt as qualityTriageMessage, u as guarded, v as REVIEW, w as fixCheck, x as awaitReview } from "./reserved-DF1HD6jL.mjs";
3
+ import { A as reviewQuality, D as fixRisks, E as fixQuality, Ot as withLapFindings, Y as handOffAwaitReviewMessage, d as healthy, it as qualityTriageMessage, j as reviewing, k as reviewMissing, m as QUALITY, rt as qualityFindingsFile, u as guarded, v as REVIEW, w as fixCheck, x as awaitReview } from "./reserved-CdOzKFVX.mjs";
4
4
  //#region src/workflows/shared/lenses.ts
5
5
  const NO_FINDINGS = {
6
6
  notes: [],