@deksden-com/dd-flow-cli 0.2.0 → 0.3.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/README.md +2 -0
- package/dist/build-info.json +10 -6
- package/dist/cli/help.js +70 -13
- package/dist/cli/run-cli.js +140 -10
- package/dist/schemas/compatibility.schema.json +26 -0
- package/dist/schemas/flow-guidance.schema.json +73 -0
- package/dist/schemas/flow-run-index.schema.json +30 -4
- package/dist/schemas/global-dashboard-data.schema.json +68 -0
- package/dist/schemas/mb-sdlc-review-report.schema.json +242 -0
- package/dist/schemas/plan-stage-report.schema.json +83 -0
- package/dist/schemas/project-dashboard-data.schema.json +98 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +5 -1
- package/dist/schemas/protocol-dashboard-data.schema.json +89 -0
- package/dist/schemas/status-report.schema.json +34 -0
- package/dist/schemas/version-report.schema.json +22 -0
- package/dist/services/build-info.js +26 -3
- package/dist/services/canon.js +93 -22
- package/dist/services/cleanup.js +14 -1
- package/dist/services/config.js +25 -0
- package/dist/services/dashboard.js +655 -10
- package/dist/services/flow-guidance.js +214 -0
- package/dist/services/ids.js +106 -0
- package/dist/services/merge-queue.js +9 -1
- package/dist/services/merge-worker.js +31 -2
- package/dist/services/projects.js +7 -1
- package/dist/services/protocols.js +637 -6
- package/dist/services/runs.js +98 -21
- package/dist/services/schema-validation.js +84 -4
- package/dist/services/status.js +189 -1
- package/dist/services/version-status.js +28 -2
- package/dist/storage/database.js +6 -0
- package/dist/storage/paths.js +21 -0
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"properties": {
|
|
19
19
|
"version": { "type": "string", "minLength": 1 },
|
|
20
20
|
"package_name": { "type": "string", "minLength": 1 },
|
|
21
|
+
"compatibility": { "$ref": "#/definitions/cliCompatibility" },
|
|
22
|
+
"registry": { "$ref": "#/definitions/registryStatus" },
|
|
21
23
|
"build": {
|
|
22
24
|
"type": "object",
|
|
23
25
|
"additionalProperties": true,
|
|
@@ -85,6 +87,38 @@
|
|
|
85
87
|
}
|
|
86
88
|
},
|
|
87
89
|
"definitions": {
|
|
90
|
+
"cliCompatibility": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"additionalProperties": true,
|
|
93
|
+
"required": ["verdict", "reason", "package_name", "installed_version"],
|
|
94
|
+
"properties": {
|
|
95
|
+
"verdict": { "type": "string", "enum": ["ok", "outdated", "incompatible", "missing", "unknown"] },
|
|
96
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
97
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
98
|
+
"expected_package_name": { "type": "string" },
|
|
99
|
+
"installed_version": { "type": "string", "minLength": 1 },
|
|
100
|
+
"memory_bank_version": { "type": ["string", "null"] },
|
|
101
|
+
"min_version": { "type": ["string", "null"] },
|
|
102
|
+
"recommended_version": { "type": ["string", "null"] },
|
|
103
|
+
"status_contract": { "type": ["string", "null"] },
|
|
104
|
+
"version_contract": { "type": ["string", "null"] },
|
|
105
|
+
"flow_contract": { "type": ["string", "null"] },
|
|
106
|
+
"update_command": { "type": ["string", "null"] }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"registryStatus": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"additionalProperties": true,
|
|
112
|
+
"required": ["package_name", "latest", "checked_at", "source", "status"],
|
|
113
|
+
"properties": {
|
|
114
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
115
|
+
"latest": { "type": ["string", "null"] },
|
|
116
|
+
"checked_at": { "type": "string", "minLength": 1 },
|
|
117
|
+
"source": { "type": "string", "enum": ["npm", "npm_cache"] },
|
|
118
|
+
"status": { "type": "string", "enum": ["ok", "degraded"] },
|
|
119
|
+
"reason": { "type": "string" }
|
|
120
|
+
}
|
|
121
|
+
},
|
|
88
122
|
"memoryBank": {
|
|
89
123
|
"type": ["object", "null"],
|
|
90
124
|
"additionalProperties": true,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "dd-flow/version-report@1",
|
|
4
|
+
"title": "dd-flow version report",
|
|
5
|
+
"description": "Small read-only JSON contract for dd-flow version --json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["ok", "schema_id", "cli"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"ok": { "const": true },
|
|
11
|
+
"schema_id": { "const": "dd-flow/version-report@1" },
|
|
12
|
+
"cli": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"required": ["package_name", "version"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
18
|
+
"version": { "type": "string", "minLength": 1 }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -21,6 +21,10 @@ export function getCliBuildInfo() {
|
|
|
21
21
|
version: null,
|
|
22
22
|
commit: null,
|
|
23
23
|
flow_contract: null,
|
|
24
|
+
repo_root: null,
|
|
25
|
+
memorybank_root: null,
|
|
26
|
+
flow_root: null,
|
|
27
|
+
layout: null,
|
|
24
28
|
status: "unknown",
|
|
25
29
|
reason: "build_info_missing"
|
|
26
30
|
}
|
|
@@ -31,10 +35,14 @@ export function getCliBuildInfo() {
|
|
|
31
35
|
const canonVersion = stringOrNull(builtWithCanon?.version);
|
|
32
36
|
const canonCommit = stringOrNull(builtWithCanon?.commit);
|
|
33
37
|
const flowContract = stringOrNull(builtWithCanon?.flow_contract);
|
|
34
|
-
const
|
|
38
|
+
const repoRoot = stringOrNull(builtWithCanon?.repo_root);
|
|
39
|
+
const memorybankRoot = stringOrNull(builtWithCanon?.memorybank_root);
|
|
40
|
+
const flowRoot = stringOrNull(builtWithCanon?.flow_root);
|
|
41
|
+
const layout = stringOrNull(builtWithCanon?.layout);
|
|
42
|
+
const canonStatus = canonVersion && canonCommit && flowContract ? "present" : "degraded";
|
|
35
43
|
return {
|
|
36
|
-
version
|
|
37
|
-
package_name:
|
|
44
|
+
version,
|
|
45
|
+
package_name: packageName,
|
|
38
46
|
build: {
|
|
39
47
|
status: "present",
|
|
40
48
|
built_at: stringOrNull(buildInfo.built_at),
|
|
@@ -43,12 +51,27 @@ export function getCliBuildInfo() {
|
|
|
43
51
|
version: canonVersion,
|
|
44
52
|
commit: canonCommit,
|
|
45
53
|
flow_contract: flowContract,
|
|
54
|
+
repo_root: repoRoot,
|
|
55
|
+
memorybank_root: memorybankRoot,
|
|
56
|
+
flow_root: flowRoot,
|
|
57
|
+
layout,
|
|
46
58
|
status: canonStatus,
|
|
47
59
|
...(canonStatus === "degraded" ? { reason: "build_canon_metadata_incomplete" } : {})
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
};
|
|
51
63
|
}
|
|
64
|
+
export function getCliVersionReport() {
|
|
65
|
+
const cli = getCliBuildInfo();
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
schema_id: "dd-flow/version-report@1",
|
|
69
|
+
cli: {
|
|
70
|
+
package_name: cli.package_name,
|
|
71
|
+
version: cli.version
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
52
75
|
function findPackageRoot(start) {
|
|
53
76
|
let current = path.resolve(start);
|
|
54
77
|
while (true) {
|
package/dist/services/canon.js
CHANGED
|
@@ -46,14 +46,21 @@ export function resolveCanonRoot(context, input = {}) {
|
|
|
46
46
|
throw new Error("Expected one canonical candidate.");
|
|
47
47
|
}
|
|
48
48
|
const root = candidate.realpath ?? candidate.root;
|
|
49
|
-
const
|
|
49
|
+
const flowRoot = candidate.flow_root ?? path.join(root, "dd-flow");
|
|
50
|
+
const memorybankRoot = candidate.memorybank_root ?? root;
|
|
51
|
+
const mbbRoot = candidate.mbb_root ?? path.join(memorybankRoot, "mbb");
|
|
52
|
+
const layout = candidate.layout ?? "legacy_root";
|
|
53
|
+
const metadata = readCanonMetadata(root, flowRoot);
|
|
50
54
|
return {
|
|
51
55
|
ok: true,
|
|
52
56
|
canon: {
|
|
53
57
|
root,
|
|
58
|
+
repo_root: root,
|
|
54
59
|
source: candidate.source,
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
layout,
|
|
61
|
+
flow_root: flowRoot,
|
|
62
|
+
mbb_root: mbbRoot,
|
|
63
|
+
memorybank_root: memorybankRoot,
|
|
57
64
|
version: metadata.version,
|
|
58
65
|
commit: metadata.commit,
|
|
59
66
|
flow_contract: metadata.flow_contract,
|
|
@@ -121,24 +128,28 @@ function validateCanonCandidate(source, root) {
|
|
|
121
128
|
if (!fs.statSync(realpath).isDirectory()) {
|
|
122
129
|
blockers.push(`${source} canonical root is not a directory: ${realpath}`);
|
|
123
130
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"mbb/index.md",
|
|
127
|
-
"protocol/index.md",
|
|
128
|
-
"dd-flow/mb-init.md",
|
|
129
|
-
"dd-flow/mb-upgrade.md",
|
|
130
|
-
"dd-flow/mb-upgrade-review.md",
|
|
131
|
-
"dd-flow/mb-distill.md"
|
|
132
|
-
];
|
|
133
|
-
for (const relativePath of requiredFiles) {
|
|
134
|
-
const file = path.join(realpath, relativePath);
|
|
135
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
136
|
-
blockers.push(`${source} canonical root is missing ${relativePath}: ${realpath}`);
|
|
137
|
-
}
|
|
131
|
+
if (path.basename(realpath) === ".memory-bank" && fs.existsSync(path.join(path.dirname(realpath), "VERSION"))) {
|
|
132
|
+
blockers.push(`${source} canonical root points to .memory-bank; use the dd-memorybank repository root instead: ${path.dirname(realpath)}`);
|
|
138
133
|
}
|
|
139
|
-
|
|
134
|
+
const layout = assessCanonLayout(realpath);
|
|
135
|
+
blockers.push(...layout.blockers.map((blocker) => `${source} ${blocker}`));
|
|
136
|
+
return {
|
|
137
|
+
source,
|
|
138
|
+
root: absolute,
|
|
139
|
+
realpath,
|
|
140
|
+
valid: blockers.length === 0 && layout.valid,
|
|
141
|
+
blockers,
|
|
142
|
+
...(layout.valid
|
|
143
|
+
? {
|
|
144
|
+
layout: layout.layout,
|
|
145
|
+
memorybank_root: layout.memorybankRoot,
|
|
146
|
+
flow_root: layout.flowRoot,
|
|
147
|
+
mbb_root: layout.mbbRoot
|
|
148
|
+
}
|
|
149
|
+
: {})
|
|
150
|
+
};
|
|
140
151
|
}
|
|
141
|
-
export function readCanonMetadata(root) {
|
|
152
|
+
export function readCanonMetadata(root, flowRoot = path.join(root, "dd-flow")) {
|
|
142
153
|
const diagnostics = [];
|
|
143
154
|
const version = readTrimmed(path.join(root, "VERSION"));
|
|
144
155
|
if (!version)
|
|
@@ -146,7 +157,7 @@ export function readCanonMetadata(root) {
|
|
|
146
157
|
const commit = gitCommit(root);
|
|
147
158
|
if (!commit)
|
|
148
159
|
diagnostics.push("canon_commit_unknown");
|
|
149
|
-
const flowContract = readFlowContractId(
|
|
160
|
+
const flowContract = readFlowContractId(flowRoot);
|
|
150
161
|
if (!flowContract)
|
|
151
162
|
diagnostics.push("flow_contract_unknown");
|
|
152
163
|
return {
|
|
@@ -166,9 +177,69 @@ function readTrimmed(file) {
|
|
|
166
177
|
return null;
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
|
-
function
|
|
180
|
+
export function resolveCanonLayout(root) {
|
|
181
|
+
return assessCanonLayout(path.resolve(root));
|
|
182
|
+
}
|
|
183
|
+
function assessCanonLayout(root) {
|
|
184
|
+
const legacy = assessLayout(root, "legacy_root", root);
|
|
185
|
+
const dot = assessLayout(root, "dot_memory_bank", path.join(root, ".memory-bank"));
|
|
186
|
+
if (dot.valid && !legacy.valid)
|
|
187
|
+
return dot;
|
|
188
|
+
if (legacy.valid && !dot.valid)
|
|
189
|
+
return legacy;
|
|
190
|
+
if (dot.valid && legacy.valid) {
|
|
191
|
+
if (dot.flowContract && legacy.flowContract && dot.flowContract !== legacy.flowContract) {
|
|
192
|
+
return {
|
|
193
|
+
...dot,
|
|
194
|
+
valid: false,
|
|
195
|
+
blockers: [
|
|
196
|
+
`canonical root has conflicting legacy_root and dot_memory_bank flow contracts: ${legacy.flowContract} vs ${dot.flowContract}`
|
|
197
|
+
]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return dot;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
...dot,
|
|
204
|
+
valid: false,
|
|
205
|
+
blockers: [
|
|
206
|
+
"canonical root does not match legacy_root or dot_memory_bank layout",
|
|
207
|
+
...legacy.blockers.map((blocker) => `legacy_root: ${blocker}`),
|
|
208
|
+
...dot.blockers.map((blocker) => `dot_memory_bank: ${blocker}`)
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function assessLayout(root, layout, memorybankRoot) {
|
|
213
|
+
const blockers = [];
|
|
214
|
+
const requiredFiles = [
|
|
215
|
+
"dd-flow/README.md",
|
|
216
|
+
"mbb/index.md",
|
|
217
|
+
"protocol/index.md",
|
|
218
|
+
"dd-flow/mb-init.md",
|
|
219
|
+
"dd-flow/mb-upgrade.md",
|
|
220
|
+
"dd-flow/mb-upgrade-review.md",
|
|
221
|
+
"dd-flow/mb-distill.md"
|
|
222
|
+
];
|
|
223
|
+
for (const relativePath of requiredFiles) {
|
|
224
|
+
const file = path.join(memorybankRoot, relativePath);
|
|
225
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
226
|
+
blockers.push(`canonical root is missing ${layout} ${relativePath}: ${root}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const flowRoot = path.join(memorybankRoot, "dd-flow");
|
|
230
|
+
return {
|
|
231
|
+
layout,
|
|
232
|
+
memorybankRoot,
|
|
233
|
+
flowRoot,
|
|
234
|
+
mbbRoot: path.join(memorybankRoot, "mbb"),
|
|
235
|
+
valid: blockers.length === 0,
|
|
236
|
+
blockers,
|
|
237
|
+
flowContract: readFlowContractId(flowRoot)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function readFlowContractId(flowRoot) {
|
|
170
241
|
try {
|
|
171
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(
|
|
242
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(flowRoot, "flow-contract.json"), "utf8"));
|
|
172
243
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
173
244
|
const id = parsed.id;
|
|
174
245
|
return typeof id === "string" && id.length > 0 ? id : null;
|
package/dist/services/cleanup.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ensureReadableFile } from "../storage/database.js";
|
|
|
7
7
|
import { projectCheckoutRoot, resolveProjectRoot } from "../storage/paths.js";
|
|
8
8
|
import { appendAudit } from "./audit.js";
|
|
9
9
|
import { requireProjectByRoot } from "./projects.js";
|
|
10
|
-
import { readProtocolRuntimeState, requireProtocol } from "./protocols.js";
|
|
10
|
+
import { protocolRunDiagnostics, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
|
|
11
11
|
const actionKinds = new Set([
|
|
12
12
|
"repair_protocol_state",
|
|
13
13
|
"cancel_queue_job",
|
|
@@ -39,6 +39,19 @@ export function cleanupScan(context, input) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
const fullProtocol = requireProtocol(context, protocol.id);
|
|
43
|
+
const state = readProtocolRuntimeState(context, fullProtocol).state;
|
|
44
|
+
const diagnostics = protocolRunDiagnostics(context, fullProtocol, state).diagnostics;
|
|
45
|
+
for (const diagnostic of diagnostics.filter((entry) => entry.code === "protocol_run_stage_mismatch")) {
|
|
46
|
+
findings.push({
|
|
47
|
+
code: "protocol_run_stage_mismatch",
|
|
48
|
+
severity: diagnostic.severity === "error" ? "warning" : "info",
|
|
49
|
+
protocol_id: protocol.id,
|
|
50
|
+
protocol_stage: state.stage,
|
|
51
|
+
latest_run: diagnostic.latest_run,
|
|
52
|
+
recommended_commands: diagnostic.recommended_commands
|
|
53
|
+
});
|
|
54
|
+
}
|
|
42
55
|
}
|
|
43
56
|
const orphanQueueJobs = context.db.all(`SELECT mq.protocol_id, mq.status FROM merge_queue mq
|
|
44
57
|
LEFT JOIN protocols p ON p.id = mq.protocol_id
|
package/dist/services/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { cmuxModes } from "../domain/contracts.js";
|
|
3
3
|
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { projectHome } from "../storage/paths.js";
|
|
4
5
|
export const defaultProjectConfig = {
|
|
5
6
|
dashboard: {
|
|
6
7
|
auto_refresh: true,
|
|
@@ -52,6 +53,30 @@ export function globalDashboardMarkdownPath(context, config) {
|
|
|
52
53
|
const configured = config?.dashboard.global_markdown_path ?? defaultProjectConfig.dashboard.global_markdown_path;
|
|
53
54
|
return path.isAbsolute(configured) ? configured : path.join(context.ddFlowHome, configured);
|
|
54
55
|
}
|
|
56
|
+
export function projectDashboardDir(ddFlowHome, projectId) {
|
|
57
|
+
return path.join(projectHome(ddFlowHome, projectId), "dashboard");
|
|
58
|
+
}
|
|
59
|
+
export function projectDashboardJsonPath(context, projectId) {
|
|
60
|
+
return path.join(projectDashboardDir(context.ddFlowHome, projectId), "project-dashboard.json");
|
|
61
|
+
}
|
|
62
|
+
export function projectDashboardHtmlPath(context, projectId) {
|
|
63
|
+
return path.join(projectDashboardDir(context.ddFlowHome, projectId), "project-dashboard.html");
|
|
64
|
+
}
|
|
65
|
+
export function protocolDashboardJsonPath(context, projectId, protocolId) {
|
|
66
|
+
return path.join(projectDashboardDir(context.ddFlowHome, projectId), "protocols", `${protocolId}.json`);
|
|
67
|
+
}
|
|
68
|
+
export function protocolDashboardHtmlPath(context, projectId, protocolId) {
|
|
69
|
+
return path.join(projectDashboardDir(context.ddFlowHome, projectId), "protocols", `${protocolId}.html`);
|
|
70
|
+
}
|
|
71
|
+
export function globalDashboardDir(context) {
|
|
72
|
+
return path.join(context.ddFlowHome, "dashboard");
|
|
73
|
+
}
|
|
74
|
+
export function globalDashboardJsonPath(context) {
|
|
75
|
+
return path.join(globalDashboardDir(context), "global-dashboard.json");
|
|
76
|
+
}
|
|
77
|
+
export function globalDashboardHtmlPath(context) {
|
|
78
|
+
return path.join(globalDashboardDir(context), "global-dashboard.html");
|
|
79
|
+
}
|
|
55
80
|
function configOverrides(context, projectId) {
|
|
56
81
|
return context.db.all("SELECT key, value_json, updated_at FROM project_config WHERE project_id = ? ORDER BY key ASC", [projectId]);
|
|
57
82
|
}
|