mcoda 0.1.23 → 0.1.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 CHANGED
@@ -27,7 +27,7 @@ mcoda docs pdr generate --workspace-root . --project WEB --rfp-path docs/rfp/web
27
27
  ## Common commands
28
28
  - Docs: `mcoda docs pdr generate`, `mcoda docs sds generate`
29
29
  - Specs: `mcoda openapi-from-docs`
30
- - Planning: `mcoda create-tasks`, `mcoda refine-tasks`, `mcoda order-tasks`
30
+ - Planning: `mcoda create-tasks`, `mcoda task-sufficiency-audit`, `mcoda refine-tasks`, `mcoda order-tasks`
31
31
  - Execution: `mcoda add-tests`, `mcoda work-on-tasks`, `mcoda code-review`, `mcoda qa-tasks`
32
32
  - Backlog: `mcoda backlog`, `mcoda task`
33
33
  - Jobs/telemetry: `mcoda jobs`, `mcoda tokens`, `mcoda telemetry`
@@ -35,6 +35,8 @@ mcoda docs pdr generate --workspace-root . --project WEB --rfp-path docs/rfp/web
35
35
  - Updates: `mcoda update --check`
36
36
 
37
37
  `mcoda work-on-tasks` auto-runs the same test-harness bootstrap logic as `mcoda add-tests` when selected tasks require tests but no runnable harness exists.
38
+ `mcoda create-tasks` auto-runs a sufficiency pass (same engine as `mcoda task-sufficiency-audit`) to compare SDS coverage against generated backlog items and fill obvious planning gaps.
39
+ If that sufficiency pass errors, create-tasks continues (fail-open) and records audit failure details in job checkpoints/logs.
38
40
 
39
41
  ## Configuration
40
42
  Environment variables are optional overrides for workspace settings:
