coding-agent-harness 1.0.8 → 1.1.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 +10 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +19 -6
- package/dist/check-dist-observation.mjs +57 -29
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +7 -7
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +51 -9
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +51 -12
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +109 -52
- package/dist/harness.mjs +6 -304
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +4 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +67 -29
- package/dist/lib/preset-registry.mjs +361 -71
- package/dist/lib/preset-runner.mjs +292 -26
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +3 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +116 -160
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +36 -17
- package/dist/lib/task-scanner.mjs +74 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +186 -29
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +16 -12
- package/postinstall.mjs +37 -0
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/release-closeout/checks/check-release-package.mjs +6 -1
- package/presets/release-closeout/preset.yaml +9 -9
- package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
- package/presets/release-closeout/templates/task_plan.append.md +5 -5
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -12,6 +12,6 @@ Confidence: low
|
|
|
12
12
|
|
|
13
13
|
## Boundary Rule
|
|
14
14
|
|
|
15
|
-
This catalog gives interface summaries and links only. Put payload bodies, auth rules, error codes, and event schemas in `
|
|
15
|
+
This catalog gives interface summaries and links only. Put payload bodies, auth rules, error codes, and event schemas in `{{paths.harnessRoot}}/context/integrations/`.
|
|
16
16
|
|
|
17
|
-
Each service or microservice gets one row. If the service affects development or task decisions in this repository, add links to `services/<service-key>.md`, `
|
|
17
|
+
Each service or microservice gets one row. If the service affects development or task decisions in this repository, add links to `services/<service-key>.md`, `{{paths.harnessRoot}}/context/development/external-context/<service-key>.md`, and the relevant `{{paths.harnessRoot}}/context/integrations/<contract>.md`.
|
|
@@ -9,7 +9,7 @@ Confidence: low
|
|
|
9
9
|
|
|
10
10
|
| Catalog Row | Development Context | Source Pack | Integration Contracts | Last Verified | Confidence |
|
|
11
11
|
| --- | --- | --- | --- | --- | --- |
|
|
12
|
-
| `
|
|
12
|
+
| `{{paths.harnessRoot}}/context/architecture/service-catalog.md` | `{{paths.harnessRoot}}/context/development/external-context/<service-key>.md` | `{{paths.harnessRoot}}/context/development/external-source-packs/<source-key>/README.md` or N/A | `{{paths.harnessRoot}}/context/integrations/<contract>.md` | unknown | low |
|
|
13
13
|
|
|
14
14
|
## Responsibility
|
|
15
15
|
|
|
@@ -7,7 +7,7 @@ let labels = window.HarnessI18n?.[locale] || {};
|
|
|
7
7
|
const state = {
|
|
8
8
|
query: "",
|
|
9
9
|
taskState: "all",
|
|
10
|
-
taskGroupMode: "migration",
|
|
10
|
+
taskGroupMode: localStorage.getItem("harness.taskGroupMode") || ((Array.isArray(bundle.modules) && bundle.modules.length > 0) ? "module" : "migration"),
|
|
11
11
|
taskPageByGroup: {},
|
|
12
12
|
taskGroupPage: 1,
|
|
13
13
|
warningFilter: "all",
|
|
@@ -24,6 +24,10 @@ const state = {
|
|
|
24
24
|
presetSeedForce: false,
|
|
25
25
|
presetUninstallScope: "project",
|
|
26
26
|
presetUninstallConfirm: "",
|
|
27
|
+
reviewBulkSelection: {},
|
|
28
|
+
reviewBulkResult: null,
|
|
29
|
+
lessonBulkSelection: {},
|
|
30
|
+
lessonBulkResult: null,
|
|
27
31
|
renderMode: "rendered",
|
|
28
32
|
theme: localStorage.getItem("harness.theme") || "system",
|
|
29
33
|
taskLayout: localStorage.getItem("harness.taskLayout") || "list",
|
|
@@ -2,6 +2,10 @@ function t(key) {
|
|
|
2
2
|
return labels[key] || key;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
function formatMessage(key, values = {}) {
|
|
6
|
+
return escapeHtml(t(key)).replace(/\{([^}]+)\}/g, (_, name) => escapeHtml(values[name] ?? ""));
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
function setLocale(nextLocale) {
|
|
6
10
|
locale = window.HarnessI18n?.[nextLocale] ? nextLocale : "en";
|
|
7
11
|
labels = window.HarnessI18n?.[locale] || {};
|
|
@@ -28,6 +32,7 @@ function shell() {
|
|
|
28
32
|
${routeLink("#/", t("overview"), "overview")}
|
|
29
33
|
${routeLink("#/tasks", t("taskIndex"), "tasks")}
|
|
30
34
|
${routeLink("#/review", t("reviewQueue"), "review")}
|
|
35
|
+
${routeLink("#/archive", t("archive"), "archive")}
|
|
31
36
|
${routeLink("#/modules", t("moduleView"), "modules")}
|
|
32
37
|
${routeLink("#/presets", t("presetCatalog"), "presets")}
|
|
33
38
|
<button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
|
|
@@ -55,6 +60,7 @@ function renderRoute() {
|
|
|
55
60
|
if (route.name === "task") return taskDetail(route);
|
|
56
61
|
if (route.name === "reviewTask") return reviewWorkspace(route);
|
|
57
62
|
if (route.name === "review") return reviewQueue();
|
|
63
|
+
if (route.name === "archive") return archiveView();
|
|
58
64
|
if (route.name === "modules") return modulesView(route.id);
|
|
59
65
|
if (route.name === "presets") return presetsView();
|
|
60
66
|
if (route.name === "tasks") return taskIndex();
|
|
@@ -67,6 +73,7 @@ function currentRoute() {
|
|
|
67
73
|
if (parts[0] === "tasks" && parts[1]) return { name: "task", id: parts[1], doc: parts[2] === "docs" ? parts[3] || "" : "" };
|
|
68
74
|
if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
|
|
69
75
|
if (parts[0] === "review") return { name: "review" };
|
|
76
|
+
if (parts[0] === "archive") return { name: "archive" };
|
|
70
77
|
if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
|
|
71
78
|
if (parts[0] === "presets") return { name: "presets" };
|
|
72
79
|
if (parts[0] === "tasks") return { name: "tasks" };
|
|
@@ -21,7 +21,7 @@ function statusStrip() {
|
|
|
21
21
|
const displayState = snapshotOnly ? "snapshot" : status;
|
|
22
22
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
23
23
|
const warnings = bundle.status?.checkState?.warnings || 0;
|
|
24
|
-
const tasks =
|
|
24
|
+
const tasks = normalCycleTasks();
|
|
25
25
|
const summary = bundle.status?.summary || {};
|
|
26
26
|
const visual = summary.visualMapCoverage || {};
|
|
27
27
|
const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
@@ -53,7 +53,7 @@ function nextActionText() {
|
|
|
53
53
|
if (dataOnly && !isWorkbenchRuntime()) return t("snapshotNotValidated");
|
|
54
54
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
55
55
|
if (failures > 0) return t("resolveBlockers");
|
|
56
|
-
const missingBriefs = (
|
|
56
|
+
const missingBriefs = normalCycleTasks().filter((task) => task.briefSource !== "standalone").length;
|
|
57
57
|
if (missingBriefs > 0) return `${missingBriefs} ${t("missingBriefs")}`;
|
|
58
58
|
const warnings = bundle.status?.checkState?.warnings || 0;
|
|
59
59
|
if (warnings > 0) return t("reviewAdvice");
|
|
@@ -66,7 +66,7 @@ function isWorkbenchRuntime() {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function flowPanel() {
|
|
69
|
-
const tasks =
|
|
69
|
+
const tasks = normalCycleTasks();
|
|
70
70
|
const total = tasks.length;
|
|
71
71
|
if (total === 0) return "";
|
|
72
72
|
const active = tasks.filter((task) => isActiveTaskState(task.state)).length;
|
|
@@ -125,14 +125,14 @@ function projectMermaid() {
|
|
|
125
125
|
|
|
126
126
|
function usesAggregateFlow() {
|
|
127
127
|
const graph = bundle.graph || { nodes: [], edges: [] };
|
|
128
|
-
const taskCount = (
|
|
128
|
+
const taskCount = normalCycleTasks().length;
|
|
129
129
|
const taskNodes = (graph.nodes || []).filter((node) => node.type === "task").length;
|
|
130
130
|
const usefulEdges = (graph.edges || []).filter((edge) => ["depends_on", "current_step"].includes(edge.type)).length;
|
|
131
131
|
return taskCount > 80 || taskNodes > 80 || ((graph.nodes || []).length > 80 && usefulEdges < 6);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
function migrationAggregateMermaid() {
|
|
135
|
-
const tasks =
|
|
135
|
+
const tasks = normalCycleTasks();
|
|
136
136
|
const warnings = warningQueue();
|
|
137
137
|
const activeContracts = warnings.filter((warning) => warning.phase === "active-task-contracts").length;
|
|
138
138
|
const moduleCount = new Set(tasks.map(taskModuleKey)).size;
|
|
@@ -148,7 +148,7 @@ function migrationAggregateMermaid() {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function migrationRunwayBreakdown() {
|
|
151
|
-
const tasks =
|
|
151
|
+
const tasks = normalCycleTasks();
|
|
152
152
|
const warnings = warningQueue();
|
|
153
153
|
const phases = [
|
|
154
154
|
["baseline", t("runwayBaseline"), tasks.length, t("tasks"), "#/tasks"],
|
|
@@ -170,7 +170,7 @@ function mermaidFromBriefs() {
|
|
|
170
170
|
|
|
171
171
|
function graphSummary() {
|
|
172
172
|
const graph = bundle.graph || { nodes: [], edges: [] };
|
|
173
|
-
if (usesAggregateFlow()) return `${t("aggregateMigrationView")} · ${(
|
|
173
|
+
if (usesAggregateFlow()) return `${t("aggregateMigrationView")} · ${normalCycleTasks().length} ${t("tasks")}`;
|
|
174
174
|
return `${graph.nodes?.length || 0} ${t("nodes")} · ${graph.edges?.length || 0} ${t("edges")}`;
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -194,7 +194,7 @@ function activeTaskBriefs() {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function activeTasks() {
|
|
197
|
-
const tasks =
|
|
197
|
+
const tasks = normalCycleTasks();
|
|
198
198
|
const active = tasks.filter((task) => isActiveTaskState(task.state) || ["planned", "not_started"].includes(task.state));
|
|
199
199
|
if (active.length > 0) return sortTasksByTime(active);
|
|
200
200
|
return sortTasksByTime(tasks.filter((task) => task.briefSource === "standalone"));
|
|
@@ -8,6 +8,22 @@ function stateToColorVar(state) {
|
|
|
8
8
|
return map[state] || "--muted";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function taskStatRows(tasks) {
|
|
12
|
+
return [
|
|
13
|
+
{ state: "in_progress", label: t("statInProgress"), className: "in-progress" },
|
|
14
|
+
{ state: "review", label: t("statReview"), className: "review" },
|
|
15
|
+
{ state: "blocked", label: t("statBlocked"), className: "blocked" },
|
|
16
|
+
{ state: "done", label: t("statDone"), className: "done" },
|
|
17
|
+
{ state: "planned", label: label("planned"), className: "planned" },
|
|
18
|
+
{ state: "not_started", label: label("not_started"), className: "not-started" },
|
|
19
|
+
{ state: "unknown", label: label("unknown"), className: "unknown" },
|
|
20
|
+
].map((row) => ({
|
|
21
|
+
...row,
|
|
22
|
+
count: tasks.filter((task) => task.state === row.state).length,
|
|
23
|
+
colorVar: stateToColorVar(row.state),
|
|
24
|
+
})).filter((row) => row.count > 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
function taskSortLabel() {
|
|
12
28
|
return state.taskSortOrder === "asc" ? t("sortOldest") : t("sortNewest");
|
|
13
29
|
}
|
|
@@ -42,6 +58,23 @@ function sortTasksByTime(tasks) {
|
|
|
42
58
|
return [...tasks].sort(compareTasksByTime);
|
|
43
59
|
}
|
|
44
60
|
|
|
61
|
+
function isArchivedTask(task) {
|
|
62
|
+
const archiveState = String(task?.archiveMetadata?.state || "").toLowerCase();
|
|
63
|
+
return task?.deletionState === "archived" || archiveState === "archived";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalCycleTasks() {
|
|
67
|
+
return (bundle.status?.tasks || []).filter((task) => !isArchivedTask(task));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function archivedTasks() {
|
|
71
|
+
return (bundle.status?.tasks || []).filter(isArchivedTask);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function archiveBucket(task) {
|
|
75
|
+
return task?.archiveMetadata?.["retention bucket"] || task?.archiveMetadata?.["Retention Bucket"] || t("archiveUnclassified");
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
function taskFolderName(task) {
|
|
46
79
|
const fromPath = String(task?.path || "").split("/").filter(Boolean).pop();
|
|
47
80
|
const fromId = String(task?.id || "").split("/").filter(Boolean).pop();
|
|
@@ -70,7 +103,7 @@ function taskToolbarCard(filteredCount) {
|
|
|
70
103
|
<div class="select-group">
|
|
71
104
|
<label>${t("stateFilter")}</label>
|
|
72
105
|
<select data-state-filter aria-label="${t("stateFilter")}">
|
|
73
|
-
${["all", "in_progress", "review", "blocked", "planned", "done", "unknown"].map((value) => `<option value="${value}" ${state.taskState === value ? "selected" : ""}>${label(value)}</option>`).join("")}
|
|
106
|
+
${["all", "in_progress", "review", "blocked", "planned", "not_started", "done", "unknown"].map((value) => `<option value="${value}" ${state.taskState === value ? "selected" : ""}>${label(value)}</option>`).join("")}
|
|
74
107
|
</select>
|
|
75
108
|
</div>
|
|
76
109
|
<div class="select-group">
|
|
@@ -90,6 +123,10 @@ function taskToolbarCard(filteredCount) {
|
|
|
90
123
|
<svg style="width:12px;height:12px;vertical-align:middle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
91
124
|
${t("layoutGrid")}
|
|
92
125
|
</button>
|
|
126
|
+
<button class="layout-btn ${state.taskLayout === "swimlane" ? "active" : ""}" data-layout="swimlane" aria-label="${t("layoutSwimlane")}">
|
|
127
|
+
<svg style="width:12px;height:12px;vertical-align:middle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/><path d="M8 4v16"/><path d="M16 4v16"/></svg>
|
|
128
|
+
${t("layoutSwimlane")}
|
|
129
|
+
</button>
|
|
93
130
|
</div>
|
|
94
131
|
</div>
|
|
95
132
|
<div class="select-group">
|
|
@@ -104,13 +141,13 @@ function taskToolbarCard(filteredCount) {
|
|
|
104
141
|
</div>
|
|
105
142
|
</div>
|
|
106
143
|
<div class="search-stats">
|
|
107
|
-
${t("showing")} <strong>${filteredCount}</strong> / ${(
|
|
144
|
+
${t("showing")} <strong>${filteredCount}</strong> / ${normalCycleTasks().length} ${t("tasks")}
|
|
108
145
|
</div>
|
|
109
146
|
</section>`;
|
|
110
147
|
}
|
|
111
148
|
|
|
112
149
|
function taskStatsCard() {
|
|
113
|
-
const allTasks =
|
|
150
|
+
const allTasks = normalCycleTasks();
|
|
114
151
|
const avgCompletion = allTasks.length ? clampCompletion(allTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / allTasks.length) : 0;
|
|
115
152
|
return `<section class="sidebar-card">
|
|
116
153
|
<h3>${t("releaseHealth")}</h3>
|
|
@@ -119,13 +156,7 @@ function taskStatsCard() {
|
|
|
119
156
|
<span class="gauge-label">${t("statOverall")}</span>
|
|
120
157
|
</div>
|
|
121
158
|
<div class="stats-breakdown">
|
|
122
|
-
${
|
|
123
|
-
{ state: "in_progress", label: t("statInProgress"), colorVar: "--accent" },
|
|
124
|
-
{ state: "review", label: t("statReview"), colorVar: "--accent-2" },
|
|
125
|
-
{ state: "blocked", label: t("statBlocked"), colorVar: "--danger" },
|
|
126
|
-
{ state: "done", label: t("statDone"), colorVar: "--ok" }
|
|
127
|
-
].map(({ state, label, colorVar }) => {
|
|
128
|
-
const count = allTasks.filter(t => t.state === state).length;
|
|
159
|
+
${taskStatRows(allTasks).map(({ label, colorVar, count }) => {
|
|
129
160
|
return `<div class="stats-breakdown-row">
|
|
130
161
|
<span class="stat-label">
|
|
131
162
|
<span class="state-dot" style="background:var(${colorVar})"></span>
|
|
@@ -161,11 +192,7 @@ function taskLegendCard() {
|
|
|
161
192
|
}
|
|
162
193
|
|
|
163
194
|
function taskStatsBar() {
|
|
164
|
-
const allTasks =
|
|
165
|
-
const inProgress = allTasks.filter(t => t.state === "in_progress").length;
|
|
166
|
-
const blocked = allTasks.filter(t => t.state === "blocked").length;
|
|
167
|
-
const done = allTasks.filter(t => t.state === "done").length;
|
|
168
|
-
const review = allTasks.filter(t => t.state === "review").length;
|
|
195
|
+
const allTasks = normalCycleTasks();
|
|
169
196
|
const avgCompletion = allTasks.length ? clampCompletion(allTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / allTasks.length) : 0;
|
|
170
197
|
|
|
171
198
|
return `<section class="task-stats-bar">
|
|
@@ -173,22 +200,10 @@ function taskStatsBar() {
|
|
|
173
200
|
<span class="stat-value">${allTasks.length}</span>
|
|
174
201
|
<span class="stat-label">${t("statTotal")}</span>
|
|
175
202
|
</div>
|
|
176
|
-
|
|
177
|
-
<span class="stat-value">${
|
|
178
|
-
<span class="stat-label">${
|
|
179
|
-
</div
|
|
180
|
-
<div class="stat-chip review">
|
|
181
|
-
<span class="stat-value">${review}</span>
|
|
182
|
-
<span class="stat-label">${t("statReview")}</span>
|
|
183
|
-
</div>
|
|
184
|
-
<div class="stat-chip blocked">
|
|
185
|
-
<span class="stat-value">${blocked}</span>
|
|
186
|
-
<span class="stat-label">${t("statBlocked")}</span>
|
|
187
|
-
</div>
|
|
188
|
-
<div class="stat-chip done">
|
|
189
|
-
<span class="stat-value">${done}</span>
|
|
190
|
-
<span class="stat-label">${t("statDone")}</span>
|
|
191
|
-
</div>
|
|
203
|
+
${taskStatRows(allTasks).map((row) => `<div class="stat-chip ${escapeAttr(row.className)}" style="--stat-color: var(${row.colorVar})">
|
|
204
|
+
<span class="stat-value">${row.count}</span>
|
|
205
|
+
<span class="stat-label">${escapeHtml(row.label)}</span>
|
|
206
|
+
</div>`).join("")}
|
|
192
207
|
<div class="stat-chip completion">
|
|
193
208
|
<div class="stat-bar-track"><div class="stat-bar-fill" style="width:${avgCompletion}%"></div></div>
|
|
194
209
|
<div style="text-align:right">
|
|
@@ -205,12 +220,14 @@ function taskRow(task) {
|
|
|
205
220
|
const mapReady = !!taskDocument(task, "visual_map.md");
|
|
206
221
|
const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
|
|
207
222
|
const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
|
|
223
|
+
const moduleLabel = taskModuleLabel(task);
|
|
224
|
+
const lifecycle = [task.lifecycleState, task.reviewStatus, task.closeoutStatus].filter(Boolean).map((item) => label(item)).join(" · ");
|
|
208
225
|
|
|
209
226
|
return `<article class="task-row-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateToColorVar(task.state)})">
|
|
210
227
|
<div class="row-accent-bar"></div>
|
|
211
228
|
<div class="row-main">
|
|
212
229
|
<strong>${escapeHtml(task.title)}</strong>
|
|
213
|
-
<span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(
|
|
230
|
+
<span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(moduleLabel)}${lifecycle ? ` · ${escapeHtml(lifecycle)}` : ""}</span>
|
|
214
231
|
${taskCopyButton(task, "row-copy")}
|
|
215
232
|
</div>
|
|
216
233
|
<div class="row-status">${tag(task.state)}</div>
|
|
@@ -240,15 +257,16 @@ function taskIndex() {
|
|
|
240
257
|
const groupPageCount = Math.max(1, Math.ceil(orderedGroups.length / taskGroupsPerPage));
|
|
241
258
|
const groupPage = Math.min(Math.max(1, Number(state.taskGroupPage) || 1), groupPageCount);
|
|
242
259
|
const visibleGroups = orderedGroups.slice((groupPage - 1) * taskGroupsPerPage, groupPage * taskGroupsPerPage);
|
|
260
|
+
const swimlane = state.taskLayout === "swimlane";
|
|
243
261
|
|
|
244
262
|
return `<div class="tasks-grid">
|
|
245
263
|
<div class="tasks-main stack">
|
|
246
264
|
${taskStatsBar()}
|
|
247
|
-
${visibleGroups.map(([group, groupTasks]) => taskGroup(group, groupTasks)).join("")}
|
|
248
|
-
|
|
265
|
+
${swimlane ? taskSwimlane(tasks) : visibleGroups.map(([group, groupTasks]) => taskGroup(group, groupTasks)).join("")}
|
|
266
|
+
${swimlane ? "" : `<section class="group-pager">
|
|
249
267
|
<span>${t("showingGroups")} ${visibleGroups.length ? (groupPage - 1) * taskGroupsPerPage + 1 : 0}-${Math.min(groupPage * taskGroupsPerPage, orderedGroups.length)} / ${orderedGroups.length}</span>
|
|
250
268
|
${pager("task-groups", groupPage, groupPageCount)}
|
|
251
|
-
</section
|
|
269
|
+
</section>`}
|
|
252
270
|
</div>
|
|
253
271
|
<aside class="tasks-sidebar stack">
|
|
254
272
|
${taskToolbarCard(tasks.length)}
|
|
@@ -311,6 +329,7 @@ function taskGroup(group, tasks) {
|
|
|
311
329
|
const start = (page - 1) * taskPageSize;
|
|
312
330
|
const visibleTasks = orderedTasks.slice(start, start + taskPageSize);
|
|
313
331
|
const avgCompletion = orderedTasks.length ? clampCompletion(orderedTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / orderedTasks.length) : 0;
|
|
332
|
+
const groupContext = taskGroupContext(group, orderedTasks);
|
|
314
333
|
|
|
315
334
|
const isGrid = state.taskLayout === "grid";
|
|
316
335
|
const layoutClass = isGrid ? "task-card-grid" : "task-list";
|
|
@@ -326,8 +345,10 @@ function taskGroup(group, tasks) {
|
|
|
326
345
|
return `<section class="task-group">
|
|
327
346
|
<div class="section-head">
|
|
328
347
|
<div>
|
|
329
|
-
<
|
|
330
|
-
<
|
|
348
|
+
<p class="eyebrow">${escapeHtml(groupContext.eyebrow)}</p>
|
|
349
|
+
<h2>${escapeHtml(groupContext.title)}</h2>
|
|
350
|
+
<p class="subtle">${escapeHtml(groupContext.summary)} · ${t("showing")} ${Math.min(start + 1, orderedTasks.length)}-${Math.min(start + visibleTasks.length, orderedTasks.length)} / ${orderedTasks.length}</p>
|
|
351
|
+
${groupContext.chips.length ? `<div class="module-chip-row">${groupContext.chips.map((chip) => `<span class="module-chip">${escapeHtml(chip)}</span>`).join("")}</div>` : ""}
|
|
331
352
|
</div>
|
|
332
353
|
<div class="group-actions">
|
|
333
354
|
<div class="group-progress" aria-label="${escapeAttr(t("groupCompletion"))}">
|
|
@@ -351,6 +372,7 @@ function taskCard(task) {
|
|
|
351
372
|
const mapReady = !!taskDocument(task, "visual_map.md");
|
|
352
373
|
const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
|
|
353
374
|
const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
|
|
375
|
+
const lifecycle = [task.lifecycleState, task.reviewStatus, task.closeoutStatus].filter(Boolean).map((item) => label(item)).join(" · ");
|
|
354
376
|
|
|
355
377
|
return `<article class="task-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateColor})">
|
|
356
378
|
<div class="card-header">
|
|
@@ -364,8 +386,9 @@ function taskCard(task) {
|
|
|
364
386
|
<div class="card-meta">
|
|
365
387
|
<span class="meta-module" title="${escapeAttr(taskModuleKey(task))}">
|
|
366
388
|
<svg style="width:12px;height:12px;vertical-align:middle;margin-right:2px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
367
|
-
${escapeHtml(
|
|
389
|
+
${escapeHtml(taskModuleLabel(task))}
|
|
368
390
|
</span>
|
|
391
|
+
${lifecycle ? `<span class="meta-lifecycle" title="${escapeAttr(lifecycle)}">${escapeHtml(lifecycle)}</span>` : ""}
|
|
369
392
|
</div>
|
|
370
393
|
<div class="card-progress">
|
|
371
394
|
<div class="card-progress-track"><div class="card-progress-fill" style="width:${completion}%"></div></div>
|
|
@@ -388,7 +411,7 @@ function taskGroupLabel(group) {
|
|
|
388
411
|
if (group === "active") return t("activeCurrent");
|
|
389
412
|
if (group === "brief-ready") return t("briefReadyGroup");
|
|
390
413
|
if (group.startsWith("legacy:")) return `${t("legacyMonth")} ${group.slice("legacy:".length)}`;
|
|
391
|
-
if (group.startsWith("module:")) return
|
|
414
|
+
if (group.startsWith("module:")) return taskGroupContext(group, []).title;
|
|
392
415
|
if (group.startsWith("month:")) return `${t("legacyMonth")} ${group.slice("month:".length)}`;
|
|
393
416
|
if (group.startsWith("state:")) return `${t("columnState")} · ${label(group.slice("state:".length))}`;
|
|
394
417
|
return label(group);
|
|
@@ -396,7 +419,7 @@ function taskGroupLabel(group) {
|
|
|
396
419
|
|
|
397
420
|
function filteredTasks() {
|
|
398
421
|
const query = state.query.trim().toLowerCase();
|
|
399
|
-
return sortTasksByTime((
|
|
422
|
+
return sortTasksByTime(normalCycleTasks().filter((task) => {
|
|
400
423
|
const stateMatch = state.taskState === "all" || task.state === state.taskState;
|
|
401
424
|
if (!stateMatch) return false;
|
|
402
425
|
if (!query) return true;
|
|
@@ -407,3 +430,72 @@ function filteredTasks() {
|
|
|
407
430
|
function taskModuleKey(task) {
|
|
408
431
|
return task.module || task.inferredModule || "legacy-unclassified";
|
|
409
432
|
}
|
|
433
|
+
|
|
434
|
+
function taskModuleDisplayLabel(key) {
|
|
435
|
+
if (key === "base") return t("baseModule");
|
|
436
|
+
if (key === "legacy-unclassified") return t("unclassifiedModule");
|
|
437
|
+
return key;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function archiveView() {
|
|
441
|
+
const tasks = sortTasksByTime(archivedTasks());
|
|
442
|
+
const groups = Object.entries(groupBy(tasks, archiveBucket)).sort(([left], [right]) => left.localeCompare(right));
|
|
443
|
+
return `<main class="stack archive-view">
|
|
444
|
+
<section class="flow-panel">
|
|
445
|
+
<div class="section-head">
|
|
446
|
+
<div>
|
|
447
|
+
<p class="eyebrow">${t("archive")}</p>
|
|
448
|
+
<h2>${t("archiveView")}</h2>
|
|
449
|
+
<p class="subtle">${t("archiveSubtitle")}</p>
|
|
450
|
+
</div>
|
|
451
|
+
<a href="#/tasks">${t("openTaskIndex")}</a>
|
|
452
|
+
</div>
|
|
453
|
+
<div class="archive-summary-grid">
|
|
454
|
+
${metric(t("archivedTasks"), tasks.length)}
|
|
455
|
+
${metric(t("archiveBuckets"), groups.length)}
|
|
456
|
+
</div>
|
|
457
|
+
</section>
|
|
458
|
+
${groups.map(([bucket, bucketTasks]) => archiveGroup(bucket, bucketTasks)).join("") || emptyState(t("noArchivedTasks"))}
|
|
459
|
+
</main>`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function archiveGroup(bucket, tasks) {
|
|
463
|
+
const orderedTasks = sortTasksByTime(tasks);
|
|
464
|
+
return `<section class="archive-group">
|
|
465
|
+
<div class="section-head">
|
|
466
|
+
<div>
|
|
467
|
+
<h2>${escapeHtml(bucket)}</h2>
|
|
468
|
+
<p class="subtle">${orderedTasks.length} ${t("tasks")}</p>
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="archive-task-list">
|
|
472
|
+
${orderedTasks.map(archiveTaskRow).join("")}
|
|
473
|
+
</div>
|
|
474
|
+
</section>`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function archiveTaskRow(task) {
|
|
478
|
+
const archiveMetadata = task.archiveMetadata || {};
|
|
479
|
+
const archivedBy = archiveMetadata?.["archived by"] || t("unknown");
|
|
480
|
+
const archivedAt = archiveMetadata?.["archived at"] || "";
|
|
481
|
+
const reviewConfirmedBy = archiveMetadata?.["review confirmed by"] || t("unknown");
|
|
482
|
+
const reviewConfirmedAt = archiveMetadata?.["review confirmed at"] || "";
|
|
483
|
+
const reviewConfirmationId = archiveMetadata?.["review confirmation id"] || "";
|
|
484
|
+
const releasePackage = archiveMetadata?.["release package"] || "";
|
|
485
|
+
const reason = task.deleteReason || archiveMetadata?.reason || "";
|
|
486
|
+
return `<article class="archive-task-row">
|
|
487
|
+
<div class="archive-task-main">
|
|
488
|
+
<a href="#/tasks/${encodeURIComponent(task.id)}">${escapeHtml(task.title || task.id)}</a>
|
|
489
|
+
<span>${escapeHtml(task.id)}</span>
|
|
490
|
+
${reason ? `<p>${escapeHtml(reason)}</p>` : ""}
|
|
491
|
+
</div>
|
|
492
|
+
<dl class="archive-meta-grid">
|
|
493
|
+
<div><dt>${t("archivedBy")}</dt><dd>${escapeHtml(archivedBy)}</dd></div>
|
|
494
|
+
<div><dt>${t("archivedAt")}</dt><dd>${escapeHtml(archivedAt || t("unknown"))}</dd></div>
|
|
495
|
+
<div><dt>${t("reviewConfirmedBy")}</dt><dd>${escapeHtml(reviewConfirmedBy)}</dd></div>
|
|
496
|
+
<div><dt>${t("reviewConfirmedAt")}</dt><dd>${escapeHtml(reviewConfirmedAt || t("unknown"))}</dd></div>
|
|
497
|
+
${reviewConfirmationId ? `<div><dt>${t("reviewConfirmationId")}</dt><dd>${escapeHtml(reviewConfirmationId)}</dd></div>` : ""}
|
|
498
|
+
${releasePackage ? `<div><dt>${t("releasePackage")}</dt><dd>${escapeHtml(releasePackage)}</dd></div>` : ""}
|
|
499
|
+
</dl>
|
|
500
|
+
</article>`;
|
|
501
|
+
}
|