@planu/cli 5.3.62 → 5.3.64

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 (56) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/.planu-build.json +1 -1
  3. package/dist/cli/commands/create.js +3 -16
  4. package/dist/cli/commands/dashboard.js +2 -16
  5. package/dist/cli/commands/init.js +2 -16
  6. package/dist/cli/commands/spec.js +5 -36
  7. package/dist/cli/commands/status.js +5 -28
  8. package/dist/cli/formatter.d.ts +7 -1
  9. package/dist/cli/formatter.js +49 -6
  10. package/dist/engine/cascade-hooks/hooks/implementing-kickoff.hook.js +38 -6
  11. package/dist/engine/dor-dod/index.d.ts +1 -1
  12. package/dist/engine/dor-dod/index.js +1 -1
  13. package/dist/engine/dor-dod/plan.d.ts +1 -0
  14. package/dist/engine/dor-dod/plan.js +2 -0
  15. package/dist/engine/evidence-gates/artifact-reader.js +1 -0
  16. package/dist/engine/evidence-gates/evidence-skeletons.js +1 -0
  17. package/dist/engine/execution-plan/plan-utils.d.ts +2 -6
  18. package/dist/engine/execution-plan/plan-utils.js +71 -37
  19. package/dist/engine/handoff-artifacts/implementation-review-reader.d.ts +4 -0
  20. package/dist/engine/handoff-artifacts/implementation-review-reader.js +72 -0
  21. package/dist/engine/rules-generator/index.js +4 -47
  22. package/dist/engine/skill-registry/installer.js +17 -28
  23. package/dist/engine/skills/skills-fetcher.js +0 -9
  24. package/dist/engine/skills-reconciler.js +1 -13
  25. package/dist/engine/validator/dor-dod.d.ts +2 -2
  26. package/dist/engine/validator/dor-dod.js +5 -4
  27. package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.d.ts +2 -4
  28. package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +24 -24
  29. package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +1 -1
  30. package/dist/tools/challenge-spec/scenarios-failure.js +1 -2
  31. package/dist/tools/challenge-spec/scenarios-utils.d.ts +3 -0
  32. package/dist/tools/challenge-spec/scenarios-utils.js +17 -1
  33. package/dist/tools/challenge-spec-helpers.d.ts +1 -1
  34. package/dist/tools/challenge-spec-helpers.js +5 -2
  35. package/dist/tools/challenge-spec.js +15 -3
  36. package/dist/tools/create-spec.js +18 -0
  37. package/dist/tools/generate-execution-plan.js +160 -54
  38. package/dist/tools/suggest-tooling/skills-catalog.js +0 -9
  39. package/dist/tools/update-status/dod-gates.js +46 -64
  40. package/dist/tools/update-status/side-effects.d.ts +4 -0
  41. package/dist/tools/update-status/side-effects.js +45 -4
  42. package/dist/tools/validate.js +8 -4
  43. package/dist/transports/transport-factory.js +13 -0
  44. package/dist/types/ai-tool-rules.d.ts +0 -1
  45. package/dist/types/cli.d.ts +3 -0
  46. package/dist/types/evidence-gates.d.ts +2 -0
  47. package/dist/types/execution.d.ts +5 -0
  48. package/dist/types/handoff-artifacts.d.ts +19 -0
  49. package/dist/types/index.d.ts +1 -0
  50. package/package.json +1 -1
  51. package/planu-plugin.json +1 -1
  52. package/scripts/lib/portable-paths.mjs +1 -0
  53. package/dist/engine/execution-plan/phases-b.d.ts +0 -4
  54. package/dist/engine/execution-plan/phases-b.js +0 -88
  55. package/dist/engine/execution-plan/phases.d.ts +0 -7
  56. package/dist/engine/execution-plan/phases.js +0 -216
