@codyswann/lisa 4.11.2 → 4.11.3
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/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +16 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/sync/lifecycle-defaults.d.ts +1 -0
- package/dist/sync/lifecycle-defaults.d.ts.map +1 -1
- package/dist/sync/lifecycle-defaults.js +1 -0
- package/dist/sync/lifecycle-defaults.js.map +1 -1
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +5 -2
- package/dist/sync/registry.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-sync/SKILL.md +1 -1
- package/plugins/lisa/rules/eager/leaf-only-lifecycle.md +11 -0
- package/plugins/lisa/rules/reference/config-resolution.md +7 -1
- package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +21 -0
- package/plugins/lisa/scripts/rollup-blocker-classification.mjs +592 -0
- package/plugins/lisa/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/lisa/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/lisa/skills/lisa-tracker-sync/SKILL.md +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/rollup-blocker-classification.mjs +592 -0
- package/plugins/lisa-agy/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/lisa-agy/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/lisa-agy/skills/lisa-tracker-sync/SKILL.md +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/eager/leaf-only-lifecycle.md +11 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +7 -1
- package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +21 -0
- package/plugins/lisa-copilot/scripts/rollup-blocker-classification.mjs +592 -0
- package/plugins/lisa-copilot/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/lisa-copilot/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/lisa-copilot/skills/lisa-tracker-sync/SKILL.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +7 -1
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +21 -0
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle.mdc +11 -0
- package/plugins/lisa-cursor/scripts/rollup-blocker-classification.mjs +592 -0
- package/plugins/lisa-cursor/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/lisa-cursor/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/lisa-cursor/skills/lisa-tracker-sync/SKILL.md +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/eager/leaf-only-lifecycle.md +11 -0
- package/plugins/src/base/rules/reference/config-resolution.md +7 -1
- package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +21 -0
- package/plugins/src/base/scripts/rollup-blocker-classification.mjs +592 -0
- package/plugins/src/base/skills/lisa-github-sync/SKILL.md +8 -2
- package/plugins/src/base/skills/lisa-jira-sync/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +31 -1
- package/plugins/src/base/skills/lisa-tracker-sync/SKILL.md +1 -1
|
@@ -687,11 +687,41 @@ left in a status it should not carry, including a stale build-ready `ready`).
|
|
|
687
687
|
them (e.g. all `On Stg` → `On Stg`); else any child started → `claimed`; else unchanged.
|
|
688
688
|
Optional / won't-do / not-planned children are terminal-but-dropped and do not hold the parent
|
|
689
689
|
open.
|
|
690
|
+
2a. **When the derived state is `blocked`, classify the holds before writing anything.** A bare
|
|
691
|
+
`blocked` on a container is a single bit, and it cannot tell a hold that waits on an external
|
|
692
|
+
event from one that waits on a person rewriting an acceptance criterion — the second never
|
|
693
|
+
clears on its own, so rendering them alike lets it accumulate silently (`leaf-only-lifecycle` →
|
|
694
|
+
**Classifying a hold**; #3045). Feed the resolved child graph to the shared classifier:
|
|
695
|
+
|
|
696
|
+
```bash
|
|
697
|
+
ROLLUP_DIR="$(mktemp -d)"
|
|
698
|
+
# children[]: { ref, state, labels[], blockedBy[{ref, open}], children[] } — the graph
|
|
699
|
+
# step 1 already resolved, nested as deep as it was read. The script probes nothing.
|
|
700
|
+
jq -n --slurpfile g "$ROLLUP_DIR/graph.json" \
|
|
701
|
+
'{container: $g[0].container, children: $g[0].children, readError: $g[0].readError}' \
|
|
702
|
+
> "$ROLLUP_DIR/input.json"
|
|
703
|
+
|
|
704
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
|
|
705
|
+
--input="$ROLLUP_DIR/input.json" --since="<last recorded fingerprint>"
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
It **exits non-zero and classifies nothing** when the tracker could not be read, the container
|
|
709
|
+
has no children, or no child was readable. That is a repair failure to report, never an
|
|
710
|
+
all-clear: do not fall through to "no blocked children" on a non-zero exit. On success its
|
|
711
|
+
report names, per class, the blocking leaf, the path to it, and **who must act** — those lines
|
|
712
|
+
are what goes in the rollup note, verbatim. Never set the `spec_defect` marker yourself and
|
|
713
|
+
never infer a class from prose; a hold with nothing recorded classifies `unknown`, and the
|
|
714
|
+
report already asks a person to decide.
|
|
715
|
+
|
|
690
716
|
3. **If the derived state differs from the parent's current state, apply it** via the vendor's
|
|
691
717
|
lifecycle write (JIRA transition, GitHub/Linear label swap keeping exactly one `status:*`),
|
|
692
718
|
removing any conflicting stale build lifecycle role — **including a stale `ready`** the parent
|
|
693
719
|
should never carry. Post an idempotent `[lisa-repair-intake]` rollup note naming the derived
|
|
694
|
-
state and the child tally (honor the backoff window + fingerprint).
|
|
720
|
+
state and the child tally (honor the backoff window + fingerprint). **When the derived state is
|
|
721
|
+
`blocked`, the note carries the classifier's per-class report** — the blocking leaf, its path,
|
|
722
|
+
and its actor — so an operator never descends the tree by hand to find out which item and which
|
|
723
|
+
kind. Use the classifier's `change.summary` as the dedupe test: an unchanged verdict has nothing
|
|
724
|
+
new to say and the note is **not** re-posted.
|
|
695
725
|
4. **Perform native closure only at the true terminal `done`.** When — and only when — the derived
|
|
696
726
|
env is the production/terminal value, finalize through the provider-native mechanism (GitHub
|
|
697
727
|
`gh issue close --reason completed`, Linear move to Done state, JIRA resolved/closed verified at
|
|
@@ -38,7 +38,7 @@ The state machine (first match wins, evaluated over the **required** leaves only
|
|
|
38
38
|
| else any leaf has **started** (claimed or in review, or shipped while a sibling has not) | active / in-progress | `claimed` (or `review` where supported) |
|
|
39
39
|
| else (leaves exist, none started) | unchanged | — |
|
|
40
40
|
|
|
41
|
-
- **Blocked dominates** — one blocked leaf surfaces blocked on the parent even while others progress.
|
|
41
|
+
- **Blocked dominates** — one blocked leaf surfaces blocked on the parent even while others progress. It never says *which* child or *which kind* of hold; run `scripts/rollup-blocker-classification.mjs` over the resolved child graph and carry its per-class report — blocking leaf, path, and who must act — into the rollup note. A non-zero exit means it classified nothing; that is a failure to report, never an all-clear. See `leaf-only-lifecycle` → **Classifying a hold**.
|
|
42
42
|
- **Least-advanced env wins** — a parent reaches an env only once all required leaves have reached at least that env (all `On Stg` → `On Stg`; mixed dev/staging → the dev value). Native terminal closure fires only at the production `done`, never at an intermediate env.
|
|
43
43
|
- **The parent never carries `ready`** — `ready` is a human "claim this leaf" signal; rollup only moves a parent between non-ready container states.
|
|
44
44
|
- **Rollup is recursive** — an Epic rolls up from its Stories, each of which rolls up from its own leaves. Evaluate bottom-up.
|
|
@@ -26,6 +26,17 @@ Evaluate over the env ladder `in-progress < dev < staging < production` (the ord
|
|
|
26
26
|
3. Else any leaf has **started** (claimed/in review, or shipped while a sibling has not) → parent is **in-progress** (`claimed`).
|
|
27
27
|
4. Else (leaves exist but none started) → parent unchanged.
|
|
28
28
|
|
|
29
|
+
**Blocked dominates — but it must say which leaf and which kind.** A parent that rolls up to `blocked` names the blocking leaf (with the path to it) and the **class** of the hold, derived by `rollup-blocker-classification` from recorded signals only:
|
|
30
|
+
|
|
31
|
+
| Class | Recorded signal on the leaf | Who must act |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `spec-defect` | the `spec_defect` marker (human-applied only) | a person rewrites the acceptance criteria — nothing external will ever clear it |
|
|
34
|
+
| `human-input` | the `human_needed` marker | a person supplies the input (access, credential, product decision) |
|
|
35
|
+
| `hard-blocker` | an open `is blocked by` link | nobody — it clears when that work closes |
|
|
36
|
+
| `unknown` | none of the above | a person says which kind it is, and records it |
|
|
37
|
+
|
|
38
|
+
Never infer a class from prose, and never auto-apply `spec_defect`: judging a criterion unbuildable is a human call, and `unknown` is the honest answer. A rollup that examined nothing — unreadable tracker, no children, no readable child — **fails**; it never reports not-blocked.
|
|
39
|
+
|
|
29
40
|
**Blocked dominates.** A parent reaches an env only once all required leaves have reached at least that env. Intermediate-env rollup (`On Dev`/`On Stg`) happens, but native closure fires only at production `done`. Optional/won't-do children do not hold a parent open. Rollup is recursive — bottom-up. The parent never carries `ready`; a container found in `ready` is reconciled by rolling it up from its children.
|
|
30
41
|
|
|
31
42
|
## Terminal native closure
|
|
@@ -462,11 +462,12 @@ Every lifecycle skill operates on a fixed set of **roles** (`ready`, `claimed`,
|
|
|
462
462
|
|
|
463
463
|
#### Build markers (additive labels, not lifecycle roles)
|
|
464
464
|
|
|
465
|
-
A **marker** is an additive label applied *alongside* a lifecycle role, not a state the item transitions *to*. Markers carry no rollup or transition semantics — they annotate an item that is already in some role. The build lifecycle defines
|
|
465
|
+
A **marker** is an additive label applied *alongside* a lifecycle role, not a state the item transitions *to*. Markers carry no rollup or transition semantics — they annotate an item that is already in some role, and the rollup reads them to say *which kind* of hold a `blocked` item is (`rollup-blocker-classification`). The build lifecycle defines two markers:
|
|
466
466
|
|
|
467
467
|
| Marker | What it means | JIRA default | GitHub/Linear default |
|
|
468
468
|
|---|---|---|---|
|
|
469
469
|
| `human_needed` | Applied with `blocked` when — **after the agent has drafted every authorable missing section via the `pre-flight-autofill` procedure** — the block still requires a human to confirm the drafted assumptions or supply something no agent can invent: real missing credentials, access/permissions, or an irreducible product/scoping decision. | `Human Needed` (label) | `human-needed` (label) |
|
|
470
|
+
| `spec_defect` | Applied with `blocked` when the thing holding the item is the item's **own specification** — an acceptance criterion that cannot be satisfied as written, a field naming an environment or surface the project does not have, a validation journey demanding something the project cannot produce. **Human-applied only.** No agent sets it, because judging a criterion unbuildable is a product call, and the agent that wrote a bad criterion is the least likely to know it did. | `Spec Defect` (label) | `blocked:spec-defect` (label) |
|
|
470
471
|
|
|
471
472
|
Markers are labels on **every** vendor, Linear included — that is the one place the Linear build lane still touches `linear.labels`.
|
|
472
473
|
|
|
@@ -475,9 +476,14 @@ Resolution keys:
|
|
|
475
476
|
- JIRA: `jira.labels.human_needed` (default `Human Needed`). Applied as a JIRA **label** — not a workflow status — because an item holds exactly one status but any number of labels. The `blocked` status still drives the lifecycle; `human_needed` is the additive marker on top of it.
|
|
476
477
|
- GitHub: `github.labels.build.human_needed` (default `human-needed`). Added next to the `blocked` label.
|
|
477
478
|
- Linear: `linear.labels.build.human_needed` (default `human-needed`). Applied as a Linear **label**, for the same reason as JIRA — an Issue holds exactly one workflow state but any number of labels, so an additive marker cannot be a state. The `blocked` **state** still drives the lifecycle; `human_needed` is the marker on top of it. This is the only build-lane key left in `linear.labels`.
|
|
479
|
+
- `spec_defect` resolves the same way on each vendor — `jira.labels.spec_defect` (default `Spec Defect`), `github.labels.build.spec_defect` and `linear.labels.build.spec_defect` (default `blocked:spec-defect`).
|
|
478
480
|
|
|
479
481
|
**When to apply it.** Apply `human_needed` only when a human must act before the item can move — the pre-flight gate failures that bounce a ticket back to its reporter are exactly this case, but **only after** the agent has run the `pre-flight-autofill` draft-then-block procedure (drafting the authorable gaps — acceptance criteria, validation journey, repository, relationship search, etc. — into the ticket as labeled assumptions). What then remains for the human is to **confirm those assumptions** or supply a genuinely human-only input (real missing credentials, an irreducible product/scoping decision). The marker means "a human must confirm or decide," not "a human must author from scratch."
|
|
480
482
|
|
|
483
|
+
**Why `spec_defect` is separate from `human_needed`.** Both name a hold a person must clear, but they are different asks and the rollup must not merge them. `human_needed` waits on an input from *outside* the item — a credential, an access grant, a product decision — and the item's own text is fine. `spec_defect` waits on the item's text being *rewritten*, and no external event will ever supply that. An item carrying `spec_defect` will sit forever until somebody edits it; that is exactly the class that accumulated silently when the rollup rendered both as plain `blocked` (issue #3045: 32 identical hold comments over six weeks on one Epic). Where both markers are present, `spec_defect` wins — it is the more specific record, and the only one carrying a judgment nothing else can supply.
|
|
484
|
+
|
|
485
|
+
**Nothing infers `spec_defect`.** It is never derived from prose, a title, or a failed gate. A `blocked` item carrying neither marker and no open `is blocked by` link classifies as **`unknown`**, and the rollup says so and asks a person to decide — an honest `unknown` beats a confident wrong answer, and auto-reclassifying would turn a human product call into a guess.
|
|
486
|
+
|
|
481
487
|
**When NOT to apply it.** Do **not** apply `human_needed` to a block that an automated cycle can clear on its own: a block whose `is blocked by` dependency is another tracked ticket that will build and close (for example a `repair-intake`-filed build-ready fix ticket for an unmergeable PR or a failed deploy), or any block waiting only on a retry. Those self-heal; flagging them for a human is noise. If such an item already carries a stale `human_needed` marker, clear it when the block becomes auto-recoverable.
|
|
482
488
|
|
|
483
489
|
The marker is **best-effort and additive**: it never replaces the `blocked` role and never gates a transition. A project that does not define the label name inherits the defaults above; a project that does not want the marker at all can leave the label absent in its tracker, in which case the add is a no-op the agent records and moves on.
|
|
@@ -74,12 +74,33 @@ The middle two rungs are the same idea seen at two resolutions: a parent reaches
|
|
|
74
74
|
Notes:
|
|
75
75
|
|
|
76
76
|
- **Blocked dominates.** A single blocked leaf surfaces blocked/attention on the parent even if other leaves are progressing, so a human sees the parent needs attention.
|
|
77
|
+
- **Blocked must name its leaf and its class.** "Blocked" alone is a single bit, and it is lossy in the one dimension that decides what the operator does next. Two holds that render identically are not the same problem: one waits on an external event and may clear with nobody touching it; the other waits on a person rewriting an acceptance criterion and will never clear on its own. Rendered the same, the second class accumulates silently — measured on one Epic at two occurrences, 32 identical hold comments and six weeks, while the initiative was two-thirds complete both times (#3045). See **Classifying a hold** below.
|
|
77
78
|
- **"Required" leaves.** Optional or won't-do children do not hold a parent open; only the leaves that must ship for the parent to be complete are counted toward the env-rollup check.
|
|
78
79
|
- **Least-advanced env wins.** The parent reflects the env the whole required set has collectively reached — never an env ahead of its laggard leaf. Native closure (below) fires only when the resolved env is the production/terminal value, never at an intermediate env (`On Dev`/`On Stg`).
|
|
79
80
|
- **Rollup is recursive.** An Epic rolls up from its Stories, each of which rolls up from its own leaves. Evaluate bottom-up: a Story reaches an env only when its leaves have all reached at least that env; an Epic reaches it only when its Stories have.
|
|
80
81
|
- **Vendor support varies.** Apply the rollup state the vendor can express. Where a vendor has no native intermediate state, use the nearest configured role or a metadata/comment signal rather than forcing a non-existent status (PRD #522 non-goal: vendors need not expose identical states).
|
|
81
82
|
- **The parent never carries `ready`.** `ready` is a *human* "this is buildable, claim it" signal and only ever lives on leaves. Rollup moves a parent between non-ready container states (in-progress / per-env / blocked / terminal); it never sets the parent to `ready`. A container found carrying `ready` is a leaf-only-invariant violation — recompute its rolled state from its children and apply that (see `repair-intake`).
|
|
82
83
|
|
|
84
|
+
### Classifying a hold
|
|
85
|
+
|
|
86
|
+
`rollup-blocker-classification` (`plugins/src/base/scripts/rollup-blocker-classification.mjs`) is the single implementation. It takes the child graph the vendor reader already produced and returns, per held leaf, a class and the actor who must clear it:
|
|
87
|
+
|
|
88
|
+
| Class | Recorded signal on the leaf | Who must act | Clears on its own? |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `spec-defect` | the `spec_defect` marker (`config-resolution` → Build markers) | a person rewrites the acceptance criteria | never |
|
|
91
|
+
| `human-input` | the `human_needed` marker | a person supplies the input — access, a credential, a product decision | no |
|
|
92
|
+
| `hard-blocker` | at least one **open** `is blocked by` link | nobody | yes, when that work closes |
|
|
93
|
+
| `unknown` | none of the above | a person says which kind it is, then records it | no |
|
|
94
|
+
|
|
95
|
+
Four properties are load-bearing:
|
|
96
|
+
|
|
97
|
+
1. **Recorded signals only.** A class comes from a marker or a link somebody wrote, never from reading the item's prose. There is no heuristic to drift.
|
|
98
|
+
2. **It never reclassifies.** Deciding an acceptance criterion is unbuildable is a human product call. The tool makes the distinction *recordable and visible*; it does not make it. `unknown` is the honest answer, and it names the person who must resolve it rather than guessing.
|
|
99
|
+
3. **A transparent parent is not the blocker.** A held item whose own children include a held one is reported *through*: the rollup names the leaf at the bottom of the chain and the path to it (`#1495 -> #1515 -> #1547`), which is what turns a five-level descent into one read.
|
|
100
|
+
4. **It fails rather than reporting all-clear.** An unreadable tracker, a container with no children, and children none of which could be read each return a refusal, not `not-blocked`. A rollup that examined nothing and reported clean would be the same defect one level up.
|
|
101
|
+
|
|
102
|
+
The report is **composed per class**: a class with nothing in it produces no text, so the summary and the per-item lines cannot disagree (the `#3101` shape). It also carries a fingerprint of `container + held refs + classes`, so a cycle whose verdict is unchanged has nothing new to post and stays quiet — the 32 identical hold comments were one verdict restated on a schedule.
|
|
103
|
+
|
|
83
104
|
### The rollup env states are the configured "done" map — multi-env capable
|
|
84
105
|
|
|
85
106
|
The env rungs are whatever the project configures for `done` — which is **env-keyed** (`config-resolution` "Env-keyed `done`"): a `done` map keyed by environment (`dev`, `staging`, `production`), each leaf's env resolved from its merged PR's base branch. This rule does **not** hardcode a `dev → staging → prod` promotion chain as required — that is a project-specific deploy topology; the ladder is simply the ordered keys of the project's `done` map. A downstream project with dev/staging/prod environments rolls a parent up to the least-advanced env value its required leaves have collectively reached (an intermediate-env parent state, e.g. `On Stg`), and only to the production `done` value once every required leaf is at production. The rule stays generic and multi-env capable.
|