phewsh 0.15.24 → 0.15.25
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/commands/session.js +20 -0
- package/lib/recall.js +59 -0
- package/package.json +1 -1
package/commands/session.js
CHANGED
|
@@ -22,6 +22,7 @@ const { recordDecision, labelOutcome, pendingDecisions, recentDecisions, outcome
|
|
|
22
22
|
const { suggest, suggestAll } = require('../lib/suggest');
|
|
23
23
|
const continuity = require('../lib/continuity');
|
|
24
24
|
const learning = require('../lib/learning');
|
|
25
|
+
const recall = require('../lib/recall');
|
|
25
26
|
const { closest } = require('../lib/closest');
|
|
26
27
|
const cmdHistory = require('../lib/history');
|
|
27
28
|
const { recordSessionEvent } = require('../lib/receipts-data');
|
|
@@ -583,7 +584,25 @@ async function main() {
|
|
|
583
584
|
const failureTracker = createFailureTracker();
|
|
584
585
|
let lastTurnFailure = null;
|
|
585
586
|
|
|
587
|
+
// The gate looking backward: if this is close to something you already
|
|
588
|
+
// reverted or failed, say so once — quietly, before the turn runs.
|
|
589
|
+
let lastRecallId = null;
|
|
590
|
+
function recallHeadsUp(input) {
|
|
591
|
+
try {
|
|
592
|
+
const past = recentDecisions(300, { project: projectName });
|
|
593
|
+
const hit = recall.closestRegret(past, input, { project: projectName, minSimilarity: 0.5 });
|
|
594
|
+
if (!hit || hit.id === lastRecallId) return;
|
|
595
|
+
lastRecallId = hit.id;
|
|
596
|
+
let s = (hit.summary || '').replace(/\s+/g, ' ');
|
|
597
|
+
if (s.length > 50) s = s.slice(0, 49).trimEnd() + '…';
|
|
598
|
+
const verb = hit.outcome === 'failed' ? 'failed' : 'reverted';
|
|
599
|
+
console.log(` ${peach('↩')} ${sage(`You ${verb} something close before:`)} ${slate('“' + s + '” · via ' + continuity.labelFor(hit.route) + ' · ' + continuity.agoText(hit.ts))}`);
|
|
600
|
+
console.log(` ${slate(' not a block — just so the record doesn\'t let you repeat it blind.')}`);
|
|
601
|
+
} catch { /* recall is advisory, never blocks a turn */ }
|
|
602
|
+
}
|
|
603
|
+
|
|
586
604
|
async function runHarnessTurn(input, harnessId, fullSystem) {
|
|
605
|
+
recallHeadsUp(input);
|
|
587
606
|
const decisionId = recordDecision({
|
|
588
607
|
project: projectName, route: harnessId, mode: sessionMode, summary: input,
|
|
589
608
|
});
|
|
@@ -623,6 +642,7 @@ async function main() {
|
|
|
623
642
|
}
|
|
624
643
|
|
|
625
644
|
async function runApiTurn(input, fullSystem) {
|
|
645
|
+
recallHeadsUp(input);
|
|
626
646
|
const decisionId = recordDecision({
|
|
627
647
|
project: projectName, route: 'api', mode: sessionMode, summary: input,
|
|
628
648
|
});
|
package/lib/recall.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Recall — the record warning you before you repeat a mistake.
|
|
2
|
+
//
|
|
3
|
+
// Every decision is labeled. When you're about to do something close to what
|
|
4
|
+
// you already tried and *reverted* or *failed*, phewsh should say so — once,
|
|
5
|
+
// quietly, before you spend the turn. This is the decision gate looking
|
|
6
|
+
// backward: "you've been here; it didn't hold."
|
|
7
|
+
//
|
|
8
|
+
// Pure: feed it past decisions + the new text, get back the closest prior
|
|
9
|
+
// regret, or null. Similarity is token-overlap (Jaccard) so it matches intent
|
|
10
|
+
// ("add dark mode toggle" ≈ "build the dark-mode switch"), not exact strings.
|
|
11
|
+
|
|
12
|
+
const STOP = new Set([
|
|
13
|
+
'the', 'a', 'an', 'to', 'of', 'and', 'or', 'for', 'in', 'on', 'at', 'with',
|
|
14
|
+
'is', 'it', 'this', 'that', 'i', 'we', 'my', 'our', 'me', 'be', 'do', 'can',
|
|
15
|
+
'will', 'would', 'should', 'let', 'lets', 'please', 'just', 'make', 'add',
|
|
16
|
+
'use', 'using', 'get', 'set', 'so', 'as', 'by', 'from', 'into', 'up',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const REGRET = new Set(['reverted', 'failed']);
|
|
20
|
+
|
|
21
|
+
function tokens(s) {
|
|
22
|
+
return new Set(
|
|
23
|
+
String(s || '')
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.split(/[^a-z0-9]+/)
|
|
26
|
+
.filter((w) => w.length > 2 && !STOP.has(w))
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Jaccard overlap of two strings' meaningful tokens, 0..1. */
|
|
31
|
+
function similarity(a, b) {
|
|
32
|
+
const A = tokens(a), B = tokens(b);
|
|
33
|
+
if (A.size === 0 || B.size === 0) return 0;
|
|
34
|
+
let inter = 0;
|
|
35
|
+
for (const t of A) if (B.has(t)) inter++;
|
|
36
|
+
return inter / (A.size + B.size - inter);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Prior reverted/failed decisions similar to `text`, most-similar first.
|
|
41
|
+
* @param {object[]} decisions
|
|
42
|
+
* @param {string} text
|
|
43
|
+
* @param {object} [opts] { project, minSimilarity=0.5 }
|
|
44
|
+
*/
|
|
45
|
+
function recallSimilar(decisions, text, { project = null, minSimilarity = 0.5 } = {}) {
|
|
46
|
+
return (decisions || [])
|
|
47
|
+
.filter((d) => d && REGRET.has(d.outcome) && (!project || d.project === project))
|
|
48
|
+
.map((d) => ({ ...d, similarity: similarity(text, d.summary) }))
|
|
49
|
+
.filter((d) => d.similarity >= minSimilarity)
|
|
50
|
+
.sort((a, b) => b.similarity - a.similarity);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The single closest prior regret, or null. */
|
|
54
|
+
function closestRegret(decisions, text, opts = {}) {
|
|
55
|
+
const hits = recallSimilar(decisions, text, opts);
|
|
56
|
+
return hits.length ? hits[0] : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { similarity, recallSimilar, closestRegret, tokens };
|