@deksden-com/dd-flow-cli 0.3.0 → 0.3.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +74 -5
  3. package/dist/build-info.json +5 -5
  4. package/dist/cli/help.js +103 -15
  5. package/dist/cli/run-cli.js +307 -27
  6. package/dist/schemas/compatibility.schema.json +81 -2
  7. package/dist/schemas/engine-manifest.schema.json +61 -0
  8. package/dist/schemas/flow-guidance.schema.json +17 -0
  9. package/dist/schemas/global-dashboard-data.schema.json +60 -2
  10. package/dist/schemas/mb-upgrade-migration-report.schema.json +93 -0
  11. package/dist/schemas/project-dashboard-data.schema.json +26 -2
  12. package/dist/schemas/project-summary.schema.json +73 -0
  13. package/dist/schemas/protocol-dashboard-data.schema.json +25 -2
  14. package/dist/schemas/status-report.schema.json +4 -2
  15. package/dist/services/cleanup.js +31 -0
  16. package/dist/services/cli-operation-classifier.js +104 -0
  17. package/dist/services/compatibility-preflight.js +124 -0
  18. package/dist/services/config.js +6 -0
  19. package/dist/services/dashboard-targets.js +95 -0
  20. package/dist/services/dashboard.js +376 -59
  21. package/dist/services/engines.js +532 -0
  22. package/dist/services/flow-guidance.js +8 -1
  23. package/dist/services/hooks.js +1 -1
  24. package/dist/services/lanes.js +333 -1
  25. package/dist/services/merge-queue.js +97 -15
  26. package/dist/services/merge-worker.js +36 -2
  27. package/dist/services/migrations.js +231 -0
  28. package/dist/services/project-summary.js +122 -0
  29. package/dist/services/projects.js +41 -6
  30. package/dist/services/protocol-lifecycle.js +144 -0
  31. package/dist/services/protocols.js +29 -7
  32. package/dist/services/sessions.js +21 -4
  33. package/dist/services/status.js +10 -0
  34. package/dist/services/version-status.js +39 -15
  35. package/dist/storage/database.js +25 -0
  36. package/dist/storage/paths.js +12 -0
  37. package/package.json +3 -2
