feat(ms-ai-architect): v1.15.0 — playground v3 project-view integration

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>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-16 20:58:51 +02:00
commit d8882f5220
47 changed files with 3722 additions and 409 deletions

View file

@ -0,0 +1,218 @@
#!/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