package/CHANGELOG.md CHANGED
@@ -1,3 +1,55 @@
1
+ ## [5.3.64] - 2026-08-27
2
+
3
+ ### Bug Fixes
4
+ - fix(SPEC-1264): merge shared implementation-review adapter with arbitrated fixes
5
+ - fix(SPEC-1257): merge post-transition observability drain with arbitrated fixes
6
+ - fix(SPEC-1264): apply arbitrated dual-review fixes
7
+ - fix(SPEC-1294): merge grounded execution plans with arbitrated dual-review fixes
8
+ - fix(SPEC-1294): apply arbitrated dual-review fixes
9
+ - fix(SPEC-1264): unify implementation-review verification behind one shared adapter
10
+ - fix(SPEC-1257): isolate postcommit and cascade launches from request scope, drain on stdio shutdown
11
+ - fix(SPEC-1329): merge grounded challenge capability gates with review fix round
12
+ - fix(SPEC-1329): widen outbound-call detection and drop narrative comments
13
+ - fix(SPEC-1294): ground execution plans in approved contract and preserve foreign plans
14
+ - fix(SPEC-1329): gate challenge templates on positive capability evidence
15
+ - fix(SPEC-1301): route every CLI json-mode result through one shared writer
16
+ - fix(SPEC-1652): reject title-less create_spec input before path redaction
17
+ - fix(SPEC-1301): emit one structured JSON document in CLI global json mode
18
+
19
+ ### Chores
20
+ - chore(SPEC-1257): record implementing transition
21
+ - chore(SPEC-1329): record implementing transition
22
+ - chore(planu): record SPEC-1652 and SPEC-1301 done transitions
23
+ - chore(SPEC-1294): record implementing transition
24
+ - chore(SPEC-1294): record approval transition
25
+ - chore(planu): move SPEC-1652 and SPEC-1301 to implementing
26
+ - chore(planu): approve SPEC-1652 and discard SPEC-1653 with codex evidence
27
+ - chore(planu): rescope SPEC-1652 after dual review and file SPEC-1654 dogfood bug
28
+
29
+
30
+ ## [5.3.63] - 2026-08-27
31
+
32
+ ### Bug Fixes
33
+ - fix(SPEC-1644): stop emitting the default architecture rule and every line-cap instruction
34
+ - fix(SPEC-1643): remove the empty typescript-patterns builtin skill
35
+ - fix(SPEC-1371): compute branch cleanup against main with literal protected matching
36
+ - fix(SPEC-1646): discriminate contradiction findings by offending criterion
37
+ - fix(SPEC-1646): discriminate contradiction findings by offending criterion
38
+ - fix(SPEC-1645): widen the load-bearing separator class per dual review
39
+ - fix(SPEC-1645): stop treating load-bearing as a scale signal
40
+
41
+ ### Chores
42
+ - chore(planu): file goal stop-hook loop dogfood bug
43
+ - chore(SPEC-1644): record done transition
44
+ - chore(planu): file SPEC-1651 and SPEC-1652 dogfood bugs
45
+ - chore(SPEC-1644): record approved status
46
+ - chore(SPEC-1643): mark acceptance criteria done
47
+ - chore(SPEC-1643): lifecycle transition to approved
48
+ - chore(planu): record SPEC-1371 approval and SPEC-1649 filing
49
+ - chore(SPEC-1646): record approval transition
50
+ - chore(SPEC-1645): record done transition
51
+
52
+
1
53
  ## [5.3.62] - 2026-08-26
2
54
 
3
55
  ### Bug Fixes
@@ -1 +1 @@
1
- {"schemaVersion":1,"commit":"653317c22ebdf21ad6b7f82f5d86dd042b05aaab"}
1
+ {"schemaVersion":1,"commit":"eb49b07b8d2c6ae54132af679fc3319180bab2b8"}
@@ -2,8 +2,8 @@
2
2
  import { parseArgs } from 'node:util';
3
3
  import { resolve } from 'node:path';
4
4
  import { handleCreateSpec } from '../../tools/create-spec.js';
5
- import { formatToolResult } from '../formatter.js';
6
- import { green, red } from '../colors.js';
5
+ import { writeCliResult } from '../formatter.js';
6
+ import { red } from '../colors.js';
7
7
  export const createCommand = {
8
8
  name: 'create',
9
9
  description: 'Create a new spec',
@@ -39,20 +39,7 @@ export const createCommand = {
39
39
  target: values.target,
40
40
  feature: values.feature ?? undefined,
41
41
  });
