@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +31 -20
- package/dist/chunks/{reserved-DF1HD6jL.mjs → reserved-CdOzKFVX.mjs} +150 -24
- package/dist/chunks/{review-Bv40kZu9.mjs → review-BChXEl2Y.mjs} +132 -24
- package/dist/chunks/{steering-D7hWeMgE.mjs → steering-DtUVUz9l.mjs} +49 -13
- package/dist/chunks/{tail-D89fU_wf.mjs → tail-DRM2eC1z.mjs} +1 -1
- package/dist/gtd.bundle.mjs +22 -5
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +1 -1
- package/dist/web/main.js +27 -3
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +16 -9
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +338 -62
- package/package.json +1 -1
- package/src/steering/index.ts +2 -0
- package/src/steering/review.test.ts +87 -0
- package/src/steering/review.ts +75 -8
- package/src/workflows/feature/coverage.feature +3 -3
- package/src/workflows/feature/fast-path.feature +2 -2
- package/src/workflows/feature/feature.ts +10 -3
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/lens-review.feature +128 -13
- package/src/workflows/feature/one-step-land.feature +2 -2
- package/src/workflows/feature/refactor-absorbs.feature +2 -2
- package/src/workflows/feature/refactor-first.feature +2 -2
- package/src/workflows/feature/refactor-rebuild.feature +2 -2
- package/src/workflows/feature/refactor-review.feature +6 -6
- package/src/workflows/feature/review-answers.feature +51 -5
- package/src/workflows/feature/review-gate.feature +23 -6
- package/src/workflows/feature/reviewer-walkthrough.feature +240 -0
- package/src/workflows/feature/seam-rejection.feature +2 -2
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/severity-budget.feature +111 -0
- package/src/workflows/feature/severity-sorted-review.feature +197 -0
- package/src/workflows/feature/slice-findings.feature +68 -5
- package/src/workflows/feature/slicing.feature +6 -6
- package/src/workflows/feature/standards.feature +29 -1
- package/src/workflows/shared/review.ts +226 -25
- package/src/workflows/shared/steps.ts +3 -2
- package/src/workflows/shared/text.test.ts +216 -0
- 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
|
|
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
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
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 {
|
|
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,
|
|
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: [],
|