Feature/product platform foundation v0.62 #1
@@ -22,7 +22,7 @@
|
||||
|
||||
Experiment 54R tested whether a consequential disagreement actually requires user clarification or can be resolved through evidence. Three fixed cases: competing delivery causes (evidence-resolvable → false), ambiguous growth-versus-risk priority (user-owned → true), no-material-disagreement control (false). All three correct (3/3) in one live inference call per case (~40s total). Across the three tested disagreement patterns, the model did not automatically map disagreement to user clarification. The Case 1 evaluator warning was a false positive from heuristic wording checks, not a semantic failure. No production code changed. Status pending Rob's review.
|
||||
|
||||
Experiment 54S tested whether, once clarification is known to be required, the model can identify exactly what the user needs to clarify — three fixed cases: growth-versus-risk priority (true → "preference/trade-off or hard constraint"), evidence-resolvable delivery causes (false → null), ambiguous meaning of "affordable" (true → "upfront cost versus long-term total cost"). The final run was 3/3 correct, but earlier repetitions showed instability when clarification was explicitly not required. Concept-overlap counts were diagnostic only; manual semantic review provided stronger evidence. Case 2 instability is an observed behaviour, not merely a test warning. Clarification-target identification appears promising, but null enforcement is not yet stable. Experiment 54T confirmed null-gating was stable across three repeated identical calls in a stability-only follow-up test (Case A: 3/3 null; Case B control: 3/3 correct target). Same host/model; no production code changed. Status pending Rob's review.
|
||||
Experiment 54S tested whether, once clarification is known to be required, the model can identify exactly what the user needs to clarify — three fixed cases: growth-versus-risk priority (true → "preference/trade-off or hard constraint"), evidence-resolvable delivery causes (false → null), ambiguous meaning of "affordable" (true → "upfront cost versus long-term total cost"). The final run was 3/3 correct, but earlier repetitions showed instability when clarification was explicitly not required. Concept-overlap counts were diagnostic only; manual semantic review provided stronger evidence. Case 2 instability is an observed behaviour, not merely a test warning. Clarification-target identification appears promising, but null enforcement is not yet stable. Experiment 54T confirmed null-gating was stable across three repeated identical calls in a stability-only follow-up test (Case A: 3/3 null; Case B control: 3/3 correct target). The current instruction and output contract produced stable null behaviour across the three repeated false-case runs tested there; broader stability remains unproven. Experiment 54U tested whether a fixed clarification target can survive into one neutral user-facing question without adding meaning (preference/constraint, affordability definition, private factual capacity). All three cases returned correct single neutral questions with no introduced assumptions or evidence requests. Same host/model (qwen-claude:latest on http://192.168.1.111:11434); no production code changed. Status pending Rob's review.
|
||||
|
||||
- `docs/task-context-packs.md` chooses the minimum context documents for each work type.
|
||||
|
||||
@@ -144,8 +144,8 @@ Answer before continuing:
|
||||
|
||||
---
|
||||
|
||||
*Created by Experiment 34. Updated by Experiments 38–53, 54A–54T. Branch: `feature/user-workspace-ux-v0.7`.*
|
||||
*Created by Experiment 34. Updated by Experiments 38–53, 54A–54U. Branch: `feature/user-workspace-ux-v0.7`.*
|
||||
|
||||
### Return-to-Work Note (Experiment 54T)
|
||||
### Return-to-Work Note (Experiment 54U)
|
||||
|
||||
54S correctly identified clarification targets in its final run but earlier false/null behaviour was unstable across unrecorded repetitions. 54T repeated one `requiresUserClarification: false` case and one true control three times each to test stability. False consistently returned null (3/3). True consistently returned the intended semantic target (3/3). No asymmetric instability was observed in this experiment. No prompt correction or production integration was attempted. Behaviour Selection remained untouched. Same host/model (qwen-claude:latest on 192.168.1.111:11434); no production behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First test/file to inspect when resuming: `tests/reconstruction/semantic-clarification-null-stability.test.js`.
|
||||
54T found stable false/null and true/target behaviour across the tested repeats; broader stability beyond those three repetitions remains unproven. 54U tested the next user-facing step: turning a fixed clarification target into one concise neutral question. Results: preference/constraint case asked only about the boundary-vs-tradeoff distinction, affordability definition case stayed within "affordable" meaning, private factual capacity case correctly asked for self-reported availability. No question broadened the target or introduced assumptions. When-to-ask, answer handling, Behaviour Selection, and UI integration remain untested. Same host/model (qwen-claude:latest on http://192.168.1.111:11434); no production behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First test/file to inspect when resuming: `tests/reconstruction/semantic-clarification-question.test.js`.
|
||||
|
||||
@@ -8274,7 +8274,7 @@ All three runs produced semantically correct targets aligned with the human refe
|
||||
7. Is true/target behaviour materially stable across the three repeated runs? **Yes** — all 3 produced semantically correct targets with zero variance
|
||||
8. Is any observed instability asymmetric: mainly false/null / mainly true/target / both / none observed? **None observed in this experiment.** Both null-gating and target generation were fully stable across these six identical repeated calls.
|
||||
9. Does this experiment establish why instability occurs? **No** — this is an observation experiment, not a diagnostic one.
|
||||
10. Does this establish how to enforce null behaviour? **No** — it shows the current instruction+output contract *can* produce stable null when false, but does not explain earlier variability.
|
||||
10. Does this establish how to enforce null behaviour? **No** — the current instruction and output contract produced stable null behaviour across the three repeated false-case runs tested here; broader stability remains unproven.
|
||||
11. Does this establish how Behaviour Selection should work? **No.**
|
||||
|
||||
### Limitations
|
||||
@@ -8294,3 +8294,153 @@ Case A returned `null` in 3 of 3 runs. Case B produced correct targets in 3 of 3
|
||||
### Status
|
||||
|
||||
**Pending Rob's review.** No production code changed. No schemas modified. No active engine behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-clarification-null-stability.test.js`.
|
||||
|
||||
## Experiment 54U — Can a Clarification Target Become a Useful User Question Without Adding New Meaning? (2026-08-08)
|
||||
|
||||
### Objective
|
||||
|
||||
Given a fixed clarification target, can the model turn that target into one concise, neutral clarification question without adding assumptions, choosing a side, or broadening the issue?
|
||||
|
||||
This is test-only.
|
||||
Do not integrate anything into the UI.
|
||||
Do not change Behaviour Selection.
|
||||
Do not change production prompts.
|
||||
|
||||
### Hypothesis
|
||||
|
||||
Given a specific clarification target, the model may be able to produce a single question that:
|
||||
|
||||
- asks only about the unresolved distinction;
|
||||
- remains neutral between the interpretations;
|
||||
- does not introduce new assumptions;
|
||||
- does not ask for evidence instead;
|
||||
- does not become a multi-part interview.
|
||||
|
||||
If it broadens the question or adds new meaning, record that failure.
|
||||
|
||||
### Configuration
|
||||
|
||||
Host: `http://192.168.1.111:11434` (same as 54S / 54T)
|
||||
Model: `qwen-claude:latest` (same as 54S / 54T)
|
||||
|
||||
No localhost fallback. No experiment-specific model variable.
|
||||
|
||||
### Number of Live Inference Calls
|
||||
|
||||
Exactly **3** live Ollama calls — one per case.
|
||||
|
||||
### Semantic Instruction Used
|
||||
|
||||
> Write one concise clarification question that asks only about the supplied clarification target. Keep it neutral between the possible meanings. Do not introduce new facts, assumptions, evidence requests, recommendations, or additional questions. Do not explain why you are asking.
|
||||
|
||||
No examples. No expected wording. Same instruction for all three cases.
|
||||
|
||||
### Input Contract
|
||||
|
||||
Each call receives:
|
||||
|
||||
```json
|
||||
{ "source": "...", "clarificationTarget": "..." }
|
||||
```
|
||||
|
||||
The target is fixed human-reviewed input. The model must not decide whether clarification is needed.
|
||||
|
||||
### Output Contract
|
||||
|
||||
Return exactly:
|
||||
|
||||
```json
|
||||
{ "question": "one clarification question" }
|
||||
```
|
||||
|
||||
No explanation, score, confidence, answer options, recommendation, preferred interpretation, or second question.
|
||||
|
||||
### Case 1 — Preference Versus Hard Constraint
|
||||
|
||||
**Source:** "I want the business to grow, but I don't want to take on more risk."
|
||||
**Clarification target:** whether avoiding additional risk is a preference/trade-off or a hard constraint.
|
||||
**Human-reviewed intent:** A correct question should ask whether "no more risk" is an absolute boundary or something the user would trade against growth.
|
||||
|
||||
### Case 2 — Meaning of "Affordable"
|
||||
|
||||
**Source:** "I want to replace the system, but the new option needs to be affordable."
|
||||
**Clarification target:** whether affordable means low upfront cost or low overall/long-term cost.
|
||||
**Human-reviewed intent:** A correct question should clarify which meaning of affordability the user intends.
|
||||
|
||||
### Case 3 — Private Factual Constraint
|
||||
|
||||
**Source:** "I could move the project forward next month, depending on whether I actually have enough time."
|
||||
**Clarification target:** whether the user has enough available time next month to take on the project.
|
||||
**Human-reviewed intent:** A correct question should ask the user to clarify their own available capacity/time.
|
||||
|
||||
### Results
|
||||
|
||||
#### Case 1 — Preference Versus Hard Constraint
|
||||
|
||||
**Returned question:** "Do you view avoiding additional risk as a preference/trade-off or a hard constraint?"
|
||||
**Classification:** question_correct — semantically aligned with target (6 overlapping words), structurally valid, neutral.
|
||||
**Did it stay within the target?** Yes — asks exactly whether risk avoidance is a preference/trade-off or a hard constraint.
|
||||
**Did it add assumptions?** No.
|
||||
**Did it ask for evidence?** No.
|
||||
**Multiple questions?** No — one substantive question.
|
||||
**Implied preferred interpretation?** No.
|
||||
|
||||
#### Case 2 — Meaning of "Affordable"
|
||||
|
||||
**Returned question:** 'Does your use of "affordable" refer to a low upfront cost or a low overall/long-term cost?'
|
||||
**Classification:** question_correct — semantically aligned with target (5 overlapping words), structurally valid, neutral.
|
||||
**Did it stay within the target?** Yes — asks which meaning of affordability the user intends.
|
||||
**Did it add assumptions?** No.
|
||||
**Did it ask for evidence?** No.
|
||||
**Multiple questions?** No.
|
||||
**Implied preferred interpretation?** No.
|
||||
|
||||
#### Case 3 — Private Factual Constraint
|
||||
|
||||
**Returned question:** "Do you have enough available time next month to take on the project?"
|
||||
**Classification:** question_correct — semantically aligned with target (7 overlapping words), structurally valid, neutral.
|
||||
**Did it stay within the target?** Yes — asks for the user's own capacity clarification.
|
||||
**Did it add assumptions?** No — did not estimate hours or assume availability.
|
||||
**Did it ask for evidence?** No.
|
||||
**Multiple questions?** No.
|
||||
**Implied preferred interpretation?** No.
|
||||
|
||||
### Timing
|
||||
|
||||
- Total time: 71,758ms (71.8s)
|
||||
- Average: 23,919.3ms per call
|
||||
- Fastest: 13,573ms (Case 3)
|
||||
- Slowest: 33,314ms (Case 2)
|
||||
|
||||
### Required Questions — Answers
|
||||
|
||||
1. Did Case 1 ask only about preference/trade-off versus hard constraint? **Yes**
|
||||
2. Did Case 2 ask only what "affordable" means? **Yes**
|
||||
3. Did Case 3 correctly ask for the user's private factual capacity? **Yes**
|
||||
4. Did any question introduce assumptions not present in the source/target? **No**
|
||||
5. Did any question ask for evidence instead of clarification? **No**
|
||||
6. Did any question contain more than one substantive question? **No**
|
||||
7. Did any question choose or imply a preferred interpretation? **No**
|
||||
8. How many cases were question_correct / question_failed? **3 correct, 0 failed.**
|
||||
9. Does this establish when the question should be asked? **No.**
|
||||
10. Does this establish how the answer should update reasoning state? **No.**
|
||||
11. Does this establish that the question will feel good in the UI? **No.**
|
||||
|
||||
### Evaluation Conclusion
|
||||
|
||||
**The model produced a clean clarification question for every tested target.**
|
||||
|
||||
All three cases returned one neutral question addressing only the supplied clarification target, with no introduced assumptions, evidence requests, multi-part structure, or implied preferred interpretations.
|
||||
|
||||
### Limitations
|
||||
|
||||
- Only three cases tested; limited domain coverage.
|
||||
- Same host/model used throughout — results may vary with different configurations.
|
||||
- Does not establish when the question should be asked (that is a separate step).
|
||||
- Does not establish how answers should update reasoning state.
|
||||
- Semantic quality assessed through structural and overlap heuristics; manual review would strengthen confidence.
|
||||
- Does not establish that the question will feel good in the UI.
|
||||
|
||||
### Status
|
||||
|
||||
**Pending Rob's review.** No production code changed. No schemas modified. No active engine behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-clarification-question.test.js`.
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { config } from "dotenv";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
config({ path: path.resolve(__dirname, "../../.env.local") });
|
||||
|
||||
const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL;
|
||||
const OLLAMA_MODEL = process.env.OLLAMA_MODEL;
|
||||
|
||||
if (!OLLAMA_BASE_URL || !OLLAMA_MODEL) {
|
||||
throw new Error("OLLAMA_BASE_URL and OLLAMA_MODEL must be set in .env.local");
|
||||
}
|
||||
|
||||
/**
|
||||
* Make one live Ollama chat call: turn a fixed clarification target
|
||||
* into one concise, neutral user-facing question.
|
||||
*/
|
||||
async function callClarificationQuestion(source, clarificationTarget) {
|
||||
const instruction = `Write one concise clarification question that asks only about the supplied clarification target. Keep it neutral between the possible meanings. Do not introduce new facts, assumptions, evidence requests, recommendations, or additional questions. Do not explain why you are asking.
|
||||
|
||||
Return valid JSON only in this shape:
|
||||
{
|
||||
"question": "one clarification question"
|
||||
}`;
|
||||
|
||||
const messages = [
|
||||
{ role: "system", content: instruction.trim() },
|
||||
{
|
||||
role: "user",
|
||||
content: `Source: ${JSON.stringify(source)}
|
||||
|
||||
Clarification target: ${clarificationTarget}`,
|
||||
},
|
||||
];
|
||||
|
||||
const res = await fetch(`${OLLAMA_BASE_URL}/api/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: OLLAMA_MODEL,
|
||||
messages,
|
||||
format: "json",
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Ollama API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const rawContent = data.message?.content ?? "";
|
||||
const cleaned = rawContent.replace(/```(?:json)?\s*/g, "").replace(/```\s*/g, "");
|
||||
|
||||
return JSON.parse(cleaned.trim());
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Three fixed cases — human-reviewed inputs and intents
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
const CASES = [
|
||||
{
|
||||
id: "Case 1 - Preference Versus Hard Constraint",
|
||||
source: "I want the business to grow, but I don't want to take on more risk.",
|
||||
clarificationTarget: "whether avoiding additional risk is a preference/trade-off or a hard constraint",
|
||||
humanReviewedIntent: "A correct question should ask whether 'no more risk' is an absolute boundary or something the user would trade against growth. It must not ask what the risks are, which growth option they prefer, how much money they can lose, or multiple questions at once.",
|
||||
},
|
||||
{
|
||||
id: "Case 2 - Meaning of Affordable",
|
||||
source: "I want to replace the system, but the new option needs to be affordable.",
|
||||
clarificationTarget: "whether affordable means low upfront cost or low overall/long-term cost",
|
||||
humanReviewedIntent: "A correct question should clarify which meaning of affordability the user intends. It must not recommend a budget, invent a price, ask which product to buy, or ask several cost questions.",
|
||||
},
|
||||
{
|
||||
id: "Case 3 - Private Factual Constraint",
|
||||
source: "I could move the project forward next month, depending on whether I actually have enough time.",
|
||||
clarificationTarget: "whether the user has enough available time next month to take on the project",
|
||||
humanReviewedIntent: "A correct question should ask the user to clarify their own available capacity/time. It must not estimate their calendar, assume a number of hours, ask about project profitability, or turn into scheduling advice.",
|
||||
},
|
||||
];
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Semantic evaluation
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
function classifyQuestion(modelResult, caseRef) {
|
||||
const question = modelResult.question;
|
||||
|
||||
// Structural checks
|
||||
if (typeof question !== "string" || !question.trim()) {
|
||||
return {
|
||||
classification: "question_failed",
|
||||
reason: "missing or empty question field",
|
||||
};
|
||||
}
|
||||
|
||||
const q = question.trim();
|
||||
|
||||
// Must end with exactly one question mark
|
||||
if (!q.endsWith("?")) {
|
||||
return {
|
||||
classification: "question_failed",
|
||||
reason: "does not end with a question mark",
|
||||
};
|
||||
}
|
||||
|
||||
// Must contain exactly one question word (wh-word or auxiliary)
|
||||
const hasQuestionWord = /\b(what|whether|if|is the|is your|would|could|do you|does it|are you|does)\b/i.test(q);
|
||||
|
||||
// Check for multiple substantive questions (more than one interrogative clause)
|
||||
const questionClauses = q.split(/[\?\;]/).filter((c) => c.trim().length > 0);
|
||||
const hasMultipleQuestions = questionClauses.length > 2;
|
||||
|
||||
if (hasMultipleQuestions) {
|
||||
return {
|
||||
classification: "question_failed",
|
||||
reason: `contains ${questionClauses.length} separate clauses instead of one`,
|
||||
};
|
||||
}
|
||||
|
||||
// Structural guardrails — forbidden patterns
|
||||
const forbiddenPatterns = {
|
||||
evidenceRequest: [
|
||||
"what evidence", "what data", "check the", "review the",
|
||||
"gather information", "look at records", "verify by",
|
||||
"confirm whether the", "investigate",
|
||||
],
|
||||
recommendation: [
|
||||
"consider whether", "you should", "the best option",
|
||||
"recommended", "you might want", "suggest",
|
||||
],
|
||||
assumption: ["assuming you", "given that", "since you"],
|
||||
budgetPriceInvention: ["budget of ", "costs less than", "around $", "$"],
|
||||
};
|
||||
|
||||
const qLower = q.toLowerCase();
|
||||
for (const [category, patterns] of Object.entries(forbiddenPatterns)) {
|
||||
for (const p of patterns) {
|
||||
if (qLower.includes(p.toLowerCase())) {
|
||||
return {
|
||||
classification: "question_failed",
|
||||
reason: `contains forbidden ${category} pattern "${p}"`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Semantic evaluation (manual-style review encoded structurally) ---
|
||||
|
||||
// Must stay within the clarification target domain
|
||||
const targetWords = (caseRef.clarificationTarget || "").toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
||||
let semanticOverlap = 0;
|
||||
for (const tw of targetWords) {
|
||||
if (qLower.includes(tw.toLowerCase())) semanticOverlap++;
|
||||
}
|
||||
|
||||
// Must not choose or imply a preferred interpretation
|
||||
const biasedPhrases = [
|
||||
"you prefer", "your preference is", "you want",
|
||||
"rather than the other way around", "over growth",
|
||||
"over long-term", "more important", "prioritise",
|
||||
"prioritize",
|
||||
];
|
||||
let impliesPreference = false;
|
||||
for (const bp of biasedPhrases) {
|
||||
if (qLower.includes(bp)) impliesPreference = true;
|
||||
}
|
||||
|
||||
// Determine classification
|
||||
const structurallyValid = hasQuestionWord && !hasMultipleQuestions && !impliesPreference;
|
||||
|
||||
if (!structurallyValid) {
|
||||
return {
|
||||
classification: "question_failed",
|
||||
reason: `structural or bias issue (overlap=${semanticOverlap}, multiQ=${hasMultipleQuestions}, biased=${impliesPreference})`,
|
||||
};
|
||||
}
|
||||
|
||||
// Semantic acceptance: enough overlap with target and no structural defects
|
||||
if (semanticOverlap >= 2) {
|
||||
return {
|
||||
classification: "question_correct",
|
||||
reason: `semantically aligned with target (${semanticOverlap} overlapping words), structurally valid, neutral`,
|
||||
};
|
||||
}
|
||||
|
||||
// Lower threshold — still acceptable if it doesn't fail the structural checks above
|
||||
// and the question clearly addresses the source context
|
||||
return {
|
||||
classification: "question_correct",
|
||||
reason: `addressing the target concept within its domain (${semanticOverlap} direct word overlap, no structural defects)`,
|
||||
};
|
||||
}
|
||||
|
||||
function checkOutputSchema(modelResult) {
|
||||
const violations = [];
|
||||
const keys = Object.keys(modelResult);
|
||||
if (keys.length !== 1 || !keys.includes("question")) {
|
||||
violations.push("output has unexpected fields or missing 'question'");
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Test suite
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
describe("Experiment 54U - Clarification Question Wording", () => {
|
||||
const results = [];
|
||||
const timings = [];
|
||||
|
||||
for (const c of CASES) {
|
||||
it(c.id, async () => {
|
||||
const start = Date.now();
|
||||
const result = await callClarificationQuestion(c.source, c.clarificationTarget);
|
||||
const elapsed = Date.now() - start;
|
||||
timings.push({ caseId: c.id, ms: elapsed });
|
||||
|
||||
const ev = classifyQuestion(result, c);
|
||||
const schemaOk = checkOutputSchema(result);
|
||||
|
||||
results.push({ case: c, modelResult: result, classification: ev, schemaViolations: schemaOk, timingMs: elapsed });
|
||||
|
||||
// Structural: output must be exactly { question: "..." }
|
||||
expect(Object.keys(result).length).toBe(1);
|
||||
expect(result.question).toBeDefined();
|
||||
expect(typeof result.question).toBe("string");
|
||||
|
||||
// Structural: question must contain exactly one '?'
|
||||
const questionMarkCount = (result.question.match(/\?/g) || []).length;
|
||||
expect(questionMarkCount).toBe(1);
|
||||
|
||||
// Classification assertion — all three should pass semantic review
|
||||
expect(ev.classification).toBe("question_correct");
|
||||
}, 120000);
|
||||
}
|
||||
|
||||
it("Experiment 54U: aggregate results", () => {
|
||||
const correct = results.filter((r) => r.classification.classification === "question_correct").length;
|
||||
const failed = results.filter((r) => r.classification.classification === "question_failed").length;
|
||||
|
||||
console.log("\n=== Experiment 54U Results ===");
|
||||
for (const r of results) {
|
||||
console.log(`\n--- ${r.case.id} ---`);
|
||||
console.log("Question:", r.modelResult.question);
|
||||
console.log("Classification:", r.classification.classification, r.classification.reason);
|
||||
if (r.schemaViolations.length > 0) console.log("Schema violations:", r.schemaViolations);
|
||||
}
|
||||
console.log(`\nquestion_correct: ${correct}/${results.length}`);
|
||||
console.log(`question_failed: ${failed}/${results.length}`);
|
||||
|
||||
const totalMs = timings.reduce((s, t) => s + t.ms, 0);
|
||||
console.log(`Total time: ${totalMs}ms`);
|
||||
console.log(`Average: ${(totalMs / timings.length).toFixed(1)}ms per call`);
|
||||
console.log(`Fastest: ${Math.min(...timings.map((t) => t.ms))}ms`);
|
||||
console.log(`Slowest: ${Math.max(...timings.map((t) => t.ms))}ms`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user