carthagent 0.0.0-stage → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +219 -2
- package/bin/ctg.mjs +736 -0
- package/docs/budgets.md +72 -0
- package/docs/compaction.md +34 -0
- package/docs/context-efficiency-plan.md +24 -0
- package/docs/context-efficiency-review.md +27 -0
- package/docs/context-efficiency.md +49 -0
- package/docs/deepseek-jev-benchmark-plan.md +70 -0
- package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
- package/docs/deepseek-jev-benchmark-restart.md +9 -0
- package/docs/deepseek-jev-benchmark-results.md +77 -0
- package/docs/deepseek-jev-benchmark-running.md +60 -0
- package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
- package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
- package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
- package/docs/deepseek-jev-http400-diagnosis.md +45 -0
- package/docs/delivery-review-evidence.md +53 -0
- package/docs/delivery-timeout-repair-plan.md +51 -0
- package/docs/evaluation-readiness.md +31 -0
- package/docs/evaluation.md +70 -0
- package/docs/extending.md +354 -0
- package/docs/glm-pilot-execution.md +9 -0
- package/docs/glm-pilot-plan.md +15 -0
- package/docs/glm-pilot-results.md +71 -0
- package/docs/graph-context-optimization.md +15 -0
- package/docs/jev-repository-pilot-plan.md +25 -0
- package/docs/jev-repository-pilot-protocol.md +39 -0
- package/docs/jev-repository-pilot-restart.md +7 -0
- package/docs/jev-repository-pilot-results.md +79 -0
- package/docs/jev.md +32 -0
- package/docs/model-registry.md +25 -0
- package/docs/native-compatibility.md +117 -0
- package/docs/opencode-archive.md +9 -0
- package/docs/opencode-migration.md +115 -0
- package/docs/opencode-runtime.md +94 -0
- package/docs/opencode-workspace.md +31 -0
- package/docs/owned-runtime-workflow-registration.md +17 -0
- package/docs/plan-advisor-evaluation.md +38 -0
- package/docs/planning-startup-fix-plan.md +23 -0
- package/docs/planning-startup-fix-review.md +24 -0
- package/docs/planning.md +118 -0
- package/docs/production-merge-review.md +23 -0
- package/docs/quality.md +35 -0
- package/docs/repository-benchmark-design.md +111 -0
- package/docs/review-assurance.md +9 -0
- package/docs/review-experiment-results.md +59 -0
- package/docs/tested-planning-roadmap.md +58 -0
- package/docs/tested-planning.md +58 -0
- package/docs/tui-pickers.md +9 -0
- package/docs/webdriver.md +83 -0
- package/docs/workflow-modes.md +26 -0
- package/docs/workflow.d2 +17 -0
- package/docs/workflow.svg +149 -0
- package/docs/workspace-coordination.md +9 -0
- package/evaluate.mjs +249 -0
- package/extensions/codegraph.ts +36 -0
- package/extensions/delivery.ts +819 -0
- package/extensions/experiential.ts +21 -0
- package/extensions/subscriptions.ts +21 -0
- package/guidance/profiles/authenticated-web.json +24 -0
- package/guidance/profiles/data-visualization.json +25 -0
- package/guidance/profiles/database-change.json +24 -0
- package/guidance/profiles/external-api.json +24 -0
- package/guidance/profiles/transactional-data.json +25 -0
- package/guidance/profiles/web-application.json +26 -0
- package/lib/acceptance-review.mjs +4 -0
- package/lib/browser-bin.mjs +160 -0
- package/lib/browser-errors.mjs +54 -0
- package/lib/browser.mjs +219 -0
- package/lib/budget.mjs +37 -0
- package/lib/cdp.mjs +221 -0
- package/lib/cloud/client.mjs +275 -0
- package/lib/cloud/control-plane.mjs +350 -0
- package/lib/cloud/gateway.mjs +380 -0
- package/lib/cloud/http.mjs +135 -0
- package/lib/cloud/ledger.mjs +215 -0
- package/lib/codegraph-query.mjs +60 -0
- package/lib/codegraph.mjs +378 -0
- package/lib/context-archive.mjs +18 -0
- package/lib/delivery-context.mjs +132 -0
- package/lib/delivery-guidance.mjs +9 -0
- package/lib/delivery-review.mjs +94 -0
- package/lib/delivery.mjs +610 -0
- package/lib/engine.mjs +137 -0
- package/lib/guidance.mjs +71 -0
- package/lib/jev-advisor.mjs +47 -0
- package/lib/opencode/archive.mjs +122 -0
- package/lib/opencode/compatibility.mjs +19 -0
- package/lib/opencode/credentials.mjs +205 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
- package/lib/opencode/evaluation.mjs +242 -0
- package/lib/opencode/file-lock.mjs +86 -0
- package/lib/opencode/headless.mjs +200 -0
- package/lib/opencode/review.mjs +213 -0
- package/lib/opencode/runtime.mjs +246 -0
- package/lib/opencode/service.mjs +249 -0
- package/lib/opencode/session-options.mjs +60 -0
- package/lib/opencode/workspace.mjs +133 -0
- package/lib/openrouter-jev.mjs +37 -0
- package/lib/outcome-contract.mjs +21 -0
- package/lib/plan-advisor.mjs +38 -0
- package/lib/planning-access.mjs +7 -0
- package/lib/planning-advice.mjs +28 -0
- package/lib/planning-discovery.mjs +14 -0
- package/lib/planning-guide.mjs +34 -0
- package/lib/planning-view.mjs +57 -0
- package/lib/planning.mjs +155 -0
- package/lib/probe-diagnostics.mjs +13 -0
- package/lib/providers/experiential.mjs +292 -0
- package/lib/providers/model-catalog.json +700 -0
- package/lib/providers/model-catalog.mjs +7 -0
- package/lib/providers/names.mjs +13 -0
- package/lib/providers/subscription-models.mjs +483 -0
- package/lib/reports.mjs +80 -0
- package/lib/review-state.mjs +49 -0
- package/lib/review.mjs +114 -0
- package/lib/scenarios.mjs +99 -0
- package/lib/ship-auth.mjs +44 -0
- package/lib/ship-jev.mjs +59 -0
- package/lib/ship-telemetry.mjs +183 -0
- package/lib/tui/ansi.mjs +255 -0
- package/lib/tui/app.mjs +2445 -0
- package/lib/tui/auth.mjs +249 -0
- package/lib/tui/carthage.mjs +109 -0
- package/lib/tui/clipboard.mjs +122 -0
- package/lib/tui/d2.mjs +431 -0
- package/lib/tui/feed.mjs +467 -0
- package/lib/tui/framing.mjs +80 -0
- package/lib/tui/glyphs.mjs +172 -0
- package/lib/tui/hash-worker.mjs +11 -0
- package/lib/tui/mock-engine.mjs +222 -0
- package/lib/tui/models.mjs +73 -0
- package/lib/tui/picker.mjs +18 -0
- package/lib/tui/theme.mjs +663 -0
- package/lib/tui/title.mjs +7 -0
- package/lib/update-channel.mjs +62 -0
- package/lib/update.mjs +230 -0
- package/lib/webdriver.mjs +82 -0
- package/lib/workflow.mjs +88 -0
- package/lib/workspace.mjs +39 -0
- package/metadata.json +8 -0
- package/package.json +94 -4
- package/plugins/carthagent/index.ts +3 -0
- package/plugins/carthagent/package.json +28 -0
- package/plugins/carthagent/src/archive-rpc.ts +46 -0
- package/plugins/carthagent/src/bootstrap.ts +38 -0
- package/plugins/carthagent/src/index.ts +62 -0
- package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
- package/plugins/carthagent/src/rpc.ts +76 -0
- package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
- package/plugins/carthagent/src/server/budgets.ts +21 -0
- package/plugins/carthagent/src/server/codegraph.ts +46 -0
- package/plugins/carthagent/src/server/delivery.ts +488 -0
- package/plugins/carthagent/src/server/device-auth.ts +91 -0
- package/plugins/carthagent/src/server/native-delivery.ts +26 -0
- package/plugins/carthagent/src/server/orchestration.ts +56 -0
- package/plugins/carthagent/src/server/policy.ts +24 -0
- package/plugins/carthagent/src/server/providers.ts +424 -0
- package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
- package/plugins/carthagent/src/server/session-controls.ts +82 -0
- package/plugins/carthagent/src/server/telemetry.ts +92 -0
- package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
- package/plugins/carthagent/src/session-rpc.ts +31 -0
- package/plugins/carthagent/src/testing.ts +85 -0
- package/plugins/carthagent/src/workspace-rpc.ts +133 -0
- package/plugins/carthagent/test-plugin/index.ts +4 -0
- package/plugins/carthagent/test-plugin/package.json +1 -0
- package/plugins/carthagent/tsconfig.json +19 -0
- package/plugins/carthagent/tui.ts +1 -0
- package/prompts/delivery.md +7 -0
- package/public/index.html +983 -0
- package/scenarios/cli/grade_cli.py +69 -0
- package/scenarios/cli/scenario.json +21 -0
- package/scenarios/game/baseline/minecraft.py +193 -0
- package/scenarios/game/context.md +1 -0
- package/scenarios/game/grade_minecraft_clone.py +603 -0
- package/scenarios/game/requirements.txt +2 -0
- package/scenarios/game/scenario.json +33 -0
- package/server.mjs +341 -0
- package/skills/game-development/SKILL.md +36 -0
- package/skills/game-development/assets/ursina_starter.py +98 -0
- package/skills/game-development/scripts/verify_ursina.py +80 -0
- package/skills/software-delivery/SKILL.md +50 -0
- package/skills/typescript-delivery/SKILL.md +69 -0
- package/tests/acceptance-review.test.mjs +13 -0
- package/tests/auth.test.mjs +234 -0
- package/tests/bounded-summary.test.mjs +68 -0
- package/tests/browser-bin.test.mjs +91 -0
- package/tests/browser.test.mjs +96 -0
- package/tests/budget-engine.test.mjs +110 -0
- package/tests/budget.test.mjs +23 -0
- package/tests/cli.test.mjs +245 -0
- package/tests/clipboard.test.mjs +79 -0
- package/tests/cloud-client.test.mjs +129 -0
- package/tests/cloud-control-plane.test.mjs +144 -0
- package/tests/cloud-gateway.test.mjs +147 -0
- package/tests/cloud-http.test.mjs +102 -0
- package/tests/cloud-ledger.test.mjs +75 -0
- package/tests/codegraph-query.test.mjs +66 -0
- package/tests/codegraph.test.mjs +142 -0
- package/tests/delivery-context.test.mjs +66 -0
- package/tests/delivery-review.test.mjs +139 -0
- package/tests/delivery.test.mjs +343 -0
- package/tests/engine-api.mjs +24 -0
- package/tests/experiential-extension.test.mjs +37 -0
- package/tests/experiential.test.mjs +300 -0
- package/tests/extension.test.mjs +921 -0
- package/tests/fixtures/archive-plugin/index.ts +10 -0
- package/tests/guidance.test.mjs +78 -0
- package/tests/model-registry.test.mjs +68 -0
- package/tests/models.test.mjs +70 -0
- package/tests/openai-idempotency.test.mjs +99 -0
- package/tests/opencode-archive-rpc.test.mjs +82 -0
- package/tests/opencode-delivery.test.mjs +231 -0
- package/tests/opencode-providers.test.mjs +414 -0
- package/tests/opencode-runtime.test.mjs +455 -0
- package/tests/opencode-session-controls.test.mjs +115 -0
- package/tests/opencode-session-options.test.mjs +26 -0
- package/tests/opencode-workspace.test.mjs +339 -0
- package/tests/outcome-contract.test.mjs +17 -0
- package/tests/picker.test.mjs +34 -0
- package/tests/plan-advisor.test.mjs +88 -0
- package/tests/plan-review-loop.test.mjs +53 -0
- package/tests/planning-advice.test.mjs +27 -0
- package/tests/planning-contract.test.mjs +65 -0
- package/tests/planning-startup.test.mjs +38 -0
- package/tests/planning-view.test.mjs +33 -0
- package/tests/planning.test.mjs +223 -0
- package/tests/probe-diagnostics.test.mjs +21 -0
- package/tests/pull-request-rpc.test.mjs +366 -0
- package/tests/review-state.test.mjs +32 -0
- package/tests/review.test.mjs +127 -0
- package/tests/server.test.mjs +252 -0
- package/tests/ship-jev.test.mjs +77 -0
- package/tests/ship-telemetry.test.mjs +333 -0
- package/tests/standard-workflow.test.mjs +28 -0
- package/tests/subscription-models.test.mjs +416 -0
- package/tests/subscriptions-extension.test.mjs +27 -0
- package/tests/terminal-title.test.mjs +13 -0
- package/tests/test_grade_minecraft_clone.py +199 -0
- package/tests/test_starter.py +78 -0
- package/tests/theme.test.mjs +175 -0
- package/tests/tui-copy-paste.test.mjs +54 -0
- package/tests/tui.test.mjs +808 -0
- package/tests/update-channels.test.mjs +129 -0
- package/tests/update.test.mjs +175 -0
- package/tests/webdriver.test.mjs +31 -0
- package/tests/workflow.test.mjs +47 -0
- package/tests/workspace-rpc.test.mjs +236 -0
- package/tests/workspace.test.mjs +24 -0
- package/tools/benchmark/advisor.test.mjs +7 -0
- package/tools/benchmark/advisor.ts +27 -0
- package/tools/benchmark/campaign.mjs +45 -0
- package/tools/benchmark/diagnose-request.mjs +40 -0
- package/tools/benchmark/eval-common.mjs +32 -0
- package/tools/benchmark/eval-ledger.mjs +22 -0
- package/tools/benchmark/eval-shop.mjs +26 -0
- package/tools/benchmark/eval-voxel.mjs +20 -0
- package/tools/benchmark/evaluate.mjs +24 -0
- package/tools/benchmark/evaluators.test.mjs +13 -0
- package/tools/benchmark/fixtures/shop/server.mjs +18 -0
- package/tools/benchmark/fixtures/shop/ui.html +8 -0
- package/tools/benchmark/freeze.mjs +30 -0
- package/tools/benchmark/gateway.mjs +91 -0
- package/tools/benchmark/gateway.test.mjs +38 -0
- package/tools/benchmark/generator.mjs +11 -0
- package/tools/benchmark/generator.test.mjs +26 -0
- package/tools/benchmark/guard.test.mjs +133 -0
- package/tools/benchmark/guard.ts +136 -0
- package/tools/benchmark/native-plugin/index.ts +24 -0
- package/tools/benchmark/planning-startup.test.mjs +47 -0
- package/tools/benchmark/preflight-live.mjs +31 -0
- package/tools/benchmark/preflight.mjs +22 -0
- package/tools/benchmark/replay-diagnostic.mjs +23 -0
- package/tools/benchmark/runner.mjs +141 -0
- package/tools/benchmark/runner.test.mjs +93 -0
- package/tools/benchmark/runtime.mjs +23 -0
- package/tools/benchmark/selftest.mjs +47 -0
- package/tools/benchmark/smoke-jev.mjs +5 -0
- package/tools/benchmark/tasks.mjs +20 -0
- package/tools/benchmark/transport.mjs +65 -0
- package/tools/benchmark/transport.test.mjs +44 -0
- package/tools/benchmark/upstream-error.mjs +18 -0
- package/tools/benchmark/upstream-error.test.mjs +10 -0
- package/tools/browser-check.mjs +109 -0
- package/tools/check-syntax.mjs +27 -0
- package/tools/evaluate-plan-advisor.mjs +20 -0
- package/tools/native-fixture.mjs +194 -0
- package/tools/native-pty-smoke.py +108 -0
- package/tools/native-report-plugin/index.ts +50 -0
- package/tools/native-report-plugin/package.json +7 -0
- package/tools/patch-compaction.mjs +24 -0
- package/tools/patch-model-catalog.mjs +12 -0
- package/tools/patch-print-shutdown.mjs +39 -0
- package/tools/repo-pilot/evaluator.cjs +32 -0
- package/tools/repo-pilot/reference.mjs +43 -0
- package/tools/repo-pilot/runner.mjs +85 -0
- package/tools/repo-pilot/sandbox-entry.mjs +12 -0
- package/tools/repo-pilot/tasks.mjs +20 -0
- package/tools/repo-pilot/type-evaluator.cjs +6 -0
- package/tools/test-native-controls.mjs +84 -0
- package/tools/test-native-delivery-host.mjs +307 -0
- package/tools/test-native-evaluation.mjs +46 -0
- package/tools/test-native-host.mjs +385 -0
- package/tools/test-native-outcomes.mjs +73 -0
- package/tools/test-native-package.mjs +70 -0
- package/tools/test-native-pty.mjs +20 -0
- package/tools/test-native-report.mjs +88 -0
- package/tools/test-native-swe-host.mjs +132 -0
- package/tools/test-native-swe.mjs +122 -0
- package/tools/test-picker-pty.py +45 -0
- package/tools/test-python.mjs +29 -0
- package/tools/test-webdriver.mjs +54 -0
- package/tools/verify-release-version.mjs +37 -0
- package/tools/webdriver-check.mjs +52 -0
- package/vendor/agent/bounded-summary.mjs +88 -0
- package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
- package/vendor/agent/chunks/anthropic.js +75 -0
- package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
- package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
- package/vendor/agent/chunks/chord-context.js +96 -0
- package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
- package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
- package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
- package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
- package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
- package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
- package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
- package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
- package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
- package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
- package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
- package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
- package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
- package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
- package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
- package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
- package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
- package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
- package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
- package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
- package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
- package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
- package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
- package/vendor/agent/chunks/github-copilot.js +2 -0
- package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
- package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
- package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
- package/vendor/agent/chunks/image-resize-worker.js +2 -0
- package/vendor/agent/chunks/kimi-coding.js +2 -0
- package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
- package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
- package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
- package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
- package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
- package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
- package/vendor/agent/chunks/openai-codex.js +75 -0
- package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
- package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
- package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
- package/vendor/agent/chunks/openrouter.js +75 -0
- package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
- package/vendor/agent/chunks/radius.js +75 -0
- package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
- package/vendor/agent/chunks/xai.js +2 -0
- package/vendor/agent/cli.js +3 -0
- package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/index.js +2 -0
- package/vendor/agent/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/package.json +14 -0
- package/vendor/agent/rpc-entry.js +3 -0
- package/vendor/agent/theme/dark.json +91 -0
- package/vendor/agent/theme/light.json +90 -0
- package/vendor/agent/theme/theme-schema.json +356 -0
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Carthagent — Delivery Control</title>
|
|
7
|
+
<meta name="description" content="Evidence-driven software delivery, from plan to proof">
|
|
8
|
+
<meta name="theme-color" content="#171021">
|
|
9
|
+
<meta property="og:title" content="Carthagent">
|
|
10
|
+
<meta property="og:description" content="Evidence-driven software delivery, from plan to proof">
|
|
11
|
+
<script>
|
|
12
|
+
(() => {
|
|
13
|
+
const param = new URLSearchParams(location.search).get('theme');
|
|
14
|
+
const chosen = param || localStorage.getItem('carthagent-theme') || 'carthage';
|
|
15
|
+
if (param) localStorage.setItem('carthagent-theme', param);
|
|
16
|
+
const dark = typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;
|
|
17
|
+
document.documentElement.dataset.theme = chosen === 'system' ? (dark ? 'obsidian' : 'paper') : chosen;
|
|
18
|
+
document.documentElement.dataset.themeChoice = chosen;
|
|
19
|
+
})();
|
|
20
|
+
</script>
|
|
21
|
+
<style>
|
|
22
|
+
:root {
|
|
23
|
+
--mono: "IBM Plex Mono", "Cascadia Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
24
|
+
--sans: "Avenir Next", Avenir, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
25
|
+
--brand-copper: #d98951;
|
|
26
|
+
--brand-mint: #61d6a5;
|
|
27
|
+
}
|
|
28
|
+
/* Every theme must define the full token set; tests/theme.test.mjs verifies
|
|
29
|
+
presence plus WCAG AA contrast (4.5:1 text, 3:1 UI components). */
|
|
30
|
+
:root, [data-theme="carthage"] {
|
|
31
|
+
color-scheme: dark;
|
|
32
|
+
--bg: #120d1c; --chrome: #171021; --panel: #1d1429; --panel-2: #261a35; --sunken: #0d0914;
|
|
33
|
+
--border: #49385d; --border-strong: #67517f; --border-hover: #846c9e; --control-border: #9a86ae;
|
|
34
|
+
--text: #f7f1e5; --muted: #b8a9c9; --faint: #a99abb; --code: #ead9ff;
|
|
35
|
+
--accent: #f2c66d; --accent-ink: #211506; --accent-bg: #3b2c16; --hover-bg: #302140;
|
|
36
|
+
--success: #76d6aa; --warning: #f2c66d; --error: #ff969e;
|
|
37
|
+
}
|
|
38
|
+
[data-theme="obsidian"] {
|
|
39
|
+
color-scheme: dark;
|
|
40
|
+
--bg: #09110f; --chrome: #0d1714; --panel: #111d19; --panel-2: #172620; --sunken: #07100d;
|
|
41
|
+
--border: #23382f; --border-strong: #365247; --border-hover: #527465; --control-border: #718f82;
|
|
42
|
+
--text: #edf4ef; --muted: #a5b7ad; --faint: #82968c; --code: #d5e1da;
|
|
43
|
+
--accent: #e1955f; --accent-ink: #22130a; --accent-bg: #3a2418; --hover-bg: #1b2c26;
|
|
44
|
+
--success: #61d6a5; --warning: #f0bd69; --error: #ff8585;
|
|
45
|
+
}
|
|
46
|
+
[data-theme="midnight"] {
|
|
47
|
+
color-scheme: dark;
|
|
48
|
+
--bg: #0d1117; --chrome: #161b22; --panel: #161b22; --panel-2: #21262d; --sunken: #010409;
|
|
49
|
+
--border: #30363d; --border-strong: #3d444d; --border-hover: #484f58; --control-border: #6e7681;
|
|
50
|
+
--text: #e6edf3; --muted: #9198a1; --faint: #7d8590; --code: #d1d7e0;
|
|
51
|
+
--accent: #58a6ff; --accent-ink: #051120; --accent-bg: #12233a; --hover-bg: #262c33;
|
|
52
|
+
--success: #56d364; --warning: #e3b341; --error: #ff7b72;
|
|
53
|
+
}
|
|
54
|
+
[data-theme="solarized-dark"] {
|
|
55
|
+
color-scheme: dark;
|
|
56
|
+
--bg: #002b36; --chrome: #00232c; --panel: #073642; --panel-2: #0a4050; --sunken: #00212b;
|
|
57
|
+
--border: #0d4a5a; --border-strong: #1a5c6e; --border-hover: #2a6d82; --control-border: #51828f;
|
|
58
|
+
--text: #a2b2b2; --muted: #93a1a1; --faint: #8d9c9e; --code: #a5b5b5;
|
|
59
|
+
--accent: #5eacdc; --accent-ink: #04222e; --accent-bg: #0a3a4d; --hover-bg: #0c4353;
|
|
60
|
+
--success: #a4b94e; --warning: #d4a72c; --error: #f0938a;
|
|
61
|
+
}
|
|
62
|
+
[data-theme="nord"] {
|
|
63
|
+
color-scheme: dark;
|
|
64
|
+
--bg: #2e3440; --chrome: #2b303c; --panel: #3b4252; --panel-2: #3f4653; --sunken: #272c37;
|
|
65
|
+
--border: #4c566a; --border-strong: #5b6a82; --border-hover: #6a7a94; --control-border: #818eab;
|
|
66
|
+
--text: #eceff4; --muted: #c0c9d8; --faint: #a6aec0; --code: #d8dee9;
|
|
67
|
+
--accent: #8fc4d3; --accent-ink: #20262f; --accent-bg: #39485f; --hover-bg: #464f61;
|
|
68
|
+
--success: #a3be8c; --warning: #ebcb8b; --error: #e6a8ad;
|
|
69
|
+
}
|
|
70
|
+
[data-theme="contrast-dark"] {
|
|
71
|
+
color-scheme: dark;
|
|
72
|
+
--bg: #000000; --chrome: #000000; --panel: #0d0d0d; --panel-2: #1a1a1a; --sunken: #000000;
|
|
73
|
+
--border: #6f6f6f; --border-strong: #8f8f8f; --border-hover: #aaaaaa; --control-border: #9a9a9a;
|
|
74
|
+
--text: #ffffff; --muted: #b8b8b8; --faint: #949494; --code: #e0e0e0;
|
|
75
|
+
--accent: #82aaff; --accent-ink: #001133; --accent-bg: #16233f; --hover-bg: #1f1f1f;
|
|
76
|
+
--success: #5eead4; --warning: #fde047; --error: #ff8a8a;
|
|
77
|
+
}
|
|
78
|
+
[data-theme="okabe-dark"] {
|
|
79
|
+
color-scheme: dark;
|
|
80
|
+
--bg: #121316; --chrome: #15171b; --panel: #1a1c21; --panel-2: #22252b; --sunken: #0d0e11;
|
|
81
|
+
--border: #30333b; --border-strong: #41454f; --border-hover: #535763; --control-border: #6d727d;
|
|
82
|
+
--text: #e8eaed; --muted: #9ba1aa; --faint: #7e8490; --code: #c9cdd4;
|
|
83
|
+
--accent: #56b4e9; --accent-ink: #071824; --accent-bg: #173246; --hover-bg: #26292f;
|
|
84
|
+
--success: #35d6a5; --warning: #e8d24a; --error: #f07c3f;
|
|
85
|
+
}
|
|
86
|
+
[data-theme="paper"] {
|
|
87
|
+
color-scheme: light;
|
|
88
|
+
--bg: #faf9f6; --chrome: #f1efe9; --panel: #ffffff; --panel-2: #f1efe9; --sunken: #f5f3ee;
|
|
89
|
+
--border: #e0dcd2; --border-strong: #c9c3b4; --border-hover: #a8a296; --control-border: #7a7466;
|
|
90
|
+
--text: #24292f; --muted: #5c6470; --faint: #666d76; --code: #3a424d;
|
|
91
|
+
--accent: #5b4bd4; --accent-ink: #ffffff; --accent-bg: #eceafc; --hover-bg: #e8e5db;
|
|
92
|
+
--success: #157347; --warning: #8a6100; --error: #cb2d3b;
|
|
93
|
+
}
|
|
94
|
+
[data-theme="daylight"] {
|
|
95
|
+
color-scheme: light;
|
|
96
|
+
--bg: #f6f8fa; --chrome: #ffffff; --panel: #ffffff; --panel-2: #eef1f4; --sunken: #f6f8fa;
|
|
97
|
+
--border: #d1d9e0; --border-strong: #b7c0c9; --border-hover: #8c959f; --control-border: #7d8590;
|
|
98
|
+
--text: #1f2328; --muted: #59636e; --faint: #6a7280; --code: #383f47;
|
|
99
|
+
--accent: #0963c9; --accent-ink: #ffffff; --accent-bg: #ddf1ff; --hover-bg: #e6eaee;
|
|
100
|
+
--success: #1a7f37; --warning: #8a5700; --error: #cf222e;
|
|
101
|
+
}
|
|
102
|
+
[data-theme="solarized-light"] {
|
|
103
|
+
color-scheme: light;
|
|
104
|
+
--bg: #fdf6e3; --chrome: #faf2de; --panel: #fdf6e3; --panel-2: #f1e9d2; --sunken: #faf2de;
|
|
105
|
+
--border: #e3d8b8; --border-strong: #cbbf98; --border-hover: #ab9f78; --control-border: #8a7f61;
|
|
106
|
+
--text: #073642; --muted: #586e75; --faint: #5a6e75; --code: #2d4a52;
|
|
107
|
+
--accent: #1e6aa4; --accent-ink: #ffffff; --accent-bg: #e6f0f8; --hover-bg: #ece2c6;
|
|
108
|
+
--success: #636f00; --warning: #866500; --error: #c62d2b;
|
|
109
|
+
}
|
|
110
|
+
[data-theme="contrast-light"] {
|
|
111
|
+
color-scheme: light;
|
|
112
|
+
--bg: #ffffff; --chrome: #ffffff; --panel: #ffffff; --panel-2: #f2f2f2; --sunken: #f7f7f7;
|
|
113
|
+
--border: #767676; --border-strong: #595959; --border-hover: #404040; --control-border: #595959;
|
|
114
|
+
--text: #000000; --muted: #3d3d3d; --faint: #525252; --code: #1a1a1a;
|
|
115
|
+
--accent: #0043c8; --accent-ink: #ffffff; --accent-bg: #e0ebff; --hover-bg: #e8e8e8;
|
|
116
|
+
--success: #00633b; --warning: #694d00; --error: #b00020;
|
|
117
|
+
}
|
|
118
|
+
[data-theme="okabe-light"] {
|
|
119
|
+
color-scheme: light;
|
|
120
|
+
--bg: #fbfbf8; --chrome: #ffffff; --panel: #ffffff; --panel-2: #f2f2ec; --sunken: #f6f6f0;
|
|
121
|
+
--border: #dadad0; --border-strong: #b8b8a8; --border-hover: #90907e; --control-border: #6f7d87;
|
|
122
|
+
--text: #20242a; --muted: #4f5862; --faint: #667079; --code: #343c46;
|
|
123
|
+
--accent: #0072b2; --accent-ink: #ffffff; --accent-bg: #e8f3fc; --hover-bg: #e8e8dd;
|
|
124
|
+
--success: #00795b; --warning: #8a6300; --error: #a3537f;
|
|
125
|
+
}
|
|
126
|
+
* { box-sizing: border-box; }
|
|
127
|
+
body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 15% -10%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 34%), var(--bg); color: var(--text); font: 14px/1.5 var(--sans); }
|
|
128
|
+
button, input, textarea, select { font: inherit; }
|
|
129
|
+
button { cursor: pointer; }
|
|
130
|
+
code, pre, textarea.code { font-family: var(--mono); }
|
|
131
|
+
.shell { min-height: 100vh; display: grid; grid-template-rows: auto auto 1fr; }
|
|
132
|
+
header { border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--chrome) 94%, transparent); backdrop-filter: blur(14px); }
|
|
133
|
+
.header-row { max-width: 1440px; margin: 0 auto; min-height: 64px; padding: 0 24px; display: flex; align-items: center; gap: 20px; }
|
|
134
|
+
.brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
|
|
135
|
+
.brand-mark { width: 30px; height: 30px; display: grid; place-items: center; border: 1px solid var(--border-strong); border-radius: 9px; background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 28%, var(--panel)), var(--sunken)); color: var(--accent); font: 700 17px/1 var(--mono); box-shadow: inset 0 1px 0 color-mix(in srgb, white 10%, transparent), 0 8px 24px color-mix(in srgb, black 24%, transparent); }
|
|
136
|
+
.brand-copy { display: grid; line-height: 1.05; }
|
|
137
|
+
.brand strong { font: 700 16px var(--sans); letter-spacing: .015em; }
|
|
138
|
+
.brand span { color: var(--muted); font: 10px var(--mono); letter-spacing: .12em; text-transform: uppercase; }
|
|
139
|
+
.workspace { color: var(--muted); font: 12px var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
140
|
+
.status { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); font: 12px var(--mono); }
|
|
141
|
+
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
|
|
142
|
+
.status.verified::before { background: var(--success); }
|
|
143
|
+
.status.blocked::before, .status.failed::before { background: var(--error); }
|
|
144
|
+
.status.verifying::before, .status.implementing::before { background: var(--warning); }
|
|
145
|
+
.hash { color: var(--faint); font: 12px var(--mono); }
|
|
146
|
+
.update-badge { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--accent); border-radius: 5px; background: var(--accent-bg); color: var(--accent); padding: 5px 10px; font: 12px var(--mono); }
|
|
147
|
+
.update-badge:hover { border-color: var(--border-hover); }
|
|
148
|
+
.update-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
149
|
+
nav { border-bottom: 1px solid var(--border); background: var(--chrome); }
|
|
150
|
+
.tabs { max-width: 1440px; margin: 0 auto; padding: 0 24px; display: flex; gap: 22px; overflow-x: auto; }
|
|
151
|
+
.tab { border: 0; border-bottom: 2px solid transparent; background: none; color: var(--muted); padding: 11px 0 10px; font-size: 13px; }
|
|
152
|
+
.tab:hover { color: var(--text); }
|
|
153
|
+
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
|
|
154
|
+
main { width: 100%; max-width: 1440px; margin: 0 auto; padding: 24px; }
|
|
155
|
+
.panel { display: none; }
|
|
156
|
+
.panel.active { display: block; }
|
|
157
|
+
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; }
|
|
158
|
+
.span-4 { grid-column: span 4; }
|
|
159
|
+
.span-5 { grid-column: span 5; }
|
|
160
|
+
.span-7 { grid-column: span 7; }
|
|
161
|
+
.span-8 { grid-column: span 8; }
|
|
162
|
+
.span-12 { grid-column: span 12; }
|
|
163
|
+
.card { border: 1px solid var(--border); background: linear-gradient(155deg, color-mix(in srgb, var(--panel) 98%, var(--accent) 2%), var(--panel)); border-radius: 10px; padding: 18px; min-width: 0; box-shadow: 0 14px 40px color-mix(in srgb, black 12%, transparent); }
|
|
164
|
+
.card h2, .card h3 { margin: 0; font-weight: 600; }
|
|
165
|
+
.card h2 { font-size: 14px; }
|
|
166
|
+
.card h3 { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .07em; }
|
|
167
|
+
.subtle { color: var(--muted); font-size: 12px; }
|
|
168
|
+
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
|
|
169
|
+
.metric { font: 22px var(--mono); margin-top: 8px; }
|
|
170
|
+
.metric small { color: var(--muted); font-size: 12px; }
|
|
171
|
+
.list { margin: 12px 0 0; padding: 0; list-style: none; }
|
|
172
|
+
.list li { padding: 9px 0; border-top: 1px solid var(--border); display: flex; gap: 10px; }
|
|
173
|
+
.index { color: var(--faint); min-width: 24px; font: 12px var(--mono); }
|
|
174
|
+
.marker { display: inline-block; min-width: 15px; color: var(--faint); font: 12px var(--mono); }
|
|
175
|
+
li.step-done .marker { color: var(--success); }
|
|
176
|
+
li.step-failed .marker { color: var(--error); }
|
|
177
|
+
li.step-active .marker { color: var(--accent); font-weight: 700; animation: step-arrow 1.1s ease-in-out infinite; }
|
|
178
|
+
li.step-active .index { color: var(--accent); }
|
|
179
|
+
@keyframes step-arrow { 0%, 100% { transform: translateX(0); opacity: 1; } 50% { transform: translateX(4px); opacity: .45; } }
|
|
180
|
+
@media (prefers-reduced-motion: reduce) { li.step-active .marker { animation: none; } }
|
|
181
|
+
.acceptance li { display: block; }
|
|
182
|
+
.acceptance code { display: block; color: var(--faint); margin-top: 4px; font-size: 11px; }
|
|
183
|
+
.cov-ok { color: var(--success); font-weight: 700; margin-right: 6px; }
|
|
184
|
+
.cov-weak { color: var(--warning); font-weight: 700; margin-right: 6px; }
|
|
185
|
+
.cov-missing { color: var(--muted); font-weight: 700; margin-right: 6px; }
|
|
186
|
+
.button { border: 1px solid var(--border-strong); border-radius: 5px; background: var(--panel-2); color: var(--text); padding: 7px 11px; font-size: 12px; }
|
|
187
|
+
.button:hover { border-color: var(--border-hover); background: var(--hover-bg); }
|
|
188
|
+
.button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
|
|
189
|
+
.button.danger { color: var(--error); }
|
|
190
|
+
.button:disabled { opacity: .45; cursor: default; }
|
|
191
|
+
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
192
|
+
label { display: block; color: var(--muted); font-size: 12px; margin-bottom: 6px; }
|
|
193
|
+
input, textarea, select { width: 100%; border: 1px solid var(--control-border); border-radius: 5px; background: var(--sunken); color: var(--text); padding: 9px 10px; outline: none; }
|
|
194
|
+
input:focus, textarea:focus, select:focus { border-color: var(--accent); }
|
|
195
|
+
textarea { resize: vertical; min-height: 90px; }
|
|
196
|
+
textarea.code { min-height: 430px; font-size: 12px; line-height: 1.55; tab-size: 2; }
|
|
197
|
+
.field + .field { margin-top: 14px; }
|
|
198
|
+
.message { min-height: 20px; margin-top: 12px; color: var(--muted); font-size: 12px; white-space: pre-wrap; }
|
|
199
|
+
.message.error { color: var(--error); }
|
|
200
|
+
.message.success { color: var(--success); }
|
|
201
|
+
.mono-block { margin: 0; border: 1px solid var(--border); border-radius: 5px; background: var(--sunken); color: var(--code); padding: 14px; overflow: auto; max-height: 480px; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; }
|
|
202
|
+
.fingerprint { color: var(--muted); font: 12px var(--mono); overflow-wrap: anywhere; }
|
|
203
|
+
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 6px; }
|
|
204
|
+
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
205
|
+
th { color: var(--muted); font-weight: 500; text-align: left; background: var(--sunken); }
|
|
206
|
+
th, td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
207
|
+
tr:last-child td { border-bottom: 0; }
|
|
208
|
+
td code { color: var(--code); white-space: nowrap; }
|
|
209
|
+
.check-state { font-family: var(--mono); }
|
|
210
|
+
.check-state.passed { color: var(--success); }
|
|
211
|
+
.check-state.failed { color: var(--error); }
|
|
212
|
+
.check-state.stale { color: var(--warning); }
|
|
213
|
+
.empty { color: var(--muted); padding: 32px 0; text-align: center; }
|
|
214
|
+
.gate { border-left: 2px solid var(--border-strong); padding-left: 14px; }
|
|
215
|
+
.gate.ready { border-left-color: var(--success); }
|
|
216
|
+
.gate.blocked { border-left-color: var(--error); }
|
|
217
|
+
.doc-tabs { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
218
|
+
.doc-tab.active { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }
|
|
219
|
+
.theme-picker { position: relative; }
|
|
220
|
+
.theme-trigger { min-width: 164px; padding: 6px 10px; border: 1px solid var(--control-border); border-radius: 5px; background: var(--sunken); color: var(--text); text-align: left; font-size: 12px; }
|
|
221
|
+
.theme-trigger::after { content: "▾"; float: right; margin-left: 12px; color: var(--muted); }
|
|
222
|
+
.theme-menu { position: absolute; z-index: 20; top: calc(100% + 6px); right: 0; display: none; width: 230px; max-height: min(70vh, 430px); padding: 6px; overflow-y: auto; border: 1px solid var(--border-strong); border-radius: 7px; background: var(--panel); box-shadow: 0 16px 42px color-mix(in srgb, black 38%, transparent); }
|
|
223
|
+
.theme-picker.open .theme-menu { display: grid; gap: 2px; }
|
|
224
|
+
.theme-group { padding: 7px 8px 3px; color: var(--faint); font: 10px var(--mono); letter-spacing: .11em; text-transform: uppercase; }
|
|
225
|
+
.theme-option { width: 100%; padding: 7px 9px; border: 1px solid transparent; border-radius: 4px; background: transparent; color: var(--text); text-align: left; font-size: 12px; }
|
|
226
|
+
.theme-option:hover, .theme-option:focus-visible { border-color: var(--border-hover); background: var(--hover-bg); color: var(--accent); outline: none; }
|
|
227
|
+
.theme-option[aria-selected="true"]::after { content: "✓"; float: right; color: var(--accent); }
|
|
228
|
+
.button:focus-visible, .tab:focus-visible, .theme-trigger:focus-visible,
|
|
229
|
+
input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
230
|
+
/* D2 flowchart (Plan tab): visual graph with a current-step highlight,
|
|
231
|
+
a swirling ring around the active node, and a slow pulse on the arrow
|
|
232
|
+
that leads into it. States mirror the console's computeNodeStates. */
|
|
233
|
+
.d2-chart { min-height: 60px; }
|
|
234
|
+
.d2-current { font: 600 12px var(--mono); color: var(--accent); margin: 10px 0 2px; min-height: 16px; }
|
|
235
|
+
.d2-current.idle { color: var(--muted); font-weight: 400; }
|
|
236
|
+
.d2-svg { width: 100%; height: auto; display: block; overflow: visible; }
|
|
237
|
+
.d2-node { stroke-width: 1.5; }
|
|
238
|
+
.d2-done { stroke: var(--success); fill: var(--success); fill-opacity: .10; }
|
|
239
|
+
.d2-active { stroke: var(--accent); fill: var(--accent); fill-opacity: .12; stroke-width: 2; }
|
|
240
|
+
.d2-fail { stroke: var(--error); fill: var(--error); fill-opacity: .12; }
|
|
241
|
+
.d2-blocked { stroke: var(--warning); fill: var(--warning); fill-opacity: .10; }
|
|
242
|
+
.d2-pending { stroke: var(--border); fill: var(--panel); }
|
|
243
|
+
.d2-label { font: 600 12px var(--mono); fill: var(--text); }
|
|
244
|
+
.d2-label-pending { fill: var(--muted); }
|
|
245
|
+
.d2-label-fail { fill: var(--error); }
|
|
246
|
+
.d2-label-blocked { fill: var(--warning); }
|
|
247
|
+
.d2-dot { fill: var(--border); }
|
|
248
|
+
.d2-dot-done { fill: var(--success); }
|
|
249
|
+
.d2-dot-active { fill: var(--accent); }
|
|
250
|
+
.d2-dot-fail { fill: var(--error); }
|
|
251
|
+
.d2-dot-blocked { fill: var(--warning); }
|
|
252
|
+
.d2-edge-label { font: 11px var(--mono); fill: var(--warning); }
|
|
253
|
+
.d2-swirl { fill: none; stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 9; transform-box: fill-box; transform-origin: center; animation: d2-swirl 2.6s linear infinite; }
|
|
254
|
+
.d2-edge-pulse { animation: d2-edge-pulse 2.8s ease-in-out infinite; }
|
|
255
|
+
.d2-arrow-pulse { animation: d2-arrow-pulse 2.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
256
|
+
@keyframes d2-swirl { to { transform: rotate(360deg); } }
|
|
257
|
+
@keyframes d2-edge-pulse { 0%, 100% { opacity: .35; stroke-width: 1.4; } 50% { opacity: 1; stroke-width: 2.6; } }
|
|
258
|
+
@keyframes d2-arrow-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
|
|
259
|
+
.d2-source { margin-top: 12px; }
|
|
260
|
+
.d2-source summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
261
|
+
.d2-source pre { margin-top: 8px; max-height: 220px; }
|
|
262
|
+
@media (prefers-reduced-motion: reduce) { .d2-swirl, .d2-edge-pulse, .d2-arrow-pulse { animation: none; } }
|
|
263
|
+
@media (max-width: 900px) {
|
|
264
|
+
.header-row { flex-wrap: wrap; padding: 12px 16px; gap: 10px 16px; }
|
|
265
|
+
.workspace { order: 3; flex-basis: 100%; }
|
|
266
|
+
.tabs, main { padding-left: 16px; padding-right: 16px; }
|
|
267
|
+
.span-4, .span-5, .span-7, .span-8 { grid-column: span 12; }
|
|
268
|
+
}
|
|
269
|
+
</style>
|
|
270
|
+
</head>
|
|
271
|
+
<body>
|
|
272
|
+
<div class="shell">
|
|
273
|
+
<header>
|
|
274
|
+
<div class="header-row">
|
|
275
|
+
<div class="brand"><span class="brand-mark" aria-hidden="true">𐤀</span><span class="brand-copy"><strong>Carthagent</strong><span>plan · prove · deliver</span></span></div>
|
|
276
|
+
<div id="workspace" class="workspace">loading workspace</div>
|
|
277
|
+
<div id="status" class="status">idle</div>
|
|
278
|
+
<div id="header-hash" class="hash">—</div>
|
|
279
|
+
<button id="update-badge" class="update-badge" type="button" hidden></button>
|
|
280
|
+
<div id="theme-picker" class="theme-picker">
|
|
281
|
+
<button id="theme-trigger" class="theme-trigger" type="button" aria-haspopup="listbox" aria-expanded="false">Carthage (default)</button>
|
|
282
|
+
<div id="theme-menu" class="theme-menu" role="listbox" aria-label="Color theme">
|
|
283
|
+
<button class="theme-option" type="button" role="option" data-theme-value="carthage">Carthage (default)</button>
|
|
284
|
+
<button class="theme-option" type="button" role="option" data-theme-value="system">System (auto)</button>
|
|
285
|
+
<div class="theme-group">Dark</div>
|
|
286
|
+
<button class="theme-option" type="button" role="option" data-theme-value="obsidian">Obsidian</button>
|
|
287
|
+
<button class="theme-option" type="button" role="option" data-theme-value="midnight">Midnight</button>
|
|
288
|
+
<button class="theme-option" type="button" role="option" data-theme-value="solarized-dark">Solarized Dark</button>
|
|
289
|
+
<button class="theme-option" type="button" role="option" data-theme-value="nord">Nord</button>
|
|
290
|
+
<button class="theme-option" type="button" role="option" data-theme-value="contrast-dark">High Contrast Dark</button>
|
|
291
|
+
<button class="theme-option" type="button" role="option" data-theme-value="okabe-dark">Okabe Dark (colorblind)</button>
|
|
292
|
+
<div class="theme-group">Light</div>
|
|
293
|
+
<button class="theme-option" type="button" role="option" data-theme-value="paper">Paper</button>
|
|
294
|
+
<button class="theme-option" type="button" role="option" data-theme-value="daylight">Daylight</button>
|
|
295
|
+
<button class="theme-option" type="button" role="option" data-theme-value="solarized-light">Solarized Light</button>
|
|
296
|
+
<button class="theme-option" type="button" role="option" data-theme-value="contrast-light">High Contrast Light</button>
|
|
297
|
+
<button class="theme-option" type="button" role="option" data-theme-value="okabe-light">Okabe Light (colorblind)</button>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
<button id="run-all-header" class="button primary">Run checks</button>
|
|
301
|
+
</div>
|
|
302
|
+
</header>
|
|
303
|
+
<nav>
|
|
304
|
+
<div class="tabs">
|
|
305
|
+
<button class="tab active" data-panel="overview">Overview</button>
|
|
306
|
+
<button class="tab" data-panel="plan">Plan</button>
|
|
307
|
+
<button class="tab" data-panel="checks">Checks</button>
|
|
308
|
+
<button class="tab" data-panel="handoff">Handoff</button>
|
|
309
|
+
<button class="tab" data-panel="docs">Docs</button>
|
|
310
|
+
</div>
|
|
311
|
+
</nav>
|
|
312
|
+
<main>
|
|
313
|
+
<section id="overview" class="panel active">
|
|
314
|
+
<div class="grid">
|
|
315
|
+
<div class="card span-8">
|
|
316
|
+
<div class="section-head"><h2 id="goal">No active delivery</h2><span id="report" class="subtle"></span></div>
|
|
317
|
+
<div id="overview-empty" class="empty">Start a delivery in Carthagent or define a plan in the Plan tab.</div>
|
|
318
|
+
<div id="overview-content" hidden>
|
|
319
|
+
<h3>Steps</h3>
|
|
320
|
+
<ol id="steps" class="list"></ol>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="card span-4">
|
|
324
|
+
<h3>Checks</h3>
|
|
325
|
+
<div id="check-metric" class="metric">0 <small>of 0 current</small></div>
|
|
326
|
+
<p id="pending-summary" class="subtle">No checks declared.</p>
|
|
327
|
+
<h3 style="margin-top:20px">Fingerprint</h3>
|
|
328
|
+
<p id="fingerprint" class="fingerprint">—</p>
|
|
329
|
+
</div>
|
|
330
|
+
<div id="acceptance-card" class="card span-12" hidden>
|
|
331
|
+
<h3>Acceptance</h3>
|
|
332
|
+
<ul id="acceptance" class="list acceptance"></ul>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</section>
|
|
336
|
+
|
|
337
|
+
<section id="plan" class="panel">
|
|
338
|
+
<div class="grid">
|
|
339
|
+
<div class="card span-7">
|
|
340
|
+
<div class="section-head">
|
|
341
|
+
<div><h2>Plan</h2><div class="subtle">Revisions relock implementation; honest matching evidence is retained.</div></div>
|
|
342
|
+
<div class="actions"><button id="validate-plan" class="button">Validate</button><button id="save-plan" class="button primary">Save plan</button></div>
|
|
343
|
+
</div>
|
|
344
|
+
<textarea id="plan-json" class="code" spellcheck="false" placeholder="No plan loaded"></textarea>
|
|
345
|
+
<div id="plan-message" class="message"></div>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="card span-5">
|
|
348
|
+
<h3>Observed workflow</h3>
|
|
349
|
+
<div id="design-gate" class="gate" role="status"></div>
|
|
350
|
+
<label for="planning-view">View</label>
|
|
351
|
+
<select id="planning-view"><option value="workflow">Workflow</option><option value="architecture">Architecture</option><option value="evidence">Evidence</option></select>
|
|
352
|
+
<pre id="planning-detail" class="mono-block" style="white-space:pre-wrap"></pre>
|
|
353
|
+
<div id="d2-current" class="d2-current" hidden></div>
|
|
354
|
+
<div id="d2" class="d2-chart" style="margin-top:12px">No plan loaded.</div>
|
|
355
|
+
<details class="d2-source"><summary>D2 source</summary><pre id="d2-source-pre" class="mono-block"></pre></details>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</section>
|
|
359
|
+
|
|
360
|
+
<section id="checks" class="panel">
|
|
361
|
+
<div class="card">
|
|
362
|
+
<div class="section-head">
|
|
363
|
+
<div><h2>Checks</h2><div class="subtle">Commands run in the selected workspace with their declared timeout.</div></div>
|
|
364
|
+
<button id="run-all" class="button primary">Run all</button>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="table-wrap">
|
|
367
|
+
<table>
|
|
368
|
+
<thead><tr><th>ID</th><th>Kind</th><th>Command</th><th>Timeout</th><th>Status</th><th>Duration</th><th></th></tr></thead>
|
|
369
|
+
<tbody id="checks-body"><tr><td colspan="7" class="empty">No checks declared.</td></tr></tbody>
|
|
370
|
+
</table>
|
|
371
|
+
</div>
|
|
372
|
+
<h3 style="margin-top:20px">Output</h3>
|
|
373
|
+
<pre id="check-output" class="mono-block" style="margin-top:10px">No check selected.</pre>
|
|
374
|
+
</div>
|
|
375
|
+
</section>
|
|
376
|
+
|
|
377
|
+
<section id="handoff" class="panel">
|
|
378
|
+
<div class="grid">
|
|
379
|
+
<div class="card span-7">
|
|
380
|
+
<h2>Handoff</h2>
|
|
381
|
+
<div class="field" style="margin-top:16px"><label for="review">Review</label><textarea id="review"></textarea></div>
|
|
382
|
+
<div class="field"><label for="launch">Launch command</label><input id="launch" class="code"></div>
|
|
383
|
+
<div class="field"><label for="limitations">Limitations, one per line</label><textarea id="limitations"></textarea></div>
|
|
384
|
+
<div class="actions" style="margin-top:16px"><button id="mark-verified" class="button primary">Mark verified</button><button id="mark-blocked" class="button danger">Mark blocked</button></div>
|
|
385
|
+
<div id="handoff-message" class="message"></div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="card span-5">
|
|
388
|
+
<h3>Verification</h3>
|
|
389
|
+
<div id="gate" class="gate" style="margin-top:14px">No active plan.</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</section>
|
|
393
|
+
|
|
394
|
+
<section id="docs" class="panel">
|
|
395
|
+
<div class="card">
|
|
396
|
+
<div class="section-head"><h2>Documentation</h2></div>
|
|
397
|
+
<div class="doc-tabs">
|
|
398
|
+
<button class="button doc-tab active" data-doc="readme">README</button>
|
|
399
|
+
<button class="button doc-tab" data-doc="delivery">Delivery prompt</button>
|
|
400
|
+
<button class="button doc-tab" data-doc="software-delivery">Software delivery</button>
|
|
401
|
+
<button class="button doc-tab" data-doc="typescript-delivery">TypeScript delivery</button>
|
|
402
|
+
<button class="button doc-tab" data-doc="game-development">Game development</button>
|
|
403
|
+
<button class="button doc-tab" data-doc="evaluation">Evaluation</button>
|
|
404
|
+
</div>
|
|
405
|
+
<pre id="doc-content" class="mono-block">Select a document.</pre>
|
|
406
|
+
</div>
|
|
407
|
+
</section>
|
|
408
|
+
</main>
|
|
409
|
+
</div>
|
|
410
|
+
<script>
|
|
411
|
+
const query = new URLSearchParams(location.search);
|
|
412
|
+
const suppliedToken = query.get('token');
|
|
413
|
+
if (suppliedToken) {
|
|
414
|
+
sessionStorage.setItem('carthagent-token', suppliedToken);
|
|
415
|
+
history.replaceState(null, '', location.pathname);
|
|
416
|
+
}
|
|
417
|
+
const token = suppliedToken || sessionStorage.getItem('carthagent-token') || '';
|
|
418
|
+
let state = { status: 'idle', plan: null, evidence: {}, pendingChecks: [], currentFingerprint: '' };
|
|
419
|
+
|
|
420
|
+
const byId = id => document.getElementById(id);
|
|
421
|
+
const node = (tag, text, className) => {
|
|
422
|
+
const element = document.createElement(tag);
|
|
423
|
+
if (text != null) element.textContent = text;
|
|
424
|
+
if (className) element.className = className;
|
|
425
|
+
return element;
|
|
426
|
+
};
|
|
427
|
+
const clear = element => { while (element.firstChild) element.removeChild(element.firstChild); };
|
|
428
|
+
const setMessage = (id, text, type = '') => {
|
|
429
|
+
const element = byId(id);
|
|
430
|
+
element.textContent = text;
|
|
431
|
+
element.className = `message ${type}`.trim();
|
|
432
|
+
};
|
|
433
|
+
async function api(path, options = {}) {
|
|
434
|
+
const response = await fetch(path, { ...options, headers: { 'content-type': 'application/json', 'x-carthagent-token': token, ...(options.headers || {}) } });
|
|
435
|
+
const data = await response.json().catch(() => ({}));
|
|
436
|
+
if (!response.ok) throw new Error(data.error || `Request failed (${response.status})`);
|
|
437
|
+
return data;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const themePicker = byId('theme-picker');
|
|
441
|
+
const themeTrigger = byId('theme-trigger');
|
|
442
|
+
const themeMenu = byId('theme-menu');
|
|
443
|
+
const themeOptions = [...themeMenu.querySelectorAll('[data-theme-value]')];
|
|
444
|
+
const prefersDark = () => typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;
|
|
445
|
+
const resolveDashboardTheme = value => value === 'system' ? (prefersDark() ? 'obsidian' : 'paper') : value;
|
|
446
|
+
const applyDashboardTheme = value => { document.documentElement.dataset.theme = resolveDashboardTheme(value); };
|
|
447
|
+
let committedTheme = document.documentElement.dataset.themeChoice || 'carthage';
|
|
448
|
+
const syncThemePicker = () => {
|
|
449
|
+
for (const option of themeOptions) option.setAttribute('aria-selected', String(option.dataset.themeValue === committedTheme));
|
|
450
|
+
themeTrigger.textContent = themeOptions.find(option => option.dataset.themeValue === committedTheme)?.textContent || 'Carthage (default)';
|
|
451
|
+
};
|
|
452
|
+
const closeThemePicker = (restore = true) => {
|
|
453
|
+
themePicker.classList.remove('open');
|
|
454
|
+
themeTrigger.setAttribute('aria-expanded', 'false');
|
|
455
|
+
if (restore) applyDashboardTheme(committedTheme);
|
|
456
|
+
};
|
|
457
|
+
syncThemePicker();
|
|
458
|
+
themeTrigger.addEventListener('click', () => {
|
|
459
|
+
const open = !themePicker.classList.contains('open');
|
|
460
|
+
themePicker.classList.toggle('open', open);
|
|
461
|
+
themeTrigger.setAttribute('aria-expanded', String(open));
|
|
462
|
+
if (open) themeOptions.find(option => option.dataset.themeValue === committedTheme)?.focus();
|
|
463
|
+
});
|
|
464
|
+
for (const option of themeOptions) {
|
|
465
|
+
const preview = () => applyDashboardTheme(option.dataset.themeValue);
|
|
466
|
+
option.addEventListener('mouseenter', preview);
|
|
467
|
+
option.addEventListener('focus', preview);
|
|
468
|
+
option.addEventListener('click', () => {
|
|
469
|
+
committedTheme = option.dataset.themeValue;
|
|
470
|
+
localStorage.setItem('carthagent-theme', committedTheme);
|
|
471
|
+
document.documentElement.dataset.themeChoice = committedTheme;
|
|
472
|
+
syncThemePicker();
|
|
473
|
+
closeThemePicker(false);
|
|
474
|
+
applyDashboardTheme(committedTheme);
|
|
475
|
+
themeTrigger.focus();
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
themeMenu.addEventListener('mouseleave', () => applyDashboardTheme(committedTheme));
|
|
479
|
+
themePicker.addEventListener('keydown', event => {
|
|
480
|
+
if (event.key === 'Escape') { closeThemePicker(); themeTrigger.focus(); }
|
|
481
|
+
});
|
|
482
|
+
document.addEventListener('click', event => { if (!themePicker.contains(event.target)) closeThemePicker(); });
|
|
483
|
+
if (typeof matchMedia === 'function') matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
484
|
+
if (committedTheme === 'system') document.documentElement.dataset.theme = event.matches ? 'obsidian' : 'paper';
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
document.querySelectorAll('.tab').forEach(tab => tab.addEventListener('click', () => {
|
|
488
|
+
document.querySelectorAll('.tab').forEach(item => item.classList.toggle('active', item === tab));
|
|
489
|
+
document.querySelectorAll('.panel').forEach(panel => panel.classList.toggle('active', panel.id === tab.dataset.panel));
|
|
490
|
+
if (tab.dataset.panel === 'docs') loadDoc(document.querySelector('.doc-tab.active').dataset.doc);
|
|
491
|
+
}));
|
|
492
|
+
|
|
493
|
+
function checkStatus(check) {
|
|
494
|
+
const evidence = state.evidence?.[check.id];
|
|
495
|
+
if (!evidence) return 'pending';
|
|
496
|
+
if (!evidence.passed) return 'failed';
|
|
497
|
+
return evidence.fingerprint === state.currentFingerprint ? 'passed' : 'stale';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function render() {
|
|
501
|
+
byId('workspace').textContent = state.workspace || 'workspace unavailable';
|
|
502
|
+
byId('status').textContent = state.status || 'idle';
|
|
503
|
+
byId('status').className = `status ${state.status || 'idle'}`;
|
|
504
|
+
byId('header-hash').textContent = state.currentFingerprint ? state.currentFingerprint.slice(0, 12) : '—';
|
|
505
|
+
const updateBadge = byId('update-badge');
|
|
506
|
+
if (state.update?.available) {
|
|
507
|
+
updateBadge.hidden = false;
|
|
508
|
+
if (updateBadge.dataset.copied !== 'true') updateBadge.textContent = `↑ ${state.update.latest} available`;
|
|
509
|
+
updateBadge.title = `Carthagent ${state.update.latest} is available (installed ${state.update.installed}) — click to copy 'ctg update'`;
|
|
510
|
+
} else {
|
|
511
|
+
updateBadge.hidden = true;
|
|
512
|
+
}
|
|
513
|
+
byId('fingerprint').textContent = state.currentFingerprint || '—';
|
|
514
|
+
byId('report').textContent = state.report || '';
|
|
515
|
+
const plan = state.plan;
|
|
516
|
+
byId('run-all').disabled = !plan?.checks?.length || state.planningStatus?.locked;
|
|
517
|
+
byId('run-all-header').disabled = !plan?.checks?.length || state.planningStatus?.locked;
|
|
518
|
+
renderPlanningView();
|
|
519
|
+
byId('mark-verified').disabled = !plan;
|
|
520
|
+
byId('mark-blocked').disabled = !plan;
|
|
521
|
+
byId('goal').textContent = plan?.goal || 'No active delivery';
|
|
522
|
+
byId('overview-empty').hidden = !!plan;
|
|
523
|
+
byId('overview-content').hidden = !plan;
|
|
524
|
+
byId('acceptance-card').hidden = !plan;
|
|
525
|
+
if (plan) {
|
|
526
|
+
clear(byId('steps'));
|
|
527
|
+
// Mirror the console's step semantics via resolveStepStates: exactly one
|
|
528
|
+
// current step, so a fresh plan no longer lights every row as active.
|
|
529
|
+
const resolvedSteps = resolveStepStates(plan, state.stepStatus, state.status);
|
|
530
|
+
plan.steps.forEach((step, index) => {
|
|
531
|
+
const resolved = resolvedSteps[index];
|
|
532
|
+
const item = node('li');
|
|
533
|
+
item.className = `step step-${resolved}`;
|
|
534
|
+
const glyph = resolved === 'done' ? '✓' : resolved === 'failed' ? '✗' : resolved === 'active' ? '▸' : '·';
|
|
535
|
+
item.append(node('span', glyph, 'marker'), node('span', String(index + 1).padStart(2, '0'), 'index'), node('span', typeof step === 'string' ? step : step.title));
|
|
536
|
+
byId('steps').append(item);
|
|
537
|
+
});
|
|
538
|
+
clear(byId('acceptance'));
|
|
539
|
+
const cov = acceptanceCoverage(plan, state.evidence || {}, state.currentFingerprint);
|
|
540
|
+
plan.acceptance.forEach((item, i) => {
|
|
541
|
+
const c = cov[i];
|
|
542
|
+
const cls = c.covered && !c.staticOnly ? 'cov-ok' : c.covered ? 'cov-weak' : 'cov-missing';
|
|
543
|
+
const mark = c.covered ? (c.staticOnly ? '~' : '✓') : '○';
|
|
544
|
+
const row = node('li');
|
|
545
|
+
const tag = node('span', mark);
|
|
546
|
+
tag.className = cls;
|
|
547
|
+
if (c.staticOnly) tag.title = 'Only static (syntax/import) checks back this criterion';
|
|
548
|
+
if (!c.covered) tag.title = 'No fresh passing check backs this criterion';
|
|
549
|
+
row.append(tag, node('span', item.requirement), node('code', item.checks.join(', ')));
|
|
550
|
+
byId('acceptance').append(row);
|
|
551
|
+
});
|
|
552
|
+
if (document.activeElement !== byId('plan-json')) byId('plan-json').value = JSON.stringify(plan, null, 2);
|
|
553
|
+
updateD2(plan);
|
|
554
|
+
if (document.activeElement !== byId('review')) byId('review').value = state.review || '';
|
|
555
|
+
if (document.activeElement !== byId('launch')) byId('launch').value = state.launch || '';
|
|
556
|
+
if (document.activeElement !== byId('limitations')) byId('limitations').value = (state.limitations || []).join('\n');
|
|
557
|
+
}
|
|
558
|
+
const checks = plan?.checks || [];
|
|
559
|
+
const current = checks.filter(check => checkStatus(check) === 'passed').length;
|
|
560
|
+
byId('check-metric').textContent = `${current} of ${checks.length} current`;
|
|
561
|
+
byId('pending-summary').textContent = state.pendingChecks?.length ? `Pending: ${state.pendingChecks.join(', ')}` : checks.length ? 'All checks are current.' : 'No checks declared.';
|
|
562
|
+
renderChecks(checks);
|
|
563
|
+
const gate = byId('gate');
|
|
564
|
+
gate.className = `gate ${state.status === 'blocked' ? 'blocked' : checks.length && current === checks.length ? 'ready' : ''}`;
|
|
565
|
+
gate.textContent = !plan ? 'No active plan.' : checks.length && current === checks.length ? 'All declared checks have current passing evidence.' : `${checks.length - current} check${checks.length - current === 1 ? '' : 's'} pending, failed, or stale.`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function renderPlanningView() {
|
|
569
|
+
const view = state.planningView;
|
|
570
|
+
const tab = byId('planning-view').value;
|
|
571
|
+
byId('design-gate').textContent = `${view?.gate.phase || 'discovery'} · revision ${state.revision || '-'}\n${view?.gate.locked ? 'LOCKED: validate, review and approve this design revision before code.' : 'Design approved; final correctness still requires evidence.'}`;
|
|
572
|
+
const lines = [];
|
|
573
|
+
if (tab === 'architecture') {
|
|
574
|
+
for (const c of view?.architecture?.nodes || []) lines.push(`${c.id} [${c.layer}] ${c.title}`, ` excludes: ${(c.excludes || []).join('; ')}`);
|
|
575
|
+
for (const edge of view?.architecture?.edges || []) lines.push(`${edge.from} -> ${edge.to}`);
|
|
576
|
+
for (const p of view?.architecture?.ports || []) lines.push(`${p.owner} -> port ${p.id} -> ${p.adapter}; injected at ${p.compositionRoot}`, ` input: ${p.input}; output: ${p.output}; errors: ${p.errors}`, ` real adapter assertion: ${p.integrationAssertion}`);
|
|
577
|
+
} else if (tab === 'evidence') {
|
|
578
|
+
for (const e of view?.evidence || []) lines.push(`${e.id}: ${e.status}; executed revision ${e.executedRevision ?? '-'}`, ` ${e.logPath || 'No execution receipt'}`);
|
|
579
|
+
} else {
|
|
580
|
+
lines.push('CURRENT: ' + ((view?.current || []).map(n => n.title).join(', ') || 'No active action recorded'));
|
|
581
|
+
for (const n of view?.workflow?.nodes || []) lines.push(`${n.id} [${n.status}] ${n.title}`);
|
|
582
|
+
for (const e of view?.workflow?.edges || []) lines.push(`${e.from} -> ${e.to}${e.label ? ': ' + e.label : ''}`);
|
|
583
|
+
}
|
|
584
|
+
if (view?.blockers.length) lines.push('BLOCKERS', ...view.blockers);
|
|
585
|
+
lines.push(view?.provenance || 'No observed delivery state');
|
|
586
|
+
byId('planning-detail').textContent = lines.join('\n');
|
|
587
|
+
byId('d2').hidden = tab !== 'workflow';
|
|
588
|
+
byId('d2-current').hidden = tab !== 'workflow';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function renderChecks(checks) {
|
|
592
|
+
const body = byId('checks-body');
|
|
593
|
+
clear(body);
|
|
594
|
+
if (!checks.length) {
|
|
595
|
+
const row = node('tr');
|
|
596
|
+
const cell = node('td', 'No checks declared.', 'empty');
|
|
597
|
+
cell.colSpan = 7;
|
|
598
|
+
row.append(cell);
|
|
599
|
+
body.append(row);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
checks.forEach(check => {
|
|
603
|
+
const evidence = state.evidence?.[check.id];
|
|
604
|
+
const status = checkStatus(check);
|
|
605
|
+
const row = node('tr');
|
|
606
|
+
const id = node('td'); id.append(node('code', check.id));
|
|
607
|
+
const kind = node('td', check.kind);
|
|
608
|
+
const command = node('td'); command.append(node('code', check.argv.join(' ')));
|
|
609
|
+
const timeout = node('td', `${check.timeoutSeconds}s`);
|
|
610
|
+
const stateCell = node('td', status, `check-state ${status}`);
|
|
611
|
+
const duration = node('td', evidence?.durationMs != null ? `${evidence.durationMs}ms` : '—');
|
|
612
|
+
const action = node('td');
|
|
613
|
+
const run = node('button', 'Run', 'button');
|
|
614
|
+
run.disabled = Boolean(state.planningStatus?.locked);
|
|
615
|
+
run.addEventListener('click', () => runChecks(check.id));
|
|
616
|
+
action.append(run);
|
|
617
|
+
row.append(id, kind, command, timeout, stateCell, duration, action);
|
|
618
|
+
body.append(row);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function loadStatus() {
|
|
623
|
+
if (!token) {
|
|
624
|
+
byId('workspace').textContent = 'Open the authenticated URL printed by ctg serve.';
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
try { state = await api('/api/status'); render(); }
|
|
628
|
+
catch (error) { byId('workspace').textContent = error.message; }
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// --- D2 flowchart (visual graph with a current-step highlight) ---
|
|
632
|
+
// The Plan tab used to dump raw D2 source into a <pre>. It now renders an
|
|
633
|
+
// SVG flowchart that mirrors the console's computeNodeStates so the active
|
|
634
|
+
// step is obvious: the active box wears a swirling ring and the arrow into
|
|
635
|
+
// it pulses slowly. The raw source stays available under a <details>.
|
|
636
|
+
const SVGNS = 'http' + '://www.w3.org/2000/svg';
|
|
637
|
+
const svgEl = tag => document.createElementNS(SVGNS, tag);
|
|
638
|
+
|
|
639
|
+
/** Parse planD2-style source into { nodes, edges } (mirrors lib/tui/d2.mjs). */
|
|
640
|
+
function parseD2Source(src) {
|
|
641
|
+
const nodes = new Map(), edges = [];
|
|
642
|
+
const ensure = id => { if (!nodes.has(id)) nodes.set(id, { id, label: id }); return nodes.get(id); };
|
|
643
|
+
for (const raw of String(src).split('\n')) {
|
|
644
|
+
const line = raw.trim();
|
|
645
|
+
if (!line || line.startsWith('#') || line.startsWith('direction:')) continue;
|
|
646
|
+
const edge = line.match(/^([\w.-]+)\s*-+>\s*([\w.-]+)\s*(?::\s*(.*))?$/);
|
|
647
|
+
if (edge) { ensure(edge[1]); ensure(edge[2]); edges.push({ from: edge[1], to: edge[2], label: (edge[3] || '').replace(/^"|"$/g, '') }); continue; }
|
|
648
|
+
const decl = line.match(/^([\w.-]+)\s*:\s*(.+)$/);
|
|
649
|
+
if (decl && !decl[2].startsWith('{')) {
|
|
650
|
+
const n = ensure(decl[1]); let label = decl[2].trim();
|
|
651
|
+
try { if (label.startsWith('"')) label = JSON.parse(label); } catch { label = label.replace(/^"|"$/g, ''); }
|
|
652
|
+
n.label = label;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return { nodes: [...nodes.values()], edges };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Longest-path ranks; back edges (cycles, e.g. repair -> verify) go to a side column. */
|
|
659
|
+
function layoutD2(graph) {
|
|
660
|
+
const ids = graph.nodes.map(n => n.id);
|
|
661
|
+
const back = new Set();
|
|
662
|
+
const adj = new Map(ids.map(id => [id, []]));
|
|
663
|
+
for (const e of graph.edges) adj.get(e.from)?.push(e);
|
|
664
|
+
const stack = new Set(), seen = new Set();
|
|
665
|
+
const dfs = id => {
|
|
666
|
+
if (stack.has(id) || seen.has(id)) return;
|
|
667
|
+
stack.add(id);
|
|
668
|
+
for (const e of adj.get(id) || []) { if (stack.has(e.to)) { back.add(e); continue; } dfs(e.to); }
|
|
669
|
+
stack.delete(id); seen.add(id);
|
|
670
|
+
};
|
|
671
|
+
const roots = ids.filter(id => !graph.edges.some(e => e.to === id));
|
|
672
|
+
(roots.length ? roots : ids.slice(0, 1)).forEach(dfs);
|
|
673
|
+
ids.forEach(dfs);
|
|
674
|
+
const rank = new Map(ids.map(id => [id, 0]));
|
|
675
|
+
for (let i = 0; i < ids.length; i++) {
|
|
676
|
+
let moved = false;
|
|
677
|
+
for (const e of graph.edges) {
|
|
678
|
+
if (back.has(e)) continue;
|
|
679
|
+
const want = rank.get(e.from) + 1;
|
|
680
|
+
if (rank.get(e.to) < want) { rank.set(e.to, want); moved = true; }
|
|
681
|
+
}
|
|
682
|
+
if (!moved) break;
|
|
683
|
+
}
|
|
684
|
+
const col = new Map(ids.map(id => [id, 0]));
|
|
685
|
+
for (const e of back) col.set(e.from, 1);
|
|
686
|
+
return { rank, col, back };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** Per-criterion evidence coverage (mirrors coverageReport in lib/delivery.mjs). */
|
|
690
|
+
function acceptanceCoverage(plan, evidence, hash) {
|
|
691
|
+
const byId = new Map((plan.checks || []).map(c => [c.id, c]));
|
|
692
|
+
return (plan.acceptance || []).map(item => {
|
|
693
|
+
let fresh = 0;
|
|
694
|
+
const kinds = new Set();
|
|
695
|
+
for (const id of item.checks || []) {
|
|
696
|
+
const e = evidence[id];
|
|
697
|
+
if (!e || !e.passed || state.pendingChecks?.includes(id)) continue;
|
|
698
|
+
if (hash && e.fingerprint !== hash) continue;
|
|
699
|
+
fresh++;
|
|
700
|
+
const c = byId.get(id);
|
|
701
|
+
if (c) kinds.add(c.kind);
|
|
702
|
+
}
|
|
703
|
+
return { covered: fresh > 0 && fresh === item.checks.length && !state.completionIssues?.unverifiedRequirements?.includes(item.requirement), staticOnly: fresh > 0 && [...kinds].every(k => k === 'static') };
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Resolve one state per plan step. Exactly one step is `active` while the
|
|
709
|
+
* run is in flight (the first un-done step, or the first explicitly active
|
|
710
|
+
* one); earlier steps read done and later steps pending. Mirrors
|
|
711
|
+
* computeNodeStates in lib/tui/app.mjs.
|
|
712
|
+
*/
|
|
713
|
+
function resolveStepStates(plan, stepStatus, status) {
|
|
714
|
+
const implActive = status === 'implementing' || status === 'planned';
|
|
715
|
+
const raw = (plan.steps || []).map((_, i) => stepStatus?.[`step${i}`]);
|
|
716
|
+
let current = raw.findIndex(s => s === 'active');
|
|
717
|
+
if (current < 0) current = raw.findIndex(s => s !== 'done' && s !== 'failed');
|
|
718
|
+
return raw.map((s, i) => {
|
|
719
|
+
if (typeof plan.steps[i] !== 'string') return s || 'pending';
|
|
720
|
+
if (s === 'done') return 'done';
|
|
721
|
+
if (s === 'failed') return 'failed';
|
|
722
|
+
if (!implActive) return 'done';
|
|
723
|
+
if (i === current) return 'active';
|
|
724
|
+
if (i < current) return 'done';
|
|
725
|
+
return 'pending';
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Map live delivery state to per-node states (mirrors computeNodeStates in the TUI). */
|
|
730
|
+
function computeD2States() {
|
|
731
|
+
const st = new Map();
|
|
732
|
+
if (state.plan?.workflow && state.planningView?.workflow) return new Map(state.planningView.workflow.nodes.map(n => [n.id, n.status]));
|
|
733
|
+
const plan = state.plan;
|
|
734
|
+
if (!plan) return st;
|
|
735
|
+
const ev = state.evidence || {};
|
|
736
|
+
const status = state.status || 'idle';
|
|
737
|
+
const checks = plan.checks || [];
|
|
738
|
+
const curHash = state.currentFingerprint;
|
|
739
|
+
let anyFail = false, allPass = checks.length > 0, allFresh = allPass;
|
|
740
|
+
for (const c of checks) {
|
|
741
|
+
const e = ev[c.id];
|
|
742
|
+
if (!e) { allPass = false; allFresh = false; }
|
|
743
|
+
else if (!e.passed) { anyFail = true; allPass = false; allFresh = false; }
|
|
744
|
+
else if (state.pendingChecks?.includes(c.id) || (curHash && e.fingerprint !== curHash)) allFresh = false;
|
|
745
|
+
}
|
|
746
|
+
st.set('goal', 'done');
|
|
747
|
+
resolveStepStates(plan, state.stepStatus, status).forEach((s, i) => {
|
|
748
|
+
st.set(`step${i}`, s === 'failed' ? 'fail' : s);
|
|
749
|
+
});
|
|
750
|
+
let verify = 'pending';
|
|
751
|
+
if (anyFail) verify = 'fail';
|
|
752
|
+
else if (status === 'verifying') verify = 'active';
|
|
753
|
+
else if (allFresh && status === 'verified') verify = 'done';
|
|
754
|
+
else if (allFresh) verify = 'done';
|
|
755
|
+
if (status === 'blocked') verify = anyFail ? 'fail' : 'blocked';
|
|
756
|
+
st.set('verify', verify);
|
|
757
|
+
st.set('repair', anyFail ? 'fail' : 'pending');
|
|
758
|
+
st.set('review', status === 'verified' ? 'done' : allFresh && !anyFail ? 'active' : 'pending');
|
|
759
|
+
st.set('deliver', status === 'verified' ? 'done' : status === 'blocked' ? 'blocked' : 'pending');
|
|
760
|
+
return st;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function d2EdgeColor(targetState, isBack) {
|
|
764
|
+
if (isBack) return 'var(--error)';
|
|
765
|
+
if (targetState === 'fail') return 'var(--error)';
|
|
766
|
+
if (targetState === 'active') return 'var(--accent)';
|
|
767
|
+
if (targetState === 'done') return 'var(--success)';
|
|
768
|
+
return 'var(--border-strong)';
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** Render D2 source as an SVG flowchart into #d2, highlighting the current step. */
|
|
772
|
+
function renderD2Chart(source, states) {
|
|
773
|
+
const container = byId('d2');
|
|
774
|
+
const cur = byId('d2-current');
|
|
775
|
+
const srcPre = byId('d2-source-pre');
|
|
776
|
+
clear(container);
|
|
777
|
+
if (srcPre) srcPre.textContent = source || '';
|
|
778
|
+
if (!source) {
|
|
779
|
+
container.textContent = 'No plan loaded.';
|
|
780
|
+
if (cur) { cur.hidden = true; cur.textContent = ''; }
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const graph = parseD2Source(source);
|
|
784
|
+
const { rank, col, back } = layoutD2(graph);
|
|
785
|
+
const st = states || new Map();
|
|
786
|
+
const spine = graph.nodes.filter(n => col.get(n.id) === 0).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
787
|
+
const side = graph.nodes.filter(n => col.get(n.id) === 1).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
788
|
+
const pad = 12, nodeW = 220, nodeH = 46, vGap = 30, sideGap = 84;
|
|
789
|
+
const pos = new Map();
|
|
790
|
+
spine.forEach((n, i) => pos.set(n.id, { x: pad, y: pad + i * (nodeH + vGap) }));
|
|
791
|
+
side.forEach(n => {
|
|
792
|
+
const inc = graph.edges.find(e => e.to === n.id && !back.has(e));
|
|
793
|
+
let row = spine.length;
|
|
794
|
+
if (inc) { const f = spine.findIndex(s => s.id === inc.from); if (f >= 0) row = f + 1; }
|
|
795
|
+
pos.set(n.id, { x: pad + nodeW + sideGap, y: pad + row * (nodeH + vGap) });
|
|
796
|
+
});
|
|
797
|
+
const W = Math.max(...[...pos.values()].map(p => p.x)) + nodeW + pad;
|
|
798
|
+
const H = Math.max(...[...pos.values()].map(p => p.y)) + nodeH + pad;
|
|
799
|
+
const svg = svgEl('svg');
|
|
800
|
+
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
|
|
801
|
+
svg.setAttribute('class', 'd2-svg');
|
|
802
|
+
svg.setAttribute('role', 'img');
|
|
803
|
+
const svgTitle = svgEl('title'); svgTitle.textContent = 'Delivery flow'; svg.append(svgTitle);
|
|
804
|
+
|
|
805
|
+
// Edges first (behind nodes), arrowheads last (above borders) so heads stay crisp.
|
|
806
|
+
const edgePaths = [], arrowheads = [], edgeLabels = [];
|
|
807
|
+
for (const e of graph.edges) {
|
|
808
|
+
const a = pos.get(e.from), b = pos.get(e.to);
|
|
809
|
+
if (!a || !b) continue;
|
|
810
|
+
const isBack = back.has(e);
|
|
811
|
+
const targetState = st.get(e.to) || 'pending';
|
|
812
|
+
const toActive = targetState === 'active';
|
|
813
|
+
const ec = d2EdgeColor(targetState, isBack);
|
|
814
|
+
let pathD, tip, dir, lx, ly, anchor = 'middle';
|
|
815
|
+
if (isBack) {
|
|
816
|
+
const sx = a.x + nodeW / 2, sy = a.y, ex = b.x + nodeW / 2, ey = b.y;
|
|
817
|
+
pathD = `M ${sx} ${sy} C ${sx} ${sy - 48} ${ex} ${ey - 48} ${ex} ${ey}`;
|
|
818
|
+
tip = [ex, ey]; dir = 'down'; lx = (sx + ex) / 2; ly = Math.min(sy, ey) - 14;
|
|
819
|
+
} else if (col.get(e.from) === 0 && col.get(e.to) === 0) {
|
|
820
|
+
const sx = a.x + nodeW / 2, sy = a.y + nodeH, ex = b.x + nodeW / 2, ey = b.y;
|
|
821
|
+
pathD = `M ${sx} ${sy} L ${ex} ${ey}`;
|
|
822
|
+
tip = [ex, ey]; dir = 'down'; lx = sx + 8; ly = sy + (ey - sy) / 2; anchor = 'start';
|
|
823
|
+
} else {
|
|
824
|
+
const sx = a.x + nodeW, sy = a.y + nodeH / 2, ex = b.x, ey = b.y + nodeH / 2;
|
|
825
|
+
pathD = `M ${sx} ${sy} L ${ex} ${ey}`;
|
|
826
|
+
tip = [ex, ey]; dir = 'right'; lx = sx + (ex - sx) / 2; ly = sy - 8;
|
|
827
|
+
}
|
|
828
|
+
const path = svgEl('path');
|
|
829
|
+
path.setAttribute('d', pathD);
|
|
830
|
+
path.setAttribute('fill', 'none');
|
|
831
|
+
path.setAttribute('stroke', ec);
|
|
832
|
+
path.setAttribute('stroke-width', '1.6');
|
|
833
|
+
if (isBack) path.setAttribute('stroke-dasharray', '5 4');
|
|
834
|
+
if (toActive) path.setAttribute('class', 'd2-edge-pulse');
|
|
835
|
+
edgePaths.push(path);
|
|
836
|
+
const ah = svgEl('polygon');
|
|
837
|
+
const [tx, ty] = tip;
|
|
838
|
+
ah.setAttribute('points', dir === 'down' ? `${tx},${ty} ${tx - 5},${ty - 8} ${tx + 5},${ty - 8}` : `${tx},${ty} ${tx - 8},${ty - 5} ${tx - 8},${ty + 5}`);
|
|
839
|
+
ah.setAttribute('fill', ec);
|
|
840
|
+
if (toActive) ah.setAttribute('class', 'd2-arrow-pulse');
|
|
841
|
+
arrowheads.push(ah);
|
|
842
|
+
if (e.label) {
|
|
843
|
+
const lt = svgEl('text');
|
|
844
|
+
lt.textContent = e.label;
|
|
845
|
+
lt.setAttribute('x', lx); lt.setAttribute('y', ly);
|
|
846
|
+
lt.setAttribute('class', 'd2-edge-label');
|
|
847
|
+
lt.setAttribute('text-anchor', anchor);
|
|
848
|
+
edgeLabels.push(lt);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
edgePaths.forEach(p => svg.append(p));
|
|
852
|
+
for (const n of graph.nodes) {
|
|
853
|
+
const p = pos.get(n.id);
|
|
854
|
+
const s = st.get(n.id) || 'pending';
|
|
855
|
+
if (s === 'active') {
|
|
856
|
+
const ring = svgEl('ellipse');
|
|
857
|
+
ring.setAttribute('cx', p.x + nodeW / 2);
|
|
858
|
+
ring.setAttribute('cy', p.y + nodeH / 2);
|
|
859
|
+
ring.setAttribute('rx', nodeW / 2 + 7);
|
|
860
|
+
ring.setAttribute('ry', nodeH / 2 + 5);
|
|
861
|
+
ring.setAttribute('class', 'd2-swirl');
|
|
862
|
+
svg.append(ring);
|
|
863
|
+
}
|
|
864
|
+
const rect = svgEl('rect');
|
|
865
|
+
rect.setAttribute('x', p.x); rect.setAttribute('y', p.y);
|
|
866
|
+
rect.setAttribute('width', nodeW); rect.setAttribute('height', nodeH);
|
|
867
|
+
rect.setAttribute('rx', 8);
|
|
868
|
+
rect.setAttribute('class', `d2-node d2-${s}`);
|
|
869
|
+
svg.append(rect);
|
|
870
|
+
const dot = svgEl('circle');
|
|
871
|
+
dot.setAttribute('cx', p.x + 16); dot.setAttribute('cy', p.y + nodeH / 2);
|
|
872
|
+
dot.setAttribute('r', 4);
|
|
873
|
+
dot.setAttribute('class', `d2-dot d2-dot-${s}`);
|
|
874
|
+
svg.append(dot);
|
|
875
|
+
const label = svgEl('text');
|
|
876
|
+
const full = String(n.label);
|
|
877
|
+
label.textContent = full.length > 30 ? full.slice(0, 29) + '…' : full;
|
|
878
|
+
label.setAttribute('x', p.x + 30); label.setAttribute('y', p.y + nodeH / 2);
|
|
879
|
+
label.setAttribute('class', `d2-label d2-label-${s}`);
|
|
880
|
+
label.setAttribute('dominant-baseline', 'middle');
|
|
881
|
+
const t = svgEl('title'); t.textContent = full; label.append(t);
|
|
882
|
+
svg.append(label);
|
|
883
|
+
}
|
|
884
|
+
arrowheads.forEach(a => svg.append(a));
|
|
885
|
+
edgeLabels.forEach(l => svg.append(l));
|
|
886
|
+
container.append(svg);
|
|
887
|
+
|
|
888
|
+
// Current-step indicator: the active node, else the next/blocked frontier, else complete.
|
|
889
|
+
if (cur) {
|
|
890
|
+
const active = graph.nodes.find(n => st.get(n.id) === 'active');
|
|
891
|
+
if (active) {
|
|
892
|
+
cur.hidden = false; cur.className = 'd2-current';
|
|
893
|
+
cur.textContent = `▶ Current: ${active.label}`;
|
|
894
|
+
} else {
|
|
895
|
+
const next = spine.find(n => (st.get(n.id) || 'pending') === 'pending') || spine.find(n => ['fail', 'blocked'].includes(st.get(n.id)));
|
|
896
|
+
if (next) {
|
|
897
|
+
const mark = st.get(next.id) === 'fail' ? '✗ Stuck at' : st.get(next.id) === 'blocked' ? '■ Blocked at' : 'Up next';
|
|
898
|
+
cur.hidden = false; cur.className = 'd2-current idle';
|
|
899
|
+
cur.textContent = `${mark}: ${next.label}`;
|
|
900
|
+
} else if (spine.length && spine.every(n => st.get(n.id) === 'done')) {
|
|
901
|
+
cur.hidden = false; cur.className = 'd2-current';
|
|
902
|
+
cur.textContent = '✓ Flow complete';
|
|
903
|
+
} else { cur.hidden = true; cur.textContent = ''; }
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async function updateD2(plan) {
|
|
909
|
+
try {
|
|
910
|
+
const result = await api('/api/plan/d2', { method: 'POST', body: JSON.stringify({ plan }) });
|
|
911
|
+
renderD2Chart(result.d2, computeD2States());
|
|
912
|
+
} catch (error) {
|
|
913
|
+
byId('d2').textContent = error.message;
|
|
914
|
+
const cur = byId('d2-current'); if (cur) { cur.hidden = true; cur.textContent = ''; }
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
async function planAction(save) {
|
|
919
|
+
try {
|
|
920
|
+
const plan = JSON.parse(byId('plan-json').value);
|
|
921
|
+
const path = save ? '/api/plan/set' : '/api/plan/validate';
|
|
922
|
+
const result = await api(path, { method: 'POST', body: JSON.stringify({ plan }) });
|
|
923
|
+
setMessage('plan-message', save ? `Saved: ${result.report}` : 'Plan is valid.', 'success');
|
|
924
|
+
if (save) await loadStatus(); else renderD2Chart(result.d2, null);
|
|
925
|
+
} catch (error) { setMessage('plan-message', error.message, 'error'); }
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async function runChecks(id) {
|
|
929
|
+
byId('check-output').textContent = `Running ${id}…`;
|
|
930
|
+
byId('run-all').disabled = true;
|
|
931
|
+
byId('run-all-header').disabled = true;
|
|
932
|
+
try {
|
|
933
|
+
const result = await api('/api/checks/run', { method: 'POST', body: JSON.stringify({ id }) });
|
|
934
|
+
byId('check-output').textContent = result.results.map(item => `${item.id} exit ${item.code}\n${item.output || ''}`).join('\n\n');
|
|
935
|
+
await loadStatus();
|
|
936
|
+
} catch (error) {
|
|
937
|
+
byId('check-output').textContent = error.message;
|
|
938
|
+
} finally {
|
|
939
|
+
byId('run-all').disabled = false;
|
|
940
|
+
byId('run-all-header').disabled = false;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async function finish(status) {
|
|
945
|
+
const limitations = byId('limitations').value.split('\n').map(value => value.trim()).filter(Boolean);
|
|
946
|
+
try {
|
|
947
|
+
await api('/api/finish', { method: 'POST', body: JSON.stringify({ status, review: byId('review').value, launch: byId('launch').value, limitations }) });
|
|
948
|
+
setMessage('handoff-message', `Status set to ${status}.`, 'success');
|
|
949
|
+
await loadStatus();
|
|
950
|
+
} catch (error) { setMessage('handoff-message', error.message, 'error'); }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function loadDoc(name) {
|
|
954
|
+
byId('doc-content').textContent = 'Loading…';
|
|
955
|
+
try { byId('doc-content').textContent = (await api(`/api/docs/${encodeURIComponent(name)}`)).content; }
|
|
956
|
+
catch (error) { byId('doc-content').textContent = error.message; }
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
byId('planning-view').addEventListener('change', renderPlanningView);
|
|
960
|
+
byId('validate-plan').addEventListener('click', () => planAction(false));
|
|
961
|
+
byId('save-plan').addEventListener('click', () => planAction(true));
|
|
962
|
+
byId('run-all').addEventListener('click', () => runChecks('all'));
|
|
963
|
+
byId('run-all-header').addEventListener('click', () => { document.querySelector('[data-panel="checks"]').click(); runChecks('all'); });
|
|
964
|
+
byId('update-badge').addEventListener('click', async () => {
|
|
965
|
+
const badge = byId('update-badge');
|
|
966
|
+
try {
|
|
967
|
+
await navigator.clipboard.writeText('ctg update');
|
|
968
|
+
badge.dataset.copied = 'true';
|
|
969
|
+
badge.textContent = 'ctg update copied';
|
|
970
|
+
setTimeout(() => { delete badge.dataset.copied; }, 2500);
|
|
971
|
+
} catch { badge.textContent = 'run: ctg update'; }
|
|
972
|
+
});
|
|
973
|
+
byId('mark-verified').addEventListener('click', () => finish('verified'));
|
|
974
|
+
byId('mark-blocked').addEventListener('click', () => finish('blocked'));
|
|
975
|
+
document.querySelectorAll('.doc-tab').forEach(button => button.addEventListener('click', () => {
|
|
976
|
+
document.querySelectorAll('.doc-tab').forEach(item => item.classList.toggle('active', item === button));
|
|
977
|
+
loadDoc(button.dataset.doc);
|
|
978
|
+
}));
|
|
979
|
+
loadStatus();
|
|
980
|
+
if (token) setInterval(loadStatus, 3000); // live step/check progress while the agent runs
|
|
981
|
+
</script>
|
|
982
|
+
</body>
|
|
983
|
+
</html>
|