@azure-id/orc 0.55.0
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/CHANGELOG.md +2017 -0
- package/README-id.md +521 -0
- package/README.md +485 -0
- package/bin/build-agents.js +86 -0
- package/bin/cli.js +29050 -0
- package/bin/mockrun-catalog.js +267 -0
- package/bin/onboarding-content.js +172 -0
- package/bin/pricing.json +200 -0
- package/bin/providers.json +437 -0
- package/bin/ui.js +95 -0
- package/bin/verify-contracts.js +3277 -0
- package/bin/verify-package.js +498 -0
- package/bin/webui/api.js +1163 -0
- package/bin/webui/app.html +198 -0
- package/bin/webui/css/00-tokens.css +92 -0
- package/bin/webui/css/01-base.css +42 -0
- package/bin/webui/css/02-shell.css +103 -0
- package/bin/webui/css/03-components.css +462 -0
- package/bin/webui/css/04-motion.css +91 -0
- package/bin/webui/css/05-tour.css +100 -0
- package/bin/webui/css/06-responsive.css +108 -0
- package/bin/webui/css/panels/boundary.css +26 -0
- package/bin/webui/css/panels/challenge.css +151 -0
- package/bin/webui/css/panels/crosslink.css +194 -0
- package/bin/webui/css/panels/docs.css +254 -0
- package/bin/webui/css/panels/experiment.css +23 -0
- package/bin/webui/css/panels/extra.css +536 -0
- package/bin/webui/css/panels/flow.css +177 -0
- package/bin/webui/css/panels/handoff.css +67 -0
- package/bin/webui/css/panels/knowledge.css +86 -0
- package/bin/webui/css/panels/learn.css +154 -0
- package/bin/webui/css/panels/maintenance.css +55 -0
- package/bin/webui/css/panels/mockrun.css +268 -0
- package/bin/webui/css/panels/overview.css +100 -0
- package/bin/webui/css/panels/pact.css +43 -0
- package/bin/webui/css/panels/runs.css +105 -0
- package/bin/webui/css/panels/settings.css +227 -0
- package/bin/webui/css/panels/stats.css +87 -0
- package/bin/webui/fixtures/boundary.js +92 -0
- package/bin/webui/fixtures/challenge.js +825 -0
- package/bin/webui/fixtures/crosslink.js +31 -0
- package/bin/webui/fixtures/docs.js +982 -0
- package/bin/webui/fixtures/extra.js +1934 -0
- package/bin/webui/fixtures/flow.js +81 -0
- package/bin/webui/fixtures/handoff.js +35 -0
- package/bin/webui/fixtures/index.js +486 -0
- package/bin/webui/fixtures/knowledge.js +291 -0
- package/bin/webui/fixtures/maintenance.js +42 -0
- package/bin/webui/fixtures/mockrun.js +36 -0
- package/bin/webui/fixtures/pact.js +111 -0
- package/bin/webui/fixtures/runs.js +109 -0
- package/bin/webui/fixtures/settings.js +78 -0
- package/bin/webui/fixtures/shell.js +48 -0
- package/bin/webui/fixtures/stats.js +104 -0
- package/bin/webui/i18n/TERMS.md +134 -0
- package/bin/webui/i18n/en/banner.json +29 -0
- package/bin/webui/i18n/en/boundary.json +20 -0
- package/bin/webui/i18n/en/challenge.json +106 -0
- package/bin/webui/i18n/en/common.json +69 -0
- package/bin/webui/i18n/en/crosslink.json +69 -0
- package/bin/webui/i18n/en/docs.json +175 -0
- package/bin/webui/i18n/en/experiment.json +17 -0
- package/bin/webui/i18n/en/extra.json +313 -0
- package/bin/webui/i18n/en/flow.json +30 -0
- package/bin/webui/i18n/en/handoff.json +37 -0
- package/bin/webui/i18n/en/knowledge.json +141 -0
- package/bin/webui/i18n/en/learn.json +16 -0
- package/bin/webui/i18n/en/maintenance.json +52 -0
- package/bin/webui/i18n/en/mockrun.json +19 -0
- package/bin/webui/i18n/en/nav.json +19 -0
- package/bin/webui/i18n/en/overview.json +89 -0
- package/bin/webui/i18n/en/pact.json +36 -0
- package/bin/webui/i18n/en/runs.json +51 -0
- package/bin/webui/i18n/en/settings.json +49 -0
- package/bin/webui/i18n/en/stats.json +69 -0
- package/bin/webui/i18n/en/tour.json +33 -0
- package/bin/webui/i18n/id/banner.json +29 -0
- package/bin/webui/i18n/id/boundary.json +20 -0
- package/bin/webui/i18n/id/challenge.json +106 -0
- package/bin/webui/i18n/id/common.json +70 -0
- package/bin/webui/i18n/id/crosslink.json +69 -0
- package/bin/webui/i18n/id/docs.json +175 -0
- package/bin/webui/i18n/id/experiment.json +17 -0
- package/bin/webui/i18n/id/extra.json +313 -0
- package/bin/webui/i18n/id/flow.json +30 -0
- package/bin/webui/i18n/id/handoff.json +37 -0
- package/bin/webui/i18n/id/knowledge.json +141 -0
- package/bin/webui/i18n/id/learn.json +16 -0
- package/bin/webui/i18n/id/maintenance.json +52 -0
- package/bin/webui/i18n/id/mockrun.json +19 -0
- package/bin/webui/i18n/id/nav.json +19 -0
- package/bin/webui/i18n/id/overview.json +89 -0
- package/bin/webui/i18n/id/pact.json +36 -0
- package/bin/webui/i18n/id/runs.json +51 -0
- package/bin/webui/i18n/id/settings.json +49 -0
- package/bin/webui/i18n/id/stats.json +69 -0
- package/bin/webui/i18n/id/tour.json +33 -0
- package/bin/webui/js/00-core.js +115 -0
- package/bin/webui/js/01-i18n.js +149 -0
- package/bin/webui/js/02-ui.js +211 -0
- package/bin/webui/js/03-md.js +286 -0
- package/bin/webui/js/04-router.js +51 -0
- package/bin/webui/js/05-banners.js +172 -0
- package/bin/webui/js/06-edit.js +183 -0
- package/bin/webui/js/90-tour.js +488 -0
- package/bin/webui/js/91-shortcuts.js +126 -0
- package/bin/webui/js/99-boot.js +118 -0
- package/bin/webui/js/panels/boundary.js +104 -0
- package/bin/webui/js/panels/challenge.js +781 -0
- package/bin/webui/js/panels/crosslink.js +654 -0
- package/bin/webui/js/panels/docs.js +1338 -0
- package/bin/webui/js/panels/experiment.js +98 -0
- package/bin/webui/js/panels/extra.js +3011 -0
- package/bin/webui/js/panels/flow.js +297 -0
- package/bin/webui/js/panels/handoff.js +169 -0
- package/bin/webui/js/panels/knowledge.js +890 -0
- package/bin/webui/js/panels/learn.js +220 -0
- package/bin/webui/js/panels/maintenance.js +235 -0
- package/bin/webui/js/panels/mockrun.js +227 -0
- package/bin/webui/js/panels/overview.js +447 -0
- package/bin/webui/js/panels/pact.js +189 -0
- package/bin/webui/js/panels/runs.js +416 -0
- package/bin/webui/js/panels/settings.js +573 -0
- package/bin/webui/js/panels/stats.js +378 -0
- package/bin/webui/serve.js +557 -0
- package/mock-run/INDEX.md +106 -0
- package/mock-run/a-normal-day.md +587 -0
- package/mock-run/context-combiner.md +100 -0
- package/mock-run/extra-recovery.md +330 -0
- package/mock-run/extra-slots.md +177 -0
- package/mock-run/media/README.md +43 -0
- package/mock-run/orc-aftermath.md +392 -0
- package/mock-run/orc-boundary.md +356 -0
- package/mock-run/orc-brainstorm.md +177 -0
- package/mock-run/orc-budget.md +534 -0
- package/mock-run/orc-challenge-council.md +262 -0
- package/mock-run/orc-challenge.md +416 -0
- package/mock-run/orc-cli.md +200 -0
- package/mock-run/orc-diy.md +129 -0
- package/mock-run/orc-doc.md +448 -0
- package/mock-run/orc-explain.md +86 -0
- package/mock-run/orc-export.md +423 -0
- package/mock-run/orc-extra.md +392 -0
- package/mock-run/orc-fast.md +106 -0
- package/mock-run/orc-grill.md +151 -0
- package/mock-run/orc-handoff.md +480 -0
- package/mock-run/orc-pact.md +421 -0
- package/mock-run/orc-pattern.md +112 -0
- package/mock-run/orc-plan.md +108 -0
- package/mock-run/orc-pr-setup.md +126 -0
- package/mock-run/orc-quick.md +113 -0
- package/mock-run/orc-route.md +93 -0
- package/mock-run/orc-ui.md +125 -0
- package/mock-run/orc-ultra.md +103 -0
- package/mock-run/orc.md +157 -0
- package/mock-run/the-example-project.md +55 -0
- package/package.json +39 -0
- package/templates/agents/MODEL-MAPPING.md +168 -0
- package/templates/agents/orc-advisor-fable-5.md +50 -0
- package/templates/agents/orc-advisor-opus-5-xhigh.md +56 -0
- package/templates/agents/orc-analyst-fable-5.md +115 -0
- package/templates/agents/orc-analyze-mini-opus-5-med.md +60 -0
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +58 -0
- package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -0
- package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -0
- package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -0
- package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -0
- package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -0
- package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -0
- package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -0
- package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -0
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +53 -0
- package/templates/agents/orc-claude-writer-opus-5-med.md +55 -0
- package/templates/agents/orc-context-combiner-opus-5-high.md +88 -0
- package/templates/agents/orc-doc-checker-opus-5-low.md +108 -0
- package/templates/agents/orc-doc-writer-opus-5-med.md +134 -0
- package/templates/agents/orc-executor-haiku-4-5.md +113 -0
- package/templates/agents/orc-executor-opus-4-7-high.md +114 -0
- package/templates/agents/orc-executor-opus-4-7-med.md +114 -0
- package/templates/agents/orc-executor-opus-4-8-high.md +114 -0
- package/templates/agents/orc-executor-opus-5-high.md +114 -0
- package/templates/agents/orc-executor-opus-5-low.md +114 -0
- package/templates/agents/orc-executor-opus-5-med.md +114 -0
- package/templates/agents/orc-executor-sonnet-4-6-high.md +114 -0
- package/templates/agents/orc-executor-sonnet-4-6-med.md +114 -0
- package/templates/agents/orc-executor-sonnet-5-high.md +114 -0
- package/templates/agents/orc-judge-fable-5.md +79 -0
- package/templates/agents/orc-judge-opus-5-xhigh.md +85 -0
- package/templates/agents/orc-learn-writer-opus-5-low.md +73 -0
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +65 -0
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +63 -0
- package/templates/agents/orc-planner-fable-5.md +152 -0
- package/templates/agents/orc-planner-mini-opus-5-med.md +69 -0
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +67 -0
- package/templates/agents/orc-planner-opus-5-med.md +160 -0
- package/templates/agents/orc-retro-opus-5-med.md +73 -0
- package/templates/agents/orc-retro-sonnet-5-high.md +72 -0
- package/templates/agents/orc-reviewer-fable-5.md +57 -0
- package/templates/agents/orc-reviewer-opus-5-med.md +60 -0
- package/templates/agents/orc-scout-opus-5-low.md +40 -0
- package/templates/agents/orc-scout-sonnet-4-6-high.md +39 -0
- package/templates/agents/orc-system-analyst-opus-5-high.md +120 -0
- package/templates/agents/orc-test-author-opus-5-med.md +71 -0
- package/templates/agents/orc-trace-writer-haiku-4-5.md +107 -0
- package/templates/agents/orc-verifier-opus-5-med.md +69 -0
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +79 -0
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +81 -0
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +106 -0
- package/templates/commands/orc-aftermath.md +49 -0
- package/templates/commands/orc-analyze.md +21 -0
- package/templates/commands/orc-boundary.md +46 -0
- package/templates/commands/orc-brainstorm.md +51 -0
- package/templates/commands/orc-budget.md +54 -0
- package/templates/commands/orc-challenge.md +58 -0
- package/templates/commands/orc-claude.md +14 -0
- package/templates/commands/orc-diy.md +13 -0
- package/templates/commands/orc-doc.md +128 -0
- package/templates/commands/orc-explain.md +20 -0
- package/templates/commands/orc-export.md +46 -0
- package/templates/commands/orc-fast.md +15 -0
- package/templates/commands/orc-grill.md +40 -0
- package/templates/commands/orc-handoff.md +52 -0
- package/templates/commands/orc-learn.md +17 -0
- package/templates/commands/orc-mini.md +12 -0
- package/templates/commands/orc-pact.md +44 -0
- package/templates/commands/orc-pattern.md +15 -0
- package/templates/commands/orc-plan.md +22 -0
- package/templates/commands/orc-poly.md +21 -0
- package/templates/commands/orc-pr-driver.md +30 -0
- package/templates/commands/orc-pr-setup.md +31 -0
- package/templates/commands/orc-quick.md +33 -0
- package/templates/commands/orc-retro.md +17 -0
- package/templates/commands/orc-route.md +41 -0
- package/templates/commands/orc-ultra.md +17 -0
- package/templates/commands/orc-verify.md +11 -0
- package/templates/commands/orc-wiki.md +42 -0
- package/templates/commands/orc.md +16 -0
- package/templates/hooks/orc-effort-guard.js +178 -0
- package/templates/hooks/orc-statusline.js +305 -0
- package/templates/hooks/orc-trace.js +471 -0
- package/templates/hooks/orc-update-lib.js +125 -0
- package/templates/skills/_shared/README.md +50 -0
- package/templates/skills/_shared/detecting-artifacts.md +90 -0
- package/templates/skills/_shared/drift-recovery.md +98 -0
- package/templates/skills/_shared/extra-dispatch.md +1051 -0
- package/templates/skills/_shared/fable5-override.md +56 -0
- package/templates/skills/_shared/fallback-handoff.md +42 -0
- package/templates/skills/_shared/gh-stack-commands.md +65 -0
- package/templates/skills/_shared/gotchas.md +177 -0
- package/templates/skills/_shared/interview.md +144 -0
- package/templates/skills/_shared/lane-suspend.md +82 -0
- package/templates/skills/_shared/opus5-only.md +131 -0
- package/templates/skills/_shared/pr-templates.md +106 -0
- package/templates/skills/_shared/read-ladder.md +54 -0
- package/templates/skills/_shared/return-validation.md +145 -0
- package/templates/skills/_shared/smoke-gate.md +28 -0
- package/templates/skills/_shared/stack-plan.md +135 -0
- package/templates/skills/_shared/untrusted-input.md +63 -0
- package/templates/skills/context-combiner/SKILL.md +187 -0
- package/templates/skills/context-combiner/schemas/combined-report.md +78 -0
- package/templates/skills/context-combiner/schemas/combined-requirement-spec.md +87 -0
- package/templates/skills/orc/README.md +150 -0
- package/templates/skills/orc/SKILL.md +526 -0
- package/templates/skills/orc/config.md +389 -0
- package/templates/skills/orc/examples/full-run-mock.md +73 -0
- package/templates/skills/orc/references/analyst-gates.md +124 -0
- package/templates/skills/orc/references/effort-and-mode.md +224 -0
- package/templates/skills/orc/references/house-rules.md +20 -0
- package/templates/skills/orc/references/intake.md +120 -0
- package/templates/skills/orc/references/log-protocol.md +39 -0
- package/templates/skills/orc/references/pattern-gate.md +89 -0
- package/templates/skills/orc/references/plan-handoff.md +84 -0
- package/templates/skills/orc/references/preflight-report.md +220 -0
- package/templates/skills/orc/references/security-checklist.md +39 -0
- package/templates/skills/orc/references/stop-and-resume.md +160 -0
- package/templates/skills/orc/references/trace-protocol.md +314 -0
- package/templates/skills/orc/references/ultra-mode.md +123 -0
- package/templates/skills/orc/references/wave-grouping.md +121 -0
- package/templates/skills/orc/references/wiki-consult.md +177 -0
- package/templates/skills/orc/schemas/checkpoint.md +122 -0
- package/templates/skills/orc/schemas/intent-spec.md +42 -0
- package/templates/skills/orc/schemas/planning-output.md +279 -0
- package/templates/skills/orc/subskills/orc-checkpoint/SKILL.md +55 -0
- package/templates/skills/orc/subskills/orc-execution/SKILL.md +72 -0
- package/templates/skills/orc/subskills/orc-execution/core.md +151 -0
- package/templates/skills/orc/subskills/orc-execution/subagent.md +13 -0
- package/templates/skills/orc/subskills/orc-planner/SKILL.md +264 -0
- package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -0
- package/templates/skills/orc/subskills/orc-pr/SKILL.md +51 -0
- package/templates/skills/orc/subskills/orc-pr/pr.md +22 -0
- package/templates/skills/orc/subskills/orc-pr/stack-gate.md +108 -0
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +76 -0
- package/templates/skills/orc/subskills/orc-review-verify/core.md +135 -0
- package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -0
- package/templates/skills/orc/subskills/orc-testgen/SKILL.md +45 -0
- package/templates/skills/orc/subskills/orc-testgen/core.md +59 -0
- package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -0
- package/templates/skills/orc-advisor/SKILL.md +60 -0
- package/templates/skills/orc-aftermath/SKILL.md +136 -0
- package/templates/skills/orc-aftermath/references/report.md +95 -0
- package/templates/skills/orc-analyze/SKILL.md +220 -0
- package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -0
- package/templates/skills/orc-analyze/references/branching.md +51 -0
- package/templates/skills/orc-analyze/references/deep-mode.md +27 -0
- package/templates/skills/orc-analyze/references/thin-input.md +77 -0
- package/templates/skills/orc-analyze/schemas/report-audit.md +83 -0
- package/templates/skills/orc-analyze/schemas/report-prose.md +63 -0
- package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -0
- package/templates/skills/orc-analyze/schemas/requirement-spec.md +77 -0
- package/templates/skills/orc-analyze-mini/SKILL.md +112 -0
- package/templates/skills/orc-analyze-mini/examples/quick-analysis-mock.md +31 -0
- package/templates/skills/orc-boundary/SKILL.md +208 -0
- package/templates/skills/orc-boundary/references/card.md +78 -0
- package/templates/skills/orc-boundary/references/gate.md +113 -0
- package/templates/skills/orc-brainstorm/SKILL.md +339 -0
- package/templates/skills/orc-brainstorm/references/brainstorm-doc.md +133 -0
- package/templates/skills/orc-brainstorm/references/lenses.md +152 -0
- package/templates/skills/orc-budget/SKILL.md +225 -0
- package/templates/skills/orc-budget/references/corpus.md +87 -0
- package/templates/skills/orc-challenge/README.md +142 -0
- package/templates/skills/orc-challenge/SKILL.md +255 -0
- package/templates/skills/orc-challenge/examples/code-module.md +110 -0
- package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -0
- package/templates/skills/orc-challenge/examples/tsd-two-iterations.md +213 -0
- package/templates/skills/orc-challenge/references/conservation.md +107 -0
- package/templates/skills/orc-challenge/references/council.md +315 -0
- package/templates/skills/orc-challenge/references/cycle-state.md +185 -0
- package/templates/skills/orc-challenge/references/dimensions.md +121 -0
- package/templates/skills/orc-challenge/references/fix-brief.md +130 -0
- package/templates/skills/orc-challenge/references/intake.md +171 -0
- package/templates/skills/orc-challenge/references/kinds.md +44 -0
- package/templates/skills/orc-challenge/references/plain-english.md +98 -0
- package/templates/skills/orc-challenge/references/rubric.md +109 -0
- package/templates/skills/orc-challenge/references/sealed-slice.md +97 -0
- package/templates/skills/orc-challenge/references/verdict-doc.md +154 -0
- package/templates/skills/orc-claude/SKILL.md +199 -0
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -0
- package/templates/skills/orc-claude/references/refresh.md +76 -0
- package/templates/skills/orc-claude/references/template.md +109 -0
- package/templates/skills/orc-diy/README.md +142 -0
- package/templates/skills/orc-diy/SKILL.md +70 -0
- package/templates/skills/orc-diy/references/blocks/analyze.md +24 -0
- package/templates/skills/orc-diy/references/blocks/execution.md +42 -0
- package/templates/skills/orc-diy/references/blocks/extra.md +55 -0
- package/templates/skills/orc-diy/references/blocks/header.md +55 -0
- package/templates/skills/orc-diy/references/blocks/mock-example.md +22 -0
- package/templates/skills/orc-diy/references/blocks/pattern.md +18 -0
- package/templates/skills/orc-diy/references/blocks/planning.md +34 -0
- package/templates/skills/orc-diy/references/blocks/review.md +18 -0
- package/templates/skills/orc-diy/references/blocks/scoring.md +16 -0
- package/templates/skills/orc-diy/references/blocks/security.md +25 -0
- package/templates/skills/orc-diy/references/blocks/ship.md +25 -0
- package/templates/skills/orc-diy/references/blocks/summary.md +18 -0
- package/templates/skills/orc-diy/references/blocks/testgen.md +17 -0
- package/templates/skills/orc-diy/references/blocks/trace.md +28 -0
- package/templates/skills/orc-diy/references/blocks/verify.md +25 -0
- package/templates/skills/orc-diy/references/blocks/wiki.md +26 -0
- package/templates/skills/orc-diy/references/compile.md +59 -0
- package/templates/skills/orc-diy/references/flow-schema.md +100 -0
- package/templates/skills/orc-diy/references/locked-blocks.md +37 -0
- package/templates/skills/orc-doc/README.md +229 -0
- package/templates/skills/orc-doc/SKILL.md +444 -0
- package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -0
- package/templates/skills/orc-doc/references/chunking.md +527 -0
- package/templates/skills/orc-doc/references/gates.md +311 -0
- package/templates/skills/orc-doc/references/generation-rules.md +122 -0
- package/templates/skills/orc-doc/references/house-rules.md +170 -0
- package/templates/skills/orc-doc/references/import-targets.md +40 -0
- package/templates/skills/orc-doc/references/plain-language.md +66 -0
- package/templates/skills/orc-doc/references/portable-markdown.md +74 -0
- package/templates/skills/orc-doc/references/resume-protocol.md +228 -0
- package/templates/skills/orc-doc/references/templates/collaboration.md +118 -0
- package/templates/skills/orc-doc/references/templates/prd.md +133 -0
- package/templates/skills/orc-doc/references/templates/report.md +102 -0
- package/templates/skills/orc-doc/references/templates/tsd.md +122 -0
- package/templates/skills/orc-doc/references/templates/workflow.md +112 -0
- package/templates/skills/orc-explain/SKILL.md +57 -0
- package/templates/skills/orc-export/SKILL.md +154 -0
- package/templates/skills/orc-fast/SKILL.md +188 -0
- package/templates/skills/orc-grill/SKILL.md +207 -0
- package/templates/skills/orc-grill/references/grill-doc.md +105 -0
- package/templates/skills/orc-handoff/SKILL.md +204 -0
- package/templates/skills/orc-handoff/references/handoff-log.md +67 -0
- package/templates/skills/orc-handoff/references/surfaces.md +88 -0
- package/templates/skills/orc-judge/SKILL.md +82 -0
- package/templates/skills/orc-learn/SKILL.md +156 -0
- package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -0
- package/templates/skills/orc-learn/references/deepen.md +70 -0
- package/templates/skills/orc-learn/references/refresh.md +65 -0
- package/templates/skills/orc-learn/references/template-knowledge.md +83 -0
- package/templates/skills/orc-learn/references/template-learning.md +67 -0
- package/templates/skills/orc-mini/SKILL.md +226 -0
- package/templates/skills/orc-mini/examples/mini-run-mock.md +50 -0
- package/templates/skills/orc-pact/SKILL.md +218 -0
- package/templates/skills/orc-pact/references/gate.md +70 -0
- package/templates/skills/orc-pact/references/ledger.md +99 -0
- package/templates/skills/orc-pattern/SKILL.md +124 -0
- package/templates/skills/orc-pattern/references/INDEX.md +73 -0
- package/templates/skills/orc-pattern/references/be-django.md +80 -0
- package/templates/skills/orc-pattern/references/be-express.md +82 -0
- package/templates/skills/orc-pattern/references/be-fastapi.md +107 -0
- package/templates/skills/orc-pattern/references/be-go.md +96 -0
- package/templates/skills/orc-pattern/references/be-nestjs.md +86 -0
- package/templates/skills/orc-pattern/references/be-postgres.md +98 -0
- package/templates/skills/orc-pattern/references/fe-a11y.md +36 -0
- package/templates/skills/orc-pattern/references/fe-angular.md +87 -0
- package/templates/skills/orc-pattern/references/fe-nextjs.md +65 -0
- package/templates/skills/orc-pattern/references/fe-perf.md +39 -0
- package/templates/skills/orc-pattern/references/fe-react.md +83 -0
- package/templates/skills/orc-pattern/references/fe-vue.md +75 -0
- package/templates/skills/orc-pattern/schemas/pattern-doc.md +62 -0
- package/templates/skills/orc-poly/SKILL.md +216 -0
- package/templates/skills/orc-poly/examples/poly-run-mock.md +51 -0
- package/templates/skills/orc-poly/references/gather.md +70 -0
- package/templates/skills/orc-poly/references/poly-spec.md +84 -0
- package/templates/skills/orc-pr-driver/README.md +349 -0
- package/templates/skills/orc-pr-driver/SKILL.md +167 -0
- package/templates/skills/orc-pr-driver/references/conflict-playbook.md +78 -0
- package/templates/skills/orc-pr-driver/references/green-gate.md +105 -0
- package/templates/skills/orc-pr-driver/references/orc-run-split.md +99 -0
- package/templates/skills/orc-pr-setup/README.md +436 -0
- package/templates/skills/orc-pr-setup/SKILL.md +184 -0
- package/templates/skills/orc-pr-setup/references/certainty-gate.md +66 -0
- package/templates/skills/orc-pr-setup/references/layer-taxonomy.md +67 -0
- package/templates/skills/orc-quick/README.md +423 -0
- package/templates/skills/orc-quick/SKILL.md +306 -0
- package/templates/skills/orc-quick/references/context-doc.md +114 -0
- package/templates/skills/orc-quick/references/dispatch-gate.md +163 -0
- package/templates/skills/orc-quick/references/gh-mode.md +127 -0
- package/templates/skills/orc-retro/SKILL.md +222 -0
- package/templates/skills/orc-retro/examples/retro-mock.md +171 -0
- package/templates/skills/orc-route/SKILL.md +165 -0
- package/templates/skills/orc-verify/SKILL.md +86 -0
- package/templates/skills/orc-verify/examples/verify-mock.md +33 -0
- package/templates/skills/orc-wiki/README.md +325 -0
- package/templates/skills/orc-wiki/SKILL.md +322 -0
- package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -0
- package/templates/skills/orc-wiki/references/claude-md-injection.md +61 -0
- package/templates/skills/orc-wiki/references/crosslink-compile.md +39 -0
- package/templates/skills/orc-wiki/references/crosslink-kinds.md +57 -0
- package/templates/skills/orc-wiki/references/crosslink.md +284 -0
- package/templates/skills/orc-wiki/references/extra.md +79 -0
- package/templates/skills/orc-wiki/references/integrity-check.md +86 -0
- package/templates/skills/orc-wiki/references/orientation.md +51 -0
- package/templates/skills/orc-wiki/references/partial-refresh.md +203 -0
- package/templates/skills/orc-wiki/references/pattern-prewarm.md +19 -0
- package/templates/skills/orc-wiki/references/staleness.md +288 -0
- package/templates/skills/orc-wiki/schemas/crosslink-tag.md +166 -0
- package/templates/skills/orc-wiki/schemas/wiki-doc.md +120 -0
|
@@ -0,0 +1,1338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* panels/docs.js — orc ui client
|
|
3
|
+
The section ribbon, the health box and the wave preview.
|
|
4
|
+
|
|
5
|
+
Loaded by app.html in the order its numeric prefix names. Classic
|
|
6
|
+
script, no import/export: an ES module import carries no query string,
|
|
7
|
+
and every static request here needs the per-launch session token. */
|
|
8
|
+
|
|
9
|
+
// The six section states the CLI can emit. The panel may KEY on one — a colour,
|
|
10
|
+
// a marker, an action — but never invent one.
|
|
11
|
+
//
|
|
12
|
+
// `unconfirmed` (v0.49.0) is a file on disk that no validated return ever
|
|
13
|
+
// confirmed: exactly what a wave killed by a usage limit leaves behind. It reads
|
|
14
|
+
// as a WARNING, never as progress, because a half-written section must never
|
|
15
|
+
// look finished.
|
|
16
|
+
const DOC_STATE_KIND = {
|
|
17
|
+
planned: "",
|
|
18
|
+
written: "ok",
|
|
19
|
+
checked: "ok",
|
|
20
|
+
"user-edited": "info",
|
|
21
|
+
open: "warn",
|
|
22
|
+
unconfirmed: "warn",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
PANELS.docs = function (host) {
|
|
26
|
+
head(host, t("docs.title"), t("docs.sub"));
|
|
27
|
+
const body = el("div", "stack");
|
|
28
|
+
host.append(body);
|
|
29
|
+
renderDocs(body);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function renderDocs(body) {
|
|
33
|
+
body.replaceChildren(skeleton(5));
|
|
34
|
+
let d;
|
|
35
|
+
try {
|
|
36
|
+
d = (await read("/api/doc")).data;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
body.replaceChildren(failBox(e));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const out = frag();
|
|
42
|
+
|
|
43
|
+
// An empty list is an ANSWER, not a gap: it renders the command that starts
|
|
44
|
+
// one, never a spinner and never a "nothing here" shrug.
|
|
45
|
+
if (!d || !d.documents || !d.documents.length) {
|
|
46
|
+
// The house rules still render: they are what the FIRST document will
|
|
47
|
+
// freeze, so setting them before there is a document is the useful order.
|
|
48
|
+
out.append(await docRulesCard(body));
|
|
49
|
+
const c = card(t("docs.title"));
|
|
50
|
+
c.append(empty(t("docs.none"), t("docs.noneHint")));
|
|
51
|
+
c.append(laneCommand("/orc-doc", t("docs.cmdWhy")));
|
|
52
|
+
out.append(c);
|
|
53
|
+
body.replaceChildren(out);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// PROJECT-LEVEL, so it is above the document list. The house rules are not a
|
|
58
|
+
// property of any one document — they are what every new document freezes.
|
|
59
|
+
out.append(await docRulesCard(body));
|
|
60
|
+
|
|
61
|
+
const sum = card(t("docs.summary"));
|
|
62
|
+
const chips = el("div", "row-actions");
|
|
63
|
+
chips.append(chip(tn(d.documents.length, "docs.docN"), "info"));
|
|
64
|
+
const edited = d.documents.reduce((a, x) => a + (x.user_edited || []).length, 0);
|
|
65
|
+
if (edited) chips.append(chip(tn(edited, "docs.editedN"), "info"));
|
|
66
|
+
sum.append(chips);
|
|
67
|
+
sum.append(el("div", "note", t("docs.contract")));
|
|
68
|
+
out.append(sum);
|
|
69
|
+
|
|
70
|
+
// One row open at a time, detail fetched on first open — the Runs-panel rule.
|
|
71
|
+
// There is no detail box below the list.
|
|
72
|
+
const list = el("div", "run-list doc-list");
|
|
73
|
+
const rows = [];
|
|
74
|
+
const setOpen = (entry, open) => {
|
|
75
|
+
entry.row.classList.toggle("open", open);
|
|
76
|
+
entry.head.setAttribute("aria-expanded", String(open));
|
|
77
|
+
if (open && !entry.loaded) {
|
|
78
|
+
entry.loaded = true;
|
|
79
|
+
loadDocDetail(entry.pane, entry.slug, body);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const collapseAll = (except) => {
|
|
83
|
+
for (const r of rows) if (r.row !== except) setOpen(r, false);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
for (const doc of d.documents) {
|
|
87
|
+
const row = el("div", "run-row");
|
|
88
|
+
const headBtn = el("button", "run-card");
|
|
89
|
+
headBtn.type = "button";
|
|
90
|
+
headBtn.setAttribute("aria-expanded", "false");
|
|
91
|
+
headBtn.append(el("span", "run-caret", "▸"));
|
|
92
|
+
// The CLI's own words. `not started` is the CLI's phrase for a document with
|
|
93
|
+
// no document.md, and it is never softened into "failed" or "empty".
|
|
94
|
+
headBtn.append(chip(doc.document, doc.document === "present" ? "ok" : ""));
|
|
95
|
+
if ((doc.user_edited || []).length) {
|
|
96
|
+
headBtn.append(chip(t("docs.editedChip"), "info"));
|
|
97
|
+
// A fifth child needs a fifth column. Without this the chip took the
|
|
98
|
+
// `run-mid` column, the title was squeezed into 88px and the count wrapped
|
|
99
|
+
// onto a second row — the identical collision v0.49.2 fixed in Overview.
|
|
100
|
+
headBtn.classList.add("has-extra");
|
|
101
|
+
}
|
|
102
|
+
const mid = el("div", "run-mid");
|
|
103
|
+
mid.append(el("div", "run-slug", doc.title || doc.slug));
|
|
104
|
+
mid.append(el("div", "run-where", `${String(doc.type).toUpperCase()} · ${doc.slug}`));
|
|
105
|
+
headBtn.append(mid);
|
|
106
|
+
headBtn.append(
|
|
107
|
+
el("div", "run-age", `${doc.sections_written}/${doc.sections_total}`)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const pane = el("div", "run-pane stack stack-sm");
|
|
111
|
+
pane.append(skeleton(4));
|
|
112
|
+
const inner = el("div", "run-body-inner");
|
|
113
|
+
inner.append(pane);
|
|
114
|
+
const fold = el("div", "run-body");
|
|
115
|
+
fold.append(inner);
|
|
116
|
+
|
|
117
|
+
const entry = { row, head: headBtn, pane, slug: doc.slug, loaded: false };
|
|
118
|
+
rows.push(entry);
|
|
119
|
+
headBtn.addEventListener("click", () => {
|
|
120
|
+
const isOpen = row.classList.contains("open");
|
|
121
|
+
collapseAll(row);
|
|
122
|
+
setOpen(entry, !isOpen);
|
|
123
|
+
});
|
|
124
|
+
row.append(headBtn, fold);
|
|
125
|
+
list.append(row);
|
|
126
|
+
}
|
|
127
|
+
out.append(list);
|
|
128
|
+
out.append(laneCommand("/orc-doc", t("docs.cmdWhy")));
|
|
129
|
+
body.replaceChildren(out);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* CARD ORDER IS DELIBERATE: MEMORY FIRST (1–4), THEN STATE (5–10).
|
|
133
|
+
|
|
134
|
+
The panel used to open with a ribbon, a health box and a wave list. That
|
|
135
|
+
answers "what state is this document in" and nothing else — and a user coming
|
|
136
|
+
back after three weeks did not come back to ask that. They need THEIR OWN
|
|
137
|
+
MEMORY back: what they asked for, in their own words; the brief that was
|
|
138
|
+
frozen and is never re-asked; which reference documents fed it and whether
|
|
139
|
+
those still hold; and the ordered story of every request from the first
|
|
140
|
+
firing to the last touch.
|
|
141
|
+
|
|
142
|
+
That is why v0.48.1 added `orc doc journal` / `context` at all: it was a DATA
|
|
143
|
+
gap, not a rendering one, and no amount of layout could have fixed it. */
|
|
144
|
+
async function loadDocDetail(pane, slug, body) {
|
|
145
|
+
let s;
|
|
146
|
+
let map = null;
|
|
147
|
+
let lint = null;
|
|
148
|
+
let plan = null;
|
|
149
|
+
let show = null;
|
|
150
|
+
let next = null;
|
|
151
|
+
let audit = null;
|
|
152
|
+
let parts = null;
|
|
153
|
+
let rules = null;
|
|
154
|
+
let forecast = null;
|
|
155
|
+
let cost = null;
|
|
156
|
+
let docExtra = null;
|
|
157
|
+
const q = "?slug=" + encodeURIComponent(slug);
|
|
158
|
+
const soft = (route) => read(route).catch(() => ({ data: null }));
|
|
159
|
+
try {
|
|
160
|
+
s = (await read("/api/doc/one" + q)).data;
|
|
161
|
+
map = (await soft("/api/doc/map" + q)).data;
|
|
162
|
+
lint = (await soft("/api/doc/lint" + q)).data;
|
|
163
|
+
plan = (await soft("/api/doc/plan" + q + "&role=write")).data;
|
|
164
|
+
show = (await soft("/api/doc/show" + q)).data;
|
|
165
|
+
next = (await soft("/api/doc/next" + q)).data;
|
|
166
|
+
audit = (await soft("/api/doc/audit" + q)).data;
|
|
167
|
+
parts = (await soft("/api/doc/parts" + q)).data;
|
|
168
|
+
rules = (await soft("/api/doc/rules/one" + q)).data;
|
|
169
|
+
forecast = (await soft("/api/doc/forecast" + q)).data;
|
|
170
|
+
cost = (await soft("/api/doc/cost" + q)).data;
|
|
171
|
+
docExtra = (await soft("/api/doc/extra" + q)).data;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
pane.replaceChildren(failBox(e));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const out = frag();
|
|
177
|
+
const sections = (map && map.sections) || [];
|
|
178
|
+
|
|
179
|
+
// ── THE MEMORY HALF ──────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
// --- 1. the header strip. Every value comes from `orc doc show --json`;
|
|
182
|
+
// the panel computes none of it.
|
|
183
|
+
if (show) out.append(docHeaderStrip(show, s));
|
|
184
|
+
|
|
185
|
+
// --- 2. the brief — expanded by default on first open. THE single
|
|
186
|
+
// highest-value card here, and the verbatim request is its payload.
|
|
187
|
+
if (show && show.context) out.append(docBriefCard(show.context));
|
|
188
|
+
|
|
189
|
+
// --- 3. reference files, with the CLI's own state words per row.
|
|
190
|
+
if (show && show.context) out.append(docSourcesCard(show.context));
|
|
191
|
+
|
|
192
|
+
// --- 4. the journal — the ordered story, oldest first, gaps AS gaps.
|
|
193
|
+
if (show && (show.journal || []).length) out.append(docJournalCard(show.journal));
|
|
194
|
+
|
|
195
|
+
// ── THE STATE HALF ───────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
// --- 5. the state chip and the ONE next action
|
|
198
|
+
const st = card(null);
|
|
199
|
+
const stRow = el("div", "row-actions");
|
|
200
|
+
// THE CLI'S STATE WORDS, VERBATIM — `shipped-drifted` is never softened into
|
|
201
|
+
// something friendlier, and it KEEPS ITS SLOT because it is an answer.
|
|
202
|
+
stRow.append(chip(s.state, DOC_DOC_STATE_KIND[s.state] !== undefined ? DOC_DOC_STATE_KIND[s.state] : "info"));
|
|
203
|
+
stRow.append(el("span", "note", s.where));
|
|
204
|
+
st.append(stRow);
|
|
205
|
+
st.append(docNextAction(s, next, body));
|
|
206
|
+
out.append(st);
|
|
207
|
+
|
|
208
|
+
// --- 5b. a v1 document, before anything else: it is one file, and every
|
|
209
|
+
// change still routes through it until it migrates.
|
|
210
|
+
if (s.version !== undefined && s.version < 2) out.append(docMigrateCard(slug, body));
|
|
211
|
+
|
|
212
|
+
// --- 6. ship. A decision, so it is a modal that names its destination and
|
|
213
|
+
// shows the exact command BEFORE it runs.
|
|
214
|
+
out.append(docShipCard(s, body));
|
|
215
|
+
|
|
216
|
+
// --- 6b. the FROZEN house rules for THIS document, and whether the project
|
|
217
|
+
// ledger has moved since. A drift NAMES every rule that moved.
|
|
218
|
+
if (rules) out.append(docFrozenRulesCard(rules, slug, body));
|
|
219
|
+
|
|
220
|
+
// --- 6c. the run map. FREE to recompute, so it gets a button; nothing here
|
|
221
|
+
// is paid, so nothing here is a copy-able command.
|
|
222
|
+
if (forecast) out.append(docForecastCard(forecast, slug, () => loadDocDetail(pane, slug, body)));
|
|
223
|
+
|
|
224
|
+
// --- 6d. what it cost, joined across EVERY session this document spanned.
|
|
225
|
+
if (cost) out.append(docCostCard(cost));
|
|
226
|
+
|
|
227
|
+
// --- 7. audit. A clean audit is a one-line answer, never an empty card.
|
|
228
|
+
if (audit) out.append(docAuditCard(audit, body));
|
|
229
|
+
|
|
230
|
+
// --- 8. THE RIBBON. The one picture this panel is for.
|
|
231
|
+
if (sections.length) {
|
|
232
|
+
const rc = card(t("docs.ribbon"), docFreeActions(slug, body));
|
|
233
|
+
rc.append(docRibbon(sections, (id) => docOpenSection(pane, id)));
|
|
234
|
+
rc.append(docRibbonLegend());
|
|
235
|
+
out.append(rc);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --- 9. health — straight from the lint. The CLI's words are the only words.
|
|
239
|
+
// `ok: false` is the CLI answering "there is no document yet", which is a
|
|
240
|
+
// real state and not a card with empty numbers in it.
|
|
241
|
+
if (lint && lint.ok !== false) {
|
|
242
|
+
const hc = card(t("docs.health"));
|
|
243
|
+
hc.append(docHealth(lint));
|
|
244
|
+
out.append(hc);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// --- 10a. THE SECTION FILES (v0.49.0). `sections/` is the source of truth,
|
|
248
|
+
// so this card is the one that works before a single compile has ever
|
|
249
|
+
// run. Every word in it is the CLI's: the state, the path, the counts.
|
|
250
|
+
if (parts && parts.parts && parts.parts.length) {
|
|
251
|
+
const pc = card(t("docs.parts"), docCompileBtn(slug, body, parts));
|
|
252
|
+
if (parts.wave) pc.append(docWaveStrip(parts.wave, s));
|
|
253
|
+
pc.append(docPartList(parts.parts, parts.dir));
|
|
254
|
+
pc.append(el("div", "note", t("docs.partsNote")));
|
|
255
|
+
if ((s.document_stale || []).length)
|
|
256
|
+
pc.append(el("div", "note bad", t("docs.docStale", { ids: s.document_stale.map((x) => x.heading).join(", ") })));
|
|
257
|
+
out.append(pc);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --- 10a2. WHICH MODEL WRITES THIS DOCUMENT (v0.52.0, D9). Per document,
|
|
261
|
+
// because a global `extra_roles` turning on for a throwaway runbook also
|
|
262
|
+
// turns on for the PRD you ship. Every word is the CLI's: the resolution
|
|
263
|
+
// order, the shadowing sentence, the band and both its edges.
|
|
264
|
+
if (docExtra) out.append(docExtraCard(docExtra, slug, body));
|
|
265
|
+
|
|
266
|
+
// --- 10b. the sections, as the COMPILED document sees them (line ranges)
|
|
267
|
+
if (sections.length) {
|
|
268
|
+
const sc = card(t("docs.sections"));
|
|
269
|
+
sc.append(docSectionList(sections, slug, show));
|
|
270
|
+
sc.append(el("div", "note", t("docs.sectionsNote")));
|
|
271
|
+
out.append(sc);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// --- 11. the wave preview
|
|
275
|
+
if (plan && plan.waves && plan.waves.length) {
|
|
276
|
+
const wc = card(t("docs.waves"));
|
|
277
|
+
wc.append(el("div", "note", t("docs.wavesNote")));
|
|
278
|
+
wc.append(docWaves(plan));
|
|
279
|
+
if (plan.clamped) wc.append(el("div", "note bad", t("docs.clamped", { from: plan.clamped.from, to: plan.clamped.to })));
|
|
280
|
+
if ((plan.oversized || []).length)
|
|
281
|
+
wc.append(el("div", "note bad", t("docs.oversized", { ids: plan.oversized.join(", ") })));
|
|
282
|
+
out.append(wc);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// --- 12. the cycles, last. History is not an action, and card 4 already told
|
|
286
|
+
// the STORY — this is the SHAPE of the run. Both, because they answer
|
|
287
|
+
// different questions.
|
|
288
|
+
if (show && show.cycles && show.cycles.length) {
|
|
289
|
+
out.append(
|
|
290
|
+
collapsible({
|
|
291
|
+
title: t("docs.cycles"),
|
|
292
|
+
count: String(show.cycles.length),
|
|
293
|
+
collapsed: true,
|
|
294
|
+
content: kvList(
|
|
295
|
+
show.cycles.map((c) => [
|
|
296
|
+
String(c.at || "—"),
|
|
297
|
+
[t("docs.cycleN", { n: c.n }), c.role, c.agents ? tn(c.agents, "docs.agentN") : null, c.model]
|
|
298
|
+
.filter(Boolean)
|
|
299
|
+
.join(" · "),
|
|
300
|
+
])
|
|
301
|
+
),
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
pane.replaceChildren(out);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// The CLI's five computed states. The panel may KEY on one — a colour, a
|
|
310
|
+
// marker, an action — but never invent one.
|
|
311
|
+
const DOC_DOC_STATE_KIND = {
|
|
312
|
+
"not-started": "",
|
|
313
|
+
"in-progress": "info",
|
|
314
|
+
complete: "ok",
|
|
315
|
+
shipped: "ok",
|
|
316
|
+
"shipped-drifted": "warn",
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// --- 1. one dense row under the title. `created_at` is here because the CLI
|
|
320
|
+
// always knew it and, until v0.48.1, never said so.
|
|
321
|
+
function docHeaderStrip(show, s) {
|
|
322
|
+
const c = card(null);
|
|
323
|
+
const rows = [
|
|
324
|
+
[t("docs.meta.started"), show.created_at || "—"],
|
|
325
|
+
[t("docs.meta.touched"), show.last_touched_at || "—"],
|
|
326
|
+
[t("docs.meta.sessions"), String(show.sessions || 0)],
|
|
327
|
+
[t("docs.meta.cycle"), String(show.cycle || 0)],
|
|
328
|
+
[t("docs.meta.type"), String(show.type || "").toUpperCase()],
|
|
329
|
+
[t("docs.meta.target"), show.target || "—"],
|
|
330
|
+
[t("docs.meta.language"), show.language || "—"],
|
|
331
|
+
[t("docs.meta.length"), show.length || "—"],
|
|
332
|
+
[t("docs.meta.template"), (show.template && show.template.source) || "—"],
|
|
333
|
+
];
|
|
334
|
+
c.append(kvList(rows));
|
|
335
|
+
return c;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// --- 2. THE BRIEF. Frozen once, read forever, never re-asked — and the panel
|
|
339
|
+
// says that out loud, because a card that looks editable invites an edit
|
|
340
|
+
// that this lane cannot honour.
|
|
341
|
+
function docBriefCard(ctx) {
|
|
342
|
+
if (!ctx.exists) {
|
|
343
|
+
const c = card(t("docs.brief"));
|
|
344
|
+
c.append(empty(t("docs.briefNone"), t("docs.briefNoneHint")));
|
|
345
|
+
return c;
|
|
346
|
+
}
|
|
347
|
+
const c = card(t("docs.brief"));
|
|
348
|
+
c.append(el("div", "note", t("docs.briefFrozen", { when: ctx.frozen_at || "—" })));
|
|
349
|
+
// THE VERBATIM REQUEST, FIRST AND LARGEST. A paraphrase is where a resumed
|
|
350
|
+
// session quietly starts writing a different document.
|
|
351
|
+
if (ctx.request) {
|
|
352
|
+
const q = el("div", "promise");
|
|
353
|
+
q.textContent = ctx.request;
|
|
354
|
+
c.append(q);
|
|
355
|
+
}
|
|
356
|
+
for (const [key, val] of [
|
|
357
|
+
["docs.briefPurpose", ctx.purpose],
|
|
358
|
+
["docs.briefTemplate", ctx.template],
|
|
359
|
+
["docs.briefDecisions", ctx.decisions],
|
|
360
|
+
])
|
|
361
|
+
if (val) c.append(collapsible({ title: t(key), collapsed: true, content: renderMd(val) }));
|
|
362
|
+
return c;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- 3. reference files. When D2 was answered "none" the card says so and
|
|
366
|
+
// KEEPS ITS SLOT — an empty card would read as a bug.
|
|
367
|
+
function docSourcesCard(ctx) {
|
|
368
|
+
const c = card(t("docs.sources"));
|
|
369
|
+
if (!ctx.exists || !(ctx.sources || []).length) {
|
|
370
|
+
c.append(el("div", "note", t("docs.sourcesNone")));
|
|
371
|
+
return c;
|
|
372
|
+
}
|
|
373
|
+
const wrap = el("div", "free-box");
|
|
374
|
+
for (const src of ctx.sources) {
|
|
375
|
+
const row = el("div", "free-row");
|
|
376
|
+
// The CLI's words, verbatim: `ok`, `MISSING`, `SOURCE-DRIFTED`.
|
|
377
|
+
row.append(chip(src.state, src.state === "ok" ? "ok" : src.state === "MISSING" ? "bad" : "warn"));
|
|
378
|
+
row.append(el("span", "mono", src.path));
|
|
379
|
+
if (src.note) row.append(el("span", "note", src.note));
|
|
380
|
+
wrap.append(row);
|
|
381
|
+
}
|
|
382
|
+
c.append(wrap);
|
|
383
|
+
c.append(el("div", "note", t("docs.sourcesNote")));
|
|
384
|
+
return c;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// --- 4. THE JOURNAL. Oldest first, because reading order IS the story.
|
|
388
|
+
function docJournalCard(rows) {
|
|
389
|
+
const recorded = rows.filter((r) => r.origin === "recorded").length;
|
|
390
|
+
const c = card(t("docs.journal"));
|
|
391
|
+
c.append(el("div", "note", t("docs.journalNote", { n: rows.length, recorded })));
|
|
392
|
+
const wrap = el("div", "after-box");
|
|
393
|
+
for (const r of rows) {
|
|
394
|
+
const row = el("div", "after-row");
|
|
395
|
+
row.append(el("span", "mono", r.at || "—"));
|
|
396
|
+
row.append(chip(r.origin, r.origin === "recorded" ? "ok" : r.origin === "observed" ? "warn" : ""));
|
|
397
|
+
row.append(chip(r.kind, "info"));
|
|
398
|
+
// A cycle nobody logged renders AS A GAP. Never a plausible reconstruction:
|
|
399
|
+
// the /orc-pact UNCHECKABLE rule — not knowing is an answer, and faking it
|
|
400
|
+
// teaches people to distrust the rows that are real.
|
|
401
|
+
if (r.gap) row.append(el("span", "note", t("docs.journalGap")));
|
|
402
|
+
else if (r.text) row.append(el("span", null, r.text));
|
|
403
|
+
if ((r.sections || []).length) row.append(el("span", "note mono", r.sections.join(" ")));
|
|
404
|
+
if (r.source && r.source !== "user") row.append(chip(r.source, "info"));
|
|
405
|
+
wrap.append(row);
|
|
406
|
+
}
|
|
407
|
+
c.append(wrap);
|
|
408
|
+
return c;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// --- 6. SHIP. A disabled control that explains itself, never a missing one.
|
|
412
|
+
function docShipCard(s, body) {
|
|
413
|
+
const c = card(t("docs.ship"));
|
|
414
|
+
const shipped = s.shipped;
|
|
415
|
+
if (shipped) {
|
|
416
|
+
c.append(kvList([[t("docs.shipWhen"), shipped.at], [t("docs.shipWhere"), shipped.where], ...(shipped.note ? [[t("docs.shipNote"), shipped.note]] : [])]));
|
|
417
|
+
if (shipped.forced) c.append(el("div", "note bad", t("docs.shipForced", { why: shipped.force_reason || "" })));
|
|
418
|
+
if (s.state === "shipped-drifted")
|
|
419
|
+
c.append(el("div", "note warn", t("docs.shipDrifted", { names: (s.drifted_sections || []).map((x) => x.heading).join(" · ") })));
|
|
420
|
+
c.append(docShipButtons(s, body, true));
|
|
421
|
+
return c;
|
|
422
|
+
}
|
|
423
|
+
const canShip = s.state === "complete";
|
|
424
|
+
const btnRow = el("div", "row-actions");
|
|
425
|
+
const b = el("button", "btn btn-primary btn-sm", t("docs.shipGo"));
|
|
426
|
+
b.type = "button";
|
|
427
|
+
b.disabled = !canShip;
|
|
428
|
+
if (canShip) b.addEventListener("click", () => docShipModal(s, body, false));
|
|
429
|
+
btnRow.append(b);
|
|
430
|
+
c.append(btnRow);
|
|
431
|
+
if (!canShip)
|
|
432
|
+
c.append(
|
|
433
|
+
el(
|
|
434
|
+
"div",
|
|
435
|
+
"note warn",
|
|
436
|
+
t("docs.shipBlocked", {
|
|
437
|
+
why: (s.open_sections || []).length
|
|
438
|
+
? (s.open_sections || []).map((x) => x.heading).join(" · ")
|
|
439
|
+
: t("docs.shipBlockedLint"),
|
|
440
|
+
})
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
c.append(el("div", "note", t("docs.shipWhy")));
|
|
444
|
+
return c;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function docShipButtons(s, body, shipped) {
|
|
448
|
+
const row = el("div", "row-actions");
|
|
449
|
+
const re = el("button", "btn btn-ghost btn-sm", t("docs.reship"));
|
|
450
|
+
re.type = "button";
|
|
451
|
+
re.addEventListener("click", () => docShipModal(s, body, false));
|
|
452
|
+
const un = el("button", "btn btn-ghost btn-sm", t("docs.unship"));
|
|
453
|
+
un.type = "button";
|
|
454
|
+
un.addEventListener("click", () => docShipModal(s, body, true));
|
|
455
|
+
row.append(re, un);
|
|
456
|
+
return row;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function docShipModal(s, body, undo) {
|
|
460
|
+
const b = frag();
|
|
461
|
+
const field = (labelKey, ph) => {
|
|
462
|
+
const wrap = el("div", "field");
|
|
463
|
+
wrap.append(el("label", "field-label", t(labelKey)));
|
|
464
|
+
const input = el("input", "text-input");
|
|
465
|
+
input.type = "text";
|
|
466
|
+
input.placeholder = ph;
|
|
467
|
+
wrap.append(input);
|
|
468
|
+
b.append(wrap);
|
|
469
|
+
return input;
|
|
470
|
+
};
|
|
471
|
+
b.append(el("p", null, t(undo ? "docs.unshipBody" : "docs.shipBody")));
|
|
472
|
+
const first = undo ? field("docs.shipReason", t("docs.shipReasonPh")) : field("docs.shipWhere", t("docs.shipWherePh"));
|
|
473
|
+
const note = undo ? null : field("docs.shipNote", t("docs.shipNotePh"));
|
|
474
|
+
// THE EXACT COMMAND IS ALWAYS VISIBLE, and it updates as you type — so it is
|
|
475
|
+
// always typeable by hand instead. The Maintenance rule, unchanged.
|
|
476
|
+
const cmd = el("pre", "cmd", "");
|
|
477
|
+
const paint = () => {
|
|
478
|
+
const v = first.value.trim();
|
|
479
|
+
cmd.textContent = undo
|
|
480
|
+
? `orc doc unship ${s.slug} --reason "${v || "…"}"`
|
|
481
|
+
: `orc doc ship ${s.slug} --where "${v || "…"}"` + (note && note.value.trim() ? ` --note "${note.value.trim()}"` : "");
|
|
482
|
+
};
|
|
483
|
+
paint();
|
|
484
|
+
first.addEventListener("input", paint);
|
|
485
|
+
if (note) note.addEventListener("input", paint);
|
|
486
|
+
b.append(cmd);
|
|
487
|
+
b.append(el("div", "note", t(undo ? "docs.unshipNote" : "docs.shipModalNote")));
|
|
488
|
+
modal({
|
|
489
|
+
title: t(undo ? "docs.unshipTitle" : "docs.shipTitle"),
|
|
490
|
+
body: b,
|
|
491
|
+
actions: [
|
|
492
|
+
{ label: t("common.cancel"), onClick: (close) => close() },
|
|
493
|
+
{
|
|
494
|
+
label: t(undo ? "docs.unship" : "docs.shipGo"),
|
|
495
|
+
onClick: async (close) => {
|
|
496
|
+
const v = first.value.trim();
|
|
497
|
+
if (!v) {
|
|
498
|
+
toast(t(undo ? "docs.unshipNeedReason" : "docs.shipNeedWhere"), "bad");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
close();
|
|
502
|
+
const r = undo
|
|
503
|
+
? await post("/api/doc/unship", { slug: s.slug, reason: v })
|
|
504
|
+
: await post("/api/doc/ship", { slug: s.slug, where: v, note: note ? note.value.trim() : "" });
|
|
505
|
+
toast(r.ok ? t("docs.shipOk") : t("common.writeFail"), r.ok ? "ok" : "bad", r.output);
|
|
506
|
+
renderDocs(body);
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// --- 7. AUDIT. A clean audit is a one-line answer, not an empty card.
|
|
514
|
+
function docAuditCard(audit, body) {
|
|
515
|
+
const c = card(t("docs.audit"));
|
|
516
|
+
if (audit.ok === false) return c;
|
|
517
|
+
if (!(audit.findings || []).length) {
|
|
518
|
+
c.append(el("div", "note ok", t("docs.auditClean")));
|
|
519
|
+
return c;
|
|
520
|
+
}
|
|
521
|
+
for (const f of audit.findings) {
|
|
522
|
+
const box = el("div", "finding");
|
|
523
|
+
const left = el("div");
|
|
524
|
+
left.append(el("div", null, f.summary));
|
|
525
|
+
left.append(el("div", "note mono", f.id));
|
|
526
|
+
box.append(left);
|
|
527
|
+
// A caution routes to the panel that can CLEAR it; `panel: null` means no
|
|
528
|
+
// button at all, never a useless one.
|
|
529
|
+
if (f.panel && f.panel !== "docs") {
|
|
530
|
+
const go = el("button", "btn btn-ghost btn-sm", t("docs.auditGo"));
|
|
531
|
+
go.type = "button";
|
|
532
|
+
go.addEventListener("click", () => (location.hash = "#/" + f.panel));
|
|
533
|
+
box.append(go);
|
|
534
|
+
}
|
|
535
|
+
c.append(box);
|
|
536
|
+
c.append(el("pre", "cmd", f.fix));
|
|
537
|
+
}
|
|
538
|
+
return c;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// EVERY STATE ANSWERS "so what do I do now?" — and since v0.48.1 the ANSWER
|
|
542
|
+
// comes from `orc doc next`, rendered verbatim. There is no ordering logic
|
|
543
|
+
// here: the panel decides nothing about the pipeline. Same rule as the Flow
|
|
544
|
+
// stepper, and for the same reason.
|
|
545
|
+
function docNextAction(s, next, body) {
|
|
546
|
+
const wrap = el("div", "stack stack-sm");
|
|
547
|
+
if ((s.user_edited || []).length)
|
|
548
|
+
wrap.append(el("div", "note", t("docs.editedNote", { names: s.user_edited.map((x) => x.heading).join(" · ") })));
|
|
549
|
+
if ((s.open_sections || []).length)
|
|
550
|
+
wrap.append(el("div", "note warn", t("docs.openNote", { names: s.open_sections.map((x) => x.heading).join(" · ") })));
|
|
551
|
+
|
|
552
|
+
if (!next || next.ok === false) {
|
|
553
|
+
wrap.append(laneCommand(s.resume, t("docs.paidWhy")));
|
|
554
|
+
return wrap;
|
|
555
|
+
}
|
|
556
|
+
wrap.append(el("div", "note", `${next.phase} · ${next.why}`));
|
|
557
|
+
if (next.blocked_by) {
|
|
558
|
+
// Waiting on a HUMAN. Never a button, because there is nothing to press.
|
|
559
|
+
wrap.append(el("div", "note warn", t("docs.nextBlocked", { why: next.blocked_by })));
|
|
560
|
+
for (const alt of next.alternatives || []) wrap.append(el("pre", "cmd", alt));
|
|
561
|
+
return wrap;
|
|
562
|
+
}
|
|
563
|
+
// A FREE action gets a button; a PAID action gets a copy-able command. That
|
|
564
|
+
// line is the W2 rule, and it is visible here rather than remembered.
|
|
565
|
+
if (next.paid) wrap.append(laneCommand(next.command, t("docs.paidWhy")));
|
|
566
|
+
else {
|
|
567
|
+
wrap.append(el("pre", "cmd", next.command));
|
|
568
|
+
wrap.append(el("div", "note ok", t("docs.nextFree")));
|
|
569
|
+
}
|
|
570
|
+
if (s.state === "complete" || s.state === "shipped") {
|
|
571
|
+
wrap.append(el("pre", "cmd", `git add ${s.document}`));
|
|
572
|
+
wrap.append(el("div", "note", t("docs.challengeNote")));
|
|
573
|
+
wrap.append(el("pre", "cmd", `/orc-challenge ${s.document}`));
|
|
574
|
+
}
|
|
575
|
+
return wrap;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// The free actions. A refetch of a deterministic read is a BUTTON; so is
|
|
579
|
+
// `assemble`, which only concatenates files already on disk in an order the
|
|
580
|
+
// outline already fixed.
|
|
581
|
+
function docFreeActions(slug, body) {
|
|
582
|
+
const row = el("div", "row-actions");
|
|
583
|
+
const relint = el("button", "btn btn-ghost btn-sm", t("docs.reLint"));
|
|
584
|
+
relint.type = "button";
|
|
585
|
+
relint.addEventListener("click", () => renderDocs(body));
|
|
586
|
+
row.append(relint);
|
|
587
|
+
const asm = el("button", "btn btn-ghost btn-sm", t("docs.compile"));
|
|
588
|
+
asm.type = "button";
|
|
589
|
+
asm.addEventListener("click", () => {
|
|
590
|
+
const b = frag();
|
|
591
|
+
b.append(el("p", null, t("docs.compileBody")));
|
|
592
|
+
// The exact command is ALWAYS on screen, so it is always typeable by hand
|
|
593
|
+
// instead — the Maintenance rule, applied to the one free write here.
|
|
594
|
+
b.append(el("pre", "cmd", `orc doc compile ${slug}`));
|
|
595
|
+
b.append(el("div", "note", t("docs.compileNote")));
|
|
596
|
+
modal({
|
|
597
|
+
title: t("docs.compileTitle"),
|
|
598
|
+
body: b,
|
|
599
|
+
actions: [
|
|
600
|
+
{ label: t("common.cancel"), onClick: (close) => close() },
|
|
601
|
+
{
|
|
602
|
+
label: t("docs.compileGo"),
|
|
603
|
+
onClick: async (close) => {
|
|
604
|
+
close();
|
|
605
|
+
const r = await post("/api/doc/compile", { slug });
|
|
606
|
+
toast(r.ok ? t("docs.compileOk") : t("common.writeFail"), r.ok ? "ok" : "bad", r.output);
|
|
607
|
+
renderDocs(body);
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
],
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
row.append(asm);
|
|
614
|
+
return row;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// A v1 document is one file, and every change routes through it. Migrating is
|
|
618
|
+
// FREE and non-destructive — document.md is never deleted — so it is a button,
|
|
619
|
+
// with the command visible and the refusal case named.
|
|
620
|
+
function docMigrateCard(slug, body) {
|
|
621
|
+
const c = card(t("docs.migrateTitle"));
|
|
622
|
+
c.append(el("p", "note", t("docs.migrateBody")));
|
|
623
|
+
c.append(el("pre", "cmd", `orc doc migrate ${slug}`));
|
|
624
|
+
const row = el("div", "row-actions");
|
|
625
|
+
const b = el("button", "btn btn-sm", t("docs.migrateGo"));
|
|
626
|
+
b.type = "button";
|
|
627
|
+
b.addEventListener("click", async () => {
|
|
628
|
+
b.disabled = true;
|
|
629
|
+
const r = await post("/api/doc/migrate", { slug });
|
|
630
|
+
toast(r.ok ? t("docs.migrateOk") : t("common.writeFail"), r.ok ? "ok" : "bad", r.output);
|
|
631
|
+
renderDocs(body);
|
|
632
|
+
});
|
|
633
|
+
row.append(b);
|
|
634
|
+
c.append(row);
|
|
635
|
+
return c;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* THE RIBBON — each section is a segment whose width is proportional to its line
|
|
639
|
+
count, coloured by its state. In one glance: how long the document is, where
|
|
640
|
+
the weight sits, what is still open, what the user edited, and where the
|
|
641
|
+
findings are.
|
|
642
|
+
|
|
643
|
+
GEOMETRY IS SOLVED FROM THE BOX SIZE, never expressed as a fraction of the
|
|
644
|
+
container (the VAULT / ringRadii lesson): a segment is `lines × PX` with a
|
|
645
|
+
floor that keeps a 3-line section clickable, the canvas is the bounding box of
|
|
646
|
+
what was PLACED, and a document too long for the panel SCROLLS rather than
|
|
647
|
+
being squeezed into unreadable slivers. */
|
|
648
|
+
const DOC_RIBBON = { H: 34, MIN: 14, PX: 1.6, GAP: 2, PAD: 8, LABEL: 16 };
|
|
649
|
+
|
|
650
|
+
function docRibbon(sections, onPick) {
|
|
651
|
+
const wrap = el("div", "doc-ribbon-wrap");
|
|
652
|
+
const widths = sections.map((s) => Math.max(DOC_RIBBON.MIN, Math.round((s.lines || 1) * DOC_RIBBON.PX)));
|
|
653
|
+
const xs = [];
|
|
654
|
+
let x = DOC_RIBBON.PAD;
|
|
655
|
+
for (const w of widths) {
|
|
656
|
+
xs.push(x);
|
|
657
|
+
x += w + DOC_RIBBON.GAP;
|
|
658
|
+
}
|
|
659
|
+
const width = x - DOC_RIBBON.GAP + DOC_RIBBON.PAD;
|
|
660
|
+
const height = DOC_RIBBON.H + DOC_RIBBON.LABEL + 2 * DOC_RIBBON.PAD;
|
|
661
|
+
|
|
662
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
663
|
+
const svg = document.createElementNS(NS, "svg");
|
|
664
|
+
svg.setAttribute("class", "doc-ribbon");
|
|
665
|
+
// The canvas keeps its aspect: a stretched `preserveAspectRatio="none"` viewBox
|
|
666
|
+
// squashes every label and every stroke.
|
|
667
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
668
|
+
svg.setAttribute("width", String(width));
|
|
669
|
+
svg.setAttribute("height", String(height));
|
|
670
|
+
svg.setAttribute("role", "img");
|
|
671
|
+
svg.setAttribute("aria-label", t("docs.ribbonAria", { n: sections.length }));
|
|
672
|
+
|
|
673
|
+
const mk = (name, attrs, text) => {
|
|
674
|
+
const n = document.createElementNS(NS, name);
|
|
675
|
+
for (const k of Object.keys(attrs)) n.setAttribute(k, String(attrs[k]));
|
|
676
|
+
if (text !== undefined) n.textContent = text;
|
|
677
|
+
return n;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
sections.forEach((s, i) => {
|
|
681
|
+
const w = widths[i];
|
|
682
|
+
const g = mk("g", {
|
|
683
|
+
class: "doc-seg doc-seg-" + s.state.replace(/[^a-z-]/g, "") + (s.findings ? " has-findings" : ""),
|
|
684
|
+
style: `animation-delay:${i * 26}ms`,
|
|
685
|
+
tabindex: "0",
|
|
686
|
+
role: "button",
|
|
687
|
+
});
|
|
688
|
+
g.append(mk("rect", { x: xs[i], y: DOC_RIBBON.PAD, width: w, height: DOC_RIBBON.H, rx: 4, class: "doc-seg-box" }));
|
|
689
|
+
// A findings marker is STATIC. A blinking error is a reduced-motion hazard
|
|
690
|
+
// and reads as an urgency this panel has no right to imply.
|
|
691
|
+
if (s.findings)
|
|
692
|
+
g.append(mk("rect", { x: xs[i], y: DOC_RIBBON.PAD, width: w, height: 3, class: "doc-seg-mark" }));
|
|
693
|
+
if (w >= 22)
|
|
694
|
+
g.append(
|
|
695
|
+
mk(
|
|
696
|
+
"text",
|
|
697
|
+
{ x: xs[i] + w / 2, y: DOC_RIBBON.PAD + DOC_RIBBON.H + 12, class: "doc-seg-n", "text-anchor": "middle" },
|
|
698
|
+
String(i + 1)
|
|
699
|
+
)
|
|
700
|
+
);
|
|
701
|
+
// The tooltip repeats the CLI's own numbers and its own state word.
|
|
702
|
+
g.append(
|
|
703
|
+
mk("title", {}, `${s.heading} — ${s.state} · ${s.start}..${s.end} · ${s.lines} L${s.findings ? " · " + s.findings : ""}`)
|
|
704
|
+
);
|
|
705
|
+
const pick = () => onPick(s.id);
|
|
706
|
+
g.addEventListener("click", pick);
|
|
707
|
+
g.addEventListener("keydown", (e) => {
|
|
708
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
709
|
+
e.preventDefault();
|
|
710
|
+
pick();
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
svg.append(g);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
wrap.append(svg);
|
|
717
|
+
return wrap;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function docRibbonLegend() {
|
|
721
|
+
const row = el("div", "row-actions doc-legend");
|
|
722
|
+
for (const state of Object.keys(DOC_STATE_KIND)) {
|
|
723
|
+
const item = el("span", "doc-legend-item");
|
|
724
|
+
item.append(el("span", "doc-legend-swatch doc-seg-" + state.replace(/[^a-z-]/g, "")));
|
|
725
|
+
item.append(el("span", "note", state));
|
|
726
|
+
row.append(item);
|
|
727
|
+
}
|
|
728
|
+
return row;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Open one section row in the list, from a ribbon click. One at a time.
|
|
732
|
+
function docOpenSection(pane, id) {
|
|
733
|
+
const btn = pane.querySelector('[data-section="' + CSS.escape(id) + '"]');
|
|
734
|
+
if (!btn) return;
|
|
735
|
+
for (const other of pane.querySelectorAll(".doc-row.open")) if (other !== btn.parentElement) other.classList.remove("open");
|
|
736
|
+
btn.parentElement.classList.add("open");
|
|
737
|
+
btn.setAttribute("aria-expanded", "true");
|
|
738
|
+
btn.scrollIntoView({ block: "nearest" });
|
|
739
|
+
btn.focus();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function docSectionList(sections, slug, show) {
|
|
743
|
+
const list = el("div", "doc-rows");
|
|
744
|
+
const outline = new Map(((show && show.outline) || []).map((o) => [o.id, o]));
|
|
745
|
+
for (const s of sections) {
|
|
746
|
+
const row = el("div", "run-row doc-row");
|
|
747
|
+
const btn = el("button", "run-card");
|
|
748
|
+
btn.type = "button";
|
|
749
|
+
btn.dataset.section = s.id;
|
|
750
|
+
btn.setAttribute("aria-expanded", "false");
|
|
751
|
+
btn.append(el("span", "run-caret", "▸"));
|
|
752
|
+
btn.append(chip(s.state, DOC_STATE_KIND[s.state] || ""));
|
|
753
|
+
const mid = el("div", "run-mid");
|
|
754
|
+
mid.append(el("div", "run-slug", s.heading));
|
|
755
|
+
mid.append(el("div", "run-where mono", `${s.start}..${s.end}`));
|
|
756
|
+
btn.append(mid);
|
|
757
|
+
btn.append(el("div", "run-age", `${s.lines} L`));
|
|
758
|
+
if (s.findings) btn.append(chip(String(s.findings), "warn"));
|
|
759
|
+
|
|
760
|
+
const pane = el("div", "run-pane stack stack-sm");
|
|
761
|
+
const rows = [];
|
|
762
|
+
const o = outline.get(s.id);
|
|
763
|
+
if (o && o.purpose) rows.push([t("docs.field.purpose"), o.purpose]);
|
|
764
|
+
rows.push([t("docs.field.required"), o && o.required === false ? t("docs.optional") : t("docs.required")]);
|
|
765
|
+
rows.push([t("docs.field.hash"), String(s.hash || "").slice(0, 12)]);
|
|
766
|
+
if (s.cycle) rows.push([t("docs.field.cycle"), String(s.cycle)]);
|
|
767
|
+
if (s.renamed_from) rows.push([t("docs.field.renamedFrom"), s.renamed_from]);
|
|
768
|
+
pane.append(kvList(rows));
|
|
769
|
+
// REVEAL — the panel never shows a section's text until somebody asks for
|
|
770
|
+
// that one section. It is rendered as DOM through `renderMd`, never as HTML.
|
|
771
|
+
pane.append(docRevealBtn(slug, s.id, pane));
|
|
772
|
+
const inner = el("div", "run-body-inner");
|
|
773
|
+
inner.append(pane);
|
|
774
|
+
const fold = el("div", "run-body");
|
|
775
|
+
fold.append(inner);
|
|
776
|
+
|
|
777
|
+
btn.addEventListener("click", () => {
|
|
778
|
+
const open = row.classList.contains("open");
|
|
779
|
+
for (const other of list.querySelectorAll(".doc-row.open")) other.classList.remove("open");
|
|
780
|
+
row.classList.toggle("open", !open);
|
|
781
|
+
btn.setAttribute("aria-expanded", String(!open));
|
|
782
|
+
});
|
|
783
|
+
row.append(btn, fold);
|
|
784
|
+
list.append(row);
|
|
785
|
+
}
|
|
786
|
+
return list;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* --- the SECTION FILES (v0.49.0) ------------------------------------------
|
|
790
|
+
One row per section, and a NESTED row per stored sub-part. It derives
|
|
791
|
+
nothing: the state words, the paths, the line counts and the wave are all
|
|
792
|
+
`orc doc parts --json`'s, verbatim. A `planned` row KEEPS ITS SLOT — "not
|
|
793
|
+
written yet" is an answer, and a list that hides it cannot be designed. */
|
|
794
|
+
function docPartList(rows, dir) {
|
|
795
|
+
const list = el("div", "doc-rows");
|
|
796
|
+
for (const p of rows) {
|
|
797
|
+
const row = el("div", "doc-part");
|
|
798
|
+
const head = el("div", "doc-part-head");
|
|
799
|
+
head.append(chip(p.state, DOC_STATE_KIND[p.state] || ""));
|
|
800
|
+
const mid = el("div", "run-mid");
|
|
801
|
+
mid.append(el("div", "run-slug", p.heading));
|
|
802
|
+
mid.append(el("div", "run-where mono", p.files.length ? p.files[0] : t("docs.partNone")));
|
|
803
|
+
head.append(mid);
|
|
804
|
+
head.append(el("div", "run-age", p.exists ? `${p.lines} L` : "—"));
|
|
805
|
+
if (!p.required) head.append(chip(t("docs.optional"), ""));
|
|
806
|
+
if (!p.ordinal_ok) head.append(chip(t("docs.misnumbered"), "warn"));
|
|
807
|
+
if (p.findings) head.append(chip(String(p.findings), "warn"));
|
|
808
|
+
row.append(head);
|
|
809
|
+
for (const sub of p.subsections || []) {
|
|
810
|
+
const sr = el("div", "doc-subpart");
|
|
811
|
+
sr.append(el("span", "doc-subpart-mark", "└"));
|
|
812
|
+
sr.append(el("span", null, sub.heading));
|
|
813
|
+
sr.append(el("span", "run-where mono", sub.file || t("docs.partNone")));
|
|
814
|
+
sr.append(el("span", "run-age", sub.exists ? `${sub.lines} L` : "—"));
|
|
815
|
+
if (sub.changed) sr.append(chip(t("docs.subChanged"), "warn"));
|
|
816
|
+
row.append(sr);
|
|
817
|
+
}
|
|
818
|
+
list.append(row);
|
|
819
|
+
}
|
|
820
|
+
const foot = el("div", "note mono", dir || "");
|
|
821
|
+
list.append(foot);
|
|
822
|
+
return list;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* WHICH MODEL WRITES THIS DOCUMENT (v0.52.0, D9).
|
|
826
|
+
|
|
827
|
+
A document is the one artifact where the model choice is visible in the
|
|
828
|
+
OUTPUT, so this is a per-document decision and its default is `off`. The
|
|
829
|
+
panel offers the CLI's four values, renders the CLI's own resolution
|
|
830
|
+
sentence, and computes nothing: a document set to `both` while
|
|
831
|
+
`extra_roles` names neither role resolves to OFF, and that shadow is the
|
|
832
|
+
CLI's to announce. */
|
|
833
|
+
function docExtraCard(dx, slug, body) {
|
|
834
|
+
const c = card(t("docs.extra.title"));
|
|
835
|
+
const top = el("div", "row-actions");
|
|
836
|
+
top.append(chip(dx.extra, dx.extra === "off" ? "" : "warn"));
|
|
837
|
+
if (dx.stored) top.append(el("span", "note", t("docs.extra.stored")));
|
|
838
|
+
else top.append(el("span", "note", t("docs.extra.fromConfig")));
|
|
839
|
+
c.append(top);
|
|
840
|
+
c.append(el("div", "note", dx.why));
|
|
841
|
+
// The disclosure the fixed-lane rule demands: this lane pins its agents, so
|
|
842
|
+
// it resolves the writer's BAND at both edges and requires them to agree.
|
|
843
|
+
if (dx.edges && dx.edges.band)
|
|
844
|
+
c.append(
|
|
845
|
+
el("div", "note mono", t("docs.extra.edges", { band: dx.edges.band, edges: (dx.edges.edges || []).join(","), agree: String(dx.edges.agree) }))
|
|
846
|
+
);
|
|
847
|
+
c.append(el("div", "note", t("docs.extra.order", { order: (dx.resolve_order || []).join(" > ") })));
|
|
848
|
+
|
|
849
|
+
const pick = el("select", "setting-control");
|
|
850
|
+
for (const o of dx.options || []) {
|
|
851
|
+
const opt = el("option", null, o);
|
|
852
|
+
opt.value = o;
|
|
853
|
+
pick.append(opt);
|
|
854
|
+
}
|
|
855
|
+
pick.value = dx.stored || dx.extra;
|
|
856
|
+
const apply = el("button", "btn btn-sm btn-primary", t("edits.apply"));
|
|
857
|
+
apply.type = "button";
|
|
858
|
+
apply.addEventListener("click", async () => {
|
|
859
|
+
apply.disabled = true;
|
|
860
|
+
setBusy(true);
|
|
861
|
+
try {
|
|
862
|
+
await post("/api/doc/extra/set", { slug, mode: pick.value });
|
|
863
|
+
renderDocs(body);
|
|
864
|
+
} catch (e) {
|
|
865
|
+
c.append(failBox(e));
|
|
866
|
+
} finally {
|
|
867
|
+
apply.disabled = false;
|
|
868
|
+
setBusy(false);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
const row = el("div", "row-actions");
|
|
872
|
+
row.append(pick, apply);
|
|
873
|
+
c.append(row);
|
|
874
|
+
return c;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// `K of N` comes from the CLI, which DERIVES it by counting waves whose
|
|
878
|
+
// sections are all hash-confirmed. The panel never counts anything.
|
|
879
|
+
function docWaveStrip(wave, s) {
|
|
880
|
+
const wrap = el("div", "row-actions doc-wave-strip");
|
|
881
|
+
wrap.append(chip(t("docs.waveOf", { k: wave.done, n: wave.total }), wave.done >= wave.total ? "ok" : "info"));
|
|
882
|
+
if (s && s.write_mode) wrap.append(chip(s.write_mode, ""));
|
|
883
|
+
// v0.52.0 — a document whose sections are written by something other than
|
|
884
|
+
// Claude says so on the row. `off` gets no chip: it is the default and the
|
|
885
|
+
// quiet state.
|
|
886
|
+
if (s && s.extra && s.extra !== "off") wrap.append(chip("extra " + "·" + " " + s.extra, "warn"));
|
|
887
|
+
wrap.append(el("span", "note", s ? s.where : ""));
|
|
888
|
+
return wrap;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// A FREE action gets a button, and the exact command is always on screen so it
|
|
892
|
+
// stays typeable by hand. `--partial` is offered only when something really is
|
|
893
|
+
// missing, because a partial compile of a finished document is just a compile.
|
|
894
|
+
function docCompileBtn(slug, body, parts) {
|
|
895
|
+
const row = el("div", "row-actions");
|
|
896
|
+
const partial = (parts.missing || []).length > 0;
|
|
897
|
+
const b = el("button", "btn btn-ghost btn-sm", t("docs.compile"));
|
|
898
|
+
b.type = "button";
|
|
899
|
+
b.addEventListener("click", () => {
|
|
900
|
+
const cmd = `orc doc compile ${slug}${partial ? " --partial" : ""}`;
|
|
901
|
+
const c = frag();
|
|
902
|
+
c.append(el("p", null, partial ? t("docs.compilePartialBody", { n: parts.missing.length }) : t("docs.compileBody")));
|
|
903
|
+
c.append(el("pre", "cmd", cmd));
|
|
904
|
+
c.append(el("div", "note", t("docs.compileNote")));
|
|
905
|
+
modal({
|
|
906
|
+
title: t("docs.compileTitle"),
|
|
907
|
+
body: c,
|
|
908
|
+
actions: [
|
|
909
|
+
{ label: t("common.cancel"), onClick: (close) => close() },
|
|
910
|
+
{
|
|
911
|
+
label: t("docs.compileGo"),
|
|
912
|
+
onClick: async (close) => {
|
|
913
|
+
close();
|
|
914
|
+
const r = await post("/api/doc/compile", { slug, partial });
|
|
915
|
+
toast(r.ok ? t("docs.compileOk") : t("common.writeFail"), r.ok ? "ok" : "bad", r.output);
|
|
916
|
+
renderDocs(body);
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
],
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
row.append(b);
|
|
923
|
+
return row;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function docRevealBtn(slug, id, pane) {
|
|
927
|
+
const wrap = el("div", "stack stack-sm");
|
|
928
|
+
const b = el("button", "btn btn-ghost btn-sm", t("docs.reveal"));
|
|
929
|
+
b.type = "button";
|
|
930
|
+
b.addEventListener("click", async () => {
|
|
931
|
+
b.disabled = true;
|
|
932
|
+
let r = null;
|
|
933
|
+
try {
|
|
934
|
+
r = (await read("/api/doc/section?slug=" + encodeURIComponent(slug) + "§ion=" + encodeURIComponent(id))).data;
|
|
935
|
+
} catch (_) {}
|
|
936
|
+
b.remove();
|
|
937
|
+
if (!r || !r.text) {
|
|
938
|
+
wrap.append(el("div", "note", t("docs.revealFail")));
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const box = el("div", "doc-reveal");
|
|
942
|
+
box.append(renderMd(r.text));
|
|
943
|
+
wrap.append(box);
|
|
944
|
+
wrap.append(el("div", "note", t("docs.revealNote")));
|
|
945
|
+
});
|
|
946
|
+
wrap.append(b);
|
|
947
|
+
return wrap;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// The health card. STRAIGHT FROM `orc doc lint --json`: the CLI's rule names,
|
|
951
|
+
// the CLI's counts, the CLI's own honesty lines. Never a friendlier synonym for
|
|
952
|
+
// a rule, and the two honesty lines are not optional chrome.
|
|
953
|
+
|
|
954
|
+
/* ── v0.49.2 — HOUSE RULES, THE RUN MAP, AND WHAT IT COST ───────────────────
|
|
955
|
+
|
|
956
|
+
Every one of these renders what `bin/cli.js --json` computed and decides
|
|
957
|
+
nothing: not a priority, not an order, not a wave shape, not a number. The
|
|
958
|
+
boundary sentence is the CLI's own words, and it is ALWAYS shown — not on
|
|
959
|
+
hover — because a rule set nobody can see the limits of is a rule set people
|
|
960
|
+
argue with instead of using. */
|
|
961
|
+
|
|
962
|
+
const DOC_RULE_KIND = { P0: "bad", P1: "warn", P2: "" };
|
|
963
|
+
|
|
964
|
+
// The PROJECT ledger, at the top of the panel.
|
|
965
|
+
//
|
|
966
|
+
// v0.49.5 — IT IS A TEXT CONFIG, SO IT IS EDITED AS TEXT. The first cut was a
|
|
967
|
+
// form: a priority dropdown, a one-line input, an Add button, and a row per
|
|
968
|
+
// rule with Enable / Disable / Remove beside it. Filing a standing instruction
|
|
969
|
+
// as numbered rows is work the tool invented for itself, and no real P0 fits on
|
|
970
|
+
// one line — so the panel now shows the FILE, in one textarea, with the P0 / P1
|
|
971
|
+
// / P2 headings already in it. Type as much as you want under each one.
|
|
972
|
+
//
|
|
973
|
+
// The panel still decides nothing: the text comes from `orc doc rules --json`
|
|
974
|
+
// and goes back through `orc doc rules set-all`, which is the only writer. The
|
|
975
|
+
// v0.44.1 rule holds unchanged — nothing is written until Apply, the pending
|
|
976
|
+
// edit is NAMED, Discard renders only while dirty, and a refused write is
|
|
977
|
+
// reported by the CLI's own words.
|
|
978
|
+
async function docRulesCard(body) {
|
|
979
|
+
const c = card(t("docs.rules.title"));
|
|
980
|
+
let d = null;
|
|
981
|
+
try {
|
|
982
|
+
d = (await read("/api/doc/rules")).data;
|
|
983
|
+
} catch (_) {}
|
|
984
|
+
if (!d) {
|
|
985
|
+
c.append(el("div", "note", t("docs.rules.unavailable")));
|
|
986
|
+
return c;
|
|
987
|
+
}
|
|
988
|
+
const edits = editSet(() => bar.paint());
|
|
989
|
+
const original = String(d.text || d.template || "");
|
|
990
|
+
|
|
991
|
+
const head = el("div", "row-actions");
|
|
992
|
+
// The CLI's own summary line, never a friendlier synonym.
|
|
993
|
+
head.append(chip(d.line, d.empty ? "" : "ok"));
|
|
994
|
+
for (const pr of d.priorities || ["P0", "P1", "P2"]) {
|
|
995
|
+
const n = (d.counts || {})[pr] || 0;
|
|
996
|
+
if (n) head.append(chip(pr + " " + n, DOC_RULE_KIND[pr] || ""));
|
|
997
|
+
}
|
|
998
|
+
c.append(head);
|
|
999
|
+
|
|
1000
|
+
// A migration is never silent, and it says what it did NOT carry over.
|
|
1001
|
+
if (d.migrated)
|
|
1002
|
+
c.append(
|
|
1003
|
+
el(
|
|
1004
|
+
"div",
|
|
1005
|
+
"note warn",
|
|
1006
|
+
t("docs.rules.migrated", { from: d.migrated.from, n: d.migrated.dropped_disabled || 0 })
|
|
1007
|
+
)
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
c.append(el("div", "note", t("docs.rules.howto")));
|
|
1011
|
+
|
|
1012
|
+
const ta = el("textarea", "rule-editor");
|
|
1013
|
+
ta.value = original;
|
|
1014
|
+
ta.spellcheck = false;
|
|
1015
|
+
ta.rows = 18;
|
|
1016
|
+
ta.setAttribute("aria-label", t("docs.rules.title"));
|
|
1017
|
+
ta.addEventListener("input", () => {
|
|
1018
|
+
if (ta.value === original) edits.drop("doc-house-rules.md");
|
|
1019
|
+
else edits.action("doc-house-rules.md", "/api/doc/rules/setAll", { text: ta.value }, t("docs.rules.staged"));
|
|
1020
|
+
});
|
|
1021
|
+
c.append(ta);
|
|
1022
|
+
|
|
1023
|
+
// The file path is CLI data. A user who would rather open it in their own
|
|
1024
|
+
// editor should not have to go hunting for it.
|
|
1025
|
+
c.append(el("div", "note rule-path", d.file));
|
|
1026
|
+
|
|
1027
|
+
// ALWAYS shown, never on hover.
|
|
1028
|
+
c.append(el("div", "note rule-boundary", d.boundary));
|
|
1029
|
+
|
|
1030
|
+
const bar = editBar(edits, {
|
|
1031
|
+
onApply: async (btn) => {
|
|
1032
|
+
await applyActions(edits, btn);
|
|
1033
|
+
edits.clear();
|
|
1034
|
+
renderDocs(body);
|
|
1035
|
+
},
|
|
1036
|
+
onReset: () => renderDocs(body),
|
|
1037
|
+
onCancel: () => {
|
|
1038
|
+
edits.clear();
|
|
1039
|
+
renderDocs(body);
|
|
1040
|
+
},
|
|
1041
|
+
resetLabel: t("docs.rules.refresh"),
|
|
1042
|
+
});
|
|
1043
|
+
c.append(bar);
|
|
1044
|
+
return c;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// The text FROZEN into ONE document, and every priority block that moved since.
|
|
1048
|
+
// A drift NAMES the block and shows what the project says now — coverage-
|
|
1049
|
+
// relative, never a "rules changed" boolean.
|
|
1050
|
+
function docFrozenRulesCard(rules, slug, body) {
|
|
1051
|
+
const c = card(t("docs.rules.frozenTitle"));
|
|
1052
|
+
if (!rules.ok) {
|
|
1053
|
+
c.append(el("div", "note", t("docs.rules.unavailable")));
|
|
1054
|
+
return c;
|
|
1055
|
+
}
|
|
1056
|
+
const drift = rules.drift || { changed: [], drifted: false };
|
|
1057
|
+
const priorities = rules.priorities || ["P0", "P1", "P2"];
|
|
1058
|
+
const frozen = rules.frozen || {};
|
|
1059
|
+
const row = el("div", "row-actions");
|
|
1060
|
+
row.append(chip(rules.line, drift.drifted ? "warn" : "ok"));
|
|
1061
|
+
if (drift.drifted) {
|
|
1062
|
+
row.append(el("span", "note", tn(drift.changed.length, "docs.rules.driftedN")));
|
|
1063
|
+
const sync = el("button", "btn btn-sm", t("docs.rules.sync"));
|
|
1064
|
+
sync.type = "button";
|
|
1065
|
+
sync.addEventListener("click", () => docSyncRulesModal(rules, slug, body));
|
|
1066
|
+
row.append(sync);
|
|
1067
|
+
} else {
|
|
1068
|
+
row.append(el("span", "note", t("docs.rules.frozenClean")));
|
|
1069
|
+
}
|
|
1070
|
+
c.append(row);
|
|
1071
|
+
|
|
1072
|
+
for (const pr of priorities) {
|
|
1073
|
+
if (!frozen[pr]) continue;
|
|
1074
|
+
const grp = el("div", "rule-group");
|
|
1075
|
+
grp.append(el("div", "rule-group-head", pr));
|
|
1076
|
+
// VERBATIM. The user's own words, never re-wrapped into ORC's voice.
|
|
1077
|
+
grp.append(el("pre", "rule-block", frozen[pr]));
|
|
1078
|
+
c.append(grp);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// A drift that names nothing is a drift nobody can act on.
|
|
1082
|
+
if (drift.drifted) {
|
|
1083
|
+
const dl = el("div", "rule-drift");
|
|
1084
|
+
for (const ch of drift.changed) {
|
|
1085
|
+
dl.append(el("div", "rule-drift-row", ch.priority));
|
|
1086
|
+
dl.append(el("pre", "rule-block rule-block-new", ch.to || t("docs.rules.nowEmpty")));
|
|
1087
|
+
}
|
|
1088
|
+
c.append(dl);
|
|
1089
|
+
}
|
|
1090
|
+
c.append(el("div", "note", rules.boundary));
|
|
1091
|
+
return c;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// A re-freeze is FREE, and it re-writes NOTHING. The confirmation names the
|
|
1095
|
+
// sections that predate the change, because whether any of them needs redoing
|
|
1096
|
+
// is the user's call and not ORC's.
|
|
1097
|
+
function docSyncRulesModal(rules, slug, body) {
|
|
1098
|
+
const b = frag();
|
|
1099
|
+
b.append(el("p", null, t("docs.rules.syncBody")));
|
|
1100
|
+
b.append(el("div", "note", t("docs.rules.syncNever")));
|
|
1101
|
+
b.append(el("pre", "cmd", `orc doc rules ${slug} --sync`));
|
|
1102
|
+
modal({
|
|
1103
|
+
title: t("docs.rules.sync"),
|
|
1104
|
+
body: b,
|
|
1105
|
+
actions: {
|
|
1106
|
+
[t("common.cancel")]: null,
|
|
1107
|
+
[t("docs.rules.sync")]: async () => {
|
|
1108
|
+
const r = await post("/api/doc/rules/sync", { slug });
|
|
1109
|
+
toast(r.ok ? t("docs.rules.synced") : t("docs.rules.syncFailed"), r.ok ? "ok" : "bad", r.output);
|
|
1110
|
+
renderDocs(body);
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// THE RUN MAP. Waves, agents, stops, and the four token kinds NEVER blended —
|
|
1117
|
+
// `cache_read` stays visibly separate, which is why the bar is stacked.
|
|
1118
|
+
function docForecastCard(fc, slug, onRecompute) {
|
|
1119
|
+
const recompute = el("div", "row-actions");
|
|
1120
|
+
// A FREE action gets a button. Nothing in this card is paid, so nothing in it
|
|
1121
|
+
// is a copy-able command.
|
|
1122
|
+
const btn = el("button", "btn btn-sm btn-ghost", t("docs.forecast.recompute"));
|
|
1123
|
+
btn.type = "button";
|
|
1124
|
+
btn.addEventListener("click", () => onRecompute());
|
|
1125
|
+
recompute.append(btn);
|
|
1126
|
+
const c = card(t("docs.forecast.title"), recompute);
|
|
1127
|
+
|
|
1128
|
+
if (!fc.ok) {
|
|
1129
|
+
// A refusal is an ANSWER and it keeps its slot. `no-history` is the CLI
|
|
1130
|
+
// saying it will not invent numbers.
|
|
1131
|
+
c.append(el("div", "note", fc.hint || t("docs.forecast.none")));
|
|
1132
|
+
c.append(laneCommand(`orc doc forecast ${slug} --naive`, t("docs.forecast.naiveWhy")));
|
|
1133
|
+
return c;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const head = el("div", "row-actions");
|
|
1137
|
+
head.append(chip(tn((fc.waves || []).length, "docs.forecast.waveN"), "info"));
|
|
1138
|
+
head.append(chip(tn(fc.stops, "docs.forecast.stopN"), fc.stops > 1 ? "warn" : ""));
|
|
1139
|
+
if (fc.write_mode) head.append(chip(fc.write_mode, "info"));
|
|
1140
|
+
c.append(head);
|
|
1141
|
+
|
|
1142
|
+
const tbl = el("div", "fc-table");
|
|
1143
|
+
for (const w of fc.waves || []) {
|
|
1144
|
+
const r = el("div", "fc-row");
|
|
1145
|
+
r.append(el("span", "fc-wave", "wave " + w.n));
|
|
1146
|
+
r.append(el("span", "fc-secs", (w.sections || []).join(" + ")));
|
|
1147
|
+
r.append(el("span", "fc-lines", "~" + w.budget_lines + "L"));
|
|
1148
|
+
r.append(el("span", "fc-agents", tn(w.agents, "docs.forecast.agentN")));
|
|
1149
|
+
tbl.append(r);
|
|
1150
|
+
}
|
|
1151
|
+
c.append(tbl);
|
|
1152
|
+
|
|
1153
|
+
c.append(docTokenBar(fc.tokens && fc.tokens.p50));
|
|
1154
|
+
c.append(
|
|
1155
|
+
el(
|
|
1156
|
+
"div",
|
|
1157
|
+
"note",
|
|
1158
|
+
t("docs.forecast.range", {
|
|
1159
|
+
p50: fc.weighted ? kNum(fc.weighted.p50) : "—",
|
|
1160
|
+
p90: fc.weighted ? kNum(fc.weighted.p90) : "—",
|
|
1161
|
+
usd: fc.usd && fc.usd.p50 !== null ? "$" + fc.usd.p50.toFixed(2) : "—",
|
|
1162
|
+
})
|
|
1163
|
+
)
|
|
1164
|
+
);
|
|
1165
|
+
c.append(el("div", "note", t("docs.forecast.samples", { write: fc.samples.write, check: fc.samples.check, min: fc.min_samples })));
|
|
1166
|
+
// NOT optional chrome.
|
|
1167
|
+
if ((fc.low_confidence_roles || []).length)
|
|
1168
|
+
c.append(el("div", "note bad", t("docs.forecast.lowConfidence", { roles: fc.low_confidence_roles.join(", ") })));
|
|
1169
|
+
if (fc.naive) c.append(el("div", "note bad", t("docs.forecast.naive")));
|
|
1170
|
+
if (fc.price_table && fc.price_table.stale)
|
|
1171
|
+
c.append(el("div", "note bad", t("docs.forecast.stalePrice", { as_of: fc.price_table.as_of, days: fc.price_table.age_days })));
|
|
1172
|
+
if (fc.quota && !fc.quota.available) c.append(el("div", "note", fc.quota.reason));
|
|
1173
|
+
return c;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// WHAT IT COST, joined across every session. A section nothing joins reads an
|
|
1177
|
+
// em dash and KEEPS ITS SLOT — an unknown reported as a number is worse than an
|
|
1178
|
+
// unknown reported as unknown.
|
|
1179
|
+
function docCostCard(cost) {
|
|
1180
|
+
const c = card(t("docs.cost.title"));
|
|
1181
|
+
if (!cost.ok) {
|
|
1182
|
+
c.append(el("div", "note", cost.hint || t("docs.cost.none")));
|
|
1183
|
+
return c;
|
|
1184
|
+
}
|
|
1185
|
+
const head = el("div", "row-actions");
|
|
1186
|
+
head.append(chip(tn((cost.runs || []).length, "docs.cost.runN"), "info"));
|
|
1187
|
+
head.append(chip(t("docs.cost.joined", { joined: cost.joined, total: cost.dispatches }), cost.joined < cost.dispatches ? "warn" : "ok"));
|
|
1188
|
+
c.append(head);
|
|
1189
|
+
|
|
1190
|
+
c.append(docTokenBar(cost.total && cost.total.tokens));
|
|
1191
|
+
c.append(
|
|
1192
|
+
el(
|
|
1193
|
+
"div",
|
|
1194
|
+
"note",
|
|
1195
|
+
t("docs.cost.total", {
|
|
1196
|
+
weighted: kNum(cost.total.weighted),
|
|
1197
|
+
raw: kNum(cost.total.raw),
|
|
1198
|
+
usd: cost.total.usd === null ? "—" : "$" + cost.total.usd.toFixed(2),
|
|
1199
|
+
})
|
|
1200
|
+
)
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
const tbl = el("div", "fc-table");
|
|
1204
|
+
for (const s of cost.by_section || []) {
|
|
1205
|
+
const r = el("div", "fc-row" + (s.joined ? "" : " fc-row-unknown"));
|
|
1206
|
+
r.append(el("span", "fc-wave", s.id));
|
|
1207
|
+
r.append(el("span", "fc-secs", s.heading));
|
|
1208
|
+
r.append(el("span", "fc-lines", String(s.dispatches)));
|
|
1209
|
+
r.append(el("span", "fc-agents", s.joined ? kNum(s.weighted) : "—"));
|
|
1210
|
+
tbl.append(r);
|
|
1211
|
+
}
|
|
1212
|
+
c.append(tbl);
|
|
1213
|
+
|
|
1214
|
+
// ALWAYS printed, including when zero.
|
|
1215
|
+
c.append(
|
|
1216
|
+
el("div", "note", t("docs.cost.unattributed", { blocks: cost.unattributed.blocks }))
|
|
1217
|
+
);
|
|
1218
|
+
for (const h of cost.honesty || []) c.append(el("div", "note", h));
|
|
1219
|
+
return c;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// The four token kinds, stacked and never blended. `cache_read` is usually the
|
|
1223
|
+
// largest count at ~0.1x the price, which is exactly why it stays its own band
|
|
1224
|
+
// instead of being folded into one number.
|
|
1225
|
+
const DOC_VEC_KINDS = ["input", "cache_write", "cache_read", "output"];
|
|
1226
|
+
function docTokenBar(vec) {
|
|
1227
|
+
const box = el("div", "tok-bar-wrap");
|
|
1228
|
+
if (!vec) {
|
|
1229
|
+
box.append(el("div", "note", "—"));
|
|
1230
|
+
return box;
|
|
1231
|
+
}
|
|
1232
|
+
const total = DOC_VEC_KINDS.reduce((a, k) => a + (vec[k] || 0), 0);
|
|
1233
|
+
const bar = el("div", "tok-bar");
|
|
1234
|
+
for (const k of DOC_VEC_KINDS) {
|
|
1235
|
+
const seg = el("div", "tok-seg tok-" + k.replace("_", "-"));
|
|
1236
|
+
// A CUSTOM PROPERTY, not an inline width: the panel's CSP is
|
|
1237
|
+
// `style-src 'self'` and a style attribute is blocked outright. Same
|
|
1238
|
+
// technique as the challenge convergence bar.
|
|
1239
|
+
seg.style.setProperty("--w", total ? (((vec[k] || 0) / total) * 100).toFixed(2) + "%" : "0%");
|
|
1240
|
+
seg.title = k + " " + kNum(vec[k] || 0);
|
|
1241
|
+
bar.append(seg);
|
|
1242
|
+
}
|
|
1243
|
+
box.append(bar);
|
|
1244
|
+
const legend = el("div", "tok-legend");
|
|
1245
|
+
for (const k of DOC_VEC_KINDS) {
|
|
1246
|
+
const item = el("span", "tok-legend-item");
|
|
1247
|
+
item.append(el("span", "tok-dot tok-" + k.replace("_", "-")));
|
|
1248
|
+
// The KIND NAMES are the CLI's own field names — never translated.
|
|
1249
|
+
item.append(document.createTextNode(k + " " + kNum(vec[k] || 0)));
|
|
1250
|
+
legend.append(item);
|
|
1251
|
+
}
|
|
1252
|
+
box.append(legend);
|
|
1253
|
+
return box;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Thousands, the way the CLI's own `kTok` prints them. The panel does not do
|
|
1257
|
+
// arithmetic on a token count — it only shortens one for display.
|
|
1258
|
+
function kNum(n) {
|
|
1259
|
+
const v = Number(n) || 0;
|
|
1260
|
+
if (v >= 1_000_000) return (v / 1_000_000).toFixed(1) + "M";
|
|
1261
|
+
if (v >= 1000) return Math.round(v / 1000) + "k";
|
|
1262
|
+
return String(v);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function docHealth(lint) {
|
|
1266
|
+
const wrap = el("div", "stack stack-sm");
|
|
1267
|
+
const chips = el("div", "row-actions");
|
|
1268
|
+
chips.append(chip(tn(lint.errors, "docs.errorN"), lint.errors ? "bad" : "ok"));
|
|
1269
|
+
chips.append(chip(tn(lint.warnings, "docs.warnN"), lint.warnings ? "warn" : ""));
|
|
1270
|
+
chips.append(chip(lint.target_label, "info"));
|
|
1271
|
+
chips.append(chip(`max H${lint.max_heading}`, ""));
|
|
1272
|
+
chips.append(chip("front matter: " + lint.front_matter, ""));
|
|
1273
|
+
wrap.append(chips);
|
|
1274
|
+
|
|
1275
|
+
const r = lint.readability || {};
|
|
1276
|
+
wrap.append(
|
|
1277
|
+
kvList([
|
|
1278
|
+
[t("docs.read.avg"), `${r.avg_sentence_words} / ${r.avg_bar}`],
|
|
1279
|
+
[t("docs.read.longest"), r.longest_sentence_line ? `${r.longest_sentence_words} → L${r.longest_sentence_line}` : "—"],
|
|
1280
|
+
[t("docs.read.longWords"), `${r.long_word_pct}%`],
|
|
1281
|
+
[t("docs.read.passive"), String(r.passive_constructions)],
|
|
1282
|
+
[t("docs.read.acronyms"), (r.undefined_acronyms || []).map((a) => a.acronym).join(", ")],
|
|
1283
|
+
])
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
// Findings grouped by the CLI's rule name, as bars. A rule keeps its slot at
|
|
1287
|
+
// zero only when it fired at least once — a table of every rule that did not
|
|
1288
|
+
// fire is noise, but a rule that fired and is not shown is a lie.
|
|
1289
|
+
const byRule = new Map();
|
|
1290
|
+
for (const f of lint.findings || []) byRule.set(f.rule, (byRule.get(f.rule) || 0) + 1);
|
|
1291
|
+
if (byRule.size) {
|
|
1292
|
+
const max = Math.max(...byRule.values());
|
|
1293
|
+
const bars = el("div", "doc-bars");
|
|
1294
|
+
for (const [rule, n] of [...byRule.entries()].sort((a, b) => b[1] - a[1])) {
|
|
1295
|
+
const row = el("div", "doc-bar-row");
|
|
1296
|
+
row.append(el("span", "doc-bar-label mono", rule));
|
|
1297
|
+
const bar = el("div", "doc-bar");
|
|
1298
|
+
const seg = el("div", "doc-bar-seg");
|
|
1299
|
+
seg.style.setProperty("--w", ((n / max) * 100).toFixed(2) + "%");
|
|
1300
|
+
bar.append(seg);
|
|
1301
|
+
row.append(bar);
|
|
1302
|
+
row.append(el("span", "doc-bar-n", String(n)));
|
|
1303
|
+
bars.append(row);
|
|
1304
|
+
}
|
|
1305
|
+
wrap.append(bars);
|
|
1306
|
+
}
|
|
1307
|
+
if (lint.import_note) wrap.append(el("div", "note warn", lint.import_note));
|
|
1308
|
+
for (const line of lint.honesty || []) wrap.append(el("div", "note", line));
|
|
1309
|
+
return wrap;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/* THE WAVE VISUALISER — the part of ORC that has never been drawn. `orc doc plan
|
|
1313
|
+
--json` gives the exact batching, so the panel can show it and decide nothing
|
|
1314
|
+
about it: the agent name, the sections, the budget and the wave number are all
|
|
1315
|
+
the CLI's. */
|
|
1316
|
+
function docWaves(plan) {
|
|
1317
|
+
const wrap = el("div", "doc-waves");
|
|
1318
|
+
plan.waves.forEach((w, wi) => {
|
|
1319
|
+
const row = el("div", "doc-wave");
|
|
1320
|
+
row.style.setProperty("--i", String(wi));
|
|
1321
|
+
row.append(el("div", "doc-wave-n", t("docs.waveN", { n: w.n })));
|
|
1322
|
+
const cards = el("div", "doc-wave-cards");
|
|
1323
|
+
for (const a of w.agents) {
|
|
1324
|
+
const c = el("div", "doc-agent" + (a.oversized ? " oversized" : ""));
|
|
1325
|
+
c.append(el("div", "doc-agent-name mono", a.agent));
|
|
1326
|
+
c.append(el("div", "doc-agent-secs", a.headings.join(" + ")));
|
|
1327
|
+
const meta = el("div", "row-actions");
|
|
1328
|
+
meta.append(chip(`${a.budget_lines} L`, ""));
|
|
1329
|
+
if (a.range) meta.append(chip(`${a.range[0]}..${a.range[1]}`, "info"));
|
|
1330
|
+
if (a.oversized) meta.append(chip(t("docs.oversizedChip"), "bad"));
|
|
1331
|
+
c.append(meta);
|
|
1332
|
+
cards.append(c);
|
|
1333
|
+
}
|
|
1334
|
+
row.append(cards);
|
|
1335
|
+
wrap.append(row);
|
|
1336
|
+
});
|
|
1337
|
+
return wrap;
|
|
1338
|
+
}
|