Erstatter v2 project-surface (screen-tabs + category-tabs + per-command paste-cards)
med v3 renderProjectView (sidebar med 17 artifacts + main-area + import-modal overlay).
renderActive() ruter project-surface til renderProjectSurfaceV3() som wrapper
renderProjectView + topbar + app-shell.
V2-surface helt fjernet:
- renderProjectSurface (152 linjer)
- renderCommandSubCard (87 linjer)
- rehydratePasteImports (15 linjer)
- ACTIONS['project-screen'], currentProjectScreen
- 5 v2-CSS-klasser: .project-tabs, .project-tab*, .sub-zone, .paste-import-row, .project-header__*, .command-cards
Zombie-handlers beholdt for test-back-compat:
currentProjectTab, ACTIONS['project-tab'], ACTIONS['parse'],
handlePasteImport, window.__handlePasteImport. Unreachable fra v3 DOM
men nødvendige for test-playground-v3.sh + test-playground-parsers.sh.
2 fingerprint-gap lukket:
- requirements.headers: utvidet med "EU AI Act — Krav" pattern
- license.headers: utvidet med "Lisens-kapabilitetsmatrise" pattern
- KNOWN_GAP_FIXTURES = {} i test-playground-fingerprints.sh
migrateDataVersion utvidet med parserFor (3. arg):
- Demo-state med kun raw_markdown auto-parses til project.artifacts[cid]
- defaultParserFor(cmdId) resolverer PARSERS[archetypeFor(cmdId)]
- 3 bootstrap-callsites oppdatert (cold-load, import, load-demo)
Ship-QA bugfixes funnet via browser-dogfood:
- components-tier4-project-view.css lagt til i <link>-kjeden (var ikke loaded
-> modal-overlay og two-column layout virket ikke)
- renderImportModal setter data-open="true" (DS-kontrakt for display: flex)
Bundler også sesjon 2-4 deliverables som ikke ble committed tidligere:
- shared/playground-design-system v0.6.0 (Tier 4 project-view CSS + 6 tokens)
- ms-ai-architect/playground/vendor/ re-sync til DS v0.6.0
- tests/test-playground-fingerprints.sh (sesjon 4 NY - 32 PASS)
- tests/test-playground-projectview.sh (sesjon 4 NY - 30 PASS)
- tests/test-playground-actions.sh (sesjon 4 NY - 19 PASS)
- tests/test-playground-migrations.sh utvidet (7 -> 16 PASS)
- tests/run-e2e.sh wirer alle 6 playground-suiter
Stats:
- bash tests/run-e2e.sh --playground: 386 PASS, 0 FAIL, 2 WARN (pre-eks)
- bash tests/run-e2e.sh (full): All E2E suites passed
- bash tests/validate-plugin.sh: 219 PASS
Screenshots regenerert til playground/screenshots/v1.15.0/ (24 PNG-er, 12
surfaces x 2 tema). Nye v3-surfaces: project-overview, project-artifact-*,
project-import-modal (viewport-only), project-search.
Docs oppdatert (3 nivåer): README.md (badge + version history),
CHANGELOG.md, CLAUDE.md (playground-seksjon + valideringstabell),
rot-README.md + rot-CLAUDE.md (marketplace-landingen + plugin-index).
.gitignore: ny pattern *.local.html + *.local.json for sesjon-state-filer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
218 lines
11 KiB
Bash
Executable file
218 lines
11 KiB
Bash
Executable file
#!/bin/bash
|
||
# test-playground-fingerprints.sh — Playground v3 inferCommandIdFromMarkdown
|
||
#
|
||
# Verifiserer:
|
||
# 1. PROJECT_VIEW_V2_BEGIN/END-markørene finnes
|
||
# 2. inferCommandIdFromMarkdown matcher hver av 17 test-fixtures mot
|
||
# egen commandId med confidence >= 0.6 (true-positive matrise)
|
||
# 3. False-positive immunity:
|
||
# - tom streng → null
|
||
# - 100 tegn lorem ipsum → null
|
||
# - kun "# Hello"-header → null
|
||
# - mixed content med kun classify-header → matcher classify
|
||
# 4. Sanity-asserts på COMMAND_FINGERPRINTS-shape og fingerprintScore-API
|
||
#
|
||
# Bash 3.2-kompatibel. Bruker node til JS-eval; ingen npm-deps.
|
||
|
||
set -euo pipefail
|
||
|
||
PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
HTML_FILE="$PLUGIN_ROOT/playground/ms-ai-architect-playground.html"
|
||
FIXTURE_DIR="$PLUGIN_ROOT/playground/test-fixtures"
|
||
|
||
# shellcheck disable=SC1091
|
||
source "$PLUGIN_ROOT/tests/lib/e2e-helpers.sh"
|
||
|
||
init_suite "Playground v3 — inferCommandIdFromMarkdown fingerprints"
|
||
|
||
# ---- 1. Filer eksisterer ----
|
||
if [ ! -f "$HTML_FILE" ]; then
|
||
fail "HTML-fila finnes ikke: $HTML_FILE"
|
||
print_summary; exit 1
|
||
fi
|
||
pass "HTML-fil finnes: $(basename "$HTML_FILE")"
|
||
|
||
if [ ! -d "$FIXTURE_DIR" ]; then
|
||
fail "Fixture-mappe mangler: $FIXTURE_DIR"
|
||
print_summary; exit 1
|
||
fi
|
||
pass "Fixture-mappe finnes"
|
||
|
||
# ---- 2. PROJECT_VIEW_V2-markører eksisterer ----
|
||
if grep -q "PROJECT_VIEW_V2_BEGIN" "$HTML_FILE" && grep -q "PROJECT_VIEW_V2_END" "$HTML_FILE"; then
|
||
pass "PROJECT_VIEW_V2_BEGIN/END markører finnes"
|
||
else
|
||
fail "Mangler PROJECT_VIEW_V2-markører i HTML"
|
||
print_summary; exit 1
|
||
fi
|
||
|
||
# ---- 3. Kjør hele test-matrisen i én node-prosess (effektivt) ----
|
||
# Node-skriptet:
|
||
# - ekstraherer PROJECT_VIEW_V2-blokken
|
||
# - stubber dependencies
|
||
# - leser alle 17 fixture-filer
|
||
# - kjører true-positive + anti-match + sanity-tester
|
||
# - skriver én linje per assert: "PASS|FAIL <description>"
|
||
|
||
NODE_OUT=$(node -e '
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const htmlPath = process.argv[1];
|
||
const fixtureDir = process.argv[2];
|
||
|
||
const html = fs.readFileSync(htmlPath, "utf8");
|
||
const beginMarker = "// === PROJECT_VIEW_V2_BEGIN ===";
|
||
const endMarker = "// === PROJECT_VIEW_V2_END ===";
|
||
const beginIdx = html.indexOf(beginMarker);
|
||
const endIdx = html.indexOf(endMarker);
|
||
if (beginIdx < 0 || endIdx < 0) {
|
||
console.error("MARKER_MISSING");
|
||
process.exit(2);
|
||
}
|
||
const block = html.substring(beginIdx, endIdx + endMarker.length);
|
||
|
||
// 17 produces_report-renderers per PROJECT_VIEW_CONFIG.renderers.
|
||
const RENDERER_IDS = ["renderAiActPyramid","renderRequirements","renderTransparency","renderFria","renderConformity","renderDpia","renderSecurity","renderRos","renderReview","renderCost","renderLicense","renderMigrate","renderAdr","renderSummary","renderPoc","renderUtredning","renderCompare"];
|
||
const rendererStubs = RENDERER_IDS.map(function (n) { return n + ": function () {}"; }).join(", ");
|
||
|
||
// CATALOG.commands: 17 produces_report=true entries med id, label, category, renderer.
|
||
const COMMANDS = [
|
||
{ id: "classify", category: "regulatory", label: "EU AI Act — Klassifisering", renderer: "renderAiActPyramid", produces_report: true, report_archetype: "aiact" },
|
||
{ id: "requirements", category: "regulatory", label: "EU AI Act — Krav per risiko", renderer: "renderRequirements", produces_report: true, report_archetype: "requirements-list" },
|
||
{ id: "transparency", category: "regulatory", label: "Transparensnotis (Art. 13/50)", renderer: "renderTransparency", produces_report: true, report_archetype: "text-document" },
|
||
{ id: "frimpact", category: "regulatory", label: "FRIA (Art. 27)", renderer: "renderFria", produces_report: true, report_archetype: "fria" },
|
||
{ id: "conformity", category: "regulatory", label: "Samsvarsvurdering (Art. 43)", renderer: "renderConformity", produces_report: true, report_archetype: "conformity-checklist" },
|
||
{ id: "dpia", category: "regulatory", label: "DPIA / PVK", renderer: "renderDpia", produces_report: true, report_archetype: "matrix-risk" },
|
||
{ id: "security", category: "security", label: "Sikkerhetsvurdering (6×5)", renderer: "renderSecurity", produces_report: true, report_archetype: "matrix-risk-6x5" },
|
||
{ id: "ros", category: "security", label: "ROS-analyse", renderer: "renderRos", produces_report: true, report_archetype: "matrix-risk" },
|
||
{ id: "review", category: "security", label: "Arkitekturgjennomgang", renderer: "renderReview", produces_report: true, report_archetype: "findings" },
|
||
{ id: "cost", category: "economy", label: "Kostnadsestimat", renderer: "renderCost", produces_report: true, report_archetype: "cost-distribution" },
|
||
{ id: "license", category: "economy", label: "Lisenskartlegging", renderer: "renderLicense", produces_report: true, report_archetype: "scenario-comparison" },
|
||
{ id: "migrate", category: "economy", label: "Migrasjonsplan", renderer: "renderMigrate", produces_report: true, report_archetype: "phase-plan" },
|
||
{ id: "adr", category: "documentation", label: "ADR", renderer: "renderAdr", produces_report: true, report_archetype: "adr" },
|
||
{ id: "summary", category: "documentation", label: "Beslutningsnotat", renderer: "renderSummary", produces_report: true, report_archetype: "verdict" },
|
||
{ id: "poc", category: "documentation", label: "POC-plan", renderer: "renderPoc", produces_report: true, report_archetype: "phase-plan" },
|
||
{ id: "utredning", category: "documentation", label: "Utredning", renderer: "renderUtredning", produces_report: true, report_archetype: "utredning" },
|
||
{ id: "compare", category: "documentation", label: "Plattformsammenligning", renderer: "renderCompare", produces_report: true, report_archetype: "scenario-comparison" }
|
||
];
|
||
|
||
const stubs = `
|
||
const window = {};
|
||
function escapeHtml(s) { return String(s == null ? "" : s); }
|
||
function escapeAttr(s) { return escapeHtml(s); }
|
||
function renderPageShell(opts, body) { return "<header>" + (opts && opts.title || "") + "</header>" + (body || ""); }
|
||
function renderVerdictPill(v) { return "<span class=\\"verdict-pill\\" data-verdict=\\"" + v + "\\">" + v + "</span>"; }
|
||
function renderKeyStatsGrid(s) { return "<div class=\\"key-stats\\">" + (s && s.length || 0) + "</div>"; }
|
||
function inferVerdict() { return "n-a"; }
|
||
function inferKeyStats() { return []; }
|
||
const PARSERS = {};
|
||
const RENDERERS = { ` + rendererStubs + ` };
|
||
const CATALOG = { commands: ` + JSON.stringify(COMMANDS) + ` };
|
||
const ACTIONS = {};
|
||
const store = { state: { ui: {}, projects: [], activeProjectId: null }, save: function () {} };
|
||
function findProject() { return null; }
|
||
function scheduleRender() {}
|
||
`;
|
||
|
||
const wrapped = stubs + block + "\nreturn { inferCommandIdFromMarkdown, fingerprintScore, COMMAND_FINGERPRINTS };";
|
||
let api;
|
||
try {
|
||
api = (new Function(wrapped))();
|
||
} catch (e) {
|
||
console.error("EVAL_FAILED: " + e.message);
|
||
process.exit(3);
|
||
}
|
||
|
||
function emit(ok, desc) {
|
||
console.log((ok ? "PASS" : "FAIL") + "\t" + desc);
|
||
}
|
||
|
||
// ---- Sanity-asserts (5) ----
|
||
emit(typeof api.inferCommandIdFromMarkdown === "function", "inferCommandIdFromMarkdown er funksjon");
|
||
emit(typeof api.fingerprintScore === "function", "fingerprintScore er funksjon");
|
||
emit(typeof api.COMMAND_FINGERPRINTS === "object" && api.COMMAND_FINGERPRINTS !== null, "COMMAND_FINGERPRINTS er objekt");
|
||
const cfKeys = Object.keys(api.COMMAND_FINGERPRINTS);
|
||
emit(cfKeys.length === 17, "COMMAND_FINGERPRINTS har 17 entries (got " + cfKeys.length + ")");
|
||
let allShapeOk = true;
|
||
for (const k of cfKeys) {
|
||
const v = api.COMMAND_FINGERPRINTS[k];
|
||
if (!v || !Array.isArray(v.headers) || !Array.isArray(v.keywords)) {
|
||
allShapeOk = false; break;
|
||
}
|
||
}
|
||
emit(allShapeOk, "Hver fingerprint har headers[] og keywords[]");
|
||
|
||
// ---- True-positive matrise (17 fixtures) ----
|
||
//
|
||
// Kjente fingerprint/fixture-gap (oppdaget av denne testen, fix i sesjon 5):
|
||
// - requirements.md har header "# EU AI Act — Krav for høyrisiko" som ikke
|
||
// matcher /^\s*#\s*(AI\s*Act-?krav|Krav per|Requirements)/i. Annex IV-
|
||
// omtale i tabellen gjør at conformity vinner med 0.76.
|
||
// - license.md har header "# Lisens-kapabilitetsmatrise" som ikke matcher
|
||
// /^\s*#\s*(Lisens(kart)?legging|License\s*Mapping)/i.
|
||
// v1.15.0 (sesjon 5): begge gap-er lukket — requirements.headers og
|
||
// license.headers utvidet i COMMAND_FINGERPRINTS. KNOWN_GAP_FIXTURES tømt.
|
||
const KNOWN_GAP_FIXTURES = {};
|
||
|
||
const expectedIds = ["classify","requirements","transparency","frimpact","conformity","dpia","security","ros","review","cost","license","migrate","adr","summary","poc","utredning","compare"];
|
||
for (const cid of expectedIds) {
|
||
const fxPath = path.join(fixtureDir, cid + ".md");
|
||
let text = "";
|
||
try { text = fs.readFileSync(fxPath, "utf8"); } catch (e) {
|
||
emit(false, "fixture " + cid + ".md kunne ikke leses: " + e.message);
|
||
continue;
|
||
}
|
||
const r = api.inferCommandIdFromMarkdown(text, {});
|
||
const matchedCid = r && r.commandId;
|
||
const conf = r && r.confidence;
|
||
const ok = r && r.commandId === cid && r.confidence >= 0.6;
|
||
if (ok) {
|
||
emit(true, "fixture " + cid + ".md → " + matchedCid + " conf=" + conf.toFixed(2));
|
||
} else if (KNOWN_GAP_FIXTURES[cid]) {
|
||
console.log("WARN\tfixture " + cid + ".md → " + (matchedCid || "null") +
|
||
" (KNOWN_GAP — fingerprint dekker ikke fixture-header; fix i sesjon 5)");
|
||
} else {
|
||
emit(false, "fixture " + cid + ".md → " + (matchedCid || "null") +
|
||
(conf != null ? " conf=" + conf.toFixed(2) : ""));
|
||
}
|
||
}
|
||
|
||
// ---- Anti-match (4) ----
|
||
emit(api.inferCommandIdFromMarkdown("", {}) === null,
|
||
"tom streng → null");
|
||
emit(api.inferCommandIdFromMarkdown(null, {}) === null,
|
||
"null input → null");
|
||
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim";
|
||
emit(api.inferCommandIdFromMarkdown(lorem, {}) === null,
|
||
"100 tegn lorem ipsum → null (got " + JSON.stringify(api.inferCommandIdFromMarkdown(lorem, {})) + ")");
|
||
emit(api.inferCommandIdFromMarkdown("# Hello\n\nIngen relevant tekst.", {}) === null,
|
||
"kun \"# Hello\"-header → null");
|
||
|
||
// ---- Mixed content: dominant header vinner ----
|
||
const mixed = "# EU AI Act — Klassifisering\n\nDette inneholder også owasp og prompt injection-referanser men headeren er klassifisering.";
|
||
const mixedR = api.inferCommandIdFromMarkdown(mixed, {});
|
||
emit(mixedR !== null && mixedR.commandId === "classify",
|
||
"mixed content med classify-header → " + (mixedR && mixedR.commandId));
|
||
|
||
// ---- fingerprintScore-API direkte ----
|
||
emit(api.fingerprintScore("any text", null) === 0,
|
||
"fingerprintScore(null spec) === 0");
|
||
emit(api.fingerprintScore("", api.COMMAND_FINGERPRINTS.classify) === 0,
|
||
"fingerprintScore(tom tekst) === 0");
|
||
' "$HTML_FILE" "$FIXTURE_DIR" 2>&1) || NODE_RC=$?
|
||
|
||
if [ "${NODE_RC:-0}" -ne 0 ]; then
|
||
fail "node-eval feilet (rc=${NODE_RC:-0}): $NODE_OUT"
|
||
print_summary; exit 1
|
||
fi
|
||
|
||
# Parse PASS/FAIL/WARN-linjer fra node-output
|
||
while IFS=$'\t' read -r status desc; do
|
||
case "$status" in
|
||
PASS) pass "$desc" ;;
|
||
FAIL) fail "$desc" ;;
|
||
WARN) warn "$desc" ;;
|
||
esac
|
||
done <<< "$NODE_OUT"
|
||
|
||
print_summary
|