42
- if (result.isError) {
43
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
44
- process.exitCode = 1;
45
- return;
46
- }
47
- const output = formatToolResult(result, flags);
48
- if (flags?.quiet) {
49
- if (output) {
50
- process.stdout.write(output + '\n');
51
- }
52
- }
53
- else {
54
- process.stdout.write(`${green('Spec created!')} ${output}\n`);
55
- }
42
+ writeCliResult(result, flags, 'Spec created!');
56
43
  },
57
44
  };
58
45
  //# sourceMappingURL=create.js.map
@@ -1,8 +1,7 @@
1
1
  // cli/commands/dashboard.ts — planu dashboard [--port 5050] (SPEC-124)
2
2
  import { parseArgs } from 'node:util';
3
3
  import { handleStartDashboard } from '../../tools/dashboard.js';
4
- import { formatToolResult } from '../formatter.js';
5
- import { green, red } from '../colors.js';
4
+ import { writeCliResult } from '../formatter.js';
6
5
  export const dashboardCommand = {
7
6
  name: 'dashboard',
8
7
  description: 'Start the Planu dashboard',
@@ -18,20 +17,7 @@ export const dashboardCommand = {
18
17
  });
19
18
  const port = values.port ? Number(values.port) : undefined;
20
19
  const result = await handleStartDashboard({ port });
21
- if (result.isError) {
22
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
23
- process.exitCode = 1;
24
- return;
25
- }
26
- const output = formatToolResult(result, flags);
27
- if (flags?.quiet) {
28
- if (output) {
29
- process.stdout.write(output + '\n');
30
- }
31
- }
32
- else {
33
- process.stdout.write(`${green('Dashboard started!')} ${output}\n`);
34
- }
20
+ writeCliResult(result, flags, 'Dashboard started!');
35
21
  },
36
22
  };
37
23
  //# sourceMappingURL=dashboard.js.map
@@ -2,8 +2,7 @@
2
2
  import { parseArgs } from 'node:util';
3
3
  import { resolve } from 'node:path';
4
4
  import { handleInitProject } from '../../tools/init-project.js';
5
- import { formatToolResult } from '../formatter.js';
6
- import { green, red } from '../colors.js';
5
+ import { writeCliResult } from '../formatter.js';
7
6
  export const initCommand = {
8
7
  name: 'init',
9
8
  description: 'Initialize a project for Planu',
@@ -32,20 +31,7 @@ export const initCommand = {
32
31
  experienceLevel: experienceLevel,
33
32
  userProfile: userProfile,
34
33
  });
35
- if (result.isError) {
36
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
37
- process.exitCode = 1;
38
- return;
39
- }
40
- const output = formatToolResult(result, flags);
41
- if (flags?.quiet) {
42
- if (output) {
43
- process.stdout.write(output + '\n');
44
- }
45
- }
46
- else {
47
- process.stdout.write(`${green('Project initialized!')} ${output}\n`);
48
- }
34
+ writeCliResult(result, flags, 'Project initialized!');
49
35
  },
50
36
  };
51
37
  //# sourceMappingURL=init.js.map
@@ -13,9 +13,9 @@ import { handleListSpecs } from '../../tools/list-specs.js';
13
13
  import { handleUpdateStatus } from '../../tools/update-status.js';
14
14
  import { handleUpdateStatusBatch } from '../../tools/update-status/batch.js';
15
15
  import { handleValidate } from '../../tools/validate.js';
16
- import { formatToolResult } from '../formatter.js';
16
+ import { formatToolResult, writeCliResult } from '../formatter.js';
17
17
  import { detectProjectId } from '../project-detector.js';
18
- import { bold, cyan, green, red, dim } from '../colors.js';
18
+ import { bold, cyan, red, dim } from '../colors.js';
19
19
  import { SINGLE_STATUS_PARSE_OPTIONS, buildSingleStatusInput } from './status.js';
20
20
  // ---------------------------------------------------------------------------
21
21
  // Helpers
@@ -107,20 +107,7 @@ async function runCreate(args, flags) {
107
107
  target: values.target,
108
108
  feature: values.feature ?? undefined,
109
109
  });