@@ -1 +1 @@
1
- {"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";AA6BA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CA0LxE"}
1
+ {"version":3,"file":"McodaEntrypoint.d.ts","sourceRoot":"","sources":["../../src/bin/McodaEntrypoint.ts"],"names":[],"mappings":";AA8BA,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CA8LxE"}
@@ -9,6 +9,7 @@ import { JobsCommands } from "../commands/jobs/JobsCommands.js";
9
9
  import { OpenapiCommands } from "../commands/openapi/OpenapiCommands.js";
10
10
  import { CreateTasksCommand } from "../commands/planning/CreateTasksCommand.js";
11
11
  import { RefineTasksCommand } from "../commands/planning/RefineTasksCommand.js";
12
+ import { TaskSufficiencyAuditCommand } from "../commands/planning/TaskSufficiencyAuditCommand.js";
12
13
  import { BacklogCommands } from "../commands/backlog/BacklogCommands.js";
13
14
  import { TaskShowCommands } from "../commands/backlog/TaskShowCommands.js";
14
15
  import { OrderTasksCommand } from "../commands/backlog/OrderTasksCommand.js";
@@ -81,7 +82,7 @@ export class McodaEntrypoint {
81
82
  return;
82
83
  }
83
84
  if (!command) {
84
- throw new Error("Usage: mcoda <agent|gateway-agent|test-agent|agent-run|routing|docs|openapi|job|jobs|tokens|telemetry|create-tasks|migrate-tasks|refine-tasks|order-tasks|tasks|add-tests|work-on-tasks|gateway-trio|code-review|qa-tasks|backlog|task|task-detail|estimate|update|set-workspace|project-guidance|pdr|sds> [...args]\n" +
85
+ throw new Error("Usage: mcoda <agent|gateway-agent|test-agent|agent-run|routing|docs|openapi|job|jobs|tokens|telemetry|create-tasks|migrate-tasks|refine-tasks|task-sufficiency-audit|order-tasks|tasks|add-tests|work-on-tasks|gateway-trio|code-review|qa-tasks|backlog|task|task-detail|estimate|update|set-workspace|project-guidance|pdr|sds> [...args]\n" +
85
86
  "Routing: use `mcoda routing defaults` to view/update workspace/global defaults, `mcoda routing preview|explain` to inspect agent selection/provenance (override → workspace_default → global_default).\n" +
86
87
  "Aliases: `tasks order-by-deps` forwards to `order-tasks` (dependency-aware ordering), `task`/`task-detail` show a single task.\n" +
87
88
  "Job commands (mcoda job --help for details): list|status|watch|logs|inspect|resume|cancel|tokens\n" +
@@ -147,6 +148,10 @@ export class McodaEntrypoint {
147
148
  await RefineTasksCommand.run(rest);
148
149
  return;
149
150
  }
151
+ if (command === "task-sufficiency-audit") {
152
+ await TaskSufficiencyAuditCommand.run(rest);
153
+ return;
154
+ }
150
155
  if (command === "qa-tasks") {
151
156
  if (rest.includes("--help") || rest.includes("-h")) {
152
157
  // eslint-disable-next-line no-console
@@ -13,6 +13,18 @@ interface ParsedArgs {
13
13
  stageOrder?: string[];
14
14
  json: boolean;
15
15
  }
16
+ type ProjectKeyCandidate = {
17
+ key: string;
18
+ createdAt?: string | null;
19
+ };
20
+ export declare const pickOrderTasksProjectKey: (options: {
21
+ requestedKey?: string;
22
+ configuredKey?: string;
23
+ existing: ProjectKeyCandidate[];
24
+ }) => {
25
+ projectKey?: string;
26
+ warnings: string[];
27
+ };
16
28
  export declare const parseOrderTasksArgs: (argv: string[]) => ParsedArgs;
17
29
  export declare class OrderTasksCommand {
18
30
  static run(argv: string[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"OrderTasksCommand.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/OrderTasksCommand.ts"],"names":[],"mappings":"AAGA,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,qBAAqB,EAAE,aAAa,GAAG,aAAa,GAAG,wBAAwB,CAAC;IAChF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf;AAuDD,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,KAAG,UA4IpD,CAAC;AAkDF,qBAAa,iBAAiB;WACf,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA+DhD"}
1
+ {"version":3,"file":"OrderTasksCommand.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/OrderTasksCommand.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,qBAAqB,EAAE,aAAa,GAAG,aAAa,GAAG,wBAAwB,CAAC;IAChF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAmCtE,eAAO,MAAM,wBAAwB,GAAI,SAAS;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,KAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAkC5C,CAAC;AAwCF,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,KAAG,UA4IpD,CAAC;AAkDF,qBAAa,iBAAiB;WACf,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA6EhD"}
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
+ import { WorkspaceRepository } from "@mcoda/db";
2
3
  import { TaskOrderingService, WorkspaceResolver } from "@mcoda/core";
3
4
  const usage = `mcoda order-tasks \\
4
5
  [--workspace-root <PATH>] \\
5
- --project <PROJECT_KEY> \\
6
+ [--project <PROJECT_KEY>] \\
6
7
  [--epic <EPIC_KEY>] \\
7
8
  [--story <STORY_KEY>] \\
8
9
  [--status <STATUS_FILTER>] \\
@@ -14,6 +15,50 @@ const usage = `mcoda order-tasks \\
14
15
  [--stage-order <foundation,backend,frontend,other>] \\
15
16
  [--rate-agents] \\
16
17
  [--json]`;
18
+ const listWorkspaceProjects = async (workspaceRoot) => {
19
+ const repo = await WorkspaceRepository.create(workspaceRoot);
20
+ try {
21
+ const rows = await repo
22
+ .getDb()
23
+ .all(`SELECT key, created_at FROM projects ORDER BY created_at ASC, key ASC`);
24
+ return rows
25
+ .map((row) => ({ key: String(row.key), createdAt: row.created_at ?? null }))
26
+ .filter((row) => row.key.trim().length > 0);
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ finally {
32
+ await repo.close();
33
+ }
34
+ };
35
+ export const pickOrderTasksProjectKey = (options) => {
36
+ const warnings = [];
37
+ const requestedKey = options.requestedKey?.trim() || undefined;
38
+ const configuredKey = options.configuredKey?.trim() || undefined;
39
+ const existing = options.existing ?? [];
40
+ const firstExisting = existing[0]?.key;
41
+ if (requestedKey) {
42
+ if (configuredKey && configuredKey !== requestedKey) {
43
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; overriding configured project key "${configuredKey}".`);
44
+ }
45
+ if (firstExisting && requestedKey !== firstExisting) {
46
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; first workspace project is "${firstExisting}".`);
47
+ }
48
+ return { projectKey: requestedKey, warnings };
49
+ }
50
+ if (configuredKey) {
51
+ if (firstExisting && configuredKey !== firstExisting) {
52
+ warnings.push(`Using configured project key "${configuredKey}" instead of first workspace project "${firstExisting}".`);
53
+ }
54
+ return { projectKey: configuredKey, warnings };
55
+ }
56
+ if (firstExisting) {
57
+ warnings.push(`No --project provided; defaulting to first workspace project "${firstExisting}".`);
58
+ return { projectKey: firstExisting, warnings };
59
+ }
60
+ return { projectKey: undefined, warnings };
61
+ };
17
62
  const parseBooleanFlag = (value, defaultValue) => {
18
63
  if (value === undefined)
19
64
  return defaultValue;
@@ -243,9 +288,19 @@ export class OrderTasksCommand {
243
288
  cwd: process.cwd(),
244
289
  explicitWorkspace: parsed.workspaceRoot,
245
290
  });
246
- if (!parsed.project) {
291
+ const existingProjects = parsed.project ? [] : await listWorkspaceProjects(workspace.workspaceRoot);
292
+ const configuredKey = typeof workspace.config?.projectKey === "string" && workspace.config.projectKey.trim().length > 0
293
+ ? workspace.config.projectKey
294
+ : undefined;
295
+ const projectResolution = pickOrderTasksProjectKey({
296
+ requestedKey: parsed.project,
297
+ configuredKey,
298
+ existing: existingProjects,
299
+ });
300
+ const commandWarnings = [...projectResolution.warnings];
301
+ if (!projectResolution.projectKey) {
247
302
  // eslint-disable-next-line no-console
248
- console.error("order-tasks requires --project <PROJECT_KEY>");
303
+ console.error("order-tasks could not resolve a project key. Provide --project <PROJECT_KEY> or create tasks for this workspace first.");
249
304
  process.exitCode = 1;
250
305
  return;
251
306
  }
@@ -263,7 +318,7 @@ export class OrderTasksCommand {
263
318
  const service = await TaskOrderingService.create(workspace);
264
319
  try {
265
320
  const result = await service.orderTasks({
266
- projectKey: parsed.project,
321
+ projectKey: projectResolution.projectKey,
267
322
  epicKey: parsed.epic,
268
323
  storyKey: parsed.story,
269
324
  statusFilter: parsed.status,
@@ -275,18 +330,19 @@ export class OrderTasksCommand {
275
330
  planningContextPolicy: parsed.planningContextPolicy,
276
331
  stageOrder: resolvedStageOrder,
277
332
  });
333
+ const warnings = [...commandWarnings, ...result.warnings];
278
334
  if (parsed.json) {
279
335
  const payload = {
280
336
  order: result.ordered,
281
337
  };
282
- if (result.warnings.length > 0) {
283
- payload.warnings = result.warnings;
338
+ if (warnings.length > 0) {
339
+ payload.warnings = warnings;
284
340
  }
285
341
  // eslint-disable-next-line no-console
286
342
  console.log(JSON.stringify(payload, null, 2));
287
343
  return;
288
344
  }
289
- renderOrder(result.ordered, result.warnings);
345
+ renderOrder(result.ordered, warnings);
290
346
  }
291
347
  catch (error) {
292
348
  // eslint-disable-next-line no-console
@@ -23,6 +23,18 @@ interface ParsedRefineArgs {
23
23
  planOut?: string;
24
24
  jobId?: string;
25
25
  }
26
+ type ProjectKeyCandidate = {
27
+ key: string;
28
+ createdAt?: string | null;
29
+ };
30
+ export declare const pickRefineTasksProjectKey: (options: {
31
+ requestedKey?: string;
32
+ configuredKey?: string;
33
+ existing: ProjectKeyCandidate[];
34
+ }) => {
35
+ projectKey?: string;
36
+ warnings: string[];
37
+ };
26
38
  export declare const parseRefineTasksArgs: (argv: string[]) => ParsedRefineArgs;
27
39
  export declare class RefineTasksCommand {
28
40
  static run(argv: string[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"RefineTasksCommand.d.ts","sourceRoot":"","sources":["../../../src/commands/planning/RefineTasksCommand.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,UAAU,gBAAgB;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8BD,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,EAAE,KAAG,gBAyNrD,CAAC;AAEF,qBAAa,kBAAkB;WAChB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA0JhD"}
1
+ {"version":3,"file":"RefineTasksCommand.d.ts","sourceRoot":"","sources":["../../../src/commands/planning/RefineTasksCommand.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,UAAU,gBAAgB;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAsBtE,eAAO,MAAM,yBAAyB,GAAI,SAAS;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,KAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAkC5C,CAAC;AA4BF,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,EAAE,KAAG,gBAyNrD,CAAC;AAEF,qBAAa,kBAAkB;WAChB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkLhD"}
@@ -1,6 +1,51 @@
1
1
  import path from "node:path";
2
+ import { WorkspaceRepository } from "@mcoda/db";
2
3
  import { RefineTasksService, WorkspaceResolver } from "@mcoda/core";
3
- const usage = `mcoda refine-tasks --project <PROJECT_KEY> [--workspace-root <PATH>] [--epic <EPIC_KEY>] [--story <STORY_KEY>] [--task <TASK_KEY> ...] [--status <STATUS>] [--max-tasks N] [--strategy split|merge|enrich|estimate|auto] [--agent <NAME>] [--agent-stream [true|false]] [--rate-agents] [--from-db [true|false]] [--dry-run] [--apply] [--resume|--skip-refined] [--run-all] [--batch-size N] [--max-batches N] [--plan-in <PATH>] [--plan-out <PATH>] [--json]`;
4
+ const usage = `mcoda refine-tasks [--project <PROJECT_KEY>] [--workspace-root <PATH>] [--epic <EPIC_KEY>] [--story <STORY_KEY>] [--task <TASK_KEY> ...] [--status <STATUS>] [--max-tasks N] [--strategy split|merge|enrich|estimate|auto] [--agent <NAME>] [--agent-stream [true|false]] [--rate-agents] [--from-db [true|false]] [--dry-run] [--apply] [--resume|--skip-refined] [--run-all] [--batch-size N] [--max-batches N] [--plan-in <PATH>] [--plan-out <PATH>] [--json]`;
5
+ const listWorkspaceProjects = async (workspaceRoot) => {
6
+ const repo = await WorkspaceRepository.create(workspaceRoot);
7
+ try {
8
+ const rows = await repo
9
+ .getDb()
10
+ .all(`SELECT key, created_at FROM projects ORDER BY created_at ASC, key ASC`);
11
+ return rows
12
+ .map((row) => ({ key: String(row.key), createdAt: row.created_at ?? null }))
13
+ .filter((row) => row.key.trim().length > 0);
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ finally {
19
+ await repo.close();
20
+ }
21
+ };
22
+ export const pickRefineTasksProjectKey = (options) => {
23
+ const warnings = [];
24
+ const requestedKey = options.requestedKey?.trim() || undefined;
25
+ const configuredKey = options.configuredKey?.trim() || undefined;
26
+ const existing = options.existing ?? [];
27
+ const firstExisting = existing[0]?.key;
28
+ if (requestedKey) {
29
+ if (configuredKey && configuredKey !== requestedKey) {
30
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; overriding configured project key "${configuredKey}".`);
31
+ }
32
+ if (firstExisting && requestedKey !== firstExisting) {
33
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; first workspace project is "${firstExisting}".`);
34
+ }
35
+ return { projectKey: requestedKey, warnings };
36
+ }
37
+ if (configuredKey) {
38
+ if (firstExisting && configuredKey !== firstExisting) {
39
+ warnings.push(`Using configured project key "${configuredKey}" instead of first workspace project "${firstExisting}".`);
40
+ }
41
+ return { projectKey: configuredKey, warnings };
42
+ }
43
+ if (firstExisting) {
44
+ warnings.push(`No --project provided; defaulting to first workspace project "${firstExisting}".`);
45
+ return { projectKey: firstExisting, warnings };
46
+ }
47
+ return { projectKey: undefined, warnings };
48
+ };
4
49
  const parseBooleanFlag = (value, defaultValue) => {
5
50
  if (value === undefined)
6
51
  return defaultValue;
@@ -251,12 +296,6 @@ export const parseRefineTasksArgs = (argv) => {
251
296
  export class RefineTasksCommand {
252
297
  static async run(argv) {
253
298
  const parsed = parseRefineTasksArgs(argv);
254
- if (!parsed.projectKey) {
255
- // eslint-disable-next-line no-console
256
- console.error("refine-tasks requires --project <PROJECT_KEY>");
257
- process.exitCode = 1;
258
- return;
259
- }
260
299
  if (parsed.apply && parsed.dryRun) {
261
300
  // eslint-disable-next-line no-console
262
301
  console.error("refine-tasks: --apply cannot be used with --dry-run");
@@ -273,11 +312,31 @@ export class RefineTasksCommand {
273
312
  cwd: process.cwd(),
274
313
  explicitWorkspace: parsed.workspaceRoot,
275
314
  });
315
+ const existingProjects = parsed.projectKey ? [] : await listWorkspaceProjects(workspace.workspaceRoot);
316
+ const configuredKey = typeof workspace.config?.projectKey === "string" && workspace.config.projectKey.trim().length > 0
317
+ ? workspace.config.projectKey
318
+ : undefined;
319
+ const projectResolution = pickRefineTasksProjectKey({
320
+ requestedKey: parsed.projectKey,
321
+ configuredKey,
322
+ existing: existingProjects,
323
+ });
324
+ const commandWarnings = [...projectResolution.warnings];
325
+ if (!projectResolution.projectKey) {
326
+ // eslint-disable-next-line no-console
327
+ console.error("refine-tasks could not resolve a project key. Provide --project <PROJECT_KEY> or create tasks for this workspace first.");
328
+ process.exitCode = 1;
329
+ return;
330
+ }
331
+ if (commandWarnings.length > 0 && !parsed.json) {
332
+ // eslint-disable-next-line no-console
333
+ console.warn(commandWarnings.map((warning) => `! ${warning}`).join("\n"));
334
+ }
276
335
  const service = await RefineTasksService.create(workspace);
277
336
  try {
278
337
  const baseRequest = {
279
338
  workspace,
280
- projectKey: parsed.projectKey,
339
+ projectKey: projectResolution.projectKey,
281
340
  epicKey: parsed.epicKey,
282
341
  storyKey: parsed.storyKey,
283
342
  taskKeys: parsed.taskKeys.length ? parsed.taskKeys : undefined,
@@ -329,7 +388,7 @@ export class RefineTasksCommand {
329
388
  }
330
389
  if (parsed.json) {
331
390
  // eslint-disable-next-line no-console
332
- console.log(JSON.stringify({ status: "completed", totalProcessed, totalAffected, batches }, null, 2));
391
+ console.log(JSON.stringify({ status: "completed", totalProcessed, totalAffected, batches, warnings: commandWarnings }, null, 2));
333
392
  }
334
393
  else {
335
394
  // eslint-disable-next-line no-console
@@ -342,12 +401,13 @@ export class RefineTasksCommand {
342
401
  maxTasks: parsed.maxTasks,
343
402
  });
344
403
  if (parsed.json) {
404
+ const warnings = [...commandWarnings, ...(result.plan.warnings ?? [])];
345
405
  // eslint-disable-next-line no-console
346
406
  console.log(JSON.stringify({
347
407
  status: result.applied ? "applied" : "dry_run",
348
408
  summary: result.summary,
349
409
  plan: result.plan,
350
- warnings: result.plan.warnings ?? [],
410
+ warnings,
351
411
  }, null, 2));
352
412
  }
353
413
  else {
@@ -0,0 +1,28 @@
1
+ interface ParsedTaskSufficiencyArgs {
2
+ workspaceRoot?: string;
3
+ projectKey?: string;
4
+ maxIterations?: number;
5
+ maxTasksPerIteration?: number;
6
+ minCoverageRatio?: number;
7
+ dryRun: boolean;
8
+ json: boolean;
9
+ quiet: boolean;
10
+ }
11
+ type ProjectKeyCandidate = {
12
+ key: string;
13
+ createdAt?: string | null;
14
+ };
15
+ export declare const pickTaskSufficiencyProjectKey: (options: {
16
+ requestedKey?: string;
17
+ configuredKey?: string;
18
+ existing: ProjectKeyCandidate[];
19
+ }) => {
20
+ projectKey?: string;
21
+ warnings: string[];
22
+ };
23
+ export declare const parseTaskSufficiencyAuditArgs: (argv: string[]) => ParsedTaskSufficiencyArgs;
24
+ export declare class TaskSufficiencyAuditCommand {
25
+ static run(argv: string[]): Promise<void>;
26
+ }
27
+ export {};
28
+ //# sourceMappingURL=TaskSufficiencyAuditCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskSufficiencyAuditCommand.d.ts","sourceRoot":"","sources":["../../../src/commands/planning/TaskSufficiencyAuditCommand.ts"],"names":[],"mappings":"AAIA,UAAU,yBAAyB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAsBtE,eAAO,MAAM,6BAA6B,GAAI,SAAS;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,KAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAkC5C,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,EAAE,KAAG,yBAmE9D,CAAC;AAEF,qBAAa,2BAA2B;WACzB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA0GhD"}
@@ -0,0 +1,209 @@
1
+ import path from "node:path";
2
+ import { WorkspaceRepository } from "@mcoda/db";
3
+ import { TaskSufficiencyService, WorkspaceResolver } from "@mcoda/core";
4
+ const usage = `mcoda task-sufficiency-audit [--workspace-root <path>] [--project <PROJECT_KEY>] [--max-iterations N] [--max-tasks-per-iteration N] [--min-coverage-ratio 0..1] [--dry-run] [--json] [--quiet]`;
5
+ const listWorkspaceProjects = async (workspaceRoot) => {
6
+ const repo = await WorkspaceRepository.create(workspaceRoot);
7
+ try {
8
+ const rows = await repo
9
+ .getDb()
10
+ .all(`SELECT key, created_at FROM projects ORDER BY created_at ASC, key ASC`);
11
+ return rows
12
+ .map((row) => ({ key: String(row.key), createdAt: row.created_at ?? null }))
13
+ .filter((row) => row.key.trim().length > 0);
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ finally {
19
+ await repo.close();
20
+ }
21
+ };
22
+ export const pickTaskSufficiencyProjectKey = (options) => {
23
+ const warnings = [];
24
+ const requestedKey = options.requestedKey?.trim() || undefined;
25
+ const configuredKey = options.configuredKey?.trim() || undefined;
26
+ const existing = options.existing ?? [];
27
+ const firstExisting = existing[0]?.key;
28
+ if (requestedKey) {
29
+ if (configuredKey && configuredKey !== requestedKey) {
30
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; overriding configured project key "${configuredKey}".`);
31
+ }
32
+ if (firstExisting && requestedKey !== firstExisting) {
33
+ warnings.push(`Using explicitly requested project key "${requestedKey}"; first workspace project is "${firstExisting}".`);
34
+ }
35
+ return { projectKey: requestedKey, warnings };
36
+ }
37
+ if (configuredKey) {
38
+ if (firstExisting && configuredKey !== firstExisting) {
39
+ warnings.push(`Using configured project key "${configuredKey}" instead of first workspace project "${firstExisting}".`);
40
+ }
41
+ return { projectKey: configuredKey, warnings };
42
+ }
43
+ if (firstExisting) {
44
+ warnings.push(`No --project provided; defaulting to first workspace project "${firstExisting}".`);
45
+ return { projectKey: firstExisting, warnings };
46
+ }
47
+ return { projectKey: undefined, warnings };
48
+ };
49
+ export const parseTaskSufficiencyAuditArgs = (argv) => {
50
+ let workspaceRoot;
51
+ let projectKey;
52
+ let maxIterations;
53
+ let maxTasksPerIteration;
54
+ let minCoverageRatio;
55
+ let dryRun = false;
56
+ let json = false;
57
+ let quiet = false;
58
+ for (let i = 0; i < argv.length; i += 1) {
59
+ const arg = argv[i];
60
+ switch (arg) {
61
+ case "--workspace-root":
62
+ case "--workspace":
63
+ workspaceRoot = argv[i + 1] ? path.resolve(argv[i + 1]) : undefined;
64
+ i += 1;
65
+ break;
66
+ case "--project":
67
+ case "--project-key":
68
+ projectKey = argv[i + 1];
69
+ i += 1;
70
+ break;
71
+ case "--max-iterations":
72
+ maxIterations = Number(argv[i + 1]);
73
+ i += 1;
74
+ break;
75
+ case "--max-tasks-per-iteration":
76
+ case "--max-new-tasks":
77
+ maxTasksPerIteration = Number(argv[i + 1]);
78
+ i += 1;
79
+ break;
80
+ case "--min-coverage-ratio":
81
+ case "--min-coverage":
82
+ minCoverageRatio = Number(argv[i + 1]);
83
+ i += 1;
84
+ break;
85
+ case "--dry-run":
86
+ dryRun = true;
87
+ break;
88
+ case "--json":
89
+ json = true;
90
+ break;
91
+ case "--quiet":
92
+ quiet = true;
93
+ break;
94
+ case "--help":
95
+ case "-h":
96
+ // eslint-disable-next-line no-console
97
+ console.log(usage);
98
+ process.exit(0);
99
+ break;
100
+ default:
101
+ break;
102
+ }
103
+ }
104
+ return {
105
+ workspaceRoot,
106
+ projectKey,
107
+ maxIterations: Number.isFinite(maxIterations) ? maxIterations : undefined,
108
+ maxTasksPerIteration: Number.isFinite(maxTasksPerIteration) ? maxTasksPerIteration : undefined,
109
+ minCoverageRatio: Number.isFinite(minCoverageRatio) ? minCoverageRatio : undefined,
110
+ dryRun,
111
+ json,
112
+ quiet,
113
+ };
114
+ };
115
+ export class TaskSufficiencyAuditCommand {
116
+ static async run(argv) {
117
+ const parsed = parseTaskSufficiencyAuditArgs(argv);
118
+ const workspace = await WorkspaceResolver.resolveWorkspace({
119
+ cwd: process.cwd(),
120
+ explicitWorkspace: parsed.workspaceRoot,
121
+ });
122
+ const existingProjects = parsed.projectKey ? [] : await listWorkspaceProjects(workspace.workspaceRoot);
123
+ const configuredKey = typeof workspace.config?.projectKey === "string" && workspace.config.projectKey.trim().length > 0
124
+ ? workspace.config.projectKey
125
+ : undefined;
126
+ const projectResolution = pickTaskSufficiencyProjectKey({
127
+ requestedKey: parsed.projectKey,
128
+ configuredKey,
129
+ existing: existingProjects,
130
+ });
131
+ if (!projectResolution.projectKey) {
132
+ // eslint-disable-next-line no-console
133
+ console.error("task-sufficiency-audit could not resolve a project key. Provide --project <PROJECT_KEY> or run create-tasks first.");
134
+ process.exitCode = 1;
135
+ return;
136
+ }
137
+ if (projectResolution.warnings.length > 0 && !parsed.json && !parsed.quiet) {
138
+ // eslint-disable-next-line no-console
139
+ console.warn(projectResolution.warnings.map((warning) => `! ${warning}`).join("\n"));
140
+ }
141
+ const service = await TaskSufficiencyService.create(workspace);
142
+ try {
143
+ const result = await service.runAudit({
144
+ workspace,
145
+ projectKey: projectResolution.projectKey,
146
+ dryRun: parsed.dryRun,
147
+ maxIterations: parsed.maxIterations,
148
+ maxTasksPerIteration: parsed.maxTasksPerIteration,
149
+ minCoverageRatio: parsed.minCoverageRatio,
150
+ });
151
+ const warnings = [...projectResolution.warnings, ...result.warnings];
152
+ if (parsed.json) {
153
+ // eslint-disable-next-line no-console
154
+ console.log(JSON.stringify({
155
+ projectKey: result.projectKey,
156
+ jobId: result.jobId,
157
+ commandRunId: result.commandRunId,
158
+ sourceCommand: result.sourceCommand ?? null,
159
+ satisfied: result.satisfied,
160
+ dryRun: result.dryRun,
161
+ maxIterations: result.maxIterations,
162
+ minCoverageRatio: result.minCoverageRatio,
163
+ totalTasksAdded: result.totalTasksAdded,
164
+ totalTasksUpdated: result.totalTasksUpdated,
165
+ finalCoverageRatio: result.finalCoverageRatio,
166
+ remainingSectionHeadings: result.remainingSectionHeadings,
167
+ remainingFolderEntries: result.remainingFolderEntries,
168
+ remainingGaps: result.remainingGaps,
169
+ iterations: result.iterations,
170
+ reportPath: result.reportPath,
171
+ reportHistoryPath: result.reportHistoryPath ?? null,
172
+ warnings,
173
+ }, null, 2));
174
+ return;
175
+ }
176
+ if (!parsed.quiet) {
177
+ const lines = [
178
+ `task-sufficiency-audit project=${result.projectKey}`,
179
+ `Job: ${result.jobId}, Command Run: ${result.commandRunId}`,
180
+ `Satisfied: ${result.satisfied ? "yes" : "no"}`,
181
+ `Dry run: ${result.dryRun ? "yes" : "no"}`,
182
+ `Coverage: ${result.finalCoverageRatio} (target ${result.minCoverageRatio})`,
183
+ `Tasks added: ${result.totalTasksAdded}`,
184
+ `Tasks updated: ${result.totalTasksUpdated}`,
185
+ `Remaining section gaps: ${result.remainingSectionHeadings.length}`,
186
+ `Remaining folder gaps: ${result.remainingFolderEntries.length}`,
187
+ `Remaining total gaps: ${result.remainingGaps.total}`,
188
+ `Report: ${result.reportPath}`,
189
+ result.reportHistoryPath ? `History snapshot: ${result.reportHistoryPath}` : undefined,
190
+ ].filter(Boolean);
191
+ // eslint-disable-next-line no-console
192
+ console.log(lines.join("\n"));
193
+ }
194
+ if (warnings.length > 0) {
195
+ // eslint-disable-next-line no-console
196
+ console.warn(warnings.map((warning) => `! ${warning}`).join("\n"));
197
+ }
198
+ }
199
+ catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ // eslint-disable-next-line no-console
202
+ console.error(`task-sufficiency-audit failed: ${message}`);
203
+ process.exitCode = 1;
204
+ }
205
+ finally {
206
+ await service.close();
207
+ }
208
+ }
209
+ }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./commands/backlog/OrderTasksCommand.js";
10
10
  export * from "./commands/estimate/EstimateCommands.js";
