instar 1.3.21 → 1.3.22
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/feedback-factory/processor/reportPartition.d.ts +42 -0
- package/dist/feedback-factory/processor/reportPartition.d.ts.map +1 -0
- package/dist/feedback-factory/processor/reportPartition.js +55 -0
- package/dist/feedback-factory/processor/reportPartition.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.22.md +25 -0
- package/upgrades/side-effects/feedback-factory-report-partition.md +33 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reportPartition.ts — TS port of scar (d): lifecycle partitioning + re-report
|
|
3
|
+
* (cycling) prevention for the operator digest.
|
|
4
|
+
*
|
|
5
|
+
* Ports the pure partition decision from the report-generation function (:2747) of
|
|
6
|
+
* the reference `the-portal/.claude/scripts/feedback-processor.py` — the half of
|
|
7
|
+
* scar (d) that complements the cluster-level cycling detection already ported in
|
|
8
|
+
* transitions.ts. Given the current clusters + what was surfaced in the LAST
|
|
9
|
+
* operator report, it decides what is actionable now: newly-open vs already-known
|
|
10
|
+
* issues, the same for investigating, and items FIXED since the last report that
|
|
11
|
+
* haven't been announced before (the re-report/cycling guard — a fix is announced
|
|
12
|
+
* once, never again). It also decides whether the whole report should be skipped
|
|
13
|
+
* (nothing new ⇒ no noise).
|
|
14
|
+
*
|
|
15
|
+
* Pure: the clusters, the previous-report state, and `now` are injected (the real
|
|
16
|
+
* reporter does the DB query + state load + Telegram render around this). `now` is
|
|
17
|
+
* an ISO string. Equivalence is by faithful transcription + both-sides-of-boundary
|
|
18
|
+
* unit tests (the decision is embedded in a Telegram-rendering function in the
|
|
19
|
+
* reference, so there is no clean isolated function for a cross-runtime harness).
|
|
20
|
+
*/
|
|
21
|
+
import type { Cluster } from './types.js';
|
|
22
|
+
export interface PreviousReportState {
|
|
23
|
+
lastReportAt?: string;
|
|
24
|
+
reportedOpenIds?: string[];
|
|
25
|
+
reportedInvestigatingIds?: string[];
|
|
26
|
+
reportedFixedIds?: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ReportPartition {
|
|
29
|
+
newIssues: Cluster[];
|
|
30
|
+
continuingOpen: Cluster[];
|
|
31
|
+
newInvestigating: Cluster[];
|
|
32
|
+
continuingInvestigating: Cluster[];
|
|
33
|
+
fixedNew: Cluster[];
|
|
34
|
+
/** True when there is nothing new to report (report should be skipped to avoid noise). */
|
|
35
|
+
shouldSkip: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Partition clusters for an operator report. `now` (ISO) is used only for the
|
|
39
|
+
* first-run window (no prior report → items fixed in the last 4h).
|
|
40
|
+
*/
|
|
41
|
+
export declare function partitionClustersForReport(clusters: Cluster[], prev: PreviousReportState, now: string): ReportPartition;
|
|
42
|
+
//# sourceMappingURL=reportPartition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportPartition.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/processor/reportPartition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,EAAE,CAAC;IACrB,cAAc,EAAE,OAAO,EAAE,CAAC;IAC1B,gBAAgB,EAAE,OAAO,EAAE,CAAC;IAC5B,uBAAuB,EAAE,OAAO,EAAE,CAAC;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,0FAA0F;IAC1F,UAAU,EAAE,OAAO,CAAC;CACrB;AAUD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,EAAE,EACnB,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,MAAM,GACV,eAAe,CA6BjB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reportPartition.ts — TS port of scar (d): lifecycle partitioning + re-report
|
|
3
|
+
* (cycling) prevention for the operator digest.
|
|
4
|
+
*
|
|
5
|
+
* Ports the pure partition decision from the report-generation function (:2747) of
|
|
6
|
+
* the reference `the-portal/.claude/scripts/feedback-processor.py` — the half of
|
|
7
|
+
* scar (d) that complements the cluster-level cycling detection already ported in
|
|
8
|
+
* transitions.ts. Given the current clusters + what was surfaced in the LAST
|
|
9
|
+
* operator report, it decides what is actionable now: newly-open vs already-known
|
|
10
|
+
* issues, the same for investigating, and items FIXED since the last report that
|
|
11
|
+
* haven't been announced before (the re-report/cycling guard — a fix is announced
|
|
12
|
+
* once, never again). It also decides whether the whole report should be skipped
|
|
13
|
+
* (nothing new ⇒ no noise).
|
|
14
|
+
*
|
|
15
|
+
* Pure: the clusters, the previous-report state, and `now` are injected (the real
|
|
16
|
+
* reporter does the DB query + state load + Telegram render around this). `now` is
|
|
17
|
+
* an ISO string. Equivalence is by faithful transcription + both-sides-of-boundary
|
|
18
|
+
* unit tests (the decision is embedded in a Telegram-rendering function in the
|
|
19
|
+
* reference, so there is no clean isolated function for a cross-runtime harness).
|
|
20
|
+
*/
|
|
21
|
+
const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
22
|
+
function bySeverity(a, b) {
|
|
23
|
+
const sa = SEVERITY_ORDER[a.severity ?? ''] ?? 9;
|
|
24
|
+
const sb = SEVERITY_ORDER[b.severity ?? ''] ?? 9;
|
|
25
|
+
return sa - sb;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Partition clusters for an operator report. `now` (ISO) is used only for the
|
|
29
|
+
* first-run window (no prior report → items fixed in the last 4h).
|
|
30
|
+
*/
|
|
31
|
+
export function partitionClustersForReport(clusters, prev, now) {
|
|
32
|
+
const prevOpen = new Set(prev.reportedOpenIds ?? []);
|
|
33
|
+
const prevInvestigating = new Set(prev.reportedInvestigatingIds ?? []);
|
|
34
|
+
const prevFixed = new Set(prev.reportedFixedIds ?? []);
|
|
35
|
+
const lastReportTime = prev.lastReportAt ?? '';
|
|
36
|
+
const allOpen = clusters.filter((c) => c.status === 'open');
|
|
37
|
+
const allInvestigating = clusters.filter((c) => c.status === 'investigating');
|
|
38
|
+
const newIssues = allOpen.filter((c) => !prevOpen.has(c.clusterId)).sort(bySeverity);
|
|
39
|
+
const continuingOpen = allOpen.filter((c) => prevOpen.has(c.clusterId));
|
|
40
|
+
const newInvestigating = allInvestigating.filter((c) => !prevInvestigating.has(c.clusterId)).sort(bySeverity);
|
|
41
|
+
const continuingInvestigating = allInvestigating.filter((c) => prevInvestigating.has(c.clusterId));
|
|
42
|
+
// Fixed since the last report AND not announced before — the re-report guard.
|
|
43
|
+
let cutoff;
|
|
44
|
+
if (lastReportTime) {
|
|
45
|
+
cutoff = lastReportTime;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// First run ever — only items fixed in the last 4 hours.
|
|
49
|
+
cutoff = new Date(new Date(now).getTime() - 4 * 60 * 60 * 1000).toISOString();
|
|
50
|
+
}
|
|
51
|
+
const fixedNew = clusters.filter((c) => c.status === 'fixed' && String(c.updatedAt ?? '') > cutoff && !prevFixed.has(c.clusterId));
|
|
52
|
+
const shouldSkip = newIssues.length === 0 && newInvestigating.length === 0 && fixedNew.length === 0;
|
|
53
|
+
return { newIssues, continuingOpen, newInvestigating, continuingInvestigating, fixedNew, shouldSkip };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=reportPartition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportPartition.js","sourceRoot":"","sources":["../../../src/feedback-factory/processor/reportPartition.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-27T08:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-05-27T08:47:50.347Z",
|
|
5
|
+
"instarVersion": "1.3.22",
|
|
6
6
|
"entryCount": 192,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
Tenth increment of the **Feedback Factory Migration** (Dawn → Echo; spec `docs/specs/feedback-factory-migration.md`, approved). Ports the remaining half of "scar (d)" — the operator-report **lifecycle partitioning with re-report prevention** — from the reference Python (`the-portal/.claude/scripts/feedback-processor.py`, `:2747`) into a pure function at `src/feedback-factory/processor/reportPartition.ts`.
|
|
8
|
+
|
|
9
|
+
Given the current bug-clusters and what was surfaced in the last operator report, it decides what is actionable now: which issues are newly-open versus already-known, the same for in-progress work, and which bugs were fixed since the last report — announcing each fix exactly once (the re-report guard that stops the digest from repeating itself). It also decides when there's nothing new and the report should be skipped entirely. Pure function; **not wired into any route or job yet** — no behavioral change.
|
|
10
|
+
|
|
11
|
+
This completes scar (d): the cluster-level cycling was ported earlier (in the state-machine increment); this is the digest-level partitioning + re-report prevention Dawn specifically flagged.
|
|
12
|
+
|
|
13
|
+
## What to Tell Your User
|
|
14
|
+
|
|
15
|
+
- The last piece of the feedback "brain" Dawn flagged is now ported — the logic that decides what goes into a status report and, crucially, doesn't re-announce the same fix over and over.
|
|
16
|
+
|
|
17
|
+
## Summary of New Capabilities
|
|
18
|
+
|
|
19
|
+
| Capability | How to Use |
|
|
20
|
+
|-----------|-----------|
|
|
21
|
+
| Report partition + re-report guard (TS port) | Internal module `src/feedback-factory/processor/reportPartition.ts` — not yet wired |
|
|
22
|
+
|
|
23
|
+
## Evidence
|
|
24
|
+
|
|
25
|
+
- The decision is embedded in a Telegram-rendering function in the reference, so equivalence is by faithful transcription plus both-sides-of-boundary tests (6 unit tests): new vs continuing open/investigating; severity ordering; the re-report guard (a fix updated after the last report and not previously announced is included; one before the last report or already announced is excluded); the first-run 4-hour window; and skip-only-when-nothing-new.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Side-Effects Review — scar (d) report partitioning + re-report guard (Phase 1, increment 10)
|
|
2
|
+
|
|
3
|
+
**Slug:** `feedback-factory-report-partition`
|
|
4
|
+
**Date:** `2026-05-27`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
|
|
7
|
+
**Scope:** The pure partition decision of scar (d) — the half Dawn flagged at `:2747` that complements the cluster-level cycling already ported in transitions.ts. The Telegram rendering + report-state persistence stay in the (later) notification layer.
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
Ports the operator-report partition decision into `src/feedback-factory/processor/reportPartition.ts` (`partitionClustersForReport(clusters, prev, now)`). Given the current clusters + what was surfaced in the last report, it decides: newly-open vs already-known open issues; new vs continuing investigating; items FIXED since the last report not announced before (the **re-report / cycling guard** — a fix is announced once, never again); severity ordering of the new buckets; and `shouldSkip` (nothing new ⇒ no noisy report). Pure — clusters, previous-report state, and `now` are injected. **Not wired into any route/job yet** — no behavioral change.
|
|
12
|
+
|
|
13
|
+
This completes scar (d): increment 3 ported the cluster-level cycling (`detect_cycling` + the chronic circuit-breaker); this ports the digest-level lifecycle partitioning + re-report prevention.
|
|
14
|
+
|
|
15
|
+
## Equivalence verification
|
|
16
|
+
|
|
17
|
+
The decision is embedded in a Telegram-rendering function in the reference, so equivalence is by faithful transcription + both-sides-of-boundary unit tests (6): new vs continuing open/investigating; severity sort (critical→low); the re-report guard (fix after last-report not-previously-announced included; before-last-report or already-announced excluded); the first-run 4-hour window; and `shouldSkip` true only when nothing is new. The set-membership, the `updatedAt > cutoff` comparison, the 4h first-run window, and the skip condition are transcribed verbatim.
|
|
18
|
+
|
|
19
|
+
## Seven-dimension review
|
|
20
|
+
|
|
21
|
+
1. **Over/under-reach** — Pure function, no I/O, no state, not imported by any runtime path. The state load + Telegram render are correctly excluded (notification layer).
|
|
22
|
+
2. **Level-of-abstraction fit** — Processor-logic layer; produces a partition the notification layer renders. The report-state persistence (`_save_last_report_state`) + Telegram formatting attach later, around this pure decision.
|
|
23
|
+
3. **Signal vs Authority** — N/A; produces buckets + a skip flag. No authority over cluster lifecycle.
|
|
24
|
+
4. **Interactions** — None. New isolated module; nothing imports it yet.
|
|
25
|
+
5. **Rollback cost** — Trivial: delete the module + tests.
|
|
26
|
+
6. **Migration parity** — N/A. New internal library code; no agent-installed file touched.
|
|
27
|
+
7. **Failure modes** — (a) Re-report guard inverted (announce a fix twice / never) → both-sides tests on prevFixed + updatedAt-vs-cutoff. (b) First-run window wrong → tested at the 4h boundary. (c) Skip-when-something-new → tested both directions. (d) Severity sort → tested.
|
|
28
|
+
|
|
29
|
+
## Tests
|
|
30
|
+
|
|
31
|
+
- Tier-1 unit (CI): `tests/unit/feedback-factory/reportPartition.test.ts` — 6 tests.
|
|
32
|
+
- No cross-runtime parity harness (decision embedded in a Telegram-rendering function; equivalence by transcription + boundary tests).
|
|
33
|
+
- No integration/E2E this increment: the digest rendering + state persistence are the notification layer, which attaches at cutover. Reasoned, documented.
|