@kbediako/codex-orchestrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +238 -0
- package/dist/bin/codex-orchestrator.js +507 -0
- package/dist/orchestrator/src/agents/builder.js +16 -0
- package/dist/orchestrator/src/agents/index.js +4 -0
- package/dist/orchestrator/src/agents/planner.js +17 -0
- package/dist/orchestrator/src/agents/reviewer.js +13 -0
- package/dist/orchestrator/src/agents/tester.js +13 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
- package/dist/orchestrator/src/cli/adapters/index.js +4 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
- package/dist/orchestrator/src/cli/doctor.js +48 -0
- package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
- package/dist/orchestrator/src/cli/exec/command.js +56 -0
- package/dist/orchestrator/src/cli/exec/context.js +108 -0
- package/dist/orchestrator/src/cli/exec/experience.js +77 -0
- package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
- package/dist/orchestrator/src/cli/exec/learning.js +62 -0
- package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
- package/dist/orchestrator/src/cli/exec/summary.js +109 -0
- package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
- package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
- package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
- package/dist/orchestrator/src/cli/exec/types.js +1 -0
- package/dist/orchestrator/src/cli/init.js +64 -0
- package/dist/orchestrator/src/cli/mcp.js +124 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
- package/dist/orchestrator/src/cli/orchestrator.js +554 -0
- package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
- package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
- package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
- package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
- package/dist/orchestrator/src/cli/run/environment.js +24 -0
- package/dist/orchestrator/src/cli/run/manifest.js +367 -0
- package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
- package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
- package/dist/orchestrator/src/cli/selfCheck.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
- package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
- package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
- package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
- package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
- package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
- package/dist/orchestrator/src/cli/types.js +1 -0
- package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
- package/dist/orchestrator/src/cli/ui/controller.js +26 -0
- package/dist/orchestrator/src/cli/ui/store.js +240 -0
- package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
- package/dist/orchestrator/src/cli/utils/fs.js +8 -0
- package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
- package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
- package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
- package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
- package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
- package/dist/orchestrator/src/cli/utils/runId.js +7 -0
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
- package/dist/orchestrator/src/cli/utils/strings.js +8 -0
- package/dist/orchestrator/src/cli/utils/time.js +6 -0
- package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
- package/dist/orchestrator/src/control-plane/index.js +3 -0
- package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
- package/dist/orchestrator/src/control-plane/types.js +1 -0
- package/dist/orchestrator/src/control-plane/validator.js +50 -0
- package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
- package/dist/orchestrator/src/events/EventBus.js +25 -0
- package/dist/orchestrator/src/learning/crystalizer.js +108 -0
- package/dist/orchestrator/src/learning/harvester.js +146 -0
- package/dist/orchestrator/src/learning/manifest.js +56 -0
- package/dist/orchestrator/src/learning/runner.js +177 -0
- package/dist/orchestrator/src/learning/validator.js +164 -0
- package/dist/orchestrator/src/logger.js +20 -0
- package/dist/orchestrator/src/manager.js +388 -0
- package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
- package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
- package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
- package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
- package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
- package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
- package/dist/orchestrator/src/persistence/lockFile.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
- package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
- package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
- package/dist/orchestrator/src/privacy/guard.js +111 -0
- package/dist/orchestrator/src/scheduler/index.js +1 -0
- package/dist/orchestrator/src/scheduler/plan.js +171 -0
- package/dist/orchestrator/src/scheduler/types.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
- package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
- package/dist/orchestrator/src/types.js +1 -0
- package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
- package/dist/orchestrator/src/utils/errorMessage.js +14 -0
- package/dist/orchestrator/src/utils/executionMode.js +69 -0
- package/dist/packages/control-plane-schemas/src/index.js +1 -0
- package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
- package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
- package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
- package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
- package/dist/packages/orchestrator/src/index.js +3 -0
- package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
- package/dist/packages/orchestrator/src/notifications/index.js +74 -0
- package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
- package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
- package/dist/packages/sdk-node/src/orchestrator.js +195 -0
- package/dist/packages/shared/config/designConfig.js +495 -0
- package/dist/packages/shared/config/env.js +37 -0
- package/dist/packages/shared/config/index.js +2 -0
- package/dist/packages/shared/design-artifacts/writer.js +221 -0
- package/dist/packages/shared/events/serializer.js +84 -0
- package/dist/packages/shared/events/types.js +1 -0
- package/dist/packages/shared/manifest/artifactUtils.js +36 -0
- package/dist/packages/shared/manifest/designArtifacts.js +665 -0
- package/dist/packages/shared/manifest/fileIO.js +29 -0
- package/dist/packages/shared/manifest/toolRuns.js +78 -0
- package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
- package/dist/packages/shared/manifest/types.js +5 -0
- package/dist/packages/shared/manifest/validator.js +73 -0
- package/dist/packages/shared/manifest/writer.js +2 -0
- package/dist/packages/shared/streams/stdio.js +112 -0
- package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
- package/dist/scripts/design/pipeline/componentize.js +74 -0
- package/dist/scripts/design/pipeline/context.js +34 -0
- package/dist/scripts/design/pipeline/extract.js +249 -0
- package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
- package/dist/scripts/design/pipeline/prepare.js +46 -0
- package/dist/scripts/design/pipeline/reference.js +94 -0
- package/dist/scripts/design/pipeline/state.js +206 -0
- package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
- package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
- package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
- package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
- package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
- package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
- package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
- package/dist/scripts/design/pipeline/visual-regression.js +137 -0
- package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
- package/package.json +97 -0
- package/schemas/manifest.json +1064 -0
- package/templates/README.md +12 -0
- package/templates/codex/mcp-client.json +8 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { appendCommandError, finalizeStatus, updateCommandStatus } from '../run/manifest.js';
|
|
2
|
+
import { isoTimestamp } from '../utils/time.js';
|
|
3
|
+
import { normalizeErrorMessage } from '../../utils/errorMessage.js';
|
|
4
|
+
export { normalizeErrorMessage };
|
|
5
|
+
export async function finalizeCommandLifecycle(context, stageResult) {
|
|
6
|
+
const commandIndex = 0;
|
|
7
|
+
let commandEntry = context.manifest.commands[commandIndex];
|
|
8
|
+
const summarySnapshot = stageResult.summary;
|
|
9
|
+
let resultExitCode = extractExecMetadataField(stageResult.toolRecord, 'exitCode') ?? null;
|
|
10
|
+
if (summarySnapshot && summarySnapshot.exitCode !== undefined) {
|
|
11
|
+
resultExitCode = summarySnapshot.exitCode;
|
|
12
|
+
}
|
|
13
|
+
let resultSignal = extractExecMetadataField(stageResult.toolRecord, 'signal') ?? null;
|
|
14
|
+
if (summarySnapshot && summarySnapshot.signal !== undefined) {
|
|
15
|
+
resultSignal = summarySnapshot.signal;
|
|
16
|
+
}
|
|
17
|
+
if (stageResult.commandError && resultExitCode === null) {
|
|
18
|
+
resultExitCode = 1;
|
|
19
|
+
}
|
|
20
|
+
if (stageResult.commandError && commandEntry) {
|
|
21
|
+
commandEntry = await finalizeFailedCommandEntry({
|
|
22
|
+
env: context.env,
|
|
23
|
+
paths: context.paths,
|
|
24
|
+
manifest: context.manifest,
|
|
25
|
+
commandEntry,
|
|
26
|
+
commandIndex,
|
|
27
|
+
error: stageResult.commandError,
|
|
28
|
+
exitCode: resultExitCode,
|
|
29
|
+
signal: resultSignal,
|
|
30
|
+
toolRecord: stageResult.toolRecord,
|
|
31
|
+
summarySnapshot,
|
|
32
|
+
shellCommand: context.shellCommand
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const runStatus = determineRunStatus(commandEntry);
|
|
36
|
+
context.manifest.summary = commandEntry?.summary ?? context.manifest.summary;
|
|
37
|
+
finalizeStatus(context.manifest, runStatus, commandEntry?.status === 'failed' ? 'exec-failed' : null);
|
|
38
|
+
return {
|
|
39
|
+
commandEntry,
|
|
40
|
+
runStatus,
|
|
41
|
+
exitCode: resultExitCode,
|
|
42
|
+
signal: resultSignal,
|
|
43
|
+
summarySnapshot,
|
|
44
|
+
toolRecord: stageResult.toolRecord,
|
|
45
|
+
commandError: stageResult.commandError
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function finalizeFailedCommandEntry(params) {
|
|
49
|
+
const { env, paths, manifest, commandEntry, commandIndex, error, exitCode, signal, toolRecord, summarySnapshot, shellCommand } = params;
|
|
50
|
+
const failureSummary = buildFailureSummary(error, exitCode, signal);
|
|
51
|
+
const updatedEntry = updateCommandStatus(manifest, commandIndex, {
|
|
52
|
+
status: 'failed',
|
|
53
|
+
completed_at: commandEntry.completed_at ?? isoTimestamp(),
|
|
54
|
+
exit_code: exitCode,
|
|
55
|
+
summary: failureSummary
|
|
56
|
+
});
|
|
57
|
+
const errorDetails = buildCommandErrorDetails({
|
|
58
|
+
error,
|
|
59
|
+
exitCode,
|
|
60
|
+
signal,
|
|
61
|
+
toolRecord,
|
|
62
|
+
summarySnapshot,
|
|
63
|
+
shellCommand
|
|
64
|
+
});
|
|
65
|
+
const errorFile = await appendCommandError(env, paths, manifest, updatedEntry, 'command-execution-error', errorDetails);
|
|
66
|
+
return updateCommandStatus(manifest, commandIndex, {
|
|
67
|
+
error_file: errorFile
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function buildFailureSummary(error, exitCode, signal) {
|
|
71
|
+
const message = normalizeErrorMessage(error);
|
|
72
|
+
if (signal) {
|
|
73
|
+
return `Command failed with signal ${signal}${message ? `: ${message}` : ''}`;
|
|
74
|
+
}
|
|
75
|
+
if (exitCode !== null && exitCode !== undefined) {
|
|
76
|
+
return `Command failed with code ${exitCode}${message ? `: ${message}` : ''}`;
|
|
77
|
+
}
|
|
78
|
+
return message ? `Command failed: ${message}` : 'Command failed.';
|
|
79
|
+
}
|
|
80
|
+
export function buildCommandErrorDetails(params) {
|
|
81
|
+
const { error, exitCode, signal, toolRecord, summarySnapshot, shellCommand } = params;
|
|
82
|
+
const execMetadata = readExecMetadata(toolRecord);
|
|
83
|
+
const details = {
|
|
84
|
+
message: normalizeErrorMessage(error),
|
|
85
|
+
command: shellCommand
|
|
86
|
+
};
|
|
87
|
+
if (exitCode !== null && exitCode !== undefined) {
|
|
88
|
+
details.exit_code = exitCode;
|
|
89
|
+
}
|
|
90
|
+
if (signal) {
|
|
91
|
+
details.signal = signal;
|
|
92
|
+
}
|
|
93
|
+
const sandboxState = summarySnapshot?.sandboxState ?? toolRecord?.sandboxState ?? execMetadata?.sandboxState;
|
|
94
|
+
if (sandboxState) {
|
|
95
|
+
details.sandbox_state = sandboxState;
|
|
96
|
+
}
|
|
97
|
+
const correlationId = execMetadata?.correlationId ?? null;
|
|
98
|
+
if (correlationId) {
|
|
99
|
+
details.correlation_id = correlationId;
|
|
100
|
+
}
|
|
101
|
+
if (execMetadata?.sessionId) {
|
|
102
|
+
details.session_id = execMetadata.sessionId;
|
|
103
|
+
}
|
|
104
|
+
if (typeof execMetadata?.persisted === 'boolean') {
|
|
105
|
+
details.persisted = execMetadata.persisted;
|
|
106
|
+
}
|
|
107
|
+
if (toolRecord?.id) {
|
|
108
|
+
details.tool_run_id = toolRecord.id;
|
|
109
|
+
}
|
|
110
|
+
if (toolRecord?.status) {
|
|
111
|
+
details.status = toolRecord.status;
|
|
112
|
+
}
|
|
113
|
+
if (typeof toolRecord?.attemptCount === 'number') {
|
|
114
|
+
details.attempts = toolRecord.attemptCount;
|
|
115
|
+
}
|
|
116
|
+
else if (typeof toolRecord?.retryCount === 'number') {
|
|
117
|
+
details.attempts = toolRecord.retryCount + 1;
|
|
118
|
+
}
|
|
119
|
+
return details;
|
|
120
|
+
}
|
|
121
|
+
export function determineRunStatus(entry) {
|
|
122
|
+
if (!entry) {
|
|
123
|
+
return 'failed';
|
|
124
|
+
}
|
|
125
|
+
return entry.status === 'succeeded' ? 'succeeded' : 'failed';
|
|
126
|
+
}
|
|
127
|
+
export function readExecMetadata(record) {
|
|
128
|
+
if (!record?.metadata || typeof record.metadata !== 'object') {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const candidate = record.metadata.exec;
|
|
132
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
export function extractExecMetadataField(record, field) {
|
|
138
|
+
const metadata = readExecMetadata(record);
|
|
139
|
+
return metadata ? metadata[field] : undefined;
|
|
140
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { persistManifest } from '../run/manifestPersister.js';
|
|
3
|
+
import { isoTimestamp } from '../utils/time.js';
|
|
4
|
+
import { logger } from '../../logger.js';
|
|
5
|
+
import { runLearningHarvester } from '../../learning/harvester.js';
|
|
6
|
+
import { synthesizeScenario } from '../../learning/runner.js';
|
|
7
|
+
import { runScenarioValidation } from '../../learning/validator.js';
|
|
8
|
+
export async function maybeTriggerLearning(runContext, runStatus) {
|
|
9
|
+
const enabled = process.env.LEARNING_PIPELINE_ENABLED === '1';
|
|
10
|
+
if (!enabled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const storageDir = process.env.LEARNING_SNAPSHOT_DIR;
|
|
14
|
+
if (runStatus !== 'succeeded') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const harvester = await runLearningHarvester(runContext.manifest, {
|
|
19
|
+
repoRoot: runContext.env.repoRoot,
|
|
20
|
+
runsRoot: runContext.env.runsRoot,
|
|
21
|
+
manifestPath: runContext.paths.manifestPath,
|
|
22
|
+
taskId: runContext.env.taskId,
|
|
23
|
+
runId: runContext.manifest.run_id,
|
|
24
|
+
diffPath: null,
|
|
25
|
+
promptPath: null,
|
|
26
|
+
executionHistoryPath: runContext.paths.logPath,
|
|
27
|
+
storageDir,
|
|
28
|
+
alertTargets: { slack: '#learning-alerts', pagerduty: 'learning-pipeline' }
|
|
29
|
+
});
|
|
30
|
+
const scenario = await synthesizeScenario({
|
|
31
|
+
manifest: harvester.manifest,
|
|
32
|
+
taskId: runContext.env.taskId,
|
|
33
|
+
runId: runContext.manifest.run_id,
|
|
34
|
+
runsRoot: runContext.env.runsRoot,
|
|
35
|
+
prompt: null,
|
|
36
|
+
diff: null,
|
|
37
|
+
executionHistory: [
|
|
38
|
+
{
|
|
39
|
+
command: runContext.shellCommand,
|
|
40
|
+
exitCode: 0,
|
|
41
|
+
cwd: runContext.stage.cwd ?? runContext.env.repoRoot,
|
|
42
|
+
timestamp: isoTimestamp()
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
alertTargets: { slack: '#learning-alerts', pagerduty: 'learning-pipeline' },
|
|
46
|
+
pagerDutySeverity: 'none'
|
|
47
|
+
});
|
|
48
|
+
const validation = await runScenarioValidation({
|
|
49
|
+
manifest: scenario.manifest,
|
|
50
|
+
repoRoot: runContext.env.repoRoot,
|
|
51
|
+
runsRoot: runContext.env.runsRoot,
|
|
52
|
+
taskId: runContext.env.taskId,
|
|
53
|
+
runId: runContext.manifest.run_id,
|
|
54
|
+
paths: runContext.paths,
|
|
55
|
+
scenarioPath: scenario.scenarioPath
|
|
56
|
+
});
|
|
57
|
+
await persistManifest(runContext.paths, validation.manifest, runContext.persister, { force: true });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
logger.warn(`[learning] auto-trigger failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { runCommandStage } from '../services/commandRunner.js';
|
|
2
|
+
import { serializeExecEvent } from '../../../../packages/shared/events/serializer.js';
|
|
3
|
+
import { sanitizeToolRunRecord } from '../../../../packages/shared/manifest/writer.js';
|
|
4
|
+
export async function runExecStage(context) {
|
|
5
|
+
let runResultSummary = null;
|
|
6
|
+
let toolRecord = null;
|
|
7
|
+
const hooks = {
|
|
8
|
+
onEvent: (event) => {
|
|
9
|
+
context.execEvents.push(event);
|
|
10
|
+
const serialized = serializeExecEvent(event);
|
|
11
|
+
context.telemetryTasks.push(Promise.resolve(context.telemetrySink.record(serialized)).then(() => undefined));
|
|
12
|
+
if (context.outputMode === 'jsonl' && context.jsonlWriter) {
|
|
13
|
+
context.jsonlWriter.write(serialized);
|
|
14
|
+
}
|
|
15
|
+
else if (context.outputMode === 'interactive') {
|
|
16
|
+
streamInteractive(context.stdout, context.stderr, event);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
onResult: (result) => {
|
|
20
|
+
runResultSummary = {
|
|
21
|
+
correlationId: result.correlationId,
|
|
22
|
+
stdout: result.stdout,
|
|
23
|
+
stderr: result.stderr,
|
|
24
|
+
exitCode: result.exitCode,
|
|
25
|
+
signal: result.signal,
|
|
26
|
+
durationMs: result.durationMs,
|
|
27
|
+
status: result.status,
|
|
28
|
+
sandboxState: result.sandboxState
|
|
29
|
+
};
|
|
30
|
+
toolRecord = sanitizeToolRunRecord(JSON.parse(JSON.stringify(result.record)));
|
|
31
|
+
},
|
|
32
|
+
onError: (error) => {
|
|
33
|
+
toolRecord = sanitizeToolRunRecord(JSON.parse(JSON.stringify(error.record)));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
let commandError = null;
|
|
37
|
+
try {
|
|
38
|
+
await runCommandStage({
|
|
39
|
+
env: context.env,
|
|
40
|
+
paths: context.paths,
|
|
41
|
+
manifest: context.manifest,
|
|
42
|
+
stage: context.stage,
|
|
43
|
+
index: 1
|
|
44
|
+
}, hooks);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
commandError = error;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
summary: runResultSummary,
|
|
51
|
+
toolRecord,
|
|
52
|
+
commandError
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function streamInteractive(stdout, stderr, event) {
|
|
56
|
+
switch (event.type) {
|
|
57
|
+
case 'exec:chunk':
|
|
58
|
+
if (event.payload.stream === 'stdout') {
|
|
59
|
+
stdout.write(event.payload.data);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
stderr.write(event.payload.data);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 'exec:retry':
|
|
66
|
+
stderr.write(`[retry] attempt ${event.attempt} in ${event.payload.delayMs}ms — ${event.payload.errorMessage}\n`);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { relativeToRepo } from '../run/runPaths.js';
|
|
3
|
+
import { writeJsonAtomic } from '../utils/fs.js';
|
|
4
|
+
import { persistManifest } from '../run/manifestPersister.js';
|
|
5
|
+
import { readExecMetadata } from './finalization.js';
|
|
6
|
+
export function createRunSummaryPayload(params) {
|
|
7
|
+
const { env, paths, manifest, runStatus, shellCommand, argv, resultSummary, toolRecord, execEvents, exitCode, signal, notificationTargets, cwd, metrics } = params;
|
|
8
|
+
const stdout = resultSummary?.stdout ?? '';
|
|
9
|
+
const stderr = resultSummary?.stderr ?? '';
|
|
10
|
+
const correlationId = resultSummary?.correlationId ?? execEvents[0]?.correlationId ?? manifest.run_id;
|
|
11
|
+
const attempts = typeof toolRecord?.attemptCount === 'number'
|
|
12
|
+
? toolRecord.attemptCount
|
|
13
|
+
: (toolRecord?.retryCount ?? 0) + 1;
|
|
14
|
+
const execMetadata = readExecMetadata(toolRecord);
|
|
15
|
+
const sessionId = typeof execMetadata?.sessionId === 'string' ? execMetadata.sessionId : 'default';
|
|
16
|
+
const persisted = Boolean(execMetadata?.persisted);
|
|
17
|
+
const metadataSandbox = execMetadata?.sandboxState;
|
|
18
|
+
const sandboxState = resultSummary?.sandboxState ?? toolRecord?.sandboxState ?? metadataSandbox ?? 'sandboxed';
|
|
19
|
+
return {
|
|
20
|
+
status: runStatus === 'succeeded' ? 'succeeded' : 'failed',
|
|
21
|
+
run: {
|
|
22
|
+
id: manifest.run_id,
|
|
23
|
+
taskId: manifest.task_id,
|
|
24
|
+
pipelineId: manifest.pipeline_id,
|
|
25
|
+
manifest: relativeToRepo(env, paths.manifestPath),
|
|
26
|
+
artifactRoot: manifest.artifact_root,
|
|
27
|
+
summary: manifest.summary
|
|
28
|
+
},
|
|
29
|
+
result: {
|
|
30
|
+
exitCode,
|
|
31
|
+
signal,
|
|
32
|
+
durationMs: resultSummary?.durationMs ?? 0,
|
|
33
|
+
status: resultSummary?.status ?? (toolRecord?.status ?? 'failed'),
|
|
34
|
+
sandboxState,
|
|
35
|
+
correlationId,
|
|
36
|
+
attempts
|
|
37
|
+
},
|
|
38
|
+
command: {
|
|
39
|
+
argv,
|
|
40
|
+
shell: shellCommand,
|
|
41
|
+
cwd,
|
|
42
|
+
sessionId,
|
|
43
|
+
persisted
|
|
44
|
+
},
|
|
45
|
+
outputs: {
|
|
46
|
+
stdout,
|
|
47
|
+
stderr
|
|
48
|
+
},
|
|
49
|
+
logs: {
|
|
50
|
+
runner: relativeToRepo(env, paths.logPath),
|
|
51
|
+
command: manifest.commands[0]?.log_path ?? null
|
|
52
|
+
},
|
|
53
|
+
toolRun: toolRecord,
|
|
54
|
+
metrics: metrics ?? undefined,
|
|
55
|
+
notifications: {
|
|
56
|
+
targets: notificationTargets,
|
|
57
|
+
delivered: [],
|
|
58
|
+
failures: []
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function renderRunOutput(context, summaryPayload, summaryEvent) {
|
|
63
|
+
if (context.outputMode === 'jsonl' && context.jsonlWriter) {
|
|
64
|
+
context.jsonlWriter.write(summaryEvent);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (context.outputMode === 'json') {
|
|
68
|
+
const spacing = context.invocation.jsonPretty ? 2 : 0;
|
|
69
|
+
context.stdout.write(`${JSON.stringify(summaryEvent, null, spacing)}\n`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (context.outputMode === 'interactive') {
|
|
73
|
+
context.stdout.write(`\n${summaryPayload.run.id} ${summaryPayload.status.toUpperCase()}\n`);
|
|
74
|
+
context.stdout.write(`Manifest: ${summaryPayload.run.manifest}\n`);
|
|
75
|
+
context.stdout.write(`Log: ${summaryPayload.logs.runner}\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function persistRunOutputs(context, summaryEvent) {
|
|
79
|
+
const runSummaryPath = join(context.paths.runDir, 'run-summary.json');
|
|
80
|
+
await writeJsonAtomic(runSummaryPath, summaryEvent);
|
|
81
|
+
context.manifest.run_summary_path = relativeToRepo(context.env, runSummaryPath);
|
|
82
|
+
await persistManifest(context.paths, context.manifest, context.persister, { force: true });
|
|
83
|
+
}
|
|
84
|
+
export function emitCommandError(context, commandError) {
|
|
85
|
+
if (!commandError) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (context.outputMode === 'jsonl' || context.outputMode === 'json') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
context.stderr.write(`Command execution failed: ${commandError?.message ?? String(commandError)}\n`);
|
|
92
|
+
}
|
|
93
|
+
export function buildExecRunSummary(params) {
|
|
94
|
+
return {
|
|
95
|
+
runId: params.manifest.run_id,
|
|
96
|
+
manifestPath: params.summaryPayload.run.manifest,
|
|
97
|
+
logPath: params.summaryPayload.logs.runner,
|
|
98
|
+
status: params.summaryPayload.status,
|
|
99
|
+
exitCode: params.summaryPayload.result.exitCode,
|
|
100
|
+
signal: params.summaryPayload.result.signal,
|
|
101
|
+
stdout: params.summaryPayload.outputs.stdout,
|
|
102
|
+
stderr: params.summaryPayload.outputs.stderr,
|
|
103
|
+
shellCommand: params.shellCommand,
|
|
104
|
+
argv: params.argv,
|
|
105
|
+
events: params.events,
|
|
106
|
+
summaryEvent: params.summaryEvent,
|
|
107
|
+
toolRun: params.toolRecord ?? null
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function deliverNotifications(context, summaryPayload, summaryEvent) {
|
|
2
|
+
const notificationOutcome = await context.notificationSink.notify(summaryEvent);
|
|
3
|
+
if (summaryPayload.notifications) {
|
|
4
|
+
summaryPayload.notifications.delivered = notificationOutcome.delivered;
|
|
5
|
+
summaryPayload.notifications.failures = notificationOutcome.failures;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function recordSummaryTelemetry(context, summaryEvent) {
|
|
9
|
+
context.telemetryTasks.push(Promise.resolve(context.telemetrySink.recordSummary(summaryEvent)).then(() => undefined));
|
|
10
|
+
}
|
|
11
|
+
export async function flushTelemetry(context) {
|
|
12
|
+
await Promise.allSettled(context.telemetryTasks);
|
|
13
|
+
await context.telemetrySink.flush();
|
|
14
|
+
}
|
|
15
|
+
export async function shutdownSinks(context) {
|
|
16
|
+
await context.telemetrySink.shutdown();
|
|
17
|
+
await context.notificationSink.shutdown();
|
|
18
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { relativeToRepo } from '../run/runPaths.js';
|
|
2
|
+
import { logger } from '../../logger.js';
|
|
3
|
+
import { summarizeTrajectory, optimizeExperience, framesFromToolMetrics } from './experience.js';
|
|
4
|
+
const COST_PER_TOKEN_USD = 0.000002;
|
|
5
|
+
const DEFAULT_EXPERIENCE_WORD_LIMIT = 32;
|
|
6
|
+
export function resolveTfgrpoContext(env = process.env) {
|
|
7
|
+
const epoch = parsePositiveInteger(env.TFGRPO_EPOCH);
|
|
8
|
+
const groupSize = parsePositiveInteger(env.TFGRPO_GROUP_SIZE);
|
|
9
|
+
const groupId = env.TFGRPO_GROUP_ID?.trim() || null;
|
|
10
|
+
return {
|
|
11
|
+
epoch,
|
|
12
|
+
groupId,
|
|
13
|
+
groupSize,
|
|
14
|
+
active: epoch !== null || groupSize !== null || groupId !== null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function resolveExperiencePolicy(env = process.env) {
|
|
18
|
+
const configured = env.TFGRPO_EXPERIENCE_MAX_WORDS;
|
|
19
|
+
const parsed = configured ? Number.parseInt(configured, 10) : NaN;
|
|
20
|
+
const maxSummaryWords = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_EXPERIENCE_WORD_LIMIT;
|
|
21
|
+
return {
|
|
22
|
+
maxSummaryWords,
|
|
23
|
+
rewardFloor: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function createToolMetricSnapshot(toolRecord, summary) {
|
|
27
|
+
if (!toolRecord && !summary) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const stdout = summary?.stdout ?? '';
|
|
31
|
+
const tokens = estimateTokenCount(stdout);
|
|
32
|
+
const latencyMs = summary?.durationMs ?? deriveDurationFromEvents(toolRecord);
|
|
33
|
+
const costUsd = roundCurrency(tokens * COST_PER_TOKEN_USD);
|
|
34
|
+
const attempts = toolRecord?.attemptCount ?? 1;
|
|
35
|
+
const status = toolRecord?.status ?? summary?.status ?? 'failed';
|
|
36
|
+
const sandboxState = summary?.sandboxState ?? toolRecord?.sandboxState ?? 'sandboxed';
|
|
37
|
+
const toolName = toolRecord?.tool ?? 'cli:command';
|
|
38
|
+
return {
|
|
39
|
+
tool: toolName,
|
|
40
|
+
tokens,
|
|
41
|
+
costUsd,
|
|
42
|
+
latencyMs,
|
|
43
|
+
attempts,
|
|
44
|
+
status,
|
|
45
|
+
sandboxState
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function createRunMetricSummary(metrics, context) {
|
|
49
|
+
const toolCalls = metrics.length;
|
|
50
|
+
if (toolCalls === 0 && !context.active) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const tokenTotal = metrics.reduce((sum, metric) => sum + metric.tokens, 0);
|
|
54
|
+
const costUsd = roundCurrency(metrics.reduce((sum, metric) => sum + metric.costUsd, 0));
|
|
55
|
+
const latencyMs = metrics.reduce((sum, metric) => sum + metric.latencyMs, 0);
|
|
56
|
+
return {
|
|
57
|
+
toolCalls,
|
|
58
|
+
tokenTotal,
|
|
59
|
+
costUsd,
|
|
60
|
+
latencyMs,
|
|
61
|
+
perTool: metrics,
|
|
62
|
+
tfgrpo: context.active
|
|
63
|
+
? {
|
|
64
|
+
epoch: context.epoch,
|
|
65
|
+
groupSize: context.groupSize,
|
|
66
|
+
groupId: context.groupId
|
|
67
|
+
}
|
|
68
|
+
: undefined
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function mergeTfgrpoManifest(existing, metrics, context) {
|
|
72
|
+
if (!metrics && !existing && !context.active) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const manifestMetrics = metrics
|
|
76
|
+
? {
|
|
77
|
+
tool_calls: metrics.toolCalls,
|
|
78
|
+
token_total: metrics.tokenTotal,
|
|
79
|
+
cost_usd: metrics.costUsd,
|
|
80
|
+
latency_ms: metrics.latencyMs,
|
|
81
|
+
per_tool: metrics.perTool.map((entry) => ({
|
|
82
|
+
tool: entry.tool,
|
|
83
|
+
tokens: entry.tokens,
|
|
84
|
+
cost_usd: entry.costUsd,
|
|
85
|
+
latency_ms: entry.latencyMs,
|
|
86
|
+
attempts: entry.attempts,
|
|
87
|
+
status: entry.status,
|
|
88
|
+
sandbox_state: entry.sandboxState
|
|
89
|
+
}))
|
|
90
|
+
}
|
|
91
|
+
: existing?.tool_metrics;
|
|
92
|
+
return {
|
|
93
|
+
epoch: context.epoch ?? existing?.epoch ?? null,
|
|
94
|
+
group_id: context.groupId ?? existing?.group_id ?? null,
|
|
95
|
+
group_size: context.groupSize ?? existing?.group_size ?? null,
|
|
96
|
+
tool_metrics: manifestMetrics ?? existing?.tool_metrics,
|
|
97
|
+
experiences: existing?.experiences
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export async function persistExperienceRecords(params) {
|
|
101
|
+
const { runMetrics } = params;
|
|
102
|
+
if (!runMetrics || runMetrics.perTool.length === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const promptPack = params.manifest.prompt_packs?.[0];
|
|
106
|
+
if (!promptPack?.domain || !promptPack.stamp) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const terminalEvent = findTerminalEvent(params.execEvents);
|
|
110
|
+
if (!terminalEvent) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const frames = framesFromToolMetrics(runMetrics.perTool, terminalEvent);
|
|
115
|
+
const trajectory = summarizeTrajectory({
|
|
116
|
+
runId: params.manifest.run_id,
|
|
117
|
+
taskId: params.manifest.task_id,
|
|
118
|
+
epoch: params.tfgrpoContext.epoch,
|
|
119
|
+
groupId: params.tfgrpoContext.groupId,
|
|
120
|
+
domain: promptPack.domain,
|
|
121
|
+
stampSignature: promptPack.stamp,
|
|
122
|
+
frames,
|
|
123
|
+
baseSummary: params.manifest.summary ?? undefined
|
|
124
|
+
});
|
|
125
|
+
const optimized = optimizeExperience(trajectory, params.policy);
|
|
126
|
+
const manifestPath = relativeToRepo(params.env, params.paths.manifestPath);
|
|
127
|
+
const written = await params.store.recordBatch([optimized], manifestPath);
|
|
128
|
+
if (written.length > 0) {
|
|
129
|
+
const existing = params.manifest.tfgrpo?.experiences ?? {
|
|
130
|
+
ids: [],
|
|
131
|
+
written: 0,
|
|
132
|
+
manifest_path: manifestPath
|
|
133
|
+
};
|
|
134
|
+
const summary = {
|
|
135
|
+
ids: [...existing.ids, ...written.map((record) => record.id)],
|
|
136
|
+
written: existing.written + written.length,
|
|
137
|
+
manifest_path: manifestPath
|
|
138
|
+
};
|
|
139
|
+
params.manifest.tfgrpo = {
|
|
140
|
+
...(params.manifest.tfgrpo ?? {
|
|
141
|
+
epoch: params.tfgrpoContext.epoch,
|
|
142
|
+
group_id: params.tfgrpoContext.groupId,
|
|
143
|
+
group_size: params.tfgrpoContext.groupSize
|
|
144
|
+
}),
|
|
145
|
+
experiences: summary
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return written;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
logger.warn(`Failed to persist TF-GRPO experience: ${String(error)}`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function parsePositiveInteger(value) {
|
|
156
|
+
if (!value) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const parsed = Number.parseInt(value, 10);
|
|
160
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
function deriveDurationFromEvents(record) {
|
|
166
|
+
const events = record?.events;
|
|
167
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
const first = events.find((event) => event.type === 'exec:begin');
|
|
171
|
+
const last = [...events].reverse().find((event) => event.type === 'exec:end');
|
|
172
|
+
if (!first || !last) {
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
const startedAt = Date.parse(first.timestamp);
|
|
176
|
+
const completedAt = Date.parse(last.timestamp);
|
|
177
|
+
if (Number.isNaN(startedAt) || Number.isNaN(completedAt) || completedAt < startedAt) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return completedAt - startedAt;
|
|
181
|
+
}
|
|
182
|
+
function estimateTokenCount(output) {
|
|
183
|
+
const trimmed = output.trim();
|
|
184
|
+
if (!trimmed) {
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
return trimmed.split(/\s+/u).filter(Boolean).length;
|
|
188
|
+
}
|
|
189
|
+
function roundCurrency(value) {
|
|
190
|
+
return Math.round(value * 1_000_000) / 1_000_000;
|
|
191
|
+
}
|
|
192
|
+
function findTerminalEvent(events) {
|
|
193
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
194
|
+
const candidate = events[index];
|
|
195
|
+
if (candidate?.type === 'exec:end') {
|
|
196
|
+
return candidate;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return events.length > 0 ? events[events.length - 1] : null;
|
|
200
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { persistManifest } from '../run/manifestPersister.js';
|
|
2
|
+
import { createRunMetricSummary, createToolMetricSnapshot, mergeTfgrpoManifest, persistExperienceRecords, resolveExperiencePolicy } from './tfgrpo.js';
|
|
3
|
+
export async function handleTfgrpoArtifacts(context, finalization, tfgrpoContext) {
|
|
4
|
+
const toolMetric = createToolMetricSnapshot(finalization.toolRecord, finalization.summarySnapshot);
|
|
5
|
+
const runMetricSummary = createRunMetricSummary(toolMetric ? [toolMetric] : [], tfgrpoContext);
|
|
6
|
+
context.manifest.tfgrpo = mergeTfgrpoManifest(context.manifest.tfgrpo, runMetricSummary, tfgrpoContext);
|
|
7
|
+
await persistExperienceRecords({
|
|
8
|
+
store: context.experienceStore,
|
|
9
|
+
manifest: context.manifest,
|
|
10
|
+
env: context.env,
|
|
11
|
+
paths: context.paths,
|
|
12
|
+
tfgrpoContext,
|
|
13
|
+
runMetrics: runMetricSummary,
|
|
14
|
+
execEvents: context.execEvents,
|
|
15
|
+
policy: resolveExperiencePolicy()
|
|
16
|
+
});
|
|
17
|
+
await persistManifest(context.paths, context.manifest, context.persister, { force: true });
|
|
18
|
+
return runMetricSummary;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative } from 'node:path';
|
|
4
|
+
import { findPackageRoot } from './utils/packageInfo.js';
|
|
5
|
+
export async function initCodexTemplates(options) {
|
|
6
|
+
const root = findPackageRoot();
|
|
7
|
+
const templateRoot = join(root, 'templates', options.template);
|
|
8
|
+
const written = [];
|
|
9
|
+
const skipped = [];
|
|
10
|
+
await assertDirectory(templateRoot);
|
|
11
|
+
await copyTemplateDir(templateRoot, options.cwd, {
|
|
12
|
+
force: options.force,
|
|
13
|
+
written,
|
|
14
|
+
skipped
|
|
15
|
+
});
|
|
16
|
+
return { written, skipped, templateRoot };
|
|
17
|
+
}
|
|
18
|
+
async function assertDirectory(path) {
|
|
19
|
+
const info = await stat(path).catch(() => null);
|
|
20
|
+
if (!info || !info.isDirectory()) {
|
|
21
|
+
throw new Error(`Template directory not found: ${path}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function copyTemplateDir(sourceDir, targetDir, options) {
|
|
25
|
+
await mkdir(targetDir, { recursive: true });
|
|
26
|
+
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
29
|
+
const targetPath = join(targetDir, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
await copyTemplateDir(sourcePath, targetPath, options);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!entry.isFile()) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(targetPath) && !options.force) {
|
|
38
|
+
options.skipped.push(targetPath);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
42
|
+
await copyFile(sourcePath, targetPath);
|
|
43
|
+
options.written.push(targetPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function formatInitSummary(result, cwd) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
if (result.written.length > 0) {
|
|
49
|
+
lines.push('Written:');
|
|
50
|
+
for (const filePath of result.written) {
|
|
51
|
+
lines.push(` - ${relative(cwd, filePath)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (result.skipped.length > 0) {
|
|
55
|
+
lines.push('Skipped (already exists):');
|
|
56
|
+
for (const filePath of result.skipped) {
|
|
57
|
+
lines.push(` - ${relative(cwd, filePath)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (lines.length === 0) {
|
|
61
|
+
lines.push('No files written.');
|
|
62
|
+
}
|
|
63
|
+
return lines;
|
|
64
|
+
}
|