feat(ai-psychosis): /interaction-report surfaces v1.2 fields
This commit is contained in:
parent
eb040cfccb
commit
6fe275825a
2 changed files with 105 additions and 10 deletions
|
|
@ -109,13 +109,16 @@ The file contains two record types interleaved:
|
|||
```
|
||||
|
||||
**End records** — have `end`, `duration_min`, `tool_count`, `edit_count`, `flags`,
|
||||
and (v1.1.0+) `domain_context` at top level plus `pushback` inside `flags`:
|
||||
and (v1.1.0+) `domain_context` at top level plus `pushback` inside `flags`.
|
||||
v1.2 records additionally carry `user_info_class`, `valseek_count`,
|
||||
`turn_count`, and `domain_context` is always an array:
|
||||
```json
|
||||
{"session_id":"abc","start":"2026-04-05T10:00:00Z","end":"2026-04-05T11:35:00Z","duration_min":95,"tool_count":47,"edit_count":12,"domain_context":"relationship","flags":{"dependency":2,"escalation":0,"fatigue":1,"validation":1,"pushback":3}}
|
||||
{"session_id":"abc","start":"2026-04-05T10:00:00Z","end":"2026-04-05T11:35:00Z","duration_min":95,"tool_count":47,"edit_count":12,"domain_context":["relationship","health"],"user_info_class":"no","valseek_count":3,"turn_count":18,"flags":{"dependency":2,"escalation":0,"fatigue":1,"validation":1,"pushback":3}}
|
||||
```
|
||||
|
||||
Records produced by v1.0.0 omit `domain_context` and `flags.pushback`.
|
||||
Treat missing values as `null` / `0` — never as `NaN`.
|
||||
v1.1.0 records have `domain_context` as a string; v1.2 records have it as
|
||||
an array. Treat missing values as `null` / `0` — never as `NaN`.
|
||||
|
||||
**Error records** — have `note: "no_state_file"`. Ignore these.
|
||||
|
||||
|
|
@ -144,11 +147,20 @@ node hooks/scripts/report-reader.mjs ${CLAUDE_PLUGIN_DATA}/sessions.jsonl
|
|||
|
||||
The script outputs a JSON object with the following fields:
|
||||
- `pushback_total` — sum of `flags.pushback` across all end records
|
||||
- `relationship_domain_count` — count of records where `domain_context === 'relationship'`
|
||||
- `relationship_domain_count` — count of records where `domain_context` includes 'relationship'
|
||||
- `null_domain_count`, `other_domain_count` — remaining domain buckets
|
||||
- `total_end_records` — number of complete sessions
|
||||
- `flags_total` — totals for dependency / escalation / fatigue / validation / pushback
|
||||
- `schema_version.v1_0_records` / `v1_1_records` — backward-compat counters
|
||||
- `schema_version.v1_0_records` / `v1_1_records` / `v1_2_records` — backward-compat counters
|
||||
- **v1.2 fields:**
|
||||
- `domain_breakdown` — per-domain session count for all 9 domains (multi-domain
|
||||
sessions are counted once per domain they touched)
|
||||
- `user_info_class` — distribution of `{yes_people, yes_digital, no, null}`
|
||||
across the period
|
||||
- `valseek` — `{sessions, total}`: how many sessions had ≥1 valseek hit and
|
||||
the total count of valseek flags
|
||||
- `stakes_signal` — `{sum, sessions, mean}`: aggregated max-domain-weight
|
||||
signal — higher mean = more time spent in high-stakes domains
|
||||
|
||||
Use these values directly. The reader handles backward-compatibility with
|
||||
v1.0.0 records (missing `pushback` / `domain_context`) and never produces NaN.
|
||||
|
|
@ -238,14 +250,67 @@ this automatically — it is a self-assessment prompt, not a measurement.
|
|||
|
||||
### Domain context
|
||||
|
||||
When `domain_breakdown` is available (v1.2 records present), surface the
|
||||
per-domain count instead of the v1.1.0 binary table. Multi-domain sessions
|
||||
are counted once per domain.
|
||||
|
||||
| Domain | Sessions |
|
||||
|--------|----------|
|
||||
| Relationship-flavored | {relationship_domain_count} |
|
||||
| Other / not classified | {null_domain_count + other_domain_count} |
|
||||
| Relationship | {domain_breakdown.relationship} |
|
||||
| Health | {domain_breakdown.health} |
|
||||
| Legal | {domain_breakdown.legal} |
|
||||
| Parenting | {domain_breakdown.parenting} |
|
||||
| Financial | {domain_breakdown.financial} |
|
||||
| Professional | {domain_breakdown.professional} |
|
||||
| Spirituality | {domain_breakdown.spirituality} |
|
||||
| Consumer | {domain_breakdown.consumer} |
|
||||
| Personal development | {domain_breakdown.personal_dev} |
|
||||
|
||||
Domain detection is heuristic and conservative. A "relationship" tag means
|
||||
patterns associated with relational decision support appeared at least once
|
||||
during the session, not that the entire session was about relationships.
|
||||
Skip rows with count 0 unless none have data, in which case show
|
||||
"No domain context recorded." Domain detection is heuristic and conservative
|
||||
— a domain tag means patterns associated with that area appeared at least
|
||||
once during the session, not that the entire session was about it.
|
||||
|
||||
### User information dimension (v1.2)
|
||||
|
||||
Surface this section ONLY when `schema_version.v1_2_records > 0`.
|
||||
|
||||
| Class | Sessions | Note |
|
||||
|-------|----------|------|
|
||||
| `yes_people` | {user_info_class.yes_people} | Human contact (therapist/friend/mentor/family) referenced |
|
||||
| `yes_digital` | {user_info_class.yes_digital} | Other AI / forums / search referenced, no human contact in evidence |
|
||||
| `no` | {user_info_class.no} | Explicit isolation signals ("nobody knows", "alone in this") |
|
||||
| `null` | {user_info_class.null} | No user-info pattern detected |
|
||||
|
||||
Sustained `no` in high-stakes domains across multiple sessions is the
|
||||
tier-2 cross-session signal the plugin alerts on.
|
||||
|
||||
### Validation-seeking (v1.2)
|
||||
|
||||
Surface this section ONLY when `schema_version.v1_2_records > 0`.
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Sessions with ≥1 valseek hit | {valseek.sessions} of {v1_2_records} |
|
||||
| Total valseek flags | {valseek.total} |
|
||||
|
||||
Validation-seeking is distinct from the existing "right?" tic counter.
|
||||
It targets reality-testing ("am I crazy?"), pre-committed stance + confirmation,
|
||||
and side-taking pressing.
|
||||
|
||||
### Stakes signal (v1.2)
|
||||
|
||||
Surface this section ONLY when `schema_version.v1_2_records > 0` and
|
||||
`stakes_signal.sessions > 0`.
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Mean stakes weight | {stakes_signal.mean} |
|
||||
| Sessions in domain context | {stakes_signal.sessions} |
|
||||
|
||||
Stakes signal is the per-session max domain weight (1.0 = baseline,
|
||||
1.5 = legal/parenting/health/financial). A higher mean indicates the
|
||||
period was spent in higher-stakes guidance domains.
|
||||
|
||||
### Tool Usage (top 10)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,17 @@ function runReader(jsonlContent) {
|
|||
}
|
||||
}
|
||||
|
||||
function runReaderRaw(jsonlContent) {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'ia-report-'));
|
||||
const path = join(dir, 'sessions.jsonl');
|
||||
writeFileSync(path, jsonlContent);
|
||||
try {
|
||||
return execSync(`node ${SCRIPT} ${path}`, { encoding: 'utf8', timeout: 5000 });
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test('pushback_total matches sum across v1.1.0 records', () => {
|
||||
const fixture = [
|
||||
{ session_id: 'a', start: '2026-04-10T10:00:00Z', end: '2026-04-10T11:00:00Z',
|
||||
|
|
@ -166,3 +177,22 @@ test('backward-compat: v1.0.0 records without pushback/domain do not produce NaN
|
|||
assert.equal(result.flags_total.dependency, 1);
|
||||
assert.equal(result.flags_total.fatigue, 1);
|
||||
});
|
||||
|
||||
test('report-reader stdout surfaces v1.2 field names (SC-12)', () => {
|
||||
// Run reader against a v1.2 fixture and assert stdout contains the field
|
||||
// names that /interaction-report references in its output template.
|
||||
const fixture = [
|
||||
{ session_id: 'a', duration_min: 30,
|
||||
domain_context: ['legal', 'health'],
|
||||
user_info_class: 'no', valseek_count: 4, turn_count: 22,
|
||||
flags: { dependency: 0, escalation: 0, fatigue: 0, validation: 0, pushback: 1 } },
|
||||
];
|
||||
const stdout = runReaderRaw(fixture.map(o => JSON.stringify(o)).join('\n') + '\n');
|
||||
// SC-12 specifies these field names must be present in the report output:
|
||||
assert.ok(stdout.includes('user_info_class'), 'stdout missing user_info_class field');
|
||||
assert.ok(stdout.includes('valseek'), 'stdout missing valseek aggregation');
|
||||
assert.ok(stdout.includes('stakes_signal'), 'stdout missing stakes_signal aggregation');
|
||||
// Also assert at least one new domain name (legal) appears in domain_breakdown.
|
||||
assert.ok(stdout.includes('legal'), 'stdout missing legal domain in breakdown');
|
||||
assert.ok(stdout.includes('domain_breakdown'), 'stdout missing domain_breakdown structure');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue