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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.12.0-rc.4",
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
  {
@@ -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; `edge-case-criteria` AC5, M8 and M9).
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–M9, the
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
- git -C "$REPO_ROOT" rev-parse HEAD > "$MARKER_FILE" 2>/dev/null || true
10291
+ ( cd "$REPO_ROOT" && git rev-parse HEAD ) > "$MARKER_FILE" 2>/dev/null || true
10397
10292
  exit 0
10398
10293
  `;
10399
10294
  }