planrails 0.2.0 → 0.2.1
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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/tools/check-plans.mjs +40 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
A careful post-release review, including an end-to-end test of the published
|
|
6
|
+
package, found one robustness gap in the checker and fixed it.
|
|
7
|
+
|
|
8
|
+
- The checker finds a task table by its columns (`id`, `status`, `proof`,
|
|
9
|
+
`evidence`), not by a `## Tasks` heading. So tasks under `## Phase 2 Tasks`,
|
|
10
|
+
`## Backlog`, any heading, or none at all are all gated — a phased plan is no
|
|
11
|
+
longer rejected with a confusing "no readable Tasks table". A table under a
|
|
12
|
+
literal `## Tasks` heading that is missing a column still names which one.
|
|
13
|
+
- Verified against the live 0.2.0 package: `npx planrails init` copies only into
|
|
14
|
+
`.project-management/`, leaves `package.json` and a pnpm lockfile untouched,
|
|
15
|
+
writes no `node_modules` and no `CLAUDE.md`, and the checker catches every
|
|
16
|
+
faked-"done" bypass. 33 tests.
|
|
17
|
+
|
|
3
18
|
## 0.2.0 — 2026-09-12
|
|
4
19
|
|
|
5
20
|
Rebuilt as a planner prompt and one checker, with a two-command CLI that only
|
package/package.json
CHANGED
package/tools/check-plans.mjs
CHANGED
|
@@ -80,41 +80,55 @@ const isRow = (l) => l.trim().startsWith("|");
|
|
|
80
80
|
const isHeading = (l) => /^#{1,6}\s/.test(l.trim());
|
|
81
81
|
const isSeparator = (l) => /^\|[\s:|-]+\|?\s*$/.test(l.trim());
|
|
82
82
|
|
|
83
|
+
const REQUIRED = ["id", "status", "proof", "evidence"];
|
|
84
|
+
/** The column indices of a row, and which required columns it is missing. */
|
|
85
|
+
function headerCols(rowText) {
|
|
86
|
+
const h = cells(rowText).map((c) => c.toLowerCase());
|
|
87
|
+
const ci = { id: h.indexOf("id"), task: h.indexOf("task"), status: h.indexOf("status"), proof: h.indexOf("proof"), evidence: h.indexOf("evidence") };
|
|
88
|
+
return { ci, missing: REQUIRED.filter((k) => ci[k] === -1) };
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
/**
|
|
84
|
-
* Parse the task rows of a plan.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* Parse the task rows of a plan. A "task table" is any markdown table whose
|
|
93
|
+
* header carries the four columns id, status, proof, evidence — found by its
|
|
94
|
+
* columns, not by a heading, so tasks under "## Tasks", "## Phase 2 Tasks",
|
|
95
|
+
* "## Backlog", or a second table are all read; blank lines inside a table are
|
|
96
|
+
* tolerated. Returns { found, tasks, missingCols }.
|
|
88
97
|
*/
|
|
89
98
|
export function parseTasks(text) {
|
|
90
99
|
const lines = text.split(/\r?\n/);
|
|
91
|
-
let found = false;
|
|
92
100
|
const tasks = [];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
let found = false;
|
|
102
|
+
let i = 0;
|
|
103
|
+
while (i < lines.length) {
|
|
104
|
+
if (!isRow(lines[i]) || isSeparator(lines[i]) || headerCols(lines[i]).missing.length) { i++; continue; }
|
|
105
|
+
found = true; // lines[i] is a task-table header (a | row naming all four columns)
|
|
106
|
+
const { ci } = headerCols(lines[i]);
|
|
97
107
|
let j = i + 1;
|
|
98
108
|
for (; j < lines.length; j++) {
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const missing = ["id", "status", "proof", "evidence"].filter((k) => ci[k] === -1);
|
|
107
|
-
if (missing.length) { missing.forEach((m) => missingCols.add(m)); continue; }
|
|
108
|
-
found = true;
|
|
109
|
-
for (const row of rows.slice(1)) {
|
|
110
|
-
if (isSeparator(row.text)) continue;
|
|
111
|
-
const c = cells(row.text);
|
|
109
|
+
const l = lines[j];
|
|
110
|
+
if (isHeading(l)) break; // a heading ends the table
|
|
111
|
+
if (l.trim() === "") continue; // a blank line inside the table does not
|
|
112
|
+
if (!isRow(l)) break; // prose ends the table
|
|
113
|
+
if (isSeparator(l)) continue;
|
|
114
|
+
if (headerCols(l).missing.length === 0) break; // the next table's header — reprocess it
|
|
115
|
+
const c = cells(l);
|
|
112
116
|
const at = (idx) => (idx >= 0 && idx < c.length ? c[idx] : "");
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
tasks.push({ id: at(ci.id), task: at(ci.task), status: at(ci.status), proof: at(ci.proof), evidence: at(ci.evidence), line: j + 1 });
|
|
118
|
+
}
|
|
119
|
+
i = j;
|
|
120
|
+
}
|
|
121
|
+
// A helpful message for the common slip: a "## Tasks" table missing one column.
|
|
122
|
+
let missingCols = [];
|
|
123
|
+
if (!found) {
|
|
124
|
+
for (let k = 0; k < lines.length && !missingCols.length; k++) {
|
|
125
|
+
if (!/^#{1,6}\s+tasks\b/i.test(lines[k].trim())) continue;
|
|
126
|
+
for (let m = k + 1; m < lines.length && !isHeading(lines[m]); m++) {
|
|
127
|
+
if (isRow(lines[m]) && !isSeparator(lines[m])) { const miss = headerCols(lines[m]).missing; if (miss.length && miss.length < REQUIRED.length) missingCols = miss; break; }
|
|
128
|
+
}
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
|
-
return { found, tasks, missingCols
|
|
131
|
+
return { found, tasks, missingCols };
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
/** The command inside a proof cell (backticks stripped), or null for `owner`/prose/empty. */
|
|
@@ -129,7 +143,7 @@ export function checkPlan({ id, text, verify = false, run = null }) {
|
|
|
129
143
|
const { found, tasks, missingCols } = parseTasks(text);
|
|
130
144
|
if (!found) {
|
|
131
145
|
if (missingCols.length) problems.push(`${id}: the Tasks table is missing the ${missingCols.map((c) => `"${c}"`).join(", ")} column(s)`);
|
|
132
|
-
else problems.push(`${id}: no readable Tasks table (needs a | id | task | status | proof | evidence | table
|
|
146
|
+
else problems.push(`${id}: no readable Tasks table (needs a | id | task | status | proof | evidence | table)`);
|
|
133
147
|
return problems;
|
|
134
148
|
}
|
|
135
149
|
for (const t of tasks) {
|