11
11
  export * from "./commands/telemetry/TelemetryCommands.js";
12
12
  export * from "./commands/planning/RefineTasksCommand.js";
13
+ export * from "./commands/planning/TaskSufficiencyAuditCommand.js";
13
14
  export * from "./commands/work/WorkOnTasksCommand.js";
14
15
  export * from "./commands/work/GatewayTrioCommand.js";
15
16
  export * from "./commands/review/CodeReviewCommand.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,oDAAoD,CAAC;AACnE,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC"}
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export * from "./commands/backlog/OrderTasksCommand.js";
10
10
  export * from "./commands/estimate/EstimateCommands.js";
11
11
  export * from "./commands/telemetry/TelemetryCommands.js";
12
12
  export * from "./commands/planning/RefineTasksCommand.js";
13
+ export * from "./commands/planning/TaskSufficiencyAuditCommand.js";
13
14
  export * from "./commands/work/WorkOnTasksCommand.js";
14
15
  export * from "./commands/work/GatewayTrioCommand.js";
15
16
  export * from "./commands/review/CodeReviewCommand.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcoda",
3
- "version": "0.1.23",
3
+ "version": "0.1.26",
4
4
  "description": "Local-first CLI for planning, documentation, and execution workflows with agent assistance.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,12 +45,12 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "yaml": "^2.4.2",
48
- "@mcoda/core": "0.1.23",
49
- "@mcoda/shared": "0.1.23"
48
+ "@mcoda/core": "0.1.26",
49
+ "@mcoda/shared": "0.1.26"
50
50
  },
51
51
  "devDependencies": {
52
- "@mcoda/db": "0.1.23",
53
- "@mcoda/integrations": "0.1.23"
52
+ "@mcoda/db": "0.1.26",
53
+ "@mcoda/integrations": "0.1.26"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc -p tsconfig.json",