@@ -0,0 +1,124 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { getCliBuildInfo } from "./build-info.js";
3
+ import { classifyCliOperation } from "./cli-operation-classifier.js";
4
+ import { selectEngine } from "./engines.js";
5
+ import { requireProtocol } from "./protocols.js";
6
+ export function preflightCliCompatibility(context, args, env = process.env) {
7
+ const classification = classifyCliOperation(args, env);
8
+ if (classification.mode === "router_native") {
9
+ return { ok: true, classification, compatibility: null };
10
+ }
11
+ const projectRoot = resolveOperationProjectRoot(context, args);
12
+ if (!projectRoot) {
13
+ return { ok: true, classification, compatibility: null };
14
+ }
15
+ const selection = selectEngine(context, { projectRoot });
16
+ const compatibility = compatibilityReport(selection, classification);
17
+ if (classification.mode === "read_only_diagnostics" || classification.mode === "mb_upgrade") {
18
+ return { ok: true, classification, compatibility };
19
+ }
20
+ if (selection.status !== "selected" || !selection.selected) {
21
+ throw new AppError("compatibility_preflight_failed", "dd-flow compatibility preflight blocked a state-changing command", 1, {
22
+ compatibility,
23
+ remediation: remediationForSelection(selection)
24
+ });
25
+ }
26
+ return { ok: true, classification, compatibility };
27
+ }
28
+ export function compatibilityReport(selection, classification) {
29
+ const build = getCliBuildInfo();
30
+ return {
31
+ verdict: selection.status === "selected" ? "ok" : "incompatible",
32
+ memorybank_version: selection.memory_bank_version,
33
+ router_version: build.version,
34
+ cli_version: build.version,
35
+ engine_version: selection.selected?.engine_version ?? selection.selected?.package_version ?? null,
36
+ engine_resolution: selection.status,
37
+ required_engine_range: selection.required_range,
38
+ recommended_engine_version: selection.recommended_version,
39
+ allowed_modes: ["read_only_diagnostics", "mb_upgrade"],
40
+ operation_mode: classification.mode,
41
+ blocked_operation: classification.mode === "normal_write" ? classification.operation : null,
42
+ install_hint: selection.install_hint,
43
+ project_root: selection.project_root,
44
+ diagnostics: selection.diagnostics
45
+ };
46
+ }
47
+ function remediationForSelection(selection) {
48
+ return {
49
+ install_hint: selection.install_hint,
50
+ mb_upgrade_mode: "Run through mb-upgrade with --compatibility-mode mb-upgrade only when intentionally migrating the project.",
51
+ diagnostics: selection.diagnostics
52
+ };
53
+ }
54
+ function resolveOperationProjectRoot(context, args) {
55
+ const [family, command, ...rest] = args;
56
+ const parsed = parseLightArgs(rest);
57
+ const explicitRoot = option(parsed, "project-root") ?? option(parsed, "root");
58
+ if (explicitRoot)
59
+ return explicitRoot;
60
+ if (family === "protocol") {
61
+ const protocolId = positional(parsed, 0);
62
+ if (protocolId && command && ["ready-for-merge", "cancel", "status"].includes(command)) {
63
+ return requireProtocol(context, protocolId).project_root;
64
+ }
65
+ }
66
+ if (family === "transition") {
67
+ const transitionParsed = parseLightArgs([command ?? "", ...rest]);
68
+ const protocolId = positional(transitionParsed, 0);
69
+ return protocolId ? requireProtocol(context, protocolId).project_root : null;
70
+ }
71
+ if (family === "plan" && (command === "set" || command === "status")) {
72
+ const protocolId = positional(parsed, 0);
73
+ return protocolId ? requireProtocol(context, protocolId).project_root : null;
74
+ }
75
+ if (family === "plan" && command === "item") {
76
+ const protocolId = positional(parsed, 1);
77
+ return protocolId ? requireProtocol(context, protocolId).project_root : null;
78
+ }
79
+ if (family === "merge-queue" && ["complete", "fail", "cancel", "note"].includes(command ?? "")) {
80
+ const protocolId = positional(parsed, 0);
81
+ return protocolId ? requireProtocol(context, protocolId).project_root : null;
82
+ }
83
+ if (family === "worktree") {
84
+ const protocolId = option(parsed, "protocol-id");
85
+ return protocolId ? requireProtocol(context, protocolId).project_root : null;
86
+ }
87
+ return null;
88
+ }
89
+ function parseLightArgs(args) {
90
+ const positional = [];
91
+ const options = new Map();
92
+ for (let index = 0; index < args.length; index += 1) {
93
+ const value = args[index];
94
+ if (value?.startsWith("--")) {
95
+ const equal = value.indexOf("=");
96
+ if (equal > 2) {
97
+ const key = value.slice(2, equal);
98
+ options.set(key, [...(options.get(key) ?? []), value.slice(equal + 1)]);
99
+ continue;
100
+ }
101
+ const key = value.slice(2);
102
+ const next = args[index + 1];
103
+ if (!next || next.startsWith("--")) {
104
+ options.set(key, [...(options.get(key) ?? []), ""]);
105
+ }
106
+ else {
107
+ options.set(key, [...(options.get(key) ?? []), next]);
108
+ index += 1;
109
+ }
110
+ }
111
+ else if (value) {
112
+ positional.push(value);
113
+ }
114
+ }
115
+ return { positional, options };
116
+ }
117
+ function option(parsed, key) {
118
+ const values = parsed.options.get(key);
119
+ const value = values?.[values.length - 1];
120
+ return value ? value : null;
121
+ }
122
+ function positional(parsed, index) {
123
+ return parsed.positional[index] ?? null;
124
+ }
@@ -62,6 +62,12 @@ export function projectDashboardJsonPath(context, projectId) {
62
62
  export function projectDashboardHtmlPath(context, projectId) {
63
63
  return path.join(projectDashboardDir(context.ddFlowHome, projectId), "project-dashboard.html");
64
64
  }
65
+ export function projectSummaryDir(context, projectId) {
66
+ return path.join(projectHome(context.ddFlowHome, projectId), "summary");
67
+ }
68
+ export function projectSummaryJsonPath(context, projectId) {
69
+ return path.join(projectSummaryDir(context, projectId), "project-summary.json");
70
+ }
65
71
  export function protocolDashboardJsonPath(context, projectId, protocolId) {
66
72
  return path.join(projectDashboardDir(context.ddFlowHome, projectId), "protocols", `${protocolId}.json`);
67
73
  }
@@ -0,0 +1,95 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { resolveProjectRoot } from "../storage/paths.js";
3
+ import { requireProjectByReference, requireProjectByRoot } from "./projects.js";
4
+ export function resolveDashboardTarget(context, input) {
5
+ if (input.all) {
6
+ if (input.action !== "refresh") {
7
+ throw new AppError("validation", "--all is supported only for dashboard refresh", 2, {
8
+ preferred_command: "dd-flow dashboard refresh --all",
9
+ related_commands: relatedDashboardCommands("refresh")
10
+ });
11
+ }
12
+ if (input.project || input.projectRoot || input.protocol || input.global) {
13
+ throw new AppError("validation", "--all cannot be combined with --project, --project-root, --protocol, or --global", 2, {
14
+ related_commands: relatedDashboardCommands("refresh")
15
+ });
16
+ }
17
+ return { kind: "all_projects" };
18
+ }
19
+ if (input.global && (input.project || input.projectRoot || input.protocol)) {
20
+ throw new AppError("validation", "--global cannot be combined with project or protocol targets", 2, {
21
+ related_commands: relatedDashboardCommands(input.action)
22
+ });
23
+ }
24
+ const project = resolveDashboardProject(context, input.project, input.projectRoot);
25
+ if (input.protocol) {
26
+ if (!project) {
27
+ throw new AppError("validation", "--protocol requires --project or --project-root", 2, {
28
+ related_commands: relatedDashboardCommands(input.action, "project")
29
+ });
30
+ }
31
+ return { kind: "protocol", project, protocolId: input.protocol };
32
+ }
33
+ if (project) {
34
+ return { kind: "project", project };
35
+ }
36
+ return { kind: "global" };
37
+ }
38
+ export function dashboardTargetSummary(target) {
39
+ if (target.kind === "project") {
40
+ return { kind: "project", project_id: target.project.id, project_root: target.project.root };
41
+ }
42
+ if (target.kind === "protocol") {
43
+ return { kind: "protocol", project_id: target.project.id, project_root: target.project.root, protocol_id: target.protocolId };
44
+ }
45
+ return { kind: target.kind };
46
+ }
47
+ export function preferredDashboardCommand(action, target) {
48
+ if (target.kind === "project") {
49
+ return `dd-flow dashboard ${action} --project <project>`;
50
+ }
51
+ if (target.kind === "protocol") {
52
+ return `dd-flow dashboard ${action} --project <project> --protocol <protocol>`;
53
+ }
54
+ if (target.kind === "all_projects") {
55
+ return "dd-flow dashboard refresh --all";
56
+ }
57
+ return `dd-flow dashboard ${action}`;
58
+ }
59
+ export function relatedDashboardCommands(action, targetKind = "global") {
60
+ if (targetKind === "all_projects") {
61
+ return ["dd-flow dashboard refresh --all", "dd-flow dashboard open", "dd-flow dashboard open --project <project>"];
62
+ }
63
+ if (targetKind === "project" || targetKind === "protocol") {
64
+ return uniqueCommands([
65
+ `dd-flow dashboard ${action} --project <project>`,
66
+ "dd-flow dashboard refresh --project <project>",
67
+ "dd-flow dashboard open --project <project>",
68
+ "dd-flow dashboard data --project <project> --json"
69
+ ]);
70
+ }
71
+ return uniqueCommands([
72
+ `dd-flow dashboard ${action}`,
73
+ "dd-flow dashboard open",
74
+ "dd-flow dashboard refresh",
75
+ "dd-flow dashboard refresh --all",
76
+ "dd-flow dashboard open --project <project>"
77
+ ]);
78
+ }
79
+ function resolveDashboardProject(context, project, projectRoot) {
80
+ if (!project && !projectRoot) {
81
+ return null;
82
+ }
83
+ const byProject = project ? requireProjectByReference(context, project) : null;
84
+ const byRoot = projectRoot ? requireProjectByRoot(context, resolveProjectRoot(projectRoot)) : null;
85
+ if (byProject && byRoot && byProject.id !== byRoot.id) {
86
+ throw new AppError("validation", "--project and --project-root resolve to different projects", 2, {
87
+ project: { id: byProject.id, root: byProject.root },
88
+ project_root: { id: byRoot.id, root: byRoot.root }
89
+ });
90
+ }
91
+ return byProject ?? byRoot;
92
+ }
93
+ function uniqueCommands(commands) {
94
+ return Array.from(new Set(commands));
95
+ }