@deksden-com/dd-flow-cli 0.7.0 → 0.8.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/README.md +7 -2
  3. package/dist/build-info.json +5 -5
  4. package/dist/cli/help.js +88 -10
  5. package/dist/cli/run-cli.js +523 -28
  6. package/dist/domain/stage-catalog.js +22 -0
  7. package/dist/domain/validation.js +1 -1
  8. package/dist/schemas/code-review-decision.schema.json +26 -0
  9. package/dist/schemas/code-review-result.schema.json +14 -0
  10. package/dist/schemas/code-verification.schema.json +14 -0
  11. package/dist/schemas/code-work-batch.schema.json +24 -0
  12. package/dist/schemas/code-work-result.schema.json +16 -0
  13. package/dist/schemas/compatibility.schema.json +32 -0
  14. package/dist/schemas/flow-contract.schema.json +9 -5
  15. package/dist/schemas/flow-run.schema.json +16 -123
  16. package/dist/schemas/plan-aspect-map.schema.json +22 -0
  17. package/dist/schemas/plan-review-decision.schema.json +14 -0
  18. package/dist/schemas/plan-review-result.schema.json +42 -0
  19. package/dist/schemas/protocol-plan.schema.json +15 -182
  20. package/dist/schemas/stage-finish-input.schema.json +16 -2
  21. package/dist/schemas/stage-report.schema.json +8 -7
  22. package/dist/schemas/stage-start-response.schema.json +4 -2
  23. package/dist/schemas/status-report.schema.json +76 -0
  24. package/dist/schemas/vnext-protocol-plan.schema.json +37 -0
  25. package/dist/schemas/vnext-protocolize-result.schema.json +29 -0
  26. package/dist/schemas/vnext-specify.schema.json +45 -0
  27. package/dist/services/branch-context.js +1 -1
  28. package/dist/services/cleanup.js +8 -8
  29. package/dist/services/cli-operation-classifier.js +10 -2
  30. package/dist/services/code-checks.js +244 -0
  31. package/dist/services/config.js +7 -1
  32. package/dist/services/dashboard.js +12 -12
  33. package/dist/services/engines.js +1 -1
  34. package/dist/services/eval-snapshots.js +404 -0
  35. package/dist/services/hooks.js +774 -18
  36. package/dist/services/ids.js +16 -6
  37. package/dist/services/lanes.js +1 -1
  38. package/dist/services/merge-queue.js +5 -5
  39. package/dist/services/merge-worker.js +2 -2
  40. package/dist/services/migrations.js +2 -2
  41. package/dist/services/plan-runtime.js +1 -1
  42. package/dist/services/projects.js +4 -4
  43. package/dist/services/prompts.js +17 -11
  44. package/dist/services/protocols.js +8 -8
  45. package/dist/services/run-projection.js +49 -13
  46. package/dist/services/runs.js +504 -51
  47. package/dist/services/schema-validation.js +21 -3
  48. package/dist/services/sessions.js +51 -12
  49. package/dist/services/stage-blocker.js +57 -0
  50. package/dist/services/stage-context.js +90 -0
  51. package/dist/services/stage-lifecycle.js +198 -75
  52. package/dist/services/stage-pause.js +175 -0
  53. package/dist/services/stage-report-renderer.js +65 -0
  54. package/dist/services/usage.js +526 -18
  55. package/dist/services/vnext-code-review.js +305 -0
  56. package/dist/services/vnext-code.js +686 -0
  57. package/dist/services/vnext-contracts.js +1 -0
  58. package/dist/services/vnext-execution-profile.js +27 -0
  59. package/dist/services/vnext-fanout.js +79 -0
  60. package/dist/services/vnext-plan-review.js +499 -0
  61. package/dist/services/vnext-plan.js +552 -0
  62. package/dist/services/vnext-protocolize.js +542 -0
  63. package/dist/services/vnext-specify.js +595 -0
  64. package/dist/services/vnext-workspace-policy.js +87 -0
  65. package/dist/services/work-registry.js +522 -0
  66. package/dist/services/worktrees.js +58 -37
  67. package/dist/storage/database.js +263 -34
  68. package/dist/storage/paths.js +47 -1
  69. package/package.json +12 -12
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ const relativePath = path.join(".memory-bank", "dd-flow", "project-execution.json");
5
+ /** The project owns defaults; a RUN snapshots them once and never re-infers them. */
6
+ export function loadVnextExecutionProfile(projectRoot) {
7
+ const file = path.join(projectRoot, relativePath);
8
+ let value;
9
+ try {
10
+ value = JSON.parse(fs.readFileSync(file, "utf8"));
11
+ }
12
+ catch (error) {
13
+ throw new AppError("execution_profile_missing", "vNext flow requires .memory-bank/dd-flow/project-execution.json", 1, { file, cause: String(error) });
14
+ }
15
+ const profile = value;
16
+ if (profile.schema_id !== "dd-flow/project-execution@1"
17
+ || (profile.stage_session_mode !== undefined && profile.stage_session_mode !== "same_session" && profile.stage_session_mode !== "new_session")
18
+ || !["auto", "off", "standard", "deep"].includes(String(profile.plan_review_mode))
19
+ || (profile.code_review_mode !== undefined && !["auto", "off", "standard", "deep"].includes(String(profile.code_review_mode)))
20
+ || !["code_completed", "code_review_completed"].includes(String(profile.stop_target))
21
+ || !profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
22
+ || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()) {
23
+ throw new AppError("execution_profile_invalid", "Project execution profile does not match dd-flow/project-execution@1", 1, { file });
24
+ }
25
+ return { ...profile, stage_session_mode: profile.stage_session_mode ?? "same_session", code_review_mode: profile.code_review_mode ?? "off" };
26
+ }
27
+ export function executionProfilePath(projectRoot) { return path.join(projectRoot, relativePath); }
@@ -0,0 +1,79 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { resolveProjectRoot } from "../storage/paths.js";
5
+ import { requireProjectByRoot } from "./projects.js";
6
+ import { getFlowRunVariables } from "./runs.js";
7
+ import { listWorks } from "./work-registry.js";
8
+ import { vnextStageDirectory } from "../domain/stage-catalog.js";
9
+ export const subagentCapacityKey = "runtime.subagent.available_slots";
10
+ export const capacityProbe = {
11
+ fanout_size: 15,
12
+ probe_hold_seconds: 60,
13
+ cleanup_deadline_seconds: 180,
14
+ completion_token: "AGENT-NN"
15
+ };
16
+ export function fanoutState(input) {
17
+ if (!input.hasWork)
18
+ return input.dispatch === "none" ? "coordinator_required" : "dispatch_required";
19
+ if (input.capacityRequired && !input.capacityKnown)
20
+ return "capacity_required";
21
+ if (input.created || input.running)
22
+ return "awaiting_children";
23
+ return "children_settled";
24
+ }
25
+ export function fanoutFile(stageRoot) { return path.join(stageRoot, "fanout.json"); }
26
+ export function writeFanoutDescriptor(stageRoot, descriptor) {
27
+ const value = { schema_id: "dd-flow/work-fanout@1", kind: "work_fanout", ...descriptor };
28
+ fs.writeFileSync(fanoutFile(stageRoot), `${JSON.stringify(value, null, 2)}\n`);
29
+ return value;
30
+ }
31
+ export function readFanoutDescriptor(stageRoot) {
32
+ const file = fanoutFile(stageRoot);
33
+ if (!fs.existsSync(file))
34
+ return null;
35
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
36
+ if (value.schema_id !== "dd-flow/work-fanout@1" || value.kind !== "work_fanout" || typeof value.stage !== "string" || typeof value.parent_work_id !== "string" || !["none", "plan-review"].includes(String(value.dispatch)) || typeof value.capacity_required !== "boolean") {
37
+ throw new AppError("runtime_missing", "Stage fan-out descriptor is invalid", 1, { file });
38
+ }
39
+ return value;
40
+ }
41
+ /** Read-only projection for an external orchestrator. dd-flow keeps the graph authority. */
42
+ export function getVnextFanoutStatus(context, input) {
43
+ const projectRoot = resolveProjectRoot(input.projectRoot);
44
+ const project = requireProjectByRoot(context, projectRoot);
45
+ const run = context.db.get("SELECT run_home_path FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
46
+ if (!run?.run_home_path)
47
+ throw new AppError("not_found", "RUN workspace is unavailable", 1, { run_id: input.runId });
48
+ const stageRoot = path.join(run.run_home_path, vnextStageDirectory(input.stage));
49
+ const descriptor = readFanoutDescriptor(stageRoot);
50
+ if (!descriptor)
51
+ return { ok: true, run_id: input.runId, stage: input.stage, orchestration: null };
52
+ if (descriptor.stage !== input.stage)
53
+ throw new AppError("runtime_missing", "Stage fan-out descriptor belongs to another stage", 1, { requested_stage: input.stage, descriptor_stage: descriptor.stage });
54
+ const variables = getFlowRunVariables(context, { projectRoot, runId: input.runId });
55
+ const available = variables.variables[subagentCapacityKey];
56
+ const all = listWorks(context, { parentWorkId: descriptor.parent_work_id, includeResults: false });
57
+ const ready = listWorks(context, { parentWorkId: descriptor.parent_work_id, ready: true, includeResults: false });
58
+ const counts = Object.fromEntries(["created", "running", "completed", "failed", "cancelled"].map((status) => [status, all.works.filter((work) => work.status === status).length]));
59
+ const hasWork = all.works.length > 0;
60
+ const capacityKnown = typeof available === "number" && Number.isInteger(available) && available >= 0;
61
+ return {
62
+ ok: true,
63
+ run_id: input.runId,
64
+ stage: descriptor.stage,
65
+ orchestration: {
66
+ ...descriptor,
67
+ // PLAN-REVIEW has a deterministic dispatcher. CODE and CODE-REVIEW do
68
+ // not: their coordinator first materializes the Work graph from its
69
+ // authoritative prompt. An empty graph is therefore not a success.
70
+ state: fanoutState({ dispatch: descriptor.dispatch, hasWork, capacityRequired: descriptor.capacity_required, capacityKnown, created: counts.created ?? 0, running: counts.running ?? 0 }),
71
+ capacity: {
72
+ run_key: subagentCapacityKey,
73
+ available_slots: capacityKnown ? available : null,
74
+ ...(descriptor.capacity_required && !capacityKnown ? { probe: capacityProbe } : {})
75
+ },
76
+ works: { ...counts, ready: ready.works.map((work) => ({ work_id: work.work_id, task: work.task, start_command: work.start_command })) }
77
+ }
78
+ };
79
+ }