@su-record/vibe 4.2.35 → 4.2.37
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/cli/common.js +4 -1
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/heldout.js +10 -4
- package/dist/cli/heldout.js.map +1 -1
- package/dist/cli/human.js +8 -4
- package/dist/cli/human.js.map +1 -1
- package/dist/cli/internal.js +37 -24
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/ledger.js +67 -0
- package/dist/cli/ledger.js.map +1 -0
- package/dist/cli/map.js +5 -3
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/memory.js +104 -74
- package/dist/cli/memory.js.map +1 -1
- package/dist/cli/mutate.js +20 -0
- package/dist/cli/mutate.js.map +1 -0
- package/dist/cli/risks.js +20 -3
- package/dist/cli/risks.js.map +1 -1
- package/dist/cli/session.js +4 -2
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/setup.js +44 -13
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/work.js +73 -21
- package/dist/cli/work.js.map +1 -1
- package/dist/cli.js +19 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/analyze.js +21 -7
- package/dist/core/analyze.js.map +1 -1
- package/dist/core/artifact-paths.js +14 -3
- package/dist/core/artifact-paths.js.map +1 -1
- package/dist/core/capabilities.js +69 -15
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/check-authorization.js +37 -0
- package/dist/core/check-authorization.js.map +1 -0
- package/dist/core/check-process.js +12 -4
- package/dist/core/check-process.js.map +1 -1
- package/dist/core/check-report.js +66 -0
- package/dist/core/check-report.js.map +1 -0
- package/dist/core/check.js +59 -71
- package/dist/core/check.js.map +1 -1
- package/dist/core/checks/changed.js +20 -6
- package/dist/core/checks/changed.js.map +1 -1
- package/dist/core/checks/eval.js +35 -10
- package/dist/core/checks/eval.js.map +1 -1
- package/dist/core/checks/file.js +26 -23
- package/dist/core/checks/file.js.map +1 -1
- package/dist/core/checks/fileRules.js +42 -16
- package/dist/core/checks/fileRules.js.map +1 -1
- package/dist/core/checks/http.js +35 -34
- package/dist/core/checks/http.js.map +1 -1
- package/dist/core/checks/packs.js +1 -1
- package/dist/core/checks/packs.js.map +1 -1
- package/dist/core/checks/review.js +75 -40
- package/dist/core/checks/review.js.map +1 -1
- package/dist/core/checks/run.js +5 -2
- package/dist/core/checks/run.js.map +1 -1
- package/dist/core/checks/schema.js +25 -0
- package/dist/core/checks/schema.js.map +1 -0
- package/dist/core/checks/source.js +11 -6
- package/dist/core/checks/source.js.map +1 -1
- package/dist/core/config.js +9 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/consent.js +24 -16
- package/dist/core/consent.js.map +1 -1
- package/dist/core/context-notes.js +15 -7
- package/dist/core/context-notes.js.map +1 -1
- package/dist/core/context.js +24 -12
- package/dist/core/context.js.map +1 -1
- package/dist/core/conventions.js +22 -9
- package/dist/core/conventions.js.map +1 -1
- package/dist/core/docs/cfb.js +81 -45
- package/dist/core/docs/cfb.js.map +1 -1
- package/dist/core/docs/html.js +14 -3
- package/dist/core/docs/html.js.map +1 -1
- package/dist/core/docs/hwp.js +44 -22
- package/dist/core/docs/hwp.js.map +1 -1
- package/dist/core/docs/office.js +20 -10
- package/dist/core/docs/office.js.map +1 -1
- package/dist/core/docs/pdf.js +10 -4
- package/dist/core/docs/pdf.js.map +1 -1
- package/dist/core/docs/read.js +32 -24
- package/dist/core/docs/read.js.map +1 -1
- package/dist/core/docs/zip.js +32 -15
- package/dist/core/docs/zip.js.map +1 -1
- package/dist/core/evidence.js +17 -7
- package/dist/core/evidence.js.map +1 -1
- package/dist/core/failure-message.js +35 -13
- package/dist/core/failure-message.js.map +1 -1
- package/dist/core/failure.js +49 -14
- package/dist/core/failure.js.map +1 -1
- package/dist/core/github.js +3 -1
- package/dist/core/github.js.map +1 -1
- package/dist/core/handoff.js +20 -7
- package/dist/core/handoff.js.map +1 -1
- package/dist/core/heldout-run.js +6 -3
- package/dist/core/heldout-run.js.map +1 -1
- package/dist/core/heldout.js +24 -13
- package/dist/core/heldout.js.map +1 -1
- package/dist/core/inbox.js +3 -1
- package/dist/core/inbox.js.map +1 -1
- package/dist/core/inputs.js +6 -3
- package/dist/core/inputs.js.map +1 -1
- package/dist/core/inspect.js +61 -20
- package/dist/core/inspect.js.map +1 -1
- package/dist/core/intent.js +35 -13
- package/dist/core/intent.js.map +1 -1
- package/dist/core/knowledge-find.js +31 -12
- package/dist/core/knowledge-find.js.map +1 -1
- package/dist/core/knowledge.js +22 -12
- package/dist/core/knowledge.js.map +1 -1
- package/dist/core/lang.js +5 -2
- package/dist/core/lang.js.map +1 -1
- package/dist/core/ledger.js +44 -20
- package/dist/core/ledger.js.map +1 -1
- package/dist/core/locations.js +12 -0
- package/dist/core/locations.js.map +1 -0
- package/dist/core/map/imports.js +20 -9
- package/dist/core/map/imports.js.map +1 -1
- package/dist/core/map/index.js +28 -13
- package/dist/core/map/index.js.map +1 -1
- package/dist/core/map/symbols.js +27 -12
- package/dist/core/map/symbols.js.map +1 -1
- package/dist/core/mutate.js +107 -0
- package/dist/core/mutate.js.map +1 -0
- package/dist/core/output-capture.js +7 -4
- package/dist/core/output-capture.js.map +1 -1
- package/dist/core/paths.js +2 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/performance.js +43 -17
- package/dist/core/performance.js.map +1 -1
- package/dist/core/profile.js +6 -3
- package/dist/core/profile.js.map +1 -1
- package/dist/core/reader.js +32 -16
- package/dist/core/reader.js.map +1 -1
- package/dist/core/readerSession.js +34 -14
- package/dist/core/readerSession.js.map +1 -1
- package/dist/core/recent.js +20 -6
- package/dist/core/recent.js.map +1 -1
- package/dist/core/regress.js +21 -12
- package/dist/core/regress.js.map +1 -1
- package/dist/core/repair.js +22 -4
- package/dist/core/repair.js.map +1 -1
- package/dist/core/research-queries.js +85 -0
- package/dist/core/research-queries.js.map +1 -0
- package/dist/core/research.js +89 -88
- package/dist/core/research.js.map +1 -1
- package/dist/core/results.js +15 -6
- package/dist/core/results.js.map +1 -1
- package/dist/core/risk-signals.js +53 -15
- package/dist/core/risk-signals.js.map +1 -1
- package/dist/core/run-id.js +10 -6
- package/dist/core/run-id.js.map +1 -1
- package/dist/core/scenarios.js +91 -60
- package/dist/core/scenarios.js.map +1 -1
- package/dist/core/session.js +3 -2
- package/dist/core/session.js.map +1 -1
- package/dist/core/size.js +28 -8
- package/dist/core/size.js.map +1 -1
- package/dist/core/skills.js +66 -28
- package/dist/core/skills.js.map +1 -1
- package/dist/core/skillsmp.js +26 -12
- package/dist/core/skillsmp.js.map +1 -1
- package/dist/core/source-basis.js +21 -9
- package/dist/core/source-basis.js.map +1 -1
- package/dist/core/status.js +26 -13
- package/dist/core/status.js.map +1 -1
- package/dist/core/table.js +8 -1
- package/dist/core/table.js.map +1 -1
- package/dist/core/tokens.js +20 -10
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/tree.js +26 -16
- package/dist/core/tree.js.map +1 -1
- package/dist/core/verification-plan.js +24 -8
- package/dist/core/verification-plan.js.map +1 -1
- package/dist/core/verification.js +14 -7
- package/dist/core/verification.js.map +1 -1
- package/dist/core/view.js +93 -44
- package/dist/core/view.js.map +1 -1
- package/dist/install/global.js +37 -18
- package/dist/install/global.js.map +1 -1
- package/dist/install/hooks.js +17 -8
- package/dist/install/hooks.js.map +1 -1
- package/dist/install/manifest.js +11 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/mcpb.js +52 -23
- package/dist/install/mcpb.js.map +1 -1
- package/dist/install/plugin.js +73 -33
- package/dist/install/plugin.js.map +1 -1
- package/dist/install/project.js +8 -7
- package/dist/install/project.js.map +1 -1
- package/dist/install/register.js +83 -48
- package/dist/install/register.js.map +1 -1
- package/dist/install/tree.js +27 -11
- package/dist/install/tree.js.map +1 -1
- package/dist/install/update.js +46 -20
- package/dist/install/update.js.map +1 -1
- package/hooks/code-map.cjs +22 -9
- package/hooks/journal.cjs +18 -16
- package/hooks/knowledge-auto.cjs +55 -21
- package/hooks/knowledge-journal.cjs +47 -20
- package/hooks/knowledge-projects.cjs +50 -21
- package/hooks/knowledge-recall.cjs +16 -6
- package/hooks/knowledge-upkeep.cjs +22 -5
- package/hooks/locations.cjs +35 -0
- package/hooks/notify.js +52 -24
- package/hooks/obsidian.cjs +17 -7
- package/hooks/private-store.cjs +25 -10
- package/hooks/retest.cjs +39 -16
- package/hooks/session-context.cjs +30 -13
- package/hooks/session-files.cjs +44 -21
- package/hooks/session-repair.cjs +26 -11
- package/hooks/session-state.cjs +78 -38
- package/hooks/session.js +19 -8
- package/hooks/slice-command.cjs +38 -17
- package/hooks/slice-guard.cjs +20 -6
- package/hooks/slice-store.cjs +25 -12
- package/internal/guides/code.md +2 -0
- package/mcpb/server/index.js +40 -12
- package/package.json +1 -1
package/dist/core/regress.js
CHANGED
|
@@ -7,22 +7,25 @@ import { usage } from './errors.js';
|
|
|
7
7
|
import { loadScenarios } from './intent.js';
|
|
8
8
|
import { record } from './ledger.js';
|
|
9
9
|
import { vibePath } from './paths.js';
|
|
10
|
-
import {
|
|
10
|
+
import { EVIDENCE_DIR } from './run-id.js';
|
|
11
|
+
import { parseScenarios, SCENARIO_ID_MAX } from './scenarios.js';
|
|
11
12
|
import { ensureDir, readJson, readText, writeAtomic } from './store.js';
|
|
12
13
|
/**
|
|
13
14
|
* Regressions — a fixed failure becomes a reproducing check that joins every `check --all`.
|
|
14
15
|
* One file = one scenario. The original check is copied; the id gets an `r-` prefix.
|
|
15
16
|
*/
|
|
17
|
+
const REGRESSIONS_DIR = 'regressions';
|
|
18
|
+
const REGRESSION_PREFIX = 'r-';
|
|
19
|
+
const DEFAULT_SLUG = 'regression';
|
|
16
20
|
export function regressionsDir(root) {
|
|
17
|
-
return vibePath(root,
|
|
21
|
+
return vibePath(root, REGRESSIONS_DIR);
|
|
18
22
|
}
|
|
19
|
-
const ID_MAX = 40;
|
|
20
23
|
/** `r-<n>-<slug>` within the 40-character id rule — the slug gives way, the number never does. */
|
|
21
24
|
function regressionId(n, title) {
|
|
22
|
-
const prefix =
|
|
25
|
+
const prefix = `${REGRESSION_PREFIX}${n}-`;
|
|
23
26
|
const words = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
24
|
-
const slug = words.slice(0,
|
|
25
|
-
return `${prefix}${slug ||
|
|
27
|
+
const slug = words.slice(0, SCENARIO_ID_MAX - prefix.length).replace(/-+$/g, '');
|
|
28
|
+
return `${prefix}${slug || DEFAULT_SLUG}`.slice(0, SCENARIO_ID_MAX).replace(/-+$/g, '');
|
|
26
29
|
}
|
|
27
30
|
function readRegressionFiles(root) {
|
|
28
31
|
const dir = regressionsDir(root);
|
|
@@ -45,7 +48,9 @@ export function listRegressions(root) {
|
|
|
45
48
|
}
|
|
46
49
|
/** Regression files that parse to nothing — a silent gap in `check --all`, so the state view names them. */
|
|
47
50
|
export function regressionProblems(root) {
|
|
48
|
-
return readRegressionFiles(root)
|
|
51
|
+
return readRegressionFiles(root)
|
|
52
|
+
.filter((f) => f.rejections.length > 0)
|
|
53
|
+
.map((f) => `${REGRESSIONS_DIR}/${f.name} is not checked — ${f.rejections.join('; ')}`);
|
|
49
54
|
}
|
|
50
55
|
export function recordRegression(root, input) {
|
|
51
56
|
const source = loadScenarios(root).find((s) => s.id === input.scenario);
|
|
@@ -53,15 +58,17 @@ export function recordRegression(root, input) {
|
|
|
53
58
|
throw usage(`unknown scenario: ${input.scenario}`);
|
|
54
59
|
if (source.check.type === 'human')
|
|
55
60
|
throw usage('a human scenario cannot become a regression check — it has no verdict');
|
|
56
|
-
if (source.irreversible)
|
|
57
|
-
|
|
61
|
+
if (source.irreversible) {
|
|
62
|
+
const action = actionOf(source.irreversible);
|
|
63
|
+
throw usage(`a regression must observe — ${source.id} mutates (${action}); write a check that reproduces the failure without changing state`);
|
|
64
|
+
}
|
|
58
65
|
if (!input.title.trim())
|
|
59
66
|
throw usage('--title is required');
|
|
60
67
|
const existing = readRegressionFiles(root).length;
|
|
61
68
|
const id = regressionId(existing + 1, input.title);
|
|
62
69
|
let tail = '';
|
|
63
70
|
if (input.fromEvidence) {
|
|
64
|
-
const evidence = readJson(vibePath(root,
|
|
71
|
+
const evidence = readJson(vibePath(root, EVIDENCE_DIR, `${input.fromEvidence}.json`));
|
|
65
72
|
tail = evidence?.results?.find((r) => r.id === input.scenario)?.failureCode ?? '';
|
|
66
73
|
}
|
|
67
74
|
const entry = { id, then: `[regression] ${input.title} — source ${source.id}: ${source.then}`, check: source.check };
|
|
@@ -69,12 +76,14 @@ export function recordRegression(root, input) {
|
|
|
69
76
|
entry.given = source.given;
|
|
70
77
|
if (source.when)
|
|
71
78
|
entry.when = source.when;
|
|
72
|
-
const
|
|
79
|
+
const output = tail ? `\n# Output at the time:\n${tail.split('\n').map((l) => `# ${l}`).join('\n')}` : '';
|
|
80
|
+
const body = `# Regression check — ${input.title}\n# Source scenario: ${source.id}${output}\n${YAML.stringify([entry])}`;
|
|
73
81
|
ensureDir(regressionsDir(root));
|
|
74
82
|
const file = path.join(regressionsDir(root), `${id}.yaml`);
|
|
75
83
|
const parsed = parseScenarios(body);
|
|
76
|
-
if (parsed.scenarios.length !== 1)
|
|
84
|
+
if (parsed.scenarios.length !== 1) {
|
|
77
85
|
throw usage(`the regression would not parse: ${parsed.rejections.map((r) => r.reason).join('; ')}`);
|
|
86
|
+
}
|
|
78
87
|
writeAtomic(file, body);
|
|
79
88
|
const edges = [{ type: 'caused', from: `regression:${id}`, to: `scenario:${source.id}` }];
|
|
80
89
|
if (input.fromEvidence)
|
package/dist/core/regress.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regress.js","sourceRoot":"","sources":["../../src/core/regress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAiB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"regress.js","sourceRoot":"","sources":["../../src/core/regress.ts"],"names":[],"mappings":"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"}
|
package/dist/core/repair.js
CHANGED
|
@@ -5,6 +5,11 @@ import { maskFailureLine } from './failure-message.js';
|
|
|
5
5
|
import { safeText } from './evidence.js';
|
|
6
6
|
export const DIAGNOSE_AFTER = 2;
|
|
7
7
|
export const ASK_AFTER = 5;
|
|
8
|
+
/** Longest declared approach kept in the repair record. */
|
|
9
|
+
const APPROACH_MAX = 240;
|
|
10
|
+
const APPROACH_NOT_RECORDED = 'not recorded';
|
|
11
|
+
const NO_FAILURE_DETAILS = 'failure details unavailable';
|
|
12
|
+
const STUCK_OPTIONS = ['provide missing information', 'revise the agreed scope', 'handoff remaining work'];
|
|
8
13
|
export function pendingRepairQuestion(root, repair) {
|
|
9
14
|
if (!repair?.waiting)
|
|
10
15
|
return undefined;
|
|
@@ -13,21 +18,34 @@ export function pendingRepairQuestion(root, repair) {
|
|
|
13
18
|
export function repairAwaitingInput(root, repair) {
|
|
14
19
|
return Boolean(repair?.waiting && !foldQuestions(root).find(q => q.id === repair.questionId)?.answer?.trim());
|
|
15
20
|
}
|
|
21
|
+
function stuckQuestion(hash, failStreak, failures, attempts) {
|
|
22
|
+
const approaches = attempts.map((item) => `${item.run}: ${JSON.stringify(item.approach)}`).join('; ');
|
|
23
|
+
return [
|
|
24
|
+
`STUCK ${hash}: same failure ${failStreak} times; repair limit reached.`,
|
|
25
|
+
`Untrusted failure summaries: ${failures.map(failureLine).join('\n')}`,
|
|
26
|
+
`Declared approaches (untrusted): ${approaches}. What new information or decision should guide the next attempt?`,
|
|
27
|
+
].join('\n');
|
|
28
|
+
}
|
|
16
29
|
export function repairFailure(root, current, hash, failures, run, approach) {
|
|
17
30
|
const same = hash === current.lastFailHash;
|
|
18
31
|
const failStreak = same ? current.failStreak + 1 : 1;
|
|
19
|
-
const
|
|
32
|
+
const declared = approach?.trim() ? safeText(maskFailureLine(approach.trim(), root), APPROACH_MAX) : APPROACH_NOT_RECORDED;
|
|
33
|
+
const attempts = [...(same ? current.repair?.attempts ?? [] : []), { run, approach: declared }].slice(-ASK_AFTER);
|
|
20
34
|
const repair = { hash, attempts, failures, waiting: failStreak >= ASK_AFTER };
|
|
21
35
|
if (repair.waiting) {
|
|
22
36
|
const existing = openQuestions(root).find(q => q.question.startsWith(`STUCK ${hash}:`));
|
|
23
|
-
repair.questionId = existing?.id ?? ask(root, { question:
|
|
37
|
+
repair.questionId = existing?.id ?? ask(root, { question: stuckQuestion(hash, failStreak, failures, attempts), options: STUCK_OPTIONS }).id;
|
|
24
38
|
}
|
|
25
39
|
return { state: failStreak >= DIAGNOSE_AFTER ? 'STUCK' : 'RUNNING', failStreak, lastFailHash: hash, repair };
|
|
26
40
|
}
|
|
27
41
|
export function repairNext(repair) {
|
|
28
|
-
const facts = repair?.failures.map(failureLine).join(' | ') ??
|
|
42
|
+
const facts = repair?.failures.map(failureLine).join(' | ') ?? NO_FAILURE_DETAILS;
|
|
29
43
|
const contexts = repair?.failures.map((failure) => `vibe context ${failure.id}`).join('; ') || 'vibe context <id>';
|
|
30
|
-
return
|
|
44
|
+
return [
|
|
45
|
+
`prove — STUCK diagnosis: ${facts}; ${contexts}; change the approach instead of repeating the same fix:`,
|
|
46
|
+
'write the one premise the failed fixes shared and test it before another fix;',
|
|
47
|
+
'then vibe check <id> --approach "the premise and what changed"',
|
|
48
|
+
].join(' ');
|
|
31
49
|
}
|
|
32
50
|
export function resumeRepair(root, state) {
|
|
33
51
|
if (!state.repair?.waiting)
|
package/dist/core/repair.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/core/repair.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/core/repair.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { intentPath, loadScenarios } from './intent.js';
|
|
3
|
+
import { readJson, readText } from './store.js';
|
|
4
|
+
/** Words in one query, queries drawn from the intent body, and queries in all (with the host and stack queries). */
|
|
5
|
+
const QUERY_WORDS_MAX = 4;
|
|
6
|
+
const BODY_QUERIES_MAX = 3;
|
|
7
|
+
const QUERIES_MAX = 5;
|
|
8
|
+
/** Intent words shorter than this, or repeated fewer times, are not terms; query words below this length are noise. */
|
|
9
|
+
export const TERM_MIN_LENGTH = 4;
|
|
10
|
+
const TERM_MIN_REPEATS = 2;
|
|
11
|
+
/** Dependencies named in a query. */
|
|
12
|
+
const QUERY_DEPS_MAX = 2;
|
|
13
|
+
const STOP = new Set([
|
|
14
|
+
'a', 'an', 'the', 'and', 'or', 'of', 'to', 'in', 'for', 'with', 'vibe', 'phase', 'into', 'from', 'on', 'by', 'is', 'it',
|
|
15
|
+
// words every intent uses — they describe the harness, not the work
|
|
16
|
+
'check', 'checks', 'scenario', 'scenarios', 'intent', 'harness', 'model', 'client', 'test', 'tests', 'file', 'files',
|
|
17
|
+
'line', 'lines', 'when', 'then', 'given', 'that', 'this', 'each', 'every', 'same', 'other', 'than', 'under', 'over',
|
|
18
|
+
'first', 'names', 'name', 'none', 'nothing', 'stays', 'still', 'earlier', 'gates', 'build', 'live',
|
|
19
|
+
]);
|
|
20
|
+
const SUCCESS_HEADING = /^## What counts as success/m;
|
|
21
|
+
export function tokens(text) {
|
|
22
|
+
return [...new Set(text.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2 && !STOP.has(w)))];
|
|
23
|
+
}
|
|
24
|
+
/** A host named by an http check, as a search key: without `www.`/`api.` and the domain suffix. Throws on a string that is not a URL. */
|
|
25
|
+
export function hostKey(url) {
|
|
26
|
+
return new URL(url).hostname.replace(/^(www|api)\./, '').split('.')[0];
|
|
27
|
+
}
|
|
28
|
+
/** The words the intent body actually repeats: backticked names first, then frequent words of `TERM_MIN_LENGTH` letters or more. */
|
|
29
|
+
function bodyTerms(text) {
|
|
30
|
+
const section = text.split(SUCCESS_HEADING)[1]?.split(/^## /m)[0] ?? text;
|
|
31
|
+
const count = new Map();
|
|
32
|
+
for (const m of section.matchAll(/`([a-z][a-z0-9-]{3,})`/g))
|
|
33
|
+
count.set(m[1], (count.get(m[1]) ?? 0) + 1);
|
|
34
|
+
const names = [...count.entries()].filter(([w]) => !STOP.has(w)).sort((a, b) => b[1] - a[1]).map(([w]) => w);
|
|
35
|
+
const freq = new Map();
|
|
36
|
+
for (const w of section.toLowerCase().replace(/`[^`]*`/g, ' ').split(/[^a-z0-9]+/)) {
|
|
37
|
+
if (w.length < TERM_MIN_LENGTH || STOP.has(w) || /^\d/.test(w))
|
|
38
|
+
continue;
|
|
39
|
+
freq.set(w, (freq.get(w) ?? 0) + 1);
|
|
40
|
+
}
|
|
41
|
+
const words = [...freq.entries()].filter(([, n]) => n >= TERM_MIN_REPEATS).sort((a, b) => b[1] - a[1]).map(([w]) => w);
|
|
42
|
+
return { names, words };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Queries come from what the intent body names and repeats — never from the title's first words —
|
|
46
|
+
* then the hosts its http checks target and the stack.
|
|
47
|
+
*/
|
|
48
|
+
export function queriesFromIntent(root) {
|
|
49
|
+
const queries = [];
|
|
50
|
+
const text = readText(intentPath(root)) ?? '';
|
|
51
|
+
const { names, words } = bodyTerms(text);
|
|
52
|
+
const push = (parts) => {
|
|
53
|
+
const q = [...new Set(parts.filter(Boolean))].slice(0, QUERY_WORDS_MAX).join(' ');
|
|
54
|
+
if (q.split(' ').length >= 2 && !queries.includes(q))
|
|
55
|
+
queries.push(q);
|
|
56
|
+
};
|
|
57
|
+
push(words.slice(0, 3));
|
|
58
|
+
push([names[0] ?? '', ...words.filter((w) => w !== names[0]).slice(0, 2)]);
|
|
59
|
+
push(names.slice(1, 4));
|
|
60
|
+
if (queries.length === 0) { // a body too thin to name anything twice: the title is all there is
|
|
61
|
+
const heading = text.split('\n').find((l) => l.startsWith('#')) ?? '';
|
|
62
|
+
const title = tokens(heading.replace(/^#+/, '')).slice(0, QUERY_WORDS_MAX);
|
|
63
|
+
push([...names.slice(0, 1), ...title]);
|
|
64
|
+
words.push(...title);
|
|
65
|
+
}
|
|
66
|
+
const body = queries.slice(0, BODY_QUERIES_MAX);
|
|
67
|
+
for (const s of loadScenarios(root)) {
|
|
68
|
+
if (s.check.type !== 'http')
|
|
69
|
+
continue;
|
|
70
|
+
try {
|
|
71
|
+
const host = hostKey(s.check.url);
|
|
72
|
+
if (host && !body.includes(`${host} sdk`))
|
|
73
|
+
body.push(`${host} sdk`);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* not a URL — nothing to ask */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const pkg = readJson(path.join(root, 'package.json'));
|
|
80
|
+
const deps = Object.keys(pkg?.dependencies ?? {}).filter((d) => !d.startsWith('@types/')).slice(0, QUERY_DEPS_MAX);
|
|
81
|
+
if (deps.length > 0 && words[0])
|
|
82
|
+
body.push(`${deps.join(' ')} ${words[0]}`);
|
|
83
|
+
return body.slice(0, QUERIES_MAX);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=research-queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-queries.js","sourceRoot":"","sources":["../../src/core/research-queries.ts"],"names":[],"mappings":"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"}
|
package/dist/core/research.js
CHANGED
|
@@ -1,91 +1,53 @@
|
|
|
1
|
-
import { skillsmpClient } from './skillsmp.js';
|
|
1
|
+
import { skillsmpClient, SKILLSMP_UNAVAILABLE } from './skillsmp.js';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { detectClient, detectModel } from './client.js';
|
|
6
6
|
import { readConfig } from './config.js';
|
|
7
|
-
import { applyWindow } from './recent.js';
|
|
7
|
+
import { applyWindow, DAY_MS } from './recent.js';
|
|
8
8
|
import { usage } from './errors.js';
|
|
9
9
|
import { githubClient } from './github.js';
|
|
10
|
-
import { intentPath, loadScenarios } from './intent.js';
|
|
11
10
|
import { record } from './ledger.js';
|
|
12
11
|
import { vibePath } from './paths.js';
|
|
13
|
-
import {
|
|
12
|
+
import { queriesFromIntent, tokens, TERM_MIN_LENGTH } from './research-queries.js';
|
|
13
|
+
import { readJson, writeAtomic, writeJson, nowIso } from './store.js';
|
|
14
|
+
export { queriesFromIntent };
|
|
14
15
|
export const SOURCES = ['repos', 'code', 'skills'];
|
|
15
|
-
const CACHE_TTL_MS =
|
|
16
|
+
const CACHE_TTL_MS = DAY_MS;
|
|
17
|
+
/** A result with warnings (a source was down) is kept only briefly, so the next run tries again. */
|
|
18
|
+
const WARNED_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
19
|
+
/** Bumped when the cached shape or the sources change, so an old cache is not read as a current answer. */
|
|
20
|
+
const CACHE_KEY_VERSION = 'skillsmp-v1';
|
|
21
|
+
const CACHE_KEY_LENGTH = 16;
|
|
16
22
|
const DEFAULT_MAX = 5;
|
|
17
23
|
const DEFAULT_DAYS = 30;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
continue;
|
|
36
|
-
freq.set(w, (freq.get(w) ?? 0) + 1);
|
|
37
|
-
}
|
|
38
|
-
const words = [...freq.entries()].filter(([, n]) => n >= 2).sort((a, b) => b[1] - a[1]).map(([w]) => w);
|
|
39
|
-
return { names, words };
|
|
40
|
-
}
|
|
41
|
-
/** Queries come from what the intent body names and repeats — never from the title's first words — then the hosts its http checks target and the stack. */
|
|
42
|
-
export function queriesFromIntent(root) {
|
|
43
|
-
const queries = [];
|
|
44
|
-
const text = readText(intentPath(root)) ?? '';
|
|
45
|
-
const { names, words } = bodyTerms(text);
|
|
46
|
-
const push = (parts) => {
|
|
47
|
-
const q = [...new Set(parts.filter(Boolean))].slice(0, 4).join(' ');
|
|
48
|
-
if (q.split(' ').length >= 2 && !queries.includes(q))
|
|
49
|
-
queries.push(q);
|
|
50
|
-
};
|
|
51
|
-
push(words.slice(0, 3));
|
|
52
|
-
push([names[0] ?? '', ...words.filter((w) => w !== names[0]).slice(0, 2)]);
|
|
53
|
-
push(names.slice(1, 4));
|
|
54
|
-
if (queries.length === 0) { // a body too thin to name anything twice: the title is all there is
|
|
55
|
-
const heading = text.split('\n').find((l) => l.startsWith('#')) ?? '';
|
|
56
|
-
const title = tokens(heading.replace(/^#+/, '')).slice(0, 4);
|
|
57
|
-
push([...names.slice(0, 1), ...title]);
|
|
58
|
-
words.push(...title);
|
|
59
|
-
}
|
|
60
|
-
const body = queries.slice(0, 3);
|
|
61
|
-
for (const s of loadScenarios(root)) {
|
|
62
|
-
if (s.check.type !== 'http')
|
|
63
|
-
continue;
|
|
64
|
-
try {
|
|
65
|
-
const host = new URL(s.check.url).hostname.replace(/^(www|api)\./, '').split('.')[0];
|
|
66
|
-
if (host && !body.includes(`${host} sdk`))
|
|
67
|
-
body.push(`${host} sdk`);
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
/* not a URL — nothing to ask */
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const pkg = readJson(path.join(root, 'package.json'));
|
|
74
|
-
const deps = Object.keys(pkg?.dependencies ?? {}).filter((d) => !d.startsWith('@types/')).slice(0, 2);
|
|
75
|
-
if (deps.length > 0 && words[0])
|
|
76
|
-
body.push(`${deps.join(' ')} ${words[0]}`);
|
|
77
|
-
return body.slice(0, 5);
|
|
78
|
-
}
|
|
24
|
+
const GITHUB_WEB = 'https://github.com';
|
|
25
|
+
/** Results asked of each GitHub search request. */
|
|
26
|
+
const PER_SEARCH = 5;
|
|
27
|
+
/** Score: points per matched query word, recency bands, and the cap of the stars bonus (log10 of the stars). */
|
|
28
|
+
const MATCH_POINTS = 3;
|
|
29
|
+
const FRESH_DAYS = 180;
|
|
30
|
+
const STALE_DAYS = 365;
|
|
31
|
+
const STARS_BONUS_MAX = 4;
|
|
32
|
+
const SLUG_MAX = 40;
|
|
33
|
+
/** A catalog skill name also matches the first letters of a longer query word. */
|
|
34
|
+
const PREFIX_MATCH_LENGTH = 4;
|
|
35
|
+
const DEFAULT_SLUG = 'research';
|
|
36
|
+
const NOTE_DATE_LENGTH = 10;
|
|
37
|
+
const GITHUB_FIXTURE_ENV = 'VIBE_GITHUB_FIXTURE';
|
|
38
|
+
const SKILLSMP_ENV = 'VIBE_SKILLSMP';
|
|
39
|
+
const NOTE_MONTH_LENGTH = 7;
|
|
40
|
+
const UNKNOWN_CELL = '—';
|
|
79
41
|
function score(text, queryTokens, stars, updatedAt, license) {
|
|
80
42
|
const haystack = text.toLowerCase();
|
|
81
43
|
const matched = queryTokens.filter((t) => haystack.includes(t));
|
|
82
|
-
let total = matched.length *
|
|
44
|
+
let total = matched.length * MATCH_POINTS;
|
|
83
45
|
if (updatedAt) {
|
|
84
46
|
const age = Date.now() - new Date(updatedAt).getTime();
|
|
85
|
-
total += age <
|
|
47
|
+
total += age < FRESH_DAYS * DAY_MS ? 2 : age < STALE_DAYS * DAY_MS ? 1 : 0;
|
|
86
48
|
}
|
|
87
49
|
if (stars)
|
|
88
|
-
total += Math.min(Math.log10(stars),
|
|
50
|
+
total += Math.min(Math.log10(stars), STARS_BONUS_MAX);
|
|
89
51
|
if (license && license !== 'NOASSERTION')
|
|
90
52
|
total += 1;
|
|
91
53
|
return { score: total, matched };
|
|
@@ -93,7 +55,7 @@ function score(text, queryTokens, stars, updatedAt, license) {
|
|
|
93
55
|
function why(matched, updatedAt, stars, license) {
|
|
94
56
|
const parts = [matched.length ? `matches "${matched.join(', ')}"` : 'no keyword match'];
|
|
95
57
|
if (updatedAt)
|
|
96
|
-
parts.push(`updated ${updatedAt.slice(0,
|
|
58
|
+
parts.push(`updated ${updatedAt.slice(0, NOTE_MONTH_LENGTH)}`);
|
|
97
59
|
if (stars !== null)
|
|
98
60
|
parts.push(`${stars} stars`);
|
|
99
61
|
parts.push(license ? license : 'no license');
|
|
@@ -107,7 +69,8 @@ function fromRepo(item, queryTokens) {
|
|
|
107
69
|
const license = item.license?.spdx_id ?? null;
|
|
108
70
|
const s = score(`${item.full_name} ${item.description ?? ''}`, queryTokens, stars, updatedAt, license);
|
|
109
71
|
const action = license ? `knowledge: save a reference note on ${item.full_name}` : 'none — license unknown';
|
|
110
|
-
|
|
72
|
+
const reason = why(s.matched, updatedAt, stars, license);
|
|
73
|
+
return { kind: 'repo', ref: item.full_name, url: item.html_url, why: reason, stars, updatedAt, license, action, score: s.score };
|
|
111
74
|
}
|
|
112
75
|
function fromCode(item, queryTokens) {
|
|
113
76
|
const repo = item.repository;
|
|
@@ -116,8 +79,12 @@ function fromCode(item, queryTokens) {
|
|
|
116
79
|
const dir = path.posix.dirname(item.path);
|
|
117
80
|
const name = dir === '.' ? '' : path.posix.basename(dir);
|
|
118
81
|
const ref = name ? `${repo.full_name}@${name}` : repo.full_name;
|
|
119
|
-
const
|
|
120
|
-
|
|
82
|
+
const updatedAt = repo.pushed_at ?? null;
|
|
83
|
+
const license = repo.license?.spdx_id ?? null;
|
|
84
|
+
const s = score(`${repo.full_name} ${item.path} ${repo.description ?? ''}`, queryTokens, null, updatedAt, license);
|
|
85
|
+
const url = `${repo.html_url ?? `${GITHUB_WEB}/${repo.full_name}`}/tree/HEAD/${dir === '.' ? '' : dir}`;
|
|
86
|
+
const reason = why(s.matched, updatedAt, null, license);
|
|
87
|
+
return { kind: 'skill', ref, url, why: reason, stars: null, updatedAt, license, action: `vibe skill add ${ref}`, score: s.score };
|
|
121
88
|
}
|
|
122
89
|
/** One tree request per catalog; a skill is any directory holding SKILL.md, at any depth under `skills/` or the root. */
|
|
123
90
|
export function skillDirsInTree(entries) {
|
|
@@ -144,10 +111,16 @@ async function catalogCandidates(client, catalog, queryTokens) {
|
|
|
144
111
|
const out = [];
|
|
145
112
|
for (const { name, dir } of skillDirsInTree(entries)) {
|
|
146
113
|
const leaf = path.posix.basename(name);
|
|
147
|
-
const
|
|
114
|
+
const matchesName = (t) => name.toLowerCase().includes(t)
|
|
115
|
+
|| (t.length >= PREFIX_MATCH_LENGTH && leaf.includes(t.slice(0, PREFIX_MATCH_LENGTH)));
|
|
116
|
+
const matched = queryTokens.filter(matchesName);
|
|
148
117
|
if (matched.length === 0)
|
|
149
118
|
continue;
|
|
150
|
-
|
|
119
|
+
const ref = `${catalog}@${name}`;
|
|
120
|
+
out.push({
|
|
121
|
+
kind: 'skill', ref, url: `${GITHUB_WEB}/${catalog}/tree/HEAD/${dir}`, why: `catalog ${catalog} · matches "${matched.join(', ')}"`,
|
|
122
|
+
stars: null, updatedAt: null, license: null, action: `vibe skill add ${ref}`, score: matched.length * MATCH_POINTS + 2,
|
|
123
|
+
});
|
|
151
124
|
}
|
|
152
125
|
return out;
|
|
153
126
|
}
|
|
@@ -156,7 +129,7 @@ async function searchRepos(client, query) {
|
|
|
156
129
|
const words = query.split(' ').filter(Boolean);
|
|
157
130
|
for (let n = words.length; n >= 1; n -= 1) {
|
|
158
131
|
const q = words.slice(0, n).join(' ');
|
|
159
|
-
const res = (await client.get(`/search/repositories?q=${encodeURIComponent(q)}&sort=updated&per_page
|
|
132
|
+
const res = (await client.get(`/search/repositories?q=${encodeURIComponent(q)}&sort=updated&per_page=${PER_SEARCH}`));
|
|
160
133
|
if ((res.items ?? []).length > 0 || n === 1)
|
|
161
134
|
return res.items ?? [];
|
|
162
135
|
}
|
|
@@ -175,7 +148,7 @@ async function search(client, queries, sources, catalogs) {
|
|
|
175
148
|
}
|
|
176
149
|
}
|
|
177
150
|
if (sources.includes('code') && client.authenticated) {
|
|
178
|
-
const res = (await client.get(`/search/code?q=${encodeURIComponent(`${q} filename:SKILL.md`)}&per_page
|
|
151
|
+
const res = (await client.get(`/search/code?q=${encodeURIComponent(`${q} filename:SKILL.md`)}&per_page=${PER_SEARCH}`));
|
|
179
152
|
for (const item of res.items ?? []) {
|
|
180
153
|
const c = fromCode(item, queryTokens);
|
|
181
154
|
if (c)
|
|
@@ -190,7 +163,7 @@ async function search(client, queries, sources, catalogs) {
|
|
|
190
163
|
// A candidate none of the query words describe is noise, whatever search returned it; so is one that matches only words under four letters.
|
|
191
164
|
for (const c of found) {
|
|
192
165
|
const matched = /matches "([^"]*)"/.exec(c.why)?.[1]?.split(', ') ?? [];
|
|
193
|
-
if (matched.length === 0 || matched.every((t) => t.length <
|
|
166
|
+
if (matched.length === 0 || matched.every((t) => t.length < TERM_MIN_LENGTH))
|
|
194
167
|
continue;
|
|
195
168
|
if (!byRef.has(c.ref) || byRef.get(c.ref).score < c.score)
|
|
196
169
|
byRef.set(c.ref, c);
|
|
@@ -198,13 +171,33 @@ async function search(client, queries, sources, catalogs) {
|
|
|
198
171
|
return [...byRef.values()].sort((a, b) => b.score - a.score);
|
|
199
172
|
}
|
|
200
173
|
function slug(text) {
|
|
201
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0,
|
|
174
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, SLUG_MAX) || DEFAULT_SLUG;
|
|
175
|
+
}
|
|
176
|
+
/** What moved last in a repository: its newest release, else its last commit. */
|
|
177
|
+
function latestActivity(c) {
|
|
178
|
+
if (!c.recent)
|
|
179
|
+
return UNKNOWN_CELL;
|
|
180
|
+
const release = c.recent.releases[0];
|
|
181
|
+
if (release)
|
|
182
|
+
return `${release.tag} ${release.at.slice(0, NOTE_DATE_LENGTH)}`;
|
|
183
|
+
return c.recent.lastCommitAt ? `commit ${c.recent.lastCommitAt.slice(0, NOTE_DATE_LENGTH)}` : 'none';
|
|
184
|
+
}
|
|
185
|
+
function noteRow(c) {
|
|
186
|
+
const inWindow = c.recent ? (c.recent.inWindow ? 'yes' : 'no') : UNKNOWN_CELL;
|
|
187
|
+
return `| ${c.kind} | [${c.ref}](${c.url}) | ${inWindow} | ${latestActivity(c)} | ${c.why} | ${c.action} |`;
|
|
202
188
|
}
|
|
203
189
|
function writeNote(root, queries, candidates, days, cutoff, warnings) {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
190
|
+
const date = nowIso().slice(0, NOTE_DATE_LENGTH);
|
|
191
|
+
const file = vibePath(root, 'knowledge', 'research', `${date}-${slug(queries[0] ?? '')}.md`);
|
|
192
|
+
const preamble = [
|
|
193
|
+
`Date: ${date} · Window: last ${days} days (since ${cutoff}) · Source: GitHub search, skill catalogs and optional SkillsMP discovery`,
|
|
194
|
+
'· GitHub results rank by activity inside the window, then keyword match, recency, stars and license;',
|
|
195
|
+
'SkillsMP hints are interleaved without a quality score. Nothing is dropped for being old. Nothing here was executed or installed.',
|
|
196
|
+
].join(' ');
|
|
197
|
+
const body = [
|
|
198
|
+
`# Research — ${queries.join(' · ')}`, '', preamble, ...warnings.map(w => `Search limitation: ${w}`), '',
|
|
199
|
+
'| kind | candidate | in window | latest | why | action |', '|---|---|---|---|---|---|', ...candidates.map(noteRow), '',
|
|
200
|
+
].join('\n');
|
|
208
201
|
writeAtomic(file, body);
|
|
209
202
|
return file;
|
|
210
203
|
}
|
|
@@ -225,7 +218,7 @@ async function searchSources(github, provider, queries, sources, catalogs, days,
|
|
|
225
218
|
discovered = await provider.search(queries[0]);
|
|
226
219
|
}
|
|
227
220
|
catch {
|
|
228
|
-
warnings.push(
|
|
221
|
+
warnings.push(SKILLSMP_UNAVAILABLE);
|
|
229
222
|
}
|
|
230
223
|
}
|
|
231
224
|
const outcome = await githubSearch;
|
|
@@ -245,9 +238,15 @@ async function searchSources(github, provider, queries, sources, catalogs, days,
|
|
|
245
238
|
unique.set(canonical(c), originals.get(canonical(c)) ?? c);
|
|
246
239
|
return [...unique.values()];
|
|
247
240
|
}
|
|
241
|
+
/** The marketplace searched next to GitHub: none under a test client, a fixture or `VIBE_SKILLSMP=off`. */
|
|
242
|
+
function marketplaceFor(client, marketplace) {
|
|
243
|
+
if (marketplace !== undefined)
|
|
244
|
+
return marketplace;
|
|
245
|
+
return client || process.env[GITHUB_FIXTURE_ENV] || process.env[SKILLSMP_ENV] === 'off' ? null : skillsmpClient();
|
|
246
|
+
}
|
|
248
247
|
export async function research(root, options, client, marketplace) {
|
|
249
248
|
const github = client ?? githubClient();
|
|
250
|
-
const provider =
|
|
249
|
+
const provider = marketplaceFor(client, marketplace);
|
|
251
250
|
const queries = options.fromIntent ? queriesFromIntent(root) : options.query ? [options.query] : [];
|
|
252
251
|
if (queries.length === 0)
|
|
253
252
|
throw usage(options.fromIntent ? 'no intent to research — draft one first' : 'research --from-intent | "query"');
|
|
@@ -255,15 +254,17 @@ export async function research(root, options, client, marketplace) {
|
|
|
255
254
|
const max = options.max ?? DEFAULT_MAX;
|
|
256
255
|
const days = options.days ?? DEFAULT_DAYS;
|
|
257
256
|
const now = options.now ?? Date.now();
|
|
258
|
-
const cutoff = new Date(now - days *
|
|
257
|
+
const cutoff = new Date(now - days * DAY_MS).toISOString().slice(0, 10);
|
|
259
258
|
const catalogs = readConfig(root).catalogs;
|
|
260
|
-
const key = createHash('sha256').update(JSON.stringify([
|
|
259
|
+
const key = createHash('sha256').update(JSON.stringify([CACHE_KEY_VERSION, queries, sources, catalogs, days, Boolean(provider)]))
|
|
260
|
+
.digest('hex').slice(0, CACHE_KEY_LENGTH);
|
|
261
261
|
const cacheFile = vibePath(root, 'cache', 'research', `${key}.json`);
|
|
262
262
|
const cached = readJson(cacheFile);
|
|
263
263
|
let candidates;
|
|
264
264
|
let fromCache = false;
|
|
265
265
|
let warnings = [];
|
|
266
|
-
|
|
266
|
+
const cacheTtl = cached?.warnings?.length ? WARNED_CACHE_TTL_MS : CACHE_TTL_MS;
|
|
267
|
+
if (cached && Date.now() - new Date(cached.at).getTime() < cacheTtl) {
|
|
267
268
|
candidates = cached.candidates;
|
|
268
269
|
warnings = cached.warnings ?? [];
|
|
269
270
|
fromCache = true;
|