110
- if (result.isError) {
111
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
112
- process.exitCode = 1;
113
- return;
114
- }
115
- const output = formatToolResult(result, flags);
116
- if (flags?.quiet) {
117
- if (output) {
118
- process.stdout.write(output + '\n');
119
- }
120
- }
121
- else {
122
- process.stdout.write(`${green('Spec created!')} ${output}\n`);
123
- }
110
+ writeCliResult(result, flags, 'Spec created!');
124
111
  }
125
112
  // ---------------------------------------------------------------------------
126
113
  // Subcommand: spec list
@@ -246,32 +233,14 @@ async function runStatus(args, flags) {
246
233
  status: status,
247
234
  reviewNotes: values.notes ?? undefined,
248
235
  });
249
- if (result.isError) {
250
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
251
- process.exitCode = 1;
252
- return;
253
- }
254
- process.stdout.write(formatToolResult(result, flags) + '\n');
236
+ writeCliResult(result, flags);
255
237
  return;
256
238
  }
257
239
  const result = await handleUpdateStatus({
258
240
  ...buildSingleStatusInput(values, specId, projectId, status),
259
241
  implementationReviewDigest: values['implementation-review-digest'] ?? undefined,
260
242
  });
261
- if (result.isError) {
262
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
263
- process.exitCode = 1;
264
- return;
265
- }
266
- const output = formatToolResult(result, flags);
267
- if (flags?.quiet) {
268
- if (output) {
269
- process.stdout.write(output + '\n');
270
- }
271
- }
272
- else {
273
- process.stdout.write(`${green('Status updated!')} ${output}\n`);
274
- }
243
+ writeCliResult(result, flags, 'Status updated!');
275
244
  }
276
245
  // ---------------------------------------------------------------------------
277
246
  // Exported command
@@ -4,9 +4,9 @@ import { resolve } from 'node:path';
4
4
  import { handleUpdateStatus } from '../../tools/update-status.js';
5
5
  import { handleUpdateStatusBatch } from '../../tools/update-status/batch.js';
6
6
  import { handlePlanStatus } from '../../tools/status-handler.js';
7
- import { formatToolResult } from '../formatter.js';
7
+ import { writeCliResult } from '../formatter.js';
8
8
  import { detectProjectId } from '../project-detector.js';
9
- import { red, green } from '../colors.js';
9
+ import { red } from '../colors.js';
10
10
  export const statusCommand = {
11
11
  name: 'status',
12
12
  description: 'Show project status or update the status of a spec',
@@ -92,15 +92,7 @@ async function runProjectStatus(values, flags) {
92
92
  projectPath: values['project-path'] ?? process.cwd(),
93
93
  projectId: values['project-id'],
94
94
  });
95
- if (result.isError) {
96
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
97
- process.exitCode = 1;
98
- return;
99
- }
100
- const output = formatToolResult(result, flags);
101
- if (output) {
102
- process.stdout.write(`${output}\n`);
103
- }
95
+ writeCliResult(result, flags);
104
96
  }
105
97
  function writeUsageError(message) {
106
98
  process.stderr.write(`${red('Error:')} ${message}\nUsage: ${statusCommand.usage}\n`);
@@ -112,25 +104,10 @@ async function runBatchStatus(specIds, values, status, flags) {
112
104
  status: status,
113
105
  reviewNotes: values.notes,
114
106
  });
115
- writeToolResult(result, flags, false);
107
+ writeCliResult(result, flags);
116
108
  }
117
109
  async function runSingleStatus(specId, values, status, flags) {
118
110
  const result = await handleUpdateStatus(buildSingleStatusInput(values, specId, values['project-id'] ?? detectProjectId(), status));
119
- writeToolResult(result, flags, true);
120
- }
121
- function writeToolResult(result, flags, includeSuccessPrefix) {
122
- if (result.isError) {
123
- process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
124
- process.exitCode = 1;
125
- return;
126
- }
127
- const output = formatToolResult(result, flags);
128
- if (flags?.quiet) {
129
- if (output) {
130
- process.stdout.write(`${output}\n`);
131
- }
132
- return;
133
- }
134
- process.stdout.write(includeSuccessPrefix ? `${green('Status updated!')} ${output}\n` : `${output}\n`);
111
+ writeCliResult(result, flags, 'Status updated!');
135
112
  }
