@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.
- package/CHANGELOG.md +52 -0
- package/dist/.planu-build.json +1 -1
- package/dist/cli/commands/create.js +3 -16
- package/dist/cli/commands/dashboard.js +2 -16
- package/dist/cli/commands/init.js +2 -16
- package/dist/cli/commands/spec.js +5 -36
- package/dist/cli/commands/status.js +5 -28
- package/dist/cli/formatter.d.ts +7 -1
- package/dist/cli/formatter.js +49 -6
- package/dist/engine/cascade-hooks/hooks/implementing-kickoff.hook.js +38 -6
- package/dist/engine/dor-dod/index.d.ts +1 -1
- package/dist/engine/dor-dod/index.js +1 -1
- package/dist/engine/dor-dod/plan.d.ts +1 -0
- package/dist/engine/dor-dod/plan.js +2 -0
- package/dist/engine/evidence-gates/artifact-reader.js +1 -0
- package/dist/engine/evidence-gates/evidence-skeletons.js +1 -0
- package/dist/engine/execution-plan/plan-utils.d.ts +2 -6
- package/dist/engine/execution-plan/plan-utils.js +71 -37
- package/dist/engine/handoff-artifacts/implementation-review-reader.d.ts +4 -0
- package/dist/engine/handoff-artifacts/implementation-review-reader.js +72 -0
- package/dist/engine/rules-generator/index.js +4 -47
- package/dist/engine/skill-registry/installer.js +17 -28
- package/dist/engine/skills/skills-fetcher.js +0 -9
- package/dist/engine/skills-reconciler.js +1 -13
- package/dist/engine/validator/dor-dod.d.ts +2 -2
- package/dist/engine/validator/dor-dod.js +5 -4
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.d.ts +2 -4
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +24 -24
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +1 -1
- package/dist/tools/challenge-spec/scenarios-failure.js +1 -2
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +3 -0
- package/dist/tools/challenge-spec/scenarios-utils.js +17 -1
- package/dist/tools/challenge-spec-helpers.d.ts +1 -1
- package/dist/tools/challenge-spec-helpers.js +5 -2
- package/dist/tools/challenge-spec.js +15 -3
- package/dist/tools/create-spec.js +18 -0
- package/dist/tools/generate-execution-plan.js +160 -54
- package/dist/tools/suggest-tooling/skills-catalog.js +0 -9
- package/dist/tools/update-status/dod-gates.js +46 -64
- package/dist/tools/update-status/side-effects.d.ts +4 -0
- package/dist/tools/update-status/side-effects.js +45 -4
- package/dist/tools/validate.js +8 -4
- package/dist/transports/transport-factory.js +13 -0
- package/dist/types/ai-tool-rules.d.ts +0 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/evidence-gates.d.ts +2 -0
- package/dist/types/execution.d.ts +5 -0
- package/dist/types/handoff-artifacts.d.ts +19 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/planu-plugin.json +1 -1
- package/scripts/lib/portable-paths.mjs +1 -0
- package/dist/engine/execution-plan/phases-b.d.ts +0 -4
- package/dist/engine/execution-plan/phases-b.js +0 -88
- package/dist/engine/execution-plan/phases.d.ts +0 -7
- 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
|
package/dist/.planu-build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"commit":"
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
7
|
+
import { writeCliResult } from '../formatter.js';
|
|
8
8
|
import { detectProjectId } from '../project-detector.js';
|
|
9
|
-
import { red
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/cli/formatter.d.ts
CHANGED
|
@@ -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
|
|
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
|
*/
|
package/dist/cli/formatter.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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()}`,
|
|
@@ -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,
|
|
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
|