halfcycle 0.12.0-rc.4 → 0.12.0-rc.6
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/dist/check-criteria.d.ts +2 -2
- package/dist/cli.js +1 -106
- package/dist/cli.js.map +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.12.0-rc.
|
|
3
|
+
"version": "0.12.0-rc.6",
|
|
4
4
|
"description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
package/dist/check-criteria.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `npx halfcycle@latest check-criteria [<record file>] [--repo <root>]`
|
|
3
|
-
* (`whole-phase-criteria-check` AC4, M1–M7
|
|
3
|
+
* (`whole-phase-criteria-check` AC4, M1–M7).
|
|
4
4
|
*
|
|
5
5
|
* OFFLINE, ALWAYS. This verb reads only files on the person's own machine — the
|
|
6
6
|
* plan file `docs/build-records/task-plan.json`, each spec file the plan lists in
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ONE HOME FOR EACH THING IT READS. The plan is parsed with `taskPlanSchema` through
|
|
12
12
|
* `check-tasks.ts`'s `readJsonFile`, as `check-tasks` and `ready` read it; the spec
|
|
13
13
|
* files are opened only by `readPlanSpecFiles`; the record only by
|
|
14
|
-
* `readCriteriaRecordFile`, through the wire's own schema for it. The rules M1–
|
|
14
|
+
* `readCriteriaRecordFile`, through the wire's own schema for it. The rules M1–M7, the
|
|
15
15
|
* clear line and every line's words are `checkCriteriaRecord` (`@halfcycle/core`),
|
|
16
16
|
* which is pure. This module's whole job is reading the files, handing them over,
|
|
17
17
|
* and turning the answer into an exit code: 0 clean, 1 any line, 2 a usage error or a
|
package/dist/cli.js
CHANGED
|
@@ -790,8 +790,6 @@ var taskPlanLineText = {
|
|
|
790
790
|
V5: (c, m, b) => `"${c}" checks the finished phase, but ${m} also holds "${b}", which builds part of it. Give "${c}" to a crew member who holds no build task in this phase.`,
|
|
791
791
|
V6Walk: (c, n, ids, w) => `${CRITERIA_RECORD_FILE} says "${c}" turned ${n} criteria into fix tasks: ${ids.join(", ")}. "${w}" must wait for a new check that waits for each of them: add a task of kind "check" with "criteriaCheck": true that waits for ${ids.join(", ")}, and add it to the dependsOn of "${w}".`,
|
|
792
792
|
V6Missing: (x) => `${CRITERIA_RECORD_FILE} names "${x}" as a fix task, and the plan has no build task "${x}". Add it, citing the criterion it fixes.`,
|
|
793
|
-
E1: (crit) => `${crit} is an edge case, and no task writes its test before it is built. Add a build task, held by a crew member who builds none of ${crit}, that writes the test and names ${crit} in its criteria, and make each task that builds ${crit} wait for it.`,
|
|
794
|
-
E2: (t, crit, m, b) => `"${t}" writes the test for ${crit}, an edge case, but ${m} also holds "${b}", which builds it. Give "${t}" to a crew member who builds none of ${crit}.`,
|
|
795
793
|
J1: (a) => `"${a}" says "cheapFirst", but it is not a build task. Remove "cheapFirst".`,
|
|
796
794
|
J2: (a, m) => `"${a}" says "cheapFirst", but ${m} is not on the standard tier. Only a standard-tier task can start on the cheaper tier. Remove "cheapFirst" or give "${a}" to a standard-tier crew member.`,
|
|
797
795
|
P: () => "The plan is clear: no two tasks that could run at once change the same thing, and every check is in place."
|
|
@@ -1148,10 +1146,6 @@ function liveCriterionNumbers(criteria) {
|
|
|
1148
1146
|
const live = new Set(criteria.filter((c) => !c.retired).map((c) => c.number));
|
|
1149
1147
|
return distinct(criteria.map((c) => c.number)).filter((n) => live.has(n));
|
|
1150
1148
|
}
|
|
1151
|
-
function liveEdgeCriterionNumbers(criteria) {
|
|
1152
|
-
const edge = new Set(criteria.filter((c) => !c.retired && c.kind === "edge").map((c) => c.number));
|
|
1153
|
-
return liveCriterionNumbers(criteria).filter((n) => edge.has(n));
|
|
1154
|
-
}
|
|
1155
1149
|
function specLines(plan, specFiles) {
|
|
1156
1150
|
const folder2 = specFiles.folder;
|
|
1157
1151
|
if (plan.specs === TASK_PLAN_SPECS_NONE)
|
|
@@ -1534,57 +1528,6 @@ function criteriaCheckLines(tasks, before, record2) {
|
|
|
1534
1528
|
}
|
|
1535
1529
|
return lines;
|
|
1536
1530
|
}
|
|
1537
|
-
function edgeCriteriaAt(plan, specFiles, before) {
|
|
1538
|
-
const criteriaOf = specCriteriaBySlug(specFiles);
|
|
1539
|
-
const out = [];
|
|
1540
|
-
for (const slug of planSpecNamesToRead(plan)) {
|
|
1541
|
-
const criteria = criteriaOf.get(slug) ?? null;
|
|
1542
|
-
if (criteria === null)
|
|
1543
|
-
continue;
|
|
1544
|
-
for (const n of liveEdgeCriterionNumbers(criteria)) {
|
|
1545
|
-
const criterion = `${slug} AC${n}`;
|
|
1546
|
-
const builders = [];
|
|
1547
|
-
plan.tasks.forEach((task, i) => {
|
|
1548
|
-
if (task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(criterion)) {
|
|
1549
|
-
builders.push(i);
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
const testTask = builders.length < 2 ? void 0 : builders.find((t) => builders.every((b) => b === t || hasBit(before[b], t)));
|
|
1553
|
-
out.push({ criterion, slug, builders, testTask: testTask ?? null });
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
return out;
|
|
1557
|
-
}
|
|
1558
|
-
function edgeCriterionTests(plan, specFiles) {
|
|
1559
|
-
const { before } = waitsFor(plan.tasks);
|
|
1560
|
-
return edgeCriteriaAt(plan, specFiles, before).map((edge) => ({
|
|
1561
|
-
criterion: edge.criterion,
|
|
1562
|
-
slug: edge.slug,
|
|
1563
|
-
builders: edge.builders.map((i) => plan.tasks[i].id),
|
|
1564
|
-
testTask: edge.testTask === null ? null : plan.tasks[edge.testTask].id
|
|
1565
|
-
}));
|
|
1566
|
-
}
|
|
1567
|
-
function edgeCaseLines(plan, specFiles, before) {
|
|
1568
|
-
const tasks = plan.tasks;
|
|
1569
|
-
const lines = [];
|
|
1570
|
-
for (const edge of edgeCriteriaAt(plan, specFiles, before)) {
|
|
1571
|
-
if (edge.builders.length === 0)
|
|
1572
|
-
continue;
|
|
1573
|
-
if (edge.testTask === null) {
|
|
1574
|
-
lines.push(refusal("E1", taskPlanLineText.E1(edge.criterion)));
|
|
1575
|
-
continue;
|
|
1576
|
-
}
|
|
1577
|
-
const t = edge.testTask;
|
|
1578
|
-
const holder = tasks[t].agent;
|
|
1579
|
-
if (holder === void 0)
|
|
1580
|
-
continue;
|
|
1581
|
-
const b = edge.builders.find((p) => p !== t && tasks[p].agent === holder);
|
|
1582
|
-
if (b !== void 0) {
|
|
1583
|
-
lines.push(refusal("E2", taskPlanLineText.E2(tasks[t].id, edge.criterion, holder, tasks[b].id)));
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
return lines;
|
|
1587
|
-
}
|
|
1588
1531
|
function phasePlanUnusableWhy(unusable, asked) {
|
|
1589
1532
|
switch (unusable.kind) {
|
|
1590
1533
|
case "unreadable":
|
|
@@ -1732,7 +1675,6 @@ function checkTaskPlan(plan, request) {
|
|
|
1732
1675
|
lines.push(...ownershipNotes(plan, request.specFiles));
|
|
1733
1676
|
lines.push(...placementLines(tasks, before));
|
|
1734
1677
|
lines.push(...criteriaCheckLines(tasks, before, request.criteriaRecord));
|
|
1735
|
-
lines.push(...edgeCaseLines(plan, request.specFiles, before));
|
|
1736
1678
|
const refused3 = lines.some((line) => line.refuses);
|
|
1737
1679
|
if (!refused3)
|
|
1738
1680
|
lines.push({ code: "P", text: taskPlanLineText.P(), refuses: false });
|
|
@@ -1748,10 +1690,6 @@ var criteriaRecordLineText = {
|
|
|
1748
1690
|
M5: (file, x, folder2) => `${file} has a row for "${x}", which is not a live criterion of this phase's features in ${folder2}. Remove it, or correct it.`,
|
|
1749
1691
|
M6: (crit) => `${crit} has more than one row. Keep one.`,
|
|
1750
1692
|
M7: (crit, x) => `${crit} is fixed by "${x}", but the plan has no build task "${x}" that names ${crit} in its criteria. Add the task, or name it there.`,
|
|
1751
|
-
M8Unmarked: (crit, folder2, slug) => `${crit} is not marked an edge case in ${folder2}/${slug}.md, so its row does not carry "edge".`,
|
|
1752
|
-
M8Marked: (crit, folder2, slug) => `${crit} is marked an edge case in ${folder2}/${slug}.md, so its row carries "edge": true.`,
|
|
1753
|
-
M9Evidence: (crit, t) => `${crit} is an edge case, so it is met only by the test written in "${t}", run on a task that builds it. Give its evidence as a test with "writtenIn": "${t}" and the task that built it.`,
|
|
1754
|
-
M9NoTestTask: (crit) => `${crit} is an edge case, and the plan has no task that writes its test, so it cannot be counted as met. Run check-tasks and add that task.`,
|
|
1755
1693
|
P: (met, fix) => `The record covers every criterion of this phase's features: ${met} met, ${fix} to fix.`
|
|
1756
1694
|
};
|
|
1757
1695
|
function criteriaRecordMismatch(record2, plan) {
|
|
@@ -1773,45 +1711,9 @@ function liveCriteria(specFiles) {
|
|
|
1773
1711
|
}
|
|
1774
1712
|
return out;
|
|
1775
1713
|
}
|
|
1776
|
-
function liveEdgeCriteria(specFiles) {
|
|
1777
|
-
const out = /* @__PURE__ */ new Set();
|
|
1778
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1779
|
-
for (const entry of specFiles.entries) {
|
|
1780
|
-
if (entry.criteria === null || seen.has(entry.slug))
|
|
1781
|
-
continue;
|
|
1782
|
-
seen.add(entry.slug);
|
|
1783
|
-
for (const n of liveEdgeCriterionNumbers(entry.criteria))
|
|
1784
|
-
out.add(`${entry.slug} AC${n}`);
|
|
1785
|
-
}
|
|
1786
|
-
return out;
|
|
1787
|
-
}
|
|
1788
|
-
function slugOf(criterion) {
|
|
1789
|
-
return criterion.slice(0, criterion.lastIndexOf(" AC"));
|
|
1790
|
-
}
|
|
1791
1714
|
function refusal2(code, text) {
|
|
1792
1715
|
return { code, text, refuses: true };
|
|
1793
1716
|
}
|
|
1794
|
-
function edgeRowLines(row, isEdge, testTask, plan, folder2) {
|
|
1795
|
-
const crit = row.criterion;
|
|
1796
|
-
const lines = [];
|
|
1797
|
-
const marked = row.edge === true;
|
|
1798
|
-
if (marked && !isEdge)
|
|
1799
|
-
lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder2, slugOf(crit))));
|
|
1800
|
-
if (!marked && isEdge)
|
|
1801
|
-
lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(crit, folder2, slugOf(crit))));
|
|
1802
|
-
if (!isEdge || row.status !== "met")
|
|
1803
|
-
return lines;
|
|
1804
|
-
if (testTask === null) {
|
|
1805
|
-
lines.push(refusal2("M9", criteriaRecordLineText.M9NoTestTask(crit)));
|
|
1806
|
-
return lines;
|
|
1807
|
-
}
|
|
1808
|
-
const evidence = row.evidence;
|
|
1809
|
-
const builtBy = (id) => id !== testTask && plan.tasks.some((task) => task.id === id && task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(crit));
|
|
1810
|
-
const held = evidence.kind === "test" && evidence.writtenIn === testTask && builtBy(evidence.task);
|
|
1811
|
-
if (!held)
|
|
1812
|
-
lines.push(refusal2("M9", criteriaRecordLineText.M9Evidence(crit, testTask)));
|
|
1813
|
-
return lines;
|
|
1814
|
-
}
|
|
1815
1717
|
function checkCriteriaRecord(record2, plan, request) {
|
|
1816
1718
|
const { file, specFiles } = request;
|
|
1817
1719
|
let met = 0;
|
|
@@ -1846,12 +1748,6 @@ function checkCriteriaRecord(record2, plan, request) {
|
|
|
1846
1748
|
}
|
|
1847
1749
|
const live = liveCriteria(specFiles);
|
|
1848
1750
|
const liveSet = new Set(live);
|
|
1849
|
-
const edges = liveEdgeCriteria(specFiles);
|
|
1850
|
-
const testTaskOf = /* @__PURE__ */ new Map();
|
|
1851
|
-
for (const edge of edgeCriterionTests(plan, specFiles)) {
|
|
1852
|
-
if (!testTaskOf.has(edge.criterion))
|
|
1853
|
-
testTaskOf.set(edge.criterion, edge.testTask);
|
|
1854
|
-
}
|
|
1855
1751
|
const named2 = /* @__PURE__ */ new Set();
|
|
1856
1752
|
const told = /* @__PURE__ */ new Set();
|
|
1857
1753
|
for (const row of record2.rows) {
|
|
@@ -1869,7 +1765,6 @@ function checkCriteriaRecord(record2, plan, request) {
|
|
|
1869
1765
|
told.add(crit);
|
|
1870
1766
|
}
|
|
1871
1767
|
named2.add(crit);
|
|
1872
|
-
lines.push(...edgeRowLines(row, edges.has(crit), testTaskOf.get(crit) ?? null, plan, specFiles.folder));
|
|
1873
1768
|
}
|
|
1874
1769
|
for (const row of record2.rows) {
|
|
1875
1770
|
if (row.status !== "fix" || !liveSet.has(row.criterion))
|
|
@@ -10393,7 +10288,7 @@ if [ -n "$SESSION_ID" ]; then
|
|
|
10393
10288
|
else
|
|
10394
10289
|
MARKER_FILE="\${MARKER_DIR}/halfcycle-session-\${HASH}.ref"
|
|
10395
10290
|
fi
|
|
10396
|
-
|
|
10291
|
+
( cd "$REPO_ROOT" && git rev-parse HEAD ) > "$MARKER_FILE" 2>/dev/null || true
|
|
10397
10292
|
exit 0
|
|
10398
10293
|
`;
|
|
10399
10294
|
}
|