136
113
  //# sourceMappingURL=status.js.map
@@ -3,11 +3,17 @@ import type { GlobalFlags } from '../types/cli.js';
3
3
  /**
4
4
  * Format a ToolResult for terminal display.
5
5
  * Parses JSON text content when possible, otherwise returns raw text.
6
- * Respects global flags: --json for raw JSON, --quiet returns empty string.
6
+ * Respects global flags: --json for one structured JSON document, --quiet returns empty string.
7
7
  */
8
8
  export declare function formatToolResult(result: ToolResult, options?: {
9
9
  json?: boolean;
10
10
  } | GlobalFlags): string;
11
+ /**
12
+ * Write a ToolResult to stdout/stderr, owning quiet/json precedence, error
13
+ * routing, and the human success prefix. In json mode stdout carries exactly
14
+ * one JSON document — no prefix, no ANSI.
15
+ */
16
+ export declare function writeCliResult(result: ToolResult, flags: GlobalFlags | undefined, successPrefix?: string): void;
11
17
  /**
12
18
  * Render an ASCII table with column alignment.
13
19
  */
@@ -1,20 +1,19 @@
1
1
  // cli/formatter.ts — Convert MCP ToolResult to terminal output (SPEC-124)
2
+ import { green, red } from './colors.js';
2
3
  /**
3
4
  * Format a ToolResult for terminal display.
4
5
  * Parses JSON text content when possible, otherwise returns raw text.
5
- * Respects global flags: --json for raw JSON, --quiet returns empty string.
6
+ * Respects global flags: --json for one structured JSON document, --quiet returns empty string.
6
7
  */
