@mcoda/core 0.1.21 → 0.1.23

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/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./services/planning/RefineTasksService.js";
9
9
  export * from "./services/planning/KeyHelpers.js";
10
10
  export * from "./services/execution/TaskSelectionService.js";
11
11
  export * from "./services/execution/TaskStateService.js";
12
+ export * from "./services/execution/AddTestsService.js";
12
13
  export * from "./services/execution/WorkOnTasksService.js";
13
14
  export * from "./services/execution/QaTasksService.js";
14
15
  export * from "./services/execution/GatewayTrioService.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export * from "./services/planning/RefineTasksService.js";
9
9
  export * from "./services/planning/KeyHelpers.js";
10
10
  export * from "./services/execution/TaskSelectionService.js";
11
11
  export * from "./services/execution/TaskStateService.js";
12
+ export * from "./services/execution/AddTestsService.js";
12
13
  export * from "./services/execution/WorkOnTasksService.js";
13
14
  export * from "./services/execution/QaTasksService.js";
14
15
  export * from "./services/execution/GatewayTrioService.js";
@@ -1 +1 @@
1
- {"version":3,"file":"TaskOrderingService.d.ts","sourceRoot":"","sources":["../../../src/services/backlog/TaskOrderingService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TaskOrderingService.d.ts","sourceRoot":"","sources":["../../../src/services/backlog/TaskOrderingService.ts"],"names":[],"mappings":"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"}
@@ -472,6 +472,7 @@ export class TaskOrderingService {
472
472
  SELECT
473
473
  td.task_id,
474
474
  td.depends_on_task_id,
475
+ td.relation_type,
475
476
  dep.key as depends_on_key,
476
477
  dep.status as depends_on_status
477
478
  FROM task_dependencies td
@@ -646,47 +647,81 @@ export class TaskOrderingService {
646
647
  return false;
647
648
  }
648
649
  async injectFoundationDependencies(tasks, deps, warnings, persist) {
650
+ const relationType = "inferred_foundation";
649
651
  const classification = new Map();
650
652
  for (const task of tasks) {
651
653
  classification.set(task.id, this.resolveClassification(task));
652
654
  }
653
655
  const foundationTasks = tasks.filter((task) => classification.get(task.id)?.foundation);
654
656
  const nonFoundationTasks = tasks.filter((task) => !classification.get(task.id)?.foundation);
655
- if (foundationTasks.length === 0 || nonFoundationTasks.length === 0)
656
- return;
657
+ let removedInferred = 0;
658
+ for (const task of tasks) {
659
+ const rows = deps.get(task.id) ?? [];
660
+ if (rows.length === 0)
661
+ continue;
662
+ const kept = rows.filter((row) => row.relation_type?.toLowerCase() !== relationType);
663
+ removedInferred += rows.length - kept.length;
664
+ deps.set(task.id, kept);
665
+ }
666
+ const foundationsByStory = new Map();
667
+ const foundationsByEpic = new Map();
668
+ for (const foundation of foundationTasks) {
669
+ const storyEntries = foundationsByStory.get(foundation.story_id) ?? [];
670
+ storyEntries.push(foundation);
671
+ foundationsByStory.set(foundation.story_id, storyEntries);
672
+ const epicEntries = foundationsByEpic.get(foundation.epic_id) ?? [];
673
+ epicEntries.push(foundation);
674
+ foundationsByEpic.set(foundation.epic_id, epicEntries);
675
+ }
676
+ const byRank = (a, b) => {
677
+ const priorityA = a.priority ?? Number.MAX_SAFE_INTEGER;
678
+ const priorityB = b.priority ?? Number.MAX_SAFE_INTEGER;
679
+ if (priorityA !== priorityB)
680
+ return priorityA - priorityB;
681
+ const createdA = Date.parse(a.created_at) || 0;
682
+ const createdB = Date.parse(b.created_at) || 0;
683
+ if (createdA !== createdB)
684
+ return createdA - createdB;
685
+ return a.key.localeCompare(b.key);
686
+ };
687
+ for (const list of foundationsByStory.values())
688
+ list.sort(byRank);
689
+ for (const list of foundationsByEpic.values())
690
+ list.sort(byRank);
657
691
  const taskById = new Map(tasks.map((task) => [task.id, task]));
658
692
  const dependencyGraph = this.buildDependencyGraph(tasks, deps);
659
693
  const inserts = [];
660
694
  let skippedCycles = 0;
661
695
  const skippedEdges = [];
662
696
  for (const task of nonFoundationTasks) {
663
- const existing = new Set((deps.get(task.id) ?? [])
664
- .map((dep) => dep.depends_on_task_id ?? "")
665
- .filter(Boolean));
666
- for (const foundation of foundationTasks) {
667
- if (task.id === foundation.id)
668
- continue;
669
- if (existing.has(foundation.id))
670
- continue;
671
- if (this.hasDependencyPath(dependencyGraph, foundation.id, task.id)) {
672
- skippedCycles += 1;
673
- if (skippedEdges.length < 5) {
674
- skippedEdges.push(`${task.key}->${foundation.key}`);
675
- }
676
- continue;
697
+ const existingDeps = (deps.get(task.id) ?? []).filter((dep) => Boolean(dep.depends_on_task_id));
698
+ if (existingDeps.length > 0)
699
+ continue;
700
+ const scopedCandidates = foundationsByStory.get(task.story_id) ?? foundationsByEpic.get(task.epic_id) ?? [];
701
+ const foundation = scopedCandidates.find((candidate) => candidate.id !== task.id);
702
+ if (!foundation)
703
+ continue;
704
+ if (this.hasDependencyPath(dependencyGraph, foundation.id, task.id)) {
705
+ skippedCycles += 1;
706
+ if (skippedEdges.length < 5) {
707
+ skippedEdges.push(`${task.key}->${foundation.key}`);
677
708
  }
678
- inserts.push({
679
- taskId: task.id,
680
- dependsOnTaskId: foundation.id,
681
- relationType: "inferred_foundation",
682
- });
683
- existing.add(foundation.id);
684
- const edges = dependencyGraph.get(task.id) ?? new Set();
685
- edges.add(foundation.id);
686
- dependencyGraph.set(task.id, edges);
709
+ continue;
687
710
  }
711
+ inserts.push({
712
+ taskId: task.id,
713
+ dependsOnTaskId: foundation.id,
714
+ relationType,
715
+ });
716
+ const edges = dependencyGraph.get(task.id) ?? new Set();
717
+ edges.add(foundation.id);
718
+ dependencyGraph.set(task.id, edges);
688
719
  }
689
- if (inserts.length === 0) {
720
+ if (persist && removedInferred > 0) {
721
+ const placeholders = tasks.map(() => "?").join(", ");
722
+ await this.db.run(`DELETE FROM task_dependencies WHERE relation_type = ? AND task_id IN (${placeholders})`, relationType, ...tasks.map((task) => task.id));
723
+ }
724
+ if (inserts.length === 0 && removedInferred === 0) {
690
725
  if (skippedCycles > 0) {
691
726
  warnings.push(`Skipped ${skippedCycles} inferred foundation deps due to cycles.`);
692
727
  if (skippedEdges.length > 0) {
@@ -704,16 +739,25 @@ export class TaskOrderingService {
704
739
  depList.push({
705
740
  task_id: insert.taskId,
706
741
  depends_on_task_id: insert.dependsOnTaskId,
742
+ relation_type: relationType,
707
743
  depends_on_key: dependsOn?.key,
708
744
  depends_on_status: dependsOn?.status,
709
745
  });
710
746
  deps.set(insert.taskId, depList);
711
747
  }
712
- if (persist) {
713
- warnings.push(`Injected ${inserts.length} inferred foundation deps.`);
748
+ if (persist && removedInferred > 0) {
749
+ warnings.push(`Reconciled ${removedInferred} inferred foundation deps before re-inference.`);
714
750
  }
715
- else {
716
- warnings.push(`Dry run: inferred ${inserts.length} foundation deps (not persisted).`);
751
+ else if (!persist && removedInferred > 0) {
752
+ warnings.push(`Dry run: would reconcile ${removedInferred} inferred foundation deps.`);
753
+ }
754
+ if (inserts.length > 0) {
755
+ if (persist) {
756
+ warnings.push(`Injected ${inserts.length} scoped inferred foundation deps.`);
757
+ }
758
+ else {
759
+ warnings.push(`Dry run: inferred ${inserts.length} scoped foundation deps (not persisted).`);
760
+ }
717
761
  }
718
762
  if (skippedCycles > 0) {
719
763
  warnings.push(`Skipped ${skippedCycles} inferred foundation deps due to cycles.`);
@@ -782,6 +826,7 @@ export class TaskOrderingService {
782
826
  depList.push({
783
827
  task_id: insert.taskId,
784
828
  depends_on_task_id: insert.dependsOnTaskId,
829
+ relation_type: "inferred_agent",
785
830
  depends_on_key: dependsOn?.key,
786
831
  depends_on_status: dependsOn?.status,
787
832
  });
@@ -0,0 +1,48 @@
1
+ import { WorkspaceRepository } from "@mcoda/db";
2
+ import { VcsClient } from "@mcoda/integrations";
3
+ import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
4
+ import { TaskSelectionFilters, TaskSelectionService } from "./TaskSelectionService.js";
5
+ type AddTestsDeps = {
6
+ workspaceRepo: WorkspaceRepository;
7
+ selectionService: TaskSelectionService;
8
+ vcsClient?: VcsClient;
9
+ };
10
+ export interface AddTestsRequest extends TaskSelectionFilters {
11
+ projectKey: string;
12
+ dryRun?: boolean;
13
+ commit?: boolean;
14
+ baseBranch?: string;
15
+ }
16
+ export interface AddTestsResult {
17
+ projectKey: string;
18
+ selectedTaskKeys: string[];
19
+ tasksRequiringTests: string[];
20
+ updatedTaskKeys: string[];
21
+ skippedTaskKeys: string[];
22
+ createdFiles: string[];
23
+ runAllScriptPath?: string;
24
+ runAllCommand?: string;
25
+ branch?: string;
26
+ commitSha?: string;
27
+ warnings: string[];
28
+ }
29
+ export declare class AddTestsService {
30
+ private workspace;
31
+ private readonly workspaceRepo;
32
+ private readonly selectionService;
33
+ private readonly vcs;
34
+ private readonly ownsWorkspaceRepo;
35
+ private readonly ownsSelectionService;
36
+ constructor(workspace: WorkspaceResolution, deps: AddTestsDeps, ownership?: {
37
+ ownsWorkspaceRepo?: boolean;
38
+ ownsSelectionService?: boolean;
39
+ });
40
+ static create(workspace: WorkspaceResolution): Promise<AddTestsService>;
41
+ close(): Promise<void>;
42
+ private resolveTaskSelection;
43
+ private resolveTaskCommands;
44
+ private ensureBaseBranchForCommit;
45
+ addTests(request: AddTestsRequest): Promise<AddTestsResult>;
46
+ }
47
+ export {};
48
+ //# sourceMappingURL=AddTestsService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddTestsService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/AddTestsService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAA0B,MAAM,2BAA2B,CAAC;AAa/G,KAAK,YAAY,GAAG;IAClB,aAAa,EAAE,mBAAmB,CAAC;IACnC,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA+ID,qBAAa,eAAe;IAQxB,OAAO,CAAC,SAAS;IAPnB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAU;gBAGrC,SAAS,EAAE,mBAAmB,EACtC,IAAI,EAAE,YAAY,EAClB,SAAS,GAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAO;WASpE,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAUvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAUd,oBAAoB;YAqBpB,mBAAmB;YAoBnB,yBAAyB;IAqBjC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;CAoIlE"}
@@ -0,0 +1,346 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { WorkspaceRepository } from "@mcoda/db";
4
+ import { VcsClient } from "@mcoda/integrations";
5
+ import { PathHelper, WORK_ALLOWED_STATUSES, filterTaskStatuses } from "@mcoda/shared";
6
+ import { QaTestCommandBuilder } from "./QaTestCommandBuilder.js";
7
+ import { TaskSelectionService } from "./TaskSelectionService.js";
8
+ const DEFAULT_BASE_BRANCH = "mcoda-dev";
9
+ const MISSING_HARNESS_BLOCKER = /No runnable test harness discovered/i;
10
+ const FALLBACK_BOOTSTRAP_COMMAND = "node -e \"console.log('mcoda add-tests bootstrap placeholder')\"";
11
+ const normalizeStringArray = (value) => {
12
+ if (!Array.isArray(value))
13
+ return [];
14
+ return value
15
+ .filter((item) => typeof item === "string")
16
+ .map((item) => item.trim())
17
+ .filter(Boolean);
18
+ };
19
+ const normalizeTestCommands = (value) => {
20
+ if (typeof value === "string") {
21
+ const trimmed = value.trim();
22
+ return trimmed ? [trimmed] : [];
23
+ }
24
+ return normalizeStringArray(value);
25
+ };
26
+ const normalizeTestRequirements = (value) => {
27
+ const raw = value && typeof value === "object" ? value : {};
28
+ return {
29
+ unit: normalizeStringArray(raw.unit),
30
+ component: normalizeStringArray(raw.component),
31
+ integration: normalizeStringArray(raw.integration),
32
+ api: normalizeStringArray(raw.api),
33
+ };
34
+ };
35
+ const hasTestRequirements = (requirements) => requirements.unit.length > 0 ||
36
+ requirements.component.length > 0 ||
37
+ requirements.integration.length > 0 ||
38
+ requirements.api.length > 0;
39
+ const dedupeCommands = (commands) => {
40
+ const seen = new Set();
41
+ const result = [];
42
+ for (const command of commands) {
43
+ const trimmed = command.trim();
44
+ if (!trimmed || seen.has(trimmed))
45
+ continue;
46
+ seen.add(trimmed);
47
+ result.push(trimmed);
48
+ }
49
+ return result;
50
+ };
51
+ const resolveNodeCommand = () => {
52
+ const override = process.env.NODE_BIN?.trim();
53
+ const resolved = override || (process.platform === "win32" ? "node.exe" : "node");
54
+ return resolved.includes(" ") ? `"${resolved}"` : resolved;
55
+ };
56
+ const quoteShellPath = (value) => (value.includes(" ") ? `"${value}"` : value);
57
+ const buildRunAllTestsCommand = (relativePath) => {
58
+ const normalized = relativePath.split(path.sep).join("/");
59
+ if (normalized.endsWith(".js"))
60
+ return `${resolveNodeCommand()} ${normalized}`;
61
+ if (normalized.endsWith(".ps1")) {
62
+ const shell = process.platform === "win32" ? "powershell" : "pwsh";
63
+ return `${shell} -File ${quoteShellPath(normalized)}`;
64
+ }
65
+ if (normalized.endsWith(".sh"))
66
+ return `bash ${quoteShellPath(normalized)}`;
67
+ if (normalized.startsWith("."))
68
+ return normalized;
69
+ return `./${normalized}`;
70
+ };
71
+ const detectRunAllTestsScript = (workspaceRoot) => {
72
+ const candidates = ["tests/all.js", "tests/all.sh", "tests/all.ps1", "tests/all"];
73
+ for (const candidate of candidates) {
74
+ if (fs.existsSync(path.join(workspaceRoot, ...candidate.split("/"))))
75
+ return candidate;
76
+ }
77
+ return undefined;
78
+ };
79
+ const detectRunAllTestsCommand = (workspaceRoot) => {
80
+ const script = detectRunAllTestsScript(workspaceRoot);
81
+ if (!script)
82
+ return undefined;
83
+ return buildRunAllTestsCommand(script);
84
+ };
85
+ const pickSeedTestCategory = (requirements) => {
86
+ const order = ["unit", "component", "integration", "api"];
87
+ const active = order.filter((key) => requirements[key].length > 0);
88
+ if (active.length === 1)
89
+ return active[0];
90
+ return "unit";
91
+ };
92
+ const buildRunAllTestsScript = (seedCategory, seedCommands) => {
93
+ const suites = {
94
+ unit: [],
95
+ component: [],
96
+ integration: [],
97
+ api: [],
98
+ };
99
+ suites[seedCategory] = seedCommands;
100
+ return [
101
+ "#!/usr/bin/env node",
102
+ 'const { spawnSync } = require("node:child_process");',
103
+ "",
104
+ "// Register test commands per discipline.",
105
+ `const testSuites = ${JSON.stringify(suites, null, 2)};`,
106
+ "",
107
+ 'const entries = Object.entries(testSuites).flatMap(([label, commands]) =>',
108
+ " commands.map((command) => ({ label, command }))",
109
+ ");",
110
+ "if (!entries.length) {",
111
+ ' console.error("No test commands registered in tests/all.js. Add unit/component/integration/api commands.");',
112
+ " process.exit(1);",
113
+ "}",
114
+ "",
115
+ 'console.log("MCODA_RUN_ALL_TESTS_START");',
116
+ "let failed = false;",
117
+ "for (const entry of entries) {",
118
+ " const result = spawnSync(entry.command, { shell: true, stdio: \"inherit\" });",
119
+ " const status = typeof result.status === \"number\" ? result.status : 1;",
120
+ " if (status !== 0) failed = true;",
121
+ "}",
122
+ 'console.log(`MCODA_RUN_ALL_TESTS_COMPLETE status=${failed ? "failed" : "passed"}`);',
123
+ 'console.log("MCODA_RUN_ALL_TESTS_END");',
124
+ "process.exit(failed ? 1 : 0);",
125
+ "",
126
+ ].join("\n");
127
+ };
128
+ const stripHarnessBlocker = (metadata) => {
129
+ const rawQa = metadata.qa;
130
+ if (!rawQa || typeof rawQa !== "object")
131
+ return metadata;
132
+ const qa = { ...rawQa };
133
+ const blockers = normalizeStringArray(qa.blockers).filter((entry) => !MISSING_HARNESS_BLOCKER.test(entry));
134
+ if (blockers.length > 0) {
135
+ qa.blockers = blockers;
136
+ }
137
+ else {
138
+ delete qa.blockers;
139
+ }
140
+ return { ...metadata, qa };
141
+ };
142
+ export class AddTestsService {
143
+ constructor(workspace, deps, ownership = {}) {
144
+ this.workspace = workspace;
145
+ this.workspaceRepo = deps.workspaceRepo;
146
+ this.selectionService = deps.selectionService;
147
+ this.vcs = deps.vcsClient ?? new VcsClient();
148
+ this.ownsWorkspaceRepo = ownership.ownsWorkspaceRepo === true;
149
+ this.ownsSelectionService = ownership.ownsSelectionService === true;
150
+ }
151
+ static async create(workspace) {
152
+ const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
153
+ const selectionService = new TaskSelectionService(workspace, workspaceRepo);
154
+ return new AddTestsService(workspace, { workspaceRepo, selectionService }, { ownsWorkspaceRepo: true, ownsSelectionService: true });
155
+ }
156
+ async close() {
157
+ if (this.ownsSelectionService) {
158
+ await this.selectionService.close();
159
+ return;
160
+ }
161
+ if (this.ownsWorkspaceRepo) {
162
+ await this.workspaceRepo.close();
163
+ }
164
+ }
165
+ async resolveTaskSelection(request) {
166
+ const ignoreStatusFilter = request.taskKeys?.length ? true : request.ignoreStatusFilter;
167
+ const { filtered } = ignoreStatusFilter
168
+ ? { filtered: request.statusFilter ?? [] }
169
+ : filterTaskStatuses(request.statusFilter, WORK_ALLOWED_STATUSES, WORK_ALLOWED_STATUSES);
170
+ return this.selectionService.selectTasks({
171
+ projectKey: request.projectKey,
172
+ epicKey: request.epicKey,
173
+ storyKey: request.storyKey,
174
+ taskKeys: request.taskKeys,
175
+ statusFilter: filtered,
176
+ ignoreStatusFilter,
177
+ includeTypes: request.includeTypes,
178
+ excludeTypes: request.excludeTypes,
179
+ limit: request.limit,
180
+ parallel: request.parallel,
181
+ ignoreDependencies: request.ignoreDependencies ?? true,
182
+ missingContextPolicy: request.missingContextPolicy ?? "allow",
183
+ });
184
+ }
185
+ async resolveTaskCommands(task) {
186
+ const metadata = (task.task.metadata ?? {});
187
+ const requirements = normalizeTestRequirements(metadata.test_requirements ?? metadata.testRequirements);
188
+ const existingCommands = dedupeCommands(normalizeTestCommands(metadata.tests ?? metadata.testCommands));
189
+ if (!hasTestRequirements(requirements)) {
190
+ return { requirements, existingCommands, discoveredCommands: [] };
191
+ }
192
+ if (existingCommands.length > 0) {
193
+ return { requirements, existingCommands, discoveredCommands: existingCommands };
194
+ }
195
+ const commandBuilder = new QaTestCommandBuilder(this.workspace.workspaceRoot);
196
+ try {
197
+ const plan = await commandBuilder.build({ task: task.task });
198
+ const discoveredCommands = dedupeCommands(plan.commands);
199
+ return { requirements, existingCommands, discoveredCommands };
200
+ }
201
+ catch {
202
+ return { requirements, existingCommands, discoveredCommands: [] };
203
+ }
204
+ }
205
+ async ensureBaseBranchForCommit(baseBranch) {
206
+ const cwd = this.workspace.workspaceRoot;
207
+ const isRepo = await this.vcs.isRepo(cwd);
208
+ if (!isRepo) {
209
+ return { warning: "add-tests commit skipped: workspace is not a git repository." };
210
+ }
211
+ const status = await this.vcs.status(cwd);
212
+ if (status.trim().length > 0) {
213
+ return { warning: "add-tests commit skipped: working tree is dirty before bootstrap." };
214
+ }
215
+ try {
216
+ await this.vcs.ensureBaseBranch(cwd, baseBranch);
217
+ await this.vcs.checkoutBranch(cwd, baseBranch);
218
+ return { branch: baseBranch };
219
+ }
220
+ catch (error) {
221
+ return {
222
+ warning: `add-tests commit skipped: failed to prepare base branch ${baseBranch} (${error.message}).`,
223
+ };
224
+ }
225
+ }
226
+ async addTests(request) {
227
+ const warnings = [];
228
+ const createdFiles = [];
229
+ const updatedTaskKeys = [];
230
+ const skippedTaskKeys = [];
231
+ const dryRun = request.dryRun === true;
232
+ const commitEnabled = request.commit !== false && !dryRun;
233
+ const selection = await this.resolveTaskSelection(request);
234
+ const selectedTaskKeys = selection.ordered.map((entry) => entry.task.key);
235
+ warnings.push(...selection.warnings);
236
+ const requiringTests = [];
237
+ for (const entry of selection.ordered) {
238
+ const commands = await this.resolveTaskCommands(entry);
239
+ if (!hasTestRequirements(commands.requirements))
240
+ continue;
241
+ requiringTests.push({ entry, commands });
242
+ }
243
+ const tasksRequiringTests = requiringTests.map((item) => item.entry.task.key);
244
+ if (tasksRequiringTests.length === 0) {
245
+ return {
246
+ projectKey: request.projectKey,
247
+ selectedTaskKeys,
248
+ tasksRequiringTests: [],
249
+ updatedTaskKeys,
250
+ skippedTaskKeys,
251
+ createdFiles,
252
+ warnings,
253
+ };
254
+ }
255
+ let runAllScriptPath = detectRunAllTestsScript(this.workspace.workspaceRoot);
256
+ let runAllCommand = runAllScriptPath ? buildRunAllTestsCommand(runAllScriptPath) : undefined;
257
+ let commitBranch;
258
+ let commitSha;
259
+ const baseBranch = (request.baseBranch ?? this.workspace.config?.branch ?? DEFAULT_BASE_BRANCH).trim() || DEFAULT_BASE_BRANCH;
260
+ const seedRequirements = requiringTests[0]?.commands.requirements ?? {
261
+ unit: [],
262
+ component: [],
263
+ integration: [],
264
+ api: [],
265
+ };
266
+ const discoveredSeedCommands = dedupeCommands(requiringTests.flatMap((item) => item.commands.discoveredCommands));
267
+ const seedCommands = discoveredSeedCommands.length > 0 ? discoveredSeedCommands : [FALLBACK_BOOTSTRAP_COMMAND];
268
+ if (!runAllScriptPath) {
269
+ if (!dryRun) {
270
+ if (commitEnabled) {
271
+ const branchPrep = await this.ensureBaseBranchForCommit(baseBranch);
272
+ if (branchPrep.branch) {
273
+ commitBranch = branchPrep.branch;
274
+ }
275
+ else if (branchPrep.warning) {
276
+ warnings.push(branchPrep.warning);
277
+ }
278
+ }
279
+ const scriptPath = path.join(this.workspace.workspaceRoot, "tests", "all.js");
280
+ await PathHelper.ensureDir(path.dirname(scriptPath));
281
+ const seedCategory = pickSeedTestCategory(seedRequirements);
282
+ const contents = buildRunAllTestsScript(seedCategory, seedCommands);
283
+ await fs.promises.writeFile(scriptPath, contents, "utf8");
284
+ createdFiles.push("tests/all.js");
285
+ runAllScriptPath = "tests/all.js";
286
+ }
287
+ else {
288
+ warnings.push("Dry-run: add-tests would create tests/all.js.");
289
+ }
290
+ runAllCommand = buildRunAllTestsCommand("tests/all.js");
291
+ if (discoveredSeedCommands.length === 0) {
292
+ warnings.push("No stack-specific test commands were discovered; created a placeholder run-all harness. Replace tests/all.js commands with real suites.");
293
+ }
294
+ }
295
+ for (const item of requiringTests) {
296
+ const metadata = (item.entry.task.metadata ?? {});
297
+ const existingCommands = item.commands.existingCommands;
298
+ const fallbackCommands = item.commands.discoveredCommands.length > 0
299
+ ? item.commands.discoveredCommands
300
+ : runAllCommand
301
+ ? [runAllCommand]
302
+ : [];
303
+ const resolvedCommands = dedupeCommands(existingCommands.length > 0 ? existingCommands : fallbackCommands);
304
+ if (resolvedCommands.length === 0) {
305
+ skippedTaskKeys.push(item.entry.task.key);
306
+ continue;
307
+ }
308
+ const nextMetadata = stripHarnessBlocker({
309
+ ...metadata,
310
+ tests: resolvedCommands,
311
+ testCommands: resolvedCommands,
312
+ });
313
+ const before = JSON.stringify(metadata);
314
+ const after = JSON.stringify(nextMetadata);
315
+ if (before !== after) {
316
+ if (!dryRun) {
317
+ await this.workspaceRepo.updateTask(item.entry.task.id, { metadata: nextMetadata });
318
+ }
319
+ updatedTaskKeys.push(item.entry.task.key);
320
+ }
321
+ }
322
+ if (commitEnabled && createdFiles.includes("tests/all.js")) {
323
+ try {
324
+ await this.vcs.stage(this.workspace.workspaceRoot, ["tests/all.js"]);
325
+ await this.vcs.commit(this.workspace.workspaceRoot, "chore(mcoda): bootstrap test harness");
326
+ commitSha = await this.vcs.lastCommitSha(this.workspace.workspaceRoot);
327
+ }
328
+ catch (error) {
329
+ warnings.push(`add-tests commit failed: ${error.message}`);
330
+ }
331
+ }
332
+ return {
333
+ projectKey: request.projectKey,
334
+ selectedTaskKeys,
335
+ tasksRequiringTests,
336
+ updatedTaskKeys,
337
+ skippedTaskKeys,
338
+ createdFiles,
339
+ runAllScriptPath,
340
+ runAllCommand,
341
+ branch: commitBranch,
342
+ commitSha,
343
+ warnings,
344
+ };
345
+ }
346
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import { JobService } from "../jobs/JobService.js";
12
12
  import { TaskSelectionService } from "./TaskSelectionService.js";
13
13
  import { TaskStateService } from "./TaskStateService.js";
14
14
  import { QaTestCommandBuilder } from "./QaTestCommandBuilder.js";
15
+ import { AddTestsService } from "./AddTestsService.js";
15
16
  import { RoutingService } from "../agents/RoutingService.js";
16
17
  import { GATEWAY_HANDOFF_ENV_PATH } from "../agents/GatewayHandoff.js";
17
18
  import { AgentRatingService } from "../agents/AgentRatingService.js";
@@ -3689,15 +3690,74 @@ export class WorkOnTasksService {
3689
3690
  warnings.push(...selection.warnings);
3690
3691
  const results = [];
3691
3692
  const taskSummaries = new Map();
3693
+ let preflightMissingHarnessTasks = await this.findMissingTestHarnessTasks(selection.ordered);
3694
+ if (preflightMissingHarnessTasks.length > 0 && !request.dryRun) {
3695
+ const bootstrapProjectKey = selection.project?.key ?? request.projectKey;
3696
+ if (!bootstrapProjectKey) {
3697
+ warnings.push("add-tests bootstrap skipped: project key could not be resolved.");
3698
+ }
3699
+ else {
3700
+ try {
3701
+ const addTestsService = new AddTestsService(this.workspace, {
3702
+ workspaceRepo: this.deps.workspaceRepo,
3703
+ selectionService: this.selectionService,
3704
+ vcsClient: this.vcs,
3705
+ });
3706
+ const bootstrap = await addTestsService.addTests({
3707
+ projectKey: bootstrapProjectKey,
3708
+ taskKeys: preflightMissingHarnessTasks.map((issue) => issue.taskKey),
3709
+ ignoreStatusFilter: true,
3710
+ ignoreDependencies: true,
3711
+ dryRun: false,
3712
+ commit: !(request.noCommit ?? false),
3713
+ baseBranch,
3714
+ });
3715
+ if (bootstrap.createdFiles.length > 0) {
3716
+ warnings.push(`add-tests bootstrap created: ${bootstrap.createdFiles.join(", ")}`);
3717
+ }
3718
+ if (bootstrap.commitSha) {
3719
+ warnings.push(`add-tests bootstrap commit: ${bootstrap.commitSha}${bootstrap.branch ? ` on ${bootstrap.branch}` : ""}`);
3720
+ }
3721
+ warnings.push(...bootstrap.warnings.map((warning) => `add-tests: ${warning}`));
3722
+ await this.checkpoint(job.id, "tests_bootstrap", {
3723
+ createdFiles: bootstrap.createdFiles,
3724
+ updatedTaskKeys: bootstrap.updatedTaskKeys,
3725
+ skippedTaskKeys: bootstrap.skippedTaskKeys,
3726
+ commitSha: bootstrap.commitSha ?? null,
3727
+ branch: bootstrap.branch ?? null,
3728
+ });
3729
+ const refreshedRows = await this.deps.workspaceRepo.getTasksByIds(selection.ordered.map((entry) => entry.task.id));
3730
+ const refreshedById = new Map(refreshedRows.map((row) => [row.id, row]));
3731
+ selection = {
3732
+ ...selection,
3733
+ ordered: selection.ordered.map((entry) => {
3734
+ const refreshed = refreshedById.get(entry.task.id);
3735
+ if (!refreshed)
3736
+ return entry;
3737
+ return {
3738
+ ...entry,
3739
+ task: {
3740
+ ...entry.task,
3741
+ metadata: refreshed.metadata,
3742
+ },
3743
+ };
3744
+ }),
3745
+ };
3746
+ }
3747
+ catch (error) {
3748
+ warnings.push(`add-tests bootstrap failed: ${error instanceof Error ? error.message : String(error)}`);
3749
+ }
3750
+ }
3751
+ preflightMissingHarnessTasks = await this.findMissingTestHarnessTasks(selection.ordered);
3752
+ }
3692
3753
  if (missingTestsPolicy === "block_job") {
3693
- const missingHarnessTasks = await this.findMissingTestHarnessTasks(selection.ordered);
3694
- if (missingHarnessTasks.length > 0) {
3695
- const taskKeys = missingHarnessTasks.map((issue) => issue.taskKey);
3754
+ if (preflightMissingHarnessTasks.length > 0) {
3755
+ const taskKeys = preflightMissingHarnessTasks.map((issue) => issue.taskKey);
3696
3756
  await this.checkpoint(job.id, "tests_preflight_blocked", {
3697
3757
  reason: "missing_test_harness",
3698
3758
  policy: missingTestsPolicy,
3699
3759
  taskKeys,
3700
- issues: missingHarnessTasks.map((issue) => ({
3760
+ issues: preflightMissingHarnessTasks.map((issue) => ({
3701
3761
  taskKey: issue.taskKey,
3702
3762
  testRequirements: issue.testRequirements,
3703
3763
  attemptedCommands: issue.attemptedCommands,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcoda/core",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Core services and APIs for the mcoda CLI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,11 +32,11 @@
32
32
  "dependencies": {
33
33
  "@apidevtools/swagger-parser": "^10.1.0",
34
34
  "yaml": "^2.4.2",
35
- "@mcoda/shared": "0.1.21",
36
- "@mcoda/db": "0.1.21",
37
- "@mcoda/agents": "0.1.21",
38
- "@mcoda/generators": "0.1.21",
39
- "@mcoda/integrations": "0.1.21"
35
+ "@mcoda/shared": "0.1.23",
36
+ "@mcoda/db": "0.1.23",
37
+ "@mcoda/generators": "0.1.23",
38
+ "@mcoda/agents": "0.1.23",
39
+ "@mcoda/integrations": "0.1.23"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.json",