From 4e6ed044a9a9c344c4f15f580bd408ca187725d7 Mon Sep 17 00:00:00 2001
From: C-West8 <125926137+C-West8@users.noreply.github.com>
Date: Wed, 22 Jul 2026 15:27:51 -0500
Subject: [PATCH] cleanup - removing ai agent for deterministic code and better
formatting
---
.env.example | 8 -
README.md | 13 +-
docker-compose.yml | 2 -
legacy/SDE_Scope_Lock_Meeting_Suite.html | 13 +
package-lock.json | 376 +----------------------
package.json | 1 -
src/app.css | 7 +
src/lib/breadcrumbs.ts | 292 ++++++++++++++++++
src/lib/server/ai.ts | 83 -----
src/routes/+page.svelte | 20 +-
src/routes/api/ai/draft/+server.ts | 28 --
src/routes/scope-lock/+page.svelte | 9 +
src/routes/workshop/+page.svelte | 71 +++--
13 files changed, 396 insertions(+), 527 deletions(-)
create mode 100644 src/lib/breadcrumbs.ts
delete mode 100644 src/lib/server/ai.ts
delete mode 100644 src/routes/api/ai/draft/+server.ts
diff --git a/.env.example b/.env.example
index d68d01c..dc607d0 100644
--- a/.env.example
+++ b/.env.example
@@ -5,14 +5,6 @@
# When running via docker-compose, this is set for you in docker-compose.yml.
DATABASE_URL=postgres://sde:sde_password@localhost:5432/sde_toolkit
-# --- Anthropic API (for the "AI draft" feature) ---
-# Get a key from https://console.anthropic.com/ . Without it, the AI button
-# fails politely and everything else keeps working.
-ANTHROPIC_API_KEY=
-
-# Model used for AI drafting.
-ANTHROPIC_MODEL=claude-sonnet-5
-
# --- Server ---
PORT=3000
diff --git a/README.md b/README.md
index 3bfbd04..8e4c76f 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
A shared, multi-user web app with two meeting tools:
-- **Field Problem Workshop** — capture field problems, cluster them into root problems, dot-vote, build "breadcrumb" trails from problem to measurable outcome, and assign actions. Includes an optional AI draft feature.
+- **Field Problem Workshop** — capture field problems, cluster them into root problems, dot-vote, build "breadcrumb" trails from problem to measurable outcome, and assign actions. A "Suggest fields" button fills each breadcrumb from built-in construction-execution methodology (no external service).
- **Scope Lock Meeting Suite** — Scope Boundary Board, MVP Priority Ranker, Decision Registry, and Micron Pilot Readiness.
Everyone who opens the same link works from the **same data**, stored on a shared server. Edits autosave, and a version check stops two people from silently overwriting each other. The original single-file HTML versions are kept in [`legacy/`](./legacy) for reference.
@@ -32,14 +32,14 @@ docker compose logs -f app # watch app logs
Data persists in a Docker volume (`db_data`), so it survives restarts.
-To enable the **AI draft** feature, provide an Anthropic API key before starting — either export `ANTHROPIC_API_KEY` in your shell or put it in a `.env` file next to `docker-compose.yml`. Without a key the AI button simply fails politely; everything else works.
+There are no API keys or external accounts to set up. The "Suggest fields" button runs entirely in-app from a built-in methodology table.
## Local development (without Docker)
Needs Node 20+ and a PostgreSQL you can reach.
```bash
-cp .env.example .env # edit DATABASE_URL (and ANTHROPIC_API_KEY if wanted)
+cp .env.example .env # edit DATABASE_URL
npm install
psql "$DATABASE_URL" -f db/init.sql # create the schema once
npm run dev # http://localhost:5173
@@ -54,8 +54,6 @@ All config is via environment variables — see [`.env.example`](./.env.example)
| Variable | Purpose |
|---|---|
| `DATABASE_URL` | Postgres connection string. Set automatically by docker-compose. |
-| `ANTHROPIC_API_KEY` | Enables the "AI draft" button. Blank = feature disabled. |
-| `ANTHROPIC_MODEL` | Model for AI drafting (default `claude-sonnet-5`). |
| `PORT` | Server port (default 3000). |
| `ORIGIN` | Public URL the app is served from; used to validate POST origins. |
@@ -67,6 +65,7 @@ src/
app.html page shell
lib/
types.ts data shapes for both tools
+ breadcrumbs.ts methodology table + keyword classifier ("Suggest fields")
workspace.svelte.ts client sync: load / autosave / poll / conflict handling
toast.svelte.ts shared toast controller
components/ Toast.svelte, SyncBadge.svelte
@@ -74,7 +73,6 @@ src/
server/
db/index.ts Postgres connection pool (lazy)
db/workspaces.ts load / save / reset with optimistic concurrency
- ai.ts Anthropic proxy (holds the API key server-side)
routes/
+layout.svelte imports global CSS
+page.svelte home / tool picker
@@ -83,7 +81,6 @@ src/
api/
workspaces/[tool]/[name]/+server.ts GET (load) + PUT (save)
workspaces/[tool]/[name]/reset/+server.ts POST (reset to baseline)
- ai/draft/+server.ts POST (AI breadcrumb draft)
db/init.sql database schema (runs once on first DB startup)
static/favicon.svg
Dockerfile, docker-compose.yml
@@ -108,7 +105,7 @@ It requires, on your Gitea instance: Actions enabled with a registered `act_runn
## The `.skill` file
-`legacy/construction-breadcrumbs.skill` is a **Claude skill** (a zip of instructions + a methodology knowledge base). It is not part of the website — it teaches Claude how to draft breadcrumb fields using construction methodology (EVM, CII AWP, Last Planner, first-time quality). The same knowledge is embedded server-side in `src/lib/server/ai.ts`, which powers the app's AI draft button. Install the skill in Claude (via "Save skill") to use the methodology in any chat.
+`legacy/construction-breadcrumbs.skill` is a **Claude skill** (a zip of instructions + a methodology knowledge base). It is not part of the website — it teaches Claude how to draft breadcrumb fields using construction methodology (EVM, CII AWP, Last Planner, first-time quality). The same methodology is encoded, deterministically and offline, in [`src/lib/breadcrumbs.ts`](./src/lib/breadcrumbs.ts), which powers the app's "Suggest fields" button. Install the skill in Claude (via "Save skill") to use the methodology in any chat.
## Possible next steps
diff --git a/docker-compose.yml b/docker-compose.yml
index cb4759c..bf029f2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -27,8 +27,6 @@ services:
condition: service_healthy
environment:
DATABASE_URL: postgres://sde:sde_password@db:5432/sde_toolkit
- ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
- ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-5}
PORT: 3000
ORIGIN: ${ORIGIN:-http://localhost:3000}
ports:
diff --git a/legacy/SDE_Scope_Lock_Meeting_Suite.html b/legacy/SDE_Scope_Lock_Meeting_Suite.html
index 7de1b29..87fcb3e 100644
--- a/legacy/SDE_Scope_Lock_Meeting_Suite.html
+++ b/legacy/SDE_Scope_Lock_Meeting_Suite.html
@@ -224,6 +224,7 @@
Project SDE| Scope Lock Meeting Suite
Micron Pilot | Innovation Team
+
@@ -658,6 +659,18 @@ document.getElementById('btnAddReady').addEventListener('click', ()=>{
});
document.getElementById('newReadyItem').addEventListener('keydown', e=>{ if(e.key==='Enter') document.getElementById('btnAddReady').click(); });
+/* ================= SAVE JSON (raw data dump for migration/import) ================= */
+document.getElementById('btnSaveJson').addEventListener('click', ()=>{
+ const stamp = new Date().toISOString().slice(0,10);
+ const blob = new Blob([JSON.stringify(S, null, 2)], {type:'application/json'});
+ const a = document.createElement('a');
+ a.href = URL.createObjectURL(blob);
+ a.download = `SDE_Scope_Lock_state_${stamp}.json`;
+ a.click();
+ URL.revokeObjectURL(a.href);
+ toast('State saved to JSON');
+});
+
/* ================= EXPORT ================= */
document.getElementById('btnExport').addEventListener('click', ()=>{
const d = new Date();
diff --git a/package-lock.json b/package-lock.json
index 8a1e33e..9219969 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,6 @@
"name": "sde-meeting-toolkit",
"version": "1.0.0",
"dependencies": {
- "@anthropic-ai/sdk": "^0.32.0",
"pg": "^8.13.0"
},
"devDependencies": {
@@ -22,21 +21,6 @@
"vite": "^5.4.0"
}
},
- "node_modules/@anthropic-ai/sdk": {
- "version": "0.32.1",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz",
- "integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "^18.11.18",
- "@types/node-fetch": "^2.6.4",
- "abort-controller": "^3.0.0",
- "agentkeepalive": "^4.2.1",
- "form-data-encoder": "1.7.2",
- "formdata-node": "^4.3.2",
- "node-fetch": "^2.6.7"
- }
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1136,21 +1120,12 @@
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
- "node_modules/@types/node-fetch": {
- "version": "2.6.13",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
- "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "form-data": "^4.0.4"
- }
- },
"node_modules/@types/pg": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
@@ -1177,18 +1152,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
- "license": "MIT",
- "dependencies": {
- "event-target-shim": "^5.0.0"
- },
- "engines": {
- "node": ">=6.5"
- }
- },
"node_modules/acorn": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
@@ -1202,18 +1165,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/agentkeepalive": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
- "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
- "license": "MIT",
- "dependencies": {
- "humanize-ms": "^1.2.1"
- },
- "engines": {
- "node": ">= 8.0.0"
- }
- },
"node_modules/aria-query": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
@@ -1224,12 +1175,6 @@
"node": ">= 0.4"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -1240,19 +1185,6 @@
"node": ">= 0.4"
}
},
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -1279,18 +1211,6 @@
"node": ">=6"
}
},
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -1336,15 +1256,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/devalue": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.2.tgz",
@@ -1352,65 +1263,16 @@
"dev": true,
"license": "MIT"
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
- "node_modules/es-object-atoms": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
- "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -1482,15 +1344,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/event-target-shim": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
- "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1509,41 +1362,6 @@
}
}
},
- "node_modules/form-data": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
- "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.4",
- "mime-types": "^2.1.35"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/form-data-encoder": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
- "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
- "license": "MIT"
- },
- "node_modules/formdata-node": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
- "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
- "license": "MIT",
- "dependencies": {
- "node-domexception": "1.0.0",
- "web-streams-polyfill": "4.0.0-beta.3"
- },
- "engines": {
- "node": ">= 12.20"
- }
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1563,91 +1381,17 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -1656,15 +1400,6 @@
"node": ">= 0.4"
}
},
- "node_modules/humanize-ms": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.0.0"
- }
- },
"node_modules/is-core-module": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
@@ -1725,36 +1460,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -1779,6 +1484,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -1800,46 +1506,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "deprecated": "Use your platform's native DOMException instead",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -2245,12 +1911,6 @@
"node": ">=6"
}
},
- "node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "license": "MIT"
- },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -2269,6 +1929,7 @@
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/vite": {
@@ -2351,31 +2012,6 @@
}
}
},
- "node_modules/web-streams-polyfill": {
- "version": "4.0.0-beta.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
- "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
- "node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 2332eec..e89f1ca 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
"vite": "^5.4.0"
},
"dependencies": {
- "@anthropic-ai/sdk": "^0.32.0",
"pg": "^8.13.0"
}
}
diff --git a/src/app.css b/src/app.css
index 4dac305..2078a4c 100644
--- a/src/app.css
+++ b/src/app.css
@@ -27,12 +27,19 @@
padding: 0;
}
+html {
+ scroll-behavior: smooth;
+}
+
body {
font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
font-size: 16px;
line-height: 1.4;
+ min-height: 100vh;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
}
button {
diff --git a/src/lib/breadcrumbs.ts b/src/lib/breadcrumbs.ts
new file mode 100644
index 0000000..2a283e5
--- /dev/null
+++ b/src/lib/breadcrumbs.ts
@@ -0,0 +1,292 @@
+// Deterministic breadcrumb suggestions — no external AI.
+//
+// This replaces the old Anthropic-backed "AI draft" feature. The methodology it
+// encodes is exactly what mattered: seven construction-execution domains, each
+// with a canonical value driver, capability, metric, method, and baseline drawn
+// from established practice (Earned Value Management, CII Advanced Work
+// Packaging, Last Planner System, PMBOK change control, first-time quality,
+// CII lessons-learned). A problem statement is classified into a domain by
+// keyword, then that domain's template fills the breadcrumb fields. The team
+// then reviews and edits — same as before, minus the API key, tokens, and
+// network round-trip.
+//
+// The metric text is always written as an outcome, never adoption language, so
+// it passes the adoption gate the UI already enforces.
+
+export const BREADCRUMB_FIELDS = ['driver', 'cap', 'metric', 'method', 'baseline'] as const;
+export type BreadcrumbField = (typeof BREADCRUMB_FIELDS)[number];
+
+export interface Domain {
+ id: string;
+ label: string;
+ /** Lowercased keywords/phrases that steer classification toward this domain. */
+ keywords: string[];
+ driver: string;
+ cap: string;
+ metric: string;
+ method: string;
+ baseline: string;
+}
+
+export interface Suggestion {
+ domain: string;
+ label: string;
+ driver: string;
+ cap: string;
+ metric: string;
+ method: string;
+ baseline: string;
+}
+
+// The catch-all used when nothing matches or the user picks "not sure". It fills
+// the fields with the gates themselves, so the card is still useful guidance.
+const GENERAL: Domain = {
+ id: 'general',
+ label: 'General / not sure',
+ keywords: [],
+ driver:
+ 'The business end this should improve — state it in cost, schedule, quality, safety, or predictability terms, not as a tool.',
+ cap: 'The capability needed to move that outcome — a function, not a product or vendor name.',
+ metric: 'An outcome ratio or rate that moves — never "implemented", "adopted", "rolled out", or "active users".',
+ method: 'Data source + comparison + cadence for measuring the metric.',
+ baseline:
+ 'A number, or the cheapest defensible pre-pilot study that produces one (poll < record trace < log audit < 2-week coding < sampling study). Set targets after the baseline, not before.'
+};
+
+export const DOMAINS: Domain[] = [
+ {
+ id: 'progress-visibility',
+ label: 'Progress visibility',
+ keywords: [
+ 'progress',
+ 'percent complete',
+ '% complete',
+ 'percent-complete',
+ 'claiming',
+ 'reported',
+ 'rules of credit',
+ 'forecast',
+ 'earned value',
+ 's-curve',
+ 'roll-up',
+ 'rollup',
+ 'status'
+ ],
+ driver:
+ 'Trustworthy progress information so cost and schedule forecasts hold up and decisions are not made on inflated percent-complete.',
+ cap: 'Objective progress measurement with rules of credit tied to verifiable field milestones.',
+ metric: 'Claiming accuracy — reported % versus field-verified % within tolerance, by phase.',
+ method: 'Monthly spot audit of 30+ sampled assets: compare reported % against a field walkdown.',
+ baseline: 'The first audit establishes the baseline claiming gap.'
+ },
+ {
+ id: 'data-freshness',
+ label: 'Data freshness / entry burden',
+ keywords: [
+ 'data entry',
+ 'stale',
+ 'out of date',
+ 'duplicate entry',
+ 're-enter',
+ 'reenter',
+ 'double entry',
+ 'paper',
+ 'spreadsheet',
+ 'manual entry',
+ 'latency',
+ 'single source',
+ 'transcribe',
+ 'rekey'
+ ],
+ driver:
+ 'Decisions made on current field reality, with data captured once instead of burdening crews with duplicate entry.',
+ cap: 'A single source of truth with data entered once at the point of work.',
+ metric: 'Entry latency — % of updates recorded within 24 hours of the work completing.',
+ method: 'Compare system timestamp against actual completion date, sampled weekly.',
+ baseline: 'Audit the current paper-to-digital lag on this project before the pilot.'
+ },
+ {
+ id: 'constraint-readiness',
+ label: 'Constraint readiness / work release',
+ keywords: [
+ 'constraint',
+ 'ready to work',
+ 'work package',
+ 'iwp',
+ 'material',
+ 'tools',
+ 'access',
+ 'permit',
+ 'scaffolding',
+ 'release',
+ 'prerequisite',
+ 'work front',
+ 'stand-down',
+ 'stand down'
+ ],
+ driver:
+ 'Crews start only constraint-free work, so time is not lost to missing material, tools, access, or prerequisites.',
+ cap: 'A release gate that verifies every constraint is cleared before a work package is issued.',
+ metric: '% of work packages released constraint-free (no later constraint-driven stop).',
+ method: 'Weekly release-gate checklist audit; a later constraint stop counts as a miss.',
+ baseline: 'Audit the last month of work starts from the daily logs.'
+ },
+ {
+ id: 'planning-resources',
+ label: 'Planning / resources / idle time',
+ keywords: [
+ 'plan',
+ 'planning',
+ 'commitment',
+ 'schedule reliability',
+ 'idle',
+ 'waiting',
+ 'wait',
+ 'crew',
+ 'resource',
+ 'look ahead',
+ 'lookahead',
+ 'promise',
+ 'weekly plan',
+ 'ppc',
+ 'churn'
+ ],
+ driver: 'Predictable weekly output, so crews are not idle and downstream work is not disrupted.',
+ cap: 'Collaborative short-interval planning with commitments tracked and variances learned from.',
+ metric: 'PPC — % of weekly commitments completed as promised.',
+ method: 'Log commitments at weekly planning, score the following week, publish the trend.',
+ baseline: 'The first 4 weeks of logging establish the baseline PPC.'
+ },
+ {
+ id: 'scope-change',
+ label: 'Scope control / change',
+ keywords: [
+ 'scope',
+ 'change order',
+ 'unauthorized',
+ 'extra work',
+ 't&m',
+ 'backcharge',
+ 'out of scope',
+ 'variation',
+ 'change log',
+ 'co log'
+ ],
+ driver: 'Changes are authorized and captured before work proceeds, protecting margin and schedule.',
+ cap: 'A change-control loop that logs and prices scope changes at the time they occur.',
+ metric: 'Unauthorized-work hours or dollars performed before a change was authorized.',
+ method: 'Monthly reconciliation of field records against the change-order log.',
+ baseline: 'Run the reconciliation on the current project to establish the baseline.'
+ },
+ {
+ id: 'quality-rework',
+ label: 'Quality / rework',
+ keywords: [
+ 'quality',
+ 'rework',
+ 'defect',
+ 'punch',
+ 'punchlist',
+ 'punch list',
+ 'redo',
+ 'first pass',
+ 'first-time',
+ 'inspection',
+ 'qc',
+ 'qa',
+ 'out of rev',
+ 'out-of-rev',
+ 'revision',
+ 'nonconformance',
+ 'ncr',
+ 'out of spec',
+ 'wrong'
+ ],
+ driver: 'Work done right the first time, cutting the craft hours lost to rework.',
+ cap: 'First-time-quality controls with rework captured and its causes coded.',
+ metric: 'Rework as a % of craft hours.',
+ method: 'A dedicated rework cost code with reason coding, reviewed monthly.',
+ baseline: 'Create the cost code; the first month of coding is the baseline.'
+ },
+ {
+ id: 'knowledge-feedback',
+ label: 'Knowledge / feedback',
+ keywords: [
+ 'lessons learned',
+ 'lesson learned',
+ 'closeout',
+ 'close-out',
+ 'handover',
+ 'handoff',
+ 'knowledge',
+ 'feedback',
+ 'estimate',
+ 'historical',
+ 'punch closure',
+ 'as-built',
+ 'as built',
+ 'documentation',
+ 'chain of custody',
+ 'design intent'
+ ],
+ driver: 'Lessons and actuals feed the next project, so estimates and plans improve over time.',
+ cap: 'A closeout and feedback loop that captures actuals and routes them to estimating.',
+ metric: 'Closeout compliance — % of closeouts completed within 60 days.',
+ method: 'Closeout checklist audit against project completion dates.',
+ baseline: 'Audit the last 4–6 projects to establish the baseline.'
+ }
+];
+
+/** Domain options for a UI picker: the seven domains plus the catch-all. */
+export const DOMAIN_OPTIONS: { id: string; label: string }[] = [
+ ...DOMAINS.map((d) => ({ id: d.id, label: d.label })),
+ { id: GENERAL.id, label: GENERAL.label }
+];
+
+function domainById(id: string): Domain | undefined {
+ if (id === GENERAL.id) return GENERAL;
+ return DOMAINS.find((d) => d.id === id);
+}
+
+/**
+ * Classify a problem into a domain by counting keyword hits. Returns the domain
+ * id with the most matches, or 'general' if nothing matches. `related` issues
+ * are weighted lightly so they can break ties but not overrule the statement.
+ */
+export function classifyDomain(prob: string, related: string[] = []): string {
+ const main = String(prob).toLowerCase();
+ const rel = related.map((r) => String(r).toLowerCase()).join(' \n ');
+
+ let best = GENERAL.id;
+ let bestScore = 0;
+ for (const d of DOMAINS) {
+ let score = 0;
+ for (const kw of d.keywords) {
+ if (main.includes(kw)) score += 2;
+ if (rel.includes(kw)) score += 1;
+ }
+ if (score > bestScore) {
+ bestScore = score;
+ best = d.id;
+ }
+ }
+ return best;
+}
+
+/**
+ * Fill the five breadcrumb fields from methodology. If `forceDomain` is a real
+ * domain id, that domain's template is used; otherwise the problem is classified
+ * automatically. The result is a starting draft for the team to edit.
+ */
+export function suggestBreadcrumb(prob: string, related: string[] = [], forceDomain = ''): Suggestion {
+ const domain = domainById(forceDomain) ?? domainById(classifyDomain(prob, related)) ?? GENERAL;
+ return {
+ domain: domain.id,
+ label: domain.label,
+ driver: domain.driver,
+ cap: domain.cap,
+ metric: domain.metric,
+ method: domain.method,
+ baseline: domain.baseline
+ };
+}
diff --git a/src/lib/server/ai.ts b/src/lib/server/ai.ts
deleted file mode 100644
index ff05e71..0000000
--- a/src/lib/server/ai.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-// Server-side "AI draft" for breadcrumb fields. The Anthropic API key lives here
-// (from an env var) and never reaches the browser.
-
-import Anthropic from '@anthropic-ai/sdk';
-import { env } from '$env/dynamic/private';
-
-export const REQUIRED_FIELDS = ['driver', 'cap', 'metric', 'method', 'baseline'] as const;
-export const ADOPTION_WORDS =
- /implement|adopt|roll ?out|have a tool|tool exists|usage|active users?|users? active/i;
-
-export interface DraftResult {
- domain: string;
- driver: string;
- cap: string;
- metric: string;
- method: string;
- baseline: string;
-}
-
-const METHOD_KB = `Domain playbook (anchor methodology -> standard metrics/methods/baselines):
-1 PROGRESS VISIBILITY - Earned Value Mgmt, rules of credit. Metrics: claiming accuracy (reported % vs field-verified % within tolerance, by phase; method: monthly spot audit of 30+ sampled assets; baseline: first audit); forecast reliability (forecast vs actual completion; baseline: last project's reports). Never use "% of assets with a status" (adoption in disguise).
-2 DATA FRESHNESS / ENTRY BURDEN - lean data flow, single source of truth. Metrics: entry latency (% of updates within 24h of completion; method: system timestamp vs completion date, weekly; baseline: audit paper-to-digital lag on current project); touch count (systems the same record is entered into; method: trace 10 records end to end; baseline: pre-pilot trace); non-productive data minutes/person/day (method+baseline: structured field poll pre-pilot, repeat midpoint).
-3 CONSTRAINT READINESS / WORK RELEASE - CII AWP IR 272-2, constraint-free IWP release (drawings, material, tools, labor, access, predecessors, permits, quality docs, scaffolding). Metrics: % releases issued constraint-free (method: release-gate checklist audit weekly, later constraint stop = miss; baseline: audit last month of work starts from daily logs); stand-down count/week (method: foreman log with fixed reason list; baseline: 2-week logging exercise); tool time % (method: activity sampling study; baseline: pre-pilot study; propose only when stakes justify cost).
-4 PLANNING / RESOURCES / IDLE - Last Planner System. Metrics: PPC (% weekly commitments completed as promised; method: log at weekly planning, score next week, publish trend; baseline: first 4 weeks of logging); reasons-for-variance distribution (fixed-list coding of misses); idle hours/crew-week (timecard or foreman reason codes; baseline: 2-week coding).
-5 SCOPE CONTROL / CHANGE - PMBOK change control, CII change research. Metrics: unauthorized-work hours/dollars at time of performance (method: monthly reconciliation of field records vs CO log; baseline: run reconciliation on current project); change capture latency days (field record date vs log date); change cycle time (log timestamps; baseline: last project's log).
-6 QUALITY / REWORK - first-time quality, CII rework research (2-6% direct cost where measured). Metrics: rework % of craft hours (method: dedicated rework cost code + reason coding, monthly; baseline: create the code, first month is baseline); first-pass yield at QC gates; out-of-rev incidents (QC walkdown coding).
-7 KNOWLEDGE / FEEDBACK - CII lessons-learned, estimate feedback. Metrics: closeout compliance % within 60 days (method: closeout checklist audit; baseline: audit last 4-6 projects); % estimates referencing prior actuals (estimating file audit); punch closure median days (issue log timestamps; if untimestamped, that gap is the finding).
-GATES: driver = business end in cost/schedule/quality/safety/predictability terms, never a tool. capability = function not product, no vendor names. metric = outcome ratio/rate, NEVER "implemented/adopted/rolled out/active users/have a tool". method = data source + comparison + cadence. baseline = a number or the named pre-pilot study that produces one (cheapest defensible: poll < record trace < log audit < 2-week coding < sampling study). Set targets after baseline, not before. Each field under ~30 words except method/baseline.`;
-
-function parseJson(text: string): Record {
- const clean = String(text).replace(/```json|```/g, '').trim();
- const candidates = ['[', '{'].map((ch) => {
- const i = clean.indexOf(ch);
- return i === -1 ? Infinity : i;
- });
- const start = Math.min(...candidates);
- return JSON.parse(start === Infinity ? clean : clean.slice(start));
-}
-
-/** True when the AI feature is configured. Used to disable the button gracefully. */
-export function aiConfigured(): boolean {
- return Boolean(env.ANTHROPIC_API_KEY);
-}
-
-export async function draftBreadcrumb(prob: string, related: string[]): Promise {
- const apiKey = env.ANTHROPIC_API_KEY;
- if (!apiKey) throw new Error('AI is not configured on this server (missing ANTHROPIC_API_KEY).');
- if (!prob.trim()) throw new Error('Write the problem statement first.');
-
- const client = new Anthropic({ apiKey });
- const rel = related.length
- ? `\nRelated issues in this cluster:\n${related.map((r) => '- ' + r).join('\n')}`
- : '';
- const prompt = `You populate breadcrumb trails for construction field problems at an industrial I&C contractor, using best-in-class execution methodology.\n\n${METHOD_KB}\n\nProblem statement:\n${prob}${rel}\n\nClassify the problem into one domain above, then draft the five fields per that domain's standard metrics, methods, and baselines, obeying every GATE. Respond ONLY with a JSON object, no prose, no markdown fences:\n{"domain":"...","driver":"...","cap":"...","metric":"...","method":"...","baseline":"..."}`;
-
- const message = await client.messages.create({
- model: env.ANTHROPIC_MODEL || 'claude-sonnet-5',
- max_tokens: 1000,
- messages: [{ role: 'user', content: prompt }]
- });
-
- const text = message.content
- .filter((block): block is Anthropic.TextBlock => block.type === 'text')
- .map((block) => block.text)
- .join('\n');
-
- const obj = parseJson(text);
- for (const f of REQUIRED_FIELDS) {
- if (!obj[f]) throw new Error('AI returned an incomplete draft; try again.');
- }
- if (ADOPTION_WORDS.test(String(obj.metric))) {
- throw new Error('Draft failed the adoption gate; try again.');
- }
-
- return {
- domain: String(obj.domain ?? 'methodology'),
- driver: String(obj.driver),
- cap: String(obj.cap),
- metric: String(obj.metric),
- method: String(obj.method),
- baseline: String(obj.baseline)
- };
-}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 3b28bca..7ac9e65 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -32,6 +32,9 @@
diff --git a/src/routes/api/ai/draft/+server.ts b/src/routes/api/ai/draft/+server.ts
deleted file mode 100644
index 7bbad6f..0000000
--- a/src/routes/api/ai/draft/+server.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { error, json } from '@sveltejs/kit';
-import { draftBreadcrumb } from '$lib/server/ai';
-import type { RequestHandler } from './$types';
-
-/**
- * POST /api/ai/draft — draft breadcrumb fields from a problem statement.
- * Body: { prob: string, related?: string[] }
- */
-export const POST: RequestHandler = async ({ request }) => {
- let body: { prob?: string; related?: string[] };
- try {
- body = await request.json();
- } catch {
- throw error(400, 'Body must be JSON');
- }
-
- const prob = String(body.prob ?? '').trim();
- const related = Array.isArray(body.related) ? body.related.map(String) : [];
-
- try {
- const draft = await draftBreadcrumb(prob, related);
- return json(draft);
- } catch (e) {
- const message = e instanceof Error ? e.message : 'AI draft failed';
- // 422: the request was well-formed but the AI step could not complete.
- throw error(422, message);
- }
-};
diff --git a/src/routes/scope-lock/+page.svelte b/src/routes/scope-lock/+page.svelte
index 3a70d6c..40ad7fb 100644
--- a/src/routes/scope-lock/+page.svelte
+++ b/src/routes/scope-lock/+page.svelte
@@ -404,6 +404,9 @@