7
8
  export function formatToolResult(result, options) {
8
9
  if (options && 'quiet' in options && options.quiet) {
9
10
  return '';
10
11
  }
11
- const useJson = options?.json;
12
+ if (options?.json) {
13
+ return JSON.stringify(resolveJsonValue(result));
14
+ }
12
15
  const parts = [];
13
16
  for (const item of result.content) {
14
- if (useJson) {
15
- parts.push(item.text);
16
- continue;
17
- }
18
17
  // Try to parse as JSON for prettier output
19
18
  try {
20
19
  const parsed = JSON.parse(item.text);
@@ -31,6 +30,50 @@ export function formatToolResult(result, options) {
31
30
  }
32
31
  return parts.join('\n');
33
32
  }
33
+ function resolveJsonValue(result) {
34
+ if (result.structuredContent !== undefined) {
35
+ return result.structuredContent;
36
+ }
37
+ if (result.content.length === 1) {
38
+ const parsed = tryParseJson(result.content[0].text);
39
+ if (parsed !== undefined) {
40
+ return parsed;
41
+ }
42
+ }
43
+ const envelope = {
44
+ content: result.content.map((item) => tryParseJson(item.text) ?? item.text),
45
+ };
46
+ return envelope;
47
+ }
48
+ function tryParseJson(text) {
49
+ try {
50
+ return JSON.parse(text);
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /**
57
+ * Write a ToolResult to stdout/stderr, owning quiet/json precedence, error
58
+ * routing, and the human success prefix. In json mode stdout carries exactly
59
+ * one JSON document — no prefix, no ANSI.
60
+ */
61
+ export function writeCliResult(result, flags, successPrefix) {
62
+ if (result.isError) {
63
+ const message = formatToolResult(result, flags);
64
+ process.stderr.write(`${flags?.json ? message : red(message)}\n`);
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ const output = formatToolResult(result, flags);
69
+ if (flags?.quiet || flags?.json) {
70
+ if (output) {
71
+ process.stdout.write(`${output}\n`);
72
+ }
73
+ return;
74
+ }
75
+ process.stdout.write(successPrefix ? `${green(successPrefix)} ${output}\n` : `${output}\n`);
76
+ }
34
77
  function formatObject(obj, indent = 0) {
35
78
  const prefix = ' '.repeat(indent);
36
79
  const lines = [];
@@ -1,14 +1,46 @@
1
+ import { appendAutopilotLogEntry } from '../../../storage/autopilot-log-store.js';
2
+ import { hashProjectPath } from '../../../storage/base-store.js';
3
+ function logExecutionPlanOutcome(projectPath, entry) {
4
+ try {
5
+ void appendAutopilotLogEntry(hashProjectPath(projectPath), entry);
6
+ }
7
+ catch {
8
+ return;
9
+ }
10
+ }
11
+ async function fireExecutionPlan(projectPath, projectId, specId) {
12
+ const start = Date.now();
13
+ try {
14
+ const { handleGenerateExecutionPlan } = await import('../../../tools/generate-execution-plan.js');
15
+ const result = await handleGenerateExecutionPlan({ specId, projectId });
16
+ logExecutionPlanOutcome(projectPath, {
17
+ specId,
18
+ hookName: 'implementing-kickoff-execution-plan',
19
+ result: result.isError ? 'fail' : 'pass',
20
+ ...(result.isError
21
+ ? { error: result.content[0]?.text ?? 'execution plan generation failed' }
22
+ : {}),
23
+ timestamp: new Date().toISOString(),
24
+ durationMs: Date.now() - start,
25
+ });
26
+ }
27
+ catch (err) {
28
+ logExecutionPlanOutcome(projectPath, {
29
+ specId,
30
+ hookName: 'implementing-kickoff-execution-plan',
31
+ result: 'fail',
32
+ error: err instanceof Error ? err.message : String(err),
33
+ timestamp: new Date().toISOString(),
34
+ durationMs: Date.now() - start,
35
+ });
36
+ }
37
+ }
1
38
  async function handler(ctx) {
2
39
  const { projectPath, projectId, specId } = ctx;
3
40
  if (!projectPath) {
4
41
  return;
5
42
  }
6
- // Sub-task 1: generate execution plan (fire-and-forget)
7
- await import('../../../tools/generate-execution-plan.js')
8
- .then(({ handleGenerateExecutionPlan }) => handleGenerateExecutionPlan({ specId, projectId }))
9
- .catch(() => {
10
- /* best-effort */
11
- });
43
+ await fireExecutionPlan(projectPath, projectId, specId);
12
44
  // Sub-task 2: fire TDD scaffold via complete-loop (fire-and-forget)
13
45
  await import('../../../engine/autopilot/complete-loop.js')
14
46
  .then(({ fireAutoScaffoldTdd }) => {
@@ -1,4 +1,4 @@
1
1
  export { generateDoR, validateDoR, addCustomDoRItem, enrichDoRWithLearning } from './dor.js';
2
2
  export { generateDoD, validateDoD, addCustomDoDItem } from './dod.js';
3
- export { generatePlanMarkdown } from './plan.js';
3
+ export { generatePlanMarkdown, EXECUTION_PLAN_MARKER } from './plan.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  // dor-dod/index.ts — Barrel re-export for DoR/DoD engine
2
2
  export { generateDoR, validateDoR, addCustomDoRItem, enrichDoRWithLearning } from './dor.js';
3
3
  export { generateDoD, validateDoD, addCustomDoDItem } from './dod.js';
4
- export { generatePlanMarkdown } from './plan.js';
4
+ export { generatePlanMarkdown, EXECUTION_PLAN_MARKER } from './plan.js';
5
5
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,5 @@
1
1
  import type { Spec, ExecutionPlan } from '../../types/index.js';
2
+ export declare const EXECUTION_PLAN_MARKER = "<!-- planu:execution-plan -->";
2
3
  /**
3
4
  * Generate a PLAN.md markdown string from an ExecutionPlan.
4
5
  * Includes RED/GREEN/VERIFY cycles, [P] markers for parallelizable steps,
@@ -1,3 +1,4 @@
1
+ export const EXECUTION_PLAN_MARKER = '<!-- planu:execution-plan -->';
1
2
  /**
2
3
  * Generate a PLAN.md markdown string from an ExecutionPlan.
3
4
  * Includes RED/GREEN/VERIFY cycles, [P] markers for parallelizable steps,
@@ -7,6 +8,7 @@ export function generatePlanMarkdown(plan, spec) {
7
8
  const totalMinutes = plan.phases.reduce((sum, ph) => sum + ph.steps.reduce((s, st) => s + st.estimatedMinutes, 0), 0);
8
9
  const estimatedHours = Math.round((totalMinutes / 60) * 10) / 10;
9
10
  const lines = [
11
+ EXECUTION_PLAN_MARKER,
10
12
  `# PLAN.md — ${spec.title}`,
11
13
  '',
12
14
  `> **Spec**: ${spec.id} | **Status**: ${spec.status} | **Generated**: ${new Date().toISOString()}`,
@@ -64,6 +64,7 @@ const TaskPlanSchema = z
64
64
  .object({
65
65
  version: z.literal(1),
66
66
  provenance: ProvenanceSchema.optional(),
67
+ generatedBy: z.literal('planu-evidence-skeleton').optional(),
67
68
  tasks: z
68
69
  .array(z
69
70
  .object({
@@ -54,6 +54,7 @@ export function generateTaskPlanSkeleton(args) {
54
54
  const criteria = args.criteria.length > 0 ? args.criteria : ['Implementation satisfies the approved spec.'];
55
55
  return {
56
56
  version: 1,
57
+ generatedBy: 'planu-evidence-skeleton',
57
58
  tasks: criteria.map((criterion, index) => ({
58
59
  id: `AC-${String(index + 1)}`,
59
60
  title: criterion.length > 500 ? `${criterion.slice(0, 497)}...` : criterion,
@@ -1,10 +1,6 @@
1
- import type { ExecutionPhase, Spec, ProjectKnowledge } from '../../types/index.js';
1
+ import type { ExecutionPhase, ExecutionPlanGraphViolation, Spec } from '../../types/index.js';
2
+ export declare function validateExecutionPlanGraph(phases: ExecutionPhase[]): ExecutionPlanGraphViolation[];
2
3
  export declare function determineCriticalPath(phases: ExecutionPhase[]): string[];
3
4
  export declare function findParallelizable(phases: ExecutionPhase[]): string[][];
4
- export declare function inferDataLayerFiles(spec: Spec, knowledge: ProjectKnowledge): string[];
5
- export declare function inferServiceFiles(spec: Spec, knowledge: ProjectKnowledge): string[];
6
- export declare function inferApiFiles(spec: Spec, knowledge: ProjectKnowledge): string[];
7
- export declare function inferPageFiles(spec: Spec, knowledge: ProjectKnowledge): string[];
8
- export declare function inferComponentFiles(spec: Spec, knowledge: ProjectKnowledge): string[];
9
5
  export declare function readSpecContent(spec: Spec): Promise<string>;
10
6
  //# sourceMappingURL=plan-utils.d.ts.map
@@ -1,8 +1,76 @@
1
- import { technologyValue } from '../technology-registry.js';
2
- // engine/execution-plan/plan-utils.ts — Utilities for execution plan generation
3
- // Critical path analysis, parallelization detection, and file inference helpers.
4
1
  import { readFile } from 'node:fs/promises';
5
2
  import { readSpecTechnicalSection } from '../spec-format/read-technical-section.js';
3
+ function findStepDependencyCycle(steps) {
4
+ const dependsOnByOrder = new Map(steps.map((step) => [step.order, step.dependsOn]));
5
+ const visiting = new Set();
6
+ const visited = new Set();
7
+ const stack = [];
8
+ function visit(order) {
9
+ if (visited.has(order)) {
10
+ return null;
11
+ }
12
+ if (visiting.has(order)) {
13
+ const cycleStart = stack.indexOf(order);
14
+ return [...stack.slice(cycleStart), order];
15
+ }
16
+ visiting.add(order);
17
+ stack.push(order);
18
+ for (const dep of dependsOnByOrder.get(order) ?? []) {
19
+ const cycle = visit(dep);
20
+ if (cycle) {
21
+ return cycle;
22
+ }
23
+ }
24
+ stack.pop();
25
+ visiting.delete(order);
26
+ visited.add(order);
27
+ return null;
28
+ }
29
+ for (const order of dependsOnByOrder.keys()) {
30
+ const cycle = visit(order);
31
+ if (cycle) {
32
+ return cycle;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ export function validateExecutionPlanGraph(phases) {
38
+ const steps = phases.flatMap((phase) => phase.steps);
39
+ const orders = new Set(steps.map((step) => step.order));
40
+ const violations = [];
41
+ for (const step of steps) {
42
+ for (const dep of step.dependsOn) {
43
+ if (!Number.isInteger(dep) || dep < 1) {
44
+ violations.push({
45
+ code: 'INVALID_STEP_DEPENDENCY',
46
+ message: `Step ${step.order} declares a non-integer or non-positive dependency (${dep}).`,
47
+ });
48
+ }
49
+ else if (dep === step.order) {
50
+ violations.push({
51
+ code: 'SELF_DEPENDENT_STEP',
52
+ message: `Step ${step.order} depends on itself.`,
53
+ });
54
+ }
55
+ else if (!orders.has(dep)) {
56
+ violations.push({
57
+ code: 'INVALID_STEP_DEPENDENCY',
58
+ message: `Step ${step.order} depends on non-existent step ${dep}.`,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ if (violations.length === 0) {
64
+ const cycle = findStepDependencyCycle(steps);
65
+ if (cycle) {
66
+ violations.push({
67
+ code: 'CYCLIC_STEP_DEPENDENCIES',
68
+ message: `Cyclic step dependency: ${cycle.join(' -> ')}.`,
69
+ });
70
+ }
71
+ }
72
+ return violations;
73
+ }
6
74
  // --- Critical path & parallelization ---
7
75
  export function determineCriticalPath(phases) {
8
76
  const path = [];
@@ -35,40 +103,6 @@ export function findParallelizable(phases) {
35
103
  }
36
104
  return groups;
37
105
  }
38
- // --- File inference helpers (SRP: each infers one type of file) ---
39
- export function inferDataLayerFiles(spec, knowledge) {
40
- const base = knowledge.architecture.layers.find((l) => l.name === 'infrastructure')?.directories[0] ?? 'src';
41
- return [`${base}/${spec.slug}-repository.ts`];
42
- }
43
- export function inferServiceFiles(spec, knowledge) {
44
- const base = knowledge.architecture.layers.find((l) => l.name === 'application')?.directories[0] ?? 'src';
45
- return [`${base}/${spec.slug}-service.ts`];
46
- }
47
- export function inferApiFiles(spec, knowledge) {
48
- const firstContract = knowledge.apiContracts[0];
49
- const apiType = firstContract ? firstContract.type : 'rest';
50
- const base = apiType === technologyValue('technology-trpc-7a75ed') ? 'src/server/routers' : 'src/routes';
51
- return [`${base}/${spec.slug}.ts`];
52
- }
53
- export function inferPageFiles(spec, knowledge) {
54
- const framework = (knowledge.framework ?? '').toLowerCase();
55
- if (framework.includes('next')) {
56
- return [`app/${spec.slug}/page.tsx`];
57
- }
58
- if (framework.includes(technologyValue('technology-nuxt-22c407'))) {
59
- return [`pages/${spec.slug}.vue`];
60
- }
61
- return [`src/pages/${spec.slug}.tsx`];
62
- }
63
- export function inferComponentFiles(spec, knowledge) {
64
- const ext = (knowledge.framework ?? '')
65
- .toLowerCase()
66
- .includes(technologyValue('technology-vue-564382'))
67
- ? technologyValue('technology-vue-564382')
68
- : 'tsx';
69
- return [`src/components/${spec.slug}/${spec.slug}.${ext}`];
70
- }
71
- // --- Spec content reader ---
72
106
  export async function readSpecContent(spec) {
73
107
  let content = `${spec.title} ${spec.type} ${spec.scope} ${spec.target} ${spec.tags.join(' ')}`;
74
108
  try {
@@ -0,0 +1,4 @@
1
+ import type { VerifiedImplementationReviewResult } from '../../types/index.js';
2
+ export declare function isLifecycleStubBody(specId: string, body: string): boolean;
3
+ export declare function readVerifiedImplementationReview(projectId: string, specId: string): Promise<VerifiedImplementationReviewResult>;
4
+ //# sourceMappingURL=implementation-review-reader.d.ts.map