coding-agent-harness 1.0.2 → 1.0.5
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 +32 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +244 -87
- package/README.zh-CN.md +77 -35
- package/SKILL.md +32 -24
- package/docs-release/README.md +9 -5
- package/docs-release/architecture/overview.md +17 -5
- package/docs-release/architecture/overview.zh-CN.md +9 -5
- package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +39 -15
- package/docs-release/guides/agent-installation.md +43 -16
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
- package/docs-release/guides/document-audience-and-surfaces.md +3 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
- package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
- package/docs-release/guides/migration-playbook.en-US.md +14 -15
- package/docs-release/guides/migration-playbook.md +14 -15
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
- package/docs-release/guides/parent-control-repository-pattern.md +7 -5
- package/docs-release/guides/preset-development.md +238 -0
- package/docs-release/guides/repository-operating-models.en-US.md +5 -4
- package/docs-release/guides/repository-operating-models.md +5 -4
- package/docs-release/guides/task-state-machine.en-US.md +224 -0
- package/docs-release/guides/task-state-machine.md +231 -0
- package/docs-release/intl/en-US.md +1 -1
- package/docs-release/intl/zh-CN.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +10 -4
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +2 -2
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +6 -7
- package/references/harness-ledger.md +53 -96
- package/references/lessons-governance.md +88 -93
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +12 -6
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +11 -2
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/walkthrough-closeout.md +7 -7
- package/scripts/check-harness.mjs +40 -301
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +126 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +328 -0
- package/scripts/harness.mjs +59 -260
- package/scripts/lib/capability-registry.mjs +82 -28
- package/scripts/lib/check-module-parallel.mjs +230 -0
- package/scripts/lib/check-profiles.mjs +90 -228
- package/scripts/lib/check-task-contracts.mjs +55 -0
- package/scripts/lib/core-shared.mjs +65 -2
- package/scripts/lib/dashboard-data.mjs +155 -24
- package/scripts/lib/dashboard-workbench.mjs +131 -12
- package/scripts/lib/dashboard-writer.mjs +20 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +611 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +6 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +494 -0
- package/scripts/lib/preset-registry.mjs +776 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +105 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-audit-metadata.mjs +385 -0
- package/scripts/lib/task-audit-migration.mjs +350 -0
- package/scripts/lib/task-completion-consistency.mjs +26 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
- package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
- package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
- package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +338 -477
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +455 -0
- package/scripts/lib/task-scanner.mjs +193 -372
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +24 -18
- package/templates/dashboard/assets/app-src/00-state.js +13 -0
- package/templates/dashboard/assets/app-src/10-router.js +5 -1
- package/templates/dashboard/assets/app-src/20-overview.js +18 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
- package/templates/dashboard/assets/app-src/45-review.js +241 -22
- package/templates/dashboard/assets/app-src/50-migration.js +24 -10
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
- package/templates/dashboard/assets/app.css +1501 -376
- package/templates/dashboard/assets/app.css.manifest.json +10 -0
- package/templates/dashboard/assets/app.js +1240 -101
- package/templates/dashboard/assets/app.manifest.json +2 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +263 -23
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +41 -0
- package/templates/planning/task_plan.md +5 -21
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +31 -3
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +7 -6
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +25 -19
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +18 -6
- package/templates-zh-CN/planning/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +41 -1
- package/templates-zh-CN/planning/task_plan.md +4 -44
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +1 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
- package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/scripts/smoke-dashboard.mjs +0 -92
- package/scripts/test-harness.mjs +0 -1395
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -16,10 +16,11 @@ Keep it as a charter and routing index. Detailed rules belong in
|
|
|
16
16
|
|
|
17
17
|
1. Preserve architecture boundaries documented in `docs/11-REFERENCE/engineering-standard.md`.
|
|
18
18
|
2. Do not commit secrets, credentials, private endpoints, or user data.
|
|
19
|
-
3. Start non-trivial work with a task directory under `docs/09-PLANNING/TASKS/`.
|
|
19
|
+
3. Start non-trivial work with a Harness CLI-created task directory under `docs/09-PLANNING/TASKS/`.
|
|
20
20
|
4. Record evidence before claiming completion.
|
|
21
21
|
5. Protect unrelated working-tree changes; never revert files outside the assigned scope.
|
|
22
|
-
6. Commit verified, meaningful work slices unless the user explicitly
|
|
22
|
+
6. Commit verified, meaningful work slices proactively. Do not leave completed work only as dirty files unless the user explicitly pauses commits, checks fail, dirty ownership is unclear, or a security boundary prevents a clean commit; in those cases record the no-commit reason, owner, and next step in progress, handoff, or closeout notes. Never mix unrelated dirty changes into a task commit.
|
|
23
|
+
7. Before the final response, inspect the current task `visual_map.md` gate phase. If the current `Exit Command` has `Actor: agent`, run it or record the blocker. Never run a `human` gate such as `review-confirm` unless the user has explicitly performed or delegated that human confirmation.
|
|
23
24
|
|
|
24
25
|
## Reading Matrix
|
|
25
26
|
|
|
@@ -32,8 +33,8 @@ Read the smallest context that can answer the task.
|
|
|
32
33
|
| Local development, mocks, stubs, or cross-repo debugging | `docs/04-DEVELOPMENT/README.md`, `docs/04-DEVELOPMENT/codebase-map.md` |
|
|
33
34
|
| API, event, webhook, SDK, or third-party contract | `docs/06-INTEGRATIONS/README.md` and the related contract file |
|
|
34
35
|
| Tests, smoke, or regression | `docs/11-REFERENCE/testing-standard.md`, `docs/05-TEST-QA/Regression-SSoT.md` |
|
|
35
|
-
| Execution, commit, PR, or release work | `docs/11-REFERENCE/execution-workflow-standard.md`, `docs/11-REFERENCE/repo-governance-standard.md`, `docs/11-REFERENCE/ci-cd-standard.md` |
|
|
36
|
-
| Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use
|
|
36
|
+
| Execution, commit, PR, or release work | `docs/11-REFERENCE/execution-workflow-standard.md`, `docs/11-REFERENCE/repo-governance-standard.md`, `docs/11-REFERENCE/pull-request-standard.md`, `docs/11-REFERENCE/ci-cd-standard.md` |
|
|
37
|
+
| Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use `harness new-task` / lifecycle commands when this project has the Harness CLI configured |
|
|
37
38
|
| Brief, Execution Strategy, or Visual Map | Current task `brief.md`, `execution_strategy.md`, and `visual_map.md` |
|
|
38
39
|
| Long-running task | `docs/11-REFERENCE/long-running-task-standard.md` |
|
|
39
40
|
| Reviewer, subagent, or adversarial review | `docs/11-REFERENCE/review-routing-standard.md`, `docs/11-REFERENCE/adversarial-review-standard.md` |
|
|
@@ -42,37 +43,42 @@ Read the smallest context that can answer the task.
|
|
|
42
43
|
| Documentation governance or Harness update | `docs/11-REFERENCE/docs-library-standard.md`, `.harness-capabilities.json` |
|
|
43
44
|
| External source material intake | `docs/11-REFERENCE/external-source-intake-standard.md` |
|
|
44
45
|
| Regression SSoT maintenance | `docs/11-REFERENCE/regression-ssot-governance.md` |
|
|
45
|
-
| Walkthrough, closeout, or lessons | `docs/11-REFERENCE/walkthrough-standard.md`, `docs/10-WALKTHROUGH/Closeout-SSoT.md`, `docs/01-GOVERNANCE/
|
|
46
|
+
| Walkthrough, closeout, or lessons | `docs/11-REFERENCE/walkthrough-standard.md`, `docs/10-WALKTHROUGH/Closeout-SSoT.md`, current task `lesson_candidates.md`, `docs/01-GOVERNANCE/lessons/` |
|
|
46
47
|
| Worktree operations | `docs/11-REFERENCE/worktree-standard.md` |
|
|
47
48
|
|
|
48
49
|
## Standard Execution Flow
|
|
49
50
|
|
|
50
51
|
1. Confirm the request, scope, and affected files.
|
|
51
|
-
2. For non-trivial work, create or update the task plan before editing code.
|
|
52
|
-
3.
|
|
53
|
-
4.
|
|
54
|
-
5.
|
|
55
|
-
6.
|
|
56
|
-
7.
|
|
57
|
-
8.
|
|
58
|
-
9.
|
|
59
|
-
10.
|
|
52
|
+
2. For non-trivial work, create or update the task plan before editing code. Use `harness new-task`; if the CLI is unavailable, keep Task Audit Metadata in the task `INDEX.md` and set `Created By` to `manual-exception` with a concrete reason.
|
|
53
|
+
3. Assess delegation from the user's goal; users do not need to know or ask for subagents.
|
|
54
|
+
4. Load only the reference documents required by the Reading Matrix.
|
|
55
|
+
5. Preserve existing project facts; merge new context instead of overwriting history.
|
|
56
|
+
6. Use a worktree or branch when the task requires isolation or parallel work.
|
|
57
|
+
7. Implement only within the approved scope.
|
|
58
|
+
8. Run the relevant checks and capture evidence in the task records.
|
|
59
|
+
9. Route required review and close blocking findings.
|
|
60
|
+
10. Follow the current `visual_map.md` lifecycle gate and its `Exit Command` / `Actor`.
|
|
61
|
+
11. Write or update the walkthrough and closeout records.
|
|
62
|
+
12. Update SSoT / ledger files that the task actually touched.
|
|
60
63
|
|
|
61
64
|
## Coordination Rules
|
|
62
65
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
66
|
+
- At task start, read the current task `execution_strategy.md` Subagent Authorization and Subagent Delegation Decision sections, then state whether reviewer or worker subagents should be used; do this even when the user only states the desired outcome.
|
|
67
|
+
- Reviewer subagents are allowed by default for read-only review within the current task.
|
|
68
|
+
- If a worker subagent would materially help and is not authorized, proactively ask the user once in plain language for task/scope/worktree authorization; it is fine to say "worker subagent", but do not wait for the user to know or suggest subagents.
|
|
69
|
+
- If independent slices are obvious but exact file paths are not, identify the file paths first and then immediately ask for independent-execution-helper authorization before implementation.
|
|
70
|
+
- Worker subagents require one user authorization recorded in `execution_strategy.md`; after that, reuse is allowed only within the same task, scope, and worktree/branch.
|
|
65
71
|
- Shared ledgers, registries, and SSoT files are coordinator-owned unless a lock is explicitly assigned.
|
|
66
72
|
- Worker handoff must include branch or worktree, changed files, checks, evidence, and residual risks.
|
|
67
73
|
|
|
68
74
|
## Single Sources Of Truth
|
|
69
75
|
|
|
70
|
-
- Feature SSoT: `docs/09-PLANNING/Feature-SSoT.md`
|
|
71
76
|
- Delivery SSoT: `docs/09-PLANNING/Delivery-SSoT.md`
|
|
72
77
|
- Module Registry: `docs/09-PLANNING/Module-Registry.md`
|
|
73
78
|
- Regression SSoT: `docs/05-TEST-QA/Regression-SSoT.md`
|
|
74
79
|
- Cadence Ledger: `docs/05-TEST-QA/Cadence-Ledger.md`
|
|
75
|
-
-
|
|
80
|
+
- Lesson Candidates: current task `lesson_candidates.md`
|
|
81
|
+
- Lesson Detail Docs: `docs/01-GOVERNANCE/lessons/`
|
|
76
82
|
- Closeout SSoT: `docs/10-WALKTHROUGH/Closeout-SSoT.md`
|
|
77
83
|
- Harness Ledger: `docs/Harness-Ledger.md`
|
|
78
84
|
|
|
@@ -12,9 +12,22 @@ const state = {
|
|
|
12
12
|
taskGroupPage: 1,
|
|
13
13
|
warningFilter: "all",
|
|
14
14
|
warningPage: 1,
|
|
15
|
+
presetQuery: "",
|
|
16
|
+
presetSourceFilter: "all",
|
|
17
|
+
selectedPresetKey: "",
|
|
18
|
+
selectedPresetId: "",
|
|
19
|
+
presetActionResult: null,
|
|
20
|
+
presetInstallSource: "",
|
|
21
|
+
presetInstallScope: "project",
|
|
22
|
+
presetInstallForce: false,
|
|
23
|
+
presetSeedScope: "project",
|
|
24
|
+
presetSeedForce: false,
|
|
25
|
+
presetUninstallScope: "project",
|
|
26
|
+
presetUninstallConfirm: "",
|
|
15
27
|
renderMode: "rendered",
|
|
16
28
|
theme: localStorage.getItem("harness.theme") || "system",
|
|
17
29
|
taskLayout: localStorage.getItem("harness.taskLayout") || "list",
|
|
30
|
+
taskSortOrder: localStorage.getItem("harness.taskSortOrder") === "asc" ? "asc" : "desc",
|
|
18
31
|
runtime: { mode: "static", csrfToken: "", writableActions: [] },
|
|
19
32
|
runtimeLoaded: false,
|
|
20
33
|
runtimePoller: null,
|
|
@@ -29,6 +29,7 @@ function shell() {
|
|
|
29
29
|
${routeLink("#/tasks", t("taskIndex"), "tasks")}
|
|
30
30
|
${routeLink("#/review", t("reviewQueue"), "review")}
|
|
31
31
|
${routeLink("#/modules", t("moduleView"), "modules")}
|
|
32
|
+
${routeLink("#/presets", t("presetCatalog"), "presets")}
|
|
32
33
|
<button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
|
|
33
34
|
<button data-theme-toggle>${themeLabel()}</button>
|
|
34
35
|
</div>
|
|
@@ -55,6 +56,7 @@ function renderRoute() {
|
|
|
55
56
|
if (route.name === "reviewTask") return reviewWorkspace(route);
|
|
56
57
|
if (route.name === "review") return reviewQueue();
|
|
57
58
|
if (route.name === "modules") return modulesView(route.id);
|
|
59
|
+
if (route.name === "presets") return presetsView();
|
|
58
60
|
if (route.name === "tasks") return taskIndex();
|
|
59
61
|
return overview();
|
|
60
62
|
}
|
|
@@ -66,11 +68,13 @@ function currentRoute() {
|
|
|
66
68
|
if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
|
|
67
69
|
if (parts[0] === "review") return { name: "review" };
|
|
68
70
|
if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
|
|
71
|
+
if (parts[0] === "presets") return { name: "presets" };
|
|
69
72
|
if (parts[0] === "tasks") return { name: "tasks" };
|
|
70
73
|
return { name: "overview" };
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function routeLink(hash, text, routeName) {
|
|
74
|
-
const
|
|
77
|
+
const current = currentRoute().name;
|
|
78
|
+
const active = current === routeName || (routeName === "review" && current === "reviewTask");
|
|
75
79
|
return `<a class="${active ? "active" : ""}" href="${hash}">${escapeHtml(text)}</a>`;
|
|
76
80
|
}
|
|
@@ -16,6 +16,9 @@ function overview() {
|
|
|
16
16
|
|
|
17
17
|
function statusStrip() {
|
|
18
18
|
const status = bundle.status?.checkState?.status || "unknown";
|
|
19
|
+
const validationMode = bundle.status?.checkState?.validationMode || "validated";
|
|
20
|
+
const dataOnly = validationMode === "data-only";
|
|
21
|
+
const displayState = dataOnly ? "snapshot" : status;
|
|
19
22
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
20
23
|
const warnings = bundle.status?.checkState?.warnings || 0;
|
|
21
24
|
const tasks = bundle.status?.tasks || [];
|
|
@@ -23,9 +26,9 @@ function statusStrip() {
|
|
|
23
26
|
const visual = summary.visualMapCoverage || {};
|
|
24
27
|
const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
25
28
|
return `<section class="status-card-group">
|
|
26
|
-
<div class="status-primary ${
|
|
27
|
-
<span>${t("readiness")}</span>
|
|
28
|
-
<strong>${label(status)}</strong>
|
|
29
|
+
<div class="status-primary ${displayState}">
|
|
30
|
+
<span>${dataOnly ? t("snapshotStatus") : t("readiness")}</span>
|
|
31
|
+
<strong>${dataOnly ? t("snapshot") : label(status)}</strong>
|
|
29
32
|
<p>${nextActionText()}</p>
|
|
30
33
|
</div>
|
|
31
34
|
<div class="metrics-grid">
|
|
@@ -46,6 +49,7 @@ function metric(labelText, value) {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function nextActionText() {
|
|
52
|
+
if ((bundle.status?.checkState?.validationMode || "validated") === "data-only") return t("snapshotNotValidated");
|
|
49
53
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
50
54
|
if (failures > 0) return t("resolveBlockers");
|
|
51
55
|
const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
|
|
@@ -165,24 +169,29 @@ function graphSummary() {
|
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
function activeTaskBriefs() {
|
|
168
|
-
const tasks = activeTasks()
|
|
172
|
+
const tasks = activeTasks();
|
|
169
173
|
return `<section class="task-briefs">
|
|
170
174
|
<div class="section-head">
|
|
171
175
|
<div>
|
|
172
176
|
<p class="eyebrow">${t("currentWork")}</p>
|
|
173
177
|
<h2>${t("activeBriefs")}</h2>
|
|
174
178
|
</div>
|
|
175
|
-
<
|
|
179
|
+
<div class="section-actions">
|
|
180
|
+
<span class="subtle">${t("activeBriefCount").replace("{count}", tasks.length).replace("{order}", taskSortLabel())}</span>
|
|
181
|
+
<a href="#/tasks">${t("openTaskIndex")}</a>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="brief-scroll">
|
|
185
|
+
<div class="brief-grid">${tasks.map((task) => taskBriefCard(task, { compact: false })).join("") || emptyState(t("noActiveTasks"))}</div>
|
|
176
186
|
</div>
|
|
177
|
-
<div class="brief-grid">${tasks.map((task) => taskBriefCard(task, { compact: false })).join("") || emptyState(t("noActiveTasks"))}</div>
|
|
178
187
|
</section>`;
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
function activeTasks() {
|
|
182
191
|
const tasks = bundle.status?.tasks || [];
|
|
183
192
|
const active = tasks.filter((task) => isActiveTaskState(task.state) || ["planned", "not_started"].includes(task.state));
|
|
184
|
-
if (active.length > 0) return active;
|
|
185
|
-
return tasks.filter((task) => task.briefSource === "standalone")
|
|
193
|
+
if (active.length > 0) return sortTasksByTime(active);
|
|
194
|
+
return sortTasksByTime(tasks.filter((task) => task.briefSource === "standalone"));
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
function isActiveTaskState(state) {
|
|
@@ -205,6 +214,7 @@ function taskBriefCard(task, { compact = true } = {}) {
|
|
|
205
214
|
<p class="brief-teaser">${escapeHtml(summaryText)}</p>
|
|
206
215
|
</div>
|
|
207
216
|
<div class="card-actions">
|
|
217
|
+
${taskCopyButton(task)}
|
|
208
218
|
<button class="btn-drawer-trigger" data-open-drawer="${escapeAttr(task.id)}">${t("viewDetails")}</button>
|
|
209
219
|
</div>
|
|
210
220
|
</article>`;
|
|
@@ -8,6 +8,59 @@ function stateToColorVar(state) {
|
|
|
8
8
|
return map[state] || "--muted";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function taskSortLabel() {
|
|
12
|
+
return state.taskSortOrder === "asc" ? t("sortOldest") : t("sortNewest");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function taskDateKey(task) {
|
|
16
|
+
const source = `${task.shortId || ""} ${task.id || ""}`.trim();
|
|
17
|
+
const match = source.match(/(?:^|[^\d])(\d{4})-(\d{2})(?:-(\d{2}))?/);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
const year = Number(match[1]);
|
|
20
|
+
const month = Number(match[2]);
|
|
21
|
+
const day = Number(match[3] || "1");
|
|
22
|
+
if (!year || month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
23
|
+
return Date.UTC(year, month - 1, day);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stableTaskLabel(task) {
|
|
27
|
+
return `${task.shortId || ""} ${task.id || ""} ${task.title || ""}`.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function compareTasksByTime(left, right) {
|
|
31
|
+
const leftDate = taskDateKey(left);
|
|
32
|
+
const rightDate = taskDateKey(right);
|
|
33
|
+
if (leftDate !== null && rightDate !== null && leftDate !== rightDate) {
|
|
34
|
+
return state.taskSortOrder === "asc" ? leftDate - rightDate : rightDate - leftDate;
|
|
35
|
+
}
|
|
36
|
+
if (leftDate !== null && rightDate === null) return -1;
|
|
37
|
+
if (leftDate === null && rightDate !== null) return 1;
|
|
38
|
+
return stableTaskLabel(left).localeCompare(stableTaskLabel(right));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sortTasksByTime(tasks) {
|
|
42
|
+
return [...tasks].sort(compareTasksByTime);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function taskFolderName(task) {
|
|
46
|
+
const fromPath = String(task?.path || "").split("/").filter(Boolean).pop();
|
|
47
|
+
const fromId = String(task?.id || "").split("/").filter(Boolean).pop();
|
|
48
|
+
return task?.shortId || fromPath || fromId || task?.title || "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function taskCopyButton(task, extraClass = "") {
|
|
52
|
+
const folderName = taskFolderName(task);
|
|
53
|
+
return `<button type="button" class="copy-task-name ${extraClass}" data-copy-task-name="${escapeAttr(folderName)}" data-copy-task-folder="${escapeAttr(folderName)}" aria-label="${escapeAttr(t("copyTaskName"))}" title="${escapeAttr(t("copyTaskName"))}">
|
|
54
|
+
${t("copyTaskNameShort")}
|
|
55
|
+
</button>`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function taskGroupTimeKey(group) {
|
|
59
|
+
const match = group.match(/^(?:month|legacy):(\d{4})-(\d{2})$/);
|
|
60
|
+
if (!match) return null;
|
|
61
|
+
return Date.UTC(Number(match[1]), Number(match[2]) - 1, 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
11
64
|
function taskToolbarCard(filteredCount) {
|
|
12
65
|
return `<section class="sidebar-card">
|
|
13
66
|
<h3>${t("filterTitle")}</h3>
|
|
@@ -39,6 +92,17 @@ function taskToolbarCard(filteredCount) {
|
|
|
39
92
|
</button>
|
|
40
93
|
</div>
|
|
41
94
|
</div>
|
|
95
|
+
<div class="select-group">
|
|
96
|
+
<label>${t("sortByTime")}</label>
|
|
97
|
+
<div class="layout-toggle-group sort-toggle-group">
|
|
98
|
+
<button class="layout-btn ${state.taskSortOrder === "desc" ? "active" : ""}" data-task-sort-order="desc" aria-label="${t("sortNewest")}">
|
|
99
|
+
${t("sortNewest")}
|
|
100
|
+
</button>
|
|
101
|
+
<button class="layout-btn ${state.taskSortOrder === "asc" ? "active" : ""}" data-task-sort-order="asc" aria-label="${t("sortOldest")}">
|
|
102
|
+
${t("sortOldest")}
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
42
106
|
<div class="search-stats">
|
|
43
107
|
${t("showing")} <strong>${filteredCount}</strong> / ${(bundle.status?.tasks || []).length} ${t("tasks")}
|
|
44
108
|
</div>
|
|
@@ -142,11 +206,12 @@ function taskRow(task) {
|
|
|
142
206
|
const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
|
|
143
207
|
const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
|
|
144
208
|
|
|
145
|
-
return `<
|
|
209
|
+
return `<article class="task-row-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateToColorVar(task.state)})">
|
|
146
210
|
<div class="row-accent-bar"></div>
|
|
147
211
|
<div class="row-main">
|
|
148
212
|
<strong>${escapeHtml(task.title)}</strong>
|
|
149
213
|
<span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(taskModuleKey(task))}</span>
|
|
214
|
+
${taskCopyButton(task, "row-copy")}
|
|
150
215
|
</div>
|
|
151
216
|
<div class="row-status">${tag(task.state)}</div>
|
|
152
217
|
<div class="row-progress">
|
|
@@ -165,7 +230,7 @@ function taskRow(task) {
|
|
|
165
230
|
${mapReady ? t("badgeMap") : t("badgeMapMissing")}
|
|
166
231
|
</span>
|
|
167
232
|
</div>
|
|
168
|
-
</
|
|
233
|
+
</article>`;
|
|
169
234
|
}
|
|
170
235
|
|
|
171
236
|
function taskIndex() {
|
|
@@ -204,7 +269,18 @@ function orderedTaskGroups(groups) {
|
|
|
204
269
|
if (group === "unknown") return 3;
|
|
205
270
|
return 4;
|
|
206
271
|
};
|
|
207
|
-
return Object.entries(groups).sort(([left], [right]) =>
|
|
272
|
+
return Object.entries(groups).sort(([left], [right]) => {
|
|
273
|
+
const rankDiff = rank(left) - rank(right);
|
|
274
|
+
if (rankDiff !== 0) return rankDiff;
|
|
275
|
+
const leftTime = taskGroupTimeKey(left);
|
|
276
|
+
const rightTime = taskGroupTimeKey(right);
|
|
277
|
+
if (leftTime !== null && rightTime !== null && leftTime !== rightTime) {
|
|
278
|
+
return state.taskSortOrder === "asc" ? leftTime - rightTime : rightTime - leftTime;
|
|
279
|
+
}
|
|
280
|
+
if (leftTime !== null && rightTime === null) return -1;
|
|
281
|
+
if (leftTime === null && rightTime !== null) return 1;
|
|
282
|
+
return left.localeCompare(right);
|
|
283
|
+
});
|
|
208
284
|
}
|
|
209
285
|
|
|
210
286
|
function taskGroups(tasks) {
|
|
@@ -229,11 +305,12 @@ function taskGroups(tasks) {
|
|
|
229
305
|
}
|
|
230
306
|
|
|
231
307
|
function taskGroup(group, tasks) {
|
|
232
|
-
const
|
|
308
|
+
const orderedTasks = sortTasksByTime(tasks);
|
|
309
|
+
const pageCount = Math.max(1, Math.ceil(orderedTasks.length / taskPageSize));
|
|
233
310
|
const page = Math.min(Math.max(1, Number(state.taskPageByGroup[group]) || 1), pageCount);
|
|
234
311
|
const start = (page - 1) * taskPageSize;
|
|
235
|
-
const visibleTasks =
|
|
236
|
-
const avgCompletion =
|
|
312
|
+
const visibleTasks = orderedTasks.slice(start, start + taskPageSize);
|
|
313
|
+
const avgCompletion = orderedTasks.length ? clampCompletion(orderedTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / orderedTasks.length) : 0;
|
|
237
314
|
|
|
238
315
|
const isGrid = state.taskLayout === "grid";
|
|
239
316
|
const layoutClass = isGrid ? "task-card-grid" : "task-list";
|
|
@@ -250,7 +327,7 @@ function taskGroup(group, tasks) {
|
|
|
250
327
|
<div class="section-head">
|
|
251
328
|
<div>
|
|
252
329
|
<h2>${taskGroupLabel(group)}</h2>
|
|
253
|
-
<p class="subtle">${t("showing")} ${Math.min(start + 1,
|
|
330
|
+
<p class="subtle">${t("showing")} ${Math.min(start + 1, orderedTasks.length)}-${Math.min(start + visibleTasks.length, orderedTasks.length)} / ${orderedTasks.length}</p>
|
|
254
331
|
</div>
|
|
255
332
|
<div class="group-actions">
|
|
256
333
|
<div class="group-progress" aria-label="${escapeAttr(t("groupCompletion"))}">
|
|
@@ -275,10 +352,13 @@ function taskCard(task) {
|
|
|
275
352
|
const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
|
|
276
353
|
const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
|
|
277
354
|
|
|
278
|
-
return `<
|
|
355
|
+
return `<article class="task-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateColor})">
|
|
279
356
|
<div class="card-header">
|
|
280
357
|
<span class="card-id">${escapeHtml(task.id)}</span>
|
|
281
|
-
|
|
358
|
+
<div class="card-header-actions">
|
|
359
|
+
${taskCopyButton(task, "compact")}
|
|
360
|
+
${tag(task.state)}
|
|
361
|
+
</div>
|
|
282
362
|
</div>
|
|
283
363
|
<h4 class="card-title" title="${escapeAttr(task.title)}">${escapeHtml(task.title)}</h4>
|
|
284
364
|
<div class="card-meta">
|
|
@@ -301,7 +381,7 @@ function taskCard(task) {
|
|
|
301
381
|
${mapReady ? t("badgeMap") : t("badgeMapMissing")}
|
|
302
382
|
</span>
|
|
303
383
|
</div>
|
|
304
|
-
</
|
|
384
|
+
</article>`;
|
|
305
385
|
}
|
|
306
386
|
|
|
307
387
|
function taskGroupLabel(group) {
|
|
@@ -316,248 +396,14 @@ function taskGroupLabel(group) {
|
|
|
316
396
|
|
|
317
397
|
function filteredTasks() {
|
|
318
398
|
const query = state.query.trim().toLowerCase();
|
|
319
|
-
return (bundle.status?.tasks || []).filter((task) => {
|
|
399
|
+
return sortTasksByTime((bundle.status?.tasks || []).filter((task) => {
|
|
320
400
|
const stateMatch = state.taskState === "all" || task.state === state.taskState;
|
|
321
401
|
if (!stateMatch) return false;
|
|
322
402
|
if (!query) return true;
|
|
323
403
|
return [task.id, task.shortId, task.title, task.module, task.inferredModule, task.classificationSource, task.classificationBucket, task.state].some((value) => String(value || "").toLowerCase().includes(query));
|
|
324
|
-
});
|
|
404
|
+
}));
|
|
325
405
|
}
|
|
326
406
|
|
|
327
407
|
function taskModuleKey(task) {
|
|
328
408
|
return task.module || task.inferredModule || "legacy-unclassified";
|
|
329
409
|
}
|
|
330
|
-
|
|
331
|
-
function taskDetail(route) {
|
|
332
|
-
const taskId = route.id;
|
|
333
|
-
const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
|
|
334
|
-
if (!task) return `<main>${emptyState(t("taskNotFound"))}</main>`;
|
|
335
|
-
return `<main class="task-detail">
|
|
336
|
-
<nav class="crumbs"><a href="#/tasks">${t("taskIndex")}</a><span>/</span><span>${escapeHtml(task.id)}</span></nav>
|
|
337
|
-
<section class="detail-hero">
|
|
338
|
-
<div>
|
|
339
|
-
<p class="eyebrow">${t("taskVisibility")}</p>
|
|
340
|
-
<h2>${escapeHtml(task.title)}</h2>
|
|
341
|
-
<p>${escapeHtml(task.path)}</p>
|
|
342
|
-
</div>
|
|
343
|
-
<div class="detail-score">${task.completion}%</div>
|
|
344
|
-
</section>
|
|
345
|
-
${taskStateSummary(task)}
|
|
346
|
-
${phaseTimeline(task)}
|
|
347
|
-
<section class="detail-grid">
|
|
348
|
-
<article class="detail-main">
|
|
349
|
-
${taskDocumentLibrary(task, route.doc)}
|
|
350
|
-
</article>
|
|
351
|
-
<aside class="detail-side">
|
|
352
|
-
${reviewActionPanel(task, { mode: "summary" })}
|
|
353
|
-
${migrationSnapshotPanel(task)}
|
|
354
|
-
${openFindings(task)}
|
|
355
|
-
${evidenceList(task)}
|
|
356
|
-
${documentTabs(task)}
|
|
357
|
-
</aside>
|
|
358
|
-
</section>
|
|
359
|
-
</main>`;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function taskStateSummary(task) {
|
|
363
|
-
return `<section class="task-state-summary">
|
|
364
|
-
<div>
|
|
365
|
-
<span>${t("legacyState")}</span>
|
|
366
|
-
${tag(task.state)}
|
|
367
|
-
</div>
|
|
368
|
-
<div>
|
|
369
|
-
<span>${t("lifecycleState")}</span>
|
|
370
|
-
${tag(task.lifecycleState || "unknown")}
|
|
371
|
-
</div>
|
|
372
|
-
<div>
|
|
373
|
-
<span>${t("reviewStatus")}</span>
|
|
374
|
-
${tag(task.reviewStatus || "missing")}
|
|
375
|
-
</div>
|
|
376
|
-
<div>
|
|
377
|
-
<span>${t("closeoutStatus")}</span>
|
|
378
|
-
${tag(task.closeoutStatus || "missing")}
|
|
379
|
-
</div>
|
|
380
|
-
</section>`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function phaseTimeline(task) {
|
|
384
|
-
return `<section class="phase-timeline">
|
|
385
|
-
<h2>${t("phaseTimeline")}</h2>
|
|
386
|
-
${(task.phases || []).map((phase) => `<div class="phase-step ${phase.state}">
|
|
387
|
-
<strong>${escapeHtml(phase.id)}</strong>
|
|
388
|
-
<span>${phase.completion}%</span>
|
|
389
|
-
<p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
|
|
390
|
-
${progressBar(phase.completion)}
|
|
391
|
-
</div>`).join("") || emptyState(t("noPhaseData"))}
|
|
392
|
-
</section>`;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function taskDocSection(task, fileName, title, required) {
|
|
396
|
-
const doc = taskDocument(task, fileName);
|
|
397
|
-
if (!doc && !required) return "";
|
|
398
|
-
return `<section class="doc-section">
|
|
399
|
-
<div class="section-head"><h2>${escapeHtml(title)}</h2>${doc ? `<button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button>` : ""}</div>
|
|
400
|
-
<div class="markdown">${doc ? window.HarnessMarkdown.render(doc.content, state.renderMode) : generatedBrief(task)}</div>
|
|
401
|
-
</section>`;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function taskDocumentLibrary(task, selectedTab) {
|
|
405
|
-
const docs = orderedTaskDocuments(task);
|
|
406
|
-
if (!docs.length) return taskDocSection(task, "brief.md", t("brief"), true);
|
|
407
|
-
const selectedKey = docs.some((doc) => doc.key === selectedTab) ? selectedTab : defaultTaskDocumentKey(task, docs);
|
|
408
|
-
return `<section class="doc-library">
|
|
409
|
-
<div class="section-head">
|
|
410
|
-
<div>
|
|
411
|
-
<p class="eyebrow">${t("taskDocuments")}</p>
|
|
412
|
-
<h2>${escapeHtml(t("sourceDocuments"))}</h2>
|
|
413
|
-
</div>
|
|
414
|
-
<button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button>
|
|
415
|
-
</div>
|
|
416
|
-
<div class="doc-accordion-list">
|
|
417
|
-
${docs.map((item) => documentAccordion(item, item.key === selectedKey)).join("")}
|
|
418
|
-
</div>
|
|
419
|
-
</section>`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function orderedTaskDocuments(task) {
|
|
423
|
-
const docs = taskDocTabs
|
|
424
|
-
.map(([key, file]) => {
|
|
425
|
-
const doc = taskDocument(task, file);
|
|
426
|
-
if (doc) return { key, file, title: t(key), path: doc.path, content: doc.content };
|
|
427
|
-
if (key === "brief") return { key, file, title: t(key), path: `${task.path}/brief.md`, content: generatedBrief(task), generated: true };
|
|
428
|
-
return null;
|
|
429
|
-
})
|
|
430
|
-
.filter(Boolean);
|
|
431
|
-
const priority = taskDocumentPriority(task);
|
|
432
|
-
const rank = new Map(priority.map((key, index) => [key, index]));
|
|
433
|
-
return docs.sort((a, b) => (rank.get(a.key) ?? 99) - (rank.get(b.key) ?? 99));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function taskDocumentPriority(task) {
|
|
437
|
-
const stateName = task?.state || "";
|
|
438
|
-
const lifecycle = task?.lifecycleState || "";
|
|
439
|
-
if (stateName === "review" || ["in_review", "review-blocked"].includes(lifecycle)) {
|
|
440
|
-
return ["walkthrough", "lessonCandidates", "review", "findings", "visualMap", "progress", "brief", "taskPlan", "strategy", "longRunningContract", "legacyRoadmap", "references", "artifacts"];
|
|
441
|
-
}
|
|
442
|
-
if (stateName === "in_progress" || lifecycle === "active" || stateName === "blocked") {
|
|
443
|
-
return ["progress", "visualMap", "brief", "taskPlan", "strategy", "findings", "review", "walkthrough", "references", "artifacts", "legacyRoadmap"];
|
|
444
|
-
}
|
|
445
|
-
if (stateName === "done" || ["closing", "closed"].includes(lifecycle)) {
|
|
446
|
-
return ["walkthrough", "progress", "review", "findings", "visualMap", "brief", "taskPlan", "strategy", "references", "artifacts", "legacyRoadmap"];
|
|
447
|
-
}
|
|
448
|
-
return ["brief", "taskPlan", "visualMap", "strategy", "progress", "findings", "review", "walkthrough", "references", "artifacts", "legacyRoadmap"];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function defaultTaskDocumentKey(task, docs) {
|
|
452
|
-
const priority = taskDocumentPriority(task);
|
|
453
|
-
return priority.find((key) => docs.some((doc) => doc.key === key)) || docs[0]?.key || "brief";
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function documentAccordion(item, open) {
|
|
457
|
-
return `<details class="doc-accordion" ${open ? "open" : ""}>
|
|
458
|
-
<summary>
|
|
459
|
-
<span>${escapeHtml(item.title)}</span>
|
|
460
|
-
<small>${escapeHtml(item.generated ? t("generatedFallback") : item.path)}</small>
|
|
461
|
-
</summary>
|
|
462
|
-
<div class="markdown">${window.HarnessMarkdown.render(item.content, state.renderMode)}</div>
|
|
463
|
-
</details>`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function documentTabs(task) {
|
|
467
|
-
const docs = orderedTaskDocuments(task);
|
|
468
|
-
return `<section class="side-panel">
|
|
469
|
-
<h3>${t("sourceDocuments")}</h3>
|
|
470
|
-
${docs.map((doc) => `<a href="#/tasks/${encodeURIComponent(task.id)}/docs/${encodeURIComponent(doc.key)}" title="${escapeAttr(doc.path)}">${escapeHtml(doc.title)}</a>`).join("") || `<p>${t("noDocuments")}</p>`}
|
|
471
|
-
</section>`;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function selectedSourceDocument(task, tab) {
|
|
475
|
-
if (!tab) return "";
|
|
476
|
-
const match = taskDocTabs.find(([key]) => key === tab);
|
|
477
|
-
if (!match) return "";
|
|
478
|
-
const doc = taskDocument(task, match[1]);
|
|
479
|
-
if (!doc) return "";
|
|
480
|
-
return `<section class="doc-section selected-source">
|
|
481
|
-
<div class="section-head"><h2>${t("selectedSource")} · ${t(match[0])}</h2><button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button></div>
|
|
482
|
-
<div class="markdown">${window.HarnessMarkdown.render(doc.content, state.renderMode)}</div>
|
|
483
|
-
</section>`;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function openFindings(task) {
|
|
487
|
-
const risks = task.risks || [];
|
|
488
|
-
return `<section class="side-panel">
|
|
489
|
-
<h3>${t("openFindings")}</h3>
|
|
490
|
-
${risks.map((risk) => `<div class="finding ${risk.open || risk.blocksRelease ? "open" : ""}"><strong>${escapeHtml(risk.severity)}</strong><span>${escapeHtml(risk.summary)}</span></div>`).join("") || `<p>${t("noOpenFindings")}</p>`}
|
|
491
|
-
</section>`;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function migrationSnapshotPanel(task) {
|
|
495
|
-
const snapshot = task.migrationSnapshot;
|
|
496
|
-
if (!snapshot) return "";
|
|
497
|
-
return `<section class="side-panel">
|
|
498
|
-
<h3>${t("migrationSnapshot")}</h3>
|
|
499
|
-
<p>${escapeHtml(t("taskPreset"))}: ${tag(task.taskPreset || "none")}</p>
|
|
500
|
-
<p>${escapeHtml(t("targetLevel"))}: ${tag(snapshot.targetLevel || "unknown")}</p>
|
|
501
|
-
<p>${escapeHtml(t("achievedLevel"))}: ${tag(snapshot.achievedLevel || "unknown")}</p>
|
|
502
|
-
<p>${escapeHtml(t("strictDeferred"))}: ${tag(snapshot.strictDeferred ? "yes" : "no")}</p>
|
|
503
|
-
<p>${escapeHtml(t("evidenceBundle"))}: <code>${escapeHtml(snapshot.evidenceBundle || "missing")}</code></p>
|
|
504
|
-
</section>`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
function reviewActionPanel(task, { mode = "summary" } = {}) {
|
|
508
|
-
if (!isTaskInReviewStage(task)) return "";
|
|
509
|
-
const blocking = task.reviewStatus === "blocked-open-findings" || (task.risks || []).some((risk) => /^P[0-2]$/i.test(risk.severity || "") && (risk.open || risk.blocksRelease));
|
|
510
|
-
const confirmed = task.reviewStatus === "confirmed";
|
|
511
|
-
const candidateBlocked = task.budget !== "simple" && !task.lessonCandidateDecisionComplete;
|
|
512
|
-
const candidateStatus = task.lessonCandidateStatus || "missing";
|
|
513
|
-
if (mode !== "workspace") {
|
|
514
|
-
return `<section class="side-panel review-actions">
|
|
515
|
-
<h3>${t("reviewActions")}</h3>
|
|
516
|
-
<p>${escapeHtml(confirmed ? t("reviewAlreadyConfirmed") : t("reviewOpenInWorkspace"))}</p>
|
|
517
|
-
<p>${escapeHtml(t("lessonCandidateStatus"))}: ${tag(candidateStatus)}</p>
|
|
518
|
-
<a href="#/review/${encodeURIComponent(task.id)}">${t("openReviewWorkspace")}</a>
|
|
519
|
-
</section>`;
|
|
520
|
-
}
|
|
521
|
-
if (!canUseWorkbenchAction("review-complete")) {
|
|
522
|
-
return `<section class="side-panel review-actions">
|
|
523
|
-
<h3>${t("reviewActions")}</h3>
|
|
524
|
-
<p>${escapeHtml(t("staticReadOnlyDetail"))}</p>
|
|
525
|
-
</section>`;
|
|
526
|
-
}
|
|
527
|
-
if (confirmed) {
|
|
528
|
-
return `<section class="side-panel review-actions">
|
|
529
|
-
<h3>${t("reviewActions")}</h3>
|
|
530
|
-
<p>${escapeHtml(t("reviewAlreadyConfirmed"))}</p>
|
|
531
|
-
</section>`;
|
|
532
|
-
}
|
|
533
|
-
const missingWalkthrough = task.budget !== "simple" && !task.walkthroughPath;
|
|
534
|
-
const disabled = blocking || missingWalkthrough || candidateBlocked;
|
|
535
|
-
const message = missingWalkthrough ? t("reviewWalkthroughRequired") : blocking ? t("reviewBlocked") : candidateBlocked ? t("reviewCandidateDecisionRequired") : t("reviewWorkbenchReady");
|
|
536
|
-
return `<section class="side-panel review-actions">
|
|
537
|
-
<h3>${t("reviewActions")}</h3>
|
|
538
|
-
<p>${escapeHtml(message)}</p>
|
|
539
|
-
<p>${escapeHtml(t("lessonCandidateStatus"))}: ${tag(candidateStatus)}</p>
|
|
540
|
-
<label class="review-check">
|
|
541
|
-
<input type="checkbox" data-review-confirm-check="${escapeAttr(task.id)}" ${disabled ? "disabled" : ""}>
|
|
542
|
-
<span>${t("reviewConfirmChecklist")}</span>
|
|
543
|
-
</label>
|
|
544
|
-
<input data-review-confirm-text="${escapeAttr(task.id)}" value="" placeholder="${escapeAttr(task.shortId || task.id)}" ${disabled ? "disabled" : ""}>
|
|
545
|
-
<button data-review-complete="${escapeAttr(task.id)}" ${disabled ? "disabled" : ""}>${t("confirmReviewComplete")}</button>
|
|
546
|
-
<div class="review-result" data-review-result="${escapeAttr(task.id)}"></div>
|
|
547
|
-
</section>`;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function isTaskInReviewStage(task) {
|
|
551
|
-
const state = task?.state || "";
|
|
552
|
-
const lifecycle = task?.lifecycleState || "";
|
|
553
|
-
if (["not_started", "planned", "in_progress"].includes(state)) return false;
|
|
554
|
-
return state === "review" || ["in_review", "review-blocked"].includes(lifecycle);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function evidenceList(task) {
|
|
558
|
-
const evidence = task.evidence || [];
|
|
559
|
-
return `<section class="side-panel">
|
|
560
|
-
<h3>${t("evidence")}</h3>
|
|
561
|
-
${evidence.map((item) => `<p><strong>${escapeHtml(item.type || "evidence")}</strong> ${escapeHtml(item.summary || "")}</p>`).join("") || `<p>${t("noEvidence")}</p>`}
|
|
562
|
-
</section>`;
|
|
563
|
-
}
|