@produtype/core 0.18.0 → 0.18.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.
@@ -33,6 +33,54 @@ const phaseMeta = {
33
33
  description: 'Tasks that are good candidates for safe templates or later AI-assisted remediation.',
34
34
  },
35
35
  };
36
+ /**
37
+ * Removes a task whose work is fully described by other tasks in the same plan.
38
+ *
39
+ * `gdpr.privacy` says "implement consent, export/erasure workflows, and retention
40
+ * policies", which is four GDPR capabilities restated as one sentence. Twelve of the
41
+ * seventy-eight repositories in the verification corpus got that step *and* all of the
42
+ * steps it summarises: a reader does the work and then meets an item telling them to do
43
+ * it. The catalogue has recorded the relationship since it was written and nothing read
44
+ * the field.
45
+ *
46
+ * Only when nothing the general task covers is left outstanding: every superseding
47
+ * capability is either in the plan already or was not a problem in the first place. With
48
+ * two of four present and the other two still open, the general task is the only step
49
+ * that reaches them, and dropping it would lose the work.
50
+ *
51
+ * The dropped task's finding keeps a step: its id moves onto the tasks that replace it,
52
+ * so nothing falls out of the plan's account of what it is answering.
53
+ */
54
+ function dropSupersededTasks(states, actionableFindingIds) {
55
+ const plannedFindingIds = new Set(states.flatMap((state) => state.task.findingIds));
56
+ const survivors = [];
57
+ const reassigned = new Map();
58
+ for (const state of states) {
59
+ const superseders = state.supersededBy ?? [];
60
+ /**
61
+ * A capability that was never a problem needs no step, so it cannot be the reason
62
+ * the general task survives — but at least one of them has to be in the plan, or
63
+ * this task is the only thing pointing at the work. That is the observed-only case:
64
+ * no expectations ran, so none of the specific steps exist.
65
+ */
66
+ const outstanding = superseders.filter((id) => !plannedFindingIds.has(id) && actionableFindingIds.has(id));
67
+ const anyPlanned = superseders.some((id) => plannedFindingIds.has(id));
68
+ const fullyCovered = superseders.length > 0 && anyPlanned && outstanding.length === 0;
69
+ if (!fullyCovered) {
70
+ survivors.push(state);
71
+ continue;
72
+ }
73
+ for (const findingId of superseders) {
74
+ reassigned.set(findingId, [...(reassigned.get(findingId) ?? []), ...state.task.findingIds]);
75
+ }
76
+ }
77
+ return survivors.map((state) => {
78
+ const inherited = state.task.findingIds.flatMap((id) => reassigned.get(id) ?? []);
79
+ if (inherited.length === 0)
80
+ return state;
81
+ return { ...state, task: { ...state.task, findingIds: unique([...state.task.findingIds, ...inherited]) } };
82
+ });
83
+ }
36
84
  function unique(values) {
37
85
  return Array.from(new Set(values.filter(Boolean)));
38
86
  }
@@ -163,10 +211,11 @@ function buildPlan(report) {
163
211
  task: buildTask(seed, evidenceFiles),
164
212
  phaseId: entry.phaseId,
165
213
  severityRank: severityOrder[finding.severity],
214
+ supersededBy: entry.supersededBy,
166
215
  });
167
216
  }
168
217
  }
169
- const states = sortStates(Array.from(taskStates.values()));
218
+ const states = sortStates(dropSupersededTasks(Array.from(taskStates.values()), new Set(actionableFindings.map((finding) => finding.id))));
170
219
  const tasks = states.map((state) => state.task);
171
220
  const phases = buildPhases(states);
172
221
  const quickWins = tasks.filter((task) => task.effort === 'small' && (task.risk === 'low' || task.risk === 'medium'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {