@aipper/aiws 0.0.24 → 0.0.26
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/README.md +89 -33
- package/package.json +3 -3
- package/src/cli.js +40 -2
- package/src/codex-skills.js +4 -4
- package/src/commands/change-advice.js +191 -0
- package/src/commands/change-evidence-command.js +242 -0
- package/src/commands/change-evidence-entry.js +40 -0
- package/src/commands/change-evidence.js +338 -0
- package/src/commands/change-finish.js +484 -0
- package/src/commands/change-git-status.js +53 -0
- package/src/commands/change-lifecycle-entry.js +60 -0
- package/src/commands/change-lifecycle.js +234 -0
- package/src/commands/change-metrics-command.js +110 -0
- package/src/commands/change-next-command.js +132 -0
- package/src/commands/change-review-gates.js +315 -0
- package/src/commands/change-scope-gate.js +34 -0
- package/src/commands/change-start.js +138 -0
- package/src/commands/change-state-command.js +40 -0
- package/src/commands/change-status-command.js +126 -0
- package/src/commands/change-status-context.js +202 -0
- package/src/commands/change-validate-entry.js +45 -0
- package/src/commands/change-validate.js +127 -0
- package/src/commands/change.js +540 -1692
- package/src/commands/dashboard.js +38 -12
- package/src/commands/init.js +1 -1
- package/src/commands/opencode-status.js +61 -0
- package/src/commands/update.js +5 -1
- package/src/dashboard/app.js +205 -2
- package/src/dashboard/index.html +5 -2
- package/src/dashboard/style.css +86 -0
- package/src/governance.js +157 -0
- package/src/json-schema-lite.js +164 -0
- package/src/opencode-env.js +76 -0
- package/src/spec.js +131 -0
- package/src/template.js +42 -2
|
@@ -5,7 +5,8 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { UserError } from "../errors.js";
|
|
6
6
|
import { runCommand } from "../exec.js";
|
|
7
7
|
import { pathExists } from "../fs.js";
|
|
8
|
-
import {
|
|
8
|
+
import { loadWorkflowStageContracts as loadWorkflowStageContractsSpec } from "../spec.js";
|
|
9
|
+
import { computeChangeNextAdvice, computeChangeStatus, validateChangeArtifacts } from "./change.js";
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -82,6 +83,33 @@ async function listChangeIds(gitRoot) {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
export async function loadWorkflowStageContracts() {
|
|
87
|
+
return loadWorkflowStageContractsSpec();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} gitRoot
|
|
92
|
+
*/
|
|
93
|
+
export async function collectDashboardChanges(gitRoot) {
|
|
94
|
+
const ids = await listChangeIds(gitRoot);
|
|
95
|
+
const changes = [];
|
|
96
|
+
for (const id of ids) {
|
|
97
|
+
try {
|
|
98
|
+
const st = await computeChangeStatus(gitRoot, id);
|
|
99
|
+
const nextAdvice = await computeChangeNextAdvice(gitRoot, id);
|
|
100
|
+
changes.push({
|
|
101
|
+
...st,
|
|
102
|
+
collaboration: st.collaboration?.counts || null,
|
|
103
|
+
nextAdvice,
|
|
104
|
+
governance: st.governance || null,
|
|
105
|
+
});
|
|
106
|
+
} catch (e) {
|
|
107
|
+
changes.push({ changeId: id, ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return changes;
|
|
111
|
+
}
|
|
112
|
+
|
|
85
113
|
function parseUrl(reqUrl) {
|
|
86
114
|
const u = new URL(reqUrl || "/", "http://127.0.0.1");
|
|
87
115
|
return { pathname: u.pathname, searchParams: u.searchParams };
|
|
@@ -109,26 +137,24 @@ export async function dashboardServeCommand(options) {
|
|
|
109
137
|
}
|
|
110
138
|
|
|
111
139
|
if (pathname === "/api/changes") {
|
|
112
|
-
const
|
|
113
|
-
const changes = [];
|
|
114
|
-
for (const id of ids) {
|
|
115
|
-
try {
|
|
116
|
-
const st = await computeChangeStatus(gitRoot, id);
|
|
117
|
-
changes.push(st);
|
|
118
|
-
} catch (e) {
|
|
119
|
-
changes.push({ changeId: id, ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
140
|
+
const changes = await collectDashboardChanges(gitRoot);
|
|
122
141
|
sendJson(res, 200, { ok: true, changes });
|
|
123
142
|
return;
|
|
124
143
|
}
|
|
125
144
|
|
|
145
|
+
if (pathname === "/api/workflow-stages") {
|
|
146
|
+
const stages = await loadWorkflowStageContracts();
|
|
147
|
+
sendJson(res, 200, stages);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
const m = pathname.match(/^\/api\/change\/([^/]+)\/validate$/);
|
|
127
152
|
if (m) {
|
|
128
153
|
const changeId = decodeURIComponent(m[1] || "");
|
|
129
154
|
const strict = searchParams.get("strict") === "1" || searchParams.get("strict") === "true";
|
|
130
155
|
const allowTruthDrift = searchParams.get("allow_truth_drift") === "1" || searchParams.get("allow_truth_drift") === "true";
|
|
131
|
-
const
|
|
156
|
+
const checkScope = searchParams.get("check_scope") === "1" || searchParams.get("check_scope") === "true";
|
|
157
|
+
const result = await validateChangeArtifacts(gitRoot, changeId, { strict, allowTruthDrift, checkScope });
|
|
132
158
|
sendJson(res, 200, result);
|
|
133
159
|
return;
|
|
134
160
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function initCommand(options) {
|
|
|
19
19
|
|
|
20
20
|
const defaults = tpl.manifest.defaults || {};
|
|
21
21
|
const includeOptional = defaults.include_optional !== false;
|
|
22
|
-
const tools = Array.isArray(defaults.tools) ? defaults.tools.map(String) : ["claude", "opencode", "codex"
|
|
22
|
+
const tools = Array.isArray(defaults.tools) ? defaults.tools.map(String) : ["claude", "opencode", "codex"];
|
|
23
23
|
|
|
24
24
|
const required = await expandManifestEntries(tpl.templateDir, tpl.manifest.required || []);
|
|
25
25
|
const optional = includeOptional ? await expandManifestEntries(tpl.templateDir, tpl.manifest.optional || []) : [];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
2
|
+
import { detectOpenCodeEnvironment } from "../opencode-env.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {{ targetPath: string }} options
|
|
6
|
+
*/
|
|
7
|
+
export async function opencodeStatusCommand(options) {
|
|
8
|
+
const workspaceRoot = await resolveWorkspaceRoot(options.targetPath, { create: false });
|
|
9
|
+
const env = await detectOpenCodeEnvironment(workspaceRoot);
|
|
10
|
+
|
|
11
|
+
console.log(`✓ aiws opencode status: ${workspaceRoot}`);
|
|
12
|
+
console.log(`.opencode/: ${env.opencodeDirExists ? "ok" : "missing"}`);
|
|
13
|
+
console.log(`mode: ${env.mode}`);
|
|
14
|
+
console.log(
|
|
15
|
+
`oMo config: ${
|
|
16
|
+
env.configStatus === "loaded"
|
|
17
|
+
? `${env.rel.configPath} (ok)`
|
|
18
|
+
: env.configStatus === "invalid"
|
|
19
|
+
? `${env.rel.configPath} (invalid JSON: ${env.configError})`
|
|
20
|
+
: "missing"
|
|
21
|
+
}`,
|
|
22
|
+
);
|
|
23
|
+
console.log(`oMo example: ${env.exampleExists ? `${env.rel.examplePath} (present)` : "missing"}`);
|
|
24
|
+
console.log(`recommended_agents_ready: ${env.recommendedAgentsReady}`);
|
|
25
|
+
console.log(`enabled_agents: ${env.enabledAgents.length > 0 ? env.enabledAgents.join(", ") : "(none)"}`);
|
|
26
|
+
|
|
27
|
+
const planner = env.agents["planner-sisyphus"];
|
|
28
|
+
const librarian = env.agents.librarian;
|
|
29
|
+
const explore = env.agents.explore;
|
|
30
|
+
const oracle = env.agents.oracle;
|
|
31
|
+
console.log(
|
|
32
|
+
`aiws_roles: planner=${planner.enabled ? "planner-sisyphus" : "missing"}, explorer=${[
|
|
33
|
+
explore.enabled ? "explore" : "",
|
|
34
|
+
librarian.enabled ? "librarian" : "",
|
|
35
|
+
]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join("+") || "missing"}, reviewer=${oracle.enabled ? "oracle" : "missing"}, integrator=current-agent`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (planner.present) {
|
|
41
|
+
console.log(`planner-sisyphus.replace_plan: ${planner.replacePlan === null ? "(unset)" : String(planner.replacePlan)}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (env.configStatus === "invalid") {
|
|
45
|
+
console.log(`Next: 修复 ${env.rel.configPath} 的 JSON,然后重新运行 aiws opencode status .`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!env.configExists && env.exampleExists) {
|
|
49
|
+
console.log(`Next: 如需启用 oMo 优先模式,可复制 ${env.rel.examplePath} -> ${env.rel.configPath} 后按项目调整 agents。`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (env.mode === "oMo-enabled" && !env.recommendedAgentsReady) {
|
|
53
|
+
console.log("Next: 补齐 planner-sisyphus / librarian / explore / oracle,或接受 fallback 到 standard-opencode。");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (env.mode === "oMo-enabled") {
|
|
57
|
+
console.log("Next: 运行 /using-aiws 或 /ws-preflight,确认技能路由输出 OpenCode mode: oMo-enabled。");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log("Next: 当前按 standard-opencode 运行;如需多 agent 优先委托,可启用 oh-my-opencode 项目配置。");
|
|
61
|
+
}
|
package/src/commands/update.js
CHANGED
|
@@ -31,6 +31,9 @@ export async function updateCommand(options) {
|
|
|
31
31
|
.filter((entry) => entry && typeof entry === "object")
|
|
32
32
|
.map((entry) => [normalizeRel(String(entry.path || "")), entry]),
|
|
33
33
|
);
|
|
34
|
+
const defaults = tpl.manifest.defaults || {};
|
|
35
|
+
const defaultTools = Array.isArray(defaults.tools) ? defaults.tools.map(String) : ["claude", "opencode", "codex"];
|
|
36
|
+
const allowedTools = new Set(defaultTools);
|
|
34
37
|
|
|
35
38
|
const update = tpl.manifest.update || {};
|
|
36
39
|
const replaceFiles = (update.replace_file || []).map(normalizeRel);
|
|
@@ -112,7 +115,8 @@ export async function updateCommand(options) {
|
|
|
112
115
|
|
|
113
116
|
const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
114
117
|
const installedAt = String(stored.installed_at || now);
|
|
115
|
-
const
|
|
118
|
+
const storedTools = Array.isArray(stored.tools) ? stored.tools.map(String).filter((tool) => allowedTools.has(tool)) : [];
|
|
119
|
+
const tools = storedTools.length > 0 ? storedTools : defaultTools;
|
|
116
120
|
|
|
117
121
|
await writeWorkspaceManifest({
|
|
118
122
|
workspaceRoot,
|
package/src/dashboard/app.js
CHANGED
|
@@ -38,6 +38,14 @@ function tasksBadge(done, total, unchecked) {
|
|
|
38
38
|
return `<span class="badge"><span class="dot ${cls}"></span>${label}</span>`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function summarizeNextAdvice(advice) {
|
|
42
|
+
const a = advice && typeof advice === "object" ? advice : {};
|
|
43
|
+
const actions = Array.isArray(a.actions) ? a.actions : [];
|
|
44
|
+
const recommended = Array.isArray(a.recommended) ? a.recommended : [];
|
|
45
|
+
const prohibitions = Array.isArray(a.prohibitions) ? a.prohibitions : [];
|
|
46
|
+
return actions[0] || recommended[0] || prohibitions[0] || "";
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
function renderDetails(obj) {
|
|
42
50
|
return JSON.stringify(obj, null, 2);
|
|
43
51
|
}
|
|
@@ -48,6 +56,8 @@ function renderValidateGroups(groups) {
|
|
|
48
56
|
["missing_files", "Missing files"],
|
|
49
57
|
["placeholders", "Placeholders / WS:TODO"],
|
|
50
58
|
["bindings", "Bindings"],
|
|
59
|
+
["scope_gate", "Scope gate"],
|
|
60
|
+
["finish_gate", "Finish gate"],
|
|
51
61
|
["plan_quality", "Plan quality"],
|
|
52
62
|
["other", "Other"],
|
|
53
63
|
];
|
|
@@ -70,20 +80,148 @@ async function runStrictValidate(changeId) {
|
|
|
70
80
|
return fetchJson(`/api/change/${encodeURIComponent(changeId)}/validate?strict=1`);
|
|
71
81
|
}
|
|
72
82
|
|
|
83
|
+
function normalizeStageId(value) {
|
|
84
|
+
return String(value || "").trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stageLabel(stageId) {
|
|
88
|
+
return normalizeStageId(stageId) || "(unknown)";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderContractField(label, value) {
|
|
92
|
+
return `<div class="contract-field">
|
|
93
|
+
<div class="contract-field-label">${escapeHtml(label)}</div>
|
|
94
|
+
<div>${escapeHtml(value || "(none)")}</div>
|
|
95
|
+
</div>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderContractCard(title, contract) {
|
|
99
|
+
if (!contract) return "";
|
|
100
|
+
return `<div class="contract-card">
|
|
101
|
+
<div class="contract-title">${escapeHtml(title)}</div>
|
|
102
|
+
<div class="contract-stage"><code>${escapeHtml(contract.stage || "")}</code></div>
|
|
103
|
+
${renderContractField("目标", contract.goal)}
|
|
104
|
+
${renderContractField("必需输入", contract.requiredInputs)}
|
|
105
|
+
${renderContractField("必需输出", contract.requiredOutputs)}
|
|
106
|
+
${renderContractField("阻断条件", contract.blockers)}
|
|
107
|
+
${renderContractField("下一步", contract.next)}
|
|
108
|
+
</div>`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeGateStatus(value) {
|
|
112
|
+
return String(value || "missing").trim() || "missing";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function reviewGateText(reviewGates) {
|
|
116
|
+
const gates = reviewGates && typeof reviewGates === "object" ? reviewGates : {};
|
|
117
|
+
const spec = normalizeGateStatus(gates?.specReview?.status);
|
|
118
|
+
const quality = normalizeGateStatus(gates?.qualityReview?.status);
|
|
119
|
+
const validate = normalizeGateStatus(gates?.validateStamp?.status);
|
|
120
|
+
const preComplete = normalizeGateStatus(gates?.verifyBeforeComplete?.status);
|
|
121
|
+
return `review-gates: spec=${spec}, quality=${quality}, validate=${validate}, pre-complete=${preComplete}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderReviewGateBadges(reviewGates) {
|
|
125
|
+
const gates = reviewGates && typeof reviewGates === "object" ? reviewGates : {};
|
|
126
|
+
const items = [
|
|
127
|
+
["spec", gates?.specReview],
|
|
128
|
+
["quality", gates?.qualityReview],
|
|
129
|
+
["validate", gates?.validateStamp],
|
|
130
|
+
["pre-complete", gates?.verifyBeforeComplete],
|
|
131
|
+
];
|
|
132
|
+
return `<div class="governance-head">${items
|
|
133
|
+
.map(([label, gate]) => {
|
|
134
|
+
const ready = gate?.ready === true;
|
|
135
|
+
const status = normalizeGateStatus(gate?.status);
|
|
136
|
+
const cls = ready ? "good" : "warn";
|
|
137
|
+
return `<span class="badge"><span class="dot ${cls}"></span>${escapeHtml(`${label}=${status}`)}</span>`;
|
|
138
|
+
})
|
|
139
|
+
.join("")}</div>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function scopeGateText(scopeGate) {
|
|
143
|
+
const gate = scopeGate && typeof scopeGate === "object" ? scopeGate : {};
|
|
144
|
+
const status = String(gate.status || "not_run").trim() || "not_run";
|
|
145
|
+
const details = [];
|
|
146
|
+
if (typeof gate.errors === "number") details.push(`errors=${gate.errors}`);
|
|
147
|
+
if (typeof gate.warnings === "number") details.push(`warnings=${gate.warnings}`);
|
|
148
|
+
if (gate.lastRunAt) details.push(`at=${gate.lastRunAt}`);
|
|
149
|
+
return `scope-gate: ${status}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderScopeGateBadge(scopeGate) {
|
|
153
|
+
const gate = scopeGate && typeof scopeGate === "object" ? scopeGate : {};
|
|
154
|
+
const status = String(gate.status || "not_run").trim() || "not_run";
|
|
155
|
+
const ready = gate.ready === true;
|
|
156
|
+
const cls = ready ? "good" : status === "failed" ? "bad" : "warn";
|
|
157
|
+
return `<span class="badge"><span class="dot ${cls}"></span>${escapeHtml(`scope=${status}`)}</span>`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderGovernanceSummary(selected, stageData) {
|
|
161
|
+
const governance = selected?.governance && typeof selected.governance === "object" ? selected.governance : {};
|
|
162
|
+
const currentStage = normalizeStageId(governance.currentStage);
|
|
163
|
+
const recommendedStage = normalizeStageId(governance.recommendedStage);
|
|
164
|
+
const phase = normalizeStageId(governance.phase || selected?.phase);
|
|
165
|
+
const contracts = Array.isArray(stageData?.stages) ? stageData.stages : [];
|
|
166
|
+
const byStage = new Map(contracts.map((contract) => [normalizeStageId(contract.stage), contract]));
|
|
167
|
+
const currentContract = byStage.get(currentStage) || null;
|
|
168
|
+
const recommendedContract = byStage.get(recommendedStage) || null;
|
|
169
|
+
|
|
170
|
+
const rail = contracts.length > 0
|
|
171
|
+
? `<div class="governance-rail">${contracts
|
|
172
|
+
.map((contract, index) => {
|
|
173
|
+
const stage = normalizeStageId(contract.stage);
|
|
174
|
+
const cls = [
|
|
175
|
+
"stage-chip",
|
|
176
|
+
stage === currentStage ? "active" : "",
|
|
177
|
+
stage === recommendedStage && stage !== currentStage ? "recommended" : "",
|
|
178
|
+
]
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join(" ");
|
|
181
|
+
const arrow = index < contracts.length - 1 ? `<span class="rail-arrow">→</span>` : "";
|
|
182
|
+
return `<span class="${cls}"><code>${escapeHtml(stageLabel(stage))}</code></span>${arrow}`;
|
|
183
|
+
})
|
|
184
|
+
.join("")}</div>`
|
|
185
|
+
: `<div class="muted">(workflow-stage-contracts unavailable)</div>`;
|
|
186
|
+
|
|
187
|
+
const cards = [];
|
|
188
|
+
if (currentContract) cards.push(renderContractCard(`Current Contract (${phase || "unknown"})`, currentContract));
|
|
189
|
+
if (recommendedContract && recommendedStage && recommendedStage !== currentStage) {
|
|
190
|
+
cards.push(renderContractCard("Next Contract", recommendedContract));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return `<div class="governance-head">
|
|
194
|
+
<span class="badge"><span class="dot good"></span>phase=${escapeHtml(phase || "(unknown)")}</span>
|
|
195
|
+
<span class="badge"><span class="dot warn"></span>current=${escapeHtml(stageLabel(currentStage))}</span>
|
|
196
|
+
<span class="badge"><span class="dot good"></span>next=${escapeHtml(stageLabel(recommendedStage || currentStage))}</span>
|
|
197
|
+
</div>
|
|
198
|
+
${governance.rationale ? `<div class="muted">${escapeHtml(governance.rationale)}</div>` : ""}
|
|
199
|
+
${governance.warning ? `<div class="muted">warning: ${escapeHtml(governance.warning)}</div>` : ""}
|
|
200
|
+
${selected?.reviewGates ? renderReviewGateBadges(selected.reviewGates) : ""}
|
|
201
|
+
${selected?.scopeGate ? `<div class="governance-head">${renderScopeGateBadge(selected.scopeGate)}</div>` : ""}
|
|
202
|
+
${rail}
|
|
203
|
+
<div class="governance-grid">
|
|
204
|
+
${cards.join("") || `<div class="muted">No contract summary available.</div>`}
|
|
205
|
+
</div>`;
|
|
206
|
+
}
|
|
207
|
+
|
|
73
208
|
async function refresh() {
|
|
74
209
|
const summaryEl = document.getElementById("summary");
|
|
75
210
|
const bodyEl = document.getElementById("changes-body");
|
|
76
211
|
const detailsTitleEl = document.getElementById("details-title");
|
|
77
212
|
const detailsEl = document.getElementById("details");
|
|
213
|
+
const governanceEl = document.getElementById("governance-summary");
|
|
78
214
|
|
|
79
215
|
summaryEl.textContent = "Loading…";
|
|
80
216
|
bodyEl.innerHTML = "";
|
|
217
|
+
if (governanceEl) governanceEl.innerHTML = `<div class="muted">Loading workflow governance…</div>`;
|
|
81
218
|
|
|
82
|
-
const data = await fetchJson("/api/changes");
|
|
219
|
+
const [data, stageData] = await Promise.all([fetchJson("/api/changes"), fetchJson("/api/workflow-stages")]);
|
|
83
220
|
if (!data || data.ok === false) {
|
|
84
221
|
summaryEl.textContent = "Failed";
|
|
85
222
|
detailsTitleEl.textContent = "Error";
|
|
86
223
|
detailsEl.textContent = renderDetails(data);
|
|
224
|
+
if (governanceEl) governanceEl.innerHTML = `<div class="muted">Failed to load governance summary.</div>`;
|
|
87
225
|
return;
|
|
88
226
|
}
|
|
89
227
|
|
|
@@ -99,8 +237,24 @@ async function refresh() {
|
|
|
99
237
|
const bindings = c.bindings || {};
|
|
100
238
|
const planFile = bindings.planFile || "";
|
|
101
239
|
const evidencePaths = Array.isArray(bindings.evidencePaths) ? bindings.evidencePaths : [];
|
|
240
|
+
const collaboration = c.collaboration && typeof c.collaboration === "object" ? c.collaboration : {};
|
|
241
|
+
const nextHint = summarizeNextAdvice(c.nextAdvice);
|
|
242
|
+
const governance = c.governance && typeof c.governance === "object" ? c.governance : {};
|
|
243
|
+
const reviewGateLine = reviewGateText(c.reviewGates);
|
|
244
|
+
const scopeGateLine = scopeGateText(c.scopeGate);
|
|
245
|
+
const collabLabel = `analysis=${collaboration.analysis || 0}, patches=${collaboration.patches || 0}, review=${collaboration.review || 0}`;
|
|
246
|
+
const stageLine = `phase=${governance.phase || c.phase || "unknown"}, current=${governance.currentStage || "unknown"}, next=${governance.recommendedStage || governance.currentStage || "unknown"}`;
|
|
247
|
+
const rationaleLine = governance.rationale ? `<div class="muted">${escapeHtml(governance.rationale)}</div>` : "";
|
|
248
|
+
const warningLine = governance.warning ? `<div class="muted">warning: ${escapeHtml(governance.warning)}</div>` : "";
|
|
102
249
|
const pe = `${planFile ? `<div><code>${escapeHtml(planFile)}</code></div>` : `<div class="muted">(no Plan_File)</div>`}
|
|
103
|
-
<div class="muted">${evidencePaths.length} evidence path(s)</div
|
|
250
|
+
<div class="muted">${evidencePaths.length} evidence path(s)</div>
|
|
251
|
+
<div class="muted">${escapeHtml(collabLabel)}</div>
|
|
252
|
+
<div class="muted">${escapeHtml(stageLine)}</div>
|
|
253
|
+
<div class="muted">${escapeHtml(reviewGateLine)}</div>
|
|
254
|
+
<div class="muted">${escapeHtml(scopeGateLine)}</div>
|
|
255
|
+
${rationaleLine}
|
|
256
|
+
${warningLine}
|
|
257
|
+
${nextHint ? `<div class="muted">${escapeHtml(nextHint)}</div>` : ""}`;
|
|
104
258
|
return `<tr data-change="${escapeHtml(changeId)}">
|
|
105
259
|
<td><a class="link" href="#" data-open="${escapeHtml(changeId)}">${escapeHtml(changeId)}</a></td>
|
|
106
260
|
<td>${tasksBadge(tasks.done || 0, tasks.total || 0, tasks.unchecked || 0)}</td>
|
|
@@ -122,9 +276,12 @@ async function refresh() {
|
|
|
122
276
|
detailsTitleEl.textContent = changeId ? `change: ${changeId}` : "Details";
|
|
123
277
|
if (!selected) {
|
|
124
278
|
detailsEl.textContent = renderDetails(selected);
|
|
279
|
+
if (governanceEl) governanceEl.innerHTML = `<div class="muted">No governance summary available.</div>`;
|
|
125
280
|
return;
|
|
126
281
|
}
|
|
282
|
+
if (governanceEl) governanceEl.innerHTML = renderGovernanceSummary(selected, stageData);
|
|
127
283
|
const b = selected.bindings || {};
|
|
284
|
+
const governance = selected.governance && typeof selected.governance === "object" ? selected.governance : {};
|
|
128
285
|
const evid = Array.isArray(b.evidencePaths) ? b.evidencePaths : [];
|
|
129
286
|
const lines = [];
|
|
130
287
|
lines.push(`changeId: ${selected.changeId || ""}`);
|
|
@@ -136,6 +293,52 @@ async function refresh() {
|
|
|
136
293
|
lines.push("Evidence_Path:");
|
|
137
294
|
for (const p of evid) lines.push(`- ${p}`);
|
|
138
295
|
}
|
|
296
|
+
if (selected.collaboration && typeof selected.collaboration === "object") {
|
|
297
|
+
lines.push("Collaboration:");
|
|
298
|
+
lines.push(`- analysis: ${selected.collaboration.analysis || 0}`);
|
|
299
|
+
lines.push(`- patches: ${selected.collaboration.patches || 0}`);
|
|
300
|
+
lines.push(`- review: ${selected.collaboration.review || 0}`);
|
|
301
|
+
lines.push(`- evidence: ${selected.collaboration.evidence || 0}`);
|
|
302
|
+
}
|
|
303
|
+
if (selected.reviewGates && typeof selected.reviewGates === "object") {
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push("Review gates:");
|
|
306
|
+
lines.push(`- ${reviewGateText(selected.reviewGates)}`);
|
|
307
|
+
for (const [label, gate] of [
|
|
308
|
+
["ws-spec-review", selected.reviewGates.specReview],
|
|
309
|
+
["ws-quality-review", selected.reviewGates.qualityReview],
|
|
310
|
+
["validate-stamp", selected.reviewGates.validateStamp],
|
|
311
|
+
["ws-verify-before-complete", selected.reviewGates.verifyBeforeComplete],
|
|
312
|
+
]) {
|
|
313
|
+
if (!gate || typeof gate !== "object") continue;
|
|
314
|
+
const foundPath = gate.foundPath ? ` path=${gate.foundPath}` : "";
|
|
315
|
+
lines.push(`- ${label}: ready=${gate.ready === true} status=${normalizeGateStatus(gate.status)}${foundPath}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (selected.scopeGate && typeof selected.scopeGate === "object") {
|
|
319
|
+
lines.push("");
|
|
320
|
+
lines.push("Scope gate:");
|
|
321
|
+
lines.push(`- ${scopeGateText(selected.scopeGate)}`);
|
|
322
|
+
}
|
|
323
|
+
if (selected.nextAdvice && typeof selected.nextAdvice === "object") {
|
|
324
|
+
const actions = Array.isArray(selected.nextAdvice.actions) ? selected.nextAdvice.actions : [];
|
|
325
|
+
const recommended = Array.isArray(selected.nextAdvice.recommended) ? selected.nextAdvice.recommended : [];
|
|
326
|
+
const prohibitions = Array.isArray(selected.nextAdvice.prohibitions) ? selected.nextAdvice.prohibitions : [];
|
|
327
|
+
if (actions.length > 0 || recommended.length > 0 || prohibitions.length > 0) {
|
|
328
|
+
lines.push("");
|
|
329
|
+
lines.push("Next:");
|
|
330
|
+
for (const x of actions) lines.push(`- action: ${x}`);
|
|
331
|
+
for (const x of recommended) lines.push(`- recommended: ${x}`);
|
|
332
|
+
for (const x of prohibitions) lines.push(`- do-not: ${x}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (governance.rationale) {
|
|
336
|
+
lines.push("");
|
|
337
|
+
lines.push(`Governance rationale: ${governance.rationale}`);
|
|
338
|
+
}
|
|
339
|
+
if (governance.warning) {
|
|
340
|
+
lines.push(`Governance warning: ${governance.warning}`);
|
|
341
|
+
}
|
|
139
342
|
if (Array.isArray(selected.blockersStrict) && selected.blockersStrict.length > 0) {
|
|
140
343
|
lines.push("");
|
|
141
344
|
lines.push("Blockers (strict):");
|
package/src/dashboard/index.html
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<th>Tasks</th>
|
|
35
35
|
<th>Truth drift</th>
|
|
36
36
|
<th>Blockers (strict)</th>
|
|
37
|
-
<th>Plan
|
|
37
|
+
<th>Plan / Evidence / Next</th>
|
|
38
38
|
<th>Actions</th>
|
|
39
39
|
</tr>
|
|
40
40
|
</thead>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</table>
|
|
43
43
|
</div>
|
|
44
44
|
<div class="hint">
|
|
45
|
-
|
|
45
|
+
建议:先看 Details 里的 <code>Next</code>,优先通过 <code>aiws change validate <id> --strict</code>;simple/local 单点修复优先 <code>$ws-dev-lite</code>,否则按阶段进入 <code>$ws-dev</code> 或 <code>$ws-deliver</code>。
|
|
46
46
|
</div>
|
|
47
47
|
</section>
|
|
48
48
|
|
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
<div class="panel-title">Details</div>
|
|
52
52
|
<div class="panel-meta" id="details-title">Select a change</div>
|
|
53
53
|
</div>
|
|
54
|
+
<div class="governance" id="governance-summary">
|
|
55
|
+
<div class="muted">Select a change to inspect workflow stage contracts.</div>
|
|
56
|
+
</div>
|
|
54
57
|
<pre class="log" id="details"></pre>
|
|
55
58
|
</section>
|
|
56
59
|
</main>
|
package/src/dashboard/style.css
CHANGED
|
@@ -205,6 +205,92 @@ code {
|
|
|
205
205
|
font-size: 12px;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
.governance {
|
|
209
|
+
padding: 14px;
|
|
210
|
+
border-bottom: 1px solid var(--border);
|
|
211
|
+
background: rgba(255, 255, 255, 0.02);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.governance-head {
|
|
215
|
+
display: flex;
|
|
216
|
+
flex-wrap: wrap;
|
|
217
|
+
gap: 8px;
|
|
218
|
+
margin-bottom: 12px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.governance-rail {
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-wrap: wrap;
|
|
224
|
+
gap: 6px;
|
|
225
|
+
align-items: center;
|
|
226
|
+
margin-bottom: 12px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.rail-arrow {
|
|
230
|
+
color: var(--muted);
|
|
231
|
+
font-size: 12px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.stage-chip {
|
|
235
|
+
display: inline-flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
gap: 6px;
|
|
238
|
+
padding: 5px 9px;
|
|
239
|
+
border-radius: 999px;
|
|
240
|
+
border: 1px solid var(--border);
|
|
241
|
+
font-size: 12px;
|
|
242
|
+
color: var(--muted);
|
|
243
|
+
background: rgba(255, 255, 255, 0.03);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.stage-chip.active {
|
|
247
|
+
color: var(--text);
|
|
248
|
+
border-color: rgba(61, 220, 151, 0.55);
|
|
249
|
+
background: rgba(61, 220, 151, 0.14);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.stage-chip.recommended {
|
|
253
|
+
color: var(--text);
|
|
254
|
+
border-color: rgba(138, 180, 255, 0.55);
|
|
255
|
+
background: rgba(138, 180, 255, 0.14);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.governance-grid {
|
|
259
|
+
display: grid;
|
|
260
|
+
grid-template-columns: 1fr;
|
|
261
|
+
gap: 10px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.contract-card {
|
|
265
|
+
border: 1px solid var(--border);
|
|
266
|
+
border-radius: 12px;
|
|
267
|
+
background: rgba(255, 255, 255, 0.04);
|
|
268
|
+
padding: 12px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.contract-title {
|
|
272
|
+
font-weight: 700;
|
|
273
|
+
margin-bottom: 8px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.contract-stage {
|
|
277
|
+
color: var(--link);
|
|
278
|
+
font-size: 12px;
|
|
279
|
+
margin-bottom: 8px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.contract-field {
|
|
283
|
+
margin-top: 8px;
|
|
284
|
+
font-size: 12px;
|
|
285
|
+
line-height: 1.45;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.contract-field-label {
|
|
289
|
+
color: rgba(255, 255, 255, 0.78);
|
|
290
|
+
font-weight: 600;
|
|
291
|
+
margin-bottom: 3px;
|
|
292
|
+
}
|
|
293
|
+
|
|
208
294
|
.log {
|
|
209
295
|
margin: 0;
|
|
210
296
|
padding: 12